diff --git a/idbstore.js b/idbstore.js index 0e1e36e..21a06d1 100644 --- a/idbstore.js +++ b/idbstore.js @@ -9,1363 +9,1403 @@ * Licensed under the MIT (X11) license */ -(function (name, definition, global) { - - 'use strict'; - - if (typeof define === 'function') { - define(definition); - } else if (typeof module !== 'undefined' && module.exports) { - module.exports = definition(); - } else { - global[name] = definition(); - } -})('IDBStore', function () { - - 'use strict'; - - var defaultErrorHandler = function (error) { - throw error; - }; - var defaultSuccessHandler = function () {}; - - var defaults = { - storeName: 'Store', - storePrefix: 'IDBWrapper-', - dbVersion: 1, - keyPath: 'id', - autoIncrement: true, - onStoreReady: function () { - }, - onError: defaultErrorHandler, - indexes: [], - implementationPreference: [ - 'indexedDB', - 'webkitIndexedDB', - 'mozIndexedDB', - 'shimIndexedDB' - ] - }; - - /** - * - * The IDBStore constructor - * - * @constructor - * @name IDBStore - * @version 1.6.2 - * - * @param {Object} [kwArgs] An options object used to configure the store and - * set callbacks - * @param {String} [kwArgs.storeName='Store'] The name of the store - * @param {String} [kwArgs.storePrefix='IDBWrapper-'] A prefix that is - * internally used to construct the name of the database, which will be - * kwArgs.storePrefix + kwArgs.storeName - * @param {Number} [kwArgs.dbVersion=1] The version of the store - * @param {String} [kwArgs.keyPath='id'] The key path to use. If you want to - * setup IDBWrapper to work with out-of-line keys, you need to set this to - * `null` - * @param {Boolean} [kwArgs.autoIncrement=true] If set to true, IDBStore will - * automatically make sure a unique keyPath value is present on each object - * that is stored. - * @param {Function} [kwArgs.onStoreReady] A callback to be called when the - * store is ready to be used. - * @param {Function} [kwArgs.onError=throw] A callback to be called when an - * error occurred during instantiation of the store. - * @param {Array} [kwArgs.indexes=[]] An array of indexData objects - * defining the indexes to use with the store. For every index to be used - * one indexData object needs to be passed in the array. - * An indexData object is defined as follows: - * @param {Object} [kwArgs.indexes.indexData] An object defining the index to - * use - * @param {String} kwArgs.indexes.indexData.name The name of the index - * @param {String} [kwArgs.indexes.indexData.keyPath] The key path of the index - * @param {Boolean} [kwArgs.indexes.indexData.unique] Whether the index is unique - * @param {Boolean} [kwArgs.indexes.indexData.multiEntry] Whether the index is multi entry - * @param {Array} [kwArgs.implementationPreference=['indexedDB','webkitIndexedDB','mozIndexedDB','shimIndexedDB']] An array of strings naming implementations to be used, in order or preference - * @param {Function} [onStoreReady] A callback to be called when the store - * is ready to be used. - * @example - // create a store for customers with an additional index over the - // `lastname` property. - var myCustomerStore = new IDBStore({ - dbVersion: 1, - storeName: 'customer-index', - keyPath: 'customerid', - autoIncrement: true, - onStoreReady: populateTable, - indexes: [ - { name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false } - ] - }); - * @example - // create a generic store - var myCustomerStore = new IDBStore({ - storeName: 'my-data-store', - onStoreReady: function(){ - // start working with the store. - } - }); - */ - var IDBStore = function (kwArgs, onStoreReady) { +(function(name, definition, global) { - if (typeof onStoreReady == 'undefined' && typeof kwArgs == 'function') { - onStoreReady = kwArgs; - } - if (Object.prototype.toString.call(kwArgs) != '[object Object]') { - kwArgs = {}; - } + 'use strict'; - for (var key in defaults) { - this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key]; + if (typeof define === 'function') { + define(definition); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = definition(); + } else { + global[name] = definition(); } +})('IDBStore', function() { - this.dbName = this.storePrefix + this.storeName; - this.dbVersion = parseInt(this.dbVersion, 10) || 1; - - onStoreReady && (this.onStoreReady = onStoreReady); - - var env = typeof window == 'object' ? window : self; - var availableImplementations = this.implementationPreference.filter(function (implName) { - return implName in env; - }); - this.implementation = availableImplementations[0]; - this.idb = env[this.implementation]; - this.keyRange = env.IDBKeyRange || env.webkitIDBKeyRange || env.mozIDBKeyRange; - - this.consts = { - 'READ_ONLY': 'readonly', - 'READ_WRITE': 'readwrite', - 'VERSION_CHANGE': 'versionchange', - 'NEXT': 'next', - 'NEXT_NO_DUPLICATE': 'nextunique', - 'PREV': 'prev', - 'PREV_NO_DUPLICATE': 'prevunique' - }; - - this.openDB(); - }; - - /** @lends IDBStore.prototype */ - var proto = { - - /** - * A pointer to the IDBStore ctor - * - * @private - * @type {Function} - * @constructs - */ - constructor: IDBStore, - - /** - * The version of IDBStore - * - * @type {String} - */ - version: '1.6.2', - - /** - * A reference to the IndexedDB object - * - * @type {Object} - */ - db: null, - - /** - * The full name of the IndexedDB used by IDBStore, composed of - * this.storePrefix + this.storeName - * - * @type {String} - */ - dbName: null, - - /** - * The version of the IndexedDB used by IDBStore - * - * @type {Number} - */ - dbVersion: null, - - /** - * A reference to the objectStore used by IDBStore - * - * @type {Object} - */ - store: null, - - /** - * The store name - * - * @type {String} - */ - storeName: null, - - /** - * The prefix to prepend to the store name - * - * @type {String} - */ - storePrefix: null, - - /** - * The key path - * - * @type {String} - */ - keyPath: null, - - /** - * Whether IDBStore uses autoIncrement - * - * @type {Boolean} - */ - autoIncrement: null, + 'use strict'; - /** - * The indexes used by IDBStore - * - * @type {Array} - */ - indexes: null, - - /** - * The implemantations to try to use, in order of preference - * - * @type {Array} - */ - implementationPreference: null, - - /** - * The actual implementation being used - * - * @type {String} - */ - implementation: '', + var defaultErrorHandler = function(error) { + throw error; + }; + var defaultSuccessHandler = function() { + }; - /** - * The callback to be called when the store is ready to be used - * - * @type {Function} - */ - onStoreReady: null, + var defaults = { + dbName: 'IDBWrapper', + dbVersion: 1, + stores: [{ + name: 'Store', + keyPath: 'id', + autoIncrement: true, + indexes: [] + }], + implementationPreference: [ + 'indexedDB', + 'webkitIndexedDB', + 'mozIndexedDB', + 'shimIndexedDB' + ], + onDbReady: function() { + }, + onError: defaultErrorHandler + }; - /** - * The callback to be called if an error occurred during instantiation - * of the store - * - * @type {Function} - */ - onError: null, + var storeDefaults = { + name: 'Store', + keyPath: 'id', + autoIncrement: true, + indexes: [] + }; - /** - * The internal insertID counter - * - * @type {Number} - * @private - */ - _insertIdCount: 0, + var consts = { + 'READ_ONLY': 'readonly', + 'READ_WRITE': 'readwrite', + 'VERSION_CHANGE': 'versionchange', + 'NEXT': 'next', + 'NEXT_NO_DUPLICATE': 'nextunique', + 'PREV': 'prev', + 'PREV_NO_DUPLICATE': 'prevunique' + }; /** - * Opens an IndexedDB; called by the constructor. * - * Will check if versions match and compare provided index configuration - * with existing ones, and update indexes if necessary. + * The IDBStore constructor * - * Will call this.onStoreReady() if everything went well and the store - * is ready to use, and this.onError() is something went wrong. - * - * @private + * @constructor + * @name IDBStore + * @version 1.6.2 * + * @param {Object} [kwArgs] An options object used to configure the store and + * set callbacks + * @param {Object} [kwArgs.stores=[ ... ] The stores + * @param {String} [kwArgs.dbName='IDBWrapper'] The name of the database + * @param {Number} [kwArgs.dbVersion=1] The version of the store + * @param {String} [kwArgs.keyPath='id'] The key path to use. If you want to + * setup IDBWrapper to work with out-of-line keys, you need to set this to + * `null` + * @param {Boolean} [kwArgs.autoIncrement=true] If set to true, IDBStore will + * automatically make sure a unique keyPath value is present on each object + * that is stored. + * @param {Function} [kwArgs.onDbReady] A callback to be called when the + * DB is ready to be used. + * @param {Function} [kwArgs.onError=throw] A callback to be called when an + * error occurred during instantiation of the store. + * @param {Array} [kwArgs.indexes=[]] An array of indexData objects + * defining the indexes to use with the store. For every index to be used + * one indexData object needs to be passed in the array. + * An indexData object is defined as follows: + * @param {Object} [kwArgs.indexes.indexData] An object defining the index to + * use + * @param {String} kwArgs.indexes.indexData.name The name of the index + * @param {String} [kwArgs.indexes.indexData.keyPath] The key path of the index + * @param {Boolean} [kwArgs.indexes.indexData.unique] Whether the index is unique + * @param {Boolean} [kwArgs.indexes.indexData.multiEntry] Whether the index is multi entry + * @param {Array} [kwArgs.implementationPreference=['indexedDB','webkitIndexedDB','mozIndexedDB','shimIndexedDB']] An array of strings naming implementations to be used, in order or preference + * @param {Function} [onDbReady] A callback to be called when the DB + * is ready to be used. + * @example + // create a store for customers with an additional index over the + // `lastname` property. + var myCustomerStore = new IDBStore({ + dbVersion: 1, + stores: [{ + 'customer-index', + keyPath: 'customerid', + autoIncrement: true, + indexes: [ + { name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false } + ] + }] + onDbReady: populateTables, + }); + * @example + // create a generic store + var myCustomerStore = new IDBStore({ + storeName: ['my-data-store'], + onDbReady: function(){ + // start working with the stores. + } + }); */ - openDB: function () { - - var openRequest = this.idb.open(this.dbName, this.dbVersion); - var preventSuccessCallback = false; + var IDBStore = function(kwArgs, onDbReady) { - openRequest.onerror = function (error) { - - var gotVersionErr = false; - if ('error' in error.target) { - gotVersionErr = error.target.error.name == 'VersionError'; - } else if ('errorCode' in error.target) { - gotVersionErr = error.target.errorCode == 12; + if (typeof onDbReady == 'undefined' && typeof kwArgs == 'function') { + onDbReady = kwArgs; } - - if (gotVersionErr) { - this.onError(new Error('The version number provided is lower than the existing one.')); - } else { - this.onError(error); + if (Object.prototype.toString.call(kwArgs) != '[object Object]') { + kwArgs = {}; } - }.bind(this); - openRequest.onsuccess = function (event) { - - if (preventSuccessCallback) { - return; + for (var key in defaults) { + this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key]; } - if(this.db){ - this.onStoreReady(); - return; - } + this.dbVersion = parseInt(this.dbVersion, 10) || 1; - this.db = event.target.result; + onDbReady && (this.onDbReady = onDbReady); - if(typeof this.db.version == 'string'){ - this.onError(new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.')); - return; - } + var env = typeof window == 'object' ? window : self; + var availableImplementations = this.implementationPreference.filter(function(implName) { + return implName in env; + }); + var implementation = availableImplementations[0]; + this.idb = env[implementation]; - if(!this.db.objectStoreNames.contains(this.storeName)){ - // We should never ever get here. - // Lets notify the user anyway. - this.onError(new Error('Object store couldn\'t be created.')); - return; - } + this.openDB(); + }; - var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY); - this.store = emptyTransaction.objectStore(this.storeName); - - // check indexes - var existingIndexes = Array.prototype.slice.call(this.getIndexList()); - this.indexes.forEach(function(indexData){ - var indexName = indexData.name; - - if(!indexName){ - preventSuccessCallback = true; - this.onError(new Error('Cannot create index: No index name given.')); - return; - } - - this.normalizeIndexData(indexData); - - if(this.hasIndex(indexName)){ - // check if it complies - var actualIndex = this.store.index(indexName); - var complies = this.indexComplies(actualIndex, indexData); - if(!complies){ - preventSuccessCallback = true; - this.onError(new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.')); + /** @lends IDBStore.prototype */ + var proto = { + + /** + * A pointer to the IDBStore ctor + * + * @private + * @type {Function} + * @constructs + */ + constructor: IDBStore, + + /** + * The version of IDBStore + * + * @type {String} + */ + version: '1.6.2', + + /** + * A reference to the IndexedDB object + * + * @type {Object} + */ + db: null, + + /** + * The full name of the IndexedDB used by IDBStore + * + * @type {String} + */ + dbName: null, + + /** + * The version of the IndexedDB used by IDBStore + * + * @type {Number} + */ + dbVersion: null, + + /** + * The stores + * + * @type {Array} + */ + stores: null, + + /** + * The callback to be called when the DB is ready to be used + * + * @type {Function} + */ + onDbReady: null, + + /** + * The callback to be called if an error occurred during instantiation + * of the store + * + * @type {Function} + */ + onError: null, + + /** + * Opens an IndexedDB; called by the constructor. + * + * Will check if versions match and compare provided index configuration + * with existing ones, and update indexes if necessary. + * + * Will call this.onDbReady() if everything went well and the DB + * is ready to use, and this.onError() is something went wrong. + * + * @private + * + */ + openDB: function() { + + var openRequest = this.idb.open(this.dbName, this.dbVersion); + var preventSuccessCallback = false; + var remainingCallbacks = this.stores.length; + + openRequest.onerror = function(error) { + + var gotVersionErr = false; + if ('error' in error.target) { + gotVersionErr = error.target.error.name == 'VersionError'; + } else if ('errorCode' in error.target) { + gotVersionErr = error.target.errorCode == 12; + } + + if (gotVersionErr) { + this.onError(new Error('The version number provided is lower than the existing one.')); + } else { + this.onError(error); + } + }.bind(this); + + openRequest.onsuccess = function(event) { + + if (preventSuccessCallback) { + return; + } + + if (this.db) { + this.onDbReady(); + return; + } + + this.db = event.target.result; + + if (typeof this.db.version == 'string') { + this.onError(new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.')); + return; + } + + for (var i = 0; i < this.stores.length; i++) { + if (!this.db.objectStoreNames.contains(this.stores[i].name)) { + // We should never ever get here. + // Lets notify the user anyway. + this.onError(new Error('Object store couldn\'t be created.')); + return; + } + + var emptyTransaction = this.db.transaction([this.stores[i].name], consts.READ_ONLY); + var currentStore = new Store(this.db, this.stores[i], emptyTransaction.objectStore(this.stores[i].name)); + this[this.stores[i].name] = currentStore; + + // check indexes + var existingIndexes = Array.prototype.slice.call(currentStore.getIndexList()); + this.stores[i].indexes && this.stores[i].indexes.forEach(handleIndexes, this); + + if (existingIndexes.length) { + preventSuccessCallback = true; + this.onError(new Error('Cannot delete index(es) "' + existingIndexes.toString() + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.')); + } + + remainingCallbacks--; + if (!preventSuccessCallback && remainingCallbacks === 0) { + this.onDbReady(); + } + } + + function handleIndexes(indexData) { + var self = this; // jshint ignore:line + var indexName = indexData.name; + + if (!indexName) { + preventSuccessCallback = true; + self.onError(new Error('Cannot create index: No index name given.')); + return; + } + + currentStore.normalizeIndexData(indexData); + + if (currentStore.hasIndex(indexName)) { + // check if it complies + var actualIndex = currentStore.store.index(indexName); + var complies = currentStore.indexComplies(actualIndex, indexData); + if (!complies) { + preventSuccessCallback = true; + self.onError(new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( self.dbVersion + 1 ) + '.')); + } + + existingIndexes.splice(existingIndexes.indexOf(indexName), 1); + } else { + preventSuccessCallback = true; + self.onError(new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( self.dbVersion + 1 ) + '.')); + } + } + + }.bind(this); + + openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event) { + + this.db = event.target.result; + + for (var i = 0; i < this.stores.length; i++) { + var currentStore; + if (this.db.objectStoreNames.contains(this.stores[i].name)) { + currentStore = new Store(this.db, this.stores[i], event.target.transaction.objectStore(this.stores[i].name)); + this[this.stores[i].name] = currentStore; + } else { + var optionalParameters = {autoIncrement: this.stores[i].autoIncrement ? this.stores[i].autoIncrement : storeDefaults.autoIncrement}; + if (this.stores[i].keyPath !== null) { + optionalParameters.keyPath = this.stores[i].keyPath ? this.stores[i].keyPath : storeDefaults.keyPath; + } + currentStore = new Store(this.db, this.stores[i], this.db.createObjectStore(this.stores[i].name, optionalParameters)); + this[this.stores[i].name] = currentStore; + } + + var existingIndexes = Array.prototype.slice.call(currentStore.getIndexList()); + this.stores[i].indexes && this.stores[i].indexes.forEach(handleIndexes, this); + + if (existingIndexes.length) { + existingIndexes.forEach(handleExistingIndexes, this); + } + } + + function handleIndexes(indexData) { + var self = this; // jshint ignore:line + var indexName = indexData.name; + + if (!indexName) { + preventSuccessCallback = true; + self.onError(new Error('Cannot create index: No index name given.')); + } + + currentStore.normalizeIndexData(indexData); + + if (currentStore.hasIndex(indexName)) { + // check if it complies + var actualIndex = currentStore.store.index(indexName); + var complies = currentStore.indexComplies(actualIndex, indexData); + if (!complies) { + // index differs, need to delete and re-create + currentStore.store.deleteIndex(indexName); + currentStore.store.createIndex(indexName, indexData.keyPath, { + unique: indexData.unique, + multiEntry: indexData.multiEntry + }); + } + + existingIndexes.splice(existingIndexes.indexOf(indexName), 1); + } else { + currentStore.store.createIndex(indexName, indexData.keyPath, { + unique: indexData.unique, + multiEntry: indexData.multiEntry + }); + } + } + + function handleExistingIndexes(_indexName) { + currentStore.deleteIndex(_indexName); + } + + }.bind(this); + }, + + /** + * Deletes the database used for this store if the IDB implementations + * provides that functionality. + * + * @param {Function} [onSuccess] A callback that is called if deletion + * was successful. + * @param {Function} [onError] A callback that is called if deletion + * failed. + */ + deleteDatabase: function(onSuccess, onError) { + if (this.idb.deleteDatabase) { + this.db.close(); + var deleteRequest = this.idb.deleteDatabase(this.dbName); + deleteRequest.onsuccess = onSuccess; + deleteRequest.onerror = onError; + } else { + onError(new Error('Browser does not support IndexedDB deleteDatabase!')); } - - existingIndexes.splice(existingIndexes.indexOf(indexName), 1); - } else { - preventSuccessCallback = true; - this.onError(new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.')); - } - - }, this); - - if (existingIndexes.length) { - preventSuccessCallback = true; - this.onError(new Error('Cannot delete index(es) "' + existingIndexes.toString() + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.')); } - preventSuccessCallback || this.onStoreReady(); - }.bind(this); - - openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){ + }; - this.db = event.target.result; + /** helpers **/ + var empty = {}; - if(this.db.objectStoreNames.contains(this.storeName)){ - this.store = event.target.transaction.objectStore(this.storeName); - } else { - var optionalParameters = { autoIncrement: this.autoIncrement }; - if (this.keyPath !== null) { - optionalParameters.keyPath = this.keyPath; - } - this.store = this.db.createObjectStore(this.storeName, optionalParameters); - } - - var existingIndexes = Array.prototype.slice.call(this.getIndexList()); - this.indexes.forEach(function(indexData){ - var indexName = indexData.name; - - if(!indexName){ - preventSuccessCallback = true; - this.onError(new Error('Cannot create index: No index name given.')); - } - - this.normalizeIndexData(indexData); - - if(this.hasIndex(indexName)){ - // check if it complies - var actualIndex = this.store.index(indexName); - var complies = this.indexComplies(actualIndex, indexData); - if(!complies){ - // index differs, need to delete and re-create - this.store.deleteIndex(indexName); - this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry }); + function mixin(target, source) { + var name, s; + for (name in source) { + s = source[name]; + if (s !== empty[name] && s !== target[name]) { + target[name] = s; } - - existingIndexes.splice(existingIndexes.indexOf(indexName), 1); - } else { - this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry }); - } - - }, this); - - if (existingIndexes.length) { - existingIndexes.forEach(function(_indexName){ - this.store.deleteIndex(_indexName); - }, this); } + return target; + } - }.bind(this); - }, + IDBStore.prototype = proto; + IDBStore.version = proto.version; - /** - * Deletes the database used for this store if the IDB implementations - * provides that functionality. - * - * @param {Function} [onSuccess] A callback that is called if deletion - * was successful. - * @param {Function} [onError] A callback that is called if deletion - * failed. - */ - deleteDatabase: function (onSuccess, onError) { - if (this.idb.deleteDatabase) { - this.db.close(); - var deleteRequest = this.idb.deleteDatabase(this.dbName); - deleteRequest.onsuccess = onSuccess; - deleteRequest.onerror = onError; - } else { - onError(new Error('Browser does not support IndexedDB deleteDatabase!')); - } - }, + return IDBStore; /********************* * data manipulation * *********************/ - /** - * Puts an object into the store. If an entry with the given id exists, - * it will be overwritten. This method has a different signature for inline - * keys and out-of-line keys; please see the examples below. - * - * @param {*} [key] The key to store. This is only needed if IDBWrapper - * is set to use out-of-line keys. For inline keys - the default scenario - - * this can be omitted. - * @param {Object} value The data object to store. - * @param {Function} [onSuccess] A callback that is called if insertion - * was successful. - * @param {Function} [onError] A callback that is called if insertion - * failed. - * @returns {IDBTransaction} The transaction used for this operation. - * @example - // Storing an object, using inline keys (the default scenario): - var myCustomer = { - customerid: 2346223, - lastname: 'Doe', - firstname: 'John' - }; - myCustomerStore.put(myCustomer, mySuccessHandler, myErrorHandler); - // Note that passing success- and error-handlers is optional. - * @example - // Storing an object, using out-of-line keys: - var myCustomer = { - lastname: 'Doe', - firstname: 'John' - }; - myCustomerStore.put(2346223, myCustomer, mySuccessHandler, myErrorHandler); - // Note that passing success- and error-handlers is optional. - */ - put: function (key, value, onSuccess, onError) { - if (this.keyPath !== null) { - onError = onSuccess; - onSuccess = value; - value = key; - } - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - - var hasSuccess = false, - result = null, - putRequest; - - var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE); - putTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - putTransaction.onabort = onError; - putTransaction.onerror = onError; - - if (this.keyPath !== null) { // in-line keys - this._addIdPropertyIfNeeded(value); - putRequest = putTransaction.objectStore(this.storeName).put(value); - } else { // out-of-line keys - putRequest = putTransaction.objectStore(this.storeName).put(value, key); - } - putRequest.onsuccess = function (event) { - hasSuccess = true; - result = event.target.result; - }; - putRequest.onerror = onError; - - return putTransaction; - }, - - /** - * Retrieves an object from the store. If no entry exists with the given id, - * the success handler will be called with null as first and only argument. - * - * @param {*} key The id of the object to fetch. - * @param {Function} [onSuccess] A callback that is called if fetching - * was successful. Will receive the object as only argument. - * @param {Function} [onError] A callback that will be called if an error - * occurred during the operation. - * @returns {IDBTransaction} The transaction used for this operation. - */ - get: function (key, onSuccess, onError) { - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - - var hasSuccess = false, - result = null; - - var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY); - getTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - getTransaction.onabort = onError; - getTransaction.onerror = onError; - var getRequest = getTransaction.objectStore(this.storeName).get(key); - getRequest.onsuccess = function (event) { - hasSuccess = true; - result = event.target.result; - }; - getRequest.onerror = onError; - - return getTransaction; - }, + function Store(db, params, store) { + /** + * A reference to the objectStore used by IDBStore + * + * @type {Object} + */ + this.store = null; + + /** + * The key path + * + * @type {String} + */ + this.keyPath = null; + + /** + * Whether IDBStore uses autoIncrement + * + * @type {Boolean} + */ + this.autoIncrement = null; + + /** + * The indexes used by IDBStore + * + * @type {Array} + */ + this.indexes = null; + + /** + * The implemantations to try to use, in order of preference + * + * @type {Array} + */ + this.implementationPreference = null; + + /** + * The actual implementation being used + * + * @type {String} + */ + this.implementation = ''; + + /** + * The internal insertID counter + * + * @type {Number} + * @private + */ + this._insertIdCount = 0; + + this.db = db; + this.store = store; + + var env = typeof window == 'object' ? window : self; + this.keyRange = env.IDBKeyRange || env.webkitIDBKeyRange || env.mozIDBKeyRange; + + for (var key in storeDefaults) { + this[key] = typeof params[key] != 'undefined' ? params[key] : storeDefaults[key]; + } - /** - * Removes an object from the store. - * - * @param {*} key The id of the object to remove. - * @param {Function} [onSuccess] A callback that is called if the removal - * was successful. - * @param {Function} [onError] A callback that will be called if an error - * occurred during the operation. - * @returns {IDBTransaction} The transaction used for this operation. - */ - remove: function (key, onSuccess, onError) { - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - - var hasSuccess = false, - result = null; - - var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE); - removeTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - removeTransaction.onabort = onError; - removeTransaction.onerror = onError; - - var deleteRequest = removeTransaction.objectStore(this.storeName)['delete'](key); - deleteRequest.onsuccess = function (event) { - hasSuccess = true; - result = event.target.result; - }; - deleteRequest.onerror = onError; - - return removeTransaction; - }, + /** + * Puts an object into the store. If an entry with the given id exists, + * it will be overwritten. This method has a different signature for inline + * keys and out-of-line keys; please see the examples below. + * + * @param {*} [key] The key to store. This is only needed if IDBWrapper + * is set to use out-of-line keys. For inline keys - the default scenario - + * this can be omitted. + * @param {Object} value The data object to store. + * @param {Function} [onSuccess] A callback that is called if insertion + * was successful. + * @param {Function} [onError] A callback that is called if insertion + * failed. + * @returns {IDBTransaction} The transaction used for this operation. + * @example + // Storing an object, using inline keys (the default scenario): + var myCustomer = { + customerid: 2346223, + lastname: 'Doe', + firstname: 'John' + }; + myCustomerStore.put(myCustomer, mySuccessHandler, myErrorHandler); + // Note that passing success- and error-handlers is optional. + * @example + // Storing an object, using out-of-line keys: + var myCustomer = { + lastname: 'Doe', + firstname: 'John' + }; + myCustomerStore.put(2346223, myCustomer, mySuccessHandler, myErrorHandler); + // Note that passing success- and error-handlers is optional. + */ + this.put = function(key, value, onSuccess, onError) { + if (this.keyPath !== null) { + onError = onSuccess; + onSuccess = value; + value = key; + } + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + + var hasSuccess = false, + result = null, + putRequest; + + var putTransaction = this.db.transaction([this.name], consts.READ_WRITE); + putTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + putTransaction.onabort = onError; + putTransaction.onerror = onError; + + if (this.keyPath !== null) { // in-line keys + this._addIdPropertyIfNeeded(value); + putRequest = putTransaction.objectStore(this.name).put(value); + } else { // out-of-line keys + putRequest = putTransaction.objectStore(this.name).put(value, key); + } + putRequest.onsuccess = function(event) { + hasSuccess = true; + result = event.target.result; + }; + putRequest.onerror = onError; - /** - * Runs a batch of put and/or remove operations on the store. - * - * @param {Array} dataArray An array of objects containing the operation to run - * and the data object (for put operations). - * @param {Function} [onSuccess] A callback that is called if all operations - * were successful. - * @param {Function} [onError] A callback that is called if an error - * occurred during one of the operations. - * @returns {IDBTransaction} The transaction used for this operation. - */ - batch: function (dataArray, onSuccess, onError) { - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - - if(Object.prototype.toString.call(dataArray) != '[object Array]'){ - onError(new Error('dataArray argument must be of type Array.')); - } else if (dataArray.length === 0) { - return onSuccess(true); - } - - var count = dataArray.length; - var called = false; - var hasSuccess = false; - - var batchTransaction = this.db.transaction([this.storeName] , this.consts.READ_WRITE); - batchTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(hasSuccess); - }; - batchTransaction.onabort = onError; - batchTransaction.onerror = onError; - - - var onItemSuccess = function () { - count--; - if (count === 0 && !called) { - called = true; - hasSuccess = true; - } - }; - - dataArray.forEach(function (operation) { - var type = operation.type; - var key = operation.key; - var value = operation.value; - - var onItemError = function (err) { - batchTransaction.abort(); - if (!called) { - called = true; - onError(err, type, key); - } + return putTransaction; }; - if (type == 'remove') { - var deleteRequest = batchTransaction.objectStore(this.storeName)['delete'](key); - deleteRequest.onsuccess = onItemSuccess; - deleteRequest.onerror = onItemError; - } else if (type == 'put') { - var putRequest; - if (this.keyPath !== null) { // in-line keys - this._addIdPropertyIfNeeded(value); - putRequest = batchTransaction.objectStore(this.storeName).put(value); - } else { // out-of-line keys - putRequest = batchTransaction.objectStore(this.storeName).put(value, key); - } - putRequest.onsuccess = onItemSuccess; - putRequest.onerror = onItemError; - } - }, this); + /** + * Retrieves an object from the store. If no entry exists with the given id, + * the success handler will be called with null as first and only argument. + * + * @param {*} key The id of the object to fetch. + * @param {Function} [onSuccess] A callback that is called if fetching + * was successful. Will receive the object as only argument. + * @param {Function} [onError] A callback that will be called if an error + * occurred during the operation. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.get = function(key, onSuccess, onError) { + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + + var hasSuccess = false, + result = null; + + var getTransaction = this.db.transaction([this.name], consts.READ_ONLY); + getTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + getTransaction.onabort = onError; + getTransaction.onerror = onError; + var getRequest = getTransaction.objectStore(this.name).get(key); + getRequest.onsuccess = function(event) { + hasSuccess = true; + result = event.target.result; + }; + getRequest.onerror = onError; - return batchTransaction; - }, + return getTransaction; + }; - /** - * Takes an array of objects and stores them in a single transaction. - * - * @param {Array} dataArray An array of objects to store - * @param {Function} [onSuccess] A callback that is called if all operations - * were successful. - * @param {Function} [onError] A callback that is called if an error - * occurred during one of the operations. - * @returns {IDBTransaction} The transaction used for this operation. - */ - putBatch: function (dataArray, onSuccess, onError) { - var batchData = dataArray.map(function(item){ - return { type: 'put', value: item }; - }); + /** + * Removes an object from the store. + * + * @param {*} key The id of the object to remove. + * @param {Function} [onSuccess] A callback that is called if the removal + * was successful. + * @param {Function} [onError] A callback that will be called if an error + * occurred during the operation. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.remove = function(key, onSuccess, onError) { + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + + var hasSuccess = false, + result = null; + + var removeTransaction = this.db.transaction([this.name], consts.READ_WRITE); + removeTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + removeTransaction.onabort = onError; + removeTransaction.onerror = onError; + + var deleteRequest = removeTransaction.objectStore(this.name)['delete'](key); + deleteRequest.onsuccess = function(event) { + hasSuccess = true; + result = event.target.result; + }; + deleteRequest.onerror = onError; - return this.batch(batchData, onSuccess, onError); - }, + return removeTransaction; + }; - /** - * Like putBatch, takes an array of objects and stores them in a single - * transaction, but allows processing of the result values. Returns the - * processed records containing the key for newly created records to the - * onSuccess calllback instead of only returning true or false for success. - * In addition, added the option for the caller to specify a key field that - * should be set to the newly created key. - * - * @param {Array} dataArray An array of objects to store - * @param {Object} [options] An object containing optional options - * @param {String} [options.keyField=this.keyPath] Specifies a field in the record to update - * with the auto-incrementing key. Defaults to the store's keyPath. - * @param {Function} [onSuccess] A callback that is called if all operations - * were successful. - * @param {Function} [onError] A callback that is called if an error - * occurred during one of the operations. - * @returns {IDBTransaction} The transaction used for this operation. - * - */ - upsertBatch: function (dataArray, options, onSuccess, onError) { - // handle `dataArray, onSuccess, onError` signature - if (typeof options == 'function') { - onSuccess = options; - onError = onSuccess; - options = {}; - } - - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - options || (options = {}); - - if (Object.prototype.toString.call(dataArray) != '[object Array]') { - onError(new Error('dataArray argument must be of type Array.')); - } - - var keyField = options.keyField || this.keyPath; - var count = dataArray.length; - var called = false; - var hasSuccess = false; - var index = 0; // assume success callbacks are executed in order - - var batchTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE); - batchTransaction.oncomplete = function () { - if (hasSuccess) { - onSuccess(dataArray); - } else { - onError(false); - } - }; - batchTransaction.onabort = onError; - batchTransaction.onerror = onError; - - var onItemSuccess = function (event) { - var record = dataArray[index++]; - record[keyField] = event.target.result; - - count--; - if (count === 0 && !called) { - called = true; - hasSuccess = true; - } - }; + /** + * Runs a batch of put and/or remove operations on the store. + * + * @param {Array} dataArray An array of objects containing the operation to run + * and the data object (for put operations). + * @param {Function} [onSuccess] A callback that is called if all operations + * were successful. + * @param {Function} [onError] A callback that is called if an error + * occurred during one of the operations. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.batch = function(dataArray, onSuccess, onError) { + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + + if (Object.prototype.toString.call(dataArray) != '[object Array]') { + onError(new Error('dataArray argument must be of type Array.')); + } else if (dataArray.length === 0) { + return onSuccess(true); + } - dataArray.forEach(function (record) { - var key = record.key; + var count = dataArray.length; + var called = false; + var hasSuccess = false; + + var batchTransaction = this.db.transaction([this.name], consts.READ_WRITE); + batchTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(hasSuccess); + }; + batchTransaction.onabort = onError; + batchTransaction.onerror = onError; + + + var onItemSuccess = function() { + count--; + if (count === 0 && !called) { + called = true; + hasSuccess = true; + } + }; + + dataArray.forEach(function(operation) { + var type = operation.type; + var key = operation.key; + var value = operation.value; + + var onItemError = function(err) { + batchTransaction.abort(); + if (!called) { + called = true; + onError(err, type, key); + } + }; + + if (type == 'remove') { + var deleteRequest = batchTransaction.objectStore(this.name)['delete'](key); + deleteRequest.onsuccess = onItemSuccess; + deleteRequest.onerror = onItemError; + } else if (type == 'put') { + var putRequest; + if (this.keyPath !== null) { // in-line keys + this._addIdPropertyIfNeeded(value); + putRequest = batchTransaction.objectStore(this.name).put(value); + } else { // out-of-line keys + putRequest = batchTransaction.objectStore(this.name).put(value, key); + } + putRequest.onsuccess = onItemSuccess; + putRequest.onerror = onItemError; + } + }, this); + + return batchTransaction; + }; - var onItemError = function (err) { - batchTransaction.abort(); - if (!called) { - called = true; - onError(err); - } + /** + * Takes an array of objects and stores them in a single transaction. + * + * @param {Array} dataArray An array of objects to store + * @param {Function} [onSuccess] A callback that is called if all operations + * were successful. + * @param {Function} [onError] A callback that is called if an error + * occurred during one of the operations. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.putBatch = function(dataArray, onSuccess, onError) { + var batchData = dataArray.map(function(item) { + return {type: 'put', value: item}; + }); + + return this.batch(batchData, onSuccess, onError); }; - var putRequest; - if (this.keyPath !== null) { // in-line keys - this._addIdPropertyIfNeeded(record); - putRequest = batchTransaction.objectStore(this.storeName).put(record); - } else { // out-of-line keys - putRequest = batchTransaction.objectStore(this.storeName).put(record, key); - } - putRequest.onsuccess = onItemSuccess; - putRequest.onerror = onItemError; - }, this); + /** + * Like putBatch, takes an array of objects and stores them in a single + * transaction, but allows processing of the result values. Returns the + * processed records containing the key for newly created records to the + * onSuccess calllback instead of only returning true or false for success. + * In addition, added the option for the caller to specify a key field that + * should be set to the newly created key. + * + * @param {Array} dataArray An array of objects to store + * @param {Object} [options] An object containing optional options + * @param {String} [options.keyField=this.keyPath] Specifies a field in the record to update + * with the auto-incrementing key. Defaults to the store's keyPath. + * @param {Function} [onSuccess] A callback that is called if all operations + * were successful. + * @param {Function} [onError] A callback that is called if an error + * occurred during one of the operations. + * @returns {IDBTransaction} The transaction used for this operation. + * + */ + this.upsertBatch = function(dataArray, options, onSuccess, onError) { + // handle `dataArray, onSuccess, onError` signature + if (typeof options == 'function') { + onSuccess = options; + onError = onSuccess; + options = {}; + } - return batchTransaction; - }, + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + options || (options = {}); - /** - * Takes an array of keys and removes matching objects in a single - * transaction. - * - * @param {Array} keyArray An array of keys to remove - * @param {Function} [onSuccess] A callback that is called if all operations - * were successful. - * @param {Function} [onError] A callback that is called if an error - * occurred during one of the operations. - * @returns {IDBTransaction} The transaction used for this operation. - */ - removeBatch: function (keyArray, onSuccess, onError) { - var batchData = keyArray.map(function(key){ - return { type: 'remove', key: key }; - }); - - return this.batch(batchData, onSuccess, onError); - }, + if (Object.prototype.toString.call(dataArray) != '[object Array]') { + onError(new Error('dataArray argument must be of type Array.')); + } - /** - * Takes an array of keys and fetches matching objects - * - * @param {Array} keyArray An array of keys identifying the objects to fetch - * @param {Function} [onSuccess] A callback that is called if all operations - * were successful. - * @param {Function} [onError] A callback that is called if an error - * occurred during one of the operations. - * @param {String} [arrayType='sparse'] The type of array to pass to the - * success handler. May be one of 'sparse', 'dense' or 'skip'. Defaults to - * 'sparse'. This parameter specifies how to handle the situation if a get - * operation did not throw an error, but there was no matching object in - * the database. In most cases, 'sparse' provides the most desired - * behavior. See the examples for details. - * @returns {IDBTransaction} The transaction used for this operation. - * @example - // given that there are two objects in the database with the keypath - // values 1 and 2, and the call looks like this: - myStore.getBatch([1, 5, 2], onError, function (data) { … }, arrayType); + var keyField = options.keyField || this.keyPath; + var count = dataArray.length; + var called = false; + var hasSuccess = false; + var index = 0; // assume success callbacks are executed in order + + var batchTransaction = this.db.transaction([this.name], consts.READ_WRITE); + batchTransaction.oncomplete = function() { + if (hasSuccess) { + onSuccess(dataArray); + } else { + onError(false); + } + }; + batchTransaction.onabort = onError; + batchTransaction.onerror = onError; + + var onItemSuccess = function(event) { + var record = dataArray[index++]; + record[keyField] = event.target.result; + + count--; + if (count === 0 && !called) { + called = true; + hasSuccess = true; + } + }; + + dataArray.forEach(function(record) { + var key = record.key; + + var onItemError = function(err) { + batchTransaction.abort(); + if (!called) { + called = true; + onError(err); + } + }; + + var putRequest; + if (this.keyPath !== null) { // in-line keys + this._addIdPropertyIfNeeded(record); + putRequest = batchTransaction.objectStore(this.name).put(record); + } else { // out-of-line keys + putRequest = batchTransaction.objectStore(this.name).put(record, key); + } + putRequest.onsuccess = onItemSuccess; + putRequest.onerror = onItemError; + }, this); + + return batchTransaction; + }; - // this is what the `data` array will be like: + /** + * Takes an array of keys and removes matching objects in a single + * transaction. + * + * @param {Array} keyArray An array of keys to remove + * @param {Function} [onSuccess] A callback that is called if all operations + * were successful. + * @param {Function} [onError] A callback that is called if an error + * occurred during one of the operations. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.removeBatch = function(keyArray, onSuccess, onError) { + var batchData = keyArray.map(function(key) { + return {type: 'remove', key: key}; + }); + + return this.batch(batchData, onSuccess, onError); + }; - // arrayType == 'sparse': - // data is a sparse array containing two entries and having a length of 3: - [Object, 2: Object] + /** + * Takes an array of keys and fetches matching objects + * + * @param {Array} keyArray An array of keys identifying the objects to fetch + * @param {Function} [onSuccess] A callback that is called if all operations + * were successful. + * @param {Function} [onError] A callback that is called if an error + * occurred during one of the operations. + * @param {String} [arrayType='sparse'] The type of array to pass to the + * success handler. May be one of 'sparse', 'dense' or 'skip'. Defaults to + * 'sparse'. This parameter specifies how to handle the situation if a get + * operation did not throw an error, but there was no matching object in + * the database. In most cases, 'sparse' provides the most desired + * behavior. See the examples for details. + * @returns {IDBTransaction} The transaction used for this operation. + * @example + // given that there are two objects in the database with the keypath + // values 1 and 2, and the call looks like this: + myStore.getBatch([1, 5, 2], onError, function (data) { … }, arrayType); + + // this is what the `data` array will be like: + + // arrayType == 'sparse': + // data is a sparse array containing two entries and having a length of 3: + [Object, 2: Object] 0: Object 2: Object length: 3 __proto__: Array[0] - // calling forEach on data will result in the callback being called two - // times, with the index parameter matching the index of the key in the - // keyArray. - - // arrayType == 'dense': - // data is a dense array containing three entries and having a length of 3, - // where data[1] is of type undefined: - [Object, undefined, Object] + // calling forEach on data will result in the callback being called two + // times, with the index parameter matching the index of the key in the + // keyArray. + + // arrayType == 'dense': + // data is a dense array containing three entries and having a length of 3, + // where data[1] is of type undefined: + [Object, undefined, Object] 0: Object 1: undefined 2: Object length: 3 __proto__: Array[0] - // calling forEach on data will result in the callback being called three - // times, with the index parameter matching the index of the key in the - // keyArray, but the second call will have undefined as first argument. + // calling forEach on data will result in the callback being called three + // times, with the index parameter matching the index of the key in the + // keyArray, but the second call will have undefined as first argument. - // arrayType == 'skip': - // data is a dense array containing two entries and having a length of 2: - [Object, Object] + // arrayType == 'skip': + // data is a dense array containing two entries and having a length of 2: + [Object, Object] 0: Object 1: Object length: 2 __proto__: Array[0] - // calling forEach on data will result in the callback being called two - // times, with the index parameter not matching the index of the key in the - // keyArray. - */ - getBatch: function (keyArray, onSuccess, onError, arrayType) { - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - arrayType || (arrayType = 'sparse'); - - if (Object.prototype.toString.call(keyArray) != '[object Array]'){ - onError(new Error('keyArray argument must be of type Array.')); - } else if (keyArray.length === 0) { - return onSuccess([]); - } - - var data = []; - var count = keyArray.length; - var called = false; - var hasSuccess = false; - var result = null; - - var batchTransaction = this.db.transaction([this.storeName] , this.consts.READ_ONLY); - batchTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - batchTransaction.onabort = onError; - batchTransaction.onerror = onError; - - var onItemSuccess = function (event) { - if (event.target.result || arrayType == 'dense') { - data.push(event.target.result); - } else if (arrayType == 'sparse') { - data.length++; - } - count--; - if (count === 0) { - called = true; - hasSuccess = true; - result = data; - } - }; - - keyArray.forEach(function (key) { + // calling forEach on data will result in the callback being called two + // times, with the index parameter not matching the index of the key in the + // keyArray. + */ + this.getBatch = function(keyArray, onSuccess, onError, arrayType) { + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + arrayType || (arrayType = 'sparse'); + + if (Object.prototype.toString.call(keyArray) != '[object Array]') { + onError(new Error('keyArray argument must be of type Array.')); + } else if (keyArray.length === 0) { + return onSuccess([]); + } - var onItemError = function (err) { - called = true; - result = err; - onError(err); - batchTransaction.abort(); + var data = []; + var count = keyArray.length; + var called = false; + var hasSuccess = false; + var result = null; + + var batchTransaction = this.db.transaction([this.name], consts.READ_ONLY); + batchTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + batchTransaction.onabort = onError; + batchTransaction.onerror = onError; + + var onItemSuccess = function(event) { + if (event.target.result || arrayType == 'dense') { + data.push(event.target.result); + } else if (arrayType == 'sparse') { + data.length++; + } + count--; + if (count === 0) { + called = true; + hasSuccess = true; + result = data; + } + }; + + keyArray.forEach(function(key) { + + var onItemError = function(err) { + called = true; + result = err; + onError(err); + batchTransaction.abort(); + }; + + var getRequest = batchTransaction.objectStore(this.name).get(key); + getRequest.onsuccess = onItemSuccess; + getRequest.onerror = onItemError; + + }, this); + + return batchTransaction; }; - var getRequest = batchTransaction.objectStore(this.storeName).get(key); - getRequest.onsuccess = onItemSuccess; - getRequest.onerror = onItemError; - - }, this); + /** + * Fetches all entries in the store. + * + * @param {Function} [onSuccess] A callback that is called if the operation + * was successful. Will receive an array of objects. + * @param {Function} [onError] A callback that will be called if an error + * occurred during the operation. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.getAll = function(onSuccess, onError) { + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + var getAllTransaction = this.db.transaction([this.name], consts.READ_ONLY); + var store = getAllTransaction.objectStore(this.name); + if (store.getAll) { + this._getAllNative(getAllTransaction, store, onSuccess, onError); + } else { + this._getAllCursor(getAllTransaction, store, onSuccess, onError); + } - return batchTransaction; - }, + return getAllTransaction; + }; - /** - * Fetches all entries in the store. - * - * @param {Function} [onSuccess] A callback that is called if the operation - * was successful. Will receive an array of objects. - * @param {Function} [onError] A callback that will be called if an error - * occurred during the operation. - * @returns {IDBTransaction} The transaction used for this operation. - */ - getAll: function (onSuccess, onError) { - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY); - var store = getAllTransaction.objectStore(this.storeName); - if (store.getAll) { - this._getAllNative(getAllTransaction, store, onSuccess, onError); - } else { - this._getAllCursor(getAllTransaction, store, onSuccess, onError); - } - - return getAllTransaction; - }, + /** + * Implements getAll for IDB implementations that have a non-standard + * getAll() method. + * + * @param {Object} getAllTransaction An open READ transaction. + * @param {Object} store A reference to the store. + * @param {Function} onSuccess A callback that will be called if the + * operation was successful. + * @param {Function} onError A callback that will be called if an + * error occurred during the operation. + * @private + */ + this._getAllNative = function(getAllTransaction, store, onSuccess, onError) { + var hasSuccess = false, + result = null; + + getAllTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + getAllTransaction.onabort = onError; + getAllTransaction.onerror = onError; + + var getAllRequest = store.getAll(); + getAllRequest.onsuccess = function(event) { + hasSuccess = true; + result = event.target.result; + }; + getAllRequest.onerror = onError; + }; - /** - * Implements getAll for IDB implementations that have a non-standard - * getAll() method. - * - * @param {Object} getAllTransaction An open READ transaction. - * @param {Object} store A reference to the store. - * @param {Function} onSuccess A callback that will be called if the - * operation was successful. - * @param {Function} onError A callback that will be called if an - * error occurred during the operation. - * @private - */ - _getAllNative: function (getAllTransaction, store, onSuccess, onError) { - var hasSuccess = false, - result = null; - - getAllTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - getAllTransaction.onabort = onError; - getAllTransaction.onerror = onError; - - var getAllRequest = store.getAll(); - getAllRequest.onsuccess = function (event) { - hasSuccess = true; - result = event.target.result; - }; - getAllRequest.onerror = onError; - }, + /** + * Implements getAll for IDB implementations that do not have a getAll() + * method. + * + * @param {Object} getAllTransaction An open READ transaction. + * @param {Object} store A reference to the store. + * @param {Function} onSuccess A callback that will be called if the + * operation was successful. + * @param {Function} onError A callback that will be called if an + * error occurred during the operation. + * @private + */ + this._getAllCursor = function(getAllTransaction, store, onSuccess, onError) { + var all = [], + hasSuccess = false, + result = null; + + getAllTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + getAllTransaction.onabort = onError; + getAllTransaction.onerror = onError; + + var cursorRequest = store.openCursor(); + cursorRequest.onsuccess = function(event) { + var cursor = event.target.result; + if (cursor) { + all.push(cursor.value); + cursor['continue'](); + } + else { + hasSuccess = true; + result = all; + } + }; + cursorRequest.onError = onError; + }; - /** - * Implements getAll for IDB implementations that do not have a getAll() - * method. - * - * @param {Object} getAllTransaction An open READ transaction. - * @param {Object} store A reference to the store. - * @param {Function} onSuccess A callback that will be called if the - * operation was successful. - * @param {Function} onError A callback that will be called if an - * error occurred during the operation. - * @private - */ - _getAllCursor: function (getAllTransaction, store, onSuccess, onError) { - var all = [], - hasSuccess = false, - result = null; - - getAllTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - getAllTransaction.onabort = onError; - getAllTransaction.onerror = onError; - - var cursorRequest = store.openCursor(); - cursorRequest.onsuccess = function (event) { - var cursor = event.target.result; - if (cursor) { - all.push(cursor.value); - cursor['continue'](); - } - else { - hasSuccess = true; - result = all; - } - }; - cursorRequest.onError = onError; - }, + /** + * Clears the store, i.e. deletes all entries in the store. + * + * @param {Function} [onSuccess] A callback that will be called if the + * operation was successful. + * @param {Function} [onError] A callback that will be called if an + * error occurred during the operation. + * @returns {IDBTransaction} The transaction used for this operation. + */ + this.clear = function(onSuccess, onError) { + onError || (onError = defaultErrorHandler); + onSuccess || (onSuccess = defaultSuccessHandler); + + var hasSuccess = false, + result = null; + + var clearTransaction = this.db.transaction([this.name], consts.READ_WRITE); + clearTransaction.oncomplete = function() { + var callback = hasSuccess ? onSuccess : onError; + callback(result); + }; + clearTransaction.onabort = onError; + clearTransaction.onerror = onError; + + var clearRequest = clearTransaction.objectStore(this.name).clear(); + clearRequest.onsuccess = function(event) { + hasSuccess = true; + result = event.target.result; + }; + clearRequest.onerror = onError; - /** - * Clears the store, i.e. deletes all entries in the store. - * - * @param {Function} [onSuccess] A callback that will be called if the - * operation was successful. - * @param {Function} [onError] A callback that will be called if an - * error occurred during the operation. - * @returns {IDBTransaction} The transaction used for this operation. - */ - clear: function (onSuccess, onError) { - onError || (onError = defaultErrorHandler); - onSuccess || (onSuccess = defaultSuccessHandler); - - var hasSuccess = false, - result = null; - - var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE); - clearTransaction.oncomplete = function () { - var callback = hasSuccess ? onSuccess : onError; - callback(result); - }; - clearTransaction.onabort = onError; - clearTransaction.onerror = onError; - - var clearRequest = clearTransaction.objectStore(this.storeName).clear(); - clearRequest.onsuccess = function (event) { - hasSuccess = true; - result = event.target.result; - }; - clearRequest.onerror = onError; - - return clearTransaction; - }, + return clearTransaction; + }; - /** - * Checks if an id property needs to present on a object and adds one if - * necessary. - * - * @param {Object} dataObj The data object that is about to be stored - * @private - */ - _addIdPropertyIfNeeded: function (dataObj) { - if (typeof dataObj[this.keyPath] == 'undefined') { - dataObj[this.keyPath] = this._insertIdCount++ + Date.now(); - } - }, + /** + * Checks if an id property needs to present on a object and adds one if + * necessary. + * + * @param {Object} dataObj The data object that is about to be stored + * @private + */ + this._addIdPropertyIfNeeded = function(dataObj) { + if (typeof dataObj[this.keyPath] == 'undefined') { + dataObj[this.keyPath] = this._insertIdCount++ + Date.now(); + } + }; - /************ - * indexing * - ************/ + /************ + * indexing * + ************/ + + /** + * Returns a DOMStringList of index names of the store. + * + * @return {DOMStringList} The list of index names + */ + this.getIndexList = function() { + return this.store.indexNames; + }; - /** - * Returns a DOMStringList of index names of the store. - * - * @return {DOMStringList} The list of index names - */ - getIndexList: function () { - return this.store.indexNames; - }, + /** + * Checks if an index with the given name exists in the store. + * + * @param {String} indexName The name of the index to look for + * @return {Boolean} Whether the store contains an index with the given name + */ + this.hasIndex = function(indexName) { + return this.store.indexNames.contains(indexName); + }; - /** - * Checks if an index with the given name exists in the store. - * - * @param {String} indexName The name of the index to look for - * @return {Boolean} Whether the store contains an index with the given name - */ - hasIndex: function (indexName) { - return this.store.indexNames.contains(indexName); - }, + /** + * Normalizes an object containing index data and assures that all + * properties are set. + * + * @param {Object} indexData The index data object to normalize + * @param {String} indexData.name The name of the index + * @param {String} [indexData.keyPath] The key path of the index + * @param {Boolean} [indexData.unique] Whether the index is unique + * @param {Boolean} [indexData.multiEntry] Whether the index is multi entry + */ + this.normalizeIndexData = function(indexData) { + indexData.keyPath = indexData.keyPath || indexData.name; + indexData.unique = !!indexData.unique; + indexData.multiEntry = !!indexData.multiEntry; + }; - /** - * Normalizes an object containing index data and assures that all - * properties are set. - * - * @param {Object} indexData The index data object to normalize - * @param {String} indexData.name The name of the index - * @param {String} [indexData.keyPath] The key path of the index - * @param {Boolean} [indexData.unique] Whether the index is unique - * @param {Boolean} [indexData.multiEntry] Whether the index is multi entry - */ - normalizeIndexData: function (indexData) { - indexData.keyPath = indexData.keyPath || indexData.name; - indexData.unique = !!indexData.unique; - indexData.multiEntry = !!indexData.multiEntry; - }, + /** + * Checks if an actual index complies with an expected index. + * + * @param {Object} actual The actual index found in the store + * @param {Object} expected An Object describing an expected index + * @return {Boolean} Whether both index definitions are identical + */ + this.indexComplies = function(actual, expected) { + var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key) { + // IE10 returns undefined for no multiEntry + if (key == 'multiEntry' && actual[key] === undefined && expected[key] === false) { + return true; + } + // Compound keys + if (key == 'keyPath' && Object.prototype.toString.call(expected[key]) == '[object Array]') { + var exp = expected.keyPath; + var act = actual.keyPath; + + // IE10 can't handle keyPath sequences and stores them as a string. + // The index will be unusable there, but let's still return true if + // the keyPath sequence matches. + if (typeof act == 'string') { + return exp.toString() == act; + } + + // Chrome/Opera stores keyPath squences as DOMStringList, Firefox + // as Array + if (!(typeof act.contains == 'function' || typeof act.indexOf == 'function')) { + return false; + } + + if (act.length !== exp.length) { + return false; + } + + for (var i = 0, m = exp.length; i < m; i++) { + if (!( (act.contains && act.contains(exp[i])) || act.indexOf(exp[i] !== -1) )) { + return false; + } + } + return true; + } + return expected[key] == actual[key]; + }); + return complies; + }; - /** - * Checks if an actual index complies with an expected index. - * - * @param {Object} actual The actual index found in the store - * @param {Object} expected An Object describing an expected index - * @return {Boolean} Whether both index definitions are identical - */ - indexComplies: function (actual, expected) { - var complies = ['keyPath', 'unique', 'multiEntry'].every(function (key) { - // IE10 returns undefined for no multiEntry - if (key == 'multiEntry' && actual[key] === undefined && expected[key] === false) { - return true; - } - // Compound keys - if (key == 'keyPath' && Object.prototype.toString.call(expected[key]) == '[object Array]') { - var exp = expected.keyPath; - var act = actual.keyPath; - - // IE10 can't handle keyPath sequences and stores them as a string. - // The index will be unusable there, but let's still return true if - // the keyPath sequence matches. - if (typeof act == 'string') { - return exp.toString() == act; - } - - // Chrome/Opera stores keyPath squences as DOMStringList, Firefox - // as Array - if ( ! (typeof act.contains == 'function' || typeof act.indexOf == 'function') ) { - return false; - } - - if (act.length !== exp.length) { - return false; - } - - for (var i = 0, m = exp.length; i