ReviewOS

stacks/very-happy-dom

IndexedDB: aborting a transaction does not roll back its writes

#1585
Closed glennmichael123 opened this 9 days ago ·
9 days ago

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 afterwards

Against 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)
9 days ago

Fixed in 70580e7 (snapshot-based rollback, abort() restores them, and upgrade-handler errors are no longer swallowed). Released in v0.1.11, so this is now available to consumers rather than only on main. The scope notes I left earlier on this issue still stand — read those for what was deliberately left out.

Sign in to comment on this issue.