From 8e8f68fa28b74328b93735d786443658d27cd400 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Fri, 23 Jun 2023 10:07:27 -0400 Subject: [PATCH] Improve error handling idb-keyval reported IDBTransaction.error if a `set` operation failed. However, based on my reading of the IndexedDB spec, and based on testing, IDBTransaction.error may not be set at the time the `error` event fires. This can be seen under the following scenarios: - If IDBObjectStore.add fails because a key already exists - If a quota is exceeded in Safari - If the transaction is aborted immediately after an IndexedDB operation is requested The `error` event's EventTarget should give the specific IDBRequest that failed, and that IDBRequest's error property should always be populated, so accessing `event.target` should fix this issue. I tried using a similar approach for `abort` events, but it did not work; strangely, the abort event's target appears to be the IDBOpenRequest associated with the transaction, rather than the transaction itself, so it does not have a usable error property. This PR also adds limited unit tests to cover this scenario. Aborting a transaction immediately after issuing a database request appears to be a good way to force an error. Fixes #163 --- src/index.ts | 3 ++- test/index.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8467fbe..c8fe0b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,9 @@ export function promisifyRequest( return new Promise((resolve, reject) => { // @ts-ignore - file size hacks request.oncomplete = request.onsuccess = () => resolve(request.result); + request.onerror = (e) => reject((e.target as IDBRequest | IDBTransaction).error); // @ts-ignore - file size hacks - request.onabort = request.onerror = () => reject(request.error); + request.onabort = () => reject(request.error); }); } diff --git a/test/index.ts b/test/index.ts index 19540b7..e0e321c 100644 --- a/test/index.ts +++ b/test/index.ts @@ -13,7 +13,8 @@ import { setMany, update, getMany, - delMany + delMany, + UseStore } from '../src'; import { assert as typeAssert, IsExact } from 'conditional-type-checks'; @@ -23,6 +24,11 @@ mocha.setup('tdd'); (async () => { await promisifyRequest(indexedDB.deleteDatabase('keyval-store')); const customStore = createStore('custom-db', 'custom-kv'); + const errorStore: UseStore = (txMode, callback) => customStore(txMode, (store) => { + const result = callback(store); + store.transaction.abort(); + return result; + }) suite('The basics', () => { test('get & set', async () => { @@ -152,6 +158,28 @@ mocha.setup('tdd'); 'Error is correct type', ); } + + try { + await get('foo', errorStore); + assert.fail('Expected throw'); + } catch (err) { + assert.strictEqual( + (err as DOMException).name, + 'AbortError', + 'Error is correct type', + ); + } + + try { + await set('foo', 'bar', errorStore); + assert.fail('Expected throw'); + } catch (err) { + assert.strictEqual( + (err as DOMException).name, + 'AbortError', + 'Error is correct type', + ); + } }); });