@librechat/data-schemas 0.0.10 → 0.0.12
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 +244 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +244 -95
- package/dist/index.es.js.map +1 -1
- package/dist/types/methods/index.d.ts +3 -3
- package/dist/types/methods/pluginAuth.d.ts +1 -2
- package/dist/types/models/plugins/mongoMeili.d.ts +26 -2
- package/dist/types/schema/defaults.d.ts +14 -1
- package/dist/types/schema/preset.d.ts +4 -0
- package/dist/types/types/convo.d.ts +4 -0
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -425,10 +425,23 @@ const conversationPreset = {
|
|
|
425
425
|
max_tokens: {
|
|
426
426
|
type: Number,
|
|
427
427
|
},
|
|
428
|
-
|
|
428
|
+
useResponsesApi: {
|
|
429
|
+
type: Boolean,
|
|
430
|
+
},
|
|
431
|
+
/** OpenAI Responses API / Anthropic API / Google API */
|
|
432
|
+
web_search: {
|
|
433
|
+
type: Boolean,
|
|
434
|
+
},
|
|
435
|
+
disableStreaming: {
|
|
436
|
+
type: Boolean,
|
|
437
|
+
},
|
|
438
|
+
/** Reasoning models only */
|
|
429
439
|
reasoning_effort: {
|
|
430
440
|
type: String,
|
|
431
441
|
},
|
|
442
|
+
reasoning_summary: {
|
|
443
|
+
type: String,
|
|
444
|
+
},
|
|
432
445
|
};
|
|
433
446
|
|
|
434
447
|
const convoSchema = new Schema({
|
|
@@ -881,7 +894,9 @@ const promptGroupSchema = new Schema({
|
|
|
881
894
|
});
|
|
882
895
|
promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
|
|
883
896
|
|
|
884
|
-
|
|
897
|
+
/**
|
|
898
|
+
* Uses a sub-schema for permissions. Notice we disable `_id` for this subdocument.
|
|
899
|
+
*/
|
|
885
900
|
const rolePermissionsSchema = new Schema({
|
|
886
901
|
[PermissionTypes.BOOKMARKS]: {
|
|
887
902
|
[Permissions.USE]: { type: Boolean, default: true },
|
|
@@ -915,6 +930,9 @@ const rolePermissionsSchema = new Schema({
|
|
|
915
930
|
[PermissionTypes.WEB_SEARCH]: {
|
|
916
931
|
[Permissions.USE]: { type: Boolean, default: true },
|
|
917
932
|
},
|
|
933
|
+
[PermissionTypes.FILE_SEARCH]: {
|
|
934
|
+
[Permissions.USE]: { type: Boolean, default: true },
|
|
935
|
+
},
|
|
918
936
|
}, { _id: false });
|
|
919
937
|
const roleSchema = new Schema({
|
|
920
938
|
name: { type: String, required: true, unique: true, index: true },
|
|
@@ -942,6 +960,7 @@ const roleSchema = new Schema({
|
|
|
942
960
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
|
|
943
961
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: true },
|
|
944
962
|
[PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: true },
|
|
963
|
+
[PermissionTypes.FILE_SEARCH]: { [Permissions.USE]: true },
|
|
945
964
|
}),
|
|
946
965
|
},
|
|
947
966
|
});
|
|
@@ -1336,6 +1355,13 @@ const searchEnabled = process.env.SEARCH != null && process.env.SEARCH.toLowerCa
|
|
|
1336
1355
|
* Flag to indicate if MeiliSearch is enabled based on required environment variables.
|
|
1337
1356
|
*/
|
|
1338
1357
|
const meiliEnabled = process.env.MEILI_HOST != null && process.env.MEILI_MASTER_KEY != null && searchEnabled;
|
|
1358
|
+
/**
|
|
1359
|
+
* Get sync configuration from environment variables
|
|
1360
|
+
*/
|
|
1361
|
+
const getSyncConfig = () => ({
|
|
1362
|
+
batchSize: parseInt(process.env.MEILI_SYNC_BATCH_SIZE || '100', 10),
|
|
1363
|
+
delayMs: parseInt(process.env.MEILI_SYNC_DELAY_MS || '100', 10),
|
|
1364
|
+
});
|
|
1339
1365
|
/**
|
|
1340
1366
|
* Local implementation of parseTextParts to avoid dependency on librechat-data-provider
|
|
1341
1367
|
* Extracts text content from an array of content items
|
|
@@ -1367,6 +1393,19 @@ const validateOptions = (options) => {
|
|
|
1367
1393
|
}
|
|
1368
1394
|
});
|
|
1369
1395
|
};
|
|
1396
|
+
/**
|
|
1397
|
+
* Helper function to process documents in batches with rate limiting
|
|
1398
|
+
*/
|
|
1399
|
+
const processBatch = async (items, batchSize, delayMs, processor) => {
|
|
1400
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
1401
|
+
const batch = items.slice(i, i + batchSize);
|
|
1402
|
+
await processor(batch);
|
|
1403
|
+
// Add delay between batches to prevent overwhelming resources
|
|
1404
|
+
if (i + batchSize < items.length && delayMs > 0) {
|
|
1405
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1370
1409
|
/**
|
|
1371
1410
|
* Factory function to create a MeiliMongooseModel class which extends a Mongoose model.
|
|
1372
1411
|
* This class contains static and instance methods to synchronize and manage the MeiliSearch index
|
|
@@ -1375,97 +1414,148 @@ const validateOptions = (options) => {
|
|
|
1375
1414
|
* @param config - Configuration object.
|
|
1376
1415
|
* @param config.index - The MeiliSearch index object.
|
|
1377
1416
|
* @param config.attributesToIndex - List of attributes to index.
|
|
1417
|
+
* @param config.syncOptions - Sync configuration options.
|
|
1378
1418
|
* @returns A class definition that will be loaded into the Mongoose schema.
|
|
1379
1419
|
*/
|
|
1380
|
-
const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
1420
|
+
const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) => {
|
|
1381
1421
|
const primaryKey = attributesToIndex[0];
|
|
1422
|
+
const syncConfig = { ...getSyncConfig(), ...syncOptions };
|
|
1382
1423
|
class MeiliMongooseModel {
|
|
1424
|
+
/**
|
|
1425
|
+
* Get the current sync progress
|
|
1426
|
+
*/
|
|
1427
|
+
static async getSyncProgress() {
|
|
1428
|
+
const totalDocuments = await this.countDocuments();
|
|
1429
|
+
const indexedDocuments = await this.countDocuments({ _meiliIndex: true });
|
|
1430
|
+
return {
|
|
1431
|
+
totalProcessed: indexedDocuments,
|
|
1432
|
+
totalDocuments,
|
|
1433
|
+
isComplete: indexedDocuments === totalDocuments,
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1383
1436
|
/**
|
|
1384
1437
|
* Synchronizes the data between the MongoDB collection and the MeiliSearch index.
|
|
1385
|
-
*
|
|
1386
|
-
* The synchronization process involves:
|
|
1387
|
-
* 1. Fetching all documents from the MongoDB collection and MeiliSearch index.
|
|
1388
|
-
* 2. Comparing documents from both sources.
|
|
1389
|
-
* 3. Deleting documents from MeiliSearch that no longer exist in MongoDB.
|
|
1390
|
-
* 4. Adding documents to MeiliSearch that exist in MongoDB but not in the index.
|
|
1391
|
-
* 5. Updating documents in MeiliSearch if key fields (such as `text` or `title`) differ.
|
|
1392
|
-
* 6. Updating the `_meiliIndex` field in MongoDB to indicate the indexing status.
|
|
1393
|
-
*
|
|
1394
|
-
* Note: The function processes documents in batches because MeiliSearch's
|
|
1395
|
-
* `index.getDocuments` requires an exact limit and `index.addDocuments` does not handle
|
|
1396
|
-
* partial failures in a batch.
|
|
1397
|
-
*
|
|
1398
|
-
* @returns {Promise<void>} Resolves when the synchronization is complete.
|
|
1438
|
+
* Now uses streaming and batching to reduce memory usage.
|
|
1399
1439
|
*/
|
|
1400
|
-
static async syncWithMeili() {
|
|
1440
|
+
static async syncWithMeili(options) {
|
|
1401
1441
|
try {
|
|
1402
|
-
|
|
1403
|
-
const
|
|
1442
|
+
const startTime = Date.now();
|
|
1443
|
+
const { batchSize, delayMs } = syncConfig;
|
|
1444
|
+
logger$1.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
|
|
1445
|
+
// Build query with resume capability
|
|
1446
|
+
const query = {};
|
|
1447
|
+
if (options === null || options === void 0 ? void 0 : options.resumeFromId) {
|
|
1448
|
+
query._id = { $gt: options.resumeFromId };
|
|
1449
|
+
}
|
|
1450
|
+
// Get total count for progress tracking
|
|
1451
|
+
const totalCount = await this.countDocuments(query);
|
|
1452
|
+
let processedCount = 0;
|
|
1453
|
+
// First, handle documents that need to be removed from Meili
|
|
1454
|
+
await this.cleanupMeiliIndex(index, primaryKey, batchSize, delayMs);
|
|
1455
|
+
// Process MongoDB documents in batches using cursor
|
|
1456
|
+
const cursor = this.find(query)
|
|
1457
|
+
.select(attributesToIndex.join(' ') + ' _meiliIndex')
|
|
1458
|
+
.sort({ _id: 1 })
|
|
1459
|
+
.batchSize(batchSize)
|
|
1460
|
+
.cursor();
|
|
1404
1461
|
const format = (doc) => _.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1462
|
+
let documentBatch = [];
|
|
1463
|
+
let updateOps = [];
|
|
1464
|
+
// Process documents in streaming fashion
|
|
1465
|
+
for await (const doc of cursor) {
|
|
1466
|
+
const typedDoc = doc.toObject();
|
|
1467
|
+
const formatted = format(typedDoc);
|
|
1468
|
+
// Check if document needs indexing
|
|
1469
|
+
if (!typedDoc._meiliIndex) {
|
|
1470
|
+
documentBatch.push(formatted);
|
|
1471
|
+
updateOps.push({
|
|
1472
|
+
updateOne: {
|
|
1473
|
+
filter: { _id: typedDoc._id },
|
|
1474
|
+
update: { $set: { _meiliIndex: true } },
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
processedCount++;
|
|
1479
|
+
// Process batch when it reaches the configured size
|
|
1480
|
+
if (documentBatch.length >= batchSize) {
|
|
1481
|
+
await this.processSyncBatch(index, documentBatch, updateOps);
|
|
1482
|
+
documentBatch = [];
|
|
1483
|
+
updateOps = [];
|
|
1484
|
+
// Log progress
|
|
1485
|
+
const progress = Math.round((processedCount / totalCount) * 100);
|
|
1486
|
+
logger$1.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
|
|
1487
|
+
// Add delay to prevent overwhelming resources
|
|
1488
|
+
if (delayMs > 0) {
|
|
1489
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
// Process remaining documents
|
|
1494
|
+
if (documentBatch.length > 0) {
|
|
1495
|
+
await this.processSyncBatch(index, documentBatch, updateOps);
|
|
1496
|
+
}
|
|
1497
|
+
const duration = Date.now() - startTime;
|
|
1498
|
+
logger$1.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
|
|
1499
|
+
}
|
|
1500
|
+
catch (error) {
|
|
1501
|
+
logger$1.error('[syncWithMeili] Error during sync:', error);
|
|
1502
|
+
throw error;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Process a batch of documents for syncing
|
|
1507
|
+
*/
|
|
1508
|
+
static async processSyncBatch(index, documents, updateOps) {
|
|
1509
|
+
if (documents.length === 0) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
try {
|
|
1513
|
+
// Add documents to MeiliSearch
|
|
1514
|
+
await index.addDocuments(documents);
|
|
1515
|
+
// Update MongoDB to mark documents as indexed
|
|
1516
|
+
if (updateOps.length > 0) {
|
|
1517
|
+
await this.collection.bulkWrite(updateOps);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
catch (error) {
|
|
1521
|
+
logger$1.error('[processSyncBatch] Error processing batch:', error);
|
|
1522
|
+
// Don't throw - allow sync to continue with other documents
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Clean up documents in MeiliSearch that no longer exist in MongoDB
|
|
1527
|
+
*/
|
|
1528
|
+
static async cleanupMeiliIndex(index, primaryKey, batchSize, delayMs) {
|
|
1529
|
+
try {
|
|
1410
1530
|
let offset = 0;
|
|
1411
|
-
|
|
1531
|
+
let moreDocuments = true;
|
|
1412
1532
|
while (moreDocuments) {
|
|
1413
1533
|
const batch = await index.getDocuments({ limit: batchSize, offset });
|
|
1414
1534
|
if (batch.results.length === 0) {
|
|
1415
1535
|
moreDocuments = false;
|
|
1536
|
+
break;
|
|
1416
1537
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1538
|
+
const meiliIds = batch.results.map((doc) => doc[primaryKey]);
|
|
1539
|
+
const query = {};
|
|
1540
|
+
query[primaryKey] = { $in: meiliIds };
|
|
1541
|
+
// Find which documents exist in MongoDB
|
|
1542
|
+
const existingDocs = await this.find(query).select(primaryKey).lean();
|
|
1543
|
+
const existingIds = new Set(existingDocs.map((doc) => doc[primaryKey]));
|
|
1544
|
+
// Delete documents that don't exist in MongoDB
|
|
1545
|
+
const toDelete = meiliIds.filter((id) => !existingIds.has(id));
|
|
1546
|
+
if (toDelete.length > 0) {
|
|
1547
|
+
await Promise.all(toDelete.map((id) => index.deleteDocument(id)));
|
|
1548
|
+
logger$1.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
|
|
1419
1549
|
}
|
|
1420
1550
|
offset += batchSize;
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
// Process documents present in the MeiliSearch index
|
|
1425
|
-
for (const [id, doc] of indexMap) {
|
|
1426
|
-
const update = {};
|
|
1427
|
-
update[primaryKey] = id;
|
|
1428
|
-
if (mongoMap.has(id)) {
|
|
1429
|
-
const mongoDoc = mongoMap.get(id);
|
|
1430
|
-
if ((doc.text && doc.text !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.text)) ||
|
|
1431
|
-
(doc.title && doc.title !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.title))) {
|
|
1432
|
-
logger$1.debug(`[syncWithMeili] ${id} had document discrepancy in ${doc.text ? 'text' : 'title'} field`);
|
|
1433
|
-
updateOps.push({
|
|
1434
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
|
1435
|
-
});
|
|
1436
|
-
await index.addDocuments([doc]);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
else {
|
|
1440
|
-
await index.deleteDocument(id);
|
|
1441
|
-
updateOps.push({
|
|
1442
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: false } } },
|
|
1443
|
-
});
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
// Process documents present in MongoDB
|
|
1447
|
-
for (const [id, doc] of mongoMap) {
|
|
1448
|
-
const update = {};
|
|
1449
|
-
update[primaryKey] = id;
|
|
1450
|
-
if (!indexMap.has(id)) {
|
|
1451
|
-
await index.addDocuments([doc]);
|
|
1452
|
-
updateOps.push({
|
|
1453
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
else if (doc._meiliIndex === false) {
|
|
1457
|
-
updateOps.push({
|
|
1458
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
|
1459
|
-
});
|
|
1551
|
+
// Add delay between batches
|
|
1552
|
+
if (delayMs > 0) {
|
|
1553
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1460
1554
|
}
|
|
1461
1555
|
}
|
|
1462
|
-
if (updateOps.length > 0) {
|
|
1463
|
-
await this.collection.bulkWrite(updateOps);
|
|
1464
|
-
logger$1.debug(`[syncWithMeili] Finished indexing ${primaryKey === 'messageId' ? 'messages' : 'conversations'}`);
|
|
1465
|
-
}
|
|
1466
1556
|
}
|
|
1467
1557
|
catch (error) {
|
|
1468
|
-
logger$1.error('[
|
|
1558
|
+
logger$1.error('[cleanupMeiliIndex] Error during cleanup:', error);
|
|
1469
1559
|
}
|
|
1470
1560
|
}
|
|
1471
1561
|
/**
|
|
@@ -1521,16 +1611,26 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1521
1611
|
return object;
|
|
1522
1612
|
}
|
|
1523
1613
|
/**
|
|
1524
|
-
* Adds the current document to the MeiliSearch index
|
|
1614
|
+
* Adds the current document to the MeiliSearch index with retry logic
|
|
1525
1615
|
*/
|
|
1526
1616
|
async addObjectToMeili(next) {
|
|
1527
1617
|
const object = this.preprocessObjectForIndex();
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1618
|
+
const maxRetries = 3;
|
|
1619
|
+
let retryCount = 0;
|
|
1620
|
+
while (retryCount < maxRetries) {
|
|
1621
|
+
try {
|
|
1622
|
+
await index.addDocuments([object]);
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
retryCount++;
|
|
1627
|
+
if (retryCount >= maxRetries) {
|
|
1628
|
+
logger$1.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
|
|
1629
|
+
return next();
|
|
1630
|
+
}
|
|
1631
|
+
// Exponential backoff
|
|
1632
|
+
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
1633
|
+
}
|
|
1534
1634
|
}
|
|
1535
1635
|
try {
|
|
1536
1636
|
await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
|
|
@@ -1631,6 +1731,8 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1631
1731
|
* @param options.apiKey - The MeiliSearch API key.
|
|
1632
1732
|
* @param options.indexName - The name of the MeiliSearch index.
|
|
1633
1733
|
* @param options.primaryKey - The primary key field for indexing.
|
|
1734
|
+
* @param options.syncBatchSize - Batch size for sync operations.
|
|
1735
|
+
* @param options.syncDelayMs - Delay between batches in milliseconds.
|
|
1634
1736
|
*/
|
|
1635
1737
|
function mongoMeili(schema, options) {
|
|
1636
1738
|
const mongoose = options.mongoose;
|
|
@@ -1645,9 +1747,37 @@ function mongoMeili(schema, options) {
|
|
|
1645
1747
|
},
|
|
1646
1748
|
});
|
|
1647
1749
|
const { host, apiKey, indexName, primaryKey } = options;
|
|
1750
|
+
const syncOptions = {
|
|
1751
|
+
batchSize: options.syncBatchSize || getSyncConfig().batchSize,
|
|
1752
|
+
delayMs: options.syncDelayMs || getSyncConfig().delayMs,
|
|
1753
|
+
};
|
|
1648
1754
|
const client = new MeiliSearch({ host, apiKey });
|
|
1649
|
-
|
|
1755
|
+
/** Create index only if it doesn't exist */
|
|
1650
1756
|
const index = client.index(indexName);
|
|
1757
|
+
// Check if index exists and create if needed
|
|
1758
|
+
(async () => {
|
|
1759
|
+
try {
|
|
1760
|
+
await index.getRawInfo();
|
|
1761
|
+
logger$1.debug(`[mongoMeili] Index ${indexName} already exists`);
|
|
1762
|
+
}
|
|
1763
|
+
catch (error) {
|
|
1764
|
+
const errorCode = error === null || error === void 0 ? void 0 : error.code;
|
|
1765
|
+
if (errorCode === 'index_not_found') {
|
|
1766
|
+
try {
|
|
1767
|
+
logger$1.info(`[mongoMeili] Creating new index: ${indexName}`);
|
|
1768
|
+
await client.createIndex(indexName, { primaryKey });
|
|
1769
|
+
logger$1.info(`[mongoMeili] Successfully created index: ${indexName}`);
|
|
1770
|
+
}
|
|
1771
|
+
catch (createError) {
|
|
1772
|
+
// Index might have been created by another instance
|
|
1773
|
+
logger$1.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
logger$1.error(`[mongoMeili] Error checking index ${indexName}:`, error);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
})();
|
|
1651
1781
|
// Collect attributes from the schema that should be indexed
|
|
1652
1782
|
const attributesToIndex = [
|
|
1653
1783
|
...Object.entries(schema.obj).reduce((results, [key, value]) => {
|
|
@@ -1655,7 +1785,7 @@ function mongoMeili(schema, options) {
|
|
|
1655
1785
|
return schemaValue.meiliIndex ? [...results, key] : results;
|
|
1656
1786
|
}, []),
|
|
1657
1787
|
];
|
|
1658
|
-
schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex }));
|
|
1788
|
+
schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex, syncOptions }));
|
|
1659
1789
|
// Register Mongoose hooks
|
|
1660
1790
|
schema.post('save', function (doc, next) {
|
|
1661
1791
|
var _a;
|
|
@@ -1676,29 +1806,38 @@ function mongoMeili(schema, options) {
|
|
|
1676
1806
|
}
|
|
1677
1807
|
try {
|
|
1678
1808
|
const conditions = this.getQuery();
|
|
1809
|
+
const { batchSize, delayMs } = syncOptions;
|
|
1679
1810
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
|
|
1680
1811
|
const convoIndex = client.index('convos');
|
|
1681
1812
|
const deletedConvos = await mongoose
|
|
1682
1813
|
.model('Conversation')
|
|
1683
1814
|
.find(conditions)
|
|
1815
|
+
.select('conversationId')
|
|
1684
1816
|
.lean();
|
|
1685
|
-
|
|
1686
|
-
await
|
|
1817
|
+
// Process deletions in batches
|
|
1818
|
+
await processBatch(deletedConvos, batchSize, delayMs, async (batch) => {
|
|
1819
|
+
const promises = batch.map((convo) => convoIndex.deleteDocument(convo.conversationId));
|
|
1820
|
+
await Promise.all(promises);
|
|
1821
|
+
});
|
|
1687
1822
|
}
|
|
1688
1823
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
|
|
1689
1824
|
const messageIndex = client.index('messages');
|
|
1690
1825
|
const deletedMessages = await mongoose
|
|
1691
1826
|
.model('Message')
|
|
1692
1827
|
.find(conditions)
|
|
1828
|
+
.select('messageId')
|
|
1693
1829
|
.lean();
|
|
1694
|
-
|
|
1695
|
-
await
|
|
1830
|
+
// Process deletions in batches
|
|
1831
|
+
await processBatch(deletedMessages, batchSize, delayMs, async (batch) => {
|
|
1832
|
+
const promises = batch.map((message) => messageIndex.deleteDocument(message.messageId));
|
|
1833
|
+
await Promise.all(promises);
|
|
1834
|
+
});
|
|
1696
1835
|
}
|
|
1697
1836
|
return next();
|
|
1698
1837
|
}
|
|
1699
1838
|
catch (error) {
|
|
1700
1839
|
if (meiliEnabled) {
|
|
1701
|
-
logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may
|
|
1840
|
+
logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
|
|
1702
1841
|
}
|
|
1703
1842
|
return next();
|
|
1704
1843
|
}
|
|
@@ -2372,12 +2511,9 @@ class SessionError extends Error {
|
|
|
2372
2511
|
}
|
|
2373
2512
|
}
|
|
2374
2513
|
const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
|
|
2375
|
-
const expires = REFRESH_TOKEN_EXPIRY
|
|
2376
|
-
? eval(REFRESH_TOKEN_EXPIRY)
|
|
2377
|
-
: 1000 * 60 * 60 * 24 * 7; // 7 days default
|
|
2514
|
+
const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
|
|
2378
2515
|
// Factory function that takes mongoose instance and returns the methods
|
|
2379
2516
|
function createSessionMethods(mongoose) {
|
|
2380
|
-
const Session = mongoose.models.Session;
|
|
2381
2517
|
/**
|
|
2382
2518
|
* Creates a new session for a user
|
|
2383
2519
|
*/
|
|
@@ -2386,12 +2522,13 @@ function createSessionMethods(mongoose) {
|
|
|
2386
2522
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2387
2523
|
}
|
|
2388
2524
|
try {
|
|
2389
|
-
const
|
|
2525
|
+
const Session = mongoose.models.Session;
|
|
2526
|
+
const currentSession = new Session({
|
|
2390
2527
|
user: userId,
|
|
2391
2528
|
expiration: options.expiration || new Date(Date.now() + expires),
|
|
2392
2529
|
});
|
|
2393
|
-
const refreshToken = await generateRefreshToken(
|
|
2394
|
-
return { session, refreshToken };
|
|
2530
|
+
const refreshToken = await generateRefreshToken(currentSession);
|
|
2531
|
+
return { session: currentSession, refreshToken };
|
|
2395
2532
|
}
|
|
2396
2533
|
catch (error) {
|
|
2397
2534
|
logger.error('[createSession] Error creating session:', error);
|
|
@@ -2403,6 +2540,7 @@ function createSessionMethods(mongoose) {
|
|
|
2403
2540
|
*/
|
|
2404
2541
|
async function findSession(params, options = { lean: true }) {
|
|
2405
2542
|
try {
|
|
2543
|
+
const Session = mongoose.models.Session;
|
|
2406
2544
|
const query = {};
|
|
2407
2545
|
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
|
2408
2546
|
throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
|
|
@@ -2443,6 +2581,7 @@ function createSessionMethods(mongoose) {
|
|
|
2443
2581
|
*/
|
|
2444
2582
|
async function updateExpiration(session, newExpiration) {
|
|
2445
2583
|
try {
|
|
2584
|
+
const Session = mongoose.models.Session;
|
|
2446
2585
|
const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
|
|
2447
2586
|
if (!sessionDoc) {
|
|
2448
2587
|
throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
|
|
@@ -2460,6 +2599,7 @@ function createSessionMethods(mongoose) {
|
|
|
2460
2599
|
*/
|
|
2461
2600
|
async function deleteSession(params) {
|
|
2462
2601
|
try {
|
|
2602
|
+
const Session = mongoose.models.Session;
|
|
2463
2603
|
if (!params.refreshToken && !params.sessionId) {
|
|
2464
2604
|
throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
|
|
2465
2605
|
}
|
|
@@ -2486,6 +2626,7 @@ function createSessionMethods(mongoose) {
|
|
|
2486
2626
|
*/
|
|
2487
2627
|
async function deleteAllUserSessions(userId, options = {}) {
|
|
2488
2628
|
try {
|
|
2629
|
+
const Session = mongoose.models.Session;
|
|
2489
2630
|
if (!userId) {
|
|
2490
2631
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2491
2632
|
}
|
|
@@ -2542,6 +2683,7 @@ function createSessionMethods(mongoose) {
|
|
|
2542
2683
|
*/
|
|
2543
2684
|
async function countActiveSessions(userId) {
|
|
2544
2685
|
try {
|
|
2686
|
+
const Session = mongoose.models.Session;
|
|
2545
2687
|
if (!userId) {
|
|
2546
2688
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2547
2689
|
}
|
|
@@ -2712,7 +2854,6 @@ const formatDate = (date) => {
|
|
|
2712
2854
|
};
|
|
2713
2855
|
// Factory function that takes mongoose instance and returns the methods
|
|
2714
2856
|
function createMemoryMethods(mongoose) {
|
|
2715
|
-
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2716
2857
|
/**
|
|
2717
2858
|
* Creates a new memory entry for a user
|
|
2718
2859
|
* Throws an error if a memory with the same key already exists
|
|
@@ -2722,6 +2863,7 @@ function createMemoryMethods(mongoose) {
|
|
|
2722
2863
|
if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
|
|
2723
2864
|
return { ok: false };
|
|
2724
2865
|
}
|
|
2866
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2725
2867
|
const existingMemory = await MemoryEntry.findOne({ userId, key });
|
|
2726
2868
|
if (existingMemory) {
|
|
2727
2869
|
throw new Error('Memory with this key already exists');
|
|
@@ -2747,6 +2889,7 @@ function createMemoryMethods(mongoose) {
|
|
|
2747
2889
|
if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
|
|
2748
2890
|
return { ok: false };
|
|
2749
2891
|
}
|
|
2892
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2750
2893
|
await MemoryEntry.findOneAndUpdate({ userId, key }, {
|
|
2751
2894
|
value,
|
|
2752
2895
|
tokenCount,
|
|
@@ -2766,6 +2909,7 @@ function createMemoryMethods(mongoose) {
|
|
|
2766
2909
|
*/
|
|
2767
2910
|
async function deleteMemory({ userId, key }) {
|
|
2768
2911
|
try {
|
|
2912
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2769
2913
|
const result = await MemoryEntry.findOneAndDelete({ userId, key });
|
|
2770
2914
|
return { ok: !!result };
|
|
2771
2915
|
}
|
|
@@ -2778,6 +2922,7 @@ function createMemoryMethods(mongoose) {
|
|
|
2778
2922
|
*/
|
|
2779
2923
|
async function getAllUserMemories(userId) {
|
|
2780
2924
|
try {
|
|
2925
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2781
2926
|
return (await MemoryEntry.find({ userId }).lean());
|
|
2782
2927
|
}
|
|
2783
2928
|
catch (error) {
|
|
@@ -3188,12 +3333,12 @@ function createShareMethods(mongoose) {
|
|
|
3188
3333
|
|
|
3189
3334
|
// Factory function that takes mongoose instance and returns the methods
|
|
3190
3335
|
function createPluginAuthMethods(mongoose) {
|
|
3191
|
-
const PluginAuth = mongoose.models.PluginAuth;
|
|
3192
3336
|
/**
|
|
3193
3337
|
* Finds a single plugin auth entry by userId and authField
|
|
3194
3338
|
*/
|
|
3195
3339
|
async function findOnePluginAuth({ userId, authField, }) {
|
|
3196
3340
|
try {
|
|
3341
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3197
3342
|
return await PluginAuth.findOne({ userId, authField }).lean();
|
|
3198
3343
|
}
|
|
3199
3344
|
catch (error) {
|
|
@@ -3208,6 +3353,7 @@ function createPluginAuthMethods(mongoose) {
|
|
|
3208
3353
|
if (!pluginKeys || pluginKeys.length === 0) {
|
|
3209
3354
|
return [];
|
|
3210
3355
|
}
|
|
3356
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3211
3357
|
return await PluginAuth.find({
|
|
3212
3358
|
userId,
|
|
3213
3359
|
pluginKey: { $in: pluginKeys },
|
|
@@ -3222,6 +3368,7 @@ function createPluginAuthMethods(mongoose) {
|
|
|
3222
3368
|
*/
|
|
3223
3369
|
async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
|
|
3224
3370
|
try {
|
|
3371
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3225
3372
|
const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
|
|
3226
3373
|
if (existingAuth) {
|
|
3227
3374
|
return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
|
|
@@ -3246,6 +3393,7 @@ function createPluginAuthMethods(mongoose) {
|
|
|
3246
3393
|
*/
|
|
3247
3394
|
async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
|
|
3248
3395
|
try {
|
|
3396
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3249
3397
|
if (all) {
|
|
3250
3398
|
const filter = { userId };
|
|
3251
3399
|
if (pluginKey) {
|
|
@@ -3267,6 +3415,7 @@ function createPluginAuthMethods(mongoose) {
|
|
|
3267
3415
|
*/
|
|
3268
3416
|
async function deleteAllUserPluginAuths(userId) {
|
|
3269
3417
|
try {
|
|
3418
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3270
3419
|
return await PluginAuth.deleteMany({ userId });
|
|
3271
3420
|
}
|
|
3272
3421
|
catch (error) {
|