From 3f2919fce50c05dfd21415f594a02f8bbc149a53 Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Sun, 2 Aug 2026 18:39:42 -0400 Subject: [PATCH] limit shelf query fallback to the postgres dialect withShelfFallback turned any shelf error into an empty shelf on both dialects, hiding real bugs and changing sqlite behavior versus upstream. Rethrow on sqlite and keep the empty-shelf fallback only for postgres, where dialect edge cases were the original motivation. --- server/utils/queries/libraryFilters.js | 2 ++ .../utils/queries/libraryFilters.test.js | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/server/utils/queries/libraryFilters.js b/server/utils/queries/libraryFilters.js index fd261e050..501f92dc0 100644 --- a/server/utils/queries/libraryFilters.js +++ b/server/utils/queries/libraryFilters.js @@ -14,6 +14,8 @@ async function withShelfFallback(scope, fallbackValue, action) { try { return await action() } catch (error) { + // Only postgres shelves fall back to empty results - on sqlite keep upstream behavior of surfacing the error + if (!Database.isPostgresDialect()) throw error Logger.error(`[LibraryFilters] Failed to load ${scope}`, error) return fallbackValue } diff --git a/test/server/utils/queries/libraryFilters.test.js b/test/server/utils/queries/libraryFilters.test.js index 1c9a2836f..f956997ce 100644 --- a/test/server/utils/queries/libraryFilters.test.js +++ b/test/server/utils/queries/libraryFilters.test.js @@ -1,6 +1,7 @@ const { expect } = require('chai') const sinon = require('sinon') +const Database = require('../../../../server/Database') const Logger = require('../../../../server/Logger') const libraryFilters = require('../../../../server/utils/queries/libraryFilters') const libraryItemsBookFilters = require('../../../../server/utils/queries/libraryItemsBookFilters') @@ -12,6 +13,7 @@ describe('libraryFilters shelf resilience', () => { }) it('should return empty discover shelf when discover query fails', async () => { + sinon.stub(Database, 'isPostgresDialect').returns(true) sinon.stub(libraryItemsBookFilters, 'getDiscoverLibraryItems').rejects(new Error('discover failed')) const errorStub = sinon.stub(Logger, 'error') @@ -22,6 +24,7 @@ describe('libraryFilters shelf resilience', () => { }) it('should return empty in-progress shelf when a query fails', async () => { + sinon.stub(Database, 'isPostgresDialect').returns(true) sinon.stub(libraryItemsBookFilters, 'getFilteredLibraryItems').rejects(new Error('progress failed')) const errorStub = sinon.stub(Logger, 'error') @@ -32,6 +35,7 @@ describe('libraryFilters shelf resilience', () => { }) it('should return empty continue-series shelf when a query fails', async () => { + sinon.stub(Database, 'isPostgresDialect').returns(true) sinon.stub(libraryItemsBookFilters, 'getContinueSeriesLibraryItems').rejects(new Error('continue failed')) const errorStub = sinon.stub(Logger, 'error') @@ -42,6 +46,7 @@ describe('libraryFilters shelf resilience', () => { }) it('should return empty newest podcast episodes shelf when a query fails', async () => { + sinon.stub(Database, 'isPostgresDialect').returns(true) sinon.stub(libraryItemsPodcastFilters, 'getFilteredPodcastEpisodes').rejects(new Error('podcast failed')) const errorStub = sinon.stub(Logger, 'error') @@ -51,6 +56,21 @@ describe('libraryFilters shelf resilience', () => { expect(errorStub.calledWithMatch('[LibraryFilters] Failed to load newest-podcast-episodes shelf for library "library-1"')).to.equal(true) }) + it('should rethrow shelf query errors on sqlite to keep upstream behavior', async () => { + sinon.stub(Database, 'isPostgresDialect').returns(false) + sinon.stub(libraryItemsBookFilters, 'getDiscoverLibraryItems').rejects(new Error('discover failed')) + + let error + try { + await libraryFilters.getLibraryItemsToDiscover({ mediaType: 'book', id: 'library-1' }, { id: 'user-1' }, [], 10) + } catch (caughtError) { + error = caughtError + } + + expect(error).to.be.an('error') + expect(error.message).to.equal('discover failed') + }) + it('should keep discover shelf mapping behavior when query succeeds', async () => { const libraryItem = { toOldJSONMinified: () => ({ id: 'item-1' }),