diff --git a/idb.js b/idb.js index 7c7eb5c..512ba6f 100644 --- a/idb.js +++ b/idb.js @@ -1,12 +1,12 @@ /*! - * idb.js IndexedDB wrapper v3.1 + * idb.js IndexedDB wrapper v3.2 * Licensed under the MIT license * Copyright (c) 2023 Lukas Jans * https://github.com/ljans/idb */ class IDB { - // Wrap IDBRequest im promise + // Wrap IDBRequest in promise promise(request) { return new Promise((resolve, reject) => { request.onerror = () => reject(request.error); @@ -16,29 +16,44 @@ class IDB { // Constructor constructor(tables, config={}) { + this.tables = tables; + this.config = config; - // Connect to DB - const request = indexedDB.open(config.name || 'IDB', config.version || 1); - this.connection = this.promise(request); - - // Perform upgrade - request.onupgradeneeded = e => { + // Bind table controllers + for(const name of Object.keys(tables)) this[name] = new this.Table(name, this); + + // Establish an initial connection to catch upgrade events + //this.connection.then(db => db.close()); + } + + // Establish a db connection + get connection() { + const request = indexedDB.open(this.config.name || 'IDB', this.config.version || 1); + + /** + * Always check for an upgade event, because querying a table when the database does not exists + * (either because it was deleted or initial creation is commented out in the constructor) + * causes indexedDB.open to create the database without any tables until another upgrade happens. + */ + request.onupgradeneeded = async e => { const db = request.result; // Clear old tables - for(const name of db.objectStoreNames) if(!tables[name]) db.deleteObjectStore(name); + for(const name of db.objectStoreNames) if(!this.tables[name]) db.deleteObjectStore(name); // Create new tables - for(const [name, options] of Object.entries(tables)) if(!db.objectStoreNames.contains(name)) db.createObjectStore(name, options); + for(const [name, options] of Object.entries(this.tables)) { + if(!db.objectStoreNames.contains(name)) db.createObjectStore(name, options); + } // Bubble upgrade event - if(config.upgrade) config.upgrade(e); + if(this.config.upgrade) await this.config.upgrade(e); } - // Bind table controllers - for(const name of Object.keys(tables)) this[name] = new this.Table(name, this); + // Return a promise that resolves to the established connection + return this.promise(request); } - + // Table controller get Table() { return class { @@ -49,18 +64,20 @@ class IDB { this.instance = instance; } - // Perform operation in transaction - transaction(operation) { - return this.instance.connection.then(db => { - - // Retrieve operation result - const transaction = db.transaction(this.name, 'readwrite'); - const table = transaction.objectStore(this.name); - const result = operation(table); - - // Return promise or wrap IDBRequest in promise - return result instanceof Promise ? result : this.instance.promise(result); - }); + // Perform operation in transaction (commit transaction and close connection so it's always possible to delete the db) + async transaction(operation) { + const db = await this.instance.connection; + + // Perform the operation + const transaction = db.transaction(this.name, 'readwrite'); + const table = transaction.objectStore(this.name); + var result = await operation(table); + transaction.commit(); + + // Wait for result and close connection + if(result instanceof IDBRequest) result = await this.instance.promise(result); + db.close(); + return result; } // Read data by index