What happens
IDBTransaction.abort() fires the abort event, but the records written in that transaction are still readable afterwards. In a browser, aborting discards every write the transaction made.
Reproduction
// repro.ts — bun add -d very-happy-dom && bun repro.ts
import { GlobalRegistrator } from 'very-happy-dom'
GlobalRegistrator.register()
const open = indexedDB.open('repro', 1)
open.addEventListener('upgradeneeded', () => open.result.createObjectStore('rows', { keyPath: 'id' }))
const db = await new Promise<IDBDatabase>(resolve => open.addEventListener('success', () => resolve(open.result)))
const write = db.transaction('rows', 'readwrite')
let aborted = false
write.addEventListener('abort', () => { aborted = true })
const put = write.objectStore('rows').put({ id: 'a' })
await new Promise<void>(resolve => put.addEventListener('success', () => { write.abort(); resolve() }))
await new Promise(resolve => setTimeout(resolve, 50))
const get = db.transaction('rows', 'readonly').objectStore('rows').get('a')
const row = await new Promise(resolve => get.addEventListener('success', () => resolve(get.result)))
console.log('abort event fired :', aborted)
console.log('row after abort :', row)
console.log(row === undefined ? 'PASS (browser behaviour)' : 'FAIL (write survived the abort)')Output with very-happy-dom 0.1.10 on Bun 1.3.14:
abort event fired : true
row after abort : { id: "a" }
FAIL (write survived the abort)Chromium, Firefox and Safari all print PASS: get('a') resolves to undefined.
Expected
Per the IndexedDB spec, aborting a transaction reverts every change it made before the abort event is fired.
Why it matters
We use IndexedDB to hold a recording that has not been uploaded yet, and our regression suite proves the app never tells somebody their run is saved when the browser rolled the write back. The test forces that case by aborting the transaction from a success handler:
const originalPut = IDBObjectStore.prototype.put
IDBObjectStore.prototype.put = function (...args) {
const request = originalPut.apply(this, args)
if (this.name === 'run-uploads')
request.addEventListener('success', () => this.transaction.abort())
return request
}
// enqueueRun() must reject, and the run must not be readable afterwardsAgainst a real browser this catches the regression. Against very-happy-dom the row survives, so the test passes whether or not the app handles the rollback, which is worse than not having the test. That keeps this suite on a real browser for now.
Environment
- very-happy-dom 0.1.10
- Bun 1.3.14, macOS (arm64)