diff --git a/api/db/index.js b/api/db/index.js index 5c29902f69..f4359c8adf 100644 --- a/api/db/index.js +++ b/api/db/index.js @@ -1,8 +1,13 @@ const mongoose = require('mongoose'); const { createModels } = require('@librechat/data-schemas'); const { connectDb } = require('./connect'); -const indexSync = require('./indexSync'); +// createModels MUST run before requiring indexSync. +// indexSync.js captures mongoose.models.Message and mongoose.models.Conversation +// at module load time. If those models are not registered first, all MeiliSearch +// sync operations will silently fail on every startup. createModels(mongoose); +const indexSync = require('./indexSync'); + module.exports = { connectDb, indexSync }; diff --git a/api/db/index.spec.js b/api/db/index.spec.js new file mode 100644 index 0000000000..e1ebe176dc --- /dev/null +++ b/api/db/index.spec.js @@ -0,0 +1,26 @@ +describe('api/db/index.js', () => { + test('createModels is called before indexSync is loaded', () => { + jest.resetModules(); + + const callOrder = []; + + jest.mock('@librechat/data-schemas', () => ({ + createModels: jest.fn((m) => { + callOrder.push('createModels'); + m.models.Message = { name: 'Message' }; + m.models.Conversation = { name: 'Conversation' }; + }), + })); + + jest.mock('./indexSync', () => { + callOrder.push('indexSync'); + return jest.fn(); + }); + + jest.mock('./connect', () => ({ connectDb: jest.fn() })); + + require('./index'); + + expect(callOrder).toEqual(['createModels', 'indexSync']); + }); +}); diff --git a/api/db/indexSync.js b/api/db/indexSync.js index 130cde77b8..13059033fb 100644 --- a/api/db/indexSync.js +++ b/api/db/indexSync.js @@ -6,9 +6,6 @@ const { isEnabled, FlowStateManager } = require('@librechat/api'); const { getLogStores } = require('~/cache'); const { batchResetMeiliFlags } = require('./utils'); -const Conversation = mongoose.models.Conversation; -const Message = mongoose.models.Message; - const searchEnabled = isEnabled(process.env.SEARCH); const indexingDisabled = isEnabled(process.env.MEILI_NO_SYNC); let currentTimeout = null; @@ -200,6 +197,14 @@ async function performSync(flowManager, flowId, flowType) { return { messagesSync: false, convosSync: false }; } + const Message = mongoose.models.Message; + const Conversation = mongoose.models.Conversation; + if (!Message || !Conversation) { + throw new Error( + '[indexSync] Models not registered. Ensure createModels() has been called before indexSync.', + ); + } + const client = MeiliSearchClient.getInstance(); const { status } = await client.health(); @@ -349,6 +354,13 @@ async function indexSync() { logger.debug('[indexSync] Creating indices...'); currentTimeout = setTimeout(async () => { try { + const Message = mongoose.models.Message; + const Conversation = mongoose.models.Conversation; + if (!Message || !Conversation) { + throw new Error( + '[indexSync] Models not registered. Ensure createModels() has been called before indexSync.', + ); + } await Message.syncWithMeili(); await Conversation.syncWithMeili(); } catch (err) {