@peopl-health/nexus 1.2.0 → 1.3.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.
@@ -0,0 +1,24 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const scheduledMessageSchema = new mongoose.Schema({
4
+ fileUrl: String,
5
+ message: String,
6
+ fileType: String,
7
+ sendTime: Date,
8
+ hidePreview: Boolean,
9
+ contentSid: String,
10
+ code: String,
11
+ author: String,
12
+ variables: Object,
13
+ extraDelay: Number,
14
+ timeZone: { type: String, default: 'Etc/GMT'},
15
+ status: { type: String, default: 'pending' },
16
+ wa_id: { type: String, default: null },
17
+ createdAt: { type: Date, default: Date.now }
18
+ });
19
+
20
+ const ScheduledMessage = mongoose.model('ScheduledMessage', scheduledMessageSchema);
21
+
22
+ module.exports = {
23
+ ScheduledMessage
24
+ };
@@ -51,6 +51,12 @@ messageSchema.pre('save', function (next) {
51
51
 
52
52
  const Message = mongoose.model('Message', messageSchema);
53
53
 
54
+ async function insertMessage(values) {
55
+ const msg = new Message(values);
56
+ await msg.save();
57
+ return msg;
58
+ }
59
+
54
60
  function formatTimestamp(unixTimestamp) {
55
61
  const date = new Date(unixTimestamp * 1000);
56
62
  return date.toLocaleString('sv-MX', {
@@ -86,6 +92,8 @@ function getMessageValues(message, content, reply, is_media) {
86
92
 
87
93
  module.exports = {
88
94
  Message,
95
+ // Backward-compatible helper used by helpers
96
+ insertMessage,
89
97
  getMessageValues,
90
98
  formatTimestamp
91
99
  };
@@ -31,7 +31,8 @@ const mediaRouteDefinitions = {
31
31
  const messageRouteDefinitions = {
32
32
  'POST /send': 'sendMessageController',
33
33
  'POST /send-bulk': 'sendBulkMessageController',
34
- 'POST /send-bulk-airtable': 'sendBulkMessageAirtableController'
34
+ 'POST /send-bulk-airtable': 'sendBulkMessageAirtableController',
35
+ 'POST /get-last': 'getLastInteractionController'
35
36
  };
36
37
 
37
38
  const templateRouteDefinitions = {
@@ -103,6 +104,7 @@ const builtInControllers = {
103
104
  sendMessageController: messageController.sendMessageController,
104
105
  sendBulkMessageController: messageController.sendBulkMessageController,
105
106
  sendBulkMessageAirtableController: messageController.sendBulkMessageAirtableController,
107
+ getLastInteractionController: messageController.getLastInteractionController,
106
108
 
107
109
  // Template controllers
108
110
  createTemplate: templateController.createTemplate,
@@ -3,6 +3,7 @@ const { airtable } = require('../config/airtableConfig');
3
3
 
4
4
  async function addRecord(baseID, tableName, fields) {
5
5
  try {
6
+ if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
6
7
  const base = airtable.base(baseID);
7
8
  const record = await base(tableName).create(fields);
8
9
  console.log('Record added at', tableName);
@@ -17,6 +18,7 @@ async function addRecord(baseID, tableName, fields) {
17
18
  async function getRecords(baseID, tableName) {
18
19
  try {
19
20
  const records = [];
21
+ if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
20
22
  const base = airtable.base(baseID);
21
23
  await base(tableName).select({
22
24
  maxRecords: 3
@@ -36,6 +38,7 @@ async function getRecords(baseID, tableName) {
36
38
  async function getRecordByFilter(baseID, tableName, filter, view = 'Grid view') {
37
39
  try {
38
40
  const records = [];
41
+ if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
39
42
  const base = airtable.base(baseID);
40
43
  await base(tableName).select({
41
44
  filterByFormula: `${filter}`,
@@ -55,6 +58,7 @@ async function getRecordByFilter(baseID, tableName, filter, view = 'Grid view')
55
58
 
56
59
  async function updateRecordByFilter(baseID, tableName, filter, updateFields) {
57
60
  try {
61
+ if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
58
62
  const base = airtable.base(baseID);
59
63
  const updatedRecords = [];
60
64
 
@@ -7,6 +7,7 @@ const configureLLMProvider = (provider) => {
7
7
 
8
8
  let assistantConfig = null;
9
9
  let assistantRegistry = {};
10
+ let customGetAssistantById = null;
10
11
 
11
12
  const { Message, formatTimestamp } = require('../models/messageModel.js');
12
13
  const { Thread } = require('../models/threadModel.js');
@@ -26,9 +27,20 @@ const registerAssistant = (assistantId, AssistantClass) => {
26
27
  assistantRegistry[assistantId] = AssistantClass;
27
28
  };
28
29
 
30
+ const overrideGetAssistantById = (resolverFn) => {
31
+ if (typeof resolverFn === 'function') {
32
+ customGetAssistantById = resolverFn;
33
+ }
34
+ };
35
+
29
36
  const getAssistantById = (assistant_id, thread) => {
37
+ if (customGetAssistantById) {
38
+ const inst = customGetAssistantById(assistant_id, thread);
39
+ if (inst) return inst;
40
+ }
41
+
30
42
  if (!assistantConfig) {
31
- throw new Error('Assistants not configured. Call configureAssistants() first.');
43
+ assistantConfig = {};
32
44
  }
33
45
 
34
46
  const AssistantClass = assistantRegistry[assistant_id];
@@ -288,5 +300,6 @@ module.exports = {
288
300
  switchAssistant,
289
301
  configureAssistants,
290
302
  registerAssistant,
291
- configureLLMProvider
303
+ configureLLMProvider,
304
+ overrideGetAssistantById
292
305
  };
@@ -0,0 +1,19 @@
1
+
2
+ /**
3
+ * Noop storage adapter - implements the storage interface but does nothing.
4
+ */
5
+ class NoopStorage {
6
+ constructor(config = {}) {
7
+ this.config = config;
8
+ }
9
+ async connect() {}
10
+ async saveMessage() { return null; }
11
+ async saveInteractive() { return null; }
12
+ async getMessages() { return []; }
13
+ async getThread() { return null; }
14
+ async createThread() { return null; }
15
+ async updateThread() { return null; }
16
+ async disconnect() {}
17
+ }
18
+
19
+ module.exports = { NoopStorage };
@@ -0,0 +1,31 @@
1
+
2
+ const { MongoStorage } = require('./MongoStorage');
3
+ const { NoopStorage } = require('./NoopStorage');
4
+
5
+ const _storages = new Map();
6
+
7
+ function registerStorage(name, StorageClassOrFactory) {
8
+ if (!name || !StorageClassOrFactory) throw new Error('registerStorage requires a name and class/factory');
9
+ _storages.set(String(name).toLowerCase(), StorageClassOrFactory);
10
+ }
11
+
12
+ function getStorage(name) {
13
+ return _storages.get(String(name || '').toLowerCase());
14
+ }
15
+
16
+ function createStorage(name, config) {
17
+ const Entry = getStorage(name);
18
+ if (!Entry) throw new Error(`Unsupported storage: ${name}`);
19
+ if (typeof Entry === 'function' && /^class\s/.test(Function.prototype.toString.call(Entry))) {
20
+ return new Entry(config || {});
21
+ }
22
+ // factory function
23
+ if (typeof Entry === 'function') return Entry(config || {});
24
+ throw new Error('Invalid storage registry entry');
25
+ }
26
+
27
+ // Register built-ins
28
+ registerStorage('mongo', MongoStorage);
29
+ registerStorage('noop', NoopStorage);
30
+
31
+ module.exports = { registerStorage, getStorage, createStorage };
@@ -76,6 +76,4 @@ const predefinedTemplates = {
76
76
  }
77
77
  };
78
78
 
79
- module.exports = {
80
- predefinedTemplates
81
- };
79
+ module.exports = predefinedTemplates;
@@ -1,4 +1,4 @@
1
- const { OpenAI } = require('openai');
1
+ const OpenAI = require('openai');
2
2
 
3
3
  /**
4
4
  * Default LLM Provider using OpenAI
@@ -1,10 +1,8 @@
1
- const AssistantManager = require('./AssistantManager');
2
- const DefaultLLMProvider = require('./DefaultLLMProvider');
3
- const MessageParser = require('./MessageParser');
4
- const logger = require('./logger');
1
+ const { DefaultLLMProvider } = require('./defaultLLMProvider');
2
+ const { MessageParser } = require('./messageParser');
3
+ const { logger } = require('./logger');
5
4
 
6
5
  module.exports = {
7
- AssistantManager,
8
6
  DefaultLLMProvider,
9
7
  MessageParser,
10
8
  logger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -34,6 +34,7 @@
34
34
  "build": "tsc",
35
35
  "dev": "tsc --watch",
36
36
  "test": "jest",
37
+ "test:inband": "jest --runInBand",
37
38
  "lint": "eslint lib/**/*.js",
38
39
  "prepublishOnly": "npm test && npm run lint",
39
40
  "version": "npm run prepublishOnly && git add -A lib",
@@ -1,55 +0,0 @@
1
- const { Calendar_ID } = require('../config/airtableConfig.js');
2
-
3
- const { ISO_DATE, parseStartTime, addDays } = require('../utils/dateUtils.js');
4
- const { dateAndTimeFromStart } = require('../utils/dateUtils.js');
5
-
6
- const { getRecordByFilter, updateRecordByFilter } = require('../services/airtableService.js');
7
-
8
-
9
- const getNextAppointmentForPatient = async (name, after = new Date()) => {
10
- const filter = `AND(patient_name = "${name}", start_time >= '${ISO_DATE(after)}')`;
11
- const rows = await getRecordByFilter(Calendar_ID, 'calendar_quimio', filter);
12
- if (!rows?.length) return null;
13
- return rows.sort((a, b) => new Date(a.start_time) - new Date(b.start_time))[0];
14
- };
15
-
16
- const getAppointmentsBetween = async (start, end) => {
17
- const filter = `AND(start_time >= '${ISO_DATE(start)}', start_time < '${ISO_DATE(end)}')`;
18
- return getRecordByFilter(Calendar_ID, 'calendar_quimio', filter);
19
- };
20
-
21
- const updateAppointmentById = async (recordId, data) => {
22
- return updateRecordByFilter(
23
- Calendar_ID,
24
- 'calendar_quimio',
25
- `REGEX_MATCH({record_id}, '${recordId}')`,
26
- data
27
- );
28
- };
29
-
30
- const buildAvailabilityWindow = async originalDate => {
31
- const start = addDays(originalDate, 1);
32
- const end = addDays(originalDate, 7);
33
-
34
- const allSlots = await getAppointmentsBetween(start, addDays(end, 1));
35
- const availableSlots = allSlots.filter(row => !row.patient);
36
-
37
- const result = {};
38
- availableSlots.forEach(row => {
39
- const { date, time } = dateAndTimeFromStart(row.start_time);
40
- if (!result[date]) result[date] = [];
41
- result[date].push({ time, availableSpots: 1 });
42
- });
43
-
44
- return result;
45
- };
46
-
47
- const parseStart = parseStartTime;
48
-
49
- module.exports = {
50
- getNextAppointmentForPatient,
51
- getAppointmentsBetween,
52
- updateAppointmentById,
53
- parseStart,
54
- buildAvailabilityWindow
55
- };