wexa-chat 0.1.2 → 0.1.4
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/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +22 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/models/Conversation.model.ts +41 -42
- package/src/models/Message.model.ts +34 -36
- package/src/models/connection.ts +7 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import mongoose, { Document, Model } from 'mongoose';
|
|
1
|
+
import mongoose, { Document, Connection, Model } from 'mongoose';
|
|
2
2
|
import { Server } from 'http';
|
|
3
3
|
import { Socket, Server as Server$1 } from 'socket.io';
|
|
4
4
|
|
|
@@ -183,9 +183,9 @@ interface IConversation extends Document {
|
|
|
183
183
|
updatedAt: Date;
|
|
184
184
|
}
|
|
185
185
|
/**
|
|
186
|
-
* Create Conversation model
|
|
186
|
+
* Create (or retrieve) Conversation model safely
|
|
187
187
|
*/
|
|
188
|
-
declare function createConversationModel(options: InitOptions): Model<IConversation>;
|
|
188
|
+
declare function createConversationModel(options: InitOptions, conn?: Connection): Model<IConversation>;
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
191
|
* Message document interface
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import mongoose, { Document, Model } from 'mongoose';
|
|
1
|
+
import mongoose, { Document, Connection, Model } from 'mongoose';
|
|
2
2
|
import { Server } from 'http';
|
|
3
3
|
import { Socket, Server as Server$1 } from 'socket.io';
|
|
4
4
|
|
|
@@ -183,9 +183,9 @@ interface IConversation extends Document {
|
|
|
183
183
|
updatedAt: Date;
|
|
184
184
|
}
|
|
185
185
|
/**
|
|
186
|
-
* Create Conversation model
|
|
186
|
+
* Create (or retrieve) Conversation model safely
|
|
187
187
|
*/
|
|
188
|
-
declare function createConversationModel(options: InitOptions): Model<IConversation>;
|
|
188
|
+
declare function createConversationModel(options: InitOptions, conn?: Connection): Model<IConversation>;
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
191
|
* Message document interface
|
package/dist/index.js
CHANGED
|
@@ -10,23 +10,26 @@ var mongoose__default = /*#__PURE__*/_interopDefault(mongoose);
|
|
|
10
10
|
var Redis__default = /*#__PURE__*/_interopDefault(Redis);
|
|
11
11
|
|
|
12
12
|
// src/models/Conversation.model.ts
|
|
13
|
-
function createConversationModel(options) {
|
|
14
|
-
const ParticipantSchema = new mongoose.Schema(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
function createConversationModel(options, conn = mongoose__default.default.connection) {
|
|
14
|
+
const ParticipantSchema = new mongoose.Schema(
|
|
15
|
+
{
|
|
16
|
+
entityModel: {
|
|
17
|
+
type: String,
|
|
18
|
+
enum: options.participantModels,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
entityId: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true
|
|
24
|
+
},
|
|
25
|
+
role: {
|
|
26
|
+
type: String,
|
|
27
|
+
enum: options.memberRoles,
|
|
28
|
+
required: true
|
|
29
|
+
}
|
|
23
30
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
enum: options.memberRoles,
|
|
27
|
-
required: true
|
|
28
|
-
}
|
|
29
|
-
}, { _id: false });
|
|
31
|
+
{ _id: false }
|
|
32
|
+
);
|
|
30
33
|
const ConversationSchema = new mongoose.Schema(
|
|
31
34
|
{
|
|
32
35
|
organizationId: {
|
|
@@ -51,9 +54,7 @@ function createConversationModel(options) {
|
|
|
51
54
|
lastMessageSenderModel: String,
|
|
52
55
|
metadata: mongoose.Schema.Types.Mixed
|
|
53
56
|
},
|
|
54
|
-
{
|
|
55
|
-
timestamps: true
|
|
56
|
-
}
|
|
57
|
+
{ timestamps: true }
|
|
57
58
|
);
|
|
58
59
|
ConversationSchema.index({
|
|
59
60
|
organizationId: 1,
|
|
@@ -63,11 +64,8 @@ function createConversationModel(options) {
|
|
|
63
64
|
organizationId: 1,
|
|
64
65
|
lastMessageAt: -1
|
|
65
66
|
});
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
return mongoose__default.default.models[modelName];
|
|
69
|
-
}
|
|
70
|
-
return mongoose__default.default.model(modelName, ConversationSchema);
|
|
67
|
+
const name = "Conversation";
|
|
68
|
+
return conn.models[name] || conn.model(name, ConversationSchema);
|
|
71
69
|
}
|
|
72
70
|
function createMessageModel(options) {
|
|
73
71
|
const MessageSchema = new mongoose.Schema(
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/models/Conversation.model.ts","../src/models/Message.model.ts","../src/models/connection.ts","../src/utils/pagination.ts","../src/services/search/search.conversations.ts","../src/services/conversations.service.ts","../src/services/search/search.messages.ts","../src/services/messages.service.ts","../src/transport/localSubs.ts","../src/transport/redis.ts","../src/transport/socket.ts","../src/transport/index.ts","../src/models/indexes.ts","../src/config/env.ts","../src/utils/ids.ts","../src/utils/validators.ts","../src/index.ts"],"names":["mongoose","Schema"],"mappings":";AAAA,OAAO,YAAY,cAA+B;AAwB3C,SAAS,wBAAwB,SAA4C;AAElF,QAAM,oBAAoB,IAAI,OAAO;AAAA,IACnC,aAAa;AAAA,MACX,MAAM;AAAA,MACN,MAAM,QAAQ;AAAA,MACd,UAAU;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,MAAM,QAAQ;AAAA,MACd,UAAU;AAAA,IACZ;AAAA,EACF,GAAG,EAAE,KAAK,MAAM,CAAC;AAGjB,QAAM,qBAAqB,IAAI;AAAA,IAC7B;AAAA,MACE,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,MACA,cAAc;AAAA,QACZ,MAAM,CAAC,iBAAiB;AAAA,QACxB,UAAU;AAAA,QACV,UAAU;AAAA,UACR,WAAW,CAAC,iBAAiC,aAAa,WAAW;AAAA,UACrE,SAAS;AAAA,QACX;AAAA,MACF;AAAA,MACA,eAAe;AAAA,QACb,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA,oBAAoB;AAAA,MACpB,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,MACxB,UAAU,OAAO,MAAM;AAAA,IACzB;AAAA,IACA;AAAA,MACE,YAAY;AAAA,IACd;AAAA,EACF;AAGA,qBAAmB,MAAM;AAAA,IACvB,gBAAgB;AAAA,IAChB,yBAAyB;AAAA,EAC3B,CAAC;AAED,qBAAmB,MAAM;AAAA,IACvB,gBAAgB;AAAA,IAChB,eAAe;AAAA,EACjB,CAAC;AAGD,QAAM,YAAY;AAClB,MAAI,SAAS,OAAO,SAAS,GAAG;AAC9B,WAAO,SAAS,OAAO,SAAS;AAAA,EAClC;AAGA,SAAO,SAAS,MAAqB,WAAW,kBAAkB;AACpE;;;AC5FA,OAAOA,aAAY,UAAAC,eAA+B;AAwB3C,SAAS,mBAAmB,SAAuC;AAExE,QAAM,gBAAgB,IAAIA;AAAA,IACxB;AAAA,MACE,gBAAgB;AAAA,QACd,MAAMA,QAAO,MAAM;AAAA,QACnB,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,MACA,gBAAgB;AAAA,QACd,MAAMA,QAAO,MAAM;AAAA,QACnB,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,MACA,aAAa;AAAA,QACX,MAAM;AAAA,QACN,MAAM,QAAQ;AAAA,QACd,UAAU;AAAA,MACZ;AAAA,MACA,UAAU;AAAA,QACR,MAAMA,QAAO,MAAM;AAAA,QACnB,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM,CAAC,QAAQ,QAAQ;AAAA,QACvB,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,QACf,MAAMA,QAAO,MAAM;AAAA,QACnB,KAAK;AAAA,MACP;AAAA,MACA,cAAc;AAAA,QACZ,MAAMA,QAAO,MAAM;AAAA,QACnB,KAAK;AAAA,MACP;AAAA,MACA,UAAU;AAAA,QACR,MAAM;AAAA,MACR;AAAA,MACA,WAAW;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,EAAE,YAAY,KAAK;AAAA,EACrB;AAGA,gBAAc,MAAM;AAAA,IAClB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,WAAW;AAAA,EACb,CAAC;AAGD,MAAI,QAAQ,kBAAkB;AAC5B,kBAAc;AAAA,MACZ,EAAE,MAAM,OAAO;AAAA,MACf;AAAA,QACE,MAAM;AAAA,QACN,SAAS,EAAE,MAAM,GAAG;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAGA,SAAOD,UAAS,MAAgB,WAAW,aAAa;AAC1D;;;ACxFO,SAAS,aAAa,kBAAmC,SAAsB;AAEpF,QAAM,eAAe,wBAAwB,OAAO;AACpD,QAAM,UAAU,mBAAmB,OAAO;AAE1C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;;;ACLO,SAAS,aAAa,WAAmB,IAAoB;AAClE,QAAM,aAAa,GAAG,SAAS,IAAI,EAAE;AACrC,SAAO,OAAO,KAAK,UAAU,EAAE,SAAS,QAAQ;AAClD;AAOO,SAAS,YAAY,QAA0D;AACpF,MAAI;AACF,UAAM,UAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAC9D,UAAM,CAAC,cAAc,EAAE,IAAI,QAAQ,MAAM,GAAG;AAE5C,UAAM,YAAY,SAAS,cAAc,EAAE;AAE3C,QAAI,MAAM,SAAS,KAAK,CAAC,IAAI;AAC3B,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,WAAW,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,WAAO;AAAA,EACT;AACF;AASO,SAAS,wBACd,OACA,OACA,eACoB;AACpB,QAAM,UAAU,MAAM,SAAS;AAI/B,QAAM,iBAAiB,UAAU,MAAM,MAAM,GAAG,KAAK,IAAI;AAGzD,QAAM,aAAa,WAAW,eAAe,SAAS,IAClD,cAAc,eAAe,eAAe,SAAS,CAAC,CAAC,IACvD;AAEJ,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;;;AC9DA,eAAsB,oBACpB,OACA,QACyC;AACzC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF,IAAI;AAGJ,QAAM,YAAiC;AAAA,IACrC;AAAA,EACF;AAGA,MAAI,oBAAoB,eAAe;AACrC,cAAU,cAAc,IAAI;AAAA,MAC1B,YAAY;AAAA,QACV,aAAa;AAAA,QACb,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAGA,MAAI,QAAQ;AACV,UAAM,eAAe,YAAY,MAAM;AAEvC,QAAI,cAAc;AAChB,YAAM,EAAE,WAAW,GAAG,IAAI;AAC1B,YAAM,aAAa,IAAI,KAAK,SAAS;AAErC,gBAAU,MAAM;AAAA,QACd,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE;AAAA,QACrC,EAAE,eAAe,YAAY,KAAK,EAAE,KAAK,GAAG,EAAE;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,MAAM,KAAK,SAAS,EAC7C,KAAK,EAAE,eAAe,GAAG,CAAC,EAC1B,MAAM,QAAQ,CAAC,EACf,KAAK;AAGR,QAAM,UAAU,cAAc,SAAS;AACvC,QAAM,QAAQ,UAAU,cAAc,MAAM,GAAG,KAAK,IAAI;AAGxD,MAAI,aAA4B;AAEhC,MAAI,WAAW,MAAM,SAAS,GAAG;AAC/B,UAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AAEvC,UAAM,YAAY,SAAS,gBACzB,SAAS,cAAc,QAAQ,IAC/B,SAAS,UAAU,QAAQ;AAE7B,iBAAa,aAAa,WAAW,SAAS,IAAI,SAAS,CAAC;AAAA,EAC9D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKA,eAAsB,wBACpB,OACA,QAK+B;AAC/B,QAAM,EAAE,gBAAgB,GAAG,EAAE,IAAI;AAEjC,QAAM,eAAe,MAAM,MAAM,QAAQ;AAAA,IACvC;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,QACE,cAAc;AAAA,UACZ,YAAY;AAAA,YACV,aAAa,EAAE;AAAA,YACf,UAAU,EAAE;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,cAAc;AAAA,UACZ,YAAY;AAAA,YACV,aAAa,EAAE;AAAA,YACf,UAAU,EAAE;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,KAAK,CAAC,EAAE,OAAO,gBAAgB,GAAG,CAAC;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AC/FO,SAAS,2BAA2B,QAGlB;AACvB,QAAM,EAAE,aAAa,IAAI;AAEzB,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,MAAM,yBAAyB,MAAsD;AACnF,YAAM,EAAE,gBAAgB,GAAG,EAAE,IAAI;AAEjC,UAAI;AAEF,cAAM,uBAAuB,MAAM,wBAAwB,cAAc;AAAA,UACvE;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,sBAAsB;AACxB,iBAAO;AAAA,QACT;AAGA,cAAM,kBAAkB,IAAI,aAAa;AAAA,UACvC;AAAA,UACA,cAAc;AAAA,YACZ;AAAA,cACE,aAAa,EAAE;AAAA,cACf,UAAU,EAAE;AAAA,cACZ,MAAM;AAAA;AAAA,YACR;AAAA,YACA;AAAA,cACE,aAAa,EAAE;AAAA,cACf,UAAU,EAAE;AAAA,cACZ,MAAM;AAAA;AAAA,YACR;AAAA,UACF;AAAA,UACA,eAAe,oBAAI,KAAK;AAAA;AAAA,QAC1B,CAAC;AAED,cAAM,gBAAgB,KAAK;AAC3B,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,sCAAsC,KAAK;AACzD,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,gBAAgB,gBAAwB,gBAAuD;AACnG,UAAI;AACF,eAAO,MAAM,aAAa,QAAQ;AAAA,UAChC;AAAA,UACA,KAAK;AAAA,QACP,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,6BAA6B,KAAK;AAChD,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,oBAAoB,MAAwE;AAChG,UAAI;AACF,eAAO,MAAM,oBAAoB,cAAc,IAAI;AAAA,MACrD,SAAS,OAAO;AACd,gBAAQ,MAAM,iCAAiC,KAAK;AACpD,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,wBAAwB,MAII;AAChC,UAAI;AACF,eAAO,MAAM,wBAAwB,cAAc,IAAI;AAAA,MACzD,SAAS,OAAO;AACd,gBAAQ,MAAM,qCAAqC,KAAK;AACxD,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;AC7GA,eAAsB,eACpB,OACA,QACoC;AACpC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF,IAAI;AAGJ,QAAM,YAAiC;AAAA,IACrC;AAAA,EACF;AAGA,MAAI,gBAAgB;AAClB,cAAU,iBAAiB;AAAA,EAC7B;AAEA,MAAI,eAAe,UAAU;AAC3B,cAAU,cAAc;AACxB,cAAU,WAAW;AAAA,EACvB,WAAW,aAAa;AACtB,cAAU,cAAc;AAAA,EAC1B;AAEA,MAAI,MAAM;AACR,cAAU,OAAO;AAAA,EACnB;AAGA,MAAI,YAAY,QAAQ;AACtB,cAAU,YAAY,CAAC;AAEvB,QAAI,UAAU;AACZ,gBAAU,UAAU,OAAO,IAAI,KAAK,QAAQ;AAAA,IAC9C;AAEA,QAAI,QAAQ;AACV,gBAAU,UAAU,OAAO,IAAI,KAAK,MAAM;AAAA,IAC5C;AAAA,EACF;AAGA,MAAI,cAAc;AAChB,cAAU,eAAe;AAAA,EAC3B;AAGA,MAAI,QAAQ;AACV,UAAM,eAAe,YAAY,MAAM;AAEvC,QAAI,cAAc;AAChB,YAAM,EAAE,WAAW,GAAG,IAAI;AAE1B,gBAAU,MAAM;AAAA,QACd,EAAE,WAAW,EAAE,KAAK,IAAI,KAAK,SAAS,EAAE,EAAE;AAAA,QAC1C,EAAE,WAAW,IAAI,KAAK,SAAS,GAAG,KAAK,EAAE,KAAK,GAAG,EAAE;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAA+B,EAAE,WAAW,GAAG;AACnD,MAAI,WAAkB,CAAC;AAGvB,MAAI,SAAS,MAAM,KAAK,GAAG;AAEzB,eAAW;AAAA,MACT,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,MAAM,GAAG,GAAG,UAAU,EAAE;AAAA,MACtD,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,YAAY,EAAE,EAAE;AAAA,MAChD,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,YAAY,GAAG,WAAW,GAAG,EAAE;AAAA,MAC1D,EAAE,QAAQ,QAAQ,EAAE;AAAA;AAAA,IACtB;AAAA,EACF,OAAO;AAEL,eAAW;AAAA,MACT,EAAE,QAAQ,UAAU;AAAA,MACpB,EAAE,OAAO,KAAK;AAAA,MACd,EAAE,QAAQ,QAAQ,EAAE;AAAA;AAAA,IACtB;AAAA,EACF;AAGA,QAAM,QAAQ,MAAM,MAAM,UAAU,QAAQ;AAG5C,QAAM,UAAU,MAAM,SAAS;AAC/B,QAAM,eAAe,UAAU,MAAM,MAAM,GAAG,KAAK,IAAI;AAGvD,QAAM,aAAa,WAAW,aAAa,SAAS,IAChD;AAAA,IACE,IAAI,KAAK,aAAa,aAAa,SAAS,CAAC,EAAE,SAAS,EAAE,QAAQ;AAAA,IAClE,aAAa,aAAa,SAAS,CAAC,EAAE,IAAI,SAAS;AAAA,EACrD,IACA;AAEJ,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;;;AC9FO,SAAS,sBACd,QAIA,QAA6B,CAAC,GACb;AACjB,QAAM,EAAE,SAAS,aAAa,IAAI;AAClC,QAAM,EAAE,kBAAkB,cAAc,IAAI;AAE5C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAML,MAAM,YAAY,MAA0C;AAC1D,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA;AAAA,MACF,IAAI;AAGJ,YAAM,UAAU,IAAI,QAAQ;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,gBAAgB;AAAA;AAAA,MAChC,CAAC;AAED,YAAM,QAAQ,KAAK;AAGnB,YAAM,aAAa;AAAA,QACjB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,YACJ,eAAe,QAAQ;AAAA,YACvB,oBAAoB,KAAK,UAAU,GAAG,GAAG;AAAA,YACzC,qBAAqB;AAAA,YACrB,wBAAwB;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAGA,UAAI,kBAAkB;AACpB,yBAAiB,OAAO;AAAA,MAC1B;AAEA,aAAO;AAAA,IACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAM,aAAa,MAA4D;AAC7E,YAAM,EAAE,gBAAgB,gBAAgB,QAAQ,IAAI,OAAO,IAAI;AAE/D,aAAO,eAAe,SAAS;AAAA,QAC7B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM,SAAS,MAAmC;AAChD,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,IAAI;AAEJ,YAAM,KAAK,oBAAI,KAAK;AAGpB,YAAM,QAAQ;AAAA,QACZ;AAAA,UACE;AAAA,UACA;AAAA,UACA,KAAK;AAAA;AAAA,UAEL,wBAAwB,EAAE,KAAK,cAAc;AAAA,QAC/C;AAAA,QACA;AAAA,UACE,OAAO;AAAA,YACL,QAAQ;AAAA,cACN;AAAA,cACA;AAAA,cACA,QAAQ;AAAA,YACV;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,YAAM,aAAa;AAAA,QACjB;AAAA,UACE;AAAA,UACA,KAAK;AAAA,UACL,4BAA4B;AAAA,UAC5B,yBAAyB;AAAA,QAC3B;AAAA,QACA;AAAA,UACE,MAAM;AAAA,YACJ,oCAAoC;AAAA,YACpC,6BAA6B;AAAA,UAC/B;AAAA,QACF;AAAA,MACF;AAGA,UAAI,eAAe;AACjB,sBAAc,EAAE,GAAG,MAAM,GAAG,CAAC;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACF;;;ACjKA,IAAM,WAAqC,oBAAI,IAAI;AAGnD,IAAM,aAAuC,oBAAI,IAAI;AAO9C,SAAS,eAAe,gBAAwB,UAAwB;AAf/E;AAiBE,MAAI,CAAC,SAAS,IAAI,cAAc,GAAG;AACjC,aAAS,IAAI,gBAAgB,oBAAI,IAAI,CAAC;AAAA,EACxC;AACA,iBAAS,IAAI,cAAc,MAA3B,mBAA8B,IAAI;AAGlC,MAAI,CAAC,WAAW,IAAI,QAAQ,GAAG;AAC7B,eAAW,IAAI,UAAU,oBAAI,IAAI,CAAC;AAAA,EACpC;AACA,mBAAW,IAAI,QAAQ,MAAvB,mBAA0B,IAAI;AAChC;AAOO,SAAS,iBAAiB,gBAAwB,UAAwB;AAE/E,QAAM,UAAU,SAAS,IAAI,cAAc;AAC3C,MAAI,SAAS;AACX,YAAQ,OAAO,QAAQ;AACvB,QAAI,QAAQ,SAAS,GAAG;AACtB,eAAS,OAAO,cAAc;AAAA,IAChC;AAAA,EACF;AAGA,QAAM,gBAAgB,WAAW,IAAI,QAAQ;AAC7C,MAAI,eAAe;AACjB,kBAAc,OAAO,cAAc;AACnC,QAAI,cAAc,SAAS,GAAG;AAC5B,iBAAW,OAAO,QAAQ;AAAA,IAC5B;AAAA,EACF;AACF;AAMO,SAAS,aAAa,UAAwB;AAEnD,QAAM,gBAAgB,WAAW,IAAI,QAAQ;AAC7C,MAAI,eAAe;AAEjB,eAAW,kBAAkB,eAAe;AAC1C,YAAM,UAAU,SAAS,IAAI,cAAc;AAC3C,UAAI,SAAS;AACX,gBAAQ,OAAO,QAAQ;AACvB,YAAI,QAAQ,SAAS,GAAG;AACtB,mBAAS,OAAO,cAAc;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAGA,eAAW,OAAO,QAAQ;AAAA,EAC5B;AACF;AAQO,SAAS,YACd,gBACA,SACA,SACM;AAEN,QAAM,UAAU,SAAS,IAAI,cAAc;AAC3C,MAAI,SAAS;AAEX,eAAW,YAAY,SAAS;AAC9B,UAAI;AACF,gBAAQ,UAAU,OAAO;AAAA,MAC3B,SAAS,OAAO;AACd,gBAAQ,MAAM,4BAA4B,QAAQ,KAAK,KAAK;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACF;;;ACrGA,OAAO,WAAW;AAQX,SAAS,kBAAkB,QAAoC;AACpE,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,iEAAiE;AAC9E,WAAO;AAAA,EACT;AAEA,MAAI;AACF,QAAI;AAGJ,QAAI,OAAO,KAAK;AACd,eAAS,IAAI,MAAM,OAAO,GAAG;AAAA,IAC/B,WAAW,OAAO,MAAM;AACtB,eAAS,IAAI,MAAM;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,IAAI,OAAO;AAAA,QACX,gBAAgB,OAAO;AAAA;AAAA,QACvB,kBAAkB;AAAA,QAClB,oBAAoB;AAAA,QACpB,cAAc,OAAO;AACnB,gBAAM,QAAQ,KAAK,IAAI,QAAQ,IAAI,GAAI;AACvC,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK,+DAA+D;AAC5E,aAAO;AAAA,IACT;AAGA,WAAO,GAAG,WAAW,MAAM;AACzB,cAAQ,KAAK,wBAAwB;AAAA,IACvC,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,cAAQ,MAAM,uBAAuB,GAAG;AAAA,IAC1C,CAAC;AAED,WAAO,GAAG,gBAAgB,MAAM;AAC9B,cAAQ,KAAK,2BAA2B;AAAA,IAC1C,CAAC;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,kCAAkC,KAAK;AACrD,WAAO;AAAA,EACT;AACF;AASA,eAAsB,sBACpB,QACA,gBACA,SACiB;AACjB,MAAI,CAAC;AAAQ,WAAO;AAEpB,MAAI;AACF,UAAM,UAAU,QAAQ,cAAc;AACtC,UAAM,UAAU,KAAK,UAAU,OAAO;AACtC,WAAO,MAAM,OAAO,QAAQ,SAAS,OAAO;AAAA,EAC9C,SAAS,OAAO;AACd,YAAQ,MAAM,qCAAqC,cAAc,KAAK,KAAK;AAC3E,WAAO;AAAA,EACT;AACF;AAQO,SAAS,mBACd,QACA,SACqB;AACrB,MAAI,CAAC;AAAQ,WAAO;AAEpB,MAAI;AAGF,UAAM,YAAY,OAAO,UAAU;AAGnC,cAAU,WAAW,QAAQ;AAG7B,cAAU,GAAG,YAAY,CAAC,UAAU,SAAS,YAAY;AACvD,UAAI;AAEF,cAAM,iBAAiB,QAAQ,MAAM,CAAC;AAGtC,cAAM,UAAU,KAAK,MAAM,OAAO;AAGlC,gBAAQ,gBAAgB,OAAO;AAAA,MACjC,SAAS,OAAO;AACd,gBAAQ,MAAM,mCAAmC,KAAK;AAAA,MACxD;AAAA,IACF,CAAC;AAGD,WAAO,MAAM;AACX,gBAAU,aAAa,QAAQ;AAC/B,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,mCAAmC,KAAK;AACtD,WAAO;AAAA,EACT;AACF;;;AChIA,SAAS,cAAsB;AAiBxB,IAAM,kBAAN,MAAsB;AAAA,EAI3B,YAAY,QAAoB,WAAsB,QAAuB;AAC3E,SAAK,KAAK,IAAI,OAAO,QAAQ;AAAA,MAC3B,OAAM,iCAAQ,SAAQ;AAAA,MACtB,kBAAkB;AAAA,MAClB,OAAM,iCAAQ,SAAQ;AAAA,QACpB,QAAQ;AAAA,QACR,SAAS,CAAC,OAAO,MAAM;AAAA,MACzB;AAAA,IACF,CAAC;AACD,SAAK,YAAY;AAGjB,SAAK,UAAU,mBAAmB,CAAC,gBAAwB,YAAiB;AAC1E,WAAK,GAAG,GAAG,QAAQ,cAAc,EAAE,EAAE,KAAK,cAAc,OAAO;AAAA,IACjE,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,WAAW,cAA8D;AAC9E,SAAK,GAAG,IAAI,OAAO,QAAQ,SAAS;AAClC,UAAI;AACF,cAAM,OAAO,MAAM,aAAa,MAAM;AACtC,YAAI,CAAC,MAAM;AACT,eAAK,IAAI,MAAM,cAAc,CAAC;AAC9B;AAAA,QACF;AAGA,eAAO,KAAK,OAAO;AACnB,aAAK;AAAA,MACP,SAAS,OAAO;AACd,aAAK,KAAc;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,aAAa;AAClB,SAAK,GAAG,GAAG,cAAc,CAAC,WAAmB;AAC3C,YAAM,OAAO,OAAO,KAAK;AAGzB,aAAO,GAAG,qBAAqB,CAAC,mBAA2B;AACzD,cAAM,WAAW,QAAQ,cAAc;AACvC,eAAO,KAAK,QAAQ;AAGpB,aAAK,UAAU,eAAe,gBAAgB,OAAO,EAAE;AAGvD,eAAO,GAAG,QAAQ,EAAE,KAAK,cAAc;AAAA,UACrC,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB,kBAAkB,KAAK;AAAA,UACvB,eAAe,KAAK;AAAA,UACpB,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC7B,CAAC;AAAA,MACH,CAAC;AAGD,aAAO,GAAG,sBAAsB,CAAC,mBAA2B;AAC1D,cAAM,WAAW,QAAQ,cAAc;AACvC,eAAO,MAAM,QAAQ;AAGrB,aAAK,UAAU,iBAAiB,gBAAgB,OAAO,EAAE;AAGzD,eAAO,GAAG,QAAQ,EAAE,KAAK,cAAc;AAAA,UACrC,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB,kBAAkB,KAAK;AAAA,UACvB,eAAe,KAAK;AAAA,UACpB,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC7B,CAAC;AAAA,MACH,CAAC;AAGD,aAAO,GAAG,UAAU,CAAC,EAAE,gBAAgB,SAAS,MAAqD;AACnG,cAAM,UAAU;AAAA,UACd,MAAM;AAAA,UACN;AAAA,UACA,kBAAkB,KAAK;AAAA,UACvB,eAAe,KAAK;AAAA,UACpB,OAAO,WAAW,UAAU;AAAA,QAC9B;AAGA,eAAO,GAAG,QAAQ,cAAc,EAAE,EAAE,KAAK,cAAc,OAAO;AAG9D,aAAK,UAAU,sBAAsB,gBAAgB,OAAO;AAAA,MAC9D,CAAC;AAGD,aAAO,GAAG,cAAc,MAAM;AAE5B,aAAK,UAAU,aAAa,OAAO,EAAE;AAAA,MACvC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,QAAgB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKO,QAAuB;AAC5B,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAK,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,IAC/B,CAAC;AAAA,EACH;AACF;;;ACjHO,SAAS,gBACd,QACA,QAC0C;AAE1C,QAAM,eAAc,iCAAQ,SAAQ,kBAAkB,OAAO,KAAK,IAAI;AAGtE,QAAM,YAAuB;AAAA;AAAA,IAE3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA,uBAAuB,CAAC,gBAAwB,YAC9C,sBAAsB,aAAa,gBAAgB,OAAO;AAAA,IAE5D,oBAAoB,CAAC,YACnB,mBAAmB,aAAa,OAAO;AAAA,EAC3C;AAGA,MAAI,QAAQ;AACV,UAAM,kBAAkB,IAAI,gBAAgB,QAAQ,WAAW,iCAAQ,MAAM;AAC7E,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AACT;;;ACtDA,eAAsB,cAAc,QAGlB;AAEhB,QAAM,QAAQ,IAAI;AAAA,IAChB,OAAO,QAAQ,cAAc;AAAA,IAC7B,OAAO,aAAa,cAAc;AAAA,EACpC,CAAC;AACH;;;ACQO,SAAS,eAAe,QAAqD;AAClF,MAAI,CAAC,OAAO,OAAO;AACjB,WAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,MAAM,OAAO,MAAM,QAAQ;AAAA,IAC3B,MAAM,OAAO,MAAM,QAAQ;AAAA,IAC3B,UAAU,OAAO,MAAM,YAAY;AAAA,IACnC,IAAI,OAAO,MAAM,MAAM;AAAA,IACvB,sBAAsB,OAAO,MAAM,wBAAwB;AAAA,IAC3D,WAAW,OAAO,MAAM,aAAa;AAAA,EACvC;AACF;;;AClCO,SAAS,WAAW,SAAS,IAAY;AAC9C,QAAM,aAAa;AACnB,MAAI,SAAS;AACb,QAAM,mBAAmB,WAAW;AAEpC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,cAAU,WAAW,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,gBAAgB,CAAC;AAAA,EAC1E;AAEA,SAAO;AACT;AAQO,SAAS,qBACd,GACA,GACQ;AAER,QAAM,eAAe;AAAA,IACnB,GAAG,EAAE,KAAK,IAAI,EAAE,EAAE;AAAA,IAClB,GAAG,EAAE,KAAK,IAAI,EAAE,EAAE;AAAA,EACpB,EAAE,KAAK;AAEP,SAAO,QAAQ,aAAa,KAAK,GAAG,CAAC;AACvC;AAMO,SAAS,iBAAyB;AACvC,SAAO,UAAU,WAAW,EAAE,CAAC;AACjC;AAMO,SAAS,kBAA0B;AACxC,SAAO,OAAO,WAAW,EAAE,CAAC;AAC9B;;;AC3CO,SAAS,oBAAoB,SAAqC;AACvE,MAAI,CAAC,QAAQ,qBAAqB,CAAC,MAAM,QAAQ,QAAQ,iBAAiB,KAAK,QAAQ,kBAAkB,WAAW,GAAG;AACrH,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,QAAQ,eAAe,CAAC,MAAM,QAAQ,QAAQ,WAAW,KAAK,QAAQ,YAAY,WAAW,GAAG;AACnG,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,OAAO;AACjB,QAAI,QAAQ,MAAM,KAAK;AACrB,UAAI,OAAO,QAAQ,MAAM,QAAQ,UAAU;AACzC,eAAO;AAAA,MACT;AAAA,IACF,WAAW,CAAC,QAAQ,MAAM,MAAM;AAC9B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,wBAAwB,MAAsC;AAC5E,MAAI,CAAC,KAAK,gBAAgB;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,gBAAgB;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,aAAa;AACrB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,UAAU;AAClB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM,IAAI;AACzC,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,QAAQ,CAAC,CAAC,QAAQ,QAAQ,EAAE,SAAS,KAAK,IAAI,GAAG;AACxD,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,mBAAmB,CAAC,KAAK,cAAc;AAC9C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAOO,SAAS,+BAA+B,MAA6C;AAC1F,MAAI,CAAC,KAAK,gBAAgB;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,IAAI;AAC1C,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,IAAI;AAC1C,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,EAAE,UAAU,KAAK,EAAE,SAAS,KAAK,EAAE,OAAO,KAAK,EAAE,IAAI;AAC5D,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAOO,SAAS,qBAAqB,MAAmC;AACtE,MAAI,CAAC,KAAK,gBAAgB;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,gBAAgB;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,eAAe;AACvB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,WAAW;AACnB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAQO,SAAS,mBACd,OACA,QAC+C;AAE/C,MAAI,aAAa,QAAQ,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,GAAG,GAAG,IAAI;AAG7D,MAAI,cAAc,UAAU,OAAO,KAAK,MAAM,KAAK,SAAS;AAE5D,SAAO,EAAE,OAAO,YAAY,QAAQ,YAAY;AAClD;;;AC5GA,eAAsB,SACpB,kBACA,SACA,QACuB;AAEvB,QAAM,SAAS,aAAa,kBAAkB,OAAO;AAGrD,QAAM,cAAc,MAAM;AAG1B,QAAM,YAAY,gBAAgB;AAAA,IAChC,OAAO,eAAe,OAAO,KAAK;AAAA,IAClC,QAAQ,QAAQ,UAAU;AAAA,EAC5B,GAAG,MAAM;AAGT,MAAI,UAAU,UAAU,QAAQ;AAE9B,cAAU,OAAO,WAAW,OAAO,WAAW;AAE5C,YAAM,SAAS,OAAO,UAAU,KAAK;AACrC,YAAM,YAAY,OAAO,UAAU,KAAK,aAAa;AAErD,UAAI,CAAC,UAAU,CAAC,QAAQ,kBAAkB,SAAS,SAAS,GAAG;AAC7D,eAAO;AAAA,MACT;AAEA,aAAO,EAAE,IAAI,QAAQ,OAAO,UAAU;AAAA,IACxC,CAAC;AAGD,cAAU,OAAO,WAAW;AAAA,EAC9B;AAGA,QAAM,WAAW;AAAA,IACf,eAAe,2BAA2B,MAAM;AAAA,IAChD,UAAU,sBAAsB,QAAQ;AAAA,MACtC,kBAAkB,CAAC,YAAY;AAC7B,cAAM,UAAU,EAAE,MAAM,mBAAmB,QAAQ;AAGnD,kBAAU;AAAA,UACR,QAAQ,eAAe,SAAS;AAAA,UAChC;AAAA,UACA,CAAC,UAAU,UAAU;AACnB,gBAAI,UAAU,QAAQ;AACpB,oBAAM,SAAS,UAAU,OAAO,MAAM,EAAE,QAAQ,QAAQ,IAAI,QAAQ;AACpE,kBAAI,QAAQ;AACV,uBAAO,KAAK,cAAc,KAAK;AAAA,cACjC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,kBAAU;AAAA,UACR,QAAQ,eAAe,SAAS;AAAA,UAChC;AAAA,QACF;AAAA,MACF;AAAA,MAEA,eAAe,CAAC,SAAS;AACvB,cAAM,UAAU;AAAA,UACd,MAAM;AAAA,UACN,gBAAgB,KAAK;AAAA,UACrB,WAAW,KAAK;AAAA,UAChB,kBAAkB,KAAK;AAAA,UACvB,eAAe,KAAK;AAAA,UACpB,IAAI,KAAK,GAAG,YAAY;AAAA,QAC1B;AAGA,kBAAU;AAAA,UACR,KAAK;AAAA,UACL;AAAA,UACA,CAAC,UAAU,UAAU;AACnB,gBAAI,UAAU,QAAQ;AACpB,oBAAM,SAAS,UAAU,OAAO,MAAM,EAAE,QAAQ,QAAQ,IAAI,QAAQ;AACpE,kBAAI,QAAQ;AACV,uBAAO,KAAK,cAAc,KAAK;AAAA,cACjC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,kBAAU;AAAA,UACR,KAAK;AAAA,UACL;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,QAAQ,UAAU,UAAU;AACvC","sourcesContent":["import mongoose, { Schema, Document, Model } from 'mongoose';\nimport { InitOptions } from '../types';\n\nexport interface IParticipant {\n entityModel: string;\n entityId: string;\n role: string;\n}\n\nexport interface IConversation extends Document {\n organizationId: string;\n participants: IParticipant[];\n lastMessageAt: Date;\n lastMessagePreview?: string;\n lastMessageSenderId?: string;\n lastMessageSenderModel?: string;\n metadata?: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create Conversation model\n */\nexport function createConversationModel(options: InitOptions): Model<IConversation> {\n // Participant schema\n const ParticipantSchema = new Schema({\n entityModel: { \n type: String, \n enum: options.participantModels,\n required: true \n },\n entityId: { \n type: String,\n required: true \n },\n role: { \n type: String, \n enum: options.memberRoles,\n required: true \n },\n }, { _id: false });\n\n // Conversation schema\n const ConversationSchema = new Schema<IConversation>(\n {\n organizationId: { \n type: String,\n required: true,\n index: true\n },\n participants: {\n type: [ParticipantSchema],\n required: true,\n validate: {\n validator: (participants: IParticipant[]) => participants.length === 2,\n message: 'A conversation must have exactly 2 participants',\n },\n },\n lastMessageAt: { \n type: Date,\n index: true\n },\n lastMessagePreview: String,\n lastMessageSenderId: String,\n lastMessageSenderModel: String,\n metadata: Schema.Types.Mixed\n },\n { \n timestamps: true \n }\n );\n\n // Create indexes\n ConversationSchema.index({ \n organizationId: 1, \n 'participants.entityId': 1 \n });\n\n ConversationSchema.index({ \n organizationId: 1, \n lastMessageAt: -1 \n });\n\n // Check if model exists\n const modelName = 'Conversation';\n if (mongoose.models[modelName]) {\n return mongoose.models[modelName] as Model<IConversation>;\n }\n\n // Create and return new model\n return mongoose.model<IConversation>(modelName, ConversationSchema);\n}","import mongoose, { Schema, Document, Model } from 'mongoose';\nimport { InitOptions } from '../types';\n\n/**\n * Message document interface\n */\nexport interface IMessage extends Document {\n organizationId: mongoose.Types.ObjectId;\n conversationId: mongoose.Types.ObjectId;\n senderModel: string;\n senderId: mongoose.Types.ObjectId;\n kind: 'text' | 'system';\n text: string;\n parentMessageId?: mongoose.Types.ObjectId;\n rootThreadId?: mongoose.Types.ObjectId;\n editedAt?: Date;\n deletedAt?: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create Message model\n */\nexport function createMessageModel(options: InitOptions): Model<IMessage> {\n // Message schema\n const MessageSchema = new Schema<IMessage>(\n {\n organizationId: {\n type: Schema.Types.ObjectId,\n ref: 'Organization',\n required: true,\n index: true\n },\n conversationId: {\n type: Schema.Types.ObjectId,\n ref: 'Conversation',\n required: true,\n index: true\n },\n senderModel: {\n type: String,\n enum: options.participantModels,\n required: true\n },\n senderId: {\n type: Schema.Types.ObjectId,\n refPath: 'senderModel',\n required: true,\n index: true\n },\n kind: {\n type: String,\n enum: ['text', 'system'],\n default: 'text',\n index: true\n },\n text: {\n type: String,\n trim: true\n },\n parentMessageId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n rootThreadId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n editedAt: {\n type: Date\n },\n deletedAt: {\n type: Date\n }\n },\n { timestamps: true }\n );\n\n // Create compound index\n MessageSchema.index({\n organizationId: 1,\n conversationId: 1,\n createdAt: -1\n });\n\n // Create text search index if enabled\n if (options.enableTextSearch) {\n MessageSchema.index(\n { text: 'text' },\n {\n name: 'text_search',\n weights: { text: 10 }\n }\n );\n }\n\n // Return model\n return mongoose.model<IMessage>('Message', MessageSchema);\n}","import mongoose from 'mongoose';\nimport { InitOptions } from '../types';\nimport { createConversationModel } from './Conversation.model';\nimport { createMessageModel } from './Message.model';\n\n/**\n * Create Mongoose models with applied configuration\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @returns Object containing Conversation and Message models\n */\nexport function createModels(mongooseInstance: typeof mongoose, options: InitOptions) {\n // Create models with provided options\n const Conversation = createConversationModel(options);\n const Message = createMessageModel(options);\n \n return {\n Conversation,\n Message\n };\n}\n\n/**\n * Connection state enum\n */\nenum ConnectionState {\n Disconnected = 0,\n Connected = 1,\n Connecting = 2,\n Disconnecting = 3,\n}","/**\n * Pagination result interface\n */\nexport interface PaginatedResult<T> {\n items: T[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n\n/**\n * Creates a pagination cursor from a timestamp and ID\n * @param timestamp Timestamp to encode in the cursor\n * @param id ID to encode in the cursor\n * @returns An encoded cursor string\n */\nexport function createCursor(timestamp: number, id: string): string {\n const cursorData = `${timestamp}:${id}`;\n return Buffer.from(cursorData).toString('base64');\n}\n\n/**\n * Parses a pagination cursor into its timestamp and ID components\n * @param cursor The cursor string to parse\n * @returns An object containing the timestamp and ID, or null if invalid\n */\nexport function parseCursor(cursor: string): { timestamp: number; id: string } | null {\n try {\n const decoded = Buffer.from(cursor, 'base64').toString('utf-8');\n const [timestampStr, id] = decoded.split(':');\n \n const timestamp = parseInt(timestampStr, 10);\n \n if (isNaN(timestamp) || !id) {\n return null;\n }\n \n return { timestamp, id };\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Creates a paginated response\n * @param items The items for the current page\n * @param limit The requested page size\n * @param getNextCursor Function to get the next cursor from the last item\n * @returns A paginated result object\n */\nexport function createPaginatedResponse<T>(\n items: T[],\n limit: number,\n getNextCursor: (lastItem: T) => string\n): PaginatedResult<T> {\n const hasMore = items.length > limit;\n \n // If we have more items than the limit, remove the extra item\n // that we used to determine if there are more pages\n const paginatedItems = hasMore ? items.slice(0, limit) : items;\n \n // Get the next cursor from the last item if we have more items\n const nextCursor = hasMore && paginatedItems.length > 0\n ? getNextCursor(paginatedItems[paginatedItems.length - 1])\n : null;\n \n return {\n items: paginatedItems,\n nextCursor,\n hasMore,\n };\n}\n\n\n\n\n","import { Model } from 'mongoose';\nimport { IConversation } from '../../models/Conversation.model';\nimport { SearchConversationsArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search conversations with pagination\n */\nexport async function searchConversations(\n model: Model<IConversation>,\n params: SearchConversationsArgs\n): Promise<PaginatedResult<IConversation>> {\n const { \n organizationId, \n participantModel, \n participantId,\n limit = 20, \n cursor \n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId \n };\n \n // Add participant filter if provided\n if (participantModel && participantId) {\n baseQuery['participants'] = {\n $elemMatch: {\n entityModel: participantModel,\n entityId: participantId\n }\n };\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n const cursorDate = new Date(timestamp);\n \n baseQuery.$or = [\n { lastMessageAt: { $lt: cursorDate } },\n { lastMessageAt: cursorDate, _id: { $lt: id } }\n ];\n }\n }\n \n // Find conversations and sort by lastMessageAt\n const conversations = await model.find(baseQuery)\n .sort({ lastMessageAt: -1 })\n .limit(limit + 1)\n .exec();\n \n // Check if we have more results\n const hasMore = conversations.length > limit;\n const items = hasMore ? conversations.slice(0, limit) : conversations;\n \n // Create next cursor if we have more results\n let nextCursor: string | null = null;\n \n if (hasMore && items.length > 0) {\n const lastItem = items[items.length - 1];\n // Use lastMessageAt if available, otherwise use createdAt\n const timestamp = lastItem.lastMessageAt ? \n lastItem.lastMessageAt.getTime() : \n lastItem.createdAt.getTime();\n \n nextCursor = createCursor(timestamp, lastItem._id.toString());\n }\n \n return {\n items,\n nextCursor,\n hasMore\n };\n}\n\n/**\n * Find a conversation between two specific participants\n */\nexport async function searchByParticipantPair(\n model: Model<IConversation>,\n params: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }\n): Promise<IConversation | null> {\n const { organizationId, a, b } = params;\n \n const conversation = await model.findOne({\n organizationId,\n $and: [\n {\n participants: {\n $elemMatch: {\n entityModel: a.model,\n entityId: a.id\n }\n }\n },\n {\n participants: {\n $elemMatch: {\n entityModel: b.model,\n entityId: b.id\n }\n }\n },\n {\n $expr: {\n $eq: [{ $size: \"$participants\" }, 2]\n }\n }\n ]\n });\n \n return conversation;\n}","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { CreateConversationArgs, SearchConversationsArgs } from '../types';\nimport { searchConversations, searchByParticipantPair } from './search/search.conversations';\nimport { PaginatedResult } from '../utils/pagination';\n\n/**\n * Service for managing conversations\n */\nexport interface ConversationsService {\n createOrFindConversation(args: CreateConversationArgs): Promise<IConversation>;\n getConversation(organizationId: string, conversationId: string): Promise<IConversation | null>;\n searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>>;\n searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null>;\n}\n\n/**\n * Create conversations service\n * @param models MongoDB models\n * @returns Conversations service instance\n */\nexport function createConversationsService(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): ConversationsService {\n const { Conversation } = models;\n\n return {\n /**\n * Create a new conversation or find existing one between participants\n */\n async createOrFindConversation(args: CreateConversationArgs): Promise<IConversation> {\n const { organizationId, a, b } = args;\n\n try {\n // Check if conversation already exists between these participants\n const existingConversation = await searchByParticipantPair(Conversation, {\n organizationId,\n a,\n b\n });\n\n if (existingConversation) {\n return existingConversation;\n }\n\n // Create new conversation\n const newConversation = new Conversation({\n organizationId,\n participants: [\n {\n entityModel: a.model,\n entityId: a.id,\n role: 'member' // Default role\n },\n {\n entityModel: b.model,\n entityId: b.id,\n role: 'member' // Default role\n }\n ],\n lastMessageAt: new Date() // Set initial lastMessageAt\n });\n\n await newConversation.save();\n return newConversation;\n } catch (error) {\n console.error('Error in createOrFindConversation:', error);\n throw error;\n }\n },\n\n /**\n * Get a conversation by ID\n */\n async getConversation(organizationId: string, conversationId: string): Promise<IConversation | null> {\n try {\n return await Conversation.findOne({\n organizationId,\n _id: conversationId\n });\n } catch (error) {\n console.error('Error in getConversation:', error);\n throw error;\n }\n },\n\n /**\n * Search conversations with pagination\n */\n async searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>> {\n try {\n return await searchConversations(Conversation, args);\n } catch (error) {\n console.error('Error in searchConversations:', error);\n throw error;\n }\n },\n\n /**\n * Find a conversation between two specific participants\n */\n async searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null> {\n try {\n return await searchByParticipantPair(Conversation, args);\n } catch (error) {\n console.error('Error in searchByParticipantPair:', error);\n throw error;\n }\n }\n };\n}","import { Model } from 'mongoose';\nimport { IMessage } from '../../models/Message.model';\nimport { SearchMessagesArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search messages based on various criteria\n * @param model Message model\n * @param params Search parameters\n * @returns Paginated messages with optional text search results\n */\nexport async function searchMessages(\n model: Model<IMessage>,\n params: SearchMessagesArgs\n): Promise<PaginatedResult<IMessage>> {\n const { \n organizationId,\n query,\n conversationId,\n senderModel,\n senderId,\n kind,\n dateFrom,\n dateTo,\n threadRootId,\n limit = 20,\n cursor\n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId\n };\n\n // Add optional filters if provided\n if (conversationId) {\n baseQuery.conversationId = conversationId;\n }\n \n if (senderModel && senderId) {\n baseQuery.senderModel = senderModel;\n baseQuery.senderId = senderId;\n } else if (senderModel) {\n baseQuery.senderModel = senderModel;\n }\n \n if (kind) {\n baseQuery.kind = kind;\n }\n \n // Date range filters\n if (dateFrom || dateTo) {\n baseQuery.createdAt = {};\n \n if (dateFrom) {\n baseQuery.createdAt.$gte = new Date(dateFrom);\n }\n \n if (dateTo) {\n baseQuery.createdAt.$lte = new Date(dateTo);\n }\n }\n \n // Thread filter\n if (threadRootId) {\n baseQuery.rootThreadId = threadRootId;\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n \n baseQuery.$or = [\n { createdAt: { $lt: new Date(timestamp) } },\n { createdAt: new Date(timestamp), _id: { $lt: id } }\n ];\n }\n }\n \n // Determine sort order and query approach\n let sort: Record<string, number> = { createdAt: -1 };\n let pipeline: any[] = [];\n \n // Use text search if query provided\n if (query && query.trim()) {\n // Set up text search with score and appropriate sort\n pipeline = [\n { $match: { $text: { $search: query }, ...baseQuery } },\n { $addFields: { score: { $meta: 'textScore' } } },\n { $sort: { score: { $meta: 'textScore' }, createdAt: -1 } },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n } else {\n // Use standard query\n pipeline = [\n { $match: baseQuery },\n { $sort: sort },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n }\n \n // Execute query\n const items = await model.aggregate(pipeline);\n \n // Check if we have more results\n const hasMore = items.length > limit;\n const limitedItems = hasMore ? items.slice(0, limit) : items;\n \n // Create next cursor if we have more results\n const nextCursor = hasMore && limitedItems.length > 0\n ? createCursor(\n new Date(limitedItems[limitedItems.length - 1].createdAt).getTime(),\n limitedItems[limitedItems.length - 1]._id.toString()\n )\n : null;\n \n return {\n items: limitedItems,\n nextCursor,\n hasMore\n };\n}\n","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { SendMessageArgs, ListMessagesArgs, MarkReadArgs } from '../types';\nimport { searchMessages } from './search/search.messages';\nimport { createPaginatedResponse, PaginatedResult } from '../utils/pagination';\n\n/**\n * Message service hooks interface\n */\nexport interface MessageServiceHooks {\n onMessageCreated?: (message: IMessage) => void;\n onMessageRead?: (args: MarkReadArgs & { at: Date }) => void;\n}\n\n/**\n * Service for managing messages\n */\nexport interface MessagesService {\n sendMessage(args: SendMessageArgs): Promise<IMessage>;\n listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>>;\n markRead(args: MarkReadArgs): Promise<void>;\n}\n\n/**\n * Create messages service\n * @param models MongoDB models\n * @param hooks Service hooks\n * @returns Messages service instance\n */\nexport function createMessagesService(\n models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n },\n hooks: MessageServiceHooks = {}\n): MessagesService {\n const { Message, Conversation } = models;\n const { onMessageCreated, onMessageRead } = hooks;\n\n return {\n /**\n * Send a new message\n * @param args Message sending arguments\n * @returns The created message\n */\n async sendMessage(args: SendMessageArgs): Promise<IMessage> {\n const { \n organizationId, \n conversationId, \n senderModel, \n senderId, \n text, \n kind = 'text',\n parentMessageId,\n rootThreadId\n } = args;\n\n // Create the message\n const message = new Message({\n organizationId,\n conversationId,\n senderModel,\n senderId,\n text,\n kind,\n parentMessageId,\n rootThreadId: rootThreadId || parentMessageId // If rootThreadId not provided but parentMessageId is, use parentMessageId\n });\n\n await message.save();\n\n // Update the conversation with last message info\n await Conversation.findByIdAndUpdate(\n conversationId,\n {\n $set: {\n lastMessageAt: message.createdAt,\n lastMessagePreview: text.substring(0, 100),\n lastMessageSenderId: senderId,\n lastMessageSenderModel: senderModel\n }\n }\n );\n\n // Trigger hook\n if (onMessageCreated) {\n onMessageCreated(message);\n }\n\n return message;\n },\n\n /**\n * List messages for a conversation with pagination\n * @param args Message listing arguments\n * @returns Paginated list of messages\n */\n async listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>> {\n const { organizationId, conversationId, limit = 20, cursor } = args;\n\n return searchMessages(Message, {\n organizationId,\n conversationId,\n limit,\n cursor\n });\n },\n\n /**\n * Mark a message as read by a participant\n * @param args Read marking arguments\n */\n async markRead(args: MarkReadArgs): Promise<void> {\n const { \n organizationId, \n conversationId, \n participantModel, \n participantId, \n messageId \n } = args;\n\n const at = new Date();\n\n // Update the message read receipts\n await Message.findOneAndUpdate(\n {\n organizationId,\n conversationId,\n _id: messageId,\n // Ensure read receipt doesn't exist for this participant\n 'readBy.participantId': { $ne: participantId }\n },\n {\n $push: {\n readBy: {\n participantModel,\n participantId,\n readAt: at\n }\n }\n }\n );\n\n // Update participant last read in conversation\n await Conversation.findOneAndUpdate(\n {\n organizationId,\n _id: conversationId,\n 'participants.entityModel': participantModel,\n 'participants.entityId': participantId\n },\n {\n $set: {\n 'participants.$.lastReadMessageId': messageId,\n 'participants.$.lastReadAt': at\n }\n }\n );\n\n // Trigger hook\n if (onMessageRead) {\n onMessageRead({ ...args, at });\n }\n }\n };\n}\n","/**\n * In-memory subscription maps for conversations and sockets\n */\n\n// Maps conversation IDs to sets of socket IDs\nconst convSubs: Map<string, Set<string>> = new Map();\n\n// Maps socket IDs to sets of conversation IDs\nconst socketSubs: Map<string, Set<string>> = new Map();\n\n/**\n * Subscribe a socket to a conversation\n * @param conversationId The conversation ID to subscribe to\n * @param socketId The socket ID to subscribe\n */\nexport function subscribeLocal(conversationId: string, socketId: string): void {\n // Add socket to conversation subscribers\n if (!convSubs.has(conversationId)) {\n convSubs.set(conversationId, new Set());\n }\n convSubs.get(conversationId)?.add(socketId);\n \n // Add conversation to socket subscriptions\n if (!socketSubs.has(socketId)) {\n socketSubs.set(socketId, new Set());\n }\n socketSubs.get(socketId)?.add(conversationId);\n}\n\n/**\n * Unsubscribe a socket from a conversation\n * @param conversationId The conversation ID to unsubscribe from\n * @param socketId The socket ID to unsubscribe\n */\nexport function unsubscribeLocal(conversationId: string, socketId: string): void {\n // Remove socket from conversation subscribers\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n \n // Remove conversation from socket subscriptions\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n conversations.delete(conversationId);\n if (conversations.size === 0) {\n socketSubs.delete(socketId);\n }\n }\n}\n\n/**\n * Clean up all subscriptions for a socket\n * @param socketId The socket ID to clean up\n */\nexport function cleanupLocal(socketId: string): void {\n // Get all conversations this socket is subscribed to\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n // Unsubscribe from each conversation\n for (const conversationId of conversations) {\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n }\n \n // Remove socket from socket subscriptions\n socketSubs.delete(socketId);\n }\n}\n\n/**\n * Fan out a message to all local subscribers of a conversation\n * @param conversationId The conversation ID to fan out to\n * @param payload The payload to send\n * @param emitter Function to emit events to socket IDs\n */\nexport function fanoutLocal(\n conversationId: string,\n payload: any,\n emitter: (toSocketId: string, event: any) => void\n): void {\n // Get all sockets subscribed to this conversation\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n // Emit to each socket\n for (const socketId of sockets) {\n try {\n emitter(socketId, payload);\n } catch (error) {\n console.error(`Error emitting to socket ${socketId}:`, error);\n }\n }\n }\n}","import Redis from 'ioredis';\nimport { RedisConfig } from '../config/env';\n\n/**\n * Create a Redis client based on provided configuration\n * @param config Redis configuration\n * @returns Redis client instance or null if configuration is missing\n */\nexport function createRedisClient(config?: RedisConfig): Redis | null {\n if (!config) {\n console.warn('Redis configuration not found. Redis features will be disabled.');\n return null;\n }\n \n try {\n let client: Redis;\n \n // Use either URL or granular connection options\n if (config.url) {\n client = new Redis(config.url);\n } else if (config.host) {\n client = new Redis({\n host: config.host,\n port: config.port,\n password: config.password,\n db: config.db,\n connectTimeout: config.socketConnectTimeout, // Changed \n enableReadyCheck: true,\n enableOfflineQueue: true,\n retryStrategy(times) {\n const delay = Math.min(times * 50, 2000);\n return delay;\n }\n });\n } else {\n console.warn('Invalid Redis configuration. Redis features will be disabled.');\n return null;\n }\n \n // Log Redis connection events\n client.on('connect', () => {\n console.info('Redis client connected');\n });\n \n client.on('error', (err) => {\n console.error('Redis client error:', err);\n });\n \n client.on('reconnecting', () => {\n console.info('Redis client reconnecting');\n });\n \n return client;\n } catch (error) {\n console.error('Failed to create Redis client:', error);\n return null;\n }\n}\n\n/**\n * Publish a message to a conversation channel\n * @param client Redis client\n * @param conversationId Conversation ID\n * @param payload Message payload\n * @returns Promise resolving to number of clients that received the message\n */\nexport async function publishToConversation(\n client: Redis | null,\n conversationId: string,\n payload: any\n): Promise<number> {\n if (!client) return 0;\n \n try {\n const channel = `conv:${conversationId}`;\n const message = JSON.stringify(payload);\n return await client.publish(channel, message);\n } catch (error) {\n console.error(`Failed to publish to conversation ${conversationId}:`, error);\n return 0;\n }\n}\n\n/**\n * Start Redis subscription listener\n * @param client Redis client\n * @param onEvent Callback function for received events\n * @returns Function to stop the listener\n */\nexport function startRedisListener(\n client: Redis | null,\n onEvent: (conversationId: string, payload: any) => void\n): (() => void) | null {\n if (!client) return null;\n \n try {\n // Create a duplicate client for subscribing\n // (Redis clients in subscribe mode cannot be used for other commands)\n const subClient = client.duplicate();\n \n // Subscribe to conversation channels\n subClient.psubscribe('conv:*');\n \n // Handle incoming messages\n subClient.on('pmessage', (_pattern, channel, message) => {\n try {\n // Extract conversation ID from channel\n const conversationId = channel.slice(5); // Remove 'conv:' prefix\n \n // Parse message payload\n const payload = JSON.parse(message);\n \n // Call event handler\n onEvent(conversationId, payload);\n } catch (error) {\n console.error('Error processing Redis message:', error);\n }\n });\n \n // Return function to stop listening\n return () => {\n subClient.punsubscribe('conv:*');\n subClient.quit();\n };\n } catch (error) {\n console.error('Failed to start Redis listener:', error);\n return null;\n }\n}","import { Server, Socket } from 'socket.io';\nimport type { Server as HTTPServer } from 'http';\nimport { Transport } from './index';\n\nexport interface SocketConfig {\n path?: string;\n cors?: {\n origin?: string | string[];\n methods?: string[];\n };\n}\n\nexport interface SocketUser {\n id: string;\n model: string;\n}\n\nexport class SocketTransport {\n private io: Server;\n private transport: Transport;\n\n constructor(server: HTTPServer, transport: Transport, config?: SocketConfig) {\n this.io = new Server(server, {\n path: config?.path || '/socket.io',\n addTrailingSlash: false,\n cors: config?.cors || {\n origin: '*',\n methods: ['GET', 'POST']\n }\n });\n this.transport = transport;\n\n // Listen for Redis events and broadcast to sockets\n this.transport.startRedisListener((conversationId: string, payload: any) => {\n this.io.to(`conv:${conversationId}`).emit('chat-event', payload);\n });\n }\n\n /**\n * Handle socket authentication\n */\n public handleAuth(authenticate: (socket: Socket) => Promise<SocketUser | null>) {\n this.io.use(async (socket, next) => {\n try {\n const user = await authenticate(socket);\n if (!user) {\n next(new Error('Unauthorized'));\n return;\n }\n \n // Store user info in socket data\n socket.data.user = user;\n next();\n } catch (error) {\n next(error as Error);\n }\n });\n }\n\n /**\n * Initialize socket event handlers\n */\n public initialize() {\n this.io.on('connection', (socket: Socket) => {\n const user = socket.data.user as SocketUser;\n\n // Join a conversation\n socket.on('join-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.join(roomName);\n \n // Register in local subscription system\n this.transport.subscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:join',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Leave a conversation\n socket.on('leave-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.leave(roomName);\n \n // Remove from local subscription system\n this.transport.unsubscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:leave',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Handle typing events\n socket.on('typing', ({ conversationId, isTyping }: { conversationId: string; isTyping: boolean }) => {\n const payload = {\n type: 'typing',\n conversationId,\n participantModel: user.model,\n participantId: user.id,\n state: isTyping ? 'start' : 'stop'\n };\n \n // Emit to conversation members\n socket.to(`conv:${conversationId}`).emit('chat-event', payload);\n \n // Publish to Redis for cross-instance communication\n this.transport.publishToConversation(conversationId, payload);\n });\n\n // Handle disconnect\n socket.on('disconnect', () => {\n // Clean up subscriptions\n this.transport.cleanupLocal(socket.id);\n });\n });\n }\n\n /**\n * Get Socket.IO server instance\n */\n public getIO(): Server {\n return this.io;\n }\n\n /**\n * Close Socket.IO server\n */\n public close(): Promise<void> {\n return new Promise((resolve) => {\n this.io.close(() => resolve());\n });\n }\n}\n","import * as localSubs from './localSubs';\nimport { createRedisClient, publishToConversation, startRedisListener } from './redis';\nimport { SocketTransport, SocketConfig } from './socket';\nimport { InitOptions } from '../types';\nimport type { Server as HTTPServer } from 'http';\n\n/**\n * Transport configuration type\n */\nexport interface TransportConfig {\n redis?: InitOptions['redis'];\n socket?: SocketConfig;\n}\n\n/**\n * Transport instance type\n */\nexport interface Transport {\n subscribeLocal: typeof localSubs.subscribeLocal;\n unsubscribeLocal: typeof localSubs.unsubscribeLocal;\n cleanupLocal: typeof localSubs.cleanupLocal;\n fanoutLocal: typeof localSubs.fanoutLocal;\n publishToConversation: (conversationId: string, payload: any) => Promise<number>;\n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => (() => void) | null;\n}\n\n/**\n * Create transport system with optional Redis and Socket.IO support\n */\nexport function createTransport(\n config?: TransportConfig,\n server?: HTTPServer\n): Transport & { socket?: SocketTransport } {\n // Initialize Redis client if configuration is provided\n const redisClient = config?.redis ? createRedisClient(config.redis) : null;\n \n // Create base transport\n const transport: Transport = {\n // Local subscription methods\n subscribeLocal: localSubs.subscribeLocal,\n unsubscribeLocal: localSubs.unsubscribeLocal,\n cleanupLocal: localSubs.cleanupLocal,\n fanoutLocal: localSubs.fanoutLocal,\n \n // Redis methods\n publishToConversation: (conversationId: string, payload: any) => \n publishToConversation(redisClient, conversationId, payload),\n \n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => \n startRedisListener(redisClient, onEvent),\n };\n\n // Initialize Socket.IO if server is provided\n if (server) {\n const socketTransport = new SocketTransport(server, transport, config?.socket);\n return {\n ...transport,\n socket: socketTransport\n };\n }\n\n return transport;\n}","import mongoose, { Model } from 'mongoose';\nimport { IConversation } from './Conversation.model';\nimport { IMessage } from './Message.model';\n\n/**\n * Ensure all indexes are created on models\n * @param models Object containing Mongoose models\n */\nexport async function ensureIndexes(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): Promise<void> {\n // Create indexes\n await Promise.all([\n models.Message.ensureIndexes(),\n models.Conversation.ensureIndexes(),\n ]);\n}","/**\n * Redis configuration type\n */\nexport type RedisConfig = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n socketConnectTimeout?: number;\n keepAlive?: number;\n};\n\n/**\n * Environment configuration\n */\nexport interface EnvironmentConfig {\n /** MongoDB connection URI */\n mongoUri: string;\n /** Redis configuration (optional) */\n redis?: RedisConfig;\n}\n/**\n * Get Redis configuration from provided options\n */\nexport function getRedisConfig(config: { redis?: RedisConfig }): RedisConfig | null {\n if (!config.redis) {\n return null;\n }\n\n // Return the full configuration\n return {\n host: config.redis.host || undefined,\n port: config.redis.port || undefined,\n password: config.redis.password || undefined,\n db: config.redis.db || undefined,\n socketConnectTimeout: config.redis.socketConnectTimeout || undefined,\n keepAlive: config.redis.keepAlive || undefined\n };\n}\n","/**\n * Generates a random string ID with the specified length\n * @param length The length of the ID to generate (default: 24)\n * @returns A random string ID\n */\nexport function generateId(length = 24): string {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n const charactersLength = characters.length;\n \n for (let i = 0; i < length; i++) {\n result += characters.charAt(Math.floor(Math.random() * charactersLength));\n }\n \n return result;\n}\n\n/**\n * Creates a conversation ID from two participant identifiers\n * @param a First participant (model:id)\n * @param b Second participant (model:id)\n * @returns A deterministic conversation ID\n */\nexport function createConversationId(\n a: { model: string; id: string },\n b: { model: string; id: string }\n): string {\n // Sort participants to ensure the same ID regardless of order\n const participants = [\n `${a.model}:${a.id}`,\n `${b.model}:${b.id}`,\n ].sort();\n \n return `conv_${participants.join('_')}`;\n}\n\n/**\n * Creates a thread ID\n * @returns A unique thread ID\n */\nexport function createThreadId(): string {\n return `thread_${generateId(16)}`;\n}\n\n/**\n * Creates a message ID\n * @returns A unique message ID\n */\nexport function createMessageId(): string {\n return `msg_${generateId(16)}`;\n}\n\n\n\n\n","import { InitOptions, SendMessageArgs, CreateConversationArgs, MarkReadArgs } from '../types';\n\n/**\n * Validates initialization options\n * @param options Options to validate\n * @returns Validation error message or null if valid\n */\nexport function validateInitOptions(options: InitOptions): string | null {\n if (!options.participantModels || !Array.isArray(options.participantModels) || options.participantModels.length === 0) {\n return 'participantModels must be a non-empty array of strings';\n }\n\n if (!options.memberRoles || !Array.isArray(options.memberRoles) || options.memberRoles.length === 0) {\n return 'memberRoles must be a non-empty array of strings';\n }\n\n // Validate Redis options if provided\n if (options.redis) {\n if (options.redis.url) {\n if (typeof options.redis.url !== 'string') {\n return 'redis.url must be a string';\n }\n } else if (!options.redis.host) {\n return 'Either redis.url or redis.host must be provided';\n }\n }\n\n return null;\n}\n\n/**\n * Validates message sending arguments\n * @param args Message sending arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateSendMessageArgs(args: SendMessageArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.senderModel) {\n return 'senderModel is required';\n }\n\n if (!args.senderId) {\n return 'senderId is required';\n }\n\n if (!args.text || args.text.trim() === '') {\n return 'text is required and cannot be empty';\n }\n\n // If kind is provided, validate it's a valid value\n if (args.kind && !['text', 'system'].includes(args.kind)) {\n return 'kind must be either \"text\" or \"system\"';\n }\n\n // If this is a reply, ensure all thread fields are present\n if (args.parentMessageId && !args.rootThreadId) {\n return 'rootThreadId is required when parentMessageId is provided';\n }\n\n return null;\n}\n\n/**\n * Validates conversation creation arguments\n * @param args Conversation creation arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateCreateConversationArgs(args: CreateConversationArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.a || !args.a.model || !args.a.id) {\n return 'a.model and a.id are required';\n }\n\n if (!args.b || !args.b.model || !args.b.id) {\n return 'b.model and b.id are required';\n }\n\n // Prevent creating a conversation between the same participant\n if (args.a.model === args.b.model && args.a.id === args.b.id) {\n return 'Cannot create a conversation between the same participant';\n }\n\n return null;\n}\n\n/**\n * Validates mark read arguments\n * @param args Mark read arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateMarkReadArgs(args: MarkReadArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.participantModel) {\n return 'participantModel is required';\n }\n\n if (!args.participantId) {\n return 'participantId is required';\n }\n\n if (!args.messageId) {\n return 'messageId is required';\n }\n\n return null;\n}\n\n/**\n * Validates pagination parameters\n * @param limit Pagination limit\n * @param cursor Pagination cursor\n * @returns Validated limit and cursor\n */\nexport function validatePagination(\n limit?: number,\n cursor?: string\n): { limit: number; cursor: string | undefined } {\n // Default limit to 20, max to 100\n let validLimit = limit ? Math.min(Math.max(1, limit), 100) : 20;\n \n // If cursor is provided but empty string, set to undefined\n let validCursor = cursor && cursor.trim() !== '' ? cursor : undefined;\n \n return { limit: validLimit, cursor: validCursor };\n}\n\n\n\n\n","import mongoose from 'mongoose';\nimport type { Server as HTTPServer } from 'http';\nimport { createModels } from './models/connection';\nimport { createConversationsService, ConversationsService } from './services/conversations.service';\nimport { createMessagesService, MessagesService } from './services/messages.service';\nimport { createTransport, Transport } from './transport';\nimport { InitOptions } from './types';\nimport { ensureIndexes } from './models/indexes';\nimport { getRedisConfig } from './config/env';\nimport { SocketTransport } from './transport/socket';\n\n/**\n * Chat services container\n */\nexport interface ChatServices {\n models: {\n Conversation: mongoose.Model<any>;\n Message: mongoose.Model<any>;\n };\n services: {\n conversations: ConversationsService;\n messages: MessagesService;\n };\n transport: Transport & { socket?: SocketTransport };\n}\n\n/**\n * Initialize the chat system\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @param server Optional HTTP server for Socket.IO integration\n * @returns Chat services\n */\nexport async function initChat(\n mongooseInstance: typeof mongoose,\n options: InitOptions,\n server?: HTTPServer\n): Promise<ChatServices> {\n // Create models\n const models = createModels(mongooseInstance, options);\n\n // Ensure indexes are created\n await ensureIndexes(models);\n\n // Create transport system with optional Socket.IO support\n const transport = createTransport({\n redis: getRedisConfig(options) || undefined,\n socket: options.socket || undefined\n }, server);\n\n // Initialize Socket.IO if server was provided\n if (server && transport.socket) {\n // Set up authentication handler\n transport.socket.handleAuth(async (socket) => {\n // Get user info from socket handshake auth\n const userId = socket.handshake.auth.userId;\n const userModel = socket.handshake.auth.userModel || 'User';\n\n if (!userId || !options.participantModels.includes(userModel)) {\n return null;\n }\n\n return { id: userId, model: userModel };\n });\n\n // Initialize socket event handlers\n transport.socket.initialize();\n }\n\n // Create services with hooks\n const services = {\n conversations: createConversationsService(models),\n messages: createMessagesService(models, {\n onMessageCreated: (message) => {\n const payload = { type: 'message:created', message };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n message.conversationId.toString(),\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n message.conversationId.toString(),\n payload\n );\n },\n \n onMessageRead: (args) => {\n const payload = { \n type: 'conversation:read', \n conversationId: args.conversationId,\n messageId: args.messageId,\n participantModel: args.participantModel,\n participantId: args.participantId,\n at: args.at.toISOString()\n };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n args.conversationId,\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n args.conversationId,\n payload\n );\n }\n })\n };\n\n return { models, services, transport };\n}\n\n// Re-export types\nexport * from './types';\nexport * from './utils';\nexport * from './models';\nexport * from './services';\nexport * from './transport';\nexport * from './config/env';"]}
|
|
1
|
+
{"version":3,"sources":["../src/models/Conversation.model.ts","../src/models/Message.model.ts","../src/models/connection.ts","../src/utils/pagination.ts","../src/services/search/search.conversations.ts","../src/services/conversations.service.ts","../src/services/search/search.messages.ts","../src/services/messages.service.ts","../src/transport/localSubs.ts","../src/transport/redis.ts","../src/transport/socket.ts","../src/transport/index.ts","../src/models/indexes.ts","../src/config/env.ts","../src/utils/ids.ts","../src/utils/validators.ts","../src/index.ts"],"names":["mongoose","Schema"],"mappings":"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","sourcesContent":["import mongoose, { Schema, Document, Model, Connection } from 'mongoose';\nimport { InitOptions } from '../types';\n\nexport interface IParticipant {\n entityModel: string;\n entityId: string;\n role: string;\n}\n\nexport interface IConversation extends Document {\n organizationId: string;\n participants: IParticipant[];\n lastMessageAt: Date;\n lastMessagePreview?: string;\n lastMessageSenderId?: string;\n lastMessageSenderModel?: string;\n metadata?: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create (or retrieve) Conversation model safely\n */\nexport function createConversationModel(\n options: InitOptions,\n conn: Connection = mongoose.connection\n): Model<IConversation> {\n // Participant schema\n const ParticipantSchema = new Schema(\n {\n entityModel: {\n type: String,\n enum: options.participantModels,\n required: true,\n },\n entityId: {\n type: String,\n required: true,\n },\n role: {\n type: String,\n enum: options.memberRoles,\n required: true,\n },\n },\n { _id: false }\n );\n\n // Conversation schema\n const ConversationSchema = new Schema<IConversation>(\n {\n organizationId: {\n type: String,\n required: true,\n index: true,\n },\n participants: {\n type: [ParticipantSchema],\n required: true,\n validate: {\n validator: (participants: IParticipant[]) => participants.length === 2,\n message: 'A conversation must have exactly 2 participants',\n },\n },\n lastMessageAt: {\n type: Date,\n index: true,\n },\n lastMessagePreview: String,\n lastMessageSenderId: String,\n lastMessageSenderModel: String,\n metadata: Schema.Types.Mixed,\n },\n { timestamps: true }\n );\n\n // Indexes\n ConversationSchema.index({\n organizationId: 1,\n 'participants.entityId': 1,\n });\n\n ConversationSchema.index({\n organizationId: 1,\n lastMessageAt: -1,\n });\n\n const name = 'Conversation';\n // ✅ Return on the same line & reuse existing model to avoid OverwriteModelError\n return (conn.models[name] as Model<IConversation>) || conn.model<IConversation>(name, ConversationSchema);\n}\n","import mongoose, { Schema, Document, Model } from 'mongoose';\nimport { InitOptions } from '../types';\n\n/**\n * Message document interface\n */\nexport interface IMessage extends Document {\n organizationId: mongoose.Types.ObjectId;\n conversationId: mongoose.Types.ObjectId;\n senderModel: string;\n senderId: mongoose.Types.ObjectId;\n kind: 'text' | 'system';\n text: string;\n parentMessageId?: mongoose.Types.ObjectId;\n rootThreadId?: mongoose.Types.ObjectId;\n editedAt?: Date;\n deletedAt?: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create Message model\n */\nexport function createMessageModel(options: InitOptions): Model<IMessage> {\n // Message schema\n const MessageSchema = new Schema<IMessage>(\n {\n organizationId: {\n type: Schema.Types.ObjectId,\n ref: 'Organization',\n required: true,\n index: true\n },\n conversationId: {\n type: Schema.Types.ObjectId,\n ref: 'Conversation',\n required: true,\n index: true\n },\n senderModel: {\n type: String,\n enum: options.participantModels,\n required: true\n },\n senderId: {\n type: Schema.Types.ObjectId,\n refPath: 'senderModel',\n required: true,\n index: true\n },\n kind: {\n type: String,\n enum: ['text', 'system'],\n default: 'text',\n index: true\n },\n text: {\n type: String,\n trim: true\n },\n parentMessageId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n rootThreadId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n editedAt: {\n type: Date\n },\n deletedAt: {\n type: Date\n }\n },\n { timestamps: true }\n );\n\n // Create compound index\n MessageSchema.index({\n organizationId: 1,\n conversationId: 1,\n createdAt: -1\n });\n\n // Create text search index if enabled\n if (options.enableTextSearch) {\n MessageSchema.index(\n { text: 'text' },\n {\n name: 'text_search',\n weights: { text: 10 }\n }\n );\n }\n\n // Return model\n return mongoose.model<IMessage>('Message', MessageSchema);\n}","import mongoose from 'mongoose';\nimport { InitOptions } from '../types';\nimport { createConversationModel } from './Conversation.model';\nimport { createMessageModel } from './Message.model';\n\n/**\n * Create Mongoose models with applied configuration\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @returns Object containing Conversation and Message models\n */\nexport function createModels(mongooseInstance: typeof mongoose, options: InitOptions) {\n // Create models with provided options\n const Conversation = createConversationModel(options);\n const Message = createMessageModel(options);\n \n return {\n Conversation,\n Message\n };\n}\n\n/**\n * Connection state enum\n */\nenum ConnectionState {\n Disconnected = 0,\n Connected = 1,\n Connecting = 2,\n Disconnecting = 3,\n}","/**\n * Pagination result interface\n */\nexport interface PaginatedResult<T> {\n items: T[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n\n/**\n * Creates a pagination cursor from a timestamp and ID\n * @param timestamp Timestamp to encode in the cursor\n * @param id ID to encode in the cursor\n * @returns An encoded cursor string\n */\nexport function createCursor(timestamp: number, id: string): string {\n const cursorData = `${timestamp}:${id}`;\n return Buffer.from(cursorData).toString('base64');\n}\n\n/**\n * Parses a pagination cursor into its timestamp and ID components\n * @param cursor The cursor string to parse\n * @returns An object containing the timestamp and ID, or null if invalid\n */\nexport function parseCursor(cursor: string): { timestamp: number; id: string } | null {\n try {\n const decoded = Buffer.from(cursor, 'base64').toString('utf-8');\n const [timestampStr, id] = decoded.split(':');\n \n const timestamp = parseInt(timestampStr, 10);\n \n if (isNaN(timestamp) || !id) {\n return null;\n }\n \n return { timestamp, id };\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Creates a paginated response\n * @param items The items for the current page\n * @param limit The requested page size\n * @param getNextCursor Function to get the next cursor from the last item\n * @returns A paginated result object\n */\nexport function createPaginatedResponse<T>(\n items: T[],\n limit: number,\n getNextCursor: (lastItem: T) => string\n): PaginatedResult<T> {\n const hasMore = items.length > limit;\n \n // If we have more items than the limit, remove the extra item\n // that we used to determine if there are more pages\n const paginatedItems = hasMore ? items.slice(0, limit) : items;\n \n // Get the next cursor from the last item if we have more items\n const nextCursor = hasMore && paginatedItems.length > 0\n ? getNextCursor(paginatedItems[paginatedItems.length - 1])\n : null;\n \n return {\n items: paginatedItems,\n nextCursor,\n hasMore,\n };\n}\n\n\n\n\n","import { Model } from 'mongoose';\nimport { IConversation } from '../../models/Conversation.model';\nimport { SearchConversationsArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search conversations with pagination\n */\nexport async function searchConversations(\n model: Model<IConversation>,\n params: SearchConversationsArgs\n): Promise<PaginatedResult<IConversation>> {\n const { \n organizationId, \n participantModel, \n participantId,\n limit = 20, \n cursor \n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId \n };\n \n // Add participant filter if provided\n if (participantModel && participantId) {\n baseQuery['participants'] = {\n $elemMatch: {\n entityModel: participantModel,\n entityId: participantId\n }\n };\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n const cursorDate = new Date(timestamp);\n \n baseQuery.$or = [\n { lastMessageAt: { $lt: cursorDate } },\n { lastMessageAt: cursorDate, _id: { $lt: id } }\n ];\n }\n }\n \n // Find conversations and sort by lastMessageAt\n const conversations = await model.find(baseQuery)\n .sort({ lastMessageAt: -1 })\n .limit(limit + 1)\n .exec();\n \n // Check if we have more results\n const hasMore = conversations.length > limit;\n const items = hasMore ? conversations.slice(0, limit) : conversations;\n \n // Create next cursor if we have more results\n let nextCursor: string | null = null;\n \n if (hasMore && items.length > 0) {\n const lastItem = items[items.length - 1];\n // Use lastMessageAt if available, otherwise use createdAt\n const timestamp = lastItem.lastMessageAt ? \n lastItem.lastMessageAt.getTime() : \n lastItem.createdAt.getTime();\n \n nextCursor = createCursor(timestamp, lastItem._id.toString());\n }\n \n return {\n items,\n nextCursor,\n hasMore\n };\n}\n\n/**\n * Find a conversation between two specific participants\n */\nexport async function searchByParticipantPair(\n model: Model<IConversation>,\n params: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }\n): Promise<IConversation | null> {\n const { organizationId, a, b } = params;\n \n const conversation = await model.findOne({\n organizationId,\n $and: [\n {\n participants: {\n $elemMatch: {\n entityModel: a.model,\n entityId: a.id\n }\n }\n },\n {\n participants: {\n $elemMatch: {\n entityModel: b.model,\n entityId: b.id\n }\n }\n },\n {\n $expr: {\n $eq: [{ $size: \"$participants\" }, 2]\n }\n }\n ]\n });\n \n return conversation;\n}","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { CreateConversationArgs, SearchConversationsArgs } from '../types';\nimport { searchConversations, searchByParticipantPair } from './search/search.conversations';\nimport { PaginatedResult } from '../utils/pagination';\n\n/**\n * Service for managing conversations\n */\nexport interface ConversationsService {\n createOrFindConversation(args: CreateConversationArgs): Promise<IConversation>;\n getConversation(organizationId: string, conversationId: string): Promise<IConversation | null>;\n searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>>;\n searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null>;\n}\n\n/**\n * Create conversations service\n * @param models MongoDB models\n * @returns Conversations service instance\n */\nexport function createConversationsService(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): ConversationsService {\n const { Conversation } = models;\n\n return {\n /**\n * Create a new conversation or find existing one between participants\n */\n async createOrFindConversation(args: CreateConversationArgs): Promise<IConversation> {\n const { organizationId, a, b } = args;\n\n try {\n // Check if conversation already exists between these participants\n const existingConversation = await searchByParticipantPair(Conversation, {\n organizationId,\n a,\n b\n });\n\n if (existingConversation) {\n return existingConversation;\n }\n\n // Create new conversation\n const newConversation = new Conversation({\n organizationId,\n participants: [\n {\n entityModel: a.model,\n entityId: a.id,\n role: 'member' // Default role\n },\n {\n entityModel: b.model,\n entityId: b.id,\n role: 'member' // Default role\n }\n ],\n lastMessageAt: new Date() // Set initial lastMessageAt\n });\n\n await newConversation.save();\n return newConversation;\n } catch (error) {\n console.error('Error in createOrFindConversation:', error);\n throw error;\n }\n },\n\n /**\n * Get a conversation by ID\n */\n async getConversation(organizationId: string, conversationId: string): Promise<IConversation | null> {\n try {\n return await Conversation.findOne({\n organizationId,\n _id: conversationId\n });\n } catch (error) {\n console.error('Error in getConversation:', error);\n throw error;\n }\n },\n\n /**\n * Search conversations with pagination\n */\n async searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>> {\n try {\n return await searchConversations(Conversation, args);\n } catch (error) {\n console.error('Error in searchConversations:', error);\n throw error;\n }\n },\n\n /**\n * Find a conversation between two specific participants\n */\n async searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null> {\n try {\n return await searchByParticipantPair(Conversation, args);\n } catch (error) {\n console.error('Error in searchByParticipantPair:', error);\n throw error;\n }\n }\n };\n}","import { Model } from 'mongoose';\nimport { IMessage } from '../../models/Message.model';\nimport { SearchMessagesArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search messages based on various criteria\n * @param model Message model\n * @param params Search parameters\n * @returns Paginated messages with optional text search results\n */\nexport async function searchMessages(\n model: Model<IMessage>,\n params: SearchMessagesArgs\n): Promise<PaginatedResult<IMessage>> {\n const { \n organizationId,\n query,\n conversationId,\n senderModel,\n senderId,\n kind,\n dateFrom,\n dateTo,\n threadRootId,\n limit = 20,\n cursor\n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId\n };\n\n // Add optional filters if provided\n if (conversationId) {\n baseQuery.conversationId = conversationId;\n }\n \n if (senderModel && senderId) {\n baseQuery.senderModel = senderModel;\n baseQuery.senderId = senderId;\n } else if (senderModel) {\n baseQuery.senderModel = senderModel;\n }\n \n if (kind) {\n baseQuery.kind = kind;\n }\n \n // Date range filters\n if (dateFrom || dateTo) {\n baseQuery.createdAt = {};\n \n if (dateFrom) {\n baseQuery.createdAt.$gte = new Date(dateFrom);\n }\n \n if (dateTo) {\n baseQuery.createdAt.$lte = new Date(dateTo);\n }\n }\n \n // Thread filter\n if (threadRootId) {\n baseQuery.rootThreadId = threadRootId;\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n \n baseQuery.$or = [\n { createdAt: { $lt: new Date(timestamp) } },\n { createdAt: new Date(timestamp), _id: { $lt: id } }\n ];\n }\n }\n \n // Determine sort order and query approach\n let sort: Record<string, number> = { createdAt: -1 };\n let pipeline: any[] = [];\n \n // Use text search if query provided\n if (query && query.trim()) {\n // Set up text search with score and appropriate sort\n pipeline = [\n { $match: { $text: { $search: query }, ...baseQuery } },\n { $addFields: { score: { $meta: 'textScore' } } },\n { $sort: { score: { $meta: 'textScore' }, createdAt: -1 } },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n } else {\n // Use standard query\n pipeline = [\n { $match: baseQuery },\n { $sort: sort },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n }\n \n // Execute query\n const items = await model.aggregate(pipeline);\n \n // Check if we have more results\n const hasMore = items.length > limit;\n const limitedItems = hasMore ? items.slice(0, limit) : items;\n \n // Create next cursor if we have more results\n const nextCursor = hasMore && limitedItems.length > 0\n ? createCursor(\n new Date(limitedItems[limitedItems.length - 1].createdAt).getTime(),\n limitedItems[limitedItems.length - 1]._id.toString()\n )\n : null;\n \n return {\n items: limitedItems,\n nextCursor,\n hasMore\n };\n}\n","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { SendMessageArgs, ListMessagesArgs, MarkReadArgs } from '../types';\nimport { searchMessages } from './search/search.messages';\nimport { createPaginatedResponse, PaginatedResult } from '../utils/pagination';\n\n/**\n * Message service hooks interface\n */\nexport interface MessageServiceHooks {\n onMessageCreated?: (message: IMessage) => void;\n onMessageRead?: (args: MarkReadArgs & { at: Date }) => void;\n}\n\n/**\n * Service for managing messages\n */\nexport interface MessagesService {\n sendMessage(args: SendMessageArgs): Promise<IMessage>;\n listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>>;\n markRead(args: MarkReadArgs): Promise<void>;\n}\n\n/**\n * Create messages service\n * @param models MongoDB models\n * @param hooks Service hooks\n * @returns Messages service instance\n */\nexport function createMessagesService(\n models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n },\n hooks: MessageServiceHooks = {}\n): MessagesService {\n const { Message, Conversation } = models;\n const { onMessageCreated, onMessageRead } = hooks;\n\n return {\n /**\n * Send a new message\n * @param args Message sending arguments\n * @returns The created message\n */\n async sendMessage(args: SendMessageArgs): Promise<IMessage> {\n const { \n organizationId, \n conversationId, \n senderModel, \n senderId, \n text, \n kind = 'text',\n parentMessageId,\n rootThreadId\n } = args;\n\n // Create the message\n const message = new Message({\n organizationId,\n conversationId,\n senderModel,\n senderId,\n text,\n kind,\n parentMessageId,\n rootThreadId: rootThreadId || parentMessageId // If rootThreadId not provided but parentMessageId is, use parentMessageId\n });\n\n await message.save();\n\n // Update the conversation with last message info\n await Conversation.findByIdAndUpdate(\n conversationId,\n {\n $set: {\n lastMessageAt: message.createdAt,\n lastMessagePreview: text.substring(0, 100),\n lastMessageSenderId: senderId,\n lastMessageSenderModel: senderModel\n }\n }\n );\n\n // Trigger hook\n if (onMessageCreated) {\n onMessageCreated(message);\n }\n\n return message;\n },\n\n /**\n * List messages for a conversation with pagination\n * @param args Message listing arguments\n * @returns Paginated list of messages\n */\n async listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>> {\n const { organizationId, conversationId, limit = 20, cursor } = args;\n\n return searchMessages(Message, {\n organizationId,\n conversationId,\n limit,\n cursor\n });\n },\n\n /**\n * Mark a message as read by a participant\n * @param args Read marking arguments\n */\n async markRead(args: MarkReadArgs): Promise<void> {\n const { \n organizationId, \n conversationId, \n participantModel, \n participantId, \n messageId \n } = args;\n\n const at = new Date();\n\n // Update the message read receipts\n await Message.findOneAndUpdate(\n {\n organizationId,\n conversationId,\n _id: messageId,\n // Ensure read receipt doesn't exist for this participant\n 'readBy.participantId': { $ne: participantId }\n },\n {\n $push: {\n readBy: {\n participantModel,\n participantId,\n readAt: at\n }\n }\n }\n );\n\n // Update participant last read in conversation\n await Conversation.findOneAndUpdate(\n {\n organizationId,\n _id: conversationId,\n 'participants.entityModel': participantModel,\n 'participants.entityId': participantId\n },\n {\n $set: {\n 'participants.$.lastReadMessageId': messageId,\n 'participants.$.lastReadAt': at\n }\n }\n );\n\n // Trigger hook\n if (onMessageRead) {\n onMessageRead({ ...args, at });\n }\n }\n };\n}\n","/**\n * In-memory subscription maps for conversations and sockets\n */\n\n// Maps conversation IDs to sets of socket IDs\nconst convSubs: Map<string, Set<string>> = new Map();\n\n// Maps socket IDs to sets of conversation IDs\nconst socketSubs: Map<string, Set<string>> = new Map();\n\n/**\n * Subscribe a socket to a conversation\n * @param conversationId The conversation ID to subscribe to\n * @param socketId The socket ID to subscribe\n */\nexport function subscribeLocal(conversationId: string, socketId: string): void {\n // Add socket to conversation subscribers\n if (!convSubs.has(conversationId)) {\n convSubs.set(conversationId, new Set());\n }\n convSubs.get(conversationId)?.add(socketId);\n \n // Add conversation to socket subscriptions\n if (!socketSubs.has(socketId)) {\n socketSubs.set(socketId, new Set());\n }\n socketSubs.get(socketId)?.add(conversationId);\n}\n\n/**\n * Unsubscribe a socket from a conversation\n * @param conversationId The conversation ID to unsubscribe from\n * @param socketId The socket ID to unsubscribe\n */\nexport function unsubscribeLocal(conversationId: string, socketId: string): void {\n // Remove socket from conversation subscribers\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n \n // Remove conversation from socket subscriptions\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n conversations.delete(conversationId);\n if (conversations.size === 0) {\n socketSubs.delete(socketId);\n }\n }\n}\n\n/**\n * Clean up all subscriptions for a socket\n * @param socketId The socket ID to clean up\n */\nexport function cleanupLocal(socketId: string): void {\n // Get all conversations this socket is subscribed to\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n // Unsubscribe from each conversation\n for (const conversationId of conversations) {\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n }\n \n // Remove socket from socket subscriptions\n socketSubs.delete(socketId);\n }\n}\n\n/**\n * Fan out a message to all local subscribers of a conversation\n * @param conversationId The conversation ID to fan out to\n * @param payload The payload to send\n * @param emitter Function to emit events to socket IDs\n */\nexport function fanoutLocal(\n conversationId: string,\n payload: any,\n emitter: (toSocketId: string, event: any) => void\n): void {\n // Get all sockets subscribed to this conversation\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n // Emit to each socket\n for (const socketId of sockets) {\n try {\n emitter(socketId, payload);\n } catch (error) {\n console.error(`Error emitting to socket ${socketId}:`, error);\n }\n }\n }\n}","import Redis from 'ioredis';\nimport { RedisConfig } from '../config/env';\n\n/**\n * Create a Redis client based on provided configuration\n * @param config Redis configuration\n * @returns Redis client instance or null if configuration is missing\n */\nexport function createRedisClient(config?: RedisConfig): Redis | null {\n if (!config) {\n console.warn('Redis configuration not found. Redis features will be disabled.');\n return null;\n }\n \n try {\n let client: Redis;\n \n // Use either URL or granular connection options\n if (config.url) {\n client = new Redis(config.url);\n } else if (config.host) {\n client = new Redis({\n host: config.host,\n port: config.port,\n password: config.password,\n db: config.db,\n connectTimeout: config.socketConnectTimeout, // Changed \n enableReadyCheck: true,\n enableOfflineQueue: true,\n retryStrategy(times) {\n const delay = Math.min(times * 50, 2000);\n return delay;\n }\n });\n } else {\n console.warn('Invalid Redis configuration. Redis features will be disabled.');\n return null;\n }\n \n // Log Redis connection events\n client.on('connect', () => {\n console.info('Redis client connected');\n });\n \n client.on('error', (err) => {\n console.error('Redis client error:', err);\n });\n \n client.on('reconnecting', () => {\n console.info('Redis client reconnecting');\n });\n \n return client;\n } catch (error) {\n console.error('Failed to create Redis client:', error);\n return null;\n }\n}\n\n/**\n * Publish a message to a conversation channel\n * @param client Redis client\n * @param conversationId Conversation ID\n * @param payload Message payload\n * @returns Promise resolving to number of clients that received the message\n */\nexport async function publishToConversation(\n client: Redis | null,\n conversationId: string,\n payload: any\n): Promise<number> {\n if (!client) return 0;\n \n try {\n const channel = `conv:${conversationId}`;\n const message = JSON.stringify(payload);\n return await client.publish(channel, message);\n } catch (error) {\n console.error(`Failed to publish to conversation ${conversationId}:`, error);\n return 0;\n }\n}\n\n/**\n * Start Redis subscription listener\n * @param client Redis client\n * @param onEvent Callback function for received events\n * @returns Function to stop the listener\n */\nexport function startRedisListener(\n client: Redis | null,\n onEvent: (conversationId: string, payload: any) => void\n): (() => void) | null {\n if (!client) return null;\n \n try {\n // Create a duplicate client for subscribing\n // (Redis clients in subscribe mode cannot be used for other commands)\n const subClient = client.duplicate();\n \n // Subscribe to conversation channels\n subClient.psubscribe('conv:*');\n \n // Handle incoming messages\n subClient.on('pmessage', (_pattern, channel, message) => {\n try {\n // Extract conversation ID from channel\n const conversationId = channel.slice(5); // Remove 'conv:' prefix\n \n // Parse message payload\n const payload = JSON.parse(message);\n \n // Call event handler\n onEvent(conversationId, payload);\n } catch (error) {\n console.error('Error processing Redis message:', error);\n }\n });\n \n // Return function to stop listening\n return () => {\n subClient.punsubscribe('conv:*');\n subClient.quit();\n };\n } catch (error) {\n console.error('Failed to start Redis listener:', error);\n return null;\n }\n}","import { Server, Socket } from 'socket.io';\nimport type { Server as HTTPServer } from 'http';\nimport { Transport } from './index';\n\nexport interface SocketConfig {\n path?: string;\n cors?: {\n origin?: string | string[];\n methods?: string[];\n };\n}\n\nexport interface SocketUser {\n id: string;\n model: string;\n}\n\nexport class SocketTransport {\n private io: Server;\n private transport: Transport;\n\n constructor(server: HTTPServer, transport: Transport, config?: SocketConfig) {\n this.io = new Server(server, {\n path: config?.path || '/socket.io',\n addTrailingSlash: false,\n cors: config?.cors || {\n origin: '*',\n methods: ['GET', 'POST']\n }\n });\n this.transport = transport;\n\n // Listen for Redis events and broadcast to sockets\n this.transport.startRedisListener((conversationId: string, payload: any) => {\n this.io.to(`conv:${conversationId}`).emit('chat-event', payload);\n });\n }\n\n /**\n * Handle socket authentication\n */\n public handleAuth(authenticate: (socket: Socket) => Promise<SocketUser | null>) {\n this.io.use(async (socket, next) => {\n try {\n const user = await authenticate(socket);\n if (!user) {\n next(new Error('Unauthorized'));\n return;\n }\n \n // Store user info in socket data\n socket.data.user = user;\n next();\n } catch (error) {\n next(error as Error);\n }\n });\n }\n\n /**\n * Initialize socket event handlers\n */\n public initialize() {\n this.io.on('connection', (socket: Socket) => {\n const user = socket.data.user as SocketUser;\n\n // Join a conversation\n socket.on('join-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.join(roomName);\n \n // Register in local subscription system\n this.transport.subscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:join',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Leave a conversation\n socket.on('leave-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.leave(roomName);\n \n // Remove from local subscription system\n this.transport.unsubscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:leave',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Handle typing events\n socket.on('typing', ({ conversationId, isTyping }: { conversationId: string; isTyping: boolean }) => {\n const payload = {\n type: 'typing',\n conversationId,\n participantModel: user.model,\n participantId: user.id,\n state: isTyping ? 'start' : 'stop'\n };\n \n // Emit to conversation members\n socket.to(`conv:${conversationId}`).emit('chat-event', payload);\n \n // Publish to Redis for cross-instance communication\n this.transport.publishToConversation(conversationId, payload);\n });\n\n // Handle disconnect\n socket.on('disconnect', () => {\n // Clean up subscriptions\n this.transport.cleanupLocal(socket.id);\n });\n });\n }\n\n /**\n * Get Socket.IO server instance\n */\n public getIO(): Server {\n return this.io;\n }\n\n /**\n * Close Socket.IO server\n */\n public close(): Promise<void> {\n return new Promise((resolve) => {\n this.io.close(() => resolve());\n });\n }\n}\n","import * as localSubs from './localSubs';\nimport { createRedisClient, publishToConversation, startRedisListener } from './redis';\nimport { SocketTransport, SocketConfig } from './socket';\nimport { InitOptions } from '../types';\nimport type { Server as HTTPServer } from 'http';\n\n/**\n * Transport configuration type\n */\nexport interface TransportConfig {\n redis?: InitOptions['redis'];\n socket?: SocketConfig;\n}\n\n/**\n * Transport instance type\n */\nexport interface Transport {\n subscribeLocal: typeof localSubs.subscribeLocal;\n unsubscribeLocal: typeof localSubs.unsubscribeLocal;\n cleanupLocal: typeof localSubs.cleanupLocal;\n fanoutLocal: typeof localSubs.fanoutLocal;\n publishToConversation: (conversationId: string, payload: any) => Promise<number>;\n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => (() => void) | null;\n}\n\n/**\n * Create transport system with optional Redis and Socket.IO support\n */\nexport function createTransport(\n config?: TransportConfig,\n server?: HTTPServer\n): Transport & { socket?: SocketTransport } {\n // Initialize Redis client if configuration is provided\n const redisClient = config?.redis ? createRedisClient(config.redis) : null;\n \n // Create base transport\n const transport: Transport = {\n // Local subscription methods\n subscribeLocal: localSubs.subscribeLocal,\n unsubscribeLocal: localSubs.unsubscribeLocal,\n cleanupLocal: localSubs.cleanupLocal,\n fanoutLocal: localSubs.fanoutLocal,\n \n // Redis methods\n publishToConversation: (conversationId: string, payload: any) => \n publishToConversation(redisClient, conversationId, payload),\n \n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => \n startRedisListener(redisClient, onEvent),\n };\n\n // Initialize Socket.IO if server is provided\n if (server) {\n const socketTransport = new SocketTransport(server, transport, config?.socket);\n return {\n ...transport,\n socket: socketTransport\n };\n }\n\n return transport;\n}","import mongoose, { Model } from 'mongoose';\nimport { IConversation } from './Conversation.model';\nimport { IMessage } from './Message.model';\n\n/**\n * Ensure all indexes are created on models\n * @param models Object containing Mongoose models\n */\nexport async function ensureIndexes(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): Promise<void> {\n // Create indexes\n await Promise.all([\n models.Message.ensureIndexes(),\n models.Conversation.ensureIndexes(),\n ]);\n}","/**\n * Redis configuration type\n */\nexport type RedisConfig = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n socketConnectTimeout?: number;\n keepAlive?: number;\n};\n\n/**\n * Environment configuration\n */\nexport interface EnvironmentConfig {\n /** MongoDB connection URI */\n mongoUri: string;\n /** Redis configuration (optional) */\n redis?: RedisConfig;\n}\n/**\n * Get Redis configuration from provided options\n */\nexport function getRedisConfig(config: { redis?: RedisConfig }): RedisConfig | null {\n if (!config.redis) {\n return null;\n }\n\n // Return the full configuration\n return {\n host: config.redis.host || undefined,\n port: config.redis.port || undefined,\n password: config.redis.password || undefined,\n db: config.redis.db || undefined,\n socketConnectTimeout: config.redis.socketConnectTimeout || undefined,\n keepAlive: config.redis.keepAlive || undefined\n };\n}\n","/**\n * Generates a random string ID with the specified length\n * @param length The length of the ID to generate (default: 24)\n * @returns A random string ID\n */\nexport function generateId(length = 24): string {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n const charactersLength = characters.length;\n \n for (let i = 0; i < length; i++) {\n result += characters.charAt(Math.floor(Math.random() * charactersLength));\n }\n \n return result;\n}\n\n/**\n * Creates a conversation ID from two participant identifiers\n * @param a First participant (model:id)\n * @param b Second participant (model:id)\n * @returns A deterministic conversation ID\n */\nexport function createConversationId(\n a: { model: string; id: string },\n b: { model: string; id: string }\n): string {\n // Sort participants to ensure the same ID regardless of order\n const participants = [\n `${a.model}:${a.id}`,\n `${b.model}:${b.id}`,\n ].sort();\n \n return `conv_${participants.join('_')}`;\n}\n\n/**\n * Creates a thread ID\n * @returns A unique thread ID\n */\nexport function createThreadId(): string {\n return `thread_${generateId(16)}`;\n}\n\n/**\n * Creates a message ID\n * @returns A unique message ID\n */\nexport function createMessageId(): string {\n return `msg_${generateId(16)}`;\n}\n\n\n\n\n","import { InitOptions, SendMessageArgs, CreateConversationArgs, MarkReadArgs } from '../types';\n\n/**\n * Validates initialization options\n * @param options Options to validate\n * @returns Validation error message or null if valid\n */\nexport function validateInitOptions(options: InitOptions): string | null {\n if (!options.participantModels || !Array.isArray(options.participantModels) || options.participantModels.length === 0) {\n return 'participantModels must be a non-empty array of strings';\n }\n\n if (!options.memberRoles || !Array.isArray(options.memberRoles) || options.memberRoles.length === 0) {\n return 'memberRoles must be a non-empty array of strings';\n }\n\n // Validate Redis options if provided\n if (options.redis) {\n if (options.redis.url) {\n if (typeof options.redis.url !== 'string') {\n return 'redis.url must be a string';\n }\n } else if (!options.redis.host) {\n return 'Either redis.url or redis.host must be provided';\n }\n }\n\n return null;\n}\n\n/**\n * Validates message sending arguments\n * @param args Message sending arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateSendMessageArgs(args: SendMessageArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.senderModel) {\n return 'senderModel is required';\n }\n\n if (!args.senderId) {\n return 'senderId is required';\n }\n\n if (!args.text || args.text.trim() === '') {\n return 'text is required and cannot be empty';\n }\n\n // If kind is provided, validate it's a valid value\n if (args.kind && !['text', 'system'].includes(args.kind)) {\n return 'kind must be either \"text\" or \"system\"';\n }\n\n // If this is a reply, ensure all thread fields are present\n if (args.parentMessageId && !args.rootThreadId) {\n return 'rootThreadId is required when parentMessageId is provided';\n }\n\n return null;\n}\n\n/**\n * Validates conversation creation arguments\n * @param args Conversation creation arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateCreateConversationArgs(args: CreateConversationArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.a || !args.a.model || !args.a.id) {\n return 'a.model and a.id are required';\n }\n\n if (!args.b || !args.b.model || !args.b.id) {\n return 'b.model and b.id are required';\n }\n\n // Prevent creating a conversation between the same participant\n if (args.a.model === args.b.model && args.a.id === args.b.id) {\n return 'Cannot create a conversation between the same participant';\n }\n\n return null;\n}\n\n/**\n * Validates mark read arguments\n * @param args Mark read arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateMarkReadArgs(args: MarkReadArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.participantModel) {\n return 'participantModel is required';\n }\n\n if (!args.participantId) {\n return 'participantId is required';\n }\n\n if (!args.messageId) {\n return 'messageId is required';\n }\n\n return null;\n}\n\n/**\n * Validates pagination parameters\n * @param limit Pagination limit\n * @param cursor Pagination cursor\n * @returns Validated limit and cursor\n */\nexport function validatePagination(\n limit?: number,\n cursor?: string\n): { limit: number; cursor: string | undefined } {\n // Default limit to 20, max to 100\n let validLimit = limit ? Math.min(Math.max(1, limit), 100) : 20;\n \n // If cursor is provided but empty string, set to undefined\n let validCursor = cursor && cursor.trim() !== '' ? cursor : undefined;\n \n return { limit: validLimit, cursor: validCursor };\n}\n\n\n\n\n","import mongoose from 'mongoose';\nimport type { Server as HTTPServer } from 'http';\nimport { createModels } from './models/connection';\nimport { createConversationsService, ConversationsService } from './services/conversations.service';\nimport { createMessagesService, MessagesService } from './services/messages.service';\nimport { createTransport, Transport } from './transport';\nimport { InitOptions } from './types';\nimport { ensureIndexes } from './models/indexes';\nimport { getRedisConfig } from './config/env';\nimport { SocketTransport } from './transport/socket';\n\n/**\n * Chat services container\n */\nexport interface ChatServices {\n models: {\n Conversation: mongoose.Model<any>;\n Message: mongoose.Model<any>;\n };\n services: {\n conversations: ConversationsService;\n messages: MessagesService;\n };\n transport: Transport & { socket?: SocketTransport };\n}\n\n/**\n * Initialize the chat system\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @param server Optional HTTP server for Socket.IO integration\n * @returns Chat services\n */\nexport async function initChat(\n mongooseInstance: typeof mongoose,\n options: InitOptions,\n server?: HTTPServer\n): Promise<ChatServices> {\n // Create models\n const models = createModels(mongooseInstance, options);\n\n // Ensure indexes are created\n await ensureIndexes(models);\n\n // Create transport system with optional Socket.IO support\n const transport = createTransport({\n redis: getRedisConfig(options) || undefined,\n socket: options.socket || undefined\n }, server);\n\n // Initialize Socket.IO if server was provided\n if (server && transport.socket) {\n // Set up authentication handler\n transport.socket.handleAuth(async (socket) => {\n // Get user info from socket handshake auth\n const userId = socket.handshake.auth.userId;\n const userModel = socket.handshake.auth.userModel || 'User';\n\n if (!userId || !options.participantModels.includes(userModel)) {\n return null;\n }\n\n return { id: userId, model: userModel };\n });\n\n // Initialize socket event handlers\n transport.socket.initialize();\n }\n\n // Create services with hooks\n const services = {\n conversations: createConversationsService(models),\n messages: createMessagesService(models, {\n onMessageCreated: (message) => {\n const payload = { type: 'message:created', message };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n message.conversationId.toString(),\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n message.conversationId.toString(),\n payload\n );\n },\n \n onMessageRead: (args) => {\n const payload = { \n type: 'conversation:read', \n conversationId: args.conversationId,\n messageId: args.messageId,\n participantModel: args.participantModel,\n participantId: args.participantId,\n at: args.at.toISOString()\n };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n args.conversationId,\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n args.conversationId,\n payload\n );\n }\n })\n };\n\n return { models, services, transport };\n}\n\n// Re-export types\nexport * from './types';\nexport * from './utils';\nexport * from './models';\nexport * from './services';\nexport * from './transport';\nexport * from './config/env';"]}
|
package/dist/index.mjs
CHANGED
|
@@ -3,23 +3,26 @@ import Redis from 'ioredis';
|
|
|
3
3
|
import { Server } from 'socket.io';
|
|
4
4
|
|
|
5
5
|
// src/models/Conversation.model.ts
|
|
6
|
-
function createConversationModel(options) {
|
|
7
|
-
const ParticipantSchema = new Schema(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
function createConversationModel(options, conn = mongoose.connection) {
|
|
7
|
+
const ParticipantSchema = new Schema(
|
|
8
|
+
{
|
|
9
|
+
entityModel: {
|
|
10
|
+
type: String,
|
|
11
|
+
enum: options.participantModels,
|
|
12
|
+
required: true
|
|
13
|
+
},
|
|
14
|
+
entityId: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true
|
|
17
|
+
},
|
|
18
|
+
role: {
|
|
19
|
+
type: String,
|
|
20
|
+
enum: options.memberRoles,
|
|
21
|
+
required: true
|
|
22
|
+
}
|
|
16
23
|
},
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
enum: options.memberRoles,
|
|
20
|
-
required: true
|
|
21
|
-
}
|
|
22
|
-
}, { _id: false });
|
|
24
|
+
{ _id: false }
|
|
25
|
+
);
|
|
23
26
|
const ConversationSchema = new Schema(
|
|
24
27
|
{
|
|
25
28
|
organizationId: {
|
|
@@ -44,9 +47,7 @@ function createConversationModel(options) {
|
|
|
44
47
|
lastMessageSenderModel: String,
|
|
45
48
|
metadata: Schema.Types.Mixed
|
|
46
49
|
},
|
|
47
|
-
{
|
|
48
|
-
timestamps: true
|
|
49
|
-
}
|
|
50
|
+
{ timestamps: true }
|
|
50
51
|
);
|
|
51
52
|
ConversationSchema.index({
|
|
52
53
|
organizationId: 1,
|
|
@@ -56,11 +57,8 @@ function createConversationModel(options) {
|
|
|
56
57
|
organizationId: 1,
|
|
57
58
|
lastMessageAt: -1
|
|
58
59
|
});
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
return mongoose.models[modelName];
|
|
62
|
-
}
|
|
63
|
-
return mongoose.model(modelName, ConversationSchema);
|
|
60
|
+
const name = "Conversation";
|
|
61
|
+
return conn.models[name] || conn.model(name, ConversationSchema);
|
|
64
62
|
}
|
|
65
63
|
function createMessageModel(options) {
|
|
66
64
|
const MessageSchema = new Schema(
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/models/Conversation.model.ts","../src/models/Message.model.ts","../src/models/connection.ts","../src/utils/pagination.ts","../src/services/search/search.conversations.ts","../src/services/conversations.service.ts","../src/services/search/search.messages.ts","../src/services/messages.service.ts","../src/transport/localSubs.ts","../src/transport/redis.ts","../src/transport/socket.ts","../src/transport/index.ts","../src/models/indexes.ts","../src/config/env.ts","../src/utils/ids.ts","../src/utils/validators.ts","../src/index.ts"],"names":["mongoose","Schema"],"mappings":"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","sourcesContent":["import mongoose, { Schema, Document, Model } from 'mongoose';\nimport { InitOptions } from '../types';\n\nexport interface IParticipant {\n entityModel: string;\n entityId: string;\n role: string;\n}\n\nexport interface IConversation extends Document {\n organizationId: string;\n participants: IParticipant[];\n lastMessageAt: Date;\n lastMessagePreview?: string;\n lastMessageSenderId?: string;\n lastMessageSenderModel?: string;\n metadata?: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create Conversation model\n */\nexport function createConversationModel(options: InitOptions): Model<IConversation> {\n // Participant schema\n const ParticipantSchema = new Schema({\n entityModel: { \n type: String, \n enum: options.participantModels,\n required: true \n },\n entityId: { \n type: String,\n required: true \n },\n role: { \n type: String, \n enum: options.memberRoles,\n required: true \n },\n }, { _id: false });\n\n // Conversation schema\n const ConversationSchema = new Schema<IConversation>(\n {\n organizationId: { \n type: String,\n required: true,\n index: true\n },\n participants: {\n type: [ParticipantSchema],\n required: true,\n validate: {\n validator: (participants: IParticipant[]) => participants.length === 2,\n message: 'A conversation must have exactly 2 participants',\n },\n },\n lastMessageAt: { \n type: Date,\n index: true\n },\n lastMessagePreview: String,\n lastMessageSenderId: String,\n lastMessageSenderModel: String,\n metadata: Schema.Types.Mixed\n },\n { \n timestamps: true \n }\n );\n\n // Create indexes\n ConversationSchema.index({ \n organizationId: 1, \n 'participants.entityId': 1 \n });\n\n ConversationSchema.index({ \n organizationId: 1, \n lastMessageAt: -1 \n });\n\n // Check if model exists\n const modelName = 'Conversation';\n if (mongoose.models[modelName]) {\n return mongoose.models[modelName] as Model<IConversation>;\n }\n\n // Create and return new model\n return mongoose.model<IConversation>(modelName, ConversationSchema);\n}","import mongoose, { Schema, Document, Model } from 'mongoose';\nimport { InitOptions } from '../types';\n\n/**\n * Message document interface\n */\nexport interface IMessage extends Document {\n organizationId: mongoose.Types.ObjectId;\n conversationId: mongoose.Types.ObjectId;\n senderModel: string;\n senderId: mongoose.Types.ObjectId;\n kind: 'text' | 'system';\n text: string;\n parentMessageId?: mongoose.Types.ObjectId;\n rootThreadId?: mongoose.Types.ObjectId;\n editedAt?: Date;\n deletedAt?: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create Message model\n */\nexport function createMessageModel(options: InitOptions): Model<IMessage> {\n // Message schema\n const MessageSchema = new Schema<IMessage>(\n {\n organizationId: {\n type: Schema.Types.ObjectId,\n ref: 'Organization',\n required: true,\n index: true\n },\n conversationId: {\n type: Schema.Types.ObjectId,\n ref: 'Conversation',\n required: true,\n index: true\n },\n senderModel: {\n type: String,\n enum: options.participantModels,\n required: true\n },\n senderId: {\n type: Schema.Types.ObjectId,\n refPath: 'senderModel',\n required: true,\n index: true\n },\n kind: {\n type: String,\n enum: ['text', 'system'],\n default: 'text',\n index: true\n },\n text: {\n type: String,\n trim: true\n },\n parentMessageId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n rootThreadId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n editedAt: {\n type: Date\n },\n deletedAt: {\n type: Date\n }\n },\n { timestamps: true }\n );\n\n // Create compound index\n MessageSchema.index({\n organizationId: 1,\n conversationId: 1,\n createdAt: -1\n });\n\n // Create text search index if enabled\n if (options.enableTextSearch) {\n MessageSchema.index(\n { text: 'text' },\n {\n name: 'text_search',\n weights: { text: 10 }\n }\n );\n }\n\n // Return model\n return mongoose.model<IMessage>('Message', MessageSchema);\n}","import mongoose from 'mongoose';\nimport { InitOptions } from '../types';\nimport { createConversationModel } from './Conversation.model';\nimport { createMessageModel } from './Message.model';\n\n/**\n * Create Mongoose models with applied configuration\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @returns Object containing Conversation and Message models\n */\nexport function createModels(mongooseInstance: typeof mongoose, options: InitOptions) {\n // Create models with provided options\n const Conversation = createConversationModel(options);\n const Message = createMessageModel(options);\n \n return {\n Conversation,\n Message\n };\n}\n\n/**\n * Connection state enum\n */\nenum ConnectionState {\n Disconnected = 0,\n Connected = 1,\n Connecting = 2,\n Disconnecting = 3,\n}","/**\n * Pagination result interface\n */\nexport interface PaginatedResult<T> {\n items: T[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n\n/**\n * Creates a pagination cursor from a timestamp and ID\n * @param timestamp Timestamp to encode in the cursor\n * @param id ID to encode in the cursor\n * @returns An encoded cursor string\n */\nexport function createCursor(timestamp: number, id: string): string {\n const cursorData = `${timestamp}:${id}`;\n return Buffer.from(cursorData).toString('base64');\n}\n\n/**\n * Parses a pagination cursor into its timestamp and ID components\n * @param cursor The cursor string to parse\n * @returns An object containing the timestamp and ID, or null if invalid\n */\nexport function parseCursor(cursor: string): { timestamp: number; id: string } | null {\n try {\n const decoded = Buffer.from(cursor, 'base64').toString('utf-8');\n const [timestampStr, id] = decoded.split(':');\n \n const timestamp = parseInt(timestampStr, 10);\n \n if (isNaN(timestamp) || !id) {\n return null;\n }\n \n return { timestamp, id };\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Creates a paginated response\n * @param items The items for the current page\n * @param limit The requested page size\n * @param getNextCursor Function to get the next cursor from the last item\n * @returns A paginated result object\n */\nexport function createPaginatedResponse<T>(\n items: T[],\n limit: number,\n getNextCursor: (lastItem: T) => string\n): PaginatedResult<T> {\n const hasMore = items.length > limit;\n \n // If we have more items than the limit, remove the extra item\n // that we used to determine if there are more pages\n const paginatedItems = hasMore ? items.slice(0, limit) : items;\n \n // Get the next cursor from the last item if we have more items\n const nextCursor = hasMore && paginatedItems.length > 0\n ? getNextCursor(paginatedItems[paginatedItems.length - 1])\n : null;\n \n return {\n items: paginatedItems,\n nextCursor,\n hasMore,\n };\n}\n\n\n\n\n","import { Model } from 'mongoose';\nimport { IConversation } from '../../models/Conversation.model';\nimport { SearchConversationsArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search conversations with pagination\n */\nexport async function searchConversations(\n model: Model<IConversation>,\n params: SearchConversationsArgs\n): Promise<PaginatedResult<IConversation>> {\n const { \n organizationId, \n participantModel, \n participantId,\n limit = 20, \n cursor \n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId \n };\n \n // Add participant filter if provided\n if (participantModel && participantId) {\n baseQuery['participants'] = {\n $elemMatch: {\n entityModel: participantModel,\n entityId: participantId\n }\n };\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n const cursorDate = new Date(timestamp);\n \n baseQuery.$or = [\n { lastMessageAt: { $lt: cursorDate } },\n { lastMessageAt: cursorDate, _id: { $lt: id } }\n ];\n }\n }\n \n // Find conversations and sort by lastMessageAt\n const conversations = await model.find(baseQuery)\n .sort({ lastMessageAt: -1 })\n .limit(limit + 1)\n .exec();\n \n // Check if we have more results\n const hasMore = conversations.length > limit;\n const items = hasMore ? conversations.slice(0, limit) : conversations;\n \n // Create next cursor if we have more results\n let nextCursor: string | null = null;\n \n if (hasMore && items.length > 0) {\n const lastItem = items[items.length - 1];\n // Use lastMessageAt if available, otherwise use createdAt\n const timestamp = lastItem.lastMessageAt ? \n lastItem.lastMessageAt.getTime() : \n lastItem.createdAt.getTime();\n \n nextCursor = createCursor(timestamp, lastItem._id.toString());\n }\n \n return {\n items,\n nextCursor,\n hasMore\n };\n}\n\n/**\n * Find a conversation between two specific participants\n */\nexport async function searchByParticipantPair(\n model: Model<IConversation>,\n params: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }\n): Promise<IConversation | null> {\n const { organizationId, a, b } = params;\n \n const conversation = await model.findOne({\n organizationId,\n $and: [\n {\n participants: {\n $elemMatch: {\n entityModel: a.model,\n entityId: a.id\n }\n }\n },\n {\n participants: {\n $elemMatch: {\n entityModel: b.model,\n entityId: b.id\n }\n }\n },\n {\n $expr: {\n $eq: [{ $size: \"$participants\" }, 2]\n }\n }\n ]\n });\n \n return conversation;\n}","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { CreateConversationArgs, SearchConversationsArgs } from '../types';\nimport { searchConversations, searchByParticipantPair } from './search/search.conversations';\nimport { PaginatedResult } from '../utils/pagination';\n\n/**\n * Service for managing conversations\n */\nexport interface ConversationsService {\n createOrFindConversation(args: CreateConversationArgs): Promise<IConversation>;\n getConversation(organizationId: string, conversationId: string): Promise<IConversation | null>;\n searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>>;\n searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null>;\n}\n\n/**\n * Create conversations service\n * @param models MongoDB models\n * @returns Conversations service instance\n */\nexport function createConversationsService(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): ConversationsService {\n const { Conversation } = models;\n\n return {\n /**\n * Create a new conversation or find existing one between participants\n */\n async createOrFindConversation(args: CreateConversationArgs): Promise<IConversation> {\n const { organizationId, a, b } = args;\n\n try {\n // Check if conversation already exists between these participants\n const existingConversation = await searchByParticipantPair(Conversation, {\n organizationId,\n a,\n b\n });\n\n if (existingConversation) {\n return existingConversation;\n }\n\n // Create new conversation\n const newConversation = new Conversation({\n organizationId,\n participants: [\n {\n entityModel: a.model,\n entityId: a.id,\n role: 'member' // Default role\n },\n {\n entityModel: b.model,\n entityId: b.id,\n role: 'member' // Default role\n }\n ],\n lastMessageAt: new Date() // Set initial lastMessageAt\n });\n\n await newConversation.save();\n return newConversation;\n } catch (error) {\n console.error('Error in createOrFindConversation:', error);\n throw error;\n }\n },\n\n /**\n * Get a conversation by ID\n */\n async getConversation(organizationId: string, conversationId: string): Promise<IConversation | null> {\n try {\n return await Conversation.findOne({\n organizationId,\n _id: conversationId\n });\n } catch (error) {\n console.error('Error in getConversation:', error);\n throw error;\n }\n },\n\n /**\n * Search conversations with pagination\n */\n async searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>> {\n try {\n return await searchConversations(Conversation, args);\n } catch (error) {\n console.error('Error in searchConversations:', error);\n throw error;\n }\n },\n\n /**\n * Find a conversation between two specific participants\n */\n async searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null> {\n try {\n return await searchByParticipantPair(Conversation, args);\n } catch (error) {\n console.error('Error in searchByParticipantPair:', error);\n throw error;\n }\n }\n };\n}","import { Model } from 'mongoose';\nimport { IMessage } from '../../models/Message.model';\nimport { SearchMessagesArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search messages based on various criteria\n * @param model Message model\n * @param params Search parameters\n * @returns Paginated messages with optional text search results\n */\nexport async function searchMessages(\n model: Model<IMessage>,\n params: SearchMessagesArgs\n): Promise<PaginatedResult<IMessage>> {\n const { \n organizationId,\n query,\n conversationId,\n senderModel,\n senderId,\n kind,\n dateFrom,\n dateTo,\n threadRootId,\n limit = 20,\n cursor\n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId\n };\n\n // Add optional filters if provided\n if (conversationId) {\n baseQuery.conversationId = conversationId;\n }\n \n if (senderModel && senderId) {\n baseQuery.senderModel = senderModel;\n baseQuery.senderId = senderId;\n } else if (senderModel) {\n baseQuery.senderModel = senderModel;\n }\n \n if (kind) {\n baseQuery.kind = kind;\n }\n \n // Date range filters\n if (dateFrom || dateTo) {\n baseQuery.createdAt = {};\n \n if (dateFrom) {\n baseQuery.createdAt.$gte = new Date(dateFrom);\n }\n \n if (dateTo) {\n baseQuery.createdAt.$lte = new Date(dateTo);\n }\n }\n \n // Thread filter\n if (threadRootId) {\n baseQuery.rootThreadId = threadRootId;\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n \n baseQuery.$or = [\n { createdAt: { $lt: new Date(timestamp) } },\n { createdAt: new Date(timestamp), _id: { $lt: id } }\n ];\n }\n }\n \n // Determine sort order and query approach\n let sort: Record<string, number> = { createdAt: -1 };\n let pipeline: any[] = [];\n \n // Use text search if query provided\n if (query && query.trim()) {\n // Set up text search with score and appropriate sort\n pipeline = [\n { $match: { $text: { $search: query }, ...baseQuery } },\n { $addFields: { score: { $meta: 'textScore' } } },\n { $sort: { score: { $meta: 'textScore' }, createdAt: -1 } },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n } else {\n // Use standard query\n pipeline = [\n { $match: baseQuery },\n { $sort: sort },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n }\n \n // Execute query\n const items = await model.aggregate(pipeline);\n \n // Check if we have more results\n const hasMore = items.length > limit;\n const limitedItems = hasMore ? items.slice(0, limit) : items;\n \n // Create next cursor if we have more results\n const nextCursor = hasMore && limitedItems.length > 0\n ? createCursor(\n new Date(limitedItems[limitedItems.length - 1].createdAt).getTime(),\n limitedItems[limitedItems.length - 1]._id.toString()\n )\n : null;\n \n return {\n items: limitedItems,\n nextCursor,\n hasMore\n };\n}\n","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { SendMessageArgs, ListMessagesArgs, MarkReadArgs } from '../types';\nimport { searchMessages } from './search/search.messages';\nimport { createPaginatedResponse, PaginatedResult } from '../utils/pagination';\n\n/**\n * Message service hooks interface\n */\nexport interface MessageServiceHooks {\n onMessageCreated?: (message: IMessage) => void;\n onMessageRead?: (args: MarkReadArgs & { at: Date }) => void;\n}\n\n/**\n * Service for managing messages\n */\nexport interface MessagesService {\n sendMessage(args: SendMessageArgs): Promise<IMessage>;\n listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>>;\n markRead(args: MarkReadArgs): Promise<void>;\n}\n\n/**\n * Create messages service\n * @param models MongoDB models\n * @param hooks Service hooks\n * @returns Messages service instance\n */\nexport function createMessagesService(\n models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n },\n hooks: MessageServiceHooks = {}\n): MessagesService {\n const { Message, Conversation } = models;\n const { onMessageCreated, onMessageRead } = hooks;\n\n return {\n /**\n * Send a new message\n * @param args Message sending arguments\n * @returns The created message\n */\n async sendMessage(args: SendMessageArgs): Promise<IMessage> {\n const { \n organizationId, \n conversationId, \n senderModel, \n senderId, \n text, \n kind = 'text',\n parentMessageId,\n rootThreadId\n } = args;\n\n // Create the message\n const message = new Message({\n organizationId,\n conversationId,\n senderModel,\n senderId,\n text,\n kind,\n parentMessageId,\n rootThreadId: rootThreadId || parentMessageId // If rootThreadId not provided but parentMessageId is, use parentMessageId\n });\n\n await message.save();\n\n // Update the conversation with last message info\n await Conversation.findByIdAndUpdate(\n conversationId,\n {\n $set: {\n lastMessageAt: message.createdAt,\n lastMessagePreview: text.substring(0, 100),\n lastMessageSenderId: senderId,\n lastMessageSenderModel: senderModel\n }\n }\n );\n\n // Trigger hook\n if (onMessageCreated) {\n onMessageCreated(message);\n }\n\n return message;\n },\n\n /**\n * List messages for a conversation with pagination\n * @param args Message listing arguments\n * @returns Paginated list of messages\n */\n async listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>> {\n const { organizationId, conversationId, limit = 20, cursor } = args;\n\n return searchMessages(Message, {\n organizationId,\n conversationId,\n limit,\n cursor\n });\n },\n\n /**\n * Mark a message as read by a participant\n * @param args Read marking arguments\n */\n async markRead(args: MarkReadArgs): Promise<void> {\n const { \n organizationId, \n conversationId, \n participantModel, \n participantId, \n messageId \n } = args;\n\n const at = new Date();\n\n // Update the message read receipts\n await Message.findOneAndUpdate(\n {\n organizationId,\n conversationId,\n _id: messageId,\n // Ensure read receipt doesn't exist for this participant\n 'readBy.participantId': { $ne: participantId }\n },\n {\n $push: {\n readBy: {\n participantModel,\n participantId,\n readAt: at\n }\n }\n }\n );\n\n // Update participant last read in conversation\n await Conversation.findOneAndUpdate(\n {\n organizationId,\n _id: conversationId,\n 'participants.entityModel': participantModel,\n 'participants.entityId': participantId\n },\n {\n $set: {\n 'participants.$.lastReadMessageId': messageId,\n 'participants.$.lastReadAt': at\n }\n }\n );\n\n // Trigger hook\n if (onMessageRead) {\n onMessageRead({ ...args, at });\n }\n }\n };\n}\n","/**\n * In-memory subscription maps for conversations and sockets\n */\n\n// Maps conversation IDs to sets of socket IDs\nconst convSubs: Map<string, Set<string>> = new Map();\n\n// Maps socket IDs to sets of conversation IDs\nconst socketSubs: Map<string, Set<string>> = new Map();\n\n/**\n * Subscribe a socket to a conversation\n * @param conversationId The conversation ID to subscribe to\n * @param socketId The socket ID to subscribe\n */\nexport function subscribeLocal(conversationId: string, socketId: string): void {\n // Add socket to conversation subscribers\n if (!convSubs.has(conversationId)) {\n convSubs.set(conversationId, new Set());\n }\n convSubs.get(conversationId)?.add(socketId);\n \n // Add conversation to socket subscriptions\n if (!socketSubs.has(socketId)) {\n socketSubs.set(socketId, new Set());\n }\n socketSubs.get(socketId)?.add(conversationId);\n}\n\n/**\n * Unsubscribe a socket from a conversation\n * @param conversationId The conversation ID to unsubscribe from\n * @param socketId The socket ID to unsubscribe\n */\nexport function unsubscribeLocal(conversationId: string, socketId: string): void {\n // Remove socket from conversation subscribers\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n \n // Remove conversation from socket subscriptions\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n conversations.delete(conversationId);\n if (conversations.size === 0) {\n socketSubs.delete(socketId);\n }\n }\n}\n\n/**\n * Clean up all subscriptions for a socket\n * @param socketId The socket ID to clean up\n */\nexport function cleanupLocal(socketId: string): void {\n // Get all conversations this socket is subscribed to\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n // Unsubscribe from each conversation\n for (const conversationId of conversations) {\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n }\n \n // Remove socket from socket subscriptions\n socketSubs.delete(socketId);\n }\n}\n\n/**\n * Fan out a message to all local subscribers of a conversation\n * @param conversationId The conversation ID to fan out to\n * @param payload The payload to send\n * @param emitter Function to emit events to socket IDs\n */\nexport function fanoutLocal(\n conversationId: string,\n payload: any,\n emitter: (toSocketId: string, event: any) => void\n): void {\n // Get all sockets subscribed to this conversation\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n // Emit to each socket\n for (const socketId of sockets) {\n try {\n emitter(socketId, payload);\n } catch (error) {\n console.error(`Error emitting to socket ${socketId}:`, error);\n }\n }\n }\n}","import Redis from 'ioredis';\nimport { RedisConfig } from '../config/env';\n\n/**\n * Create a Redis client based on provided configuration\n * @param config Redis configuration\n * @returns Redis client instance or null if configuration is missing\n */\nexport function createRedisClient(config?: RedisConfig): Redis | null {\n if (!config) {\n console.warn('Redis configuration not found. Redis features will be disabled.');\n return null;\n }\n \n try {\n let client: Redis;\n \n // Use either URL or granular connection options\n if (config.url) {\n client = new Redis(config.url);\n } else if (config.host) {\n client = new Redis({\n host: config.host,\n port: config.port,\n password: config.password,\n db: config.db,\n connectTimeout: config.socketConnectTimeout, // Changed \n enableReadyCheck: true,\n enableOfflineQueue: true,\n retryStrategy(times) {\n const delay = Math.min(times * 50, 2000);\n return delay;\n }\n });\n } else {\n console.warn('Invalid Redis configuration. Redis features will be disabled.');\n return null;\n }\n \n // Log Redis connection events\n client.on('connect', () => {\n console.info('Redis client connected');\n });\n \n client.on('error', (err) => {\n console.error('Redis client error:', err);\n });\n \n client.on('reconnecting', () => {\n console.info('Redis client reconnecting');\n });\n \n return client;\n } catch (error) {\n console.error('Failed to create Redis client:', error);\n return null;\n }\n}\n\n/**\n * Publish a message to a conversation channel\n * @param client Redis client\n * @param conversationId Conversation ID\n * @param payload Message payload\n * @returns Promise resolving to number of clients that received the message\n */\nexport async function publishToConversation(\n client: Redis | null,\n conversationId: string,\n payload: any\n): Promise<number> {\n if (!client) return 0;\n \n try {\n const channel = `conv:${conversationId}`;\n const message = JSON.stringify(payload);\n return await client.publish(channel, message);\n } catch (error) {\n console.error(`Failed to publish to conversation ${conversationId}:`, error);\n return 0;\n }\n}\n\n/**\n * Start Redis subscription listener\n * @param client Redis client\n * @param onEvent Callback function for received events\n * @returns Function to stop the listener\n */\nexport function startRedisListener(\n client: Redis | null,\n onEvent: (conversationId: string, payload: any) => void\n): (() => void) | null {\n if (!client) return null;\n \n try {\n // Create a duplicate client for subscribing\n // (Redis clients in subscribe mode cannot be used for other commands)\n const subClient = client.duplicate();\n \n // Subscribe to conversation channels\n subClient.psubscribe('conv:*');\n \n // Handle incoming messages\n subClient.on('pmessage', (_pattern, channel, message) => {\n try {\n // Extract conversation ID from channel\n const conversationId = channel.slice(5); // Remove 'conv:' prefix\n \n // Parse message payload\n const payload = JSON.parse(message);\n \n // Call event handler\n onEvent(conversationId, payload);\n } catch (error) {\n console.error('Error processing Redis message:', error);\n }\n });\n \n // Return function to stop listening\n return () => {\n subClient.punsubscribe('conv:*');\n subClient.quit();\n };\n } catch (error) {\n console.error('Failed to start Redis listener:', error);\n return null;\n }\n}","import { Server, Socket } from 'socket.io';\nimport type { Server as HTTPServer } from 'http';\nimport { Transport } from './index';\n\nexport interface SocketConfig {\n path?: string;\n cors?: {\n origin?: string | string[];\n methods?: string[];\n };\n}\n\nexport interface SocketUser {\n id: string;\n model: string;\n}\n\nexport class SocketTransport {\n private io: Server;\n private transport: Transport;\n\n constructor(server: HTTPServer, transport: Transport, config?: SocketConfig) {\n this.io = new Server(server, {\n path: config?.path || '/socket.io',\n addTrailingSlash: false,\n cors: config?.cors || {\n origin: '*',\n methods: ['GET', 'POST']\n }\n });\n this.transport = transport;\n\n // Listen for Redis events and broadcast to sockets\n this.transport.startRedisListener((conversationId: string, payload: any) => {\n this.io.to(`conv:${conversationId}`).emit('chat-event', payload);\n });\n }\n\n /**\n * Handle socket authentication\n */\n public handleAuth(authenticate: (socket: Socket) => Promise<SocketUser | null>) {\n this.io.use(async (socket, next) => {\n try {\n const user = await authenticate(socket);\n if (!user) {\n next(new Error('Unauthorized'));\n return;\n }\n \n // Store user info in socket data\n socket.data.user = user;\n next();\n } catch (error) {\n next(error as Error);\n }\n });\n }\n\n /**\n * Initialize socket event handlers\n */\n public initialize() {\n this.io.on('connection', (socket: Socket) => {\n const user = socket.data.user as SocketUser;\n\n // Join a conversation\n socket.on('join-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.join(roomName);\n \n // Register in local subscription system\n this.transport.subscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:join',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Leave a conversation\n socket.on('leave-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.leave(roomName);\n \n // Remove from local subscription system\n this.transport.unsubscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:leave',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Handle typing events\n socket.on('typing', ({ conversationId, isTyping }: { conversationId: string; isTyping: boolean }) => {\n const payload = {\n type: 'typing',\n conversationId,\n participantModel: user.model,\n participantId: user.id,\n state: isTyping ? 'start' : 'stop'\n };\n \n // Emit to conversation members\n socket.to(`conv:${conversationId}`).emit('chat-event', payload);\n \n // Publish to Redis for cross-instance communication\n this.transport.publishToConversation(conversationId, payload);\n });\n\n // Handle disconnect\n socket.on('disconnect', () => {\n // Clean up subscriptions\n this.transport.cleanupLocal(socket.id);\n });\n });\n }\n\n /**\n * Get Socket.IO server instance\n */\n public getIO(): Server {\n return this.io;\n }\n\n /**\n * Close Socket.IO server\n */\n public close(): Promise<void> {\n return new Promise((resolve) => {\n this.io.close(() => resolve());\n });\n }\n}\n","import * as localSubs from './localSubs';\nimport { createRedisClient, publishToConversation, startRedisListener } from './redis';\nimport { SocketTransport, SocketConfig } from './socket';\nimport { InitOptions } from '../types';\nimport type { Server as HTTPServer } from 'http';\n\n/**\n * Transport configuration type\n */\nexport interface TransportConfig {\n redis?: InitOptions['redis'];\n socket?: SocketConfig;\n}\n\n/**\n * Transport instance type\n */\nexport interface Transport {\n subscribeLocal: typeof localSubs.subscribeLocal;\n unsubscribeLocal: typeof localSubs.unsubscribeLocal;\n cleanupLocal: typeof localSubs.cleanupLocal;\n fanoutLocal: typeof localSubs.fanoutLocal;\n publishToConversation: (conversationId: string, payload: any) => Promise<number>;\n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => (() => void) | null;\n}\n\n/**\n * Create transport system with optional Redis and Socket.IO support\n */\nexport function createTransport(\n config?: TransportConfig,\n server?: HTTPServer\n): Transport & { socket?: SocketTransport } {\n // Initialize Redis client if configuration is provided\n const redisClient = config?.redis ? createRedisClient(config.redis) : null;\n \n // Create base transport\n const transport: Transport = {\n // Local subscription methods\n subscribeLocal: localSubs.subscribeLocal,\n unsubscribeLocal: localSubs.unsubscribeLocal,\n cleanupLocal: localSubs.cleanupLocal,\n fanoutLocal: localSubs.fanoutLocal,\n \n // Redis methods\n publishToConversation: (conversationId: string, payload: any) => \n publishToConversation(redisClient, conversationId, payload),\n \n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => \n startRedisListener(redisClient, onEvent),\n };\n\n // Initialize Socket.IO if server is provided\n if (server) {\n const socketTransport = new SocketTransport(server, transport, config?.socket);\n return {\n ...transport,\n socket: socketTransport\n };\n }\n\n return transport;\n}","import mongoose, { Model } from 'mongoose';\nimport { IConversation } from './Conversation.model';\nimport { IMessage } from './Message.model';\n\n/**\n * Ensure all indexes are created on models\n * @param models Object containing Mongoose models\n */\nexport async function ensureIndexes(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): Promise<void> {\n // Create indexes\n await Promise.all([\n models.Message.ensureIndexes(),\n models.Conversation.ensureIndexes(),\n ]);\n}","/**\n * Redis configuration type\n */\nexport type RedisConfig = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n socketConnectTimeout?: number;\n keepAlive?: number;\n};\n\n/**\n * Environment configuration\n */\nexport interface EnvironmentConfig {\n /** MongoDB connection URI */\n mongoUri: string;\n /** Redis configuration (optional) */\n redis?: RedisConfig;\n}\n/**\n * Get Redis configuration from provided options\n */\nexport function getRedisConfig(config: { redis?: RedisConfig }): RedisConfig | null {\n if (!config.redis) {\n return null;\n }\n\n // Return the full configuration\n return {\n host: config.redis.host || undefined,\n port: config.redis.port || undefined,\n password: config.redis.password || undefined,\n db: config.redis.db || undefined,\n socketConnectTimeout: config.redis.socketConnectTimeout || undefined,\n keepAlive: config.redis.keepAlive || undefined\n };\n}\n","/**\n * Generates a random string ID with the specified length\n * @param length The length of the ID to generate (default: 24)\n * @returns A random string ID\n */\nexport function generateId(length = 24): string {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n const charactersLength = characters.length;\n \n for (let i = 0; i < length; i++) {\n result += characters.charAt(Math.floor(Math.random() * charactersLength));\n }\n \n return result;\n}\n\n/**\n * Creates a conversation ID from two participant identifiers\n * @param a First participant (model:id)\n * @param b Second participant (model:id)\n * @returns A deterministic conversation ID\n */\nexport function createConversationId(\n a: { model: string; id: string },\n b: { model: string; id: string }\n): string {\n // Sort participants to ensure the same ID regardless of order\n const participants = [\n `${a.model}:${a.id}`,\n `${b.model}:${b.id}`,\n ].sort();\n \n return `conv_${participants.join('_')}`;\n}\n\n/**\n * Creates a thread ID\n * @returns A unique thread ID\n */\nexport function createThreadId(): string {\n return `thread_${generateId(16)}`;\n}\n\n/**\n * Creates a message ID\n * @returns A unique message ID\n */\nexport function createMessageId(): string {\n return `msg_${generateId(16)}`;\n}\n\n\n\n\n","import { InitOptions, SendMessageArgs, CreateConversationArgs, MarkReadArgs } from '../types';\n\n/**\n * Validates initialization options\n * @param options Options to validate\n * @returns Validation error message or null if valid\n */\nexport function validateInitOptions(options: InitOptions): string | null {\n if (!options.participantModels || !Array.isArray(options.participantModels) || options.participantModels.length === 0) {\n return 'participantModels must be a non-empty array of strings';\n }\n\n if (!options.memberRoles || !Array.isArray(options.memberRoles) || options.memberRoles.length === 0) {\n return 'memberRoles must be a non-empty array of strings';\n }\n\n // Validate Redis options if provided\n if (options.redis) {\n if (options.redis.url) {\n if (typeof options.redis.url !== 'string') {\n return 'redis.url must be a string';\n }\n } else if (!options.redis.host) {\n return 'Either redis.url or redis.host must be provided';\n }\n }\n\n return null;\n}\n\n/**\n * Validates message sending arguments\n * @param args Message sending arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateSendMessageArgs(args: SendMessageArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.senderModel) {\n return 'senderModel is required';\n }\n\n if (!args.senderId) {\n return 'senderId is required';\n }\n\n if (!args.text || args.text.trim() === '') {\n return 'text is required and cannot be empty';\n }\n\n // If kind is provided, validate it's a valid value\n if (args.kind && !['text', 'system'].includes(args.kind)) {\n return 'kind must be either \"text\" or \"system\"';\n }\n\n // If this is a reply, ensure all thread fields are present\n if (args.parentMessageId && !args.rootThreadId) {\n return 'rootThreadId is required when parentMessageId is provided';\n }\n\n return null;\n}\n\n/**\n * Validates conversation creation arguments\n * @param args Conversation creation arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateCreateConversationArgs(args: CreateConversationArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.a || !args.a.model || !args.a.id) {\n return 'a.model and a.id are required';\n }\n\n if (!args.b || !args.b.model || !args.b.id) {\n return 'b.model and b.id are required';\n }\n\n // Prevent creating a conversation between the same participant\n if (args.a.model === args.b.model && args.a.id === args.b.id) {\n return 'Cannot create a conversation between the same participant';\n }\n\n return null;\n}\n\n/**\n * Validates mark read arguments\n * @param args Mark read arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateMarkReadArgs(args: MarkReadArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.participantModel) {\n return 'participantModel is required';\n }\n\n if (!args.participantId) {\n return 'participantId is required';\n }\n\n if (!args.messageId) {\n return 'messageId is required';\n }\n\n return null;\n}\n\n/**\n * Validates pagination parameters\n * @param limit Pagination limit\n * @param cursor Pagination cursor\n * @returns Validated limit and cursor\n */\nexport function validatePagination(\n limit?: number,\n cursor?: string\n): { limit: number; cursor: string | undefined } {\n // Default limit to 20, max to 100\n let validLimit = limit ? Math.min(Math.max(1, limit), 100) : 20;\n \n // If cursor is provided but empty string, set to undefined\n let validCursor = cursor && cursor.trim() !== '' ? cursor : undefined;\n \n return { limit: validLimit, cursor: validCursor };\n}\n\n\n\n\n","import mongoose from 'mongoose';\nimport type { Server as HTTPServer } from 'http';\nimport { createModels } from './models/connection';\nimport { createConversationsService, ConversationsService } from './services/conversations.service';\nimport { createMessagesService, MessagesService } from './services/messages.service';\nimport { createTransport, Transport } from './transport';\nimport { InitOptions } from './types';\nimport { ensureIndexes } from './models/indexes';\nimport { getRedisConfig } from './config/env';\nimport { SocketTransport } from './transport/socket';\n\n/**\n * Chat services container\n */\nexport interface ChatServices {\n models: {\n Conversation: mongoose.Model<any>;\n Message: mongoose.Model<any>;\n };\n services: {\n conversations: ConversationsService;\n messages: MessagesService;\n };\n transport: Transport & { socket?: SocketTransport };\n}\n\n/**\n * Initialize the chat system\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @param server Optional HTTP server for Socket.IO integration\n * @returns Chat services\n */\nexport async function initChat(\n mongooseInstance: typeof mongoose,\n options: InitOptions,\n server?: HTTPServer\n): Promise<ChatServices> {\n // Create models\n const models = createModels(mongooseInstance, options);\n\n // Ensure indexes are created\n await ensureIndexes(models);\n\n // Create transport system with optional Socket.IO support\n const transport = createTransport({\n redis: getRedisConfig(options) || undefined,\n socket: options.socket || undefined\n }, server);\n\n // Initialize Socket.IO if server was provided\n if (server && transport.socket) {\n // Set up authentication handler\n transport.socket.handleAuth(async (socket) => {\n // Get user info from socket handshake auth\n const userId = socket.handshake.auth.userId;\n const userModel = socket.handshake.auth.userModel || 'User';\n\n if (!userId || !options.participantModels.includes(userModel)) {\n return null;\n }\n\n return { id: userId, model: userModel };\n });\n\n // Initialize socket event handlers\n transport.socket.initialize();\n }\n\n // Create services with hooks\n const services = {\n conversations: createConversationsService(models),\n messages: createMessagesService(models, {\n onMessageCreated: (message) => {\n const payload = { type: 'message:created', message };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n message.conversationId.toString(),\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n message.conversationId.toString(),\n payload\n );\n },\n \n onMessageRead: (args) => {\n const payload = { \n type: 'conversation:read', \n conversationId: args.conversationId,\n messageId: args.messageId,\n participantModel: args.participantModel,\n participantId: args.participantId,\n at: args.at.toISOString()\n };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n args.conversationId,\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n args.conversationId,\n payload\n );\n }\n })\n };\n\n return { models, services, transport };\n}\n\n// Re-export types\nexport * from './types';\nexport * from './utils';\nexport * from './models';\nexport * from './services';\nexport * from './transport';\nexport * from './config/env';"]}
|
|
1
|
+
{"version":3,"sources":["../src/models/Conversation.model.ts","../src/models/Message.model.ts","../src/models/connection.ts","../src/utils/pagination.ts","../src/services/search/search.conversations.ts","../src/services/conversations.service.ts","../src/services/search/search.messages.ts","../src/services/messages.service.ts","../src/transport/localSubs.ts","../src/transport/redis.ts","../src/transport/socket.ts","../src/transport/index.ts","../src/models/indexes.ts","../src/config/env.ts","../src/utils/ids.ts","../src/utils/validators.ts","../src/index.ts"],"names":["mongoose","Schema"],"mappings":"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","sourcesContent":["import mongoose, { Schema, Document, Model, Connection } from 'mongoose';\nimport { InitOptions } from '../types';\n\nexport interface IParticipant {\n entityModel: string;\n entityId: string;\n role: string;\n}\n\nexport interface IConversation extends Document {\n organizationId: string;\n participants: IParticipant[];\n lastMessageAt: Date;\n lastMessagePreview?: string;\n lastMessageSenderId?: string;\n lastMessageSenderModel?: string;\n metadata?: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create (or retrieve) Conversation model safely\n */\nexport function createConversationModel(\n options: InitOptions,\n conn: Connection = mongoose.connection\n): Model<IConversation> {\n // Participant schema\n const ParticipantSchema = new Schema(\n {\n entityModel: {\n type: String,\n enum: options.participantModels,\n required: true,\n },\n entityId: {\n type: String,\n required: true,\n },\n role: {\n type: String,\n enum: options.memberRoles,\n required: true,\n },\n },\n { _id: false }\n );\n\n // Conversation schema\n const ConversationSchema = new Schema<IConversation>(\n {\n organizationId: {\n type: String,\n required: true,\n index: true,\n },\n participants: {\n type: [ParticipantSchema],\n required: true,\n validate: {\n validator: (participants: IParticipant[]) => participants.length === 2,\n message: 'A conversation must have exactly 2 participants',\n },\n },\n lastMessageAt: {\n type: Date,\n index: true,\n },\n lastMessagePreview: String,\n lastMessageSenderId: String,\n lastMessageSenderModel: String,\n metadata: Schema.Types.Mixed,\n },\n { timestamps: true }\n );\n\n // Indexes\n ConversationSchema.index({\n organizationId: 1,\n 'participants.entityId': 1,\n });\n\n ConversationSchema.index({\n organizationId: 1,\n lastMessageAt: -1,\n });\n\n const name = 'Conversation';\n // ✅ Return on the same line & reuse existing model to avoid OverwriteModelError\n return (conn.models[name] as Model<IConversation>) || conn.model<IConversation>(name, ConversationSchema);\n}\n","import mongoose, { Schema, Document, Model } from 'mongoose';\nimport { InitOptions } from '../types';\n\n/**\n * Message document interface\n */\nexport interface IMessage extends Document {\n organizationId: mongoose.Types.ObjectId;\n conversationId: mongoose.Types.ObjectId;\n senderModel: string;\n senderId: mongoose.Types.ObjectId;\n kind: 'text' | 'system';\n text: string;\n parentMessageId?: mongoose.Types.ObjectId;\n rootThreadId?: mongoose.Types.ObjectId;\n editedAt?: Date;\n deletedAt?: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Create Message model\n */\nexport function createMessageModel(options: InitOptions): Model<IMessage> {\n // Message schema\n const MessageSchema = new Schema<IMessage>(\n {\n organizationId: {\n type: Schema.Types.ObjectId,\n ref: 'Organization',\n required: true,\n index: true\n },\n conversationId: {\n type: Schema.Types.ObjectId,\n ref: 'Conversation',\n required: true,\n index: true\n },\n senderModel: {\n type: String,\n enum: options.participantModels,\n required: true\n },\n senderId: {\n type: Schema.Types.ObjectId,\n refPath: 'senderModel',\n required: true,\n index: true\n },\n kind: {\n type: String,\n enum: ['text', 'system'],\n default: 'text',\n index: true\n },\n text: {\n type: String,\n trim: true\n },\n parentMessageId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n rootThreadId: {\n type: Schema.Types.ObjectId,\n ref: 'Message'\n },\n editedAt: {\n type: Date\n },\n deletedAt: {\n type: Date\n }\n },\n { timestamps: true }\n );\n\n // Create compound index\n MessageSchema.index({\n organizationId: 1,\n conversationId: 1,\n createdAt: -1\n });\n\n // Create text search index if enabled\n if (options.enableTextSearch) {\n MessageSchema.index(\n { text: 'text' },\n {\n name: 'text_search',\n weights: { text: 10 }\n }\n );\n }\n\n // Return model\n return mongoose.model<IMessage>('Message', MessageSchema);\n}","import mongoose from 'mongoose';\nimport { InitOptions } from '../types';\nimport { createConversationModel } from './Conversation.model';\nimport { createMessageModel } from './Message.model';\n\n/**\n * Create Mongoose models with applied configuration\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @returns Object containing Conversation and Message models\n */\nexport function createModels(mongooseInstance: typeof mongoose, options: InitOptions) {\n // Create models with provided options\n const Conversation = createConversationModel(options);\n const Message = createMessageModel(options);\n \n return {\n Conversation,\n Message\n };\n}\n\n/**\n * Connection state enum\n */\nenum ConnectionState {\n Disconnected = 0,\n Connected = 1,\n Connecting = 2,\n Disconnecting = 3,\n}","/**\n * Pagination result interface\n */\nexport interface PaginatedResult<T> {\n items: T[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n\n/**\n * Creates a pagination cursor from a timestamp and ID\n * @param timestamp Timestamp to encode in the cursor\n * @param id ID to encode in the cursor\n * @returns An encoded cursor string\n */\nexport function createCursor(timestamp: number, id: string): string {\n const cursorData = `${timestamp}:${id}`;\n return Buffer.from(cursorData).toString('base64');\n}\n\n/**\n * Parses a pagination cursor into its timestamp and ID components\n * @param cursor The cursor string to parse\n * @returns An object containing the timestamp and ID, or null if invalid\n */\nexport function parseCursor(cursor: string): { timestamp: number; id: string } | null {\n try {\n const decoded = Buffer.from(cursor, 'base64').toString('utf-8');\n const [timestampStr, id] = decoded.split(':');\n \n const timestamp = parseInt(timestampStr, 10);\n \n if (isNaN(timestamp) || !id) {\n return null;\n }\n \n return { timestamp, id };\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Creates a paginated response\n * @param items The items for the current page\n * @param limit The requested page size\n * @param getNextCursor Function to get the next cursor from the last item\n * @returns A paginated result object\n */\nexport function createPaginatedResponse<T>(\n items: T[],\n limit: number,\n getNextCursor: (lastItem: T) => string\n): PaginatedResult<T> {\n const hasMore = items.length > limit;\n \n // If we have more items than the limit, remove the extra item\n // that we used to determine if there are more pages\n const paginatedItems = hasMore ? items.slice(0, limit) : items;\n \n // Get the next cursor from the last item if we have more items\n const nextCursor = hasMore && paginatedItems.length > 0\n ? getNextCursor(paginatedItems[paginatedItems.length - 1])\n : null;\n \n return {\n items: paginatedItems,\n nextCursor,\n hasMore,\n };\n}\n\n\n\n\n","import { Model } from 'mongoose';\nimport { IConversation } from '../../models/Conversation.model';\nimport { SearchConversationsArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search conversations with pagination\n */\nexport async function searchConversations(\n model: Model<IConversation>,\n params: SearchConversationsArgs\n): Promise<PaginatedResult<IConversation>> {\n const { \n organizationId, \n participantModel, \n participantId,\n limit = 20, \n cursor \n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId \n };\n \n // Add participant filter if provided\n if (participantModel && participantId) {\n baseQuery['participants'] = {\n $elemMatch: {\n entityModel: participantModel,\n entityId: participantId\n }\n };\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n const cursorDate = new Date(timestamp);\n \n baseQuery.$or = [\n { lastMessageAt: { $lt: cursorDate } },\n { lastMessageAt: cursorDate, _id: { $lt: id } }\n ];\n }\n }\n \n // Find conversations and sort by lastMessageAt\n const conversations = await model.find(baseQuery)\n .sort({ lastMessageAt: -1 })\n .limit(limit + 1)\n .exec();\n \n // Check if we have more results\n const hasMore = conversations.length > limit;\n const items = hasMore ? conversations.slice(0, limit) : conversations;\n \n // Create next cursor if we have more results\n let nextCursor: string | null = null;\n \n if (hasMore && items.length > 0) {\n const lastItem = items[items.length - 1];\n // Use lastMessageAt if available, otherwise use createdAt\n const timestamp = lastItem.lastMessageAt ? \n lastItem.lastMessageAt.getTime() : \n lastItem.createdAt.getTime();\n \n nextCursor = createCursor(timestamp, lastItem._id.toString());\n }\n \n return {\n items,\n nextCursor,\n hasMore\n };\n}\n\n/**\n * Find a conversation between two specific participants\n */\nexport async function searchByParticipantPair(\n model: Model<IConversation>,\n params: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }\n): Promise<IConversation | null> {\n const { organizationId, a, b } = params;\n \n const conversation = await model.findOne({\n organizationId,\n $and: [\n {\n participants: {\n $elemMatch: {\n entityModel: a.model,\n entityId: a.id\n }\n }\n },\n {\n participants: {\n $elemMatch: {\n entityModel: b.model,\n entityId: b.id\n }\n }\n },\n {\n $expr: {\n $eq: [{ $size: \"$participants\" }, 2]\n }\n }\n ]\n });\n \n return conversation;\n}","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { CreateConversationArgs, SearchConversationsArgs } from '../types';\nimport { searchConversations, searchByParticipantPair } from './search/search.conversations';\nimport { PaginatedResult } from '../utils/pagination';\n\n/**\n * Service for managing conversations\n */\nexport interface ConversationsService {\n createOrFindConversation(args: CreateConversationArgs): Promise<IConversation>;\n getConversation(organizationId: string, conversationId: string): Promise<IConversation | null>;\n searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>>;\n searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null>;\n}\n\n/**\n * Create conversations service\n * @param models MongoDB models\n * @returns Conversations service instance\n */\nexport function createConversationsService(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): ConversationsService {\n const { Conversation } = models;\n\n return {\n /**\n * Create a new conversation or find existing one between participants\n */\n async createOrFindConversation(args: CreateConversationArgs): Promise<IConversation> {\n const { organizationId, a, b } = args;\n\n try {\n // Check if conversation already exists between these participants\n const existingConversation = await searchByParticipantPair(Conversation, {\n organizationId,\n a,\n b\n });\n\n if (existingConversation) {\n return existingConversation;\n }\n\n // Create new conversation\n const newConversation = new Conversation({\n organizationId,\n participants: [\n {\n entityModel: a.model,\n entityId: a.id,\n role: 'member' // Default role\n },\n {\n entityModel: b.model,\n entityId: b.id,\n role: 'member' // Default role\n }\n ],\n lastMessageAt: new Date() // Set initial lastMessageAt\n });\n\n await newConversation.save();\n return newConversation;\n } catch (error) {\n console.error('Error in createOrFindConversation:', error);\n throw error;\n }\n },\n\n /**\n * Get a conversation by ID\n */\n async getConversation(organizationId: string, conversationId: string): Promise<IConversation | null> {\n try {\n return await Conversation.findOne({\n organizationId,\n _id: conversationId\n });\n } catch (error) {\n console.error('Error in getConversation:', error);\n throw error;\n }\n },\n\n /**\n * Search conversations with pagination\n */\n async searchConversations(args: SearchConversationsArgs): Promise<PaginatedResult<IConversation>> {\n try {\n return await searchConversations(Conversation, args);\n } catch (error) {\n console.error('Error in searchConversations:', error);\n throw error;\n }\n },\n\n /**\n * Find a conversation between two specific participants\n */\n async searchByParticipantPair(args: {\n organizationId: string;\n a: { model: string; id: string };\n b: { model: string; id: string };\n }): Promise<IConversation | null> {\n try {\n return await searchByParticipantPair(Conversation, args);\n } catch (error) {\n console.error('Error in searchByParticipantPair:', error);\n throw error;\n }\n }\n };\n}","import { Model } from 'mongoose';\nimport { IMessage } from '../../models/Message.model';\nimport { SearchMessagesArgs } from '../../types';\nimport { createCursor, parseCursor, PaginatedResult } from '../../utils/pagination';\n\n/**\n * Search messages based on various criteria\n * @param model Message model\n * @param params Search parameters\n * @returns Paginated messages with optional text search results\n */\nexport async function searchMessages(\n model: Model<IMessage>,\n params: SearchMessagesArgs\n): Promise<PaginatedResult<IMessage>> {\n const { \n organizationId,\n query,\n conversationId,\n senderModel,\n senderId,\n kind,\n dateFrom,\n dateTo,\n threadRootId,\n limit = 20,\n cursor\n } = params;\n \n // Base query with required organizationId\n const baseQuery: Record<string, any> = { \n organizationId\n };\n\n // Add optional filters if provided\n if (conversationId) {\n baseQuery.conversationId = conversationId;\n }\n \n if (senderModel && senderId) {\n baseQuery.senderModel = senderModel;\n baseQuery.senderId = senderId;\n } else if (senderModel) {\n baseQuery.senderModel = senderModel;\n }\n \n if (kind) {\n baseQuery.kind = kind;\n }\n \n // Date range filters\n if (dateFrom || dateTo) {\n baseQuery.createdAt = {};\n \n if (dateFrom) {\n baseQuery.createdAt.$gte = new Date(dateFrom);\n }\n \n if (dateTo) {\n baseQuery.createdAt.$lte = new Date(dateTo);\n }\n }\n \n // Thread filter\n if (threadRootId) {\n baseQuery.rootThreadId = threadRootId;\n }\n \n // Handle cursor-based pagination\n if (cursor) {\n const parsedCursor = parseCursor(cursor);\n \n if (parsedCursor) {\n const { timestamp, id } = parsedCursor;\n \n baseQuery.$or = [\n { createdAt: { $lt: new Date(timestamp) } },\n { createdAt: new Date(timestamp), _id: { $lt: id } }\n ];\n }\n }\n \n // Determine sort order and query approach\n let sort: Record<string, number> = { createdAt: -1 };\n let pipeline: any[] = [];\n \n // Use text search if query provided\n if (query && query.trim()) {\n // Set up text search with score and appropriate sort\n pipeline = [\n { $match: { $text: { $search: query }, ...baseQuery } },\n { $addFields: { score: { $meta: 'textScore' } } },\n { $sort: { score: { $meta: 'textScore' }, createdAt: -1 } },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n } else {\n // Use standard query\n pipeline = [\n { $match: baseQuery },\n { $sort: sort },\n { $limit: limit + 1 } // Get one extra for pagination\n ];\n }\n \n // Execute query\n const items = await model.aggregate(pipeline);\n \n // Check if we have more results\n const hasMore = items.length > limit;\n const limitedItems = hasMore ? items.slice(0, limit) : items;\n \n // Create next cursor if we have more results\n const nextCursor = hasMore && limitedItems.length > 0\n ? createCursor(\n new Date(limitedItems[limitedItems.length - 1].createdAt).getTime(),\n limitedItems[limitedItems.length - 1]._id.toString()\n )\n : null;\n \n return {\n items: limitedItems,\n nextCursor,\n hasMore\n };\n}\n","import { Model } from 'mongoose';\nimport { IConversation } from '../models/Conversation.model';\nimport { IMessage } from '../models/Message.model';\nimport { SendMessageArgs, ListMessagesArgs, MarkReadArgs } from '../types';\nimport { searchMessages } from './search/search.messages';\nimport { createPaginatedResponse, PaginatedResult } from '../utils/pagination';\n\n/**\n * Message service hooks interface\n */\nexport interface MessageServiceHooks {\n onMessageCreated?: (message: IMessage) => void;\n onMessageRead?: (args: MarkReadArgs & { at: Date }) => void;\n}\n\n/**\n * Service for managing messages\n */\nexport interface MessagesService {\n sendMessage(args: SendMessageArgs): Promise<IMessage>;\n listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>>;\n markRead(args: MarkReadArgs): Promise<void>;\n}\n\n/**\n * Create messages service\n * @param models MongoDB models\n * @param hooks Service hooks\n * @returns Messages service instance\n */\nexport function createMessagesService(\n models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n },\n hooks: MessageServiceHooks = {}\n): MessagesService {\n const { Message, Conversation } = models;\n const { onMessageCreated, onMessageRead } = hooks;\n\n return {\n /**\n * Send a new message\n * @param args Message sending arguments\n * @returns The created message\n */\n async sendMessage(args: SendMessageArgs): Promise<IMessage> {\n const { \n organizationId, \n conversationId, \n senderModel, \n senderId, \n text, \n kind = 'text',\n parentMessageId,\n rootThreadId\n } = args;\n\n // Create the message\n const message = new Message({\n organizationId,\n conversationId,\n senderModel,\n senderId,\n text,\n kind,\n parentMessageId,\n rootThreadId: rootThreadId || parentMessageId // If rootThreadId not provided but parentMessageId is, use parentMessageId\n });\n\n await message.save();\n\n // Update the conversation with last message info\n await Conversation.findByIdAndUpdate(\n conversationId,\n {\n $set: {\n lastMessageAt: message.createdAt,\n lastMessagePreview: text.substring(0, 100),\n lastMessageSenderId: senderId,\n lastMessageSenderModel: senderModel\n }\n }\n );\n\n // Trigger hook\n if (onMessageCreated) {\n onMessageCreated(message);\n }\n\n return message;\n },\n\n /**\n * List messages for a conversation with pagination\n * @param args Message listing arguments\n * @returns Paginated list of messages\n */\n async listMessages(args: ListMessagesArgs): Promise<PaginatedResult<IMessage>> {\n const { organizationId, conversationId, limit = 20, cursor } = args;\n\n return searchMessages(Message, {\n organizationId,\n conversationId,\n limit,\n cursor\n });\n },\n\n /**\n * Mark a message as read by a participant\n * @param args Read marking arguments\n */\n async markRead(args: MarkReadArgs): Promise<void> {\n const { \n organizationId, \n conversationId, \n participantModel, \n participantId, \n messageId \n } = args;\n\n const at = new Date();\n\n // Update the message read receipts\n await Message.findOneAndUpdate(\n {\n organizationId,\n conversationId,\n _id: messageId,\n // Ensure read receipt doesn't exist for this participant\n 'readBy.participantId': { $ne: participantId }\n },\n {\n $push: {\n readBy: {\n participantModel,\n participantId,\n readAt: at\n }\n }\n }\n );\n\n // Update participant last read in conversation\n await Conversation.findOneAndUpdate(\n {\n organizationId,\n _id: conversationId,\n 'participants.entityModel': participantModel,\n 'participants.entityId': participantId\n },\n {\n $set: {\n 'participants.$.lastReadMessageId': messageId,\n 'participants.$.lastReadAt': at\n }\n }\n );\n\n // Trigger hook\n if (onMessageRead) {\n onMessageRead({ ...args, at });\n }\n }\n };\n}\n","/**\n * In-memory subscription maps for conversations and sockets\n */\n\n// Maps conversation IDs to sets of socket IDs\nconst convSubs: Map<string, Set<string>> = new Map();\n\n// Maps socket IDs to sets of conversation IDs\nconst socketSubs: Map<string, Set<string>> = new Map();\n\n/**\n * Subscribe a socket to a conversation\n * @param conversationId The conversation ID to subscribe to\n * @param socketId The socket ID to subscribe\n */\nexport function subscribeLocal(conversationId: string, socketId: string): void {\n // Add socket to conversation subscribers\n if (!convSubs.has(conversationId)) {\n convSubs.set(conversationId, new Set());\n }\n convSubs.get(conversationId)?.add(socketId);\n \n // Add conversation to socket subscriptions\n if (!socketSubs.has(socketId)) {\n socketSubs.set(socketId, new Set());\n }\n socketSubs.get(socketId)?.add(conversationId);\n}\n\n/**\n * Unsubscribe a socket from a conversation\n * @param conversationId The conversation ID to unsubscribe from\n * @param socketId The socket ID to unsubscribe\n */\nexport function unsubscribeLocal(conversationId: string, socketId: string): void {\n // Remove socket from conversation subscribers\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n \n // Remove conversation from socket subscriptions\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n conversations.delete(conversationId);\n if (conversations.size === 0) {\n socketSubs.delete(socketId);\n }\n }\n}\n\n/**\n * Clean up all subscriptions for a socket\n * @param socketId The socket ID to clean up\n */\nexport function cleanupLocal(socketId: string): void {\n // Get all conversations this socket is subscribed to\n const conversations = socketSubs.get(socketId);\n if (conversations) {\n // Unsubscribe from each conversation\n for (const conversationId of conversations) {\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n sockets.delete(socketId);\n if (sockets.size === 0) {\n convSubs.delete(conversationId);\n }\n }\n }\n \n // Remove socket from socket subscriptions\n socketSubs.delete(socketId);\n }\n}\n\n/**\n * Fan out a message to all local subscribers of a conversation\n * @param conversationId The conversation ID to fan out to\n * @param payload The payload to send\n * @param emitter Function to emit events to socket IDs\n */\nexport function fanoutLocal(\n conversationId: string,\n payload: any,\n emitter: (toSocketId: string, event: any) => void\n): void {\n // Get all sockets subscribed to this conversation\n const sockets = convSubs.get(conversationId);\n if (sockets) {\n // Emit to each socket\n for (const socketId of sockets) {\n try {\n emitter(socketId, payload);\n } catch (error) {\n console.error(`Error emitting to socket ${socketId}:`, error);\n }\n }\n }\n}","import Redis from 'ioredis';\nimport { RedisConfig } from '../config/env';\n\n/**\n * Create a Redis client based on provided configuration\n * @param config Redis configuration\n * @returns Redis client instance or null if configuration is missing\n */\nexport function createRedisClient(config?: RedisConfig): Redis | null {\n if (!config) {\n console.warn('Redis configuration not found. Redis features will be disabled.');\n return null;\n }\n \n try {\n let client: Redis;\n \n // Use either URL or granular connection options\n if (config.url) {\n client = new Redis(config.url);\n } else if (config.host) {\n client = new Redis({\n host: config.host,\n port: config.port,\n password: config.password,\n db: config.db,\n connectTimeout: config.socketConnectTimeout, // Changed \n enableReadyCheck: true,\n enableOfflineQueue: true,\n retryStrategy(times) {\n const delay = Math.min(times * 50, 2000);\n return delay;\n }\n });\n } else {\n console.warn('Invalid Redis configuration. Redis features will be disabled.');\n return null;\n }\n \n // Log Redis connection events\n client.on('connect', () => {\n console.info('Redis client connected');\n });\n \n client.on('error', (err) => {\n console.error('Redis client error:', err);\n });\n \n client.on('reconnecting', () => {\n console.info('Redis client reconnecting');\n });\n \n return client;\n } catch (error) {\n console.error('Failed to create Redis client:', error);\n return null;\n }\n}\n\n/**\n * Publish a message to a conversation channel\n * @param client Redis client\n * @param conversationId Conversation ID\n * @param payload Message payload\n * @returns Promise resolving to number of clients that received the message\n */\nexport async function publishToConversation(\n client: Redis | null,\n conversationId: string,\n payload: any\n): Promise<number> {\n if (!client) return 0;\n \n try {\n const channel = `conv:${conversationId}`;\n const message = JSON.stringify(payload);\n return await client.publish(channel, message);\n } catch (error) {\n console.error(`Failed to publish to conversation ${conversationId}:`, error);\n return 0;\n }\n}\n\n/**\n * Start Redis subscription listener\n * @param client Redis client\n * @param onEvent Callback function for received events\n * @returns Function to stop the listener\n */\nexport function startRedisListener(\n client: Redis | null,\n onEvent: (conversationId: string, payload: any) => void\n): (() => void) | null {\n if (!client) return null;\n \n try {\n // Create a duplicate client for subscribing\n // (Redis clients in subscribe mode cannot be used for other commands)\n const subClient = client.duplicate();\n \n // Subscribe to conversation channels\n subClient.psubscribe('conv:*');\n \n // Handle incoming messages\n subClient.on('pmessage', (_pattern, channel, message) => {\n try {\n // Extract conversation ID from channel\n const conversationId = channel.slice(5); // Remove 'conv:' prefix\n \n // Parse message payload\n const payload = JSON.parse(message);\n \n // Call event handler\n onEvent(conversationId, payload);\n } catch (error) {\n console.error('Error processing Redis message:', error);\n }\n });\n \n // Return function to stop listening\n return () => {\n subClient.punsubscribe('conv:*');\n subClient.quit();\n };\n } catch (error) {\n console.error('Failed to start Redis listener:', error);\n return null;\n }\n}","import { Server, Socket } from 'socket.io';\nimport type { Server as HTTPServer } from 'http';\nimport { Transport } from './index';\n\nexport interface SocketConfig {\n path?: string;\n cors?: {\n origin?: string | string[];\n methods?: string[];\n };\n}\n\nexport interface SocketUser {\n id: string;\n model: string;\n}\n\nexport class SocketTransport {\n private io: Server;\n private transport: Transport;\n\n constructor(server: HTTPServer, transport: Transport, config?: SocketConfig) {\n this.io = new Server(server, {\n path: config?.path || '/socket.io',\n addTrailingSlash: false,\n cors: config?.cors || {\n origin: '*',\n methods: ['GET', 'POST']\n }\n });\n this.transport = transport;\n\n // Listen for Redis events and broadcast to sockets\n this.transport.startRedisListener((conversationId: string, payload: any) => {\n this.io.to(`conv:${conversationId}`).emit('chat-event', payload);\n });\n }\n\n /**\n * Handle socket authentication\n */\n public handleAuth(authenticate: (socket: Socket) => Promise<SocketUser | null>) {\n this.io.use(async (socket, next) => {\n try {\n const user = await authenticate(socket);\n if (!user) {\n next(new Error('Unauthorized'));\n return;\n }\n \n // Store user info in socket data\n socket.data.user = user;\n next();\n } catch (error) {\n next(error as Error);\n }\n });\n }\n\n /**\n * Initialize socket event handlers\n */\n public initialize() {\n this.io.on('connection', (socket: Socket) => {\n const user = socket.data.user as SocketUser;\n\n // Join a conversation\n socket.on('join-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.join(roomName);\n \n // Register in local subscription system\n this.transport.subscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:join',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Leave a conversation\n socket.on('leave-conversation', (conversationId: string) => {\n const roomName = `conv:${conversationId}`;\n socket.leave(roomName);\n \n // Remove from local subscription system\n this.transport.unsubscribeLocal(conversationId, socket.id);\n \n // Emit presence event\n socket.to(roomName).emit('chat-event', {\n type: 'presence:leave',\n organizationId: 'default',\n participantModel: user.model,\n participantId: user.id,\n at: new Date().toISOString()\n });\n });\n\n // Handle typing events\n socket.on('typing', ({ conversationId, isTyping }: { conversationId: string; isTyping: boolean }) => {\n const payload = {\n type: 'typing',\n conversationId,\n participantModel: user.model,\n participantId: user.id,\n state: isTyping ? 'start' : 'stop'\n };\n \n // Emit to conversation members\n socket.to(`conv:${conversationId}`).emit('chat-event', payload);\n \n // Publish to Redis for cross-instance communication\n this.transport.publishToConversation(conversationId, payload);\n });\n\n // Handle disconnect\n socket.on('disconnect', () => {\n // Clean up subscriptions\n this.transport.cleanupLocal(socket.id);\n });\n });\n }\n\n /**\n * Get Socket.IO server instance\n */\n public getIO(): Server {\n return this.io;\n }\n\n /**\n * Close Socket.IO server\n */\n public close(): Promise<void> {\n return new Promise((resolve) => {\n this.io.close(() => resolve());\n });\n }\n}\n","import * as localSubs from './localSubs';\nimport { createRedisClient, publishToConversation, startRedisListener } from './redis';\nimport { SocketTransport, SocketConfig } from './socket';\nimport { InitOptions } from '../types';\nimport type { Server as HTTPServer } from 'http';\n\n/**\n * Transport configuration type\n */\nexport interface TransportConfig {\n redis?: InitOptions['redis'];\n socket?: SocketConfig;\n}\n\n/**\n * Transport instance type\n */\nexport interface Transport {\n subscribeLocal: typeof localSubs.subscribeLocal;\n unsubscribeLocal: typeof localSubs.unsubscribeLocal;\n cleanupLocal: typeof localSubs.cleanupLocal;\n fanoutLocal: typeof localSubs.fanoutLocal;\n publishToConversation: (conversationId: string, payload: any) => Promise<number>;\n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => (() => void) | null;\n}\n\n/**\n * Create transport system with optional Redis and Socket.IO support\n */\nexport function createTransport(\n config?: TransportConfig,\n server?: HTTPServer\n): Transport & { socket?: SocketTransport } {\n // Initialize Redis client if configuration is provided\n const redisClient = config?.redis ? createRedisClient(config.redis) : null;\n \n // Create base transport\n const transport: Transport = {\n // Local subscription methods\n subscribeLocal: localSubs.subscribeLocal,\n unsubscribeLocal: localSubs.unsubscribeLocal,\n cleanupLocal: localSubs.cleanupLocal,\n fanoutLocal: localSubs.fanoutLocal,\n \n // Redis methods\n publishToConversation: (conversationId: string, payload: any) => \n publishToConversation(redisClient, conversationId, payload),\n \n startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => \n startRedisListener(redisClient, onEvent),\n };\n\n // Initialize Socket.IO if server is provided\n if (server) {\n const socketTransport = new SocketTransport(server, transport, config?.socket);\n return {\n ...transport,\n socket: socketTransport\n };\n }\n\n return transport;\n}","import mongoose, { Model } from 'mongoose';\nimport { IConversation } from './Conversation.model';\nimport { IMessage } from './Message.model';\n\n/**\n * Ensure all indexes are created on models\n * @param models Object containing Mongoose models\n */\nexport async function ensureIndexes(models: {\n Conversation: Model<IConversation>;\n Message: Model<IMessage>;\n}): Promise<void> {\n // Create indexes\n await Promise.all([\n models.Message.ensureIndexes(),\n models.Conversation.ensureIndexes(),\n ]);\n}","/**\n * Redis configuration type\n */\nexport type RedisConfig = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n socketConnectTimeout?: number;\n keepAlive?: number;\n};\n\n/**\n * Environment configuration\n */\nexport interface EnvironmentConfig {\n /** MongoDB connection URI */\n mongoUri: string;\n /** Redis configuration (optional) */\n redis?: RedisConfig;\n}\n/**\n * Get Redis configuration from provided options\n */\nexport function getRedisConfig(config: { redis?: RedisConfig }): RedisConfig | null {\n if (!config.redis) {\n return null;\n }\n\n // Return the full configuration\n return {\n host: config.redis.host || undefined,\n port: config.redis.port || undefined,\n password: config.redis.password || undefined,\n db: config.redis.db || undefined,\n socketConnectTimeout: config.redis.socketConnectTimeout || undefined,\n keepAlive: config.redis.keepAlive || undefined\n };\n}\n","/**\n * Generates a random string ID with the specified length\n * @param length The length of the ID to generate (default: 24)\n * @returns A random string ID\n */\nexport function generateId(length = 24): string {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n const charactersLength = characters.length;\n \n for (let i = 0; i < length; i++) {\n result += characters.charAt(Math.floor(Math.random() * charactersLength));\n }\n \n return result;\n}\n\n/**\n * Creates a conversation ID from two participant identifiers\n * @param a First participant (model:id)\n * @param b Second participant (model:id)\n * @returns A deterministic conversation ID\n */\nexport function createConversationId(\n a: { model: string; id: string },\n b: { model: string; id: string }\n): string {\n // Sort participants to ensure the same ID regardless of order\n const participants = [\n `${a.model}:${a.id}`,\n `${b.model}:${b.id}`,\n ].sort();\n \n return `conv_${participants.join('_')}`;\n}\n\n/**\n * Creates a thread ID\n * @returns A unique thread ID\n */\nexport function createThreadId(): string {\n return `thread_${generateId(16)}`;\n}\n\n/**\n * Creates a message ID\n * @returns A unique message ID\n */\nexport function createMessageId(): string {\n return `msg_${generateId(16)}`;\n}\n\n\n\n\n","import { InitOptions, SendMessageArgs, CreateConversationArgs, MarkReadArgs } from '../types';\n\n/**\n * Validates initialization options\n * @param options Options to validate\n * @returns Validation error message or null if valid\n */\nexport function validateInitOptions(options: InitOptions): string | null {\n if (!options.participantModels || !Array.isArray(options.participantModels) || options.participantModels.length === 0) {\n return 'participantModels must be a non-empty array of strings';\n }\n\n if (!options.memberRoles || !Array.isArray(options.memberRoles) || options.memberRoles.length === 0) {\n return 'memberRoles must be a non-empty array of strings';\n }\n\n // Validate Redis options if provided\n if (options.redis) {\n if (options.redis.url) {\n if (typeof options.redis.url !== 'string') {\n return 'redis.url must be a string';\n }\n } else if (!options.redis.host) {\n return 'Either redis.url or redis.host must be provided';\n }\n }\n\n return null;\n}\n\n/**\n * Validates message sending arguments\n * @param args Message sending arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateSendMessageArgs(args: SendMessageArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.senderModel) {\n return 'senderModel is required';\n }\n\n if (!args.senderId) {\n return 'senderId is required';\n }\n\n if (!args.text || args.text.trim() === '') {\n return 'text is required and cannot be empty';\n }\n\n // If kind is provided, validate it's a valid value\n if (args.kind && !['text', 'system'].includes(args.kind)) {\n return 'kind must be either \"text\" or \"system\"';\n }\n\n // If this is a reply, ensure all thread fields are present\n if (args.parentMessageId && !args.rootThreadId) {\n return 'rootThreadId is required when parentMessageId is provided';\n }\n\n return null;\n}\n\n/**\n * Validates conversation creation arguments\n * @param args Conversation creation arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateCreateConversationArgs(args: CreateConversationArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.a || !args.a.model || !args.a.id) {\n return 'a.model and a.id are required';\n }\n\n if (!args.b || !args.b.model || !args.b.id) {\n return 'b.model and b.id are required';\n }\n\n // Prevent creating a conversation between the same participant\n if (args.a.model === args.b.model && args.a.id === args.b.id) {\n return 'Cannot create a conversation between the same participant';\n }\n\n return null;\n}\n\n/**\n * Validates mark read arguments\n * @param args Mark read arguments to validate\n * @returns Validation error message or null if valid\n */\nexport function validateMarkReadArgs(args: MarkReadArgs): string | null {\n if (!args.organizationId) {\n return 'organizationId is required';\n }\n\n if (!args.conversationId) {\n return 'conversationId is required';\n }\n\n if (!args.participantModel) {\n return 'participantModel is required';\n }\n\n if (!args.participantId) {\n return 'participantId is required';\n }\n\n if (!args.messageId) {\n return 'messageId is required';\n }\n\n return null;\n}\n\n/**\n * Validates pagination parameters\n * @param limit Pagination limit\n * @param cursor Pagination cursor\n * @returns Validated limit and cursor\n */\nexport function validatePagination(\n limit?: number,\n cursor?: string\n): { limit: number; cursor: string | undefined } {\n // Default limit to 20, max to 100\n let validLimit = limit ? Math.min(Math.max(1, limit), 100) : 20;\n \n // If cursor is provided but empty string, set to undefined\n let validCursor = cursor && cursor.trim() !== '' ? cursor : undefined;\n \n return { limit: validLimit, cursor: validCursor };\n}\n\n\n\n\n","import mongoose from 'mongoose';\nimport type { Server as HTTPServer } from 'http';\nimport { createModels } from './models/connection';\nimport { createConversationsService, ConversationsService } from './services/conversations.service';\nimport { createMessagesService, MessagesService } from './services/messages.service';\nimport { createTransport, Transport } from './transport';\nimport { InitOptions } from './types';\nimport { ensureIndexes } from './models/indexes';\nimport { getRedisConfig } from './config/env';\nimport { SocketTransport } from './transport/socket';\n\n/**\n * Chat services container\n */\nexport interface ChatServices {\n models: {\n Conversation: mongoose.Model<any>;\n Message: mongoose.Model<any>;\n };\n services: {\n conversations: ConversationsService;\n messages: MessagesService;\n };\n transport: Transport & { socket?: SocketTransport };\n}\n\n/**\n * Initialize the chat system\n * @param mongooseInstance Mongoose instance\n * @param options Initialization options\n * @param server Optional HTTP server for Socket.IO integration\n * @returns Chat services\n */\nexport async function initChat(\n mongooseInstance: typeof mongoose,\n options: InitOptions,\n server?: HTTPServer\n): Promise<ChatServices> {\n // Create models\n const models = createModels(mongooseInstance, options);\n\n // Ensure indexes are created\n await ensureIndexes(models);\n\n // Create transport system with optional Socket.IO support\n const transport = createTransport({\n redis: getRedisConfig(options) || undefined,\n socket: options.socket || undefined\n }, server);\n\n // Initialize Socket.IO if server was provided\n if (server && transport.socket) {\n // Set up authentication handler\n transport.socket.handleAuth(async (socket) => {\n // Get user info from socket handshake auth\n const userId = socket.handshake.auth.userId;\n const userModel = socket.handshake.auth.userModel || 'User';\n\n if (!userId || !options.participantModels.includes(userModel)) {\n return null;\n }\n\n return { id: userId, model: userModel };\n });\n\n // Initialize socket event handlers\n transport.socket.initialize();\n }\n\n // Create services with hooks\n const services = {\n conversations: createConversationsService(models),\n messages: createMessagesService(models, {\n onMessageCreated: (message) => {\n const payload = { type: 'message:created', message };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n message.conversationId.toString(),\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n message.conversationId.toString(),\n payload\n );\n },\n \n onMessageRead: (args) => {\n const payload = { \n type: 'conversation:read', \n conversationId: args.conversationId,\n messageId: args.messageId,\n participantModel: args.participantModel,\n participantId: args.participantId,\n at: args.at.toISOString()\n };\n \n // Fan out to local subscribers\n transport.fanoutLocal(\n args.conversationId,\n payload,\n (socketId, event) => {\n if (transport.socket) {\n const socket = transport.socket.getIO().sockets.sockets.get(socketId);\n if (socket) {\n socket.emit('chat-event', event);\n }\n }\n }\n );\n \n // Publish to Redis for cross-instance communication\n transport.publishToConversation(\n args.conversationId,\n payload\n );\n }\n })\n };\n\n return { models, services, transport };\n}\n\n// Re-export types\nexport * from './types';\nexport * from './utils';\nexport * from './models';\nexport * from './services';\nexport * from './transport';\nexport * from './config/env';"]}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import mongoose, { Schema, Document, Model } from 'mongoose';
|
|
1
|
+
import mongoose, { Schema, Document, Model, Connection } from 'mongoose';
|
|
2
2
|
import { InitOptions } from '../types';
|
|
3
3
|
|
|
4
4
|
export interface IParticipant {
|
|
@@ -20,34 +20,40 @@ export interface IConversation extends Document {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Create Conversation model
|
|
23
|
+
* Create (or retrieve) Conversation model safely
|
|
24
24
|
*/
|
|
25
|
-
export function createConversationModel(
|
|
25
|
+
export function createConversationModel(
|
|
26
|
+
options: InitOptions,
|
|
27
|
+
conn: Connection = mongoose.connection
|
|
28
|
+
): Model<IConversation> {
|
|
26
29
|
// Participant schema
|
|
27
|
-
const ParticipantSchema = new Schema(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
const ParticipantSchema = new Schema(
|
|
31
|
+
{
|
|
32
|
+
entityModel: {
|
|
33
|
+
type: String,
|
|
34
|
+
enum: options.participantModels,
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
entityId: {
|
|
38
|
+
type: String,
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
role: {
|
|
42
|
+
type: String,
|
|
43
|
+
enum: options.memberRoles,
|
|
44
|
+
required: true,
|
|
45
|
+
},
|
|
41
46
|
},
|
|
42
|
-
|
|
47
|
+
{ _id: false }
|
|
48
|
+
);
|
|
43
49
|
|
|
44
50
|
// Conversation schema
|
|
45
51
|
const ConversationSchema = new Schema<IConversation>(
|
|
46
52
|
{
|
|
47
|
-
organizationId: {
|
|
53
|
+
organizationId: {
|
|
48
54
|
type: String,
|
|
49
55
|
required: true,
|
|
50
|
-
index: true
|
|
56
|
+
index: true,
|
|
51
57
|
},
|
|
52
58
|
participants: {
|
|
53
59
|
type: [ParticipantSchema],
|
|
@@ -57,37 +63,30 @@ export function createConversationModel(options: InitOptions): Model<IConversati
|
|
|
57
63
|
message: 'A conversation must have exactly 2 participants',
|
|
58
64
|
},
|
|
59
65
|
},
|
|
60
|
-
lastMessageAt: {
|
|
66
|
+
lastMessageAt: {
|
|
61
67
|
type: Date,
|
|
62
|
-
index: true
|
|
68
|
+
index: true,
|
|
63
69
|
},
|
|
64
70
|
lastMessagePreview: String,
|
|
65
71
|
lastMessageSenderId: String,
|
|
66
72
|
lastMessageSenderModel: String,
|
|
67
|
-
metadata: Schema.Types.Mixed
|
|
73
|
+
metadata: Schema.Types.Mixed,
|
|
68
74
|
},
|
|
69
|
-
{
|
|
70
|
-
timestamps: true
|
|
71
|
-
}
|
|
75
|
+
{ timestamps: true }
|
|
72
76
|
);
|
|
73
77
|
|
|
74
|
-
//
|
|
75
|
-
ConversationSchema.index({
|
|
76
|
-
organizationId: 1,
|
|
77
|
-
'participants.entityId': 1
|
|
78
|
+
// Indexes
|
|
79
|
+
ConversationSchema.index({
|
|
80
|
+
organizationId: 1,
|
|
81
|
+
'participants.entityId': 1,
|
|
78
82
|
});
|
|
79
83
|
|
|
80
|
-
ConversationSchema.index({
|
|
81
|
-
organizationId: 1,
|
|
82
|
-
lastMessageAt: -1
|
|
84
|
+
ConversationSchema.index({
|
|
85
|
+
organizationId: 1,
|
|
86
|
+
lastMessageAt: -1,
|
|
83
87
|
});
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Create and return new model
|
|
92
|
-
return mongoose.model<IConversation>(modelName, ConversationSchema);
|
|
93
|
-
}
|
|
89
|
+
const name = 'Conversation';
|
|
90
|
+
// ✅ Return on the same line & reuse existing model to avoid OverwriteModelError
|
|
91
|
+
return (conn.models[name] as Model<IConversation>) || conn.model<IConversation>(name, ConversationSchema);
|
|
92
|
+
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import mongoose, { Schema, Document, Model } from 'mongoose';
|
|
1
|
+
import mongoose, { Schema, Document, Model, Connection } from 'mongoose';
|
|
2
2
|
import { InitOptions } from '../types';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Message document interface
|
|
6
|
-
*/
|
|
7
4
|
export interface IMessage extends Document {
|
|
8
|
-
organizationId:
|
|
9
|
-
conversationId:
|
|
5
|
+
organizationId: string;
|
|
6
|
+
conversationId: string;
|
|
10
7
|
senderModel: string;
|
|
11
|
-
senderId:
|
|
12
|
-
kind: 'text' | 'system';
|
|
8
|
+
senderId: string;
|
|
13
9
|
text: string;
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
kind: 'text' | 'system';
|
|
11
|
+
parentMessageId?: string;
|
|
12
|
+
rootThreadId?: string;
|
|
16
13
|
editedAt?: Date;
|
|
17
14
|
deletedAt?: Date;
|
|
18
15
|
createdAt: Date;
|
|
@@ -20,68 +17,68 @@ export interface IMessage extends Document {
|
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
|
-
* Create Message model
|
|
20
|
+
* Create (or retrieve) Message model safely
|
|
24
21
|
*/
|
|
25
|
-
export function createMessageModel(
|
|
22
|
+
export function createMessageModel(
|
|
23
|
+
options: InitOptions,
|
|
24
|
+
conn: Connection = mongoose.connection
|
|
25
|
+
): Model<IMessage> {
|
|
26
26
|
// Message schema
|
|
27
27
|
const MessageSchema = new Schema<IMessage>(
|
|
28
28
|
{
|
|
29
29
|
organizationId: {
|
|
30
|
-
type:
|
|
31
|
-
ref: 'Organization',
|
|
30
|
+
type: String,
|
|
32
31
|
required: true,
|
|
33
|
-
index: true
|
|
32
|
+
index: true,
|
|
34
33
|
},
|
|
35
34
|
conversationId: {
|
|
36
|
-
type:
|
|
37
|
-
ref: 'Conversation',
|
|
35
|
+
type: String,
|
|
38
36
|
required: true,
|
|
39
|
-
index: true
|
|
37
|
+
index: true,
|
|
40
38
|
},
|
|
41
39
|
senderModel: {
|
|
42
40
|
type: String,
|
|
43
41
|
enum: options.participantModels,
|
|
44
|
-
required: true
|
|
42
|
+
required: true,
|
|
45
43
|
},
|
|
46
44
|
senderId: {
|
|
47
|
-
type:
|
|
48
|
-
refPath: 'senderModel',
|
|
45
|
+
type: String,
|
|
49
46
|
required: true,
|
|
50
|
-
index: true
|
|
47
|
+
index: true,
|
|
51
48
|
},
|
|
52
49
|
kind: {
|
|
53
50
|
type: String,
|
|
54
51
|
enum: ['text', 'system'],
|
|
55
52
|
default: 'text',
|
|
56
|
-
index: true
|
|
53
|
+
index: true,
|
|
57
54
|
},
|
|
58
55
|
text: {
|
|
59
56
|
type: String,
|
|
60
|
-
trim: true
|
|
57
|
+
trim: true,
|
|
61
58
|
},
|
|
62
59
|
parentMessageId: {
|
|
63
|
-
type:
|
|
64
|
-
ref: 'Message'
|
|
60
|
+
type: String,
|
|
61
|
+
ref: 'Message',
|
|
65
62
|
},
|
|
66
63
|
rootThreadId: {
|
|
67
|
-
type:
|
|
68
|
-
ref: 'Message'
|
|
64
|
+
type: String,
|
|
65
|
+
ref: 'Message',
|
|
69
66
|
},
|
|
70
67
|
editedAt: {
|
|
71
|
-
type: Date
|
|
68
|
+
type: Date,
|
|
72
69
|
},
|
|
73
70
|
deletedAt: {
|
|
74
|
-
type: Date
|
|
75
|
-
}
|
|
71
|
+
type: Date,
|
|
72
|
+
},
|
|
76
73
|
},
|
|
77
74
|
{ timestamps: true }
|
|
78
75
|
);
|
|
79
76
|
|
|
80
|
-
//
|
|
77
|
+
// Indexes
|
|
81
78
|
MessageSchema.index({
|
|
82
79
|
organizationId: 1,
|
|
83
80
|
conversationId: 1,
|
|
84
|
-
createdAt: -1
|
|
81
|
+
createdAt: -1,
|
|
85
82
|
});
|
|
86
83
|
|
|
87
84
|
// Create text search index if enabled
|
|
@@ -90,11 +87,12 @@ export function createMessageModel(options: InitOptions): Model<IMessage> {
|
|
|
90
87
|
{ text: 'text' },
|
|
91
88
|
{
|
|
92
89
|
name: 'text_search',
|
|
93
|
-
weights: { text: 10 }
|
|
90
|
+
weights: { text: 10 },
|
|
94
91
|
}
|
|
95
92
|
);
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
const name = 'Message';
|
|
96
|
+
// ✅ Return on the same line & reuse existing model to avoid OverwriteModelError
|
|
97
|
+
return (conn.models[name] as Model<IMessage>) || conn.model<IMessage>(name, MessageSchema);
|
|
100
98
|
}
|
package/src/models/connection.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
1
|
+
import mongoose, { Connection } from 'mongoose';
|
|
2
2
|
import { InitOptions } from '../types';
|
|
3
3
|
import { createConversationModel } from './Conversation.model';
|
|
4
4
|
import { createMessageModel } from './Message.model';
|
|
@@ -10,9 +10,12 @@ import { createMessageModel } from './Message.model';
|
|
|
10
10
|
* @returns Object containing Conversation and Message models
|
|
11
11
|
*/
|
|
12
12
|
export function createModels(mongooseInstance: typeof mongoose, options: InitOptions) {
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
13
|
+
// Get the connection instance
|
|
14
|
+
const conn: Connection = mongooseInstance.connection;
|
|
15
|
+
|
|
16
|
+
// Create models with provided options and connection
|
|
17
|
+
const Conversation = createConversationModel(options, conn);
|
|
18
|
+
const Message = createMessageModel(options, conn);
|
|
16
19
|
|
|
17
20
|
return {
|
|
18
21
|
Conversation,
|