From 6466483ae30d2a2e83c00ef7048a5352f0014b33 Mon Sep 17 00:00:00 2001 From: Adi Singhal <35391599+adityaarunsinghal@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:02:44 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Resolve=20MeiliSearch=20S?= =?UTF-8?q?tartup=20Sync=20Failure=20from=20Model=20Loading=20Order=20(#12?= =?UTF-8?q?397)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: resolve MeiliSearch startup sync failure from model loading order `indexSync.js` captures `mongoose.models.Message` and `mongoose.models.Conversation` at module load time (lines 9-10). Since PR #11830 moved model registration into `createModels()`, these references are always `undefined` because `require('./indexSync')` ran before `createModels(mongoose)` in `api/db/index.js`. Move the `require('./indexSync')` below `createModels(mongoose)` so Mongoose models are registered before `indexSync.js` captures them. * fix: defer model lookups in indexSync, add load-order regression test - Move mongoose.models.Message and mongoose.models.Conversation lookups from module-level into performSync() and the indexSync() catch block, eliminating the fragile load-order dependency that caused the original regression - Add guard assertion that throws a clear error if models are missing - Add comment in api/db/index.js documenting the createModels ordering constraint - Add api/db/index.spec.js regression test to prevent future re-introduction of the load-order bug * fix: strengthen regression test to verify call order, add guard in setTimeout path - Rewrite index.spec.js to track call sequence via callOrder array, ensuring createModels executes before indexSync module loads (verified: test fails if order is reversed) - Add missing model guard assertion in the setTimeout fallback path in indexSync.js for consistency with performSync --------- Co-authored-by: Danny Avila --- api/db/index.js | 7 ++++++- api/db/index.spec.js | 26 ++++++++++++++++++++++++++ api/db/indexSync.js | 18 +++++++++++++++--- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 api/db/index.spec.js 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) {