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.
This commit is contained in:
Kevin Gatera 2026-08-02 18:39:42 -04:00
parent 9a5980e76b
commit 8c97a35206
No known key found for this signature in database
GPG key ID: F0D9F5932458CFB9
2 changed files with 22 additions and 0 deletions

View file

@ -14,6 +14,8 @@ async function withShelfFallback(scope, fallbackValue, action) {
try { try {
return await action() return await action()
} catch (error) { } 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) Logger.error(`[LibraryFilters] Failed to load ${scope}`, error)
return fallbackValue return fallbackValue
} }

View file

@ -1,6 +1,7 @@
const { expect } = require('chai') const { expect } = require('chai')
const sinon = require('sinon') const sinon = require('sinon')
const Database = require('../../../../server/Database')
const Logger = require('../../../../server/Logger') const Logger = require('../../../../server/Logger')
const libraryFilters = require('../../../../server/utils/queries/libraryFilters') const libraryFilters = require('../../../../server/utils/queries/libraryFilters')
const libraryItemsBookFilters = require('../../../../server/utils/queries/libraryItemsBookFilters') 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 () => { 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')) sinon.stub(libraryItemsBookFilters, 'getDiscoverLibraryItems').rejects(new Error('discover failed'))
const errorStub = sinon.stub(Logger, 'error') 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 () => { 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')) sinon.stub(libraryItemsBookFilters, 'getFilteredLibraryItems').rejects(new Error('progress failed'))
const errorStub = sinon.stub(Logger, 'error') 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 () => { 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')) sinon.stub(libraryItemsBookFilters, 'getContinueSeriesLibraryItems').rejects(new Error('continue failed'))
const errorStub = sinon.stub(Logger, 'error') 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 () => { 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')) sinon.stub(libraryItemsPodcastFilters, 'getFilteredPodcastEpisodes').rejects(new Error('podcast failed'))
const errorStub = sinon.stub(Logger, 'error') 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) 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 () => { it('should keep discover shelf mapping behavior when query succeeds', async () => {
const libraryItem = { const libraryItem = {
toOldJSONMinified: () => ({ id: 'item-1' }), toOldJSONMinified: () => ({ id: 'item-1' }),