IndexedDB: Can you manually initiate a version change transaction?

4.8k views Asked by At

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?

2

There are 2 answers

0
hagrawal7777 On BEST ANSWER

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});
    }
}
0
Josh On

The only way to trigger an upgrade-style transaction (a version change transaction) is to open a connection with a larger version number.