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 {
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
}

View file

@ -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' }),