@dawntech/dispatcher 0.0.1
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.
- package/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/README.md +3 -0
- package/dist/chunk-64334IMD.mjs +2 -0
- package/dist/chunk-64334IMD.mjs.map +1 -0
- package/dist/chunk-6IAFH374.mjs +2 -0
- package/dist/chunk-6IAFH374.mjs.map +1 -0
- package/dist/chunk-HYW2O7ZX.mjs +2 -0
- package/dist/chunk-HYW2O7ZX.mjs.map +1 -0
- package/dist/chunk-OR34YAY3.mjs +2 -0
- package/dist/chunk-OR34YAY3.mjs.map +1 -0
- package/dist/chunk-OXXLVJVC.mjs +2 -0
- package/dist/chunk-OXXLVJVC.mjs.map +1 -0
- package/dist/chunk-QA6PFVGP.mjs +2 -0
- package/dist/chunk-QA6PFVGP.mjs.map +1 -0
- package/dist/chunk-SLZLYBPB.mjs +2 -0
- package/dist/chunk-SLZLYBPB.mjs.map +1 -0
- package/dist/chunk-UFUGEIFL.mjs +2 -0
- package/dist/chunk-UFUGEIFL.mjs.map +1 -0
- package/dist/chunk-XLRB2LAD.mjs +2 -0
- package/dist/chunk-XLRB2LAD.mjs.map +1 -0
- package/dist/chunk-YDWZJWCU.mjs +2 -0
- package/dist/chunk-YDWZJWCU.mjs.map +1 -0
- package/dist/core/Blip.d.mts +84 -0
- package/dist/core/Blip.js +2 -0
- package/dist/core/Blip.js.map +1 -0
- package/dist/core/Blip.mjs +2 -0
- package/dist/core/Blip.mjs.map +1 -0
- package/dist/core/Dispatcher.d.mts +52 -0
- package/dist/core/Dispatcher.js +2 -0
- package/dist/core/Dispatcher.js.map +1 -0
- package/dist/core/Dispatcher.mjs +2 -0
- package/dist/core/Dispatcher.mjs.map +1 -0
- package/dist/core/DispatcherDescriptor.d.mts +23 -0
- package/dist/core/DispatcherDescriptor.js +2 -0
- package/dist/core/DispatcherDescriptor.js.map +1 -0
- package/dist/core/DispatcherDescriptor.mjs +2 -0
- package/dist/core/DispatcherDescriptor.mjs.map +1 -0
- package/dist/core/DispatcherMonitor.d.mts +52 -0
- package/dist/core/DispatcherMonitor.js +2 -0
- package/dist/core/DispatcherMonitor.js.map +1 -0
- package/dist/core/DispatcherMonitor.mjs +2 -0
- package/dist/core/DispatcherMonitor.mjs.map +1 -0
- package/dist/core/DispatcherQuery.d.mts +14 -0
- package/dist/core/DispatcherQuery.js +2 -0
- package/dist/core/DispatcherQuery.js.map +1 -0
- package/dist/core/DispatcherQuery.mjs +2 -0
- package/dist/core/DispatcherQuery.mjs.map +1 -0
- package/dist/core/DispatcherRepository.d.mts +47 -0
- package/dist/core/DispatcherRepository.js +2 -0
- package/dist/core/DispatcherRepository.js.map +1 -0
- package/dist/core/DispatcherRepository.mjs +2 -0
- package/dist/core/DispatcherRepository.mjs.map +1 -0
- package/dist/core/DispatcherStateMachine.d.mts +20 -0
- package/dist/core/DispatcherStateMachine.js +2 -0
- package/dist/core/DispatcherStateMachine.js.map +1 -0
- package/dist/core/DispatcherStateMachine.mjs +2 -0
- package/dist/core/DispatcherStateMachine.mjs.map +1 -0
- package/dist/errors/index.d.mts +54 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/index.mjs +2 -0
- package/dist/errors/index.mjs.map +1 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +2 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types/blip.d.mts +127 -0
- package/dist/types/blip.js +2 -0
- package/dist/types/blip.js.map +1 -0
- package/dist/types/blip.mjs +2 -0
- package/dist/types/blip.mjs.map +1 -0
- package/dist/types/index.d.mts +155 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +2 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utils/logger.d.mts +73 -0
- package/dist/utils/logger.js +2 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger.mjs +2 -0
- package/dist/utils/logger.mjs.map +1 -0
- package/jest.config.js +48 -0
- package/package.json +81 -0
- package/tsconfig.json +22 -0
- package/tsconfig.test.json +10 -0
- package/tsup.config.js +10 -0
- package/types/core/Blip.d.ts.map +1 -0
- package/types/core/Dispatcher.d.ts.map +1 -0
- package/types/core/DispatcherDescriptor.d.ts.map +1 -0
- package/types/core/DispatcherMonitor.d.ts.map +1 -0
- package/types/core/DispatcherQuery.d.ts.map +1 -0
- package/types/core/DispatcherRepository.d.ts.map +1 -0
- package/types/core/DispatcherStateMachine.d.ts.map +1 -0
- package/types/errors/index.d.ts.map +1 -0
- package/types/index.d.ts.map +1 -0
- package/types/server.d.ts.map +1 -0
- package/types/types/blip.d.ts.map +1 -0
- package/types/types/index.d.ts.map +1 -0
- package/types/utils/logger.d.ts.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/Blip.ts","../../src/types/blip.ts","../../src/utils/logger.ts"],"sourcesContent":["/**\n * Blip - Low-level client wrapper for Blip Platform API interactions\n *\n * Responsibilities:\n * - Send messages to contacts via Blip HTTP API\n * - Merge contact metadata (custom fields)\n * - Send analytics events to Blip\n * - Manage bot conversation state transitions\n * - Check message delivery status\n */\n\nimport axios, { AxiosInstance } from 'axios';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Vnd } from '../types/blip.js';\nimport { MessageData, DispatchState } from '../types/index.js';\nimport { getLogger } from '../utils/logger.js';\n\nconst logger = getLogger('Blip');\n\nexport class Blip {\n private client: AxiosInstance;\n\n /**\n * Create a new Blip instance\n * @param contract - Blip contract ID (without https:// prefix)\n * @param key - Blip API key\n * @param timeout - Request timeout in milliseconds (default: 30000)\n */\n constructor(contract: string, key: string, timeout: number = 30000) {\n const botUrl = `https://${contract}.http.msging.net`;\n\n this.client = axios.create({\n baseURL: botUrl,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: key,\n },\n });\n\n // Response interceptor for error handling\n this.client.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response) {\n const reason = error.response.data?.reason || {\n code: error.response.status,\n description: error.response.statusText || 'Unknown error',\n };\n throw new BlipError(reason.description, reason.code);\n } else if (error.request) {\n throw new BlipError('No response from server', 0);\n } else {\n throw new BlipError(error.message, 0);\n }\n }\n );\n }\n\n /**\n * Send a command to the Blip API\n */\n private async postCommand<T = any>(\n request: Vnd.Lime.Transport.Payload.Request\n ): Promise<Vnd.Lime.Transport.Payload.Response<T>> {\n const payload = {\n ...request,\n id: request.id || uuidv4(),\n };\n\n logger.debug('[postCommand] payload', payload);\n\n const response = await this.client.post<Vnd.Lime.Transport.Payload.Response<T>>(\n '/commands',\n payload\n );\n\n const result = response.data;\n\n if (result.status !== Vnd.Lime.Status.SUCCESS) {\n logger.error('[postCommand] failed', {\n method: payload.method,\n uri: payload.uri,\n status: result.status,\n reason: result.reason,\n });\n throw new BlipError(result.reason?.description || 'Command failed', result.reason?.code || 0);\n }\n\n logger.debug('[postCommand] succeeded', payload.uri);\n return result;\n }\n\n /**\n * Send a message through the Blip API\n */\n private async postMessage<T = any>(\n message: Vnd.Lime.Message<T>\n ): Promise<Vnd.Lime.Transport.Payload.Response> {\n const payload = {\n ...message,\n id: message.id || uuidv4(),\n };\n logger.info('[postMessage] payload', payload);\n\n const response = await this.client.post<Vnd.Lime.Transport.Payload.Response>(\n '/messages',\n payload\n );\n\n return response.data;\n }\n\n /**\n * Merge contact metadata (custom fields)\n * @param contactId - Contact identifier\n * @param data - Key-value pairs to merge into contact\n */\n async mergeContact(contactId: string, data: Omit<Vnd.Iris.Contact, 'identity'>): Promise<void> {\n logger.info('[mergeContact] called with', { contactId, data });\n const contact = {\n ...data,\n identity: contactId,\n };\n\n const request: Vnd.Lime.Transport.Payload.Request = {\n method: Vnd.Lime.Method.MERGE,\n uri: '/contacts',\n type: 'application/vnd.lime.contact+json',\n resource: contact,\n };\n\n await this.postCommand(request);\n }\n\n /**\n * Send a message to a contact\n * @param contactId - Contact identifier\n * @param message - Message data to send\n * @param id - Optional message ID (if not provided, a new one is generated)\n * @returns message ID\n */\n async sendMessage(contactId: string, message: MessageData, id?: string): Promise<string> {\n logger.info('[sendMessage] called with', { contactId, message, id });\n\n const messageId = id || uuidv4();\n\n const limeMessage: Vnd.Lime.Message = {\n id: messageId,\n to: contactId,\n type: message.type,\n content: message.content,\n };\n\n await this.postMessage(limeMessage);\n\n logger.info('[sendMessage] sent', { contactId, messageId });\n return messageId;\n }\n\n /**\n * Get raw dispatch state from Blip notifications\n * @param messageId - Message ID to check\n * @param contactId - Contact identifier to filter notifications\n * @returns DispatchState or null if not found\n */\n async getDispatchState(messageId: string, contactId: string): Promise<DispatchState | null> {\n logger.info('[getDispatchState] called with', { messageId, contactId });\n\n const request: Vnd.Lime.Transport.Payload.Request = {\n method: Vnd.Lime.Method.GET,\n uri: `/notifications?id=${messageId}`,\n to: 'postmaster@msging.net',\n };\n\n try {\n const response = await this.postCommand<{\n total: number;\n itemType: string;\n items: Array<{\n event: string;\n id: string;\n from: string;\n to: string;\n reason?: {\n code: number;\n description: string;\n };\n metadata?: {\n '#envelope.storageDate'?: string;\n [key: string]: any;\n };\n }>;\n }>(request);\n\n if (!response.resource || !response.resource.items || response.resource.items.length === 0) {\n logger.debug('[getDispatchState] no notifications found', { messageId, contactId });\n return null;\n }\n\n const statePriority: Record<string, number> = {\n [DispatchState.FAILED]: 4,\n [DispatchState.CONSUMED]: 3,\n [DispatchState.RECEIVED]: 2,\n [DispatchState.ACCEPTED]: 1,\n [DispatchState.DISPATCHED]: 0,\n };\n\n let bestState: DispatchState | null = null;\n let maxPriority = -1;\n\n for (const item of response.resource.items) {\n const state = item.event as DispatchState;\n const priority = statePriority[state] ?? -1;\n\n if (priority > maxPriority) {\n maxPriority = priority;\n bestState = state;\n }\n }\n\n logger.info('[getDispatchState] state retrieved', {\n messageId,\n contactId,\n state: bestState,\n notificationsCount: response.resource.items.length,\n });\n\n return bestState;\n } catch (error) {\n if (error instanceof BlipError && error.code === 67) {\n logger.debug('[getDispatchState] resource not found', { messageId, contactId });\n return null;\n }\n\n logger.error('[getDispatchState] failed', {\n messageId,\n contactId,\n error,\n });\n throw error;\n }\n }\n\n /**\n * Get the first message received after a specific message ID\n * @param contactId - Contact identifier\n * @param messageId - Message ID to use as anchor\n * @returns ThreadMessage or null if no message found\n */\n async getMessageAfter(\n contactId: string,\n messageId: string\n ): Promise<Vnd.Iris.ThreadMessage | null> {\n logger.info('[getMessageAfter] called with', { contactId, messageId });\n\n let currentMessageId = messageId;\n let attempts = 0;\n const maxAttempts = 10;\n\n while (attempts < maxAttempts) {\n const request: Vnd.Lime.Transport.Payload.Request = {\n method: Vnd.Lime.Method.GET,\n uri: `/threads/${contactId}?$skip=0&$take=1&$order=asc&messageId=${currentMessageId}`,\n to: 'postmaster@msging.net',\n };\n\n try {\n const response = await this.postCommand<{\n total: number;\n itemType: string;\n items: Vnd.Iris.ThreadMessage[];\n }>(request);\n\n if (\n !response.resource ||\n !response.resource.items ||\n response.resource.items.length === 0\n ) {\n logger.debug('[getMessageAfter] no message found after', {\n contactId,\n messageId: currentMessageId,\n });\n return null;\n }\n\n const nextMessage = response.resource.items[0];\n\n if (nextMessage.direction === 'received') {\n logger.info('[getMessageAfter] found received message', {\n contactId,\n messageId: currentMessageId,\n nextMessageId: nextMessage.id,\n });\n return nextMessage;\n }\n\n // If 'sent', treat this message as the new anchor and try again\n logger.debug('[getMessageAfter] skipping sent message', {\n contactId,\n messageId: nextMessage.id,\n });\n currentMessageId = nextMessage.id;\n attempts++;\n } catch (error) {\n if (error instanceof BlipError && error.code === 67) {\n logger.debug('[getMessageAfter] resource not found', {\n contactId,\n messageId: currentMessageId,\n });\n return null;\n }\n\n logger.error('[getMessageAfter] failed', {\n contactId,\n messageId: currentMessageId,\n error,\n });\n throw error;\n }\n }\n\n logger.warn('[getMessageAfter] max traversal attempts reached', {\n contactId,\n startMessageId: messageId,\n });\n return null;\n }\n\n /**\n * Send an analytics event to Blip\n * @param contactId - Contact identifier\n * @param category - Event category\n * @param action - Event action\n * @param extras - Additional event data\n */\n async sendEvent(\n contactId: string,\n category: string,\n action: string,\n extras: Record<string, string>\n ): Promise<void> {\n logger.info('[sendEvent] called with', { contactId, category, action, extras });\n const request: Vnd.Lime.Transport.Payload.Request = {\n to: 'postmaster@analytics.msging.net',\n method: Vnd.Lime.Method.SET,\n type: 'application/vnd.iris.eventTrack+json',\n uri: '/event-track',\n resource: {\n category,\n action,\n contact: {\n identity: contactId,\n },\n extras,\n },\n };\n\n await this.postCommand(request);\n }\n\n /**\n * Set the bot conversation state for a contact\n * @param contactId - Contact identifier\n * @param botId - Bot identifier (short name)\n * @param stateId - State identifier (default: \"onboarding\")\n */\n async setState(contactId: string, botId: string, stateId: string = 'onboarding'): Promise<void> {\n logger.info('[setState] called with', { contactId, botId, stateId });\n // Get flow ID for the bot\n const flowIdRequest: Vnd.Lime.Transport.Payload.Request = {\n uri: `/flow-id?shortName=${botId}`,\n to: 'postmaster@builder.msging.net',\n method: Vnd.Lime.Method.GET,\n };\n\n const flowIdResponse = await this.postCommand<string>(flowIdRequest);\n if (!flowIdResponse.resource) {\n logger.error('[setState] flow ID not found', { botId });\n throw new BlipError(`Flow ID not found for bot: ${botId}`, 404);\n }\n const flowId = flowIdResponse.resource;\n\n // Change contact bot state\n const stateRequest: Vnd.Lime.Transport.Payload.Request<string> = {\n method: Vnd.Lime.Method.SET,\n uri: `/contexts/${contactId}/stateid@${flowId}`,\n resource: stateId,\n type: 'text/plain',\n };\n\n await this.postCommand(stateRequest);\n\n // Change contact bot\n const botRequest: Vnd.Lime.Transport.Payload.Request<string> = {\n method: Vnd.Lime.Method.SET,\n uri: `/contexts/${contactId}/master-state`,\n resource: `${botId}@msging.net`,\n type: 'text/plain',\n };\n\n await this.postCommand(botRequest);\n }\n}\n\n/**\n * Custom error class for Blip errors\n */\nexport class BlipError extends Error {\n public code: number;\n\n constructor(message: string, code: number) {\n super(message);\n this.name = 'BlipError';\n this.code = code;\n Object.setPrototypeOf(this, BlipError.prototype);\n }\n}\n\nexport default Blip;\n","/**\n * Type definitions for Blip Platform API\n * Based on the LIME protocol and Blip extensions\n */\n\nexport namespace Vnd {\n export namespace Lime {\n export enum Method {\n GET = 'get',\n SET = 'set',\n DELETE = 'delete',\n OBSERVE = 'observe',\n SUBSCRIBE = 'subscribe',\n MERGE = 'merge',\n }\n\n export enum Status {\n SUCCESS = 'success',\n FAILURE = 'failure',\n }\n\n export namespace Transport {\n export namespace Payload {\n export interface Request<T = any> {\n id?: string;\n to?: string;\n method: Method;\n uri: string;\n type?: string;\n resource?: T;\n }\n\n export interface Response<T = any> {\n id?: string;\n from?: string;\n to?: string;\n method?: Method;\n status: Status;\n type?: string;\n resource?: T;\n reason?: {\n code: number;\n description: string;\n };\n }\n }\n }\n\n export interface Message<T = any> {\n id?: string;\n to: string;\n from?: string;\n type: string;\n content: T;\n }\n\n export namespace Command {\n export interface Resource<T = any> {\n [key: string]: T;\n }\n }\n }\n\n export namespace Iris {\n export interface ThreadMessage {\n id: string;\n direction: 'sent' | 'received';\n type: string;\n content: any;\n date: string;\n status?: string;\n [key: string]: any;\n }\n\n export interface Contact {\n identity: string;\n name?: string;\n email?: string;\n phoneNumber?: string;\n taxDocument?: string;\n gender?: string;\n city?: string;\n extras?: Record<string, string>;\n [key: string]: any;\n }\n\n export interface EventTrack {\n category: string;\n action: string;\n contact?: {\n identity: string;\n };\n extras?: any;\n count?: number;\n }\n\n export interface Ticket {\n id: string;\n sequentialId: number;\n ownerIdentity: string;\n customerIdentity: string;\n providerIdentity: string;\n status: string;\n storageDate: string;\n externalId?: string;\n rating?: number;\n team?: string;\n agentIdentity?: string;\n closedDate?: string;\n transfers?: number;\n [key: string]: any;\n }\n\n export namespace ActiveCampaign {\n export interface AudienceSummary {\n total: number;\n sent: number;\n failed: number;\n scheduled: number;\n canceled: number;\n }\n }\n }\n}\n\nexport interface BlipConfig {\n botUrl: string;\n botKey: string;\n timeout?: number;\n maxRetries?: number;\n}\n\nexport interface DateInterval {\n from?: Date;\n to: Date;\n}\n\nexport type PartiallyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\nexport type PartiallyRequiredStrict<T, K extends keyof T> = T & Required<Pick<T, K>>;\n","/**\n * Logger utility using the 'debug' package.\n *\n * This module provides a configurable logger based on the 'debug' package.\n * By default, all logging is disabled. To enable logging, set the DEBUG\n * environment variable.\n *\n * The DEBUG variable can be a single namespace or a comma-separated list of\n * namespaces. Wildcards are supported.\n *\n * For example:\n * - `DEBUG=blip-api` enables all logs for the 'blip-api' scope.\n * - `DEBUG=blip-api:*` enables all sub-scopes under 'blip-api'.\n * - `DEBUG=*` enables all logs.\n *\n * The logger exposes different levels (debug, info, warn, error), which are\n * different namespaces.\n *\n * Example usage:\n * ```\n * import { getLogger } from './logger';\n *\n * const logger = getLogger('my-feature');\n *\n * logger.info('This is an info message');\n * logger.error('This is an error');\n * ```\n *\n * To see the output, run your application with:\n * `DEBUG=my-feature:* node your-app.js`\n */\n\nimport debug from 'debug';\n\n// Define the available log levels\nexport const LogLevels = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\nexport type LogLevel = keyof typeof LogLevels;\n\ntype LogFunction = (...args: any[]) => void;\n\nexport interface Logger {\n debug: LogFunction;\n info: LogFunction;\n warn: LogFunction;\n error: LogFunction;\n}\n\nconst loggers = new Map<string, Logger>();\n\nfunction serializeArg(arg: any): any {\n if (arg === null || arg === undefined) return arg;\n if (typeof arg === 'object') {\n try {\n return JSON.stringify(arg, null, 2);\n } catch (e) {\n return arg;\n }\n }\n return arg;\n}\n\nfunction createLogFunction(debugInstance: debug.Debugger): LogFunction {\n return (...args: any[]) => {\n const serialized = args.map(serializeArg);\n (debugInstance as any)(...serialized);\n };\n}\n\n/**\n * Creates a logger with a specific scope.\n *\n * @param scope - The scope for the logger (e.g., 'blip-api', 'dispatcher').\n * @returns A logger object with methods for each log level.\n */\nexport function getLogger(scope: string): Logger {\n if (!loggers.has(scope)) {\n const debugInstance = debug(`${scope}:${LogLevels.debug}`);\n const infoInstance = debug(`${scope}:${LogLevels.info}`);\n const warnInstance = debug(`${scope}:${LogLevels.warn}`);\n const errorInstance = debug(`${scope}:${LogLevels.error}`);\n\n errorInstance.log = console.error.bind(console);\n\n const logger: Logger = {\n debug: createLogFunction(debugInstance),\n info: createLogFunction(infoInstance),\n warn: createLogFunction(warnInstance),\n error: createLogFunction(errorInstance),\n };\n\n loggers.set(scope, logger);\n }\n return loggers.get(scope)!;\n}\n\n/**\n * Enables logging for a specific scope and level.\n * This is a convenience function to programmatically enable logging,\n * equivalent to setting the DEBUG environment variable.\n *\n * @param scope - The scope to enable (e.g., 'blip-api').\n * @param level - The minimum log level to enable (e.g., 'info').\n */\nexport function enableLogger(namespaces: string): void {\n const currentDebug = debug.disable();\n debug.enable(`${currentDebug},${namespaces}`);\n}\n\n/**\n * Disables all logging.\n */\nexport function disableLogger(): void {\n debug.disable();\n}\n\n/**\n * Get all registered logger scopes.\n *\n * @returns An array of all registered scopes.\n */\nexport function getAllScopes(): string[] {\n return Array.from(loggers.keys());\n}\n"],"mappings":"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","names":["Blip_exports","__export","Blip","BlipError","Blip_default","__toCommonJS","import_axios","import_uuid","Vnd","Lime","Method","Status","import_debug","LogLevels","loggers","serializeArg","arg","createLogFunction","debugInstance","args","serialized","getLogger","scope","debug","infoInstance","warnInstance","errorInstance","logger","logger","getLogger","Blip","contract","key","timeout","botUrl","axios","response","error","reason","BlipError","request","payload","uuidv4","result","Vnd","message","contactId","data","contact","id","messageId","limeMessage","statePriority","bestState","maxPriority","item","state","priority","currentMessageId","attempts","maxAttempts","nextMessage","category","action","extras","botId","stateId","flowIdRequest","flowIdResponse","flowId","stateRequest","botRequest","_BlipError","code","Blip_default"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DispatcherDescriptor } from './DispatcherDescriptor.mjs';
|
|
2
|
+
import { DispatcherRepository } from './DispatcherRepository.mjs';
|
|
3
|
+
import { Blip } from './Blip.mjs';
|
|
4
|
+
import { ConnectionConfig, DispatcherOptions, CallbackEvent, Message, DispatcherMetrics, MessagePayload, DispatchMessageOptions } from '../types/index.mjs';
|
|
5
|
+
import { DispatcherQuery } from './DispatcherQuery.mjs';
|
|
6
|
+
import 'ioredis';
|
|
7
|
+
import '../types/blip.mjs';
|
|
8
|
+
|
|
9
|
+
declare class Dispatcher {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
private repository;
|
|
12
|
+
private stateMachine;
|
|
13
|
+
private api;
|
|
14
|
+
private callbacks;
|
|
15
|
+
private descriptors;
|
|
16
|
+
private queue;
|
|
17
|
+
private worker;
|
|
18
|
+
private redis;
|
|
19
|
+
private queueName;
|
|
20
|
+
private maxRetries;
|
|
21
|
+
private retryIntervals;
|
|
22
|
+
private timeouts;
|
|
23
|
+
private retention;
|
|
24
|
+
private timeoutTimer;
|
|
25
|
+
private pollingIntervals;
|
|
26
|
+
private isRunning;
|
|
27
|
+
private setupCompleted;
|
|
28
|
+
readonly query: DispatcherQuery;
|
|
29
|
+
constructor(id: string, repository: DispatcherRepository, connection: ConnectionConfig, options?: DispatcherOptions);
|
|
30
|
+
setup(): Promise<void>;
|
|
31
|
+
teardown(): Promise<void>;
|
|
32
|
+
on(event: CallbackEvent, callback: (message: Message, api: Blip, dispatcherId: string) => void): this;
|
|
33
|
+
getMetrics(): Promise<DispatcherMetrics>;
|
|
34
|
+
private emit;
|
|
35
|
+
send(descriptor: DispatcherDescriptor, contactId: string, payload: MessagePayload, options?: DispatchMessageOptions): Promise<Message>;
|
|
36
|
+
cancel(messageId: string): Promise<boolean>;
|
|
37
|
+
private processJob;
|
|
38
|
+
private handleSendJob;
|
|
39
|
+
private handlePostSendOperations;
|
|
40
|
+
private handleCheckJob;
|
|
41
|
+
private checkAndHandleTimeout;
|
|
42
|
+
private handleTimeout;
|
|
43
|
+
private startTimeoutMonitor;
|
|
44
|
+
private rescheduleCheck;
|
|
45
|
+
private handleDispatchFailure;
|
|
46
|
+
private calculateScheduledTime;
|
|
47
|
+
private isWithinShifts;
|
|
48
|
+
private findNextShiftTime;
|
|
49
|
+
private getStatusRank;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Dispatcher };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var U=Object.create;var v=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var K=(c,t)=>{for(var e in t)v(c,e,{get:t[e],enumerable:!0})},k=(c,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of G(t))!J.call(c,s)&&s!==e&&v(c,s,{get:()=>t[s],enumerable:!(i=V(t,s))||i.enumerable});return c};var O=(c,t,e)=>(e=c!=null?U(H(c)):{},k(t||!c||!c.__esModule?v(e,"default",{value:c,enumerable:!0}):e,c)),j=c=>k(v({},"__esModule",{value:!0}),c);var Y={};K(Y,{Dispatcher:()=>P});module.exports=j(Y);var $=require("uuid"),x=require("bullmq");var M=(r=>(r.INIT="INIT",r.DISPATCHED="DISPATCHED",r.SCHEDULED="SCHEDULED",r.QUEUED="QUEUED",r.FINAL="FINAL",r))(M||{}),b=(n=>(n.INIT="INIT",n.PENDING="PENDING",n.SENDING="SENDING",n.DELIVERED="DELIVERED",n.READ="READ",n.REPLIED="REPLIED",n.FAILED="FAILED",n.CANCELED="CANCELED",n))(b||{});var w=O(require("debug")),A={debug:"debug",info:"info",warn:"warn",error:"error"},N=new Map;function Q(c){if(c==null)return c;if(typeof c=="object")try{return JSON.stringify(c,null,2)}catch{return c}return c}function S(c){return(...t)=>{let e=t.map(Q);c(...e)}}function I(c){if(!N.has(c)){let t=(0,w.default)(`${c}:${A.debug}`),e=(0,w.default)(`${c}:${A.info}`),i=(0,w.default)(`${c}:${A.warn}`),s=(0,w.default)(`${c}:${A.error}`);s.log=console.error.bind(console);let r={debug:S(t),info:S(e),warn:S(i),error:S(s)};N.set(c,r)}return N.get(c)}var F=I("StateMachine"),T=class{constructor(t,e,i){this.id=t;this.repository=e;this.emit=i}async transition(t,e,i,s){let r=t.state,a=t.status;r==="FINAL"&&e!=="FINAL"&&F.warn(`[transition] Attempting to move from FINAL back to ${e}`,{messageId:t.messageId}),t.state=e,t.status=i,s&&Object.assign(t,s);let o=new Date().toISOString();return i==="SENDING"&&!t.acceptedAt&&a!=="SENDING"&&(t.acceptedAt=o),i==="DELIVERED"&&!t.deliveredAt&&(t.deliveredAt=o),i==="READ"&&!t.readAt&&(t.readAt=o),i==="REPLIED"&&!t.repliedAt&&(t.repliedAt=o,t.readAt||(t.readAt=o)),i==="FAILED"&&!t.failedAt&&(t.failedAt=o),e==="DISPATCHED"&&!t.sentAt&&"PENDING",await this.repository.upsertMessage(t),a!==i&&(i==="REPLIED"&&a!=="READ"&&this.emit("read",t),this.emitStatusEvent(t,i)),e==="SCHEDULED"&&r!=="SCHEDULED"&&this.emit("scheduled",t),F.debug(`[transition] ${t.messageId} : ${r}/${a} -> ${e}/${i}`),t}emitStatusEvent(t,e){switch(e){case"SENDING":this.emit("sending",t);break;case"DELIVERED":this.emit("delivered",t);break;case"READ":this.emit("read",t);break;case"REPLIED":this.emit("replied",t);break;case"FAILED":this.emit("failed",t);break;case"CANCELED":this.emit("canceled",t);break}}};var q=O(require("axios")),C=require("uuid");var m;(t=>{let c;(s=>{let e;(u=>(u.GET="get",u.SET="set",u.DELETE="delete",u.OBSERVE="observe",u.SUBSCRIBE="subscribe",u.MERGE="merge"))(e=s.Method||(s.Method={}));let i;(o=>(o.SUCCESS="success",o.FAILURE="failure"))(i=s.Status||(s.Status={}))})(c=t.Lime||(t.Lime={}))})(m||(m={}));var p=I("Blip"),L=class{constructor(t,e,i=3e4){let s=`https://${t}.http.msging.net`;this.client=q.default.create({baseURL:s,timeout:i,headers:{"Content-Type":"application/json",Authorization:e}}),this.client.interceptors.response.use(r=>r,r=>{if(r.response){let a=r.response.data?.reason||{code:r.response.status,description:r.response.statusText||"Unknown error"};throw new D(a.description,a.code)}else throw r.request?new D("No response from server",0):new D(r.message,0)})}async postCommand(t){let e={...t,id:t.id||(0,C.v4)()};p.debug("[postCommand] payload",e);let s=(await this.client.post("/commands",e)).data;if(s.status!==m.Lime.Status.SUCCESS)throw p.error("[postCommand] failed",{method:e.method,uri:e.uri,status:s.status,reason:s.reason}),new D(s.reason?.description||"Command failed",s.reason?.code||0);return p.debug("[postCommand] succeeded",e.uri),s}async postMessage(t){let e={...t,id:t.id||(0,C.v4)()};return p.info("[postMessage] payload",e),(await this.client.post("/messages",e)).data}async mergeContact(t,e){p.info("[mergeContact] called with",{contactId:t,data:e});let i={...e,identity:t},s={method:m.Lime.Method.MERGE,uri:"/contacts",type:"application/vnd.lime.contact+json",resource:i};await this.postCommand(s)}async sendMessage(t,e,i){p.info("[sendMessage] called with",{contactId:t,message:e,id:i});let s=i||(0,C.v4)(),r={id:s,to:t,type:e.type,content:e.content};return await this.postMessage(r),p.info("[sendMessage] sent",{contactId:t,messageId:s}),s}async getDispatchState(t,e){p.info("[getDispatchState] called with",{messageId:t,contactId:e});let i={method:m.Lime.Method.GET,uri:`/notifications?id=${t}`,to:"postmaster@msging.net"};try{let s=await this.postCommand(i);if(!s.resource||!s.resource.items||s.resource.items.length===0)return p.debug("[getDispatchState] no notifications found",{messageId:t,contactId:e}),null;let r={failed:4,consumed:3,received:2,accepted:1,dispatched:0},a=null,o=-1;for(let n of s.resource.items){let h=n.event,d=r[h]??-1;d>o&&(o=d,a=h)}return p.info("[getDispatchState] state retrieved",{messageId:t,contactId:e,state:a,notificationsCount:s.resource.items.length}),a}catch(s){if(s instanceof D&&s.code===67)return p.debug("[getDispatchState] resource not found",{messageId:t,contactId:e}),null;throw p.error("[getDispatchState] failed",{messageId:t,contactId:e,error:s}),s}}async getMessageAfter(t,e){p.info("[getMessageAfter] called with",{contactId:t,messageId:e});let i=e,s=0,r=10;for(;s<r;){let a={method:m.Lime.Method.GET,uri:`/threads/${t}?$skip=0&$take=1&$order=asc&messageId=${i}`,to:"postmaster@msging.net"};try{let o=await this.postCommand(a);if(!o.resource||!o.resource.items||o.resource.items.length===0)return p.debug("[getMessageAfter] no message found after",{contactId:t,messageId:i}),null;let n=o.resource.items[0];if(n.direction==="received")return p.info("[getMessageAfter] found received message",{contactId:t,messageId:i,nextMessageId:n.id}),n;p.debug("[getMessageAfter] skipping sent message",{contactId:t,messageId:n.id}),i=n.id,s++}catch(o){if(o instanceof D&&o.code===67)return p.debug("[getMessageAfter] resource not found",{contactId:t,messageId:i}),null;throw p.error("[getMessageAfter] failed",{contactId:t,messageId:i,error:o}),o}}return p.warn("[getMessageAfter] max traversal attempts reached",{contactId:t,startMessageId:e}),null}async sendEvent(t,e,i,s){p.info("[sendEvent] called with",{contactId:t,category:e,action:i,extras:s});let r={to:"postmaster@analytics.msging.net",method:m.Lime.Method.SET,type:"application/vnd.iris.eventTrack+json",uri:"/event-track",resource:{category:e,action:i,contact:{identity:t},extras:s}};await this.postCommand(r)}async setState(t,e,i="onboarding"){p.info("[setState] called with",{contactId:t,botId:e,stateId:i});let s={uri:`/flow-id?shortName=${e}`,to:"postmaster@builder.msging.net",method:m.Lime.Method.GET},r=await this.postCommand(s);if(!r.resource)throw p.error("[setState] flow ID not found",{botId:e}),new D(`Flow ID not found for bot: ${e}`,404);let a=r.resource,o={method:m.Lime.Method.SET,uri:`/contexts/${t}/stateid@${a}`,resource:i,type:"text/plain"};await this.postCommand(o);let n={method:m.Lime.Method.SET,uri:`/contexts/${t}/master-state`,resource:`${e}@msging.net`,type:"text/plain"};await this.postCommand(n)}},D=class c extends Error{constructor(t,e){super(t),this.name="BlipError",this.code=e,Object.setPrototypeOf(this,c.prototype)}};var z=I("DispatcherQuery"),R=class{constructor(t){this.repository=t}get client(){return this.repository.redis}async query(t){let e=[],i=this.repository.keyPrefix;if(t.contactId&&e.push(this.repository.getContactKey(t.contactId)),t.descriptorId&&e.push(this.repository.getDescriptorKey(t.descriptorId)),t.status){let d=Array.isArray(t.status)?t.status:[t.status];d.length===1?e.push(this.repository.getStatusKey(d[0])):d.length>1&&e.push(this.repository.getStatusKey(d[0]))}if(t.state){let d=Array.isArray(t.state)?t.state:[t.state];d.length===1&&e.push(this.repository.getStateKey(d[0]))}let s=[];if(e.length>0)s=await this.client.sinter(e);else{let d=Object.values(b).map(u=>this.repository.getStatusKey(u));s=await this.client.sunion(d)}let r=t.skip??0,a=t.size??50,o=s.slice(r,r+a),n=[],h=[];for(let d of o){let u=await this.repository.getMessage(d);if(u){if(t.status&&!(Array.isArray(t.status)?t.status:[t.status]).includes(u.status)||t.state&&!(Array.isArray(t.state)?t.state:[t.state]).includes(u.state))continue;n.push(u)}else h.push(d)}return h.length>0&&this.cleanupIndices(h,t),n}async cleanupIndices(t,e){let i=this.client.pipeline(),s=this.repository.keyPrefix;e.contactId&&i.srem(this.repository.getContactKey(e.contactId),t),e.descriptorId&&i.srem(this.repository.getDescriptorKey(e.descriptorId),t),e.status&&(Array.isArray(e.status)?e.status:[e.status]).forEach(a=>{i.srem(this.repository.getStatusKey(a),t)}),e.state&&(Array.isArray(e.state)?e.state:[e.state]).forEach(a=>{i.srem(this.repository.getStateKey(a),t)}),await i.exec(),z.debug("[cleanupIndices] Removed expired IDs from checked indices",{count:t.length})}};var l=I("Dispatcher"),P=class{constructor(t,e,i,s){this.callbacks={};this.descriptors=new Map;this.isRunning=!1;this.setupCompleted=!1;this.id=t,this.repository=e,this.redis=this.repository.redis,this.stateMachine=new T(this.id,this.repository,(r,a)=>{this.emit(r,a),this.descriptors.get(a.descriptorId)?.emit(r,a,this.api,this.id)}),this.api=new L(i.contract,i.key),this.queueName=`dispatcher-${this.id}`,this.maxRetries=s?.maxRetries??0,this.retryIntervals=s?.retryIntervals??[1*1e3,5*1e3,15*1e3],this.timeouts={pending:s?.timeouts?.pending??120*1e3,sending:s?.timeouts?.sending??120*1e3},this.retention=s?.retention??2880*60*1e3,this.pollingIntervals={scheduled:s?.pollingIntervals?.scheduled??30*1e3,pending:s?.pollingIntervals?.pending??10*1e3,sending:s?.pollingIntervals?.sending??10*1e3,delivered:s?.pollingIntervals?.delivered??1800*1e3,read:s?.pollingIntervals?.read??1800*1e3,queue:s?.pollingIntervals?.queue??1*1e3},this.timeoutTimer=null,this.query=new R(this.repository),this.queue=new x.Queue(this.queueName,{connection:this.redis,defaultJobOptions:{removeOnComplete:!0,removeOnFail:!0}}),this.worker=new x.Worker(this.queueName,async r=>{try{await this.processJob(r)}catch(a){throw l.error(`[Worker] Job ${r.name} failed`,a),a}},{connection:this.redis,concurrency:s?.batchSize||50,limiter:s?.rateLimits?.global?{max:s.rateLimits.global.points,duration:s.rateLimits.global.duration*1e3}:void 0}),this.worker.on("error",r=>l.error("[Worker] Error",r)),this.worker.on("failed",(r,a)=>l.error(`[Worker] Job ${r?.id} failed`,a))}async setup(){this.setupCompleted||(await this.repository.setup(),await this.queue.waitUntilReady(),this.isRunning=!0,this.setupCompleted=!0,this.startTimeoutMonitor(),l.info("[setup] Dispatcher started (BullMQ)",{queue:this.queueName}))}async teardown(){this.isRunning=!1,this.timeoutTimer&&(clearInterval(this.timeoutTimer),this.timeoutTimer=null),await this.queue.close(),await this.worker.close(),await this.repository.teardown(),l.info("[teardown] Dispatcher stopped")}on(t,e){return this.callbacks[t]=e,this}async getMetrics(){let t={total:0,byState:{},byStatus:{},cumulative:{dispatched:await this.repository.getMetric("dispatched"),delivered:await this.repository.getMetric("delivered"),failed:await this.repository.getMetric("failed")}},e=Object.values(M);for(let s of e)t.byState[s]=await this.repository.countMessages({state:s});let i=Object.values(b);for(let s of i)t.byStatus[s]=await this.repository.countMessages({status:s});return t.total=Object.values(t.byState).reduce((s,r)=>s+(r||0),0),t}emit(t,e){this.callbacks[t]?.(e,this.api,this.id)}async send(t,e,i,s){this.descriptors.set(t.id,t);let r=t.toContactId(e),a=t.transform(i),o=new Date().toISOString(),n={messageId:(0,$.v4)(),contactId:r,descriptorId:t.id,payload:a,status:"INIT",state:"INIT",createdAt:o,attempts:0,retries:this.maxRetries},d={...t.messageOptions,...s},{schedule:u,...y}=d;n.options=y,this.emit("dispatch",n),t.emit("dispatch",n,this.api,this.id);let f=this.calculateScheduledTime(u,d.shifts),g=0;if(f){n.scheduledTo=f,n.state="SCHEDULED";let E=new Date(f).getTime();g=Math.max(0,E-Date.now()),this.emit("scheduled",n),t.emit("scheduled",n,this.api,this.id),l.info("[send] message scheduled",{messageId:n.messageId,scheduledTo:f,delay:g})}else n.state="QUEUED",n.status="INIT",l.info("[send] message queued",{messageId:n.messageId});return n.expiresAt=new Date(Date.now()+(n.state==="SCHEDULED"?g+this.retention:this.retention)).toISOString(),await this.stateMachine.transition(n,n.state,n.status),await this.queue.add("send",{messageId:n.messageId},{jobId:n.messageId,delay:g,priority:1}),n}async cancel(t){let e=await this.repository.getMessage(t);if(!e)return l.warn("[cancel] message not found",{messageId:t}),!1;if(e.state==="FINAL")return l.warn("[cancel] message already final",{messageId:t,status:e.status}),!1;let i=await this.queue.getJob(t);return i&&(await i.remove(),l.info("[cancel] removed job from queue",{messageId:t})),await this.stateMachine.transition(e,"FINAL","CANCELED"),l.info("[cancel] message canceled",{messageId:t}),!0}async processJob(t){let{messageId:e}=t.data,i=await this.repository.getMessage(e);if(!i){l.warn(`[processJob] Message not found: ${e}`);return}let s=this.descriptors.get(i.descriptorId)||null;switch(t.name){case"send":await this.handleSendJob(i,s);break;case"check":await this.handleCheckJob(i,s);break;default:l.warn(`[processJob] Unknown job name: ${t.name}`)}}async handleSendJob(t,e){t.lastDispatchAttemptAt=new Date().toISOString(),await this.stateMachine.transition(t,"DISPATCHED","PENDING");try{await this.api.sendMessage(t.contactId,t.payload,t.messageId),await this.handlePostSendOperations(t,t.options),t.sentAt=new Date().toISOString(),await this.stateMachine.transition(t,"DISPATCHED","PENDING"),l.info("[handleSendJob] Message sent to API",{messageId:t.messageId}),await this.repository.incrementMetric("dispatched"),await this.queue.add("check",{messageId:t.messageId},{delay:this.pollingIntervals.pending,priority:5})}catch(i){let s=i instanceof Error?i:new Error(String(i));await this.handleDispatchFailure(t,e,s)}}async handlePostSendOperations(t,e={}){let i={...e.contact||{}};if(e.intent)if(typeof e.intent=="string")i.intent=e.intent;else{i.intent=e.intent.intent;let{intent:s,...r}=e.intent;Object.entries(r).forEach(([a,o])=>{o!=null&&(i[a]=typeof o=="object"?JSON.stringify(o):String(o))})}Object.keys(i).length>0&&await this.api.mergeContact(t.contactId,i),e.state&&await this.api.setState(t.contactId,e.state.botId,e.state.stateId)}async handleCheckJob(t,e){if(t.state!=="FINAL"){if(this.checkAndHandleTimeout(t)){await this.handleTimeout(t,e);return}try{let i=await this.api.getDispatchState(t.messageId,t.contactId);if(!i){await this.rescheduleCheck(t,this.pollingIntervals.pending);return}let s=this.pollingIntervals.pending,r=!1;switch(i){case"accepted":t.status!=="SENDING"&&(await this.stateMachine.transition(t,t.state,"SENDING"),r=!0),s=this.pollingIntervals.sending;break;case"received":case"consumed":if(await this.api.getMessageAfter(t.contactId,t.messageId)){let h="REPLIED";t.status!==h&&t.status!=="READ"&&(await this.repository.incrementMetric("delivered"),await this.stateMachine.transition(t,"FINAL","REPLIED"),r=!0);break}let o=i==="consumed"?"READ":"DELIVERED";t.status!==o&&(await this.repository.incrementMetric("delivered"),await this.stateMachine.transition(t,t.state,o),r=!0);let n=t.options?.finalStatus||"DELIVERED";this.getStatusRank(t.status)>=this.getStatusRank(n)?(await this.stateMachine.transition(t,"FINAL",t.status),r=!0):s=this.pollingIntervals.delivered;break;case"failed":await this.handleDispatchFailure(t,e,new Error("Dispatch failed from Gateway"));return}t.state!=="FINAL"&&await this.rescheduleCheck(t,s)}catch(i){l.error("[handleCheckJob] Error",i),await this.rescheduleCheck(t,this.pollingIntervals.pending)}}}checkAndHandleTimeout(t){let e=new Date;if(t.status==="PENDING"){let i=t.lastDispatchAttemptAt||t.sentAt||t.createdAt;if(e.getTime()-new Date(i).getTime()>this.timeouts.pending)return!0}return!!(t.status==="SENDING"&&t.acceptedAt&&e.getTime()-new Date(t.acceptedAt).getTime()>this.timeouts.sending)}async handleTimeout(t,e){await this.stateMachine.transition(t,"FINAL","FAILED",{error:"Timeout Exceeded"}),l.info("[handleTimeout] Message timed out",{messageId:t.messageId})}startTimeoutMonitor(){this.timeoutTimer||(this.timeoutTimer=setInterval(async()=>{try{let t=await this.repository.getMessages({status:"PENDING"}),e=await this.repository.getMessages({status:"SENDING"}),i=[...t,...e];for(let r of i)if(this.checkAndHandleTimeout(r)){let a=this.descriptors.get(r.descriptorId)||null;await this.handleTimeout(r,a)}let s=await this.repository.getRetentionMessages(100);if(s.length>0){l.debug("[CleanupMonitor] Cleaning up expired messages",{count:s.length});for(let r of s)await this.repository.deleteMessage(r)}}catch(t){l.error("[TimeoutMonitor] Error during scan",t)}},10*1e3))}async rescheduleCheck(t,e){await this.queue.add("check",{messageId:t.messageId},{delay:e,priority:5})}async handleDispatchFailure(t,e,i){if(t.attempts=(t.attempts??0)+1,t.error=i.message,l.error("[handleDispatchFailure]",{messageId:t.messageId,attempts:t.attempts,maxRetries:this.maxRetries,error:i.message}),t.attempts<=this.maxRetries){t.retries=this.maxRetries-t.attempts;let s=this.retryIntervals[t.attempts-1]||this.retryIntervals[this.retryIntervals.length-1];await this.stateMachine.transition(t,"SCHEDULED",t.status),this.emit("retry",t),e?.emit("retry",t,this.api,this.id),await this.queue.add("send",{messageId:t.messageId},{delay:s,priority:1}),l.info("[handleDispatchFailure] Rescheduled retry",{messageId:t.messageId,retryDelay:s})}else t.retries=0,await this.stateMachine.transition(t,"FINAL","FAILED"),await this.repository.incrementMetric("failed")}calculateScheduledTime(t,e){if(t)return t;if(!e||e.length===0)return;let i=new Date;return this.isWithinShifts(i,e)?void 0:this.findNextShiftTime(i,e)?.toISOString()}isWithinShifts(t,e){let i=t.getDay(),s=i===0?64:Math.pow(2,i-1);for(let r of e){if((r.days&s)===0)continue;let a=r.gmt||"-3",o=parseInt(a,10),n=new Date(t.getTime()-o*60*60*1e3),h=n.getHours()*60+n.getMinutes(),[d,u]=r.start.split(":").map(Number),[y,f]=r.end.split(":").map(Number),g=d*60+u,E=y*60+f;if(h>=g&&h<E)return!0}return!1}findNextShiftTime(t,e){for(let s=0;s<=7;s++){let r=new Date(t);r.setDate(r.getDate()+s);let a=r.getDay(),o=a===0?64:Math.pow(2,a-1),n=e.filter(h=>(h.days&o)!==0);if(n.length!==0){n.sort((h,d)=>{let[u,y]=h.start.split(":").map(Number),[f,g]=d.start.split(":").map(Number);return u*60+y-(f*60+g)});for(let h of n){let d=h.gmt||"-3",u=parseInt(d,10),[y,f]=h.start.split(":").map(Number),g=new Date(r);g.setHours(y,f,0,0);let E=new Date(g.getTime()+u*60*60*1e3);if(s===0){if(E>t)return E}else return E}}}}getStatusRank(t){return{INIT:0,PENDING:1,SENDING:2,DELIVERED:3,READ:4,REPLIED:5,FAILED:6,CANCELED:6}[t]||0}};0&&(module.exports={Dispatcher});
|
|
2
|
+
//# sourceMappingURL=Dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/Dispatcher.ts","../../src/types/index.ts","../../src/utils/logger.ts","../../src/core/DispatcherStateMachine.ts","../../src/core/Blip.ts","../../src/types/blip.ts","../../src/core/DispatcherQuery.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\nimport { Queue, Worker, Job, ConnectionOptions } from 'bullmq';\nimport IORedis from 'ioredis';\nimport { DispatcherDescriptor, CallbackMap } from './DispatcherDescriptor.js';\nimport { DispatcherRepository } from './DispatcherRepository.js';\nimport { DispatcherStateMachine } from './DispatcherStateMachine.js';\nimport { Blip } from './Blip.js';\nimport {\n Contact,\n Message,\n MessagePayload,\n MessageState,\n MessageStatus,\n MessageOptions,\n DispatchMessageOptions,\n DispatcherOptions,\n ConnectionConfig,\n Shift,\n DispatchState,\n CallbackEvent,\n DispatcherMetrics,\n QueryFilter,\n} from '../types/index.js';\nimport { getLogger } from '../utils/logger.js';\nimport { DispatcherQuery } from './DispatcherQuery.js';\nconst logger = getLogger('Dispatcher');\n\nexport class Dispatcher {\n public readonly id: string;\n private repository: DispatcherRepository;\n private stateMachine: DispatcherStateMachine;\n\n private api: Blip;\n private callbacks: CallbackMap = {};\n private descriptors: Map<string, DispatcherDescriptor> = new Map();\n\n private queue: Queue;\n private worker: Worker;\n private redis: IORedis;\n private queueName: string;\n\n private maxRetries: number;\n private retryIntervals: number[];\n\n private timeouts: {\n pending: number;\n sending: number;\n };\n private retention: number;\n\n private timeoutTimer: NodeJS.Timeout | null;\n\n private pollingIntervals: {\n scheduled: number;\n pending: number;\n sending: number;\n delivered: number;\n read: number;\n queue: number;\n };\n\n private isRunning = false;\n private setupCompleted = false;\n public readonly query: DispatcherQuery;\n\n constructor(\n id: string,\n repository: DispatcherRepository,\n connection: ConnectionConfig,\n options?: DispatcherOptions\n ) {\n this.id = id;\n\n this.repository = repository;\n this.redis = this.repository.redis;\n\n // Initialize State Machine with emit callback\n this.stateMachine = new DispatcherStateMachine(this.id, this.repository, (event, message) => {\n this.emit(event, message);\n const descriptor = this.descriptors.get(message.descriptorId);\n descriptor?.emit(event, message, this.api, this.id);\n });\n\n this.api = new Blip(connection.contract, connection.key);\n this.queueName = `dispatcher-${this.id}`;\n\n this.maxRetries = options?.maxRetries ?? 0;\n this.retryIntervals = options?.retryIntervals ?? [1 * 1000, 5 * 1000, 15 * 1000];\n\n this.timeouts = {\n pending: options?.timeouts?.pending ?? 2 * 60 * 1000, // 2 minutes\n sending: options?.timeouts?.sending ?? 2 * 60 * 1000, // 2 minute\n };\n this.retention = options?.retention ?? 2 * 24 * 60 * 60 * 1000; // 2 days\n\n // Polling intervals for status checks (re-queue delays)\n this.pollingIntervals = {\n scheduled: options?.pollingIntervals?.scheduled ?? 30 * 1000,\n pending: options?.pollingIntervals?.pending ?? 10 * 1000,\n sending: options?.pollingIntervals?.sending ?? 10 * 1000,\n delivered: options?.pollingIntervals?.delivered ?? 30 * 60 * 1000,\n read: options?.pollingIntervals?.read ?? 30 * 60 * 1000,\n queue: options?.pollingIntervals?.queue ?? 1 * 1000,\n };\n this.timeoutTimer = null;\n this.query = new DispatcherQuery(this.repository);\n\n // Queue Configuration\n this.queue = new Queue(this.queueName, {\n connection: this.redis as ConnectionOptions,\n defaultJobOptions: {\n removeOnComplete: true, // Keep Redis clean\n removeOnFail: true, // We handle failures manually\n },\n });\n\n // Worker Configuration\n this.worker = new Worker(\n this.queueName,\n async (job: Job) => {\n try {\n await this.processJob(job);\n } catch (error) {\n logger.error(`[Worker] Job ${job.name} failed`, error);\n throw error; // Let BullMQ mark as failed\n }\n },\n {\n // @ts-ignore\n connection: this.redis,\n concurrency: options?.batchSize || 50, // Concurrency matches batch capability\n limiter: options?.rateLimits?.global\n ? {\n max: options.rateLimits.global.points,\n duration: options.rateLimits.global.duration * 1000,\n }\n : undefined, // Worker limiter is alternative to Queue limiter\n }\n );\n\n this.worker.on('error', (err) => logger.error('[Worker] Error', err));\n this.worker.on('failed', (job, err) => logger.error(`[Worker] Job ${job?.id} failed`, err));\n }\n\n async setup(): Promise<void> {\n if (this.setupCompleted) return;\n await this.repository.setup();\n // Worker starts automatically, but we can verify connection\n await this.queue.waitUntilReady();\n this.isRunning = true;\n this.setupCompleted = true;\n this.startTimeoutMonitor();\n logger.info('[setup] Dispatcher started (BullMQ)', { queue: this.queueName });\n }\n\n async teardown(): Promise<void> {\n this.isRunning = false;\n if (this.timeoutTimer) {\n clearInterval(this.timeoutTimer);\n this.timeoutTimer = null;\n }\n await this.queue.close();\n await this.worker.close();\n await this.repository.teardown();\n logger.info('[teardown] Dispatcher stopped');\n }\n\n on(\n event: CallbackEvent,\n callback: (message: Message, api: Blip, dispatcherId: string) => void\n ): this {\n this.callbacks[event] = callback;\n return this;\n }\n\n async getMetrics(): Promise<DispatcherMetrics> {\n const metrics: DispatcherMetrics = {\n total: 0,\n byState: {},\n byStatus: {},\n cumulative: {\n dispatched: await this.repository.getMetric('dispatched'),\n delivered: await this.repository.getMetric('delivered'),\n failed: await this.repository.getMetric('failed'),\n },\n };\n\n // Use Repository to get counts (Persistent View)\n const states = Object.values(MessageState);\n for (const state of states) {\n metrics.byState[state] = await this.repository.countMessages({ state });\n }\n\n const statuses = Object.values(MessageStatus);\n for (const status of statuses) {\n metrics.byStatus[status] = await this.repository.countMessages({ status });\n }\n\n metrics.total = Object.values(metrics.byState).reduce((a, b) => a + (b || 0), 0);\n return metrics;\n }\n\n private emit(event: CallbackEvent, message: Message): void {\n this.callbacks[event]?.(message, this.api, this.id);\n }\n\n // ----------------------------------------------------------------------\n // SEND LOGIC\n // ----------------------------------------------------------------------\n\n async send(\n descriptor: DispatcherDescriptor,\n contactId: string,\n payload: MessagePayload,\n options?: DispatchMessageOptions\n ): Promise<Message> {\n this.descriptors.set(descriptor.id, descriptor);\n\n const validatedContactId = descriptor.toContactId(contactId);\n const messageData = descriptor.transform(payload);\n const now = new Date().toISOString();\n\n const message: Message = {\n messageId: uuidv4(),\n contactId: validatedContactId,\n descriptorId: descriptor.id,\n payload: messageData,\n status: MessageStatus.INIT,\n state: MessageState.INIT,\n createdAt: now,\n attempts: 0,\n retries: this.maxRetries,\n };\n\n // Options\n const descriptorOptions = descriptor.messageOptions;\n const mergedOptions: DispatchMessageOptions = { ...descriptorOptions, ...options };\n const { schedule, ...messageOptions } = mergedOptions;\n message.options = messageOptions;\n\n // Events\n this.emit('dispatch', message);\n descriptor.emit('dispatch', message, this.api, this.id);\n\n // Scheduling\n const scheduledTo = this.calculateScheduledTime(schedule, mergedOptions.shifts);\n let delay = 0;\n\n if (scheduledTo) {\n message.scheduledTo = scheduledTo;\n message.state = MessageState.SCHEDULED;\n\n const scheduleTime = new Date(scheduledTo).getTime();\n delay = Math.max(0, scheduleTime - Date.now());\n\n this.emit('scheduled', message);\n descriptor.emit('scheduled', message, this.api, this.id);\n logger.info('[send] message scheduled', { messageId: message.messageId, scheduledTo, delay });\n } else {\n message.state = MessageState.QUEUED;\n message.status = MessageStatus.INIT;\n logger.info('[send] message queued', { messageId: message.messageId });\n }\n\n // message.expiresAt is now managed by Redis TTL (Retention), separate from state timeouts.\n // We don't need to manually set `message.expiresAt` field unless we want to track it for debugging/API.\n // Let's set it to indicate when it WILL expire from Redis.\n message.expiresAt = new Date(\n Date.now() +\n (message.state === MessageState.SCHEDULED ? delay + this.retention : this.retention)\n ).toISOString();\n\n // Save Initial State\n await this.stateMachine.transition(message, message.state, message.status);\n\n // Add to Queue\n await this.queue.add(\n 'send',\n { messageId: message.messageId },\n {\n jobId: message.messageId, // Use messageId match\n delay,\n priority: 1, // High priority for sends\n }\n );\n\n return message;\n }\n\n async cancel(messageId: string): Promise<boolean> {\n const message = await this.repository.getMessage(messageId);\n\n if (!message) {\n logger.warn('[cancel] message not found', { messageId });\n return false;\n }\n\n if (message.state === MessageState.FINAL) {\n logger.warn('[cancel] message already final', { messageId, status: message.status });\n return false;\n }\n\n // Try to remove from BullMQ\n const job = await this.queue.getJob(messageId);\n if (job) {\n await job.remove();\n logger.info('[cancel] removed job from queue', { messageId });\n }\n\n await this.stateMachine.transition(message, MessageState.FINAL, MessageStatus.CANCELED);\n\n logger.info('[cancel] message canceled', { messageId });\n return true;\n }\n\n // ----------------------------------------------------------------------\n // WORKER PROCESSOR\n // ----------------------------------------------------------------------\n\n private async processJob(job: Job): Promise<void> {\n const { messageId } = job.data;\n const message = await this.repository.getMessage(messageId);\n\n if (!message) {\n logger.warn(`[processJob] Message not found: ${messageId}`);\n return;\n }\n\n // Refresh context\n const descriptor = this.descriptors.get(message.descriptorId) || null;\n\n switch (job.name) {\n case 'send':\n await this.handleSendJob(message, descriptor);\n break;\n case 'check':\n await this.handleCheckJob(message, descriptor);\n break;\n default:\n logger.warn(`[processJob] Unknown job name: ${job.name}`);\n }\n }\n\n private async handleSendJob(\n message: Message,\n descriptor: DispatcherDescriptor | null\n ): Promise<void> {\n message.lastDispatchAttemptAt = new Date().toISOString();\n await this.stateMachine.transition(message, MessageState.DISPATCHED, MessageStatus.PENDING);\n\n try {\n await this.api.sendMessage(message.contactId, message.payload, message.messageId);\n\n // Handle Extras/Intent/State\n await this.handlePostSendOperations(message, message.options);\n\n // Update Status: PENDING -> Wait for Accept/Deliver\n message.sentAt = new Date().toISOString();\n await this.stateMachine.transition(message, MessageState.DISPATCHED, MessageStatus.PENDING); // Persist sentAt update\n\n logger.info('[handleSendJob] Message sent to API', { messageId: message.messageId });\n await this.repository.incrementMetric('dispatched');\n\n // Schedule status check\n await this.queue.add(\n 'check',\n { messageId: message.messageId },\n {\n delay: this.pollingIntervals.pending,\n priority: 5, // Lower priority than sends\n }\n );\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n await this.handleDispatchFailure(message, descriptor, err);\n }\n }\n\n private async handlePostSendOperations(\n message: Message,\n options: MessageOptions = {}\n ): Promise<void> {\n const contact: Contact = { ...(options.contact || {}) };\n\n if (options.intent) {\n if (typeof options.intent === 'string') {\n contact.intent = options.intent;\n } else {\n contact.intent = options.intent.intent;\n const { intent, ...rest } = options.intent;\n Object.entries(rest).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n contact[key] = typeof value === 'object' ? JSON.stringify(value) : String(value);\n }\n });\n }\n }\n\n if (Object.keys(contact).length > 0) {\n await this.api.mergeContact(message.contactId, contact);\n }\n\n if (options.state) {\n await this.api.setState(message.contactId, options.state.botId, options.state.stateId);\n }\n }\n\n private async handleCheckJob(\n message: Message,\n descriptor: DispatcherDescriptor | null\n ): Promise<void> {\n if (message.state === MessageState.FINAL) return;\n\n // Timeout Check logic is now handled by startTimeoutMonitor, but we can do a quick check here too.\n if (this.checkAndHandleTimeout(message)) {\n await this.handleTimeout(message, descriptor);\n return;\n }\n\n try {\n const state = await this.api.getDispatchState(message.messageId, message.contactId);\n if (!state) {\n // No state found yet? Schedule check again\n await this.rescheduleCheck(message, this.pollingIntervals.pending);\n return;\n }\n\n let nextCheckDelay = this.pollingIntervals.pending;\n let updated = false;\n\n switch (state) {\n case DispatchState.ACCEPTED:\n if (message.status !== MessageStatus.SENDING) {\n await this.stateMachine.transition(message, message.state, MessageStatus.SENDING);\n updated = true;\n }\n // Use longer interval for subsequent checks\n nextCheckDelay = this.pollingIntervals.sending;\n break;\n\n case DispatchState.RECEIVED:\n case DispatchState.CONSUMED:\n // Check for implicit reply first\n // If we find a reply, it means the user read and replied.\n const reply = await this.api.getMessageAfter(message.contactId, message.messageId);\n if (reply) {\n const newStatus = MessageStatus.REPLIED; // Using REPLIED if available, or READ\n if (message.status !== newStatus && message.status !== MessageStatus.READ) {\n // Implicit Read and Replied\n // We need to transition to REPLIED.\n // Note: StateMachine sets readAt/repliedAt if missing.\n await this.repository.incrementMetric('delivered'); // Ensure delivered count\n // Force FINAL state as REPLIED is terminal\n await this.stateMachine.transition(\n message,\n MessageState.FINAL,\n MessageStatus.REPLIED\n );\n updated = true;\n }\n break;\n }\n\n const newStatus =\n state === DispatchState.CONSUMED ? MessageStatus.READ : MessageStatus.DELIVERED;\n if (message.status !== newStatus) {\n // Increment delivered only on first transition to delivered/read/replied\n await this.repository.incrementMetric('delivered');\n await this.stateMachine.transition(message, message.state, newStatus);\n updated = true;\n }\n\n // Check for Final\n const finalStatus = message.options?.finalStatus || 'DELIVERED';\n if (\n this.getStatusRank(message.status) >= this.getStatusRank(finalStatus as MessageStatus)\n ) {\n await this.stateMachine.transition(message, MessageState.FINAL, message.status);\n updated = true;\n } else {\n nextCheckDelay = this.pollingIntervals.delivered;\n }\n break;\n\n case DispatchState.FAILED:\n await this.handleDispatchFailure(\n message,\n descriptor,\n new Error('Dispatch failed from Gateway')\n );\n return;\n }\n\n if (updated) {\n }\n\n // If not final, reschedule check\n if ((message.state as MessageState) !== MessageState.FINAL) {\n await this.rescheduleCheck(message, nextCheckDelay);\n }\n } catch (error) {\n logger.error('[handleCheckJob] Error', error);\n // Retry check later\n await this.rescheduleCheck(message, this.pollingIntervals.pending);\n }\n }\n\n private checkAndHandleTimeout(message: Message): boolean {\n const now = new Date();\n\n // Check Status Timeouts\n if (message.status === MessageStatus.PENDING) {\n const startTime = message.lastDispatchAttemptAt || message.sentAt || message.createdAt;\n if (now.getTime() - new Date(startTime).getTime() > this.timeouts.pending) return true;\n }\n\n if (message.status === MessageStatus.SENDING && message.acceptedAt) {\n if (now.getTime() - new Date(message.acceptedAt).getTime() > this.timeouts.sending)\n return true;\n }\n\n return false;\n }\n\n private async handleTimeout(\n message: Message,\n descriptor: DispatcherDescriptor | null\n ): Promise<void> {\n await this.stateMachine.transition(message, MessageState.FINAL, MessageStatus.FAILED, {\n error: 'Timeout Exceeded',\n });\n logger.info('[handleTimeout] Message timed out', { messageId: message.messageId });\n }\n\n private startTimeoutMonitor(): void {\n if (this.timeoutTimer) return;\n\n // Scan periodically (e.g. every 10s)\n this.timeoutTimer = setInterval(async () => {\n try {\n const pending = await this.repository.getMessages({ status: MessageStatus.PENDING });\n const sending = await this.repository.getMessages({ status: MessageStatus.SENDING });\n const all = [...pending, ...sending];\n\n for (const message of all) {\n if (this.checkAndHandleTimeout(message)) {\n const descriptor = this.descriptors.get(message.descriptorId) || null;\n await this.handleTimeout(message, descriptor);\n }\n }\n\n // --- Active Cleanup Monitor ---\n const expiredIds = await this.repository.getRetentionMessages(100);\n if (expiredIds.length > 0) {\n logger.debug('[CleanupMonitor] Cleaning up expired messages', {\n count: expiredIds.length,\n });\n for (const id of expiredIds) {\n await this.repository.deleteMessage(id);\n }\n }\n } catch (error) {\n logger.error('[TimeoutMonitor] Error during scan', error);\n }\n }, 10 * 1000);\n }\n\n private async rescheduleCheck(message: Message, delay: number): Promise<void> {\n await this.queue.add('check', { messageId: message.messageId }, { delay, priority: 5 });\n }\n\n private async handleDispatchFailure(\n message: Message,\n descriptor: DispatcherDescriptor | null,\n error: Error\n ): Promise<void> {\n message.attempts = (message.attempts ?? 0) + 1;\n message.error = error.message;\n\n logger.error('[handleDispatchFailure]', {\n messageId: message.messageId,\n attempts: message.attempts,\n maxRetries: this.maxRetries,\n error: error.message,\n });\n\n if (message.attempts <= this.maxRetries) {\n message.retries = this.maxRetries - message.attempts;\n const retryDelay =\n this.retryIntervals[message.attempts - 1] ||\n this.retryIntervals[this.retryIntervals.length - 1];\n\n await this.stateMachine.transition(message, MessageState.SCHEDULED, message.status);\n\n this.emit('retry', message);\n descriptor?.emit('retry', message, this.api, this.id);\n\n await this.queue.add(\n 'send',\n { messageId: message.messageId },\n {\n delay: retryDelay,\n priority: 1,\n }\n );\n\n logger.info('[handleDispatchFailure] Rescheduled retry', {\n messageId: message.messageId,\n retryDelay,\n });\n } else {\n message.retries = 0;\n await this.stateMachine.transition(message, MessageState.FINAL, MessageStatus.FAILED);\n await this.repository.incrementMetric('failed');\n }\n }\n\n private calculateScheduledTime(schedule?: string, shifts?: Shift[]): string | undefined {\n if (schedule) {\n return schedule;\n }\n\n if (!shifts || shifts.length === 0) {\n return undefined;\n }\n\n const now = new Date();\n\n if (this.isWithinShifts(now, shifts)) {\n return undefined;\n }\n\n const nextShiftTime = this.findNextShiftTime(now, shifts);\n return nextShiftTime?.toISOString();\n }\n\n private isWithinShifts(date: Date, shifts: Shift[]): boolean {\n const dayOfWeek = date.getDay();\n const dayBit = dayOfWeek === 0 ? 64 : Math.pow(2, dayOfWeek - 1);\n\n for (const shift of shifts) {\n if ((shift.days & dayBit) === 0) {\n continue;\n }\n\n const gmt = shift.gmt || '-3';\n const offset = parseInt(gmt, 10);\n const shiftDate = new Date(date.getTime() - offset * 60 * 60 * 1000);\n\n const currentTime = shiftDate.getHours() * 60 + shiftDate.getMinutes();\n const [startHour, startMin] = shift.start.split(':').map(Number);\n const [endHour, endMin] = shift.end.split(':').map(Number);\n const startTime = startHour * 60 + startMin;\n const endTime = endHour * 60 + endMin;\n\n if (currentTime >= startTime && currentTime < endTime) {\n return true;\n }\n }\n\n return false;\n }\n\n private findNextShiftTime(date: Date, shifts: Shift[]): Date | undefined {\n const maxDaysAhead = 7;\n\n for (let daysAhead = 0; daysAhead <= maxDaysAhead; daysAhead++) {\n const checkDate = new Date(date);\n checkDate.setDate(checkDate.getDate() + daysAhead);\n\n const dayOfWeek = checkDate.getDay();\n const dayBit = dayOfWeek === 0 ? 64 : Math.pow(2, dayOfWeek - 1);\n\n const availableShifts = shifts.filter((shift) => (shift.days & dayBit) !== 0);\n\n if (availableShifts.length === 0) {\n continue;\n }\n\n availableShifts.sort((a, b) => {\n const [aHour, aMin] = a.start.split(':').map(Number);\n const [bHour, bMin] = b.start.split(':').map(Number);\n return aHour * 60 + aMin - (bHour * 60 + bMin);\n });\n\n for (const shift of availableShifts) {\n const gmt = shift.gmt || '-3';\n const offset = parseInt(gmt, 10);\n\n const [startHour, startMin] = shift.start.split(':').map(Number);\n\n const shiftStart = new Date(checkDate);\n shiftStart.setHours(startHour, startMin, 0, 0);\n const shiftStartUTC = new Date(shiftStart.getTime() + offset * 60 * 60 * 1000);\n\n if (daysAhead === 0) {\n if (shiftStartUTC > date) {\n return shiftStartUTC;\n }\n } else {\n return shiftStartUTC;\n }\n }\n }\n\n return undefined;\n }\n\n private getStatusRank(status: MessageStatus): number {\n const ranks: Record<MessageStatus, number> = {\n [MessageStatus.INIT]: 0,\n [MessageStatus.PENDING]: 1,\n [MessageStatus.SENDING]: 2,\n [MessageStatus.DELIVERED]: 3,\n [MessageStatus.READ]: 4,\n [MessageStatus.REPLIED]: 5,\n [MessageStatus.FAILED]: 6,\n [MessageStatus.CANCELED]: 6,\n };\n return ranks[status] || 0;\n }\n}\n","import { Vnd } from './blip';\n\nexport type Contact = Omit<Vnd.Iris.Contact, 'identity'>;\n\nexport type MessageData = {\n type: string;\n content: Record<string, any> | string;\n};\n\nexport type MessagePayload = Record<string, any> | Array<any> | string;\n\nexport enum MessageState {\n INIT = 'INIT',\n DISPATCHED = 'DISPATCHED',\n SCHEDULED = 'SCHEDULED',\n QUEUED = 'QUEUED',\n FINAL = 'FINAL',\n}\n\nexport enum MessageStatus {\n INIT = 'INIT',\n PENDING = 'PENDING',\n SENDING = 'SENDING',\n DELIVERED = 'DELIVERED',\n READ = 'READ',\n REPLIED = 'REPLIED',\n FAILED = 'FAILED',\n CANCELED = 'CANCELED',\n}\n\nexport enum DispatchState {\n ACCEPTED = 'accepted',\n DISPATCHED = 'dispatched',\n RECEIVED = 'received',\n CONSUMED = 'consumed',\n FAILED = 'failed',\n}\n\nexport interface Message {\n messageId: string;\n contactId: string;\n descriptorId: string;\n\n payload: MessageData;\n status: MessageStatus;\n state: MessageState;\n\n attempts?: number;\n retries?: number;\n\n options?: MessageOptions; // Persisted options for scheduled/retried messages\n\n expiresAt?: string; // ISO 8601 datetime\n\n scheduledTo?: string; // ISO 8601 datetime\n sentAt?: string; // ISO 8601 datetime\n acceptedAt?: string; // ISO 8601 datetime - When message was accepted by gateway (SENDING status)\n lastDispatchAttemptAt?: string; // ISO 8601 datetime - When last dispatch attempt started\n createdAt: string; // ISO 8601 datetime\n deliveredAt?: string; // ISO 8601 datetime\n readAt?: string; // ISO 8601 datetime\n repliedAt?: string; // ISO 8601 datetime\n failedAt?: string; // ISO 8601 datetime\n\n error?: string;\n}\n\nexport interface Shift {\n days: number; // Bitmask: use Weekdays enum (e.g., Weekdays.MONDAY | Weekdays.FRIDAY) or manual (31=Mon-Fri)\n start: string; // HH:mm format (e.g., \"09:00\")\n end: string; // HH:mm format (e.g., \"18:00\")\n gmt?: string; // Timezone offset (default: \"-3\" for BRT)\n}\n\nexport enum Weekdays {\n MONDAY = 1,\n TUESDAY = 2,\n WEDNESDAY = 4,\n THURSDAY = 8,\n FRIDAY = 16,\n SATURDAY = 32,\n SUNDAY = 64,\n}\n\nexport interface Intent {\n intent: string;\n dueDate?: string;\n event?: string;\n payload?: any | null;\n expired?: {\n event?: string;\n intent?: string;\n };\n}\n\nexport type CallbackEvent =\n | 'dispatch' // Before message send process begins\n | 'sending' // MessageStatus.SENDING - message accepted by gateway\n | 'delivered' // MessageStatus.DELIVERED - message successfully delivered\n | 'failed' // MessageStatus.FAILED - message failed permanently\n | 'read' // MessageStatus.READ - message read by recipient\n | 'replied' // MessageStatus.REPLIED - recipient replied\n | 'scheduled' // MessageState.SCHEDULED - message scheduled\n | 'retry' // Retry attempt scheduled\n | 'evicted' // Message evicted from state machine due to TTL\n | 'canceled'; // Message canceled before send\n\nexport type ConnectionConfig = {\n contract: string; // Contract ID (without https:// prefix)\n key: string; // Bot authorization key\n};\n\nexport interface MessageOptions {\n shifts?: Array<Shift>; // Business hours constraints\n contact?: Contact;\n state?: {\n // Bot state to set after send\n stateId: string;\n botId: string;\n botIds?: Array<string>; // Bot IDs to reset before setting state\n };\n intent?: string | Intent; // Intent to trigger after send\n finalStatus?: 'DELIVERED' | 'READ' | 'REPLIED'; // Default: 'DELIVERED'\n}\n\nexport interface DispatchMessageOptions extends MessageOptions {\n schedule?: string; // ISO 8601 datetime to schedule send\n}\n\nexport interface DispatcherOptions {\n maxRetries?: number; // Default: 0\n retryIntervals?: number[]; // Default: [1000, 5000, 15000] (exponential backoff in ms)\n\n // Storage Duration (Redis TTL)\n retention?: number; // Default: 3600000 ms (1 hour) - Time to keep message in storage after creation/dispatch\n\n // State Constraints (Force transition to FAILED)\n timeouts?: {\n pending?: number; // Default: 10000 ms (10 seconds)\n sending?: number; // Default: 60000 ms (60 seconds)\n };\n\n batchSize?: number; // Default: 100 messages per polling cycle\n pollingIntervals?: {\n scheduled?: number; // Default: 5000 ms\n pending?: number; // Default: 5000 ms\n sending?: number; // Default: 10000 ms\n delivered?: number; // Default: 1800000 ms (30 minutes)\n read?: number; // Default: 3600000 ms (60 minutes)\n queue?: number; // Default: 100 ms\n };\n maxQueueSize?: number; // Maximum number of pending/scheduled messages. Oldest evicted if exceeded.\n rateLimits?: {\n global?: { points: number; duration: number }; // Global rate limit (requests per duration)\n message?: { points: number; duration: number }; // Message sending rate limit (requests per duration)\n };\n}\n\nexport interface QueryFilter {\n contactId?: string;\n descriptorId?: string;\n status?: MessageStatus | MessageStatus[];\n state?: MessageState | MessageState[];\n size?: number;\n skip?: number;\n}\n\nexport interface DispatcherMetrics {\n total: number;\n byState: {\n [key in MessageState]?: number;\n };\n byStatus: {\n [key in MessageStatus]?: number;\n };\n cumulative: {\n dispatched: number;\n delivered: number;\n failed: number;\n };\n}\n\n/**\n * @deprecated Use DispatcherOptions instead\n */\nexport type MessageDispatcherOptions = DispatcherOptions;\n","/**\n * Logger utility using the 'debug' package.\n *\n * This module provides a configurable logger based on the 'debug' package.\n * By default, all logging is disabled. To enable logging, set the DEBUG\n * environment variable.\n *\n * The DEBUG variable can be a single namespace or a comma-separated list of\n * namespaces. Wildcards are supported.\n *\n * For example:\n * - `DEBUG=blip-api` enables all logs for the 'blip-api' scope.\n * - `DEBUG=blip-api:*` enables all sub-scopes under 'blip-api'.\n * - `DEBUG=*` enables all logs.\n *\n * The logger exposes different levels (debug, info, warn, error), which are\n * different namespaces.\n *\n * Example usage:\n * ```\n * import { getLogger } from './logger';\n *\n * const logger = getLogger('my-feature');\n *\n * logger.info('This is an info message');\n * logger.error('This is an error');\n * ```\n *\n * To see the output, run your application with:\n * `DEBUG=my-feature:* node your-app.js`\n */\n\nimport debug from 'debug';\n\n// Define the available log levels\nexport const LogLevels = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\nexport type LogLevel = keyof typeof LogLevels;\n\ntype LogFunction = (...args: any[]) => void;\n\nexport interface Logger {\n debug: LogFunction;\n info: LogFunction;\n warn: LogFunction;\n error: LogFunction;\n}\n\nconst loggers = new Map<string, Logger>();\n\nfunction serializeArg(arg: any): any {\n if (arg === null || arg === undefined) return arg;\n if (typeof arg === 'object') {\n try {\n return JSON.stringify(arg, null, 2);\n } catch (e) {\n return arg;\n }\n }\n return arg;\n}\n\nfunction createLogFunction(debugInstance: debug.Debugger): LogFunction {\n return (...args: any[]) => {\n const serialized = args.map(serializeArg);\n (debugInstance as any)(...serialized);\n };\n}\n\n/**\n * Creates a logger with a specific scope.\n *\n * @param scope - The scope for the logger (e.g., 'blip-api', 'dispatcher').\n * @returns A logger object with methods for each log level.\n */\nexport function getLogger(scope: string): Logger {\n if (!loggers.has(scope)) {\n const debugInstance = debug(`${scope}:${LogLevels.debug}`);\n const infoInstance = debug(`${scope}:${LogLevels.info}`);\n const warnInstance = debug(`${scope}:${LogLevels.warn}`);\n const errorInstance = debug(`${scope}:${LogLevels.error}`);\n\n errorInstance.log = console.error.bind(console);\n\n const logger: Logger = {\n debug: createLogFunction(debugInstance),\n info: createLogFunction(infoInstance),\n warn: createLogFunction(warnInstance),\n error: createLogFunction(errorInstance),\n };\n\n loggers.set(scope, logger);\n }\n return loggers.get(scope)!;\n}\n\n/**\n * Enables logging for a specific scope and level.\n * This is a convenience function to programmatically enable logging,\n * equivalent to setting the DEBUG environment variable.\n *\n * @param scope - The scope to enable (e.g., 'blip-api').\n * @param level - The minimum log level to enable (e.g., 'info').\n */\nexport function enableLogger(namespaces: string): void {\n const currentDebug = debug.disable();\n debug.enable(`${currentDebug},${namespaces}`);\n}\n\n/**\n * Disables all logging.\n */\nexport function disableLogger(): void {\n debug.disable();\n}\n\n/**\n * Get all registered logger scopes.\n *\n * @returns An array of all registered scopes.\n */\nexport function getAllScopes(): string[] {\n return Array.from(loggers.keys());\n}\n","import { Message, MessageState, MessageStatus, CallbackEvent } from '../types/index.js';\nimport { DispatcherRepository } from './DispatcherRepository.js';\nimport { getLogger } from '../utils/logger.js';\n\nconst logger = getLogger('StateMachine');\n\nexport type StateTransitionCallback = (event: CallbackEvent, message: Message) => void;\n\nexport class DispatcherStateMachine {\n constructor(\n private id: string,\n private repository: DispatcherRepository,\n private emit: StateTransitionCallback\n ) {}\n\n /**\n * Transition message to a new state/status.\n * Validates transition, updates timestamps, persists to Redis, and emits events.\n */\n async transition(\n message: Message,\n targetState: MessageState,\n targetStatus: MessageStatus,\n data?: Partial<Message> // Additional data to merge (e.g. error, timestamps)\n ): Promise<Message> {\n const fromState = message.state;\n const fromStatus = message.status;\n\n // 1. Validation (Optional: Strict Transition Rules)\n // For now, we allow most transitions but can add guards here.\n if (fromState === MessageState.FINAL && targetState !== MessageState.FINAL) {\n logger.warn(`[transition] Attempting to move from FINAL back to ${targetState}`, {\n messageId: message.messageId,\n });\n // We could throw or ignore. For safety, let's log and proceed for now, or block.\n // Usually FINAL is terminal.\n }\n\n // 2. Update Message Object\n message.state = targetState;\n message.status = targetStatus;\n\n if (data) {\n Object.assign(message, data);\n }\n\n // 3. Update Timestamps based on Status/State\n const now = new Date().toISOString();\n\n // Status-based timestamps\n if (\n targetStatus === MessageStatus.SENDING &&\n !message.acceptedAt &&\n fromStatus !== MessageStatus.SENDING\n ) {\n message.acceptedAt = now;\n }\n if (targetStatus === MessageStatus.DELIVERED && !message.deliveredAt) {\n message.deliveredAt = now;\n }\n if (targetStatus === MessageStatus.READ && !message.readAt) {\n message.readAt = now;\n }\n if (targetStatus === MessageStatus.REPLIED && !message.repliedAt) {\n message.repliedAt = now;\n // Implicit Read for Reply\n if (!message.readAt) message.readAt = now;\n }\n if (targetStatus === MessageStatus.FAILED && !message.failedAt) {\n message.failedAt = now;\n }\n\n // State-based timestamps\n if (\n targetState === MessageState.DISPATCHED &&\n !message.sentAt &&\n targetStatus === MessageStatus.PENDING\n ) {\n // Dispatcher sets dispatched state before sending to API (PENDING)\n // Dispatcher sets sentAt AFTER successful API call.\n // This logic might need to be flexible or handled by caller providing 'sentAt' in `data`.\n }\n\n // 4. Persistence\n // We rely on Repository to handle queues based on state/status\n await this.repository.upsertMessage(message);\n\n // 5. Emit Events based on Transition\n // Detect Status Changes\n if (fromStatus !== targetStatus) {\n // Implicit Read on Reply\n if (targetStatus === MessageStatus.REPLIED && fromStatus !== MessageStatus.READ) {\n this.emit('read', message);\n }\n this.emitStatusEvent(message, targetStatus);\n }\n\n // Detect State Changes (specific ones)\n if (targetState === MessageState.SCHEDULED && fromState !== MessageState.SCHEDULED) {\n this.emit('scheduled', message);\n }\n // Retry is usually a specific flow/event, handled by caller or explicit call?\n // Dispatcher calls transition -> SCHEDULED (retry).\n\n logger.debug(\n `[transition] ${message.messageId} : ${fromState}/${fromStatus} -> ${targetState}/${targetStatus}`\n );\n\n return message;\n }\n\n private emitStatusEvent(message: Message, status: MessageStatus) {\n switch (status) {\n case MessageStatus.SENDING:\n this.emit('sending', message);\n break;\n case MessageStatus.DELIVERED:\n this.emit('delivered', message);\n break;\n case MessageStatus.READ:\n this.emit('read', message);\n break;\n case MessageStatus.REPLIED:\n this.emit('replied', message);\n break;\n case MessageStatus.FAILED:\n this.emit('failed', message);\n break;\n case MessageStatus.CANCELED:\n this.emit('canceled', message);\n break;\n }\n }\n}\n","/**\n * Blip - Low-level client wrapper for Blip Platform API interactions\n *\n * Responsibilities:\n * - Send messages to contacts via Blip HTTP API\n * - Merge contact metadata (custom fields)\n * - Send analytics events to Blip\n * - Manage bot conversation state transitions\n * - Check message delivery status\n */\n\nimport axios, { AxiosInstance } from 'axios';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Vnd } from '../types/blip.js';\nimport { MessageData, DispatchState } from '../types/index.js';\nimport { getLogger } from '../utils/logger.js';\n\nconst logger = getLogger('Blip');\n\nexport class Blip {\n private client: AxiosInstance;\n\n /**\n * Create a new Blip instance\n * @param contract - Blip contract ID (without https:// prefix)\n * @param key - Blip API key\n * @param timeout - Request timeout in milliseconds (default: 30000)\n */\n constructor(contract: string, key: string, timeout: number = 30000) {\n const botUrl = `https://${contract}.http.msging.net`;\n\n this.client = axios.create({\n baseURL: botUrl,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: key,\n },\n });\n\n // Response interceptor for error handling\n this.client.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response) {\n const reason = error.response.data?.reason || {\n code: error.response.status,\n description: error.response.statusText || 'Unknown error',\n };\n throw new BlipError(reason.description, reason.code);\n } else if (error.request) {\n throw new BlipError('No response from server', 0);\n } else {\n throw new BlipError(error.message, 0);\n }\n }\n );\n }\n\n /**\n * Send a command to the Blip API\n */\n private async postCommand<T = any>(\n request: Vnd.Lime.Transport.Payload.Request\n ): Promise<Vnd.Lime.Transport.Payload.Response<T>> {\n const payload = {\n ...request,\n id: request.id || uuidv4(),\n };\n\n logger.debug('[postCommand] payload', payload);\n\n const response = await this.client.post<Vnd.Lime.Transport.Payload.Response<T>>(\n '/commands',\n payload\n );\n\n const result = response.data;\n\n if (result.status !== Vnd.Lime.Status.SUCCESS) {\n logger.error('[postCommand] failed', {\n method: payload.method,\n uri: payload.uri,\n status: result.status,\n reason: result.reason,\n });\n throw new BlipError(result.reason?.description || 'Command failed', result.reason?.code || 0);\n }\n\n logger.debug('[postCommand] succeeded', payload.uri);\n return result;\n }\n\n /**\n * Send a message through the Blip API\n */\n private async postMessage<T = any>(\n message: Vnd.Lime.Message<T>\n ): Promise<Vnd.Lime.Transport.Payload.Response> {\n const payload = {\n ...message,\n id: message.id || uuidv4(),\n };\n logger.info('[postMessage] payload', payload);\n\n const response = await this.client.post<Vnd.Lime.Transport.Payload.Response>(\n '/messages',\n payload\n );\n\n return response.data;\n }\n\n /**\n * Merge contact metadata (custom fields)\n * @param contactId - Contact identifier\n * @param data - Key-value pairs to merge into contact\n */\n async mergeContact(contactId: string, data: Omit<Vnd.Iris.Contact, 'identity'>): Promise<void> {\n logger.info('[mergeContact] called with', { contactId, data });\n const contact = {\n ...data,\n identity: contactId,\n };\n\n const request: Vnd.Lime.Transport.Payload.Request = {\n method: Vnd.Lime.Method.MERGE,\n uri: '/contacts',\n type: 'application/vnd.lime.contact+json',\n resource: contact,\n };\n\n await this.postCommand(request);\n }\n\n /**\n * Send a message to a contact\n * @param contactId - Contact identifier\n * @param message - Message data to send\n * @param id - Optional message ID (if not provided, a new one is generated)\n * @returns message ID\n */\n async sendMessage(contactId: string, message: MessageData, id?: string): Promise<string> {\n logger.info('[sendMessage] called with', { contactId, message, id });\n\n const messageId = id || uuidv4();\n\n const limeMessage: Vnd.Lime.Message = {\n id: messageId,\n to: contactId,\n type: message.type,\n content: message.content,\n };\n\n await this.postMessage(limeMessage);\n\n logger.info('[sendMessage] sent', { contactId, messageId });\n return messageId;\n }\n\n /**\n * Get raw dispatch state from Blip notifications\n * @param messageId - Message ID to check\n * @param contactId - Contact identifier to filter notifications\n * @returns DispatchState or null if not found\n */\n async getDispatchState(messageId: string, contactId: string): Promise<DispatchState | null> {\n logger.info('[getDispatchState] called with', { messageId, contactId });\n\n const request: Vnd.Lime.Transport.Payload.Request = {\n method: Vnd.Lime.Method.GET,\n uri: `/notifications?id=${messageId}`,\n to: 'postmaster@msging.net',\n };\n\n try {\n const response = await this.postCommand<{\n total: number;\n itemType: string;\n items: Array<{\n event: string;\n id: string;\n from: string;\n to: string;\n reason?: {\n code: number;\n description: string;\n };\n metadata?: {\n '#envelope.storageDate'?: string;\n [key: string]: any;\n };\n }>;\n }>(request);\n\n if (!response.resource || !response.resource.items || response.resource.items.length === 0) {\n logger.debug('[getDispatchState] no notifications found', { messageId, contactId });\n return null;\n }\n\n const statePriority: Record<string, number> = {\n [DispatchState.FAILED]: 4,\n [DispatchState.CONSUMED]: 3,\n [DispatchState.RECEIVED]: 2,\n [DispatchState.ACCEPTED]: 1,\n [DispatchState.DISPATCHED]: 0,\n };\n\n let bestState: DispatchState | null = null;\n let maxPriority = -1;\n\n for (const item of response.resource.items) {\n const state = item.event as DispatchState;\n const priority = statePriority[state] ?? -1;\n\n if (priority > maxPriority) {\n maxPriority = priority;\n bestState = state;\n }\n }\n\n logger.info('[getDispatchState] state retrieved', {\n messageId,\n contactId,\n state: bestState,\n notificationsCount: response.resource.items.length,\n });\n\n return bestState;\n } catch (error) {\n if (error instanceof BlipError && error.code === 67) {\n logger.debug('[getDispatchState] resource not found', { messageId, contactId });\n return null;\n }\n\n logger.error('[getDispatchState] failed', {\n messageId,\n contactId,\n error,\n });\n throw error;\n }\n }\n\n /**\n * Get the first message received after a specific message ID\n * @param contactId - Contact identifier\n * @param messageId - Message ID to use as anchor\n * @returns ThreadMessage or null if no message found\n */\n async getMessageAfter(\n contactId: string,\n messageId: string\n ): Promise<Vnd.Iris.ThreadMessage | null> {\n logger.info('[getMessageAfter] called with', { contactId, messageId });\n\n let currentMessageId = messageId;\n let attempts = 0;\n const maxAttempts = 10;\n\n while (attempts < maxAttempts) {\n const request: Vnd.Lime.Transport.Payload.Request = {\n method: Vnd.Lime.Method.GET,\n uri: `/threads/${contactId}?$skip=0&$take=1&$order=asc&messageId=${currentMessageId}`,\n to: 'postmaster@msging.net',\n };\n\n try {\n const response = await this.postCommand<{\n total: number;\n itemType: string;\n items: Vnd.Iris.ThreadMessage[];\n }>(request);\n\n if (\n !response.resource ||\n !response.resource.items ||\n response.resource.items.length === 0\n ) {\n logger.debug('[getMessageAfter] no message found after', {\n contactId,\n messageId: currentMessageId,\n });\n return null;\n }\n\n const nextMessage = response.resource.items[0];\n\n if (nextMessage.direction === 'received') {\n logger.info('[getMessageAfter] found received message', {\n contactId,\n messageId: currentMessageId,\n nextMessageId: nextMessage.id,\n });\n return nextMessage;\n }\n\n // If 'sent', treat this message as the new anchor and try again\n logger.debug('[getMessageAfter] skipping sent message', {\n contactId,\n messageId: nextMessage.id,\n });\n currentMessageId = nextMessage.id;\n attempts++;\n } catch (error) {\n if (error instanceof BlipError && error.code === 67) {\n logger.debug('[getMessageAfter] resource not found', {\n contactId,\n messageId: currentMessageId,\n });\n return null;\n }\n\n logger.error('[getMessageAfter] failed', {\n contactId,\n messageId: currentMessageId,\n error,\n });\n throw error;\n }\n }\n\n logger.warn('[getMessageAfter] max traversal attempts reached', {\n contactId,\n startMessageId: messageId,\n });\n return null;\n }\n\n /**\n * Send an analytics event to Blip\n * @param contactId - Contact identifier\n * @param category - Event category\n * @param action - Event action\n * @param extras - Additional event data\n */\n async sendEvent(\n contactId: string,\n category: string,\n action: string,\n extras: Record<string, string>\n ): Promise<void> {\n logger.info('[sendEvent] called with', { contactId, category, action, extras });\n const request: Vnd.Lime.Transport.Payload.Request = {\n to: 'postmaster@analytics.msging.net',\n method: Vnd.Lime.Method.SET,\n type: 'application/vnd.iris.eventTrack+json',\n uri: '/event-track',\n resource: {\n category,\n action,\n contact: {\n identity: contactId,\n },\n extras,\n },\n };\n\n await this.postCommand(request);\n }\n\n /**\n * Set the bot conversation state for a contact\n * @param contactId - Contact identifier\n * @param botId - Bot identifier (short name)\n * @param stateId - State identifier (default: \"onboarding\")\n */\n async setState(contactId: string, botId: string, stateId: string = 'onboarding'): Promise<void> {\n logger.info('[setState] called with', { contactId, botId, stateId });\n // Get flow ID for the bot\n const flowIdRequest: Vnd.Lime.Transport.Payload.Request = {\n uri: `/flow-id?shortName=${botId}`,\n to: 'postmaster@builder.msging.net',\n method: Vnd.Lime.Method.GET,\n };\n\n const flowIdResponse = await this.postCommand<string>(flowIdRequest);\n if (!flowIdResponse.resource) {\n logger.error('[setState] flow ID not found', { botId });\n throw new BlipError(`Flow ID not found for bot: ${botId}`, 404);\n }\n const flowId = flowIdResponse.resource;\n\n // Change contact bot state\n const stateRequest: Vnd.Lime.Transport.Payload.Request<string> = {\n method: Vnd.Lime.Method.SET,\n uri: `/contexts/${contactId}/stateid@${flowId}`,\n resource: stateId,\n type: 'text/plain',\n };\n\n await this.postCommand(stateRequest);\n\n // Change contact bot\n const botRequest: Vnd.Lime.Transport.Payload.Request<string> = {\n method: Vnd.Lime.Method.SET,\n uri: `/contexts/${contactId}/master-state`,\n resource: `${botId}@msging.net`,\n type: 'text/plain',\n };\n\n await this.postCommand(botRequest);\n }\n}\n\n/**\n * Custom error class for Blip errors\n */\nexport class BlipError extends Error {\n public code: number;\n\n constructor(message: string, code: number) {\n super(message);\n this.name = 'BlipError';\n this.code = code;\n Object.setPrototypeOf(this, BlipError.prototype);\n }\n}\n\nexport default Blip;\n","/**\n * Type definitions for Blip Platform API\n * Based on the LIME protocol and Blip extensions\n */\n\nexport namespace Vnd {\n export namespace Lime {\n export enum Method {\n GET = 'get',\n SET = 'set',\n DELETE = 'delete',\n OBSERVE = 'observe',\n SUBSCRIBE = 'subscribe',\n MERGE = 'merge',\n }\n\n export enum Status {\n SUCCESS = 'success',\n FAILURE = 'failure',\n }\n\n export namespace Transport {\n export namespace Payload {\n export interface Request<T = any> {\n id?: string;\n to?: string;\n method: Method;\n uri: string;\n type?: string;\n resource?: T;\n }\n\n export interface Response<T = any> {\n id?: string;\n from?: string;\n to?: string;\n method?: Method;\n status: Status;\n type?: string;\n resource?: T;\n reason?: {\n code: number;\n description: string;\n };\n }\n }\n }\n\n export interface Message<T = any> {\n id?: string;\n to: string;\n from?: string;\n type: string;\n content: T;\n }\n\n export namespace Command {\n export interface Resource<T = any> {\n [key: string]: T;\n }\n }\n }\n\n export namespace Iris {\n export interface ThreadMessage {\n id: string;\n direction: 'sent' | 'received';\n type: string;\n content: any;\n date: string;\n status?: string;\n [key: string]: any;\n }\n\n export interface Contact {\n identity: string;\n name?: string;\n email?: string;\n phoneNumber?: string;\n taxDocument?: string;\n gender?: string;\n city?: string;\n extras?: Record<string, string>;\n [key: string]: any;\n }\n\n export interface EventTrack {\n category: string;\n action: string;\n contact?: {\n identity: string;\n };\n extras?: any;\n count?: number;\n }\n\n export interface Ticket {\n id: string;\n sequentialId: number;\n ownerIdentity: string;\n customerIdentity: string;\n providerIdentity: string;\n status: string;\n storageDate: string;\n externalId?: string;\n rating?: number;\n team?: string;\n agentIdentity?: string;\n closedDate?: string;\n transfers?: number;\n [key: string]: any;\n }\n\n export namespace ActiveCampaign {\n export interface AudienceSummary {\n total: number;\n sent: number;\n failed: number;\n scheduled: number;\n canceled: number;\n }\n }\n }\n}\n\nexport interface BlipConfig {\n botUrl: string;\n botKey: string;\n timeout?: number;\n maxRetries?: number;\n}\n\nexport interface DateInterval {\n from?: Date;\n to: Date;\n}\n\nexport type PartiallyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\nexport type PartiallyRequiredStrict<T, K extends keyof T> = T & Required<Pick<T, K>>;\n","import { DispatcherRepository } from './DispatcherRepository.js';\nimport { Message, MessageState, MessageStatus, QueryFilter } from '../types/index.js';\nimport { getLogger } from '../utils/logger.js';\nimport IORedis from 'ioredis';\n\nconst logger = getLogger('DispatcherQuery');\n\nexport class DispatcherQuery {\n private repository: DispatcherRepository;\n\n constructor(repository: DispatcherRepository) {\n this.repository = repository;\n }\n\n private get client(): IORedis {\n return this.repository.redis;\n }\n\n public async query(filter: QueryFilter): Promise<Message[]> {\n const keysToIntersect: string[] = [];\n const prefix = this.repository['keyPrefix'];\n\n // 1. Build Intersection Sets using new INDEX keys\n if (filter.contactId) {\n keysToIntersect.push(this.repository.getContactKey(filter.contactId));\n }\n\n if (filter.descriptorId) {\n keysToIntersect.push(this.repository.getDescriptorKey(filter.descriptorId));\n }\n\n if (filter.status) {\n const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];\n if (statuses.length === 1) {\n keysToIntersect.push(this.repository.getStatusKey(statuses[0]));\n } else if (statuses.length > 1) {\n // For MVP, if multiple statuses, we intersect the FIRST one and filter the rest in memory.\n keysToIntersect.push(this.repository.getStatusKey(statuses[0]));\n }\n }\n\n // Now safe to use state index for ALL states (even SCHEDULED/QUEUED) because we index them as Sets too.\n if (filter.state) {\n const states = Array.isArray(filter.state) ? filter.state : [filter.state];\n if (states.length === 1) {\n keysToIntersect.push(this.repository.getStateKey(states[0]));\n }\n }\n\n let messageIds: string[] = [];\n\n if (keysToIntersect.length > 0) {\n // Execute Intersection\n messageIds = await this.client.sinter(keysToIntersect);\n } else {\n // Fallback: No filters provided (e.g. \"All\" selected without other filters)\n // We want to return ALL messages. We can do this by Unioning all Status sets.\n // This allows us to see Delivered/Failed messages even if not filtering by them.\n const allStatusKeys = Object.values(MessageStatus).map((status) =>\n this.repository.getStatusKey(status)\n );\n messageIds = await this.client.sunion(allStatusKeys);\n }\n\n // 2. Handle OR logic for multiple statuses/states if needed\n // (Omitted for MVP, assuming single values or handling in memory for small intersections)\n\n // 3. Paging\n const skip = filter.skip ?? 0;\n const size = filter.size ?? 50;\n const pagedIds = messageIds.slice(skip, skip + size);\n\n // 4. Fetch Messages & Lazy Cleanup\n const messages: Message[] = [];\n const idsToRemove: string[] = [];\n\n for (const id of pagedIds) {\n const message = await this.repository.getMessage(id);\n if (message) {\n // Validate in-memory filters (e.g. multiple statuses, or if SINTER wasn't perfect)\n if (filter.status) {\n const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];\n if (!statuses.includes(message.status)) continue;\n }\n if (filter.state) {\n const states = Array.isArray(filter.state) ? filter.state : [filter.state];\n if (!states.includes(message.state)) continue;\n }\n\n messages.push(message);\n } else {\n // Message ID exists in index but not in storage -> Expired\n idsToRemove.push(id);\n }\n }\n\n // Lazy Cleanup\n if (idsToRemove.length > 0) {\n this.cleanupIndices(idsToRemove, filter);\n }\n\n return messages;\n }\n\n private async cleanupIndices(ids: string[], context: QueryFilter): Promise<void> {\n const pipeline = this.client.pipeline();\n const prefix = this.repository['keyPrefix'];\n\n // Basic cleanup: Remove from the keys we just queried.\n if (context.contactId) {\n pipeline.srem(this.repository.getContactKey(context.contactId), ids);\n }\n if (context.descriptorId) {\n pipeline.srem(this.repository.getDescriptorKey(context.descriptorId), ids);\n }\n\n if (context.status) {\n const statuses = Array.isArray(context.status) ? context.status : [context.status];\n statuses.forEach((s) => {\n pipeline.srem(this.repository.getStatusKey(s), ids);\n });\n }\n if (context.state) {\n const states = Array.isArray(context.state) ? context.state : [context.state];\n states.forEach((s) => {\n pipeline.srem(this.repository.getStateKey(s), ids);\n });\n }\n\n await pipeline.exec();\n logger.debug('[cleanupIndices] Removed expired IDs from checked indices', {\n count: ids.length,\n });\n }\n}\n"],"mappings":"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","names":["Dispatcher_exports","__export","Dispatcher","__toCommonJS","import_uuid","import_bullmq","MessageState","MessageStatus","import_debug","LogLevels","loggers","serializeArg","arg","createLogFunction","debugInstance","args","serialized","getLogger","scope","debug","infoInstance","warnInstance","errorInstance","logger","logger","getLogger","DispatcherStateMachine","id","repository","emit","message","targetState","targetStatus","data","fromState","fromStatus","now","status","import_axios","import_uuid","Vnd","Lime","Method","Status","logger","getLogger","Blip","contract","key","timeout","botUrl","axios","response","error","reason","BlipError","request","payload","uuidv4","result","Vnd","message","contactId","data","contact","id","messageId","limeMessage","statePriority","bestState","maxPriority","item","state","priority","currentMessageId","attempts","maxAttempts","nextMessage","category","action","extras","botId","stateId","flowIdRequest","flowIdResponse","flowId","stateRequest","botRequest","_BlipError","code","logger","getLogger","DispatcherQuery","repository","filter","keysToIntersect","prefix","statuses","states","messageIds","allStatusKeys","MessageStatus","status","skip","size","pagedIds","messages","idsToRemove","id","message","ids","context","pipeline","s","logger","getLogger","Dispatcher","id","repository","connection","options","DispatcherStateMachine","event","message","Blip","DispatcherQuery","job","error","err","callback","metrics","states","MessageState","state","statuses","MessageStatus","status","a","b","descriptor","contactId","payload","validatedContactId","messageData","now","uuidv4","mergedOptions","schedule","messageOptions","scheduledTo","delay","scheduleTime","messageId","contact","intent","rest","key","value","nextCheckDelay","updated","newStatus","finalStatus","startTime","pending","sending","all","expiredIds","retryDelay","shifts","date","dayOfWeek","dayBit","shift","gmt","offset","shiftDate","currentTime","startHour","startMin","endHour","endMin","endTime","daysAhead","checkDate","availableShifts","aHour","aMin","bHour","bMin","shiftStart","shiftStartUTC"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a}from"../chunk-OR34YAY3.mjs";import"../chunk-UFUGEIFL.mjs";import"../chunk-QA6PFVGP.mjs";import"../chunk-HYW2O7ZX.mjs";import"../chunk-XLRB2LAD.mjs";import"../chunk-6IAFH374.mjs";import"../chunk-OXXLVJVC.mjs";export{a as Dispatcher};
|
|
2
|
+
//# sourceMappingURL=Dispatcher.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CallbackEvent, Message, MessagePayload, MessageData, MessageOptions } from '../types/index.mjs';
|
|
2
|
+
import { Blip } from './Blip.mjs';
|
|
3
|
+
import '../types/blip.mjs';
|
|
4
|
+
|
|
5
|
+
type CallbackMap = Partial<Record<CallbackEvent, (message: Message, api: Blip, dispatcherId: string) => void>>;
|
|
6
|
+
interface DispatcherDescriptorOptions extends MessageOptions {
|
|
7
|
+
toContactId?: (contactId: string) => string;
|
|
8
|
+
}
|
|
9
|
+
declare class DispatcherDescriptor {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
private transformFn;
|
|
12
|
+
private callbacks;
|
|
13
|
+
private options?;
|
|
14
|
+
private contactIdTransform;
|
|
15
|
+
constructor(id: string, transform: (payload: MessagePayload) => MessageData, options?: DispatcherDescriptorOptions);
|
|
16
|
+
transform(payload: MessagePayload): MessageData;
|
|
17
|
+
toContactId(contactId: string): string;
|
|
18
|
+
get messageOptions(): MessageOptions | undefined;
|
|
19
|
+
on(event: CallbackEvent, callback: (message: Message, api: Blip, dispatcherId: string) => void): this;
|
|
20
|
+
emit(event: CallbackEvent, message: Message, api: Blip, dispatcherId: string): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { type CallbackMap, DispatcherDescriptor, type DispatcherDescriptorOptions };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var n=Object.defineProperty;var o=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var c=(i,t)=>{for(var s in t)n(i,s,{get:t[s],enumerable:!0})},d=(i,t,s,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of l(t))!p.call(i,e)&&e!==s&&n(i,e,{get:()=>t[e],enumerable:!(a=o(t,e))||a.enumerable});return i};var g=i=>d(n({},"__esModule",{value:!0}),i);var h={};c(h,{DispatcherDescriptor:()=>r});module.exports=g(h);var r=class{constructor(t,s,a){this.callbacks={};this.id=t,this.transformFn=s,this.contactIdTransform=a?.toContactId||(e=>e),this.options=a}transform(t){return this.transformFn(t)}toContactId(t){return this.contactIdTransform(t)}get messageOptions(){return this.options}on(t,s){let a=this.options?.finalStatus||"DELIVERED";if(t==="read"&&a!=="READ"&&a!=="REPLIED")throw new Error(`Cannot listen to 'read' event when finalStatus is '${a}'. Set finalStatus to 'READ' or 'REPLIED'.`);if(t==="replied"&&a!=="REPLIED")throw new Error(`Cannot listen to 'replied' event when finalStatus is '${a}'. Set finalStatus to 'REPLIED'.`);return this.callbacks[t]=s,this}emit(t,s,a,e){this.callbacks[t]?.(s,a,e)}};0&&(module.exports={DispatcherDescriptor});
|
|
2
|
+
//# sourceMappingURL=DispatcherDescriptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/DispatcherDescriptor.ts"],"sourcesContent":["import {\n Message,\n MessageData,\n MessagePayload,\n CallbackEvent,\n MessageOptions,\n} from '../types/index.js';\nimport { Blip } from './Blip.js';\n\nexport type CallbackMap = Partial<\n Record<CallbackEvent, (message: Message, api: Blip, dispatcherId: string) => void>\n>;\n\nexport interface DispatcherDescriptorOptions extends MessageOptions {\n toContactId?: (contactId: string) => string;\n}\n\nexport class DispatcherDescriptor {\n public readonly id: string;\n private transformFn: (payload: MessagePayload) => MessageData;\n private callbacks: CallbackMap = {};\n private options?: DispatcherDescriptorOptions;\n private contactIdTransform: (contactId: string) => string;\n\n constructor(\n id: string,\n transform: (payload: MessagePayload) => MessageData,\n options?: DispatcherDescriptorOptions\n ) {\n this.id = id;\n this.transformFn = transform;\n this.contactIdTransform = options?.toContactId || ((cid) => cid);\n this.options = options;\n }\n\n transform(payload: MessagePayload): MessageData {\n return this.transformFn(payload);\n }\n\n toContactId(contactId: string): string {\n return this.contactIdTransform(contactId);\n }\n\n get messageOptions(): MessageOptions | undefined {\n return this.options;\n }\n\n on(\n event: CallbackEvent,\n callback: (message: Message, api: Blip, dispatcherId: string) => void\n ): this {\n const finalStatus = this.options?.finalStatus || 'DELIVERED';\n\n if (event === 'read' && finalStatus !== 'READ' && finalStatus !== 'REPLIED') {\n throw new Error(\n `Cannot listen to 'read' event when finalStatus is '${finalStatus}'. Set finalStatus to 'READ' or 'REPLIED'.`\n );\n }\n\n if (event === 'replied' && finalStatus !== 'REPLIED') {\n throw new Error(\n `Cannot listen to 'replied' event when finalStatus is '${finalStatus}'. Set finalStatus to 'REPLIED'.`\n );\n }\n\n this.callbacks[event] = callback;\n return this;\n }\n\n emit(event: CallbackEvent, message: Message, api: Blip, dispatcherId: string): void {\n this.callbacks[event]?.(message, api, dispatcherId);\n }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,0BAAAE,IAAA,eAAAC,EAAAH,GAiBO,IAAME,EAAN,KAA2B,CAOhC,YACEE,EACAC,EACAC,EACA,CARF,KAAQ,UAAyB,CAAC,EAShC,KAAK,GAAKF,EACV,KAAK,YAAcC,EACnB,KAAK,mBAAqBC,GAAS,cAAiBC,GAAQA,GAC5D,KAAK,QAAUD,CACjB,CAEA,UAAUE,EAAsC,CAC9C,OAAO,KAAK,YAAYA,CAAO,CACjC,CAEA,YAAYC,EAA2B,CACrC,OAAO,KAAK,mBAAmBA,CAAS,CAC1C,CAEA,IAAI,gBAA6C,CAC/C,OAAO,KAAK,OACd,CAEA,GACEC,EACAC,EACM,CACN,IAAMC,EAAc,KAAK,SAAS,aAAe,YAEjD,GAAIF,IAAU,QAAUE,IAAgB,QAAUA,IAAgB,UAChE,MAAM,IAAI,MACR,sDAAsDA,CAAW,4CACnE,EAGF,GAAIF,IAAU,WAAaE,IAAgB,UACzC,MAAM,IAAI,MACR,yDAAyDA,CAAW,kCACtE,EAGF,YAAK,UAAUF,CAAK,EAAIC,EACjB,IACT,CAEA,KAAKD,EAAsBG,EAAkBC,EAAWC,EAA4B,CAClF,KAAK,UAAUL,CAAK,IAAIG,EAASC,EAAKC,CAAY,CACpD,CACF","names":["DispatcherDescriptor_exports","__export","DispatcherDescriptor","__toCommonJS","id","transform","options","cid","payload","contactId","event","callback","finalStatus","message","api","dispatcherId"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { DispatcherRepository } from './DispatcherRepository.mjs';
|
|
3
|
+
import '../types/index.mjs';
|
|
4
|
+
import '../types/blip.mjs';
|
|
5
|
+
import 'ioredis';
|
|
6
|
+
|
|
7
|
+
interface MonitorRule {
|
|
8
|
+
type: 'failure_rate' | 'queue_size';
|
|
9
|
+
threshold: number;
|
|
10
|
+
window?: number;
|
|
11
|
+
debounce?: number;
|
|
12
|
+
}
|
|
13
|
+
interface MonitorOptions {
|
|
14
|
+
interval?: number;
|
|
15
|
+
rules: MonitorRule[];
|
|
16
|
+
historySize?: number;
|
|
17
|
+
}
|
|
18
|
+
type MonitorAlert = {
|
|
19
|
+
type: string;
|
|
20
|
+
message: string;
|
|
21
|
+
level: 'warning' | 'critical';
|
|
22
|
+
details: any;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
};
|
|
25
|
+
declare class DispatcherMonitor extends EventEmitter {
|
|
26
|
+
private id;
|
|
27
|
+
private repository;
|
|
28
|
+
private options;
|
|
29
|
+
private queue;
|
|
30
|
+
private worker;
|
|
31
|
+
private queueName;
|
|
32
|
+
private history;
|
|
33
|
+
private lastAlerts;
|
|
34
|
+
private activeAlerts;
|
|
35
|
+
private isRunning;
|
|
36
|
+
constructor(id: string, repository: DispatcherRepository, options: MonitorOptions);
|
|
37
|
+
start(): Promise<void>;
|
|
38
|
+
stop(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Independent metrics collection directly from Repository
|
|
41
|
+
*/
|
|
42
|
+
private collectMetrics;
|
|
43
|
+
private check;
|
|
44
|
+
private cleanHistory;
|
|
45
|
+
private evaluateRule;
|
|
46
|
+
private isDebounced;
|
|
47
|
+
private emitAlert;
|
|
48
|
+
private resolveAlert;
|
|
49
|
+
private findSnapshotAt;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { DispatcherMonitor, type MonitorAlert, type MonitorOptions, type MonitorRule };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var N=Object.create;var d=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty;var $=(r,s)=>{for(var t in s)d(r,t,{get:s[t],enumerable:!0})},I=(r,s,t,e)=>{if(s&&typeof s=="object"||typeof s=="function")for(let n of C(s))!P.call(r,n)&&n!==t&&d(r,n,{get:()=>s[n],enumerable:!(e=O(s,n))||e.enumerable});return r};var q=(r,s,t)=>(t=r!=null?N(F(r)):{},I(s||!r||!r.__esModule?d(t,"default",{value:r,enumerable:!0}):t,r)),z=r=>I(d({},"__esModule",{value:!0}),r);var U={};$(U,{DispatcherMonitor:()=>w});module.exports=z(U);var M=require("events"),m=require("bullmq");var v=(i=>(i.INIT="INIT",i.DISPATCHED="DISPATCHED",i.SCHEDULED="SCHEDULED",i.QUEUED="QUEUED",i.FINAL="FINAL",i))(v||{}),D=(o=>(o.INIT="INIT",o.PENDING="PENDING",o.SENDING="SENDING",o.DELIVERED="DELIVERED",o.READ="READ",o.REPLIED="REPLIED",o.FAILED="FAILED",o.CANCELED="CANCELED",o))(D||{});var p=q(require("debug")),g={debug:"debug",info:"info",warn:"warn",error:"error"},A=new Map;function T(r){if(r==null)return r;if(typeof r=="object")try{return JSON.stringify(r,null,2)}catch{return r}return r}function h(r){return(...s)=>{let t=s.map(T);r(...t)}}function x(r){if(!A.has(r)){let s=(0,p.default)(`${r}:${g.debug}`),t=(0,p.default)(`${r}:${g.info}`),e=(0,p.default)(`${r}:${g.warn}`),n=(0,p.default)(`${r}:${g.error}`);n.log=console.error.bind(console);let i={debug:h(s),info:h(t),warn:h(e),error:h(n)};A.set(r,i)}return A.get(r)}var l=x("DispatcherMonitor"),w=class extends M.EventEmitter{constructor(t,e,n){super();this.history=[];this.lastAlerts={};this.activeAlerts=new Set;this.isRunning=!1;this.id=t,this.repository=e,this.options={interval:6e4,historySize:1e3,...n},this.queueName=`monitor-${this.id}`,this.queue=new m.Queue(this.queueName,{connection:e.redis,defaultJobOptions:{removeOnComplete:!0,removeOnFail:!0}}),this.worker=new m.Worker(this.queueName,async i=>{i.name==="check"&&await this.check()},{connection:e.redis}),this.worker.on("error",i=>l.error("[MonitorWorker] Error",i)),this.worker.on("failed",(i,u)=>l.error(`[MonitorWorker] Job ${i?.id} failed`,u))}async start(){this.isRunning||(l.info("[Monitor] Started"),await this.queue.obliterate({force:!0}),await this.queue.add("check",{},{repeat:{every:this.options.interval,immediately:!0}}),this.isRunning=!0)}async stop(){this.isRunning=!1,await this.queue.close(),await this.worker.close(),l.info("[Monitor] Stopped")}async collectMetrics(){let t=this.repository,e={total:0,byState:{},byStatus:{},cumulative:{dispatched:0,delivered:0,failed:0}},i=Object.values(v).map(async a=>{e.byState[a]=await t.countMessages({state:a})}),c=Object.values(D).map(async a=>{e.byStatus[a]=await t.countMessages({status:a})}),f=["dispatched","delivered","failed"].map(async a=>{e.cumulative[a]=await t.getMetric(a)});return await Promise.all([...i,...c,...f]),e.total=Object.values(e.byState).reduce((a,y)=>a+(y||0),0),e}async check(){try{let t=await this.collectMetrics(),e=Date.now();this.history.push({timestamp:e,metrics:t}),this.cleanHistory();for(let n of this.options.rules)await this.evaluateRule(n,t,e)}catch(t){l.error("[Monitor] Error during check",t)}}cleanHistory(){let t=this.options.historySize;this.history.length>t&&(this.history=this.history.slice(this.history.length-t));let e=Math.max(...this.options.rules.map(i=>i.window||0)),n=Date.now()-e-6e4;if(this.history.length>0&&this.history[0].timestamp<n){let i=this.history.findIndex(u=>u.timestamp>=n);i>0&&(this.history=this.history.slice(i))}}async evaluateRule(t,e,n){let i=`${t.type}`,u=!1,c=0,o={};switch(t.type){case"queue_size":let f=e.byState.FINAL||0;c=e.total-f,u=c>t.threshold,o={current:c,threshold:t.threshold};break;case"failure_rate":if(!t.window){l.warn("[Monitor] failure_rate rule missing window");return}let a=this.findSnapshotAt(n-t.window);if(!a)return;let y=e.cumulative.failed,L=a.metrics.cumulative.failed,E=y-L,S=e.cumulative.dispatched,R=a.metrics.cumulative.dispatched,b=S-R;b===0?c=0:c=E/b,u=c>t.threshold,o={rate:(c*100).toFixed(2)+"%",threshold:(t.threshold*100).toFixed(2)+"%",failed:E,dispatched:b,window:t.window};break}u?this.activeAlerts.has(i)?t.debounce&&!this.isDebounced(i,t.debounce)&&this.emitAlert(i,t,c,o):(this.emitAlert(i,t,c,o),this.activeAlerts.add(i)):this.activeAlerts.has(i)&&(this.resolveAlert(i,t),this.activeAlerts.delete(i))}isDebounced(t,e){if(!e)return!1;let n=this.lastAlerts[t];return n?Date.now()-n<e:!1}emitAlert(t,e,n,i){l.warn(`[Monitor] Alert triggered: ${e.type}`,i),this.lastAlerts[t]=Date.now();let u={type:e.type,message:`${e.type} exceeded threshold`,level:"warning",details:i,timestamp:new Date().toISOString()};this.emit("alert",u)}resolveAlert(t,e){l.info(`[Monitor] Alert resolved: ${e.type}`);let n={type:e.type,message:`${e.type} resolved`,level:"warning",details:{},timestamp:new Date().toISOString()};this.emit("resolved",n)}findSnapshotAt(t){if(this.history.length===0)return null;for(let e of this.history)if(e.timestamp>=t)return e;return this.history[0]}};0&&(module.exports={DispatcherMonitor});
|
|
2
|
+
//# sourceMappingURL=DispatcherMonitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/DispatcherMonitor.ts","../../src/types/index.ts","../../src/utils/logger.ts"],"sourcesContent":["import { EventEmitter } from 'events';\nimport { Queue, Worker, Job } from 'bullmq';\nimport { DispatcherRepository } from './DispatcherRepository.js';\nimport { DispatcherMetrics, MessageState, MessageStatus } from '../types/index.js';\nimport { getLogger } from '../utils/logger.js';\n\nconst logger = getLogger('DispatcherMonitor');\n\nexport interface MonitorRule {\n type: 'failure_rate' | 'queue_size';\n threshold: number;\n window?: number; // Time window in ms for rate calculation (only for failure_rate)\n debounce?: number; // Time to wait before re-alerting in ms\n}\n\nexport interface MonitorOptions {\n interval?: number; // Polling interval in ms (default: 60s)\n rules: MonitorRule[];\n historySize?: number; // Max snapshots to keep (default: 1000)\n}\n\ninterface MetricSnapshot {\n timestamp: number;\n metrics: DispatcherMetrics;\n}\n\nexport type MonitorAlert = {\n type: string;\n message: string;\n level: 'warning' | 'critical';\n details: any;\n timestamp: string;\n};\n\nexport class DispatcherMonitor extends EventEmitter {\n private id: string;\n private repository: DispatcherRepository;\n private options: MonitorOptions;\n\n // BullMQ for scheduling\n private queue: Queue;\n private worker: Worker;\n private queueName: string;\n\n private history: MetricSnapshot[] = [];\n private lastAlerts: Record<string, number> = {};\n private activeAlerts: Set<string> = new Set();\n private isRunning: boolean = false;\n\n constructor(id: string, repository: DispatcherRepository, options: MonitorOptions) {\n super();\n this.id = id;\n this.repository = repository;\n this.options = {\n interval: 60000,\n historySize: 1000,\n ...options,\n };\n\n this.queueName = `monitor-${this.id}`;\n\n // Reuse dispatcher's Redis connection for BullMQ\n // Note: BullMQ will manage duplication for blocking connections if needed\n this.queue = new Queue(this.queueName, {\n // @ts-ignore\n connection: repository.redis,\n defaultJobOptions: {\n removeOnComplete: true,\n removeOnFail: true,\n },\n });\n\n this.worker = new Worker(\n this.queueName,\n async (job: Job) => {\n if (job.name === 'check') {\n await this.check();\n }\n },\n {\n // @ts-ignore\n connection: repository.redis,\n }\n );\n\n this.worker.on('error', (err) => logger.error('[MonitorWorker] Error', err));\n this.worker.on('failed', (job, err) =>\n logger.error(`[MonitorWorker] Job ${job?.id} failed`, err)\n );\n }\n\n async start(): Promise<void> {\n if (this.isRunning) return;\n\n logger.info('[Monitor] Started');\n\n // Clean up any old repeatable jobs\n await this.queue.obliterate({ force: true });\n\n // Add repeatable job or just simple delayed recursion?\n // Using repeatable job is robust.\n await this.queue.add(\n 'check',\n {},\n {\n repeat: {\n every: this.options.interval,\n immediately: true, // Run on start\n },\n }\n );\n\n this.isRunning = true;\n }\n\n async stop(): Promise<void> {\n this.isRunning = false;\n await this.queue.close();\n await this.worker.close();\n logger.info('[Monitor] Stopped');\n }\n\n /**\n * Independent metrics collection directly from Repository\n */\n private async collectMetrics(): Promise<DispatcherMetrics> {\n const repository = this.repository;\n\n const metrics: DispatcherMetrics = {\n total: 0,\n byState: {},\n byStatus: {},\n cumulative: {\n dispatched: 0,\n delivered: 0,\n failed: 0,\n },\n };\n\n // Parallelize fetch? For simplicity/consistency, sequential is safer for now but parallel is faster.\n // Let's optimize slightly with Promise.all for groups.\n\n const states = Object.values(MessageState);\n const statePromises = states.map(async (state) => {\n metrics.byState[state] = await repository.countMessages({ state });\n });\n\n const statuses = Object.values(MessageStatus);\n const statusPromises = statuses.map(async (status) => {\n metrics.byStatus[status] = await repository.countMessages({ status });\n });\n\n const cumulativeKeys = ['dispatched', 'delivered', 'failed'] as const;\n const cumulativePromises = cumulativeKeys.map(async (key) => {\n metrics.cumulative![key] = await repository.getMetric(key);\n });\n\n await Promise.all([...statePromises, ...statusPromises, ...cumulativePromises]);\n\n metrics.total = Object.values(metrics.byState).reduce((a, b) => a + (b || 0), 0);\n\n return metrics;\n }\n\n private async check(): Promise<void> {\n try {\n const metrics = await this.collectMetrics();\n const now = Date.now();\n\n // Record snapshot\n this.history.push({ timestamp: now, metrics });\n this.cleanHistory();\n\n // Check rules\n for (const rule of this.options.rules) {\n await this.evaluateRule(rule, metrics, now);\n }\n } catch (error) {\n logger.error('[Monitor] Error during check', error);\n }\n }\n\n private cleanHistory(): void {\n const maxSize = this.options.historySize!;\n if (this.history.length > maxSize) {\n this.history = this.history.slice(this.history.length - maxSize);\n }\n\n // Also remove snapshots older than the largest window needed\n const maxWindow = Math.max(...this.options.rules.map((r) => r.window || 0));\n const cutoff = Date.now() - maxWindow - 60000; // Buffer\n if (this.history.length > 0 && this.history[0].timestamp < cutoff) {\n const idx = this.history.findIndex((s) => s.timestamp >= cutoff);\n if (idx > 0) {\n this.history = this.history.slice(idx);\n }\n }\n }\n\n private async evaluateRule(\n rule: MonitorRule,\n currentMetrics: DispatcherMetrics,\n now: number\n ): Promise<void> {\n const alertKey = `${rule.type}`;\n let triggered = false;\n let value = 0;\n let details = {};\n\n switch (rule.type) {\n case 'queue_size':\n const finalCount = currentMetrics.byState.FINAL || 0;\n value = currentMetrics.total - finalCount;\n triggered = value > rule.threshold;\n details = { current: value, threshold: rule.threshold };\n break;\n\n case 'failure_rate':\n if (!rule.window) {\n logger.warn('[Monitor] failure_rate rule missing window');\n return;\n }\n\n const previousSnapshot = this.findSnapshotAt(now - rule.window);\n if (!previousSnapshot) {\n return;\n }\n\n const currFailed = currentMetrics.cumulative!.failed;\n const prevFailed = previousSnapshot.metrics.cumulative!.failed;\n const failedDelta = currFailed - prevFailed;\n\n const currDispatched = currentMetrics.cumulative!.dispatched;\n const prevDispatched = previousSnapshot.metrics.cumulative!.dispatched;\n const dispatchedDelta = currDispatched - prevDispatched;\n\n if (dispatchedDelta === 0) {\n value = 0;\n } else {\n value = failedDelta / dispatchedDelta;\n }\n\n triggered = value > rule.threshold;\n details = {\n rate: (value * 100).toFixed(2) + '%',\n threshold: (rule.threshold * 100).toFixed(2) + '%',\n failed: failedDelta,\n dispatched: dispatchedDelta,\n window: rule.window,\n };\n break;\n }\n\n if (triggered) {\n if (!this.activeAlerts.has(alertKey)) {\n this.emitAlert(alertKey, rule, value, details);\n this.activeAlerts.add(alertKey);\n } else {\n if (rule.debounce && !this.isDebounced(alertKey, rule.debounce)) {\n this.emitAlert(alertKey, rule, value, details);\n }\n }\n } else {\n if (this.activeAlerts.has(alertKey)) {\n this.resolveAlert(alertKey, rule);\n this.activeAlerts.delete(alertKey);\n }\n }\n }\n\n private isDebounced(key: string, debounceTime?: number): boolean {\n if (!debounceTime) return false;\n const last = this.lastAlerts[key];\n if (!last) return false;\n return Date.now() - last < debounceTime;\n }\n\n private emitAlert(key: string, rule: MonitorRule, value: number, details: any): void {\n logger.warn(`[Monitor] Alert triggered: ${rule.type}`, details);\n this.lastAlerts[key] = Date.now();\n\n const alert: MonitorAlert = {\n type: rule.type,\n message: `${rule.type} exceeded threshold`,\n level: 'warning',\n details,\n timestamp: new Date().toISOString(),\n };\n\n this.emit('alert', alert);\n }\n\n private resolveAlert(key: string, rule: MonitorRule): void {\n logger.info(`[Monitor] Alert resolved: ${rule.type}`);\n const alert: MonitorAlert = {\n type: rule.type,\n message: `${rule.type} resolved`,\n level: 'warning',\n details: {},\n timestamp: new Date().toISOString(),\n };\n this.emit('resolved', alert);\n }\n\n private findSnapshotAt(timestamp: number): MetricSnapshot | null {\n if (this.history.length === 0) return null;\n for (const snap of this.history) {\n if (snap.timestamp >= timestamp) {\n return snap;\n }\n }\n return this.history[0];\n }\n}\n","import { Vnd } from './blip';\n\nexport type Contact = Omit<Vnd.Iris.Contact, 'identity'>;\n\nexport type MessageData = {\n type: string;\n content: Record<string, any> | string;\n};\n\nexport type MessagePayload = Record<string, any> | Array<any> | string;\n\nexport enum MessageState {\n INIT = 'INIT',\n DISPATCHED = 'DISPATCHED',\n SCHEDULED = 'SCHEDULED',\n QUEUED = 'QUEUED',\n FINAL = 'FINAL',\n}\n\nexport enum MessageStatus {\n INIT = 'INIT',\n PENDING = 'PENDING',\n SENDING = 'SENDING',\n DELIVERED = 'DELIVERED',\n READ = 'READ',\n REPLIED = 'REPLIED',\n FAILED = 'FAILED',\n CANCELED = 'CANCELED',\n}\n\nexport enum DispatchState {\n ACCEPTED = 'accepted',\n DISPATCHED = 'dispatched',\n RECEIVED = 'received',\n CONSUMED = 'consumed',\n FAILED = 'failed',\n}\n\nexport interface Message {\n messageId: string;\n contactId: string;\n descriptorId: string;\n\n payload: MessageData;\n status: MessageStatus;\n state: MessageState;\n\n attempts?: number;\n retries?: number;\n\n options?: MessageOptions; // Persisted options for scheduled/retried messages\n\n expiresAt?: string; // ISO 8601 datetime\n\n scheduledTo?: string; // ISO 8601 datetime\n sentAt?: string; // ISO 8601 datetime\n acceptedAt?: string; // ISO 8601 datetime - When message was accepted by gateway (SENDING status)\n lastDispatchAttemptAt?: string; // ISO 8601 datetime - When last dispatch attempt started\n createdAt: string; // ISO 8601 datetime\n deliveredAt?: string; // ISO 8601 datetime\n readAt?: string; // ISO 8601 datetime\n repliedAt?: string; // ISO 8601 datetime\n failedAt?: string; // ISO 8601 datetime\n\n error?: string;\n}\n\nexport interface Shift {\n days: number; // Bitmask: use Weekdays enum (e.g., Weekdays.MONDAY | Weekdays.FRIDAY) or manual (31=Mon-Fri)\n start: string; // HH:mm format (e.g., \"09:00\")\n end: string; // HH:mm format (e.g., \"18:00\")\n gmt?: string; // Timezone offset (default: \"-3\" for BRT)\n}\n\nexport enum Weekdays {\n MONDAY = 1,\n TUESDAY = 2,\n WEDNESDAY = 4,\n THURSDAY = 8,\n FRIDAY = 16,\n SATURDAY = 32,\n SUNDAY = 64,\n}\n\nexport interface Intent {\n intent: string;\n dueDate?: string;\n event?: string;\n payload?: any | null;\n expired?: {\n event?: string;\n intent?: string;\n };\n}\n\nexport type CallbackEvent =\n | 'dispatch' // Before message send process begins\n | 'sending' // MessageStatus.SENDING - message accepted by gateway\n | 'delivered' // MessageStatus.DELIVERED - message successfully delivered\n | 'failed' // MessageStatus.FAILED - message failed permanently\n | 'read' // MessageStatus.READ - message read by recipient\n | 'replied' // MessageStatus.REPLIED - recipient replied\n | 'scheduled' // MessageState.SCHEDULED - message scheduled\n | 'retry' // Retry attempt scheduled\n | 'evicted' // Message evicted from state machine due to TTL\n | 'canceled'; // Message canceled before send\n\nexport type ConnectionConfig = {\n contract: string; // Contract ID (without https:// prefix)\n key: string; // Bot authorization key\n};\n\nexport interface MessageOptions {\n shifts?: Array<Shift>; // Business hours constraints\n contact?: Contact;\n state?: {\n // Bot state to set after send\n stateId: string;\n botId: string;\n botIds?: Array<string>; // Bot IDs to reset before setting state\n };\n intent?: string | Intent; // Intent to trigger after send\n finalStatus?: 'DELIVERED' | 'READ' | 'REPLIED'; // Default: 'DELIVERED'\n}\n\nexport interface DispatchMessageOptions extends MessageOptions {\n schedule?: string; // ISO 8601 datetime to schedule send\n}\n\nexport interface DispatcherOptions {\n maxRetries?: number; // Default: 0\n retryIntervals?: number[]; // Default: [1000, 5000, 15000] (exponential backoff in ms)\n\n // Storage Duration (Redis TTL)\n retention?: number; // Default: 3600000 ms (1 hour) - Time to keep message in storage after creation/dispatch\n\n // State Constraints (Force transition to FAILED)\n timeouts?: {\n pending?: number; // Default: 10000 ms (10 seconds)\n sending?: number; // Default: 60000 ms (60 seconds)\n };\n\n batchSize?: number; // Default: 100 messages per polling cycle\n pollingIntervals?: {\n scheduled?: number; // Default: 5000 ms\n pending?: number; // Default: 5000 ms\n sending?: number; // Default: 10000 ms\n delivered?: number; // Default: 1800000 ms (30 minutes)\n read?: number; // Default: 3600000 ms (60 minutes)\n queue?: number; // Default: 100 ms\n };\n maxQueueSize?: number; // Maximum number of pending/scheduled messages. Oldest evicted if exceeded.\n rateLimits?: {\n global?: { points: number; duration: number }; // Global rate limit (requests per duration)\n message?: { points: number; duration: number }; // Message sending rate limit (requests per duration)\n };\n}\n\nexport interface QueryFilter {\n contactId?: string;\n descriptorId?: string;\n status?: MessageStatus | MessageStatus[];\n state?: MessageState | MessageState[];\n size?: number;\n skip?: number;\n}\n\nexport interface DispatcherMetrics {\n total: number;\n byState: {\n [key in MessageState]?: number;\n };\n byStatus: {\n [key in MessageStatus]?: number;\n };\n cumulative: {\n dispatched: number;\n delivered: number;\n failed: number;\n };\n}\n\n/**\n * @deprecated Use DispatcherOptions instead\n */\nexport type MessageDispatcherOptions = DispatcherOptions;\n","/**\n * Logger utility using the 'debug' package.\n *\n * This module provides a configurable logger based on the 'debug' package.\n * By default, all logging is disabled. To enable logging, set the DEBUG\n * environment variable.\n *\n * The DEBUG variable can be a single namespace or a comma-separated list of\n * namespaces. Wildcards are supported.\n *\n * For example:\n * - `DEBUG=blip-api` enables all logs for the 'blip-api' scope.\n * - `DEBUG=blip-api:*` enables all sub-scopes under 'blip-api'.\n * - `DEBUG=*` enables all logs.\n *\n * The logger exposes different levels (debug, info, warn, error), which are\n * different namespaces.\n *\n * Example usage:\n * ```\n * import { getLogger } from './logger';\n *\n * const logger = getLogger('my-feature');\n *\n * logger.info('This is an info message');\n * logger.error('This is an error');\n * ```\n *\n * To see the output, run your application with:\n * `DEBUG=my-feature:* node your-app.js`\n */\n\nimport debug from 'debug';\n\n// Define the available log levels\nexport const LogLevels = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\nexport type LogLevel = keyof typeof LogLevels;\n\ntype LogFunction = (...args: any[]) => void;\n\nexport interface Logger {\n debug: LogFunction;\n info: LogFunction;\n warn: LogFunction;\n error: LogFunction;\n}\n\nconst loggers = new Map<string, Logger>();\n\nfunction serializeArg(arg: any): any {\n if (arg === null || arg === undefined) return arg;\n if (typeof arg === 'object') {\n try {\n return JSON.stringify(arg, null, 2);\n } catch (e) {\n return arg;\n }\n }\n return arg;\n}\n\nfunction createLogFunction(debugInstance: debug.Debugger): LogFunction {\n return (...args: any[]) => {\n const serialized = args.map(serializeArg);\n (debugInstance as any)(...serialized);\n };\n}\n\n/**\n * Creates a logger with a specific scope.\n *\n * @param scope - The scope for the logger (e.g., 'blip-api', 'dispatcher').\n * @returns A logger object with methods for each log level.\n */\nexport function getLogger(scope: string): Logger {\n if (!loggers.has(scope)) {\n const debugInstance = debug(`${scope}:${LogLevels.debug}`);\n const infoInstance = debug(`${scope}:${LogLevels.info}`);\n const warnInstance = debug(`${scope}:${LogLevels.warn}`);\n const errorInstance = debug(`${scope}:${LogLevels.error}`);\n\n errorInstance.log = console.error.bind(console);\n\n const logger: Logger = {\n debug: createLogFunction(debugInstance),\n info: createLogFunction(infoInstance),\n warn: createLogFunction(warnInstance),\n error: createLogFunction(errorInstance),\n };\n\n loggers.set(scope, logger);\n }\n return loggers.get(scope)!;\n}\n\n/**\n * Enables logging for a specific scope and level.\n * This is a convenience function to programmatically enable logging,\n * equivalent to setting the DEBUG environment variable.\n *\n * @param scope - The scope to enable (e.g., 'blip-api').\n * @param level - The minimum log level to enable (e.g., 'info').\n */\nexport function enableLogger(namespaces: string): void {\n const currentDebug = debug.disable();\n debug.enable(`${currentDebug},${namespaces}`);\n}\n\n/**\n * Disables all logging.\n */\nexport function disableLogger(): void {\n debug.disable();\n}\n\n/**\n * Get all registered logger scopes.\n *\n * @returns An array of all registered scopes.\n */\nexport function getAllScopes(): string[] {\n return Array.from(loggers.keys());\n}\n"],"mappings":"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","names":["DispatcherMonitor_exports","__export","DispatcherMonitor","__toCommonJS","import_events","import_bullmq","MessageState","MessageStatus","import_debug","LogLevels","loggers","serializeArg","arg","createLogFunction","debugInstance","args","serialized","getLogger","scope","debug","infoInstance","warnInstance","errorInstance","logger","logger","getLogger","DispatcherMonitor","id","repository","options","job","err","metrics","statePromises","MessageState","state","statusPromises","MessageStatus","status","cumulativePromises","key","b","now","rule","error","maxSize","maxWindow","r","cutoff","idx","s","currentMetrics","alertKey","triggered","value","details","finalCount","previousSnapshot","currFailed","prevFailed","failedDelta","currDispatched","prevDispatched","dispatchedDelta","debounceTime","last","alert","timestamp","snap"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DispatcherRepository } from './DispatcherRepository.mjs';
|
|
2
|
+
import { QueryFilter, Message } from '../types/index.mjs';
|
|
3
|
+
import 'ioredis';
|
|
4
|
+
import '../types/blip.mjs';
|
|
5
|
+
|
|
6
|
+
declare class DispatcherQuery {
|
|
7
|
+
private repository;
|
|
8
|
+
constructor(repository: DispatcherRepository);
|
|
9
|
+
private get client();
|
|
10
|
+
query(filter: QueryFilter): Promise<Message[]>;
|
|
11
|
+
private cleanupIndices;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { DispatcherQuery };
|