fix: retry failed cursor page loads

This commit is contained in:
Jonathan Finley 2026-03-13 23:28:49 -04:00
parent 5c3e8559d4
commit f1fe40c3c7
2 changed files with 54 additions and 44 deletions

View file

@ -379,55 +379,49 @@ export default {
let entityPath = this.entityName === 'series-books' ? 'items' : this.entityName
const fullQueryString = this.getFetchQueryString(page)
const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
console.error('failed to fetch items', error)
return null
})
try {
const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`)
const results = Array.isArray(payload.results) ? payload.results : null
if (!results) {
throw new Error('Invalid browse payload: missing results array')
}
this.isFetchingEntities = false
if (this.pendingReset) {
this.pendingReset = false
this.resetEntities()
return
}
if (payload) {
try {
const results = Array.isArray(payload.results) ? payload.results : null
if (!results) {
throw new Error('Invalid browse payload: missing results array')
this.paginationMode = payload.paginationMode || this.paginationMode
this.nextCursor = payload.nextCursor || null
this.isCountDeferred = !!payload.isCountDeferred
if (this.paginationMode === 'keyset') {
if (this.nextCursor) {
this.pageCursors[page + 1] = this.nextCursor
} else {
delete this.pageCursors[page + 1]
}
}
this.paginationMode = payload.paginationMode || this.paginationMode
this.nextCursor = payload.nextCursor || null
this.isCountDeferred = !!payload.isCountDeferred
if (!this.initialized) {
this.initialized = true
this.totalEntities = payload.total
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
this.entities = new Array(this.totalEntities)
}
if (this.paginationMode === 'keyset') {
if (this.nextCursor) {
this.pageCursors[page + 1] = this.nextCursor
} else {
delete this.pageCursors[page + 1]
}
for (let i = 0; i < results.length; i++) {
const index = i + startIndex
this.entities[index] = results[i]
if (this.entityComponentRefs[index]) {
this.entityComponentRefs[index].setEntity(this.entities[index])
}
}
if (!this.initialized) {
this.initialized = true
this.totalEntities = payload.total
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
this.entities = new Array(this.totalEntities)
}
for (let i = 0; i < results.length; i++) {
const index = i + startIndex
this.entities[index] = results[i]
if (this.entityComponentRefs[index]) {
this.entityComponentRefs[index].setEntity(this.entities[index])
}
}
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
} catch (error) {
console.error(`Failed to process page ${page}`, error)
return null
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
} catch (error) {
console.error(`Failed to load page ${page}`, error)
throw error
} finally {
this.isFetchingEntities = false
if (this.pendingReset) {
this.pendingReset = false
this.resetEntities()
}
}
},

View file

@ -121,7 +121,7 @@ describe('LazyBookshelf', () => {
})
})
it('clears progressive loading state when a keyset follow-up chunk fails', () => {
it('retries a failed keyset follow-up chunk after the initial load recovers', () => {
const getStub = cy.stub()
getStub.onFirstCall().resolves({
@ -137,12 +137,28 @@ describe('LazyBookshelf', () => {
paginationMode: 'keyset',
isCountDeferred: true
})
getStub.onThirdCall().resolves({
results: [{ id: 'item-2', mediaType: 'book', media: { metadata: { title: 'Beta' } } }],
total: 4,
nextCursor: null,
paginationMode: 'keyset',
isCountDeferred: true
})
cy.spy(console, 'error').as('consoleError')
mountBookshelf(getStub).then(({ wrapper }) => {
cy.wrap(getStub).should('have.been.calledTwice')
cy.get('@consoleError').should('have.been.called')
cy.wrap(null)
.then(() => {
expect(wrapper.vm.pagesLoaded[1]).to.equal(undefined)
return wrapper.vm.loadPage(1)
})
.then(() => {
expect(getStub).to.have.been.calledThrice
expect(wrapper.vm.entities[1].id).to.equal('item-2')
})
cy.wrap(null).then(() => {
expect(wrapper.vm.isProgressiveLoading).to.equal(false)
expect(wrapper.vm.progressiveLoadProgress).to.equal(100)