IndexedDB key generator resets after put-transaction

This problem has me stumped.

For some reason, the autoincrementing key generator in indexedDB resets after performing and update on an existing object with a put-transaction, leading to overwrites of data in the database.

For my app, I'm using a self written IndexedDB service for angularJS with all the basic CRUD functions implemented. I may also add that I'm developing with Ionic Framework, even though I doubt that is to blame.

Considering the service is a work-in-progress, I've let the key path for an object store default to "id" with an autoincrementing strategy. The indices for the given store, nevertheless, are up to the user to decide in a specific object. As an example:

dbHelper.objectStores = [{'employees', 
    indices: [{indexName: 'name', isUnique: false}, 
              {indexName: 'phone', isUnique: true}]}];

This would, unless already created in the db, create the object store 'employees' with indices 'name' and 'phone', where 'phone' would have to be a unique value while 'name' would not.

Here is the implementation of the openDB function. Please note that dbHelper.objectStores is supposed to be empty as it's up to the user to assign these properties before opening the db(or else it is defaulted).

angular.module('dbProvider', [])
.factory('$db', ['$window', function($window) {

// DB Object

var dbHelper = {};

// Properties - Are given defaults unless assigned manually by user before openDB is invoked.

dbHelper.dbName = 'defaultDB';
dbHelper.dbVersion = 1;
dbHelper.objectStores = [];

dbHelper.openDB = function(onCompleteCallback, onErrorCallback) {

        console.log('Atempting to open db with name ' + dbHelper.dbName + '.');

        var request = $window.indexedDB.open(dbHelper.dbName, dbHelper.dbVersion);

      // Invoked by indexedDB if version changes

      request.onupgradeneeded = function(e) {

        console.log('Version change. Current version: ' + dbHelper.dbVersion);

        var db = e.target.result;

        e.target.transaction.onerror = onErrorCallback;

        if(dbHelper.objectStores.length === 0) {
            dbHelper.objectStores.push({name:'defaultStore', indices: []});
        }

        for(var store in dbHelper.objectStores) {

            if(db.objectStoreNames.contains(dbHelper.objectStores[store].name)) {

                console.log(dbHelper.objectStores[store].name + ' deleted.');
                db.deleteObjectStore(dbHelper.objectStores[store].name);

            }

            var newStore = db.createObjectStore(dbHelper.objectStores[store].name, {keyPath: "id", autoIncrement: true});

            for(var index in dbHelper.objectStores[store].indices) {

                newStore.createIndex(dbHelper.objectStores[store].indices[index].indexName,
              dbHelper.objectStores[store].indices[index].indexName,  
              {unique : dbHelper.objectStores[store].indices[index].isUnique});
            }

            console.log(dbHelper.objectStores[store].name + ' created.');
        }

    };

    request.onsuccess = function(e) {
        console.log('DB ' + dbHelper.dbName + ' open.');
        dbHelper.indexedDB.db =  e.target.result;

        onCompleteCallback();
    };

    request.onerror = onErrorCallback;

};

Here are some of the CRUD functions(the ones in question):

dbHelper.findItemWithIndex = function(keyValue, storename,
    onCompleteCallback,onErrorCallback) {

    var db = dbHelper.indexedDB.db;
    var trans = db.transaction([storename], "readwrite");
    var store = trans.objectStore(storename);

    var index = store.index(keyValue.key);
    index.get(keyValue.value).onsuccess = function(event) {

    onCompleteCallback(event.target.result);

    };

};

dbHelper.addItemToStore = function(item, storename, 
    onCompleteCallback, onErrorCallback) {

    var db = dbHelper.indexedDB.db;
    var trans = db.transaction([storename], "readwrite");
    var store = trans.objectStore(storename);

    var request = store.add(item);

    trans.oncomplete = onCompleteCallback;

    request.onerror = onErrorCallback;
};

dbHelper.deleteItemFromStore = function(itemId, storename, 
    onCompleteCallback, onErrorCallback) {

    var db = dbHelper.indexedDB.db;
    var trans = db.transaction([storename], "readwrite");
    var store = trans.objectStore(storename);

    var request = store.delete(itemId);

    trans.oncomplete = onCompleteCallback;

    request.onerror = onErrorCallback;

};

dbHelper.updateItem = function(item, storename, onCompleteCallback, onErrorCallback) {

    var db = dbHelper.indexedDB.db;
    var trans = db.transaction([storename], "readwrite");
    var store = trans.objectStore(storename);

    var request = store.put(item);

    trans.oncomplete = onCompleteCallback;

    request.onerror = onErrorCallback;


};

Finally, the code from the controller where the transactions are invoked. The strategy here, is that the item is added to the db using the addItemToStore function the first time it is persisted, and then afterwards the updateItem function. After adding the first time, the object is immediately fetched in order to keep working on it with the assigned id from the db.

$scope.updateTemplate = function() {

    console.log('Saving..');

    var onCompleteCallback = {};

    if(!$scope.formTemplate.firstSave) {
        onCompleteCallback = $scope.updateModel;
    } else {
        $scope.formTemplate.firstSave = false;
            onCompleteCallback = $scope.setId;
    }

    $db.updateItem($scope.formTemplate, $scope.objectStore.name, 
        onCompleteCallback, $scope.dbError);

};

$scope.newItem = function() {

    $db.addItemToStore($scope.formTemplate, $scope.objectStore.name, 
        $scope.setId, $scope.dbError);

};

$scope.setId = function() {

    $db.findItemWithIndex(
        {key: 'title', 
        value: $scope.formTemplate.title},
        $scope.objectStore.name,
        function(result) {
            console.log(JSON.stringify(result));
            $scope.formTemplate = result;
        },
        function(error) {
            $scope.dbError(error);
        });

}

It's here everything goes to hell. I add an object, go back to another view and find it in the list with id=1. I add another object, go back to the list view, and there it is with id=2. And so forth and so forth..

Then, after updating either of the objects with the $scope.updateTemplate function, which also works like a charm, things get interesting:

The next object added gets id=1 and totally erases good old numero uno from earlier.

The next objects also get id's that cause them to replace the already existing objects.

What could cause this?

For testing I'm using Safari 8 in OS 10.10 and I'm deploying to an LGG2 with KitKat 4.4.2.

To be honest, I skimmed, but I saw this, "Safari 8" - the latest iOS and Safari have serious bugs with IndexedDB: http://www.raymondcamden.com/2014/9/25/IndexedDB-on-iOS-8--Broken-Bad

In iOS9, many of the IndexedDb bugs are fixed, but not all. We are currently testing on iOS9 Beta 2 and this particular bug that you found is not fixed.

We were able to work around this problem by not using autoincrement on our object stores. We just manually find the max key value and increment that.

Inserting an object looks something like this:

var store = db.transaction([entity], "readwrite").objectStore(entity);
store.openCursor(null, "prev").onsuccess = function (event) {
  var maxKey = event.target.result.key || 0;
  object.id = maxKey + 1;
  store.add(object);
}