@peopl-health/nexus 3.15.4 → 3.15.5

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.
@@ -22,6 +22,7 @@ const { ProcessingPipeline } = require('../core/ProcessingPipeline');
22
22
  const { AssistantProcessor } = require('../core/AssistantProcessor');
23
23
 
24
24
  const { createQueueAdapter } = require('../queue');
25
+ const { ScheduledMessageJob } = require('../jobs/ScheduledMessageJob');
25
26
 
26
27
  /**
27
28
  * Core messaging orchestrator for providers, storage, and assistant processing.
@@ -77,6 +78,11 @@ class NexusMessaging {
77
78
  sendMessage: this.sendMessage.bind(this),
78
79
  storeRunMetrics,
79
80
  });
81
+
82
+ this.scheduledMessageJob = new ScheduledMessageJob({
83
+ queueAdapter: this.queueAdapter,
84
+ sendMessage: this.sendMessage.bind(this)
85
+ });
80
86
  }
81
87
 
82
88
  async initialize(options = {}) {
@@ -257,10 +263,16 @@ class NexusMessaging {
257
263
  }
258
264
 
259
265
  async sendScheduledMessage(scheduledMessage) {
260
- if (!this.provider) {
261
- throw new Error('No provider initialized');
266
+ if (!scheduledMessage?._id) {
267
+ throw new Error('sendScheduledMessage requires a persisted ScheduledMessage with _id');
262
268
  }
263
- return await this.provider.sendScheduledMessage(scheduledMessage);
269
+ if (!scheduledMessage?.sendTime) {
270
+ throw new Error('sendScheduledMessage requires sendTime');
271
+ }
272
+ return await this.scheduledMessageJob.schedule({
273
+ scheduledMessageId: scheduledMessage._id,
274
+ sendTime: scheduledMessage.sendTime
275
+ });
264
276
  }
265
277
 
266
278
  /*
@@ -1,4 +1,5 @@
1
1
  const { logger } = require('../utils/logger');
2
+ const { ScheduledMessage } = require('../models/agendaMessageModel');
2
3
  const { BaseJob } = require('./BaseJob');
3
4
 
4
5
  const QUEUE_NAME = 'scheduled-messages';
@@ -10,9 +11,15 @@ const DEFAULT_JOB_OPTIONS = {
10
11
  removeOnFail: 50
11
12
  };
12
13
 
14
+ const TERMINAL_STATUSES = ['sent', 'cancelled'];
15
+
13
16
  class ScheduledMessageJob extends BaseJob {
14
- constructor({ queueAdapter } = {}) {
17
+ constructor({ queueAdapter, sendMessage } = {}) {
15
18
  super({ queueAdapter, queueName: QUEUE_NAME });
19
+ if (typeof sendMessage !== 'function') {
20
+ throw new Error('ScheduledMessageJob requires a sendMessage function');
21
+ }
22
+ this.sendMessage = sendMessage;
16
23
  }
17
24
 
18
25
  async schedule({ scheduledMessageId, sendTime }) {
@@ -47,9 +54,66 @@ class ScheduledMessageJob extends BaseJob {
47
54
  return `scheduled-msg-${scheduledMessageId}`;
48
55
  }
49
56
 
50
- async _process(data) {
51
- logger.warn('[ScheduledMessageJob] Processor stub invoked; real implementation pending', data);
52
- return { success: false, reason: 'not_implemented' };
57
+ async _process({ scheduledMessageId }) {
58
+ const msg = await ScheduledMessage.findById(scheduledMessageId);
59
+
60
+ if (!msg) {
61
+ logger.warn('[ScheduledMessageJob] Scheduled message not found', { scheduledMessageId });
62
+ return { success: false, reason: 'not_found' };
63
+ }
64
+
65
+ if (TERMINAL_STATUSES.includes(msg.status)) {
66
+ logger.info('[ScheduledMessageJob] Already processed', { scheduledMessageId, status: msg.status });
67
+ return { success: false, reason: 'already_processed', status: msg.status };
68
+ }
69
+
70
+ const claim = await ScheduledMessage.updateOne(
71
+ { _id: scheduledMessageId, status: { $nin: TERMINAL_STATUSES.concat(['sending']) } },
72
+ { $set: { status: 'sending', sendingAt: new Date() } }
73
+ );
74
+ if (!claim.modifiedCount && !claim.nModified) {
75
+ logger.info('[ScheduledMessageJob] Send already claimed by another worker', { scheduledMessageId });
76
+ return { success: false, reason: 'already_claimed' };
77
+ }
78
+
79
+ try {
80
+ const result = await this.sendMessage({
81
+ code: msg.code,
82
+ body: msg.message,
83
+ fileUrl: msg.fileUrl,
84
+ fileType: msg.fileType,
85
+ contentSid: msg.contentSid,
86
+ variables: msg.variables,
87
+ hidePreview: msg.hidePreview
88
+ });
89
+
90
+ const messageId = result?.messageId || result?.sid || null;
91
+
92
+ await ScheduledMessage.updateOne(
93
+ { _id: scheduledMessageId },
94
+ { $set: { status: 'sent', sentAt: new Date(), wa_id: messageId } }
95
+ );
96
+
97
+ logger.info('[ScheduledMessageJob] Sent', { scheduledMessageId, messageId });
98
+
99
+ return { success: true, messageId };
100
+ } catch (error) {
101
+ await ScheduledMessage.updateOne(
102
+ { _id: scheduledMessageId },
103
+ {
104
+ $set: {
105
+ status: 'failed',
106
+ failedAt: new Date(),
107
+ errorCode: error?.code || null,
108
+ errorMessage: error?.message || null
109
+ }
110
+ }
111
+ );
112
+
113
+ logger.error('[ScheduledMessageJob] Send failed', { scheduledMessageId, error: error.message });
114
+
115
+ throw error;
116
+ }
53
117
  }
54
118
  }
55
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.15.4",
3
+ "version": "3.15.5",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",