Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IndexedDB: Can you manually initiate a version change transaction?

I am writing a Chrome extension that utilizes IndexedDB to store some information client side in an IDBObjectStore within an IDBDatabase.

The nature of the data is such that I need my users to be able to modify the object store at their whim. (add new objects modify existing ones etc.) I am accomplishing this through a settings page and all is fine and dandy so far.

The caveat comes when I want to release a new version of the (default) object store. If I didn't care about overwriting my users' data, then I could just handle the onupgradeneeded event the same way I handle it when it is fired in reaction to a fresh install. Which would be something like:

var request = window.indexedDB.open(DB_NAME, CURRENT_DB_VERSION);
request.onupgradeneeded = upgrade;
function upgrade(event){
  var db = event.target.result;
  var objectStore = db.createObjectStore("domains", {keyPath: "id", autoIncrement: true});
  objectStore.createIndex("domain", "domain", {multiEntry: true });
  for(var i=0; i<tags.length; i++){
    objectStore.add(tags[i]);
    console.log("added " + tags[i]["domain"] + " to the IDBObjectStore 'domains' in the IDBDatabase 'Tags' (Format)");
  }
}

...but I do care.

So, what I am currently doing is placing a conditional inside of the upgrade(event) function that checks for the truthyness of event.oldVersion (its 0 on a fresh install). If the value is greater than 0, then I open a new tab that contains an options page where the user can pick and choose which objects he wants to update.

Now the tricky part: Once this page closes I need to send a message to the background page that contains the necessary upgrade information. Normally, I would just receive the message in my listener and perform the update, utilizing the provided information. However, IDBDatabase.createObjectStore() throws an InvalidStateError if:

The method [is] not called from a versionchange transaction callback.

When I look at the spec for IDBDatabase.transaction(storenames, mode), in the mode parameter description, it says:

versionchange mode can't be specified here.

So, it seems to me like I need to trigger an onupgradeneeded event, but I also need to pass the event handler an additional parameter, besides the event itself, which contains information that it can use to decide how to perform the upgrade.

I don't know how to go about doing this though.

Can anyone offer me some insight?

like image 252
Luke Avatar asked Jun 13 '15 23:06

Luke


People also ask

How do I update my IndexedDB data?

To update an existing data in the database, the put(item, key) method is used. However, if the requested data doesn't exist, this method creates it and inserts it in the Object Store. This method returns the key of the stored object as a result.

Is IndexedDB asynchronous?

Operations performed using IndexedDB are done asynchronously, so as not to block applications.

Is IndexedDB deprecated?

Why to use indexeddb? The W3C has announced that the Web SQL database is a deprecated local storage specification so web developer should not use this technology any more.

What is version in IndexedDB?

The version property of the IDBDatabase interface is a 64-bit integer that contains the version of the connected database. When a database is first created, this attribute is an empty string. Note: This feature is available in Web Workers.


1 Answers

You are getting InvalidStateError because you may not be calling IDBDatabase.createObjectStore() from onupgradeneeded event handler. In case of IDB, all object store creation and manipulation has to happen from inside of onupgradeneeded event handler or after onupgradeneeded is triggered.

If I understood your requirement correctly then you do not want to override the user's existing object store in certain conditions, and for that you want to pass some information in onupgradeneeded event handler to tell whether to create afresh object store or do some modification on top of existing object store.

My recommendation would be - have 2 global array variables DB_SCHEMA_DROP_QUERIES and DB_SCHEMA_CREATE_QUERIES which you can prepare with drop and create data before opening the database using window.indexedDB.open(DB_NAME, CURRENT_DB_VERSION);

Then have code as below (I am just giving a heads up), so when you want to 1. create afresh database then first drop all your existing data stores from database and create new (which means populate both DB_SCHEMA_DROP_QUERIES and DB_SCHEMA_CREATE_QUERIES). 2. just add one more object store then only prepare DB_SCHEMA_CREATE_QUERIES 3. modify an existing object store then prepare both DB_SCHEMA_DROP_QUERIES and DB_SCHEMA_CREATE_QUERIES but only for that particular object store

Basically what we are trying to achieve to making the things dynamic instead of hard-coding data store creation like this db.createObjectStore("domains", {keyPath: "id", autoIncrement: true});. This will also help you to get rid of maintaining version records.

var dropQueriesLength = DB_SCHEMA_DROP_QUERIES.length;
for(var i =0; i < dropQueriesLength; i++){
try{
    DB_HANDLER.deleteObjectStore(DB_SCHEMA_DROP_QUERIES[i].name);
} catch(e){

}
}

for(var i =0; i < DB_SCHEMA_CREATE_QUERIES.length; i++){
  var objectStore = null;
    if(DB_SCHEMA_CREATE_QUERIES[i].primaryKeyCol != null && DB_SCHEMA_CREATE_QUERIES[i].primaryKeyCol != undefined){
        objectStore = DB_HANDLER.createObjectStore(DB_SCHEMA_CREATE_QUERIES[i].name, { keyPath: DB_SCHEMA_CREATE_QUERIES[i].primaryKeyCol});
    }
}
like image 179
hagrawal Avatar answered Oct 27 '22 00:10

hagrawal