@peopl-health/nexus 3.4.1 → 3.5.0

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.
@@ -14,8 +14,12 @@ class AssistantProcessor {
14
14
 
15
15
  async process({ code, body = null, runOptions = {} }) {
16
16
  if (!code) throw new Error('code is required for assistant processing');
17
- if (this.mode === 'queue') return await this._processViaQueue({ code, body, runOptions });
18
- return await this._processLocal({ code, body, runOptions });
17
+
18
+ const result = (this.mode === 'queue')
19
+ ? await this._processViaQueue({ code, body, runOptions })
20
+ : await this._processLocal({ code, body, runOptions });
21
+
22
+ return result;
19
23
  }
20
24
 
21
25
  async _processLocal({ code, body = null, runOptions = {} }) {
@@ -5,10 +5,12 @@ const llmConfigModule = require('../config/llmConfig');
5
5
  const { connect } = require('../config/mongoConfig');
6
6
 
7
7
  const { logger } = require('../utils/logger');
8
+ const { createEvent, safeEmit } = require('../utils/eventUtils');
8
9
 
9
10
  const { Message, getMessageValues, insertMessage } = require('../models/messageModel');
10
11
  const { Thread } = require('../models/threadModel');
11
12
 
13
+ const { setEventBus: setStatusEventBus } = require('../helpers/messageStatusHelper');
12
14
  const { ensureThreadExists } = require('../helpers/threadHelper');
13
15
  const { enrichMessageWithTwilioMedia } = require('../helpers/twilioMediaHelper');
14
16
  const { convertTwilioToInternalFormat } = require('../helpers/twilioHelper');
@@ -43,6 +45,7 @@ class NexusMessaging {
43
45
  onFlow: null
44
46
  };
45
47
  this.events = new EventEmitter();
48
+ setStatusEventBus(this.events);
46
49
  this.middleware = {
47
50
  any: [],
48
51
  message: [],
@@ -192,7 +195,6 @@ class NexusMessaging {
192
195
  }
193
196
 
194
197
  async _handleWithPipeline(type, handlerKey, messageData, fallback) {
195
- this.events.emit(`${type}:received`, messageData);
196
198
  const result = await this._runPipeline(type, messageData, async (ctx) => {
197
199
  const handler = this.handlers[handlerKey];
198
200
  if (handler) {
@@ -202,7 +204,6 @@ class NexusMessaging {
202
204
  return await fallback(ctx);
203
205
  }
204
206
  });
205
- this.events.emit(`${type}:handled`, messageData);
206
207
  return result;
207
208
  }
208
209
 
@@ -274,6 +275,19 @@ class NexusMessaging {
274
275
  }
275
276
 
276
277
  const chatId = messageData.from || messageData.From;
278
+ const messageId = messageData.id || messageData.message_id;
279
+
280
+ if (chatId && messageId) {
281
+ safeEmit(this.events, 'message:new', createEvent('message:new', {
282
+ messageId,
283
+ from: chatId,
284
+ body: messageData.body || messageData.message || '',
285
+ media: messageData.media || null,
286
+ type: messageData.interactive ? 'interactive' : messageData.media ? 'media' : 'message'
287
+ }));
288
+ } else {
289
+ logger.warn('[processIncomingMessage] Skipping event emission: missing chatId or messageId', { chatId, messageId });
290
+ }
277
291
 
278
292
  if (chatId) {
279
293
  ensureThreadExists(chatId);
@@ -423,6 +437,8 @@ class NexusMessaging {
423
437
  async disconnect() {
424
438
  if (this.provider) await this.provider.disconnect();
425
439
  if (this.queueAdapter) await this.queueAdapter.shutdown();
440
+ this.events.removeAllListeners();
441
+ setStatusEventBus(null);
426
442
  }
427
443
  }
428
444
 
@@ -1,9 +1,16 @@
1
1
  const { logger } = require('../utils/logger');
2
+ const { createEvent, safeEmit } = require('../utils/eventUtils');
2
3
 
3
4
  const { Message } = require('../models/messageModel');
4
5
 
5
6
  const { handle24HourWindowError } = require('../helpers/templateRecoveryHelper');
6
7
 
8
+ let _eventBus = null;
9
+
10
+ function setEventBus(eventBus) {
11
+ _eventBus = eventBus;
12
+ }
13
+
7
14
  async function updateMessageStatus(messageSid, status, errorCode = null, errorMessage = null) {
8
15
  try {
9
16
  const updateData = {
@@ -40,6 +47,16 @@ async function handleStatusCallback(twilioStatusData) {
40
47
 
41
48
  const updated = await updateMessageStatus(MessageSid, MessageStatus.toLowerCase(), ErrorCode, ErrorMessage);
42
49
 
50
+ if (updated && _eventBus) {
51
+ safeEmit(_eventBus, 'message:status', createEvent('message:status', {
52
+ messageId: MessageSid,
53
+ to: updated.numero,
54
+ status: MessageStatus.toLowerCase(),
55
+ errorCode: ErrorCode || null,
56
+ errorMessage: ErrorMessage || null
57
+ }));
58
+ }
59
+
43
60
  if ((ErrorCode === 63016 || ErrorCode === '63016') && updated) {
44
61
  handle24HourWindowError(updated, MessageSid).catch(err =>
45
62
  logger.error('[MessageStatus] Recovery error', { messageSid: MessageSid, error: err.message })
@@ -63,5 +80,6 @@ async function getMessageStatus(messageSid) {
63
80
  module.exports = {
64
81
  updateMessageStatus,
65
82
  handleStatusCallback,
66
- getMessageStatus
83
+ getMessageStatus,
84
+ setEventBus
67
85
  };
@@ -66,6 +66,12 @@ async function handle24HourWindowError(message, messageSid) {
66
66
  try {
67
67
  await sendMessage({ code: message.numero, contentSid: twilioContent.sid, variables: {} });
68
68
  logger.info('[TemplateRecovery] Template sent', { messageSid, templateSid: twilioContent.sid });
69
+ try {
70
+ await provider.deleteTemplate(twilioContent.sid);
71
+ logger.info('[TemplateRecovery] Template deleted', { messageSid, templateSid: twilioContent.sid });
72
+ } catch (deleteErr) {
73
+ logger.warn('[TemplateRecovery] Failed to delete template after send', { messageSid, templateSid: twilioContent.sid, error: deleteErr.message });
74
+ }
69
75
  } catch (sendErr) {
70
76
  await Message.updateOne(
71
77
  { message_id: messageSid },
@@ -75,6 +81,12 @@ async function handle24HourWindowError(message, messageSid) {
75
81
  }
76
82
  } else if (approvalStatus === 'REJECTED') {
77
83
  logger.warn('[TemplateRecovery] Template rejected', { messageSid, templateSid: twilioContent.sid });
84
+ try {
85
+ await provider.deleteTemplate(twilioContent.sid);
86
+ logger.info('[TemplateRecovery] Rejected template deleted', { messageSid, templateSid: twilioContent.sid });
87
+ } catch (deleteErr) {
88
+ logger.warn('[TemplateRecovery] Failed to delete rejected template', { messageSid, templateSid: twilioContent.sid, error: deleteErr.message });
89
+ }
78
90
  } else {
79
91
  checkApproval(attempt + 1);
80
92
  }
package/lib/index.d.ts CHANGED
@@ -161,6 +161,36 @@ declare module '@peopl-health/nexus' {
161
161
  setReplies(replies: any): void;
162
162
  }
163
163
 
164
+ // Event Types
165
+ export interface NexusEventEnvelope<T = any> {
166
+ event: string;
167
+ timestamp: number;
168
+ data: T;
169
+ }
170
+
171
+ export interface MessageNewEventData {
172
+ messageId: string;
173
+ from: string;
174
+ body: string;
175
+ media: any | null;
176
+ type: 'message' | 'interactive' | 'media';
177
+ }
178
+
179
+ export interface MessageStatusEventData {
180
+ messageId: string;
181
+ to: string;
182
+ status: 'queued' | 'sending' | 'sent' | 'delivered' | 'undelivered' | 'failed' | 'read';
183
+ errorCode: string | null;
184
+ errorMessage: string | null;
185
+ }
186
+
187
+ export interface NexusEventMap {
188
+ 'message:new': NexusEventEnvelope<MessageNewEventData>;
189
+ 'message:status': NexusEventEnvelope<MessageStatusEventData>;
190
+ }
191
+
192
+ export type NexusEventName = keyof NexusEventMap;
193
+
164
194
  // Core Classes
165
195
  export abstract class MessageProvider {
166
196
  constructor(config: any);
@@ -212,6 +242,9 @@ declare module '@peopl-health/nexus' {
212
242
  sendMessage(messageData: MessageData): Promise<any>;
213
243
  sendScheduledMessage(scheduledMessage: ScheduledMessage): Promise<void>;
214
244
  processIncomingMessage(messageData: MessageData): Promise<any>;
245
+ getEventBus(): import('events').EventEmitter;
246
+ getBatchingManager(): BatchingManager;
247
+ getAssistantProcessor(): AssistantProcessor;
215
248
  isConnected(): boolean;
216
249
  disconnect(): Promise<void>;
217
250
  }
@@ -0,0 +1,15 @@
1
+ const { logger } = require('./logger');
2
+
3
+ function createEvent(event, data) {
4
+ return { event, timestamp: Date.now(), data };
5
+ }
6
+
7
+ function safeEmit(emitter, event, payload) {
8
+ try {
9
+ emitter.emit(event, payload);
10
+ } catch (err) {
11
+ logger.error('[EventBus] Listener error', { event, error: err.message });
12
+ }
13
+ }
14
+
15
+ module.exports = { createEvent, safeEmit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",