@librechat/data-schemas 0.0.8 → 0.0.11

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.cjs CHANGED
@@ -11,6 +11,7 @@ var winston = require('winston');
11
11
  require('winston-daily-rotate-file');
12
12
  var klona = require('klona');
13
13
  var traverse = require('traverse');
14
+ var nanoid = require('nanoid');
14
15
 
15
16
  async function signPayload({ payload, secret, expirationTime, }) {
16
17
  return jwt.sign(payload, secret, { expiresIn: expirationTime });
@@ -426,10 +427,23 @@ const conversationPreset = {
426
427
  max_tokens: {
427
428
  type: Number,
428
429
  },
429
- /** omni models only */
430
+ useResponsesApi: {
431
+ type: Boolean,
432
+ },
433
+ /** OpenAI Responses API / Anthropic API / Google API */
434
+ web_search: {
435
+ type: Boolean,
436
+ },
437
+ disableStreaming: {
438
+ type: Boolean,
439
+ },
440
+ /** Reasoning models only */
430
441
  reasoning_effort: {
431
442
  type: String,
432
443
  },
444
+ reasoning_summary: {
445
+ type: String,
446
+ },
433
447
  };
434
448
 
435
449
  const convoSchema = new mongoose.Schema({
@@ -870,7 +884,7 @@ const promptGroupSchema = new mongoose.Schema({
870
884
  validator: function (v) {
871
885
  return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v);
872
886
  },
873
- message: (props) => `${props.value} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`,
887
+ message: (props) => { var _a; return `${(_a = props === null || props === void 0 ? void 0 : props.value) !== null && _a !== void 0 ? _a : 'Value'} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`; },
874
888
  },
875
889
  maxlength: [
876
890
  librechatDataProvider.Constants.COMMANDS_MAX_LENGTH,
@@ -882,7 +896,9 @@ const promptGroupSchema = new mongoose.Schema({
882
896
  });
883
897
  promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
884
898
 
885
- // Create a sub-schema for permissions. Notice we disable _id for this subdocument.
899
+ /**
900
+ * Uses a sub-schema for permissions. Notice we disable `_id` for this subdocument.
901
+ */
886
902
  const rolePermissionsSchema = new mongoose.Schema({
887
903
  [librechatDataProvider.PermissionTypes.BOOKMARKS]: {
888
904
  [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
@@ -892,6 +908,13 @@ const rolePermissionsSchema = new mongoose.Schema({
892
908
  [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
893
909
  [librechatDataProvider.Permissions.CREATE]: { type: Boolean, default: true },
894
910
  },
911
+ [librechatDataProvider.PermissionTypes.MEMORIES]: {
912
+ [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
913
+ [librechatDataProvider.Permissions.CREATE]: { type: Boolean, default: true },
914
+ [librechatDataProvider.Permissions.UPDATE]: { type: Boolean, default: true },
915
+ [librechatDataProvider.Permissions.READ]: { type: Boolean, default: true },
916
+ [librechatDataProvider.Permissions.OPT_OUT]: { type: Boolean, default: true },
917
+ },
895
918
  [librechatDataProvider.PermissionTypes.AGENTS]: {
896
919
  [librechatDataProvider.Permissions.SHARED_GLOBAL]: { type: Boolean, default: false },
897
920
  [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
@@ -909,6 +932,9 @@ const rolePermissionsSchema = new mongoose.Schema({
909
932
  [librechatDataProvider.PermissionTypes.WEB_SEARCH]: {
910
933
  [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
911
934
  },
935
+ [librechatDataProvider.PermissionTypes.FILE_SEARCH]: {
936
+ [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
937
+ },
912
938
  }, { _id: false });
913
939
  const roleSchema = new mongoose.Schema({
914
940
  name: { type: String, required: true, unique: true, index: true },
@@ -921,6 +947,12 @@ const roleSchema = new mongoose.Schema({
921
947
  [librechatDataProvider.Permissions.USE]: true,
922
948
  [librechatDataProvider.Permissions.CREATE]: true,
923
949
  },
950
+ [librechatDataProvider.PermissionTypes.MEMORIES]: {
951
+ [librechatDataProvider.Permissions.USE]: true,
952
+ [librechatDataProvider.Permissions.CREATE]: true,
953
+ [librechatDataProvider.Permissions.UPDATE]: true,
954
+ [librechatDataProvider.Permissions.READ]: true,
955
+ },
924
956
  [librechatDataProvider.PermissionTypes.AGENTS]: {
925
957
  [librechatDataProvider.Permissions.SHARED_GLOBAL]: false,
926
958
  [librechatDataProvider.Permissions.USE]: true,
@@ -930,6 +962,7 @@ const roleSchema = new mongoose.Schema({
930
962
  [librechatDataProvider.PermissionTypes.TEMPORARY_CHAT]: { [librechatDataProvider.Permissions.USE]: true },
931
963
  [librechatDataProvider.PermissionTypes.RUN_CODE]: { [librechatDataProvider.Permissions.USE]: true },
932
964
  [librechatDataProvider.PermissionTypes.WEB_SEARCH]: { [librechatDataProvider.Permissions.USE]: true },
965
+ [librechatDataProvider.PermissionTypes.FILE_SEARCH]: { [librechatDataProvider.Permissions.USE]: true },
933
966
  }),
934
967
  },
935
968
  });
@@ -1198,8 +1231,46 @@ const userSchema = new mongoose.Schema({
1198
1231
  type: Boolean,
1199
1232
  default: false,
1200
1233
  },
1234
+ personalization: {
1235
+ type: {
1236
+ memories: {
1237
+ type: Boolean,
1238
+ default: true,
1239
+ },
1240
+ },
1241
+ default: {},
1242
+ },
1201
1243
  }, { timestamps: true });
1202
1244
 
1245
+ const MemoryEntrySchema = new mongoose.Schema({
1246
+ userId: {
1247
+ type: mongoose.Schema.Types.ObjectId,
1248
+ ref: 'User',
1249
+ index: true,
1250
+ required: true,
1251
+ },
1252
+ key: {
1253
+ type: String,
1254
+ required: true,
1255
+ validate: {
1256
+ validator: (v) => /^[a-z_]+$/.test(v),
1257
+ message: 'Key must only contain lowercase letters and underscores',
1258
+ },
1259
+ },
1260
+ value: {
1261
+ type: String,
1262
+ required: true,
1263
+ },
1264
+ tokenCount: {
1265
+ type: Number,
1266
+ default: 0,
1267
+ },
1268
+ updated_at: {
1269
+ type: Date,
1270
+ default: Date.now,
1271
+ },
1272
+ });
1273
+
1203
1274
  /**
1204
1275
  * Creates or returns the User model using the provided mongoose instance and schema
1205
1276
  */
@@ -1228,7 +1299,7 @@ function createBalanceModel(mongoose) {
1228
1299
  return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
1229
1300
  }
1230
1301
 
1231
- const logDir$1 = path.join(__dirname, '..', 'logs');
1302
+ const logDir$1 = path.join(__dirname, '..', '..', '..', 'api', 'logs');
1232
1303
  const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1 = 'false' } = process.env;
1233
1304
  const useDebugLogging$1 = (typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true') ||
1234
1305
  DEBUG_LOGGING$1 === 'true';
@@ -1286,6 +1357,13 @@ const searchEnabled = process.env.SEARCH != null && process.env.SEARCH.toLowerCa
1286
1357
  * Flag to indicate if MeiliSearch is enabled based on required environment variables.
1287
1358
  */
1288
1359
  const meiliEnabled = process.env.MEILI_HOST != null && process.env.MEILI_MASTER_KEY != null && searchEnabled;
1360
+ /**
1361
+ * Get sync configuration from environment variables
1362
+ */
1363
+ const getSyncConfig = () => ({
1364
+ batchSize: parseInt(process.env.MEILI_SYNC_BATCH_SIZE || '100', 10),
1365
+ delayMs: parseInt(process.env.MEILI_SYNC_DELAY_MS || '100', 10),
1366
+ });
1289
1367
  /**
1290
1368
  * Local implementation of parseTextParts to avoid dependency on librechat-data-provider
1291
1369
  * Extracts text content from an array of content items
@@ -1317,6 +1395,19 @@ const validateOptions = (options) => {
1317
1395
  }
1318
1396
  });
1319
1397
  };
1398
+ /**
1399
+ * Helper function to process documents in batches with rate limiting
1400
+ */
1401
+ const processBatch = async (items, batchSize, delayMs, processor) => {
1402
+ for (let i = 0; i < items.length; i += batchSize) {
1403
+ const batch = items.slice(i, i + batchSize);
1404
+ await processor(batch);
1405
+ // Add delay between batches to prevent overwhelming resources
1406
+ if (i + batchSize < items.length && delayMs > 0) {
1407
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1408
+ }
1409
+ }
1410
+ };
1320
1411
  /**
1321
1412
  * Factory function to create a MeiliMongooseModel class which extends a Mongoose model.
1322
1413
  * This class contains static and instance methods to synchronize and manage the MeiliSearch index
@@ -1325,97 +1416,148 @@ const validateOptions = (options) => {
1325
1416
  * @param config - Configuration object.
1326
1417
  * @param config.index - The MeiliSearch index object.
1327
1418
  * @param config.attributesToIndex - List of attributes to index.
1419
+ * @param config.syncOptions - Sync configuration options.
1328
1420
  * @returns A class definition that will be loaded into the Mongoose schema.
1329
1421
  */
1330
- const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1422
+ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) => {
1331
1423
  const primaryKey = attributesToIndex[0];
1424
+ const syncConfig = { ...getSyncConfig(), ...syncOptions };
1332
1425
  class MeiliMongooseModel {
1426
+ /**
1427
+ * Get the current sync progress
1428
+ */
1429
+ static async getSyncProgress() {
1430
+ const totalDocuments = await this.countDocuments();
1431
+ const indexedDocuments = await this.countDocuments({ _meiliIndex: true });
1432
+ return {
1433
+ totalProcessed: indexedDocuments,
1434
+ totalDocuments,
1435
+ isComplete: indexedDocuments === totalDocuments,
1436
+ };
1437
+ }
1333
1438
  /**
1334
1439
  * Synchronizes the data between the MongoDB collection and the MeiliSearch index.
1335
- *
1336
- * The synchronization process involves:
1337
- * 1. Fetching all documents from the MongoDB collection and MeiliSearch index.
1338
- * 2. Comparing documents from both sources.
1339
- * 3. Deleting documents from MeiliSearch that no longer exist in MongoDB.
1340
- * 4. Adding documents to MeiliSearch that exist in MongoDB but not in the index.
1341
- * 5. Updating documents in MeiliSearch if key fields (such as `text` or `title`) differ.
1342
- * 6. Updating the `_meiliIndex` field in MongoDB to indicate the indexing status.
1343
- *
1344
- * Note: The function processes documents in batches because MeiliSearch's
1345
- * `index.getDocuments` requires an exact limit and `index.addDocuments` does not handle
1346
- * partial failures in a batch.
1347
- *
1348
- * @returns {Promise<void>} Resolves when the synchronization is complete.
1440
+ * Now uses streaming and batching to reduce memory usage.
1349
1441
  */
1350
- static async syncWithMeili() {
1442
+ static async syncWithMeili(options) {
1351
1443
  try {
1352
- let moreDocuments = true;
1353
- const mongoDocuments = await this.find().lean();
1444
+ const startTime = Date.now();
1445
+ const { batchSize, delayMs } = syncConfig;
1446
+ logger$1.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
1447
+ // Build query with resume capability
1448
+ const query = {};
1449
+ if (options === null || options === void 0 ? void 0 : options.resumeFromId) {
1450
+ query._id = { $gt: options.resumeFromId };
1451
+ }
1452
+ // Get total count for progress tracking
1453
+ const totalCount = await this.countDocuments(query);
1454
+ let processedCount = 0;
1455
+ // First, handle documents that need to be removed from Meili
1456
+ await this.cleanupMeiliIndex(index, primaryKey, batchSize, delayMs);
1457
+ // Process MongoDB documents in batches using cursor
1458
+ const cursor = this.find(query)
1459
+ .select(attributesToIndex.join(' ') + ' _meiliIndex')
1460
+ .sort({ _id: 1 })
1461
+ .batchSize(batchSize)
1462
+ .cursor();
1354
1463
  const format = (doc) => _.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
1355
- const mongoMap = new Map(mongoDocuments.map((doc) => {
1356
- const typedDoc = doc;
1357
- return [typedDoc[primaryKey], format(typedDoc)];
1358
- }));
1359
- const indexMap = new Map();
1464
+ let documentBatch = [];
1465
+ let updateOps = [];
1466
+ // Process documents in streaming fashion
1467
+ for await (const doc of cursor) {
1468
+ const typedDoc = doc.toObject();
1469
+ const formatted = format(typedDoc);
1470
+ // Check if document needs indexing
1471
+ if (!typedDoc._meiliIndex) {
1472
+ documentBatch.push(formatted);
1473
+ updateOps.push({
1474
+ updateOne: {
1475
+ filter: { _id: typedDoc._id },
1476
+ update: { $set: { _meiliIndex: true } },
1477
+ },
1478
+ });
1479
+ }
1480
+ processedCount++;
1481
+ // Process batch when it reaches the configured size
1482
+ if (documentBatch.length >= batchSize) {
1483
+ await this.processSyncBatch(index, documentBatch, updateOps);
1484
+ documentBatch = [];
1485
+ updateOps = [];
1486
+ // Log progress
1487
+ const progress = Math.round((processedCount / totalCount) * 100);
1488
+ logger$1.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
1489
+ // Add delay to prevent overwhelming resources
1490
+ if (delayMs > 0) {
1491
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1492
+ }
1493
+ }
1494
+ }
1495
+ // Process remaining documents
1496
+ if (documentBatch.length > 0) {
1497
+ await this.processSyncBatch(index, documentBatch, updateOps);
1498
+ }
1499
+ const duration = Date.now() - startTime;
1500
+ logger$1.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
1501
+ }
1502
+ catch (error) {
1503
+ logger$1.error('[syncWithMeili] Error during sync:', error);
1504
+ throw error;
1505
+ }
1506
+ }
1507
+ /**
1508
+ * Process a batch of documents for syncing
1509
+ */
1510
+ static async processSyncBatch(index, documents, updateOps) {
1511
+ if (documents.length === 0) {
1512
+ return;
1513
+ }
1514
+ try {
1515
+ // Add documents to MeiliSearch
1516
+ await index.addDocuments(documents);
1517
+ // Update MongoDB to mark documents as indexed
1518
+ if (updateOps.length > 0) {
1519
+ await this.collection.bulkWrite(updateOps);
1520
+ }
1521
+ }
1522
+ catch (error) {
1523
+ logger$1.error('[processSyncBatch] Error processing batch:', error);
1524
+ // Don't throw - allow sync to continue with other documents
1525
+ }
1526
+ }
1527
+ /**
1528
+ * Clean up documents in MeiliSearch that no longer exist in MongoDB
1529
+ */
1530
+ static async cleanupMeiliIndex(index, primaryKey, batchSize, delayMs) {
1531
+ try {
1360
1532
  let offset = 0;
1361
- const batchSize = 1000;
1533
+ let moreDocuments = true;
1362
1534
  while (moreDocuments) {
1363
1535
  const batch = await index.getDocuments({ limit: batchSize, offset });
1364
1536
  if (batch.results.length === 0) {
1365
1537
  moreDocuments = false;
1538
+ break;
1366
1539
  }
1367
- for (const doc of batch.results) {
1368
- indexMap.set(doc[primaryKey], format(doc));
1540
+ const meiliIds = batch.results.map((doc) => doc[primaryKey]);
1541
+ const query = {};
1542
+ query[primaryKey] = { $in: meiliIds };
1543
+ // Find which documents exist in MongoDB
1544
+ const existingDocs = await this.find(query).select(primaryKey).lean();
1545
+ const existingIds = new Set(existingDocs.map((doc) => doc[primaryKey]));
1546
+ // Delete documents that don't exist in MongoDB
1547
+ const toDelete = meiliIds.filter((id) => !existingIds.has(id));
1548
+ if (toDelete.length > 0) {
1549
+ await Promise.all(toDelete.map((id) => index.deleteDocument(id)));
1550
+ logger$1.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
1369
1551
  }
1370
1552
  offset += batchSize;
1371
- }
1372
- logger$1.debug('[syncWithMeili]', { indexMap: indexMap.size, mongoMap: mongoMap.size });
1373
- const updateOps = [];
1374
- // Process documents present in the MeiliSearch index
1375
- for (const [id, doc] of indexMap) {
1376
- const update = {};
1377
- update[primaryKey] = id;
1378
- if (mongoMap.has(id)) {
1379
- const mongoDoc = mongoMap.get(id);
1380
- if ((doc.text && doc.text !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.text)) ||
1381
- (doc.title && doc.title !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.title))) {
1382
- logger$1.debug(`[syncWithMeili] ${id} had document discrepancy in ${doc.text ? 'text' : 'title'} field`);
1383
- updateOps.push({
1384
- updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
1385
- });
1386
- await index.addDocuments([doc]);
1387
- }
1388
- }
1389
- else {
1390
- await index.deleteDocument(id);
1391
- updateOps.push({
1392
- updateOne: { filter: update, update: { $set: { _meiliIndex: false } } },
1393
- });
1394
- }
1395
- }
1396
- // Process documents present in MongoDB
1397
- for (const [id, doc] of mongoMap) {
1398
- const update = {};
1399
- update[primaryKey] = id;
1400
- if (!indexMap.has(id)) {
1401
- await index.addDocuments([doc]);
1402
- updateOps.push({
1403
- updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
1404
- });
1405
- }
1406
- else if (doc._meiliIndex === false) {
1407
- updateOps.push({
1408
- updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
1409
- });
1553
+ // Add delay between batches
1554
+ if (delayMs > 0) {
1555
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1410
1556
  }
1411
1557
  }
1412
- if (updateOps.length > 0) {
1413
- await this.collection.bulkWrite(updateOps);
1414
- logger$1.debug(`[syncWithMeili] Finished indexing ${primaryKey === 'messageId' ? 'messages' : 'conversations'}`);
1415
- }
1416
1558
  }
1417
1559
  catch (error) {
1418
- logger$1.error('[syncWithMeili] Error adding document to Meili', error);
1560
+ logger$1.error('[cleanupMeiliIndex] Error during cleanup:', error);
1419
1561
  }
1420
1562
  }
1421
1563
  /**
@@ -1471,32 +1613,64 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1471
1613
  return object;
1472
1614
  }
1473
1615
  /**
1474
- * Adds the current document to the MeiliSearch index
1616
+ * Adds the current document to the MeiliSearch index with retry logic
1475
1617
  */
1476
- async addObjectToMeili() {
1618
+ async addObjectToMeili(next) {
1477
1619
  const object = this.preprocessObjectForIndex();
1620
+ const maxRetries = 3;
1621
+ let retryCount = 0;
1622
+ while (retryCount < maxRetries) {
1623
+ try {
1624
+ await index.addDocuments([object]);
1625
+ break;
1626
+ }
1627
+ catch (error) {
1628
+ retryCount++;
1629
+ if (retryCount >= maxRetries) {
1630
+ logger$1.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
1631
+ return next();
1632
+ }
1633
+ // Exponential backoff
1634
+ await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
1635
+ }
1636
+ }
1478
1637
  try {
1479
- await index.addDocuments([object]);
1638
+ await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
1480
1639
  }
1481
1640
  catch (error) {
1482
- logger$1.error('[addObjectToMeili] Error adding document to Meili', error);
1641
+ logger$1.error('[addObjectToMeili] Error updating _meiliIndex field:', error);
1642
+ return next();
1483
1643
  }
1484
- await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
1644
+ next();
1485
1645
  }
1486
1646
  /**
1487
1647
  * Updates the current document in the MeiliSearch index
1488
1648
  */
1489
- async updateObjectToMeili() {
1490
- const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
1491
- await index.updateDocuments([object]);
1649
+ async updateObjectToMeili(next) {
1650
+ try {
1651
+ const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
1652
+ await index.updateDocuments([object]);
1653
+ next();
1654
+ }
1655
+ catch (error) {
1656
+ logger$1.error('[updateObjectToMeili] Error updating document in Meili:', error);
1657
+ return next();
1658
+ }
1492
1659
  }
1493
1660
  /**
1494
1661
  * Deletes the current document from the MeiliSearch index.
1495
1662
  *
1496
1663
  * @returns {Promise<void>}
1497
1664
  */
1498
- async deleteObjectFromMeili() {
1499
- await index.deleteDocument(this._id);
1665
+ async deleteObjectFromMeili(next) {
1666
+ try {
1667
+ await index.deleteDocument(this._id);
1668
+ next();
1669
+ }
1670
+ catch (error) {
1671
+ logger$1.error('[deleteObjectFromMeili] Error deleting document from Meili:', error);
1672
+ return next();
1673
+ }
1500
1674
  }
1501
1675
  /**
1502
1676
  * Post-save hook to synchronize the document with MeiliSearch.
@@ -1504,12 +1678,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1504
1678
  * If the document is already indexed (i.e. `_meiliIndex` is true), it updates it;
1505
1679
  * otherwise, it adds the document to the index.
1506
1680
  */
1507
- postSaveHook() {
1681
+ postSaveHook(next) {
1508
1682
  if (this._meiliIndex) {
1509
- this.updateObjectToMeili();
1683
+ this.updateObjectToMeili(next);
1510
1684
  }
1511
1685
  else {
1512
- this.addObjectToMeili();
1686
+ this.addObjectToMeili(next);
1513
1687
  }
1514
1688
  }
1515
1689
  /**
@@ -1518,9 +1692,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1518
1692
  * This hook is triggered after a document update, ensuring that changes are
1519
1693
  * propagated to the MeiliSearch index if the document is indexed.
1520
1694
  */
1521
- postUpdateHook() {
1695
+ postUpdateHook(next) {
1522
1696
  if (this._meiliIndex) {
1523
- this.updateObjectToMeili();
1697
+ this.updateObjectToMeili(next);
1698
+ }
1699
+ else {
1700
+ next();
1524
1701
  }
1525
1702
  }
1526
1703
  /**
@@ -1529,9 +1706,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1529
1706
  * This hook is triggered after a document is removed, ensuring that the document
1530
1707
  * is also removed from the MeiliSearch index if it was previously indexed.
1531
1708
  */
1532
- postRemoveHook() {
1709
+ postRemoveHook(next) {
1533
1710
  if (this._meiliIndex) {
1534
- this.deleteObjectFromMeili();
1711
+ this.deleteObjectFromMeili(next);
1712
+ }
1713
+ else {
1714
+ next();
1535
1715
  }
1536
1716
  }
1537
1717
  }
@@ -1553,6 +1733,8 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1553
1733
  * @param options.apiKey - The MeiliSearch API key.
1554
1734
  * @param options.indexName - The name of the MeiliSearch index.
1555
1735
  * @param options.primaryKey - The primary key field for indexing.
1736
+ * @param options.syncBatchSize - Batch size for sync operations.
1737
+ * @param options.syncDelayMs - Delay between batches in milliseconds.
1556
1738
  */
1557
1739
  function mongoMeili(schema, options) {
1558
1740
  const mongoose = options.mongoose;
@@ -1567,9 +1749,37 @@ function mongoMeili(schema, options) {
1567
1749
  },
1568
1750
  });
1569
1751
  const { host, apiKey, indexName, primaryKey } = options;
1752
+ const syncOptions = {
1753
+ batchSize: options.syncBatchSize || getSyncConfig().batchSize,
1754
+ delayMs: options.syncDelayMs || getSyncConfig().delayMs,
1755
+ };
1570
1756
  const client = new meilisearch.MeiliSearch({ host, apiKey });
1571
- client.createIndex(indexName, { primaryKey });
1757
+ /** Create index only if it doesn't exist */
1572
1758
  const index = client.index(indexName);
1759
+ // Check if index exists and create if needed
1760
+ (async () => {
1761
+ try {
1762
+ await index.getRawInfo();
1763
+ logger$1.debug(`[mongoMeili] Index ${indexName} already exists`);
1764
+ }
1765
+ catch (error) {
1766
+ const errorCode = error === null || error === void 0 ? void 0 : error.code;
1767
+ if (errorCode === 'index_not_found') {
1768
+ try {
1769
+ logger$1.info(`[mongoMeili] Creating new index: ${indexName}`);
1770
+ await client.createIndex(indexName, { primaryKey });
1771
+ logger$1.info(`[mongoMeili] Successfully created index: ${indexName}`);
1772
+ }
1773
+ catch (createError) {
1774
+ // Index might have been created by another instance
1775
+ logger$1.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
1776
+ }
1777
+ }
1778
+ else {
1779
+ logger$1.error(`[mongoMeili] Error checking index ${indexName}:`, error);
1780
+ }
1781
+ }
1782
+ })();
1573
1783
  // Collect attributes from the schema that should be indexed
1574
1784
  const attributesToIndex = [
1575
1785
  ...Object.entries(schema.obj).reduce((results, [key, value]) => {
@@ -1577,19 +1787,19 @@ function mongoMeili(schema, options) {
1577
1787
  return schemaValue.meiliIndex ? [...results, key] : results;
1578
1788
  }, []),
1579
1789
  ];
1580
- schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex }));
1790
+ schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex, syncOptions }));
1581
1791
  // Register Mongoose hooks
1582
- schema.post('save', function (doc) {
1792
+ schema.post('save', function (doc, next) {
1583
1793
  var _a;
1584
- (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1794
+ (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1585
1795
  });
1586
- schema.post('updateOne', function (doc) {
1796
+ schema.post('updateOne', function (doc, next) {
1587
1797
  var _a;
1588
- (_a = doc.postUpdateHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1798
+ (_a = doc.postUpdateHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1589
1799
  });
1590
- schema.post('deleteOne', function (doc) {
1800
+ schema.post('deleteOne', function (doc, next) {
1591
1801
  var _a;
1592
- (_a = doc.postRemoveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1802
+ (_a = doc.postRemoveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1593
1803
  });
1594
1804
  // Pre-deleteMany hook: remove corresponding documents from MeiliSearch when multiple documents are deleted.
1595
1805
  schema.pre('deleteMany', async function (next) {
@@ -1598,41 +1808,50 @@ function mongoMeili(schema, options) {
1598
1808
  }
1599
1809
  try {
1600
1810
  const conditions = this.getQuery();
1811
+ const { batchSize, delayMs } = syncOptions;
1601
1812
  if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
1602
1813
  const convoIndex = client.index('convos');
1603
1814
  const deletedConvos = await mongoose
1604
1815
  .model('Conversation')
1605
1816
  .find(conditions)
1817
+ .select('conversationId')
1606
1818
  .lean();
1607
- const promises = deletedConvos.map((convo) => convoIndex.deleteDocument(convo.conversationId));
1608
- await Promise.all(promises);
1819
+ // Process deletions in batches
1820
+ await processBatch(deletedConvos, batchSize, delayMs, async (batch) => {
1821
+ const promises = batch.map((convo) => convoIndex.deleteDocument(convo.conversationId));
1822
+ await Promise.all(promises);
1823
+ });
1609
1824
  }
1610
1825
  if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
1611
1826
  const messageIndex = client.index('messages');
1612
1827
  const deletedMessages = await mongoose
1613
1828
  .model('Message')
1614
1829
  .find(conditions)
1830
+ .select('messageId')
1615
1831
  .lean();
1616
- const promises = deletedMessages.map((message) => messageIndex.deleteDocument(message.messageId));
1617
- await Promise.all(promises);
1832
+ // Process deletions in batches
1833
+ await processBatch(deletedMessages, batchSize, delayMs, async (batch) => {
1834
+ const promises = batch.map((message) => messageIndex.deleteDocument(message.messageId));
1835
+ await Promise.all(promises);
1836
+ });
1618
1837
  }
1619
1838
  return next();
1620
1839
  }
1621
1840
  catch (error) {
1622
1841
  if (meiliEnabled) {
1623
- logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may be slow due to syncing.', error);
1842
+ logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
1624
1843
  }
1625
1844
  return next();
1626
1845
  }
1627
1846
  });
1628
1847
  // Post-findOneAndUpdate hook
1629
- schema.post('findOneAndUpdate', async function (doc) {
1848
+ schema.post('findOneAndUpdate', async function (doc, next) {
1630
1849
  var _a;
1631
1850
  if (!meiliEnabled) {
1632
- return;
1851
+ return next();
1633
1852
  }
1634
1853
  if (doc.unfinished) {
1635
- return;
1854
+ return next();
1636
1855
  }
1637
1856
  let meiliDoc;
1638
1857
  if (doc.messages) {
@@ -1645,9 +1864,9 @@ function mongoMeili(schema, options) {
1645
1864
  }
1646
1865
  }
1647
1866
  if (meiliDoc && meiliDoc.title === doc.title) {
1648
- return;
1867
+ return next();
1649
1868
  }
1650
- (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1869
+ (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1651
1870
  });
1652
1871
  }
1653
1872
 
@@ -1798,6 +2017,10 @@ function createToolCallModel(mongoose) {
1798
2017
  return mongoose.models.ToolCall || mongoose.model('ToolCall', toolCallSchema);
1799
2018
  }
1800
2019
 
2020
+ function createMemoryModel(mongoose) {
2021
+ return mongoose.models.MemoryEntry || mongoose.model('MemoryEntry', MemoryEntrySchema);
2022
+ }
2023
+
1801
2024
  /**
1802
2025
  * Creates all database models for all collections
1803
2026
  */
@@ -1825,6 +2048,7 @@ function createModels(mongoose) {
1825
2048
  ConversationTag: createConversationTagModel(mongoose),
1826
2049
  SharedLink: createSharedLinkModel(mongoose),
1827
2050
  ToolCall: createToolCallModel(mongoose),
2051
+ MemoryEntry: createMemoryModel(mongoose),
1828
2052
  };
1829
2053
  }
1830
2054
 
@@ -1960,6 +2184,28 @@ function createUserMethods(mongoose) {
1960
2184
  expirationTime: expires / 1000,
1961
2185
  });
1962
2186
  }
2187
+ /**
2188
+ * Update a user's personalization memories setting.
2189
+ * Handles the edge case where the personalization object doesn't exist.
2190
+ */
2191
+ async function toggleUserMemories(userId, memoriesEnabled) {
2192
+ const User = mongoose.models.User;
2193
+ // First, ensure the personalization object exists
2194
+ const user = await User.findById(userId);
2195
+ if (!user) {
2196
+ return null;
2197
+ }
2198
+ // Use $set to update the nested field, which will create the personalization object if it doesn't exist
2199
+ const updateOperation = {
2200
+ $set: {
2201
+ 'personalization.memories': memoriesEnabled,
2202
+ },
2203
+ };
2204
+ return (await User.findByIdAndUpdate(userId, updateOperation, {
2205
+ new: true,
2206
+ runValidators: true,
2207
+ }).lean());
2208
+ }
1963
2209
  // Return all methods
1964
2210
  return {
1965
2211
  findUser,
@@ -1969,6 +2215,7 @@ function createUserMethods(mongoose) {
1969
2215
  getUserById,
1970
2216
  deleteUserById,
1971
2217
  generateToken,
2218
+ toggleUserMemories,
1972
2219
  };
1973
2220
  }
1974
2221
 
@@ -2173,14 +2420,11 @@ const jsonTruncateFormat = winston.format((info) => {
2173
2420
  return truncateObject(info);
2174
2421
  });
2175
2422
 
2176
- // Define log directory
2177
- const logDir = path.join(__dirname, '..', 'logs');
2178
- // Type-safe environment variables
2423
+ const logDir = path.join(__dirname, '..', '..', '..', 'api', 'logs');
2179
2424
  const { NODE_ENV, DEBUG_LOGGING, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
2180
2425
  const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
2181
2426
  const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
2182
2427
  const useDebugLogging = typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true';
2183
- // Define custom log levels
2184
2428
  const levels = {
2185
2429
  error: 0,
2186
2430
  warn: 1,
@@ -2260,7 +2504,7 @@ const logger = winston.createLogger({
2260
2504
  transports,
2261
2505
  });
2262
2506
 
2263
- var _a, _b;
2507
+ var _a;
2264
2508
  class SessionError extends Error {
2265
2509
  constructor(message, code = 'SESSION_ERROR') {
2266
2510
  super(message);
@@ -2269,10 +2513,9 @@ class SessionError extends Error {
2269
2513
  }
2270
2514
  }
2271
2515
  const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
2272
- const expires = (_b = eval(REFRESH_TOKEN_EXPIRY !== null && REFRESH_TOKEN_EXPIRY !== void 0 ? REFRESH_TOKEN_EXPIRY : '0')) !== null && _b !== void 0 ? _b : 1000 * 60 * 60 * 24 * 7; // 7 days default
2516
+ const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
2273
2517
  // Factory function that takes mongoose instance and returns the methods
2274
2518
  function createSessionMethods(mongoose) {
2275
- const Session = mongoose.models.Session;
2276
2519
  /**
2277
2520
  * Creates a new session for a user
2278
2521
  */
@@ -2281,12 +2524,13 @@ function createSessionMethods(mongoose) {
2281
2524
  throw new SessionError('User ID is required', 'INVALID_USER_ID');
2282
2525
  }
2283
2526
  try {
2284
- const session = new Session({
2527
+ const Session = mongoose.models.Session;
2528
+ const currentSession = new Session({
2285
2529
  user: userId,
2286
2530
  expiration: options.expiration || new Date(Date.now() + expires),
2287
2531
  });
2288
- const refreshToken = await generateRefreshToken(session);
2289
- return { session, refreshToken };
2532
+ const refreshToken = await generateRefreshToken(currentSession);
2533
+ return { session: currentSession, refreshToken };
2290
2534
  }
2291
2535
  catch (error) {
2292
2536
  logger.error('[createSession] Error creating session:', error);
@@ -2298,6 +2542,7 @@ function createSessionMethods(mongoose) {
2298
2542
  */
2299
2543
  async function findSession(params, options = { lean: true }) {
2300
2544
  try {
2545
+ const Session = mongoose.models.Session;
2301
2546
  const query = {};
2302
2547
  if (!params.refreshToken && !params.userId && !params.sessionId) {
2303
2548
  throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
@@ -2338,6 +2583,7 @@ function createSessionMethods(mongoose) {
2338
2583
  */
2339
2584
  async function updateExpiration(session, newExpiration) {
2340
2585
  try {
2586
+ const Session = mongoose.models.Session;
2341
2587
  const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
2342
2588
  if (!sessionDoc) {
2343
2589
  throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
@@ -2355,6 +2601,7 @@ function createSessionMethods(mongoose) {
2355
2601
  */
2356
2602
  async function deleteSession(params) {
2357
2603
  try {
2604
+ const Session = mongoose.models.Session;
2358
2605
  if (!params.refreshToken && !params.sessionId) {
2359
2606
  throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
2360
2607
  }
@@ -2381,6 +2628,7 @@ function createSessionMethods(mongoose) {
2381
2628
  */
2382
2629
  async function deleteAllUserSessions(userId, options = {}) {
2383
2630
  try {
2631
+ const Session = mongoose.models.Session;
2384
2632
  if (!userId) {
2385
2633
  throw new SessionError('User ID is required', 'INVALID_USER_ID');
2386
2634
  }
@@ -2437,6 +2685,7 @@ function createSessionMethods(mongoose) {
2437
2685
  */
2438
2686
  async function countActiveSessions(userId) {
2439
2687
  try {
2688
+ const Session = mongoose.models.Session;
2440
2689
  if (!userId) {
2441
2690
  throw new SessionError('User ID is required', 'INVALID_USER_ID');
2442
2691
  }
@@ -2599,6 +2848,591 @@ function createRoleMethods(mongoose) {
2599
2848
  };
2600
2849
  }
2601
2850
 
2851
+ /**
2852
+ * Formats a date in YYYY-MM-DD format
2853
+ */
2854
+ const formatDate = (date) => {
2855
+ return date.toISOString().split('T')[0];
2856
+ };
2857
+ // Factory function that takes mongoose instance and returns the methods
2858
+ function createMemoryMethods(mongoose) {
2859
+ /**
2860
+ * Creates a new memory entry for a user
2861
+ * Throws an error if a memory with the same key already exists
2862
+ */
2863
+ async function createMemory({ userId, key, value, tokenCount = 0, }) {
2864
+ try {
2865
+ if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
2866
+ return { ok: false };
2867
+ }
2868
+ const MemoryEntry = mongoose.models.MemoryEntry;
2869
+ const existingMemory = await MemoryEntry.findOne({ userId, key });
2870
+ if (existingMemory) {
2871
+ throw new Error('Memory with this key already exists');
2872
+ }
2873
+ await MemoryEntry.create({
2874
+ userId,
2875
+ key,
2876
+ value,
2877
+ tokenCount,
2878
+ updated_at: new Date(),
2879
+ });
2880
+ return { ok: true };
2881
+ }
2882
+ catch (error) {
2883
+ throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2884
+ }
2885
+ }
2886
+ /**
2887
+ * Sets or updates a memory entry for a user
2888
+ */
2889
+ async function setMemory({ userId, key, value, tokenCount = 0, }) {
2890
+ try {
2891
+ if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
2892
+ return { ok: false };
2893
+ }
2894
+ const MemoryEntry = mongoose.models.MemoryEntry;
2895
+ await MemoryEntry.findOneAndUpdate({ userId, key }, {
2896
+ value,
2897
+ tokenCount,
2898
+ updated_at: new Date(),
2899
+ }, {
2900
+ upsert: true,
2901
+ new: true,
2902
+ });
2903
+ return { ok: true };
2904
+ }
2905
+ catch (error) {
2906
+ throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2907
+ }
2908
+ }
2909
+ /**
2910
+ * Deletes a specific memory entry for a user
2911
+ */
2912
+ async function deleteMemory({ userId, key }) {
2913
+ try {
2914
+ const MemoryEntry = mongoose.models.MemoryEntry;
2915
+ const result = await MemoryEntry.findOneAndDelete({ userId, key });
2916
+ return { ok: !!result };
2917
+ }
2918
+ catch (error) {
2919
+ throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2920
+ }
2921
+ }
2922
+ /**
2923
+ * Gets all memory entries for a user
2924
+ */
2925
+ async function getAllUserMemories(userId) {
2926
+ try {
2927
+ const MemoryEntry = mongoose.models.MemoryEntry;
2928
+ return (await MemoryEntry.find({ userId }).lean());
2929
+ }
2930
+ catch (error) {
2931
+ throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
2932
+ }
2933
+ }
2934
+ /**
2935
+ * Gets and formats all memories for a user in two different formats
2936
+ */
2937
+ async function getFormattedMemories({ userId, }) {
2938
+ try {
2939
+ const memories = await getAllUserMemories(userId);
2940
+ if (!memories || memories.length === 0) {
2941
+ return { withKeys: '', withoutKeys: '', totalTokens: 0 };
2942
+ }
2943
+ const sortedMemories = memories.sort((a, b) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime());
2944
+ const totalTokens = sortedMemories.reduce((sum, memory) => {
2945
+ return sum + (memory.tokenCount || 0);
2946
+ }, 0);
2947
+ const withKeys = sortedMemories
2948
+ .map((memory, index) => {
2949
+ const date = formatDate(new Date(memory.updated_at));
2950
+ const tokenInfo = memory.tokenCount ? ` [${memory.tokenCount} tokens]` : '';
2951
+ return `${index + 1}. [${date}]. ["key": "${memory.key}"]${tokenInfo}. ["value": "${memory.value}"]`;
2952
+ })
2953
+ .join('\n\n');
2954
+ const withoutKeys = sortedMemories
2955
+ .map((memory, index) => {
2956
+ const date = formatDate(new Date(memory.updated_at));
2957
+ return `${index + 1}. [${date}]. ${memory.value}`;
2958
+ })
2959
+ .join('\n\n');
2960
+ return { withKeys, withoutKeys, totalTokens };
2961
+ }
2962
+ catch (error) {
2963
+ logger.error('Failed to get formatted memories:', error);
2964
+ return { withKeys: '', withoutKeys: '', totalTokens: 0 };
2965
+ }
2966
+ }
2967
+ return {
2968
+ setMemory,
2969
+ createMemory,
2970
+ deleteMemory,
2971
+ getAllUserMemories,
2972
+ getFormattedMemories,
2973
+ };
2974
+ }
2975
+
2976
+ class ShareServiceError extends Error {
2977
+ constructor(message, code) {
2978
+ super(message);
2979
+ this.name = 'ShareServiceError';
2980
+ this.code = code;
2981
+ }
2982
+ }
2983
+ function memoizedAnonymizeId(prefix) {
2984
+ const memo = new Map();
2985
+ return (id) => {
2986
+ if (!memo.has(id)) {
2987
+ memo.set(id, `${prefix}_${nanoid.nanoid()}`);
2988
+ }
2989
+ return memo.get(id);
2990
+ };
2991
+ }
2992
+ const anonymizeConvoId = memoizedAnonymizeId('convo');
2993
+ const anonymizeAssistantId = memoizedAnonymizeId('a');
2994
+ const anonymizeMessageId = (id) => id === librechatDataProvider.Constants.NO_PARENT ? id : memoizedAnonymizeId('msg')(id);
2995
+ function anonymizeConvo(conversation) {
2996
+ if (!conversation) {
2997
+ return null;
2998
+ }
2999
+ const newConvo = { ...conversation };
3000
+ if (newConvo.assistant_id) {
3001
+ newConvo.assistant_id = anonymizeAssistantId(newConvo.assistant_id);
3002
+ }
3003
+ return newConvo;
3004
+ }
3005
+ function anonymizeMessages(messages, newConvoId) {
3006
+ if (!Array.isArray(messages)) {
3007
+ return [];
3008
+ }
3009
+ const idMap = new Map();
3010
+ return messages.map((message) => {
3011
+ var _a, _b;
3012
+ const newMessageId = anonymizeMessageId(message.messageId);
3013
+ idMap.set(message.messageId, newMessageId);
3014
+ const anonymizedAttachments = (_a = message.attachments) === null || _a === void 0 ? void 0 : _a.map((attachment) => {
3015
+ return {
3016
+ ...attachment,
3017
+ messageId: newMessageId,
3018
+ conversationId: newConvoId,
3019
+ };
3020
+ });
3021
+ return {
3022
+ ...message,
3023
+ messageId: newMessageId,
3024
+ parentMessageId: idMap.get(message.parentMessageId || '') ||
3025
+ anonymizeMessageId(message.parentMessageId || ''),
3026
+ conversationId: newConvoId,
3027
+ model: ((_b = message.model) === null || _b === void 0 ? void 0 : _b.startsWith('asst_'))
3028
+ ? anonymizeAssistantId(message.model)
3029
+ : message.model,
3030
+ attachments: anonymizedAttachments,
3031
+ };
3032
+ });
3033
+ }
3034
+ /** Factory function that takes mongoose instance and returns the methods */
3035
+ function createShareMethods(mongoose) {
3036
+ /**
3037
+ * Get shared messages for a public share link
3038
+ */
3039
+ async function getSharedMessages(shareId) {
3040
+ try {
3041
+ const SharedLink = mongoose.models.SharedLink;
3042
+ const share = (await SharedLink.findOne({ shareId, isPublic: true })
3043
+ .populate({
3044
+ path: 'messages',
3045
+ select: '-_id -__v -user',
3046
+ })
3047
+ .select('-_id -__v -user')
3048
+ .lean());
3049
+ if (!(share === null || share === void 0 ? void 0 : share.conversationId) || !share.isPublic) {
3050
+ return null;
3051
+ }
3052
+ const newConvoId = anonymizeConvoId(share.conversationId);
3053
+ const result = {
3054
+ shareId: share.shareId || shareId,
3055
+ title: share.title,
3056
+ isPublic: share.isPublic,
3057
+ createdAt: share.createdAt,
3058
+ updatedAt: share.updatedAt,
3059
+ conversationId: newConvoId,
3060
+ messages: anonymizeMessages(share.messages, newConvoId),
3061
+ };
3062
+ return result;
3063
+ }
3064
+ catch (error) {
3065
+ logger.error('[getSharedMessages] Error getting share link', {
3066
+ error: error instanceof Error ? error.message : 'Unknown error',
3067
+ shareId,
3068
+ });
3069
+ throw new ShareServiceError('Error getting share link', 'SHARE_FETCH_ERROR');
3070
+ }
3071
+ }
3072
+ /**
3073
+ * Get shared links for a specific user with pagination and search
3074
+ */
3075
+ async function getSharedLinks(user, pageParam, pageSize = 10, isPublic = true, sortBy = 'createdAt', sortDirection = 'desc', search) {
3076
+ var _a;
3077
+ try {
3078
+ const SharedLink = mongoose.models.SharedLink;
3079
+ const Conversation = mongoose.models.Conversation;
3080
+ const query = { user, isPublic };
3081
+ if (pageParam) {
3082
+ if (sortDirection === 'desc') {
3083
+ query[sortBy] = { $lt: pageParam };
3084
+ }
3085
+ else {
3086
+ query[sortBy] = { $gt: pageParam };
3087
+ }
3088
+ }
3089
+ if (search && search.trim()) {
3090
+ try {
3091
+ const searchResults = await Conversation.meiliSearch(search);
3092
+ if (!((_a = searchResults === null || searchResults === void 0 ? void 0 : searchResults.hits) === null || _a === void 0 ? void 0 : _a.length)) {
3093
+ return {
3094
+ links: [],
3095
+ nextCursor: undefined,
3096
+ hasNextPage: false,
3097
+ };
3098
+ }
3099
+ const conversationIds = searchResults.hits.map((hit) => hit.conversationId);
3100
+ query['conversationId'] = { $in: conversationIds };
3101
+ }
3102
+ catch (searchError) {
3103
+ logger.error('[getSharedLinks] Meilisearch error', {
3104
+ error: searchError instanceof Error ? searchError.message : 'Unknown error',
3105
+ user,
3106
+ });
3107
+ return {
3108
+ links: [],
3109
+ nextCursor: undefined,
3110
+ hasNextPage: false,
3111
+ };
3112
+ }
3113
+ }
3114
+ const sort = {};
3115
+ sort[sortBy] = sortDirection === 'desc' ? -1 : 1;
3116
+ const sharedLinks = await SharedLink.find(query)
3117
+ .sort(sort)
3118
+ .limit(pageSize + 1)
3119
+ .select('-__v -user')
3120
+ .lean();
3121
+ const hasNextPage = sharedLinks.length > pageSize;
3122
+ const links = sharedLinks.slice(0, pageSize);
3123
+ const nextCursor = hasNextPage
3124
+ ? links[links.length - 1][sortBy]
3125
+ : undefined;
3126
+ return {
3127
+ links: links.map((link) => ({
3128
+ shareId: link.shareId || '',
3129
+ title: (link === null || link === void 0 ? void 0 : link.title) || 'Untitled',
3130
+ isPublic: link.isPublic,
3131
+ createdAt: link.createdAt || new Date(),
3132
+ conversationId: link.conversationId,
3133
+ })),
3134
+ nextCursor,
3135
+ hasNextPage,
3136
+ };
3137
+ }
3138
+ catch (error) {
3139
+ logger.error('[getSharedLinks] Error getting shares', {
3140
+ error: error instanceof Error ? error.message : 'Unknown error',
3141
+ user,
3142
+ });
3143
+ throw new ShareServiceError('Error getting shares', 'SHARES_FETCH_ERROR');
3144
+ }
3145
+ }
3146
+ /**
3147
+ * Delete all shared links for a user
3148
+ */
3149
+ async function deleteAllSharedLinks(user) {
3150
+ try {
3151
+ const SharedLink = mongoose.models.SharedLink;
3152
+ const result = await SharedLink.deleteMany({ user });
3153
+ return {
3154
+ message: 'All shared links deleted successfully',
3155
+ deletedCount: result.deletedCount,
3156
+ };
3157
+ }
3158
+ catch (error) {
3159
+ logger.error('[deleteAllSharedLinks] Error deleting shared links', {
3160
+ error: error instanceof Error ? error.message : 'Unknown error',
3161
+ user,
3162
+ });
3163
+ throw new ShareServiceError('Error deleting shared links', 'BULK_DELETE_ERROR');
3164
+ }
3165
+ }
3166
+ /**
3167
+ * Create a new shared link for a conversation
3168
+ */
3169
+ async function createSharedLink(user, conversationId) {
3170
+ if (!user || !conversationId) {
3171
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3172
+ }
3173
+ try {
3174
+ const Message = mongoose.models.Message;
3175
+ const SharedLink = mongoose.models.SharedLink;
3176
+ const Conversation = mongoose.models.Conversation;
3177
+ const [existingShare, conversationMessages] = await Promise.all([
3178
+ SharedLink.findOne({ conversationId, user, isPublic: true })
3179
+ .select('-_id -__v -user')
3180
+ .lean(),
3181
+ Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
3182
+ ]);
3183
+ if (existingShare && existingShare.isPublic) {
3184
+ logger.error('[createSharedLink] Share already exists', {
3185
+ user,
3186
+ conversationId,
3187
+ });
3188
+ throw new ShareServiceError('Share already exists', 'SHARE_EXISTS');
3189
+ }
3190
+ else if (existingShare) {
3191
+ await SharedLink.deleteOne({ conversationId, user });
3192
+ }
3193
+ const conversation = (await Conversation.findOne({ conversationId, user }).lean());
3194
+ // Check if user owns the conversation
3195
+ if (!conversation) {
3196
+ throw new ShareServiceError('Conversation not found or access denied', 'CONVERSATION_NOT_FOUND');
3197
+ }
3198
+ // Check if there are any messages to share
3199
+ if (!conversationMessages || conversationMessages.length === 0) {
3200
+ throw new ShareServiceError('No messages to share', 'NO_MESSAGES');
3201
+ }
3202
+ const title = conversation.title || 'Untitled';
3203
+ const shareId = nanoid.nanoid();
3204
+ await SharedLink.create({
3205
+ shareId,
3206
+ conversationId,
3207
+ messages: conversationMessages,
3208
+ title,
3209
+ user,
3210
+ });
3211
+ return { shareId, conversationId };
3212
+ }
3213
+ catch (error) {
3214
+ if (error instanceof ShareServiceError) {
3215
+ throw error;
3216
+ }
3217
+ logger.error('[createSharedLink] Error creating shared link', {
3218
+ error: error instanceof Error ? error.message : 'Unknown error',
3219
+ user,
3220
+ conversationId,
3221
+ });
3222
+ throw new ShareServiceError('Error creating shared link', 'SHARE_CREATE_ERROR');
3223
+ }
3224
+ }
3225
+ /**
3226
+ * Get a shared link for a conversation
3227
+ */
3228
+ async function getSharedLink(user, conversationId) {
3229
+ if (!user || !conversationId) {
3230
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3231
+ }
3232
+ try {
3233
+ const SharedLink = mongoose.models.SharedLink;
3234
+ const share = (await SharedLink.findOne({ conversationId, user, isPublic: true })
3235
+ .select('shareId -_id')
3236
+ .lean());
3237
+ if (!share) {
3238
+ return { shareId: null, success: false };
3239
+ }
3240
+ return { shareId: share.shareId || null, success: true };
3241
+ }
3242
+ catch (error) {
3243
+ logger.error('[getSharedLink] Error getting shared link', {
3244
+ error: error instanceof Error ? error.message : 'Unknown error',
3245
+ user,
3246
+ conversationId,
3247
+ });
3248
+ throw new ShareServiceError('Error getting shared link', 'SHARE_FETCH_ERROR');
3249
+ }
3250
+ }
3251
+ /**
3252
+ * Update a shared link with new messages
3253
+ */
3254
+ async function updateSharedLink(user, shareId) {
3255
+ if (!user || !shareId) {
3256
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3257
+ }
3258
+ try {
3259
+ const SharedLink = mongoose.models.SharedLink;
3260
+ const Message = mongoose.models.Message;
3261
+ const share = (await SharedLink.findOne({ shareId, user })
3262
+ .select('-_id -__v -user')
3263
+ .lean());
3264
+ if (!share) {
3265
+ throw new ShareServiceError('Share not found', 'SHARE_NOT_FOUND');
3266
+ }
3267
+ const updatedMessages = await Message.find({ conversationId: share.conversationId, user })
3268
+ .sort({ createdAt: 1 })
3269
+ .lean();
3270
+ const newShareId = nanoid.nanoid();
3271
+ const update = {
3272
+ messages: updatedMessages,
3273
+ user,
3274
+ shareId: newShareId,
3275
+ };
3276
+ const updatedShare = (await SharedLink.findOneAndUpdate({ shareId, user }, update, {
3277
+ new: true,
3278
+ upsert: false,
3279
+ runValidators: true,
3280
+ }).lean());
3281
+ if (!updatedShare) {
3282
+ throw new ShareServiceError('Share update failed', 'SHARE_UPDATE_ERROR');
3283
+ }
3284
+ anonymizeConvo(updatedShare);
3285
+ return { shareId: newShareId, conversationId: updatedShare.conversationId };
3286
+ }
3287
+ catch (error) {
3288
+ logger.error('[updateSharedLink] Error updating shared link', {
3289
+ error: error instanceof Error ? error.message : 'Unknown error',
3290
+ user,
3291
+ shareId,
3292
+ });
3293
+ throw new ShareServiceError(error instanceof ShareServiceError ? error.message : 'Error updating shared link', error instanceof ShareServiceError ? error.code : 'SHARE_UPDATE_ERROR');
3294
+ }
3295
+ }
3296
+ /**
3297
+ * Delete a shared link
3298
+ */
3299
+ async function deleteSharedLink(user, shareId) {
3300
+ if (!user || !shareId) {
3301
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3302
+ }
3303
+ try {
3304
+ const SharedLink = mongoose.models.SharedLink;
3305
+ const result = await SharedLink.findOneAndDelete({ shareId, user }).lean();
3306
+ if (!result) {
3307
+ return null;
3308
+ }
3309
+ return {
3310
+ success: true,
3311
+ shareId,
3312
+ message: 'Share deleted successfully',
3313
+ };
3314
+ }
3315
+ catch (error) {
3316
+ logger.error('[deleteSharedLink] Error deleting shared link', {
3317
+ error: error instanceof Error ? error.message : 'Unknown error',
3318
+ user,
3319
+ shareId,
3320
+ });
3321
+ throw new ShareServiceError('Error deleting shared link', 'SHARE_DELETE_ERROR');
3322
+ }
3323
+ }
3324
+ // Return all methods
3325
+ return {
3326
+ getSharedLink,
3327
+ getSharedLinks,
3328
+ createSharedLink,
3329
+ updateSharedLink,
3330
+ deleteSharedLink,
3331
+ getSharedMessages,
3332
+ deleteAllSharedLinks,
3333
+ };
3334
+ }
3335
+
3336
+ // Factory function that takes mongoose instance and returns the methods
3337
+ function createPluginAuthMethods(mongoose) {
3338
+ /**
3339
+ * Finds a single plugin auth entry by userId and authField
3340
+ */
3341
+ async function findOnePluginAuth({ userId, authField, }) {
3342
+ try {
3343
+ const PluginAuth = mongoose.models.PluginAuth;
3344
+ return await PluginAuth.findOne({ userId, authField }).lean();
3345
+ }
3346
+ catch (error) {
3347
+ throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3348
+ }
3349
+ }
3350
+ /**
3351
+ * Finds multiple plugin auth entries by userId and pluginKeys
3352
+ */
3353
+ async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
3354
+ try {
3355
+ if (!pluginKeys || pluginKeys.length === 0) {
3356
+ return [];
3357
+ }
3358
+ const PluginAuth = mongoose.models.PluginAuth;
3359
+ return await PluginAuth.find({
3360
+ userId,
3361
+ pluginKey: { $in: pluginKeys },
3362
+ }).lean();
3363
+ }
3364
+ catch (error) {
3365
+ throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3366
+ }
3367
+ }
3368
+ /**
3369
+ * Updates or creates a plugin auth entry
3370
+ */
3371
+ async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
3372
+ try {
3373
+ const PluginAuth = mongoose.models.PluginAuth;
3374
+ const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
3375
+ if (existingAuth) {
3376
+ return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
3377
+ }
3378
+ else {
3379
+ const newPluginAuth = await new PluginAuth({
3380
+ userId,
3381
+ authField,
3382
+ value,
3383
+ pluginKey,
3384
+ });
3385
+ await newPluginAuth.save();
3386
+ return newPluginAuth.toObject();
3387
+ }
3388
+ }
3389
+ catch (error) {
3390
+ throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3391
+ }
3392
+ }
3393
+ /**
3394
+ * Deletes plugin auth entries based on provided parameters
3395
+ */
3396
+ async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
3397
+ try {
3398
+ const PluginAuth = mongoose.models.PluginAuth;
3399
+ if (all) {
3400
+ const filter = { userId };
3401
+ if (pluginKey) {
3402
+ filter.pluginKey = pluginKey;
3403
+ }
3404
+ return await PluginAuth.deleteMany(filter);
3405
+ }
3406
+ if (!authField) {
3407
+ throw new Error('authField is required when all is false');
3408
+ }
3409
+ return await PluginAuth.deleteOne({ userId, authField });
3410
+ }
3411
+ catch (error) {
3412
+ throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3413
+ }
3414
+ }
3415
+ /**
3416
+ * Deletes all plugin auth entries for a user
3417
+ */
3418
+ async function deleteAllUserPluginAuths(userId) {
3419
+ try {
3420
+ const PluginAuth = mongoose.models.PluginAuth;
3421
+ return await PluginAuth.deleteMany({ userId });
3422
+ }
3423
+ catch (error) {
3424
+ throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3425
+ }
3426
+ }
3427
+ return {
3428
+ findOnePluginAuth,
3429
+ findPluginAuthsByKeys,
3430
+ updatePluginAuth,
3431
+ deletePluginAuth,
3432
+ deleteAllUserPluginAuths,
3433
+ };
3434
+ }
3435
+
2602
3436
  /**
2603
3437
  * Creates all database methods for all collections
2604
3438
  */
@@ -2608,6 +3442,9 @@ function createMethods(mongoose) {
2608
3442
  ...createSessionMethods(mongoose),
2609
3443
  ...createTokenMethods(mongoose),
2610
3444
  ...createRoleMethods(mongoose),
3445
+ ...createMemoryMethods(mongoose),
3446
+ ...createShareMethods(mongoose),
3447
+ ...createPluginAuthMethods(mongoose),
2611
3448
  };
2612
3449
  }
2613
3450
 
@@ -2626,6 +3463,7 @@ exports.hashToken = hashToken;
2626
3463
  exports.keySchema = keySchema;
2627
3464
  exports.logger = logger;
2628
3465
  exports.meiliLogger = logger$1;
3466
+ exports.memorySchema = MemoryEntrySchema;
2629
3467
  exports.messageSchema = messageSchema;
2630
3468
  exports.pluginAuthSchema = pluginAuthSchema;
2631
3469
  exports.presetSchema = presetSchema;