/// /// var linq2indexedDB; var enableLogging = false; // Initializes the linq2indexeddb object. (function () { "use strict"; linq2indexedDB = function (name, configuration, enableDebugging) { /// Creates a new or opens an existing database for the given name /// The name of the database /// /// [Optional] provide comment /// /// var dbConfig = { autoGenerateAllowed: true }; if (name) { dbConfig.name = name; } if (configuration) { if (configuration.version) { dbConfig.version = configuration.version; } // From the moment the configuration is provided by the developper, autoGeneration isn't allowed. // If this would be allowed, the developper wouldn't be able to determine what to do for which version. if (configuration.schema) { var appVersion = dbConfig.version || -1; for (key in configuration.schema) { if (typeof key === "number") { appVersion = version > key ? version : key; } } if (version > -1) { dbConfig.autoGenerateAllowed = false; dbConfig.version = appVersion; dbConfig.schema = configuration.schema; } } if (configuration.definition) { dbConfig.autoGenerateAllowed = false; dbConfig.definition = configuration.definition; } if (configuration.onupgradeneeded) { dbConfig.autoGenerateAllowed = false; dbConfig.onupgradeneeded = configuration.onupgradeneeded; } if (configuration.oninitializeversion) { dbConfig.autoGenerateAllowed = false; dbConfig.oninitializeversion = configuration.oninitializeversion; } } var returnObject = { utilities: linq2indexedDB.prototype.utilities, core: linq2indexedDB.prototype.core, linq: linq(dbConfig), initialize: function () { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Initialize Started"); return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { linq2indexedDB.prototype.core.db(dbConfig.name, dbConfig.version).then(function (args) /*db*/ { var db = args[0]; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Close dbconnection"); db.close(); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Initialize Succesfull"); pw.complete(); }, pw.error, function (args) /*txn, e*/ { var txn = args[0]; var e = args[1]; if (e.type == "upgradeneeded") { upgradeDatabase(dbConfig, e.oldVersion, e.newVersion, txn); } }); }); }, deleteDatabase: function () { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { linq2indexedDB.prototype.core.deleteDb(dbConfig.name).then(function () { pw.complete(); }, pw.error); }); } }; enableLogging = enableDebugging; if (enableDebugging) { returnObject.viewer = viewer(dbConfig); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.warning, "Debugging enabled: be carefull when using in production enviroment. Complex objects get written to the log and may cause memory leaks.") } else { returnObject.viewer = null; } return returnObject; }; function linq(dbConfig) { var queryBuilderObj = function (objectStoreName) { this.from = objectStoreName; this.where = []; this.select = []; this.sortClauses = []; this.get = []; this.insert = []; this.merge = []; this.update = []; this.remove = []; this.clear = false; }; queryBuilderObj.prototype = { executeQuery: function () { executeQuery(this); } }; function from(queryBuilder, objectStoreName) { queryBuilder.from = objectStoreName; return { where: function (filter) { /// Filters the selected data. /// /// The filter argument can be a string (In this case the string represents the property name you want to filter on) or a function. /// (In this case the function will be used to filter the data. This callback function is called with 1 parameter: data /// ,this argument holds the data that has to be validated. The return type of the function must be a boolean.) /// return where(queryBuilder, filter, true, false); }, orderBy: function (propertyName) { /// Sorts the selected data ascending. /// The name of the property you want to sort on. return orderBy(queryBuilder, propertyName, false); }, orderByDesc: function (propertyName) { /// Sorts the selected data descending. /// The name of the property you want to sort on. return orderBy(queryBuilder, propertyName, true); }, select: function (propertyNames) { /// Selects the data. /// A list of the names of the properties you want to select. /// A list with the selected objects. return select(queryBuilder, propertyNames); }, insert: function (data, key) { /// inserts data. /// The object you want to insert. /// /// [Optional] The key of the data you want to insert. /// /// The object that was inserted. return insert(queryBuilder, data, key); }, update: function (data, key) { /// updates data. /// The object you want to update. /// /// [Optional] The key of the data you want to update. /// /// The object that was updated. return update(queryBuilder, data, key); }, merge: function (data, key) { /// merges data. /// The data you want to merge. /// /// The key of the data you want to update. /// /// The object that was updated. return merge(queryBuilder, data, key); }, remove: function (key) { /// Removes data from the objectstore by his key. /// The key of the object you want to remove. return remove(queryBuilder, key); }, clear: function () { /// Removes all data from the objectstore. return clear(queryBuilder); }, get: function (key) { /// Gets an object by his key. /// The key of the object you want to retrieve. /// The object that has the provided key. return get(queryBuilder, key); } }; } function where(queryBuilder, filter, isAndClause, isOrClause, isNotClause) { var whereClauses = {}; var filterMetaData; if (isNotClause === "undefined") { whereClauses.not = function () { return where(queryBuilder, filter, isAndClause, isOrClause, true); }; } if (typeof filter === "function") { filterMetaData = { propertyName: filter, isOrClause: isOrClause, isAndClause: isAndClause, isNotClause: (isNotClause === "undefined" ? false : isNotClause), filter: linq2indexedDB.prototype.linq.createFilter("anonymous" + queryBuilder.where.length, filter, null) }; return whereClause(queryBuilder, filterMetaData); } else if (typeof filter === "string") { // Builds up the where filter methodes for (var filterName in linq2indexedDB.prototype.linq.filters) { filterMetaData = { propertyName: filter, isOrClause: isOrClause, isAndClause: isAndClause, isNotClause: (typeof isNotClause === "undefined" ? false : isNotClause), filter: linq2indexedDB.prototype.linq.filters[filterName] }; if (typeof linq2indexedDB.prototype.linq.filters[filterName].filter !== "function") { throw "Linq2IndexedDB: a filter methods needs to be provided for the filter '" + filterName + "'"; } if (typeof linq2indexedDB.prototype.linq.filters[filterName].name === "undefined") { throw "Linq2IndexedDB: a filter name needs to be provided for the filter '" + filterName + "'"; } whereClauses[linq2indexedDB.prototype.linq.filters[filterName].name] = linq2indexedDB.prototype.linq.filters[filterName].filter(whereClause, queryBuilder, filterMetaData); } } return whereClauses; } function whereClause(queryBuilder, filterMetaData) { queryBuilder.where.push(filterMetaData); return { and: function (filter) { /// Adds an extra filter. /// /// The filter argument can be a string (In this case the string represents the property name you want to filter on) or a function. /// (In this case the function will be used to filter the data. This callback function is called with 1 parameter: data /// ,this argument holds the data that has to be validated. The return type of the function must be a boolean.) /// return where(queryBuilder, filter, true, false); }, or: function (filter) { /// Adds an extra filter. /// /// The filter argument can be a string (In this case the string represents the property name you want to filter on) or a function. /// (In this case the function will be used to filter the data. This callback function is called with 1 parameter: data /// ,this argument holds the data that has to be validated. The return type of the function must be a boolean.) /// return where(queryBuilder, filter, false, true); }, orderBy: function (propertyName) { /// Sorts the selected data ascending. /// The name of the property you want to sort on. return orderBy(queryBuilder, propertyName, false); }, orderByDesc: function (propertyName) { /// Sorts the selected data descending. /// The name of the property you want to sort on. return orderBy(queryBuilder, propertyName, true); }, select: function (propertyNames) { /// Selects the data. /// A list of the names of the properties you want to select. return select(queryBuilder, propertyNames); }, remove: function () { return remove(queryBuilder); }, merge: function (data) { return merge(queryBuilder, data); } }; } function orderBy(queryBuilder, propName, descending) { queryBuilder.sortClauses.push({ propertyName: propName, descending: descending }); return { orderBy: function (propertyName) { /// Sorts the selected data ascending. /// The name of the property you want to sort on. return orderBy(queryBuilder, propertyName, false); }, orderByDesc: function (propertyName) { /// Sorts the selected data descending. /// The name of the property you want to sort on. return orderBy(queryBuilder, propertyName, true); }, select: function (propertyNames) { /// Selects the data. /// A list of the names of the properties you want to select. return select(queryBuilder, propertyNames); } }; } function select(queryBuilder, propertyNames) { if (propertyNames) { if (!linq2indexedDB.prototype.utilities.isArray(propertyNames)) { propertyNames = [propertyNames]; } for (var i = 0; i < propertyNames.length; i++) { queryBuilder.select.push(propertyNames[i]); } } return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { var returnData = []; executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, executeWhere).then(function () { pw.complete(this, returnData); },pw.error, function(args) { var obj = selectData(args[0].data, queryBuilder.select); returnData.push(obj); pw.progress(this, obj /*[obj]*/); }); }); } function insert(queryBuilder, data, key) { queryBuilder.insert.push({ data: data, key: key }); return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) { var objectStorePromis = linq2indexedDB.prototype.core.objectStore(transaction, qb.from); if (linq2indexedDB.prototype.utilities.isArray(qb.insert[0].data) && !qb.insert[0].key) { var returnData = []; for (var i = 0; i < qb.insert[0].data.length; i++) { linq2indexedDB.prototype.core.insert(objectStorePromis, qb.insert[0].data[i]).then(function (args /*storedData, storedkey*/) { pw.progress(this, {object: args[0], key: args[1]}/*[storedData, storedkey]*/); returnData.push({ object: args[0], key: args[1] }); if (returnData.length == qb.insert[0].data.length) { pw.complete(this, returnData); } }, pw.error); } } else { linq2indexedDB.prototype.core.insert(objectStorePromis, qb.insert[0].data, qb.insert[0].key).then(function(args /*storedData, storedkey*/) { pw.complete(this, {object: args[0], key: args[1]} /*[storedData, storedkey]*/); }, pw.error); } }); } function update(queryBuilder, data, key) { queryBuilder.update.push({ data: data, key: key }); return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) { linq2indexedDB.prototype.core.update(linq2indexedDB.prototype.core.objectStore(transaction, qb.from), qb.update[0].data, qb.update[0].key).then(function (args /*storedData, storedkey*/) { pw.complete(this, {object: args[0], key: args[1]} /*[storedData, storedkey]*/); }, pw.error); }); } function merge(queryBuilder, data, key) { queryBuilder.merge.push({ data: data, key: key }); if (key) { return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function(qb, pw, transaction) { var objectStore = linq2indexedDB.prototype.core.objectStore(transaction, qb.from); var obj = null; linq2indexedDB.prototype.core.cursor(objectStore, IDBKeyRange.only(qb.merge[0].key)).then(function() { }, pw.error, function(args /*data*/) { obj = args[0].data; for (var prop in qb.merge[0].data) { obj[prop] = qb.merge[0].data[prop]; } args[0].update(obj); pw.complete(this, obj); }, pw.error); }); } else { var returnData = []; return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, executeWhere).then(function (args) { if (returnData.length > 0) { pw.complete(this, returnData); } else { executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, promise, transaction) { linq2indexedDB.prototype.core.objectStore(transaction, qb.from).then(function (objectStoreArgs) { for (var i = 0; i < args.length; i++) { var obj = args[i]; for (var prop in queryBuilder.merge[0].data) { obj[prop] = queryBuilder.merge[0].data[prop]; } linq2indexedDB.prototype.core.update(objectStoreArgs[1], obj).then(function (args1 /*data*/) { pw.progress(this, args1[0] /*[data]*/); returnData.push(args1[0]); if (returnData.length == args.length) { promise.complete(this, returnData); } }, promise.error); } }, promise.error); }).then(pw.complete, pw.error, pw.progress); } }, null, function (args) { if (args[0].update) { var obj = args[0].data; for (var prop in queryBuilder.merge[0].data) { obj[prop] = queryBuilder.merge[0].data[prop]; } args[0].update(obj); pw.progress(this, obj); returnData.push(obj); } }); }); } } function remove(queryBuilder, key) { if (key) { queryBuilder.remove.push({ key: key }); return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) { linq2indexedDB.prototype.core.remove(linq2indexedDB.prototype.core.objectStore(transaction, qb.from), qb.remove[0].key).then(function () { pw.complete(this, queryBuilder.remove[0].key /*[queryBuilder.remove[0].key]*/); }, pw.error); }); } else { var cursorDelete = false; return linq2indexedDB.prototype.utilities.promiseWrapper(function(pw) { executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, executeWhere).then(function (data) { if (cursorDelete) { pw.complete(this); } else { executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, promise, transaction) { linq2indexedDB.prototype.core.objectStore(transaction, qb.from).then(function (objectStoreArgs) { var itemsDeleted = 0; for (var i = 0; i < data.length; i++) { linq2indexedDB.prototype.core.remove(objectStoreArgs[1], linq2indexedDB.prototype.utilities.getPropertyValue(data[i], objectStoreArgs[1].keyPath)).then(function(args1 /*data*/) { pw.progress(this, args1[0] /*[data]*/); if (++itemsDeleted == data.length) { promise.complete(this); } }, promise.error); } }, promise.error); }).then(pw.complete, pw.error, pw.progress); } }, null, function(args) { if (args[0].remove) { args[0].remove(); pw.progress(this); cursorDelete = true; } }); }); } } function clear(queryBuilder) { queryBuilder.clear = true; return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) { linq2indexedDB.prototype.core.clear(linq2indexedDB.prototype.core.objectStore(transaction, qb.from)).then(function () { pw.complete(this); }, pw.error); }); } function get(queryBuilder, key) { queryBuilder.get.push({ key: key }); return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_ONLY, function (qb, pw, transaction) { linq2indexedDB.prototype.core.get(linq2indexedDB.prototype.core.objectStore(transaction, qb.from), qb.get[0].key).then(function (args /*data*/) { pw.complete(this, args[0] /*[data]*/); }, pw.error); }); } function executeQuery(queryBuilder, transactionType, callBack) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { // Create DB connection linq2indexedDB.prototype.core.db(dbConfig.name, dbConfig.version).then(function (args /* [db, event] */) { // Opening a transaction linq2indexedDB.prototype.core.transaction(args[0], queryBuilder.from, transactionType, dbConfig.autoGenerateAllowed).then(function (transactionArgs /* [transaction] */) { var txn = transactionArgs[0]; txn.db.close(); // call complete if it isn't called already //pw.complete(); }, pw.error, function (transactionArgs /* [transaction] */) { callBack(queryBuilder, pw, transactionArgs[0]); }); } , pw.error , function (args /*txn, e*/) { var txn = args[0]; var e = args[1]; // Upgrading the database to the correct version if (e.type == "upgradeneeded") { upgradeDatabase(dbConfig, e.oldVersion, e.newVersion, txn); } }); }); } function executeWhere(queryBuilder, pw, transaction) { linq2indexedDB.prototype.core.objectStore(transaction, queryBuilder.from).then(function (objArgs) { try { var objectStore = objArgs[1]; var whereClauses = queryBuilder.where || []; var returnData = []; var cursorPromise = determineCursor(objectStore, whereClauses); cursorPromise.then( function (args1 /*data*/) { var data = args1[0]; linq2indexedDB.prototype.utilities.linq2indexedDBWorker(data, whereClauses, queryBuilder.sortClauses).then(function (d) { // No need to notify again if it allready happend in the onProgress method of the cursor. if (returnData.length == 0) { for (var j = 0; j < d.length; j++) { pw.progress(this, [d[j]] /*[obj]*/); } } pw.complete(this, d /*[returnData]*/); }); }, pw.error, function (args1 /*data*/) { // When there are no more where clauses to fulfill and the collection doesn't need to be sorted, the data can be returned. // In the other case let the complete handle it. if (whereClauses.length == 0 && queryBuilder.sortClauses.length == 0) { returnData.push({ data: args1[0].data, key: args1[0].key }); pw.progress(this, args1 /*[obj]*/); } } ); } catch (ex) { // Handle errors like an invalid keyRange. linq2indexedDB.prototype.core.abortTransaction(args[0]); pw.error(this, [ex.message, ex]); } }, pw.error); } function determineCursor(objectStore, whereClauses) { var cursorPromise; // Checks if an indexeddb filter can be used if (whereClauses.length > 0 && !whereClauses[0].isNotClause && whereClauses[0].filter.indexeddbFilter && (whereClauses.length == 1 || (whereClauses.length > 1 && !whereClauses[1].isOrClause))) { var source = objectStore; var indexPossible = dbConfig.autoGenerateAllowed || objectStore.indexNames.contains(whereClauses[0].propertyName + linq2indexedDB.prototype.core.indexSuffix); // Checks if we can use an index if (whereClauses[0].propertyName != objectStore.keyPath && indexPossible) { source = linq2indexedDB.prototype.core.index(objectStore, whereClauses[0].propertyName, dbConfig.autoGenerateAllowed); } // Checks if we can use indexeddb filter if (whereClauses[0].propertyName == objectStore.keyPath || indexPossible) { // Gets the where clause + removes it from the collection var clause = whereClauses.shift(); switch (clause.filter) { case linq2indexedDB.prototype.linq.filters.equals: cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.only(clause.value)); break; case linq2indexedDB.prototype.linq.filters.between: cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.bound(clause.minValue, clause.maxValue, clause.minValueIncluded, clause.maxValueIncluded)); break; case linq2indexedDB.prototype.linq.filters.greaterThan: cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.lowerBound(clause.value, clause.valueIncluded)); break; case linq2indexedDB.prototype.linq.filters.smallerThan: cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.upperBound(clause.value, clause.valueIncluded)); break; default: cursorPromise = linq2indexedDB.prototype.core.cursor(source); break; } } else { // Get everything if the index can't be used cursorPromise = linq2indexedDB.prototype.core.cursor(source); } } else { // Get's everything, manually filter data cursorPromise = linq2indexedDB.prototype.core.cursor(objectStore); } return cursorPromise; } function selectData(data, propertyNames) { if (propertyNames && propertyNames.length > 0) { if (!linq2indexedDB.prototype.utilities.isArray(propertyNames)) { propertyNames = [propertyNames]; } var obj = new Object(); for (var i = 0; i < propertyNames.length; i++) { linq2indexedDB.prototype.utilities.setPropertyValue(obj, propertyNames[i], linq2indexedDB.prototype.utilities.getPropertyValue(data, propertyNames[i])); } return obj; } return data; } return { from: function (objectStoreName) { return from(new queryBuilderObj(objectStoreName), objectStoreName); } }; } function viewer(dbConfig) { var dbView = {}; var refresh = true; function refreshInternal() { if (refresh) { refresh = false; getDbInformation(dbView, dbConfig); } } dbView.Configuration = { name: dbConfig.name, version: dbConfig.version, autoGenerateAllowed: dbConfig.autoGenerateAllowed, schema: dbConfig.schema, definition: dbConfig.definition, onupgradeneeded: dbConfig.onupgradeneeded, oninitializeversion: dbConfig.oninitializeversion }; dbView.refresh = function () { refresh = true; refreshInternal(); }; linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseUpgrade, function () { refresh = true; }); linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseOpened, function () { refreshInternal(); }); linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseRemoved, function () { dbView.name = null; dbView.version = null; dbView.ObjectStores = []; }); linq2indexedDB.prototype.core.dbDataChanged.addListener([linq2indexedDB.prototype.core.dataEvents.dataInserted, linq2indexedDB.prototype.core.dataEvents.dataRemoved, linq2indexedDB.prototype.core.dataEvents.dataUpdated, linq2indexedDB.prototype.core.dataEvents.objectStoreCleared], function () { dbView.refresh(); }); return dbView; } function getDbInformation(dbView, dbConfig) { linq2indexedDB.prototype.core.db(dbConfig.name).then(function () { var connection = arguments[0][0]; dbView.name = connection.name; dbView.version = connection.version; dbView.ObjectStores = []; linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseBlocked, function () { connection.close(); }); var objectStoreNames = []; for (var k = 0; k < connection.objectStoreNames.length; k++) { objectStoreNames.push(connection.objectStoreNames[k]); } if (objectStoreNames.length > 0) { linq2indexedDB.prototype.core.transaction(connection, objectStoreNames, linq2indexedDB.prototype.core.transactionTypes.READ_ONLY, false).then(null, null, function () { var transaction = arguments[0][0]; for (var i = 0; i < connection.objectStoreNames.length; i++) { linq2indexedDB.prototype.core.objectStore(transaction, connection.objectStoreNames[i]).then(function () { var objectStore = arguments[0][1]; var indexes = []; var objectStoreData = []; for (var j = 0; j < objectStore.indexNames.length; j++) { linq2indexedDB.prototype.core.index(objectStore, objectStore.indexNames[j], false).then(function () { var index = arguments[0][1]; var indexData = []; linq2indexedDB.prototype.core.cursor(index).then(null, null, function () { var data = arguments[0][0]; var key = arguments[0][1].primaryKey; indexData.push({ key: key, data: data }); }); indexes.push({ name: index.name, keyPath: index.keyPath, multiEntry: index.multiEntry, data: indexData }); }); } linq2indexedDB.prototype.core.cursor(objectStore).then(null, null, function () { var data = arguments[0][0]; var key = arguments[0][1].primaryKey; objectStoreData.push({ key: key, data: data }); }); dbView.ObjectStores.push({ name: objectStore.name, keyPath: objectStore.keyPath, autoIncrement: objectStore.autoIncrement, indexes: indexes, data: objectStoreData }); }); } }); } }, null, function (args) { if (args[1].type == "upgradeneeded") { args[0].abort(); } }); } function getVersionDefinition(version, definitions) { var result = null; for (var i = 0; i < definitions.length; i++) { if (parseInt(definitions[i].version) == parseInt(version)) { result = definitions[i]; } } return result; } function initializeVersion(txn, definition) { try { if (definition.objectStores) { for (var i = 0; i < definition.objectStores.length; i++) { var objectStoreDefinition = definition.objectStores[i]; if (objectStoreDefinition.remove) { linq2indexedDB.prototype.core.deleteObjectStore(txn, objectStoreDefinition.name); } else { linq2indexedDB.prototype.core.createObjectStore(txn, objectStoreDefinition.name, objectStoreDefinition.objectStoreOptions); } } } if (definition.indexes) { for (var j = 0; j < definition.indexes.length; j++) { var indexDefinition = definition.indexes[j]; if (indexDefinition.remove) { linq2indexedDB.prototype.core.deleteIndex(linq2indexedDB.prototype.core.objectStore(txn, indexDefinition.objectStoreName), indexDefinition.propertyName); } else { linq2indexedDB.prototype.core.createIndex(linq2indexedDB.prototype.core.objectStore(txn, indexDefinition.objectStoreName), indexDefinition.propertyName, indexDefinition.indexOptions); } } } if (definition.defaultData) { for (var k = 0; k < definition.defaultData.length; k++) { var defaultDataDefinition = definition.defaultData[k]; if (defaultDataDefinition.remove) { linq2indexedDB.prototype.core.remove(linq2indexedDB.prototype.core.objectStore(txn, defaultDataDefinition.objectStoreName), defaultDataDefinition.key); } else { linq2indexedDB.prototype.core.insert(linq2indexedDB.prototype.core.objectStore(txn, defaultDataDefinition.objectStoreName), defaultDataDefinition.data, defaultDataDefinition.key); } } } } catch (ex) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.exception, "initialize version exception: ", ex); linq2indexedDB.prototype.core.abortTransaction(txn); } } function upgradeDatabase(dbConfig, oldVersion, newVersion, txn) { if (dbConfig.onupgradeneeded) { dbConfig.onupgradeneeded(txn, oldVersion, newVersion); } if (dbConfig.oninitializeversion || dbConfig.schema || dbConfig.definition) { for (var version = oldVersion + 1; version <= newVersion; version++) { if (dbConfig.schema) { dbConfig.schema[version](txn); } if (dbConfig.definition) { var versionDefinition = getVersionDefinition(version, dbConfig.definition); if (versionDefinition) { initializeVersion(txn, versionDefinition); } } else if (dbConfig.oninitializeversion) { dbConfig.oninitializeversion(txn, version); } } } } })(); // Namespace linq2indexedDB.prototype.linq (function () { linq2indexedDB.prototype.linq = { addFilter: function (name, isValid, filterCallback) { if (typeof linq2indexedDB.prototype.linq.filters[name] !== 'undefined') { throw "linq2IndexedDB: A filter with the name '" + name + "' already exists."; } linq2indexedDB.prototype.linq.filters[name] = linq2indexedDB.prototype.linq.createFilter(name, isValid, filterCallback); }, createFilter: function (name, isValid, filterCallback) { if (typeof name === 'undefined') { throw "linq2IndexedDB: No name argument provided to the addFilter method."; } if (typeof name !== 'string') { throw "linq2IndexedDB: The name argument provided to the addFilterObject method must be a string."; } if (typeof isValid === 'undefined') { throw "linq2IndexedDB: No isValid argument provided to the addFilter method."; } if (typeof isValid !== 'function') { throw "linq2IndexedDB: The isValid argument provided to the addFilterObject method must be a function."; } if (typeof filterCallback === 'undefined') { throw "linq2IndexedDB: No filterCallback argument provided to the addFilter method."; } //if (typeof filterCallback !== 'function') { // throw "linq2IndexedDB: The filterCallback argument provided to the addFilterObject method must be a function."; //} return { name: name, indexeddbFilter: false, sortOrder: 99, isValid: isValid, filter: filterCallback }; }, filters: { equals: { name: "equals", indexeddbFilter: true, sortOrder: 0, isValid: function (data, filter) { return linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName) == filter.value; }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function (value) { if (typeof (value) === "undefined") { throw "linq2indexedDB: value needs to be provided to the equal clause"; } filterMetaData.value = value; return callback(queryBuilder, filterMetaData); }; } }, between: { name: "between", sortOrder: 1, indexeddbFilter: true, isValid: function (data, filter) { var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName); return (value > filter.minValue || (filter.minValueIncluded && value == filter.minValue)) && (value < filter.maxValue || (filter.maxValueIncluded && value == filter.maxValue)); }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function (minValue, maxValue, minValueIncluded, maxValueIncluded) { var isMinValueIncluded = typeof (minValueIncluded) === undefined ? false : minValueIncluded; var isMasValueIncluded = typeof (maxValueIncluded) === undefined ? false : maxValueIncluded; if (typeof (minValue) === "undefined") { throw "linq2indexedDB: minValue needs to be provided to the between clause"; } if (typeof (maxValue) === "undefined") { throw "linq2indexedDB: maxValue needs to be provided to the between clause"; } filterMetaData.minValue = minValue; filterMetaData.maxValue = maxValue; filterMetaData.minValueIncluded = isMinValueIncluded; filterMetaData.maxValueIncluded = isMasValueIncluded; return callback(queryBuilder, filterMetaData); }; } }, greaterThan: { name: "greaterThan", sortOrder: 2, indexeddbFilter: true, isValid: function (data, filter) { var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName); return value > filter.value || (filter.valueIncluded && value == filter.value); }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function (value, valueIncluded) { if (typeof (value) === "undefined") { throw "linq2indexedDB: value needs to be provided to the greatherThan clause"; } var isValueIncluded = typeof (valueIncluded) === undefined ? false : valueIncluded; filterMetaData.value = value; filterMetaData.valueIncluded = isValueIncluded; return callback(queryBuilder, filterMetaData); }; } }, smallerThan: { name: "smallerThan", sortOrder: 2, indexeddbFilter: true, isValid: function (data, filter) { var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName); return value < filter.value || (filter.valueIncluded && value == filter.value); }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function (value, valueIncluded) { if (typeof (value) === "undefined") { throw "linq2indexedDB: value needs to be provided to the smallerThan clause"; } var isValueIncluded = typeof (valueIncluded) === undefined ? false : valueIncluded; filterMetaData.value = value; filterMetaData.valueIncluded = isValueIncluded; return callback(queryBuilder, filterMetaData); }; } }, inArray: { name: "inArray", sortOrder: 3, indexeddbFilter: false, isValid: function (data, filter) { var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName); if (value) { return filter.value.indexOf(value) >= 0; } else { return false; } }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function (array) { if (typeof (array) === "undefined" || typeof array !== "Array") { throw "linq2indexedDB: array needs to be provided to the inArray clause"; } filterMetaData.value = array; return callback(queryBuilder, filterMetaData); }; } }, like: { name: "like", sortOrder: 4, indexeddbFilter: false, isValid: function (data, filter) { var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName); if (value) { return value.indexOf(filter.value) >= 0; } else { return false; } }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function (value) { if (typeof (value) === "undefined") { throw "linq2indexedDB: value needs to be provided to the like clause"; } filterMetaData.value = value; return callback(queryBuilder, filterMetaData); }; } }, isUndefined: { name: "isUndefined", sortOrder: 5, indexeddbFilter: false, isValid: function (data, filter) { return linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName) === undefined; }, filter: function (callback, queryBuilder, filterMetaData) { /// Creates a function to retrieve values for the filter and adds the filter to the querybuilder. /// /// Callback method so the query expression can be builded. /// /// /// The objects that builds up the query for the user. /// /// /// The metadata for the filter. /// /// /// returns a function to retrieve the necessary values for the filter /// return function () { return callback(queryBuilder, filterMetaData); }; } } } }; })(); // Namespace linq2indexedDB.prototype.utitlities (function (isMetroApp) { "use strict"; var utilities = { linq2indexedDBWorkerFileLocation: "/Scripts/Linq2IndexedDB.js", linq2indexedDBWorker: function (data, filters, sortClauses) { return utilities.promiseWrapper(function (pw) { if (!!window.Worker) { var worker = new Worker(utilities.linq2indexedDBWorkerFileLocation); worker.onmessage = function (event) { pw.complete(this, event.data); worker.terminate(); }; worker.onerror = pw.error; var filtersString = JSON.stringify(filters, linq2indexedDB.prototype.utilities.serialize); worker.postMessage({ data: data, filters: filtersString, sortClauses: sortClauses }); } else { // Fallback when there are no webworkers present. Beware, this runs on the UI thread and can block the UI pw.complete(this, utilities.filterSort(data, filters, sortClauses)); } }); }, isArray: function (array) { if (array instanceof Array) { return true; } else { return false; } }, JSONComparer: function (propertyName, descending) { return { sort: function (valueX, valueY) { if (descending) { return ((valueX[propertyName] == valueY[propertyName]) ? 0 : ((valueX[propertyName] > valueY[propertyName]) ? -1 : 1)); } else { return ((valueX[propertyName] == valueY[propertyName]) ? 0 : ((valueX[propertyName] > valueY[propertyName]) ? 1 : -1)); } } }; }, promiseWrapper: function (promise, arg1, arg2, arg3, arg4, arg5) { if (isMetroApp) { return new WinJS.Promise(function (completed, error, progress) { promise({ complete: function (context, args) { completed(args); }, error: function (context, args) { error(args); }, progress: function (context, args) { progress(args); } }, arg1, arg2, arg3, arg4, arg5); }); } else if (typeof ($) === "function" && $.Deferred) { return $.Deferred(function (dfd) { promise({ complete: function (context, args) { dfd.resolveWith(context, [args]); }, error: function (context, args) { dfd.rejectWith(context, [args]); }, progress: function (context, args) { dfd.notifyWith(context, [args]); } }, arg1, arg2, arg3, arg4, arg5); }).promise(); } else { throw "linq2indexedDB: No framework (WinJS or jQuery) that supports promises found. Please ensure jQuery or WinJS is referenced before the linq2indexedDB.js file."; } }, log: function () { if ((window && typeof (window.console) === "undefined") || !enableLogging) { return false; } var currtime = (function currentTime() { var time = new Date(); return time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds() + '.' + time.getMilliseconds(); })(); var args = []; var severity = arguments[0]; args.push(currtime + ' Linq2IndexedDB: '); for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]); } switch (severity) { case linq2indexedDB.prototype.utilities.severity.exception: if (window.console.exception) { window.console.exception.apply(console, args); } else { window.console.error.apply(console, args); } break; case linq2indexedDB.prototype.utilities.severity.error: window.console.error.apply(console, args); break; case linq2indexedDB.prototype.utilities.severity.warning: window.console.warning.apply(console, args); break; case linq2indexedDB.prototype.utilities.severity.information: window.console.log.apply(console, args); break; default: window.console.log.apply(console, args); } return true; }, logError: function (error) { return linq2indexedDB.prototype.utilities.log(error.severity, error.message, error.type, error.method, error.orignialError); }, filterSort: function (data, filters, sortClauses) { var returnData = []; for (var i = 0; i < data.length; i++) { if (utilities.isDataValid(data[i].data, filters)) { returnData = utilities.addToSortedArray(returnData, data[i], sortClauses); } } return returnData; }, isDataValid: function (data, filters) { var isValid = true; for (var i = 0; i < filters.length; i++) { var filterValid = filters[i].filter.isValid(data, filters[i]); if (filters[i].isNotClause) { filterValid = !filterValid; } if (filters[i].isAndClause) { isValid = isValid && filterValid; } else if (filters[i].isOrClause) { isValid = isValid || filterValid; } } return isValid; }, addToSortedArray: function (array, data, sortClauses) { var newArray = []; if (array.length == 0 || sortClauses.length == 0) { newArray = array; newArray.push(data); } else { var valueAdded = false; for (var i = 0; i < array.length; i++) { var valueX = array[i].data; var valueY = data.data; for (var j = 0; j < sortClauses.length; j++) { var sortPropvalueX = linq2indexedDB.prototype.utilities.getPropertyValue(valueX, sortClauses[j].propertyName); var sortPropvalueY = linq2indexedDB.prototype.utilities.getPropertyValue(valueY, sortClauses[j].propertyName); if (sortPropvalueX != sortPropvalueY) { if ((sortClauses[j].descending && sortPropvalueX > sortPropvalueY) || (!sortClauses[j].descending && sortPropvalueX < sortPropvalueY)) { newArray.push(array[i]); } else { if (!valueAdded) { valueAdded = true; newArray.push(data); } newArray.push(array[i]); } } else if (j == (sortClauses.length - 1)) { newArray.push(array[i]); } } } // Add at the end if (!valueAdded) { newArray.push(data); } } return newArray; }, serialize: function (key, value) { if (typeof value === 'function') { return value.toString(); } return value; }, deserialize: function (key, value) { if (value && typeof value === "string" && value.substr(0, 8) == "function") { var startBody = value.indexOf('{') + 1; var endBody = value.lastIndexOf('}'); var startArgs = value.indexOf('(') + 1; var endArgs = value.indexOf(')'); return new Function(value.substring(startArgs, endArgs), value.substring(startBody, endBody)); } return value; }, getPropertyValue: function (data, propertyName) { var structure = propertyName.split("."); var value = data; for (var i = 0; i < structure.length; i++) { if (value) { value = value[structure[i]]; } } return value; }, setPropertyValue: function (data, propertyName, value) { var structure = propertyName.split("."); var obj = data; for (var i = 0; i < structure.length; i++) { if (i != (structure.length - 1)) { obj[structure[i]] = {}; obj = obj[structure[i]]; } else { obj[structure[i]] = value; } } return obj; }, severity: { information: 0, warning: 1, error: 2, exception: 3 } }; linq2indexedDB.prototype.utilities = utilities; })(typeof Windows !== "undefined"); if (typeof window !== "undefined") { // UI Thread // Namespace linq2indexedDB.prototype.core (function (window, isMetroApp) { "use strict"; // Region variables var defaultDatabaseName = "Default"; var implementations = { NONE: 0, NATIVE: 1, MICROSOFT: 2, MOZILLA: 3, GOOGLE: 4, MICROSOFTPROTOTYPE: 5, SHIM: 6 }; var transactionTypes = { READ_ONLY: "readonly", READ_WRITE: "readwrite", VERSION_CHANGE: "versionchange" }; var implementation = initializeIndexedDb(); var handlers = { IDBRequest: function (request) { return deferredHandler(IDBRequestHandler, request); }, IDBBlockedRequest: function (request) { return deferredHandler(IDBBlockedRequestHandler, request); }, IDBOpenDBRequest: function (request) { return deferredHandler(IDBOpenDbRequestHandler, request); }, IDBDatabase: function (database) { return deferredHandler(IDBDatabaseHandler, database); }, IDBTransaction: function (txn) { return deferredHandler(IDBTransactionHandler, txn); }, IDBCursorRequest: function (request) { return deferredHandler(IDBCursorRequestHandler, request); } }; //Copyright (c) 2010 Nicholas C. Zakas. All rights reserved. //MIT License function eventTarget() { this._listeners = {}; } eventTarget.prototype = { constructor: eventTarget, addListener: function (type, listener) { if (!linq2indexedDB.prototype.utilities.isArray(type)) { type = [type]; } for (var i = 0; i < type.length; i++) { if (typeof this._listeners[type[i]] == "undefined") { this._listeners[type[i]] = []; } this._listeners[type[i]].push(listener); } }, fire: function (event) { if (typeof event == "string") { event = { type: event }; } if (!event.target) { event.target = this; } if (!event.type) { //falsy throw new Error("Event object missing 'type' property."); } if (this._listeners[event.type] instanceof Array) { var listeners = this._listeners[event.type]; for (var i = 0, len = listeners.length; i < len; i++) { listeners[i].call(this, event); } } }, removeListener: function (type, listener) { if (!linq2indexedDB.prototype.utilities.isArray(type)) { type = [type]; } for (var j = 0; j < type[j].length; j++) { if (this._listeners[type[j]] instanceof Array) { var listeners = this._listeners[type[j]]; for (var i = 0, len = listeners.length; i < len; i++) { if (listeners[i] === listener) { listeners.splice(i, 1); break; } } } } } }; // End copyright var dbEvents = { objectStoreCreated: "Object store created", objectStoreRemoved: "Object store removed", indexCreated: "Index created", indexRemoved: "Index removed", databaseRemoved: "Database removed", databaseBlocked: "Database blocked", databaseUpgrade: "Database upgrade", databaseOpened: "Database opened" }; var dataEvents = { dataInserted: "Data inserted", dataUpdated: "Data updated", dataRemoved: "Data removed", objectStoreCleared: "Object store cleared" }; var upgradingDatabase = false; var internal = { db: function (pw, name, version) { var req; try { // Initializing defaults name = name ? name : defaultDatabaseName; // Creating a new database conection if (version) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "db opening", name, version); req = window.indexedDB.open(name, version); } else { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "db opening", name); req = window.indexedDB.open(name); } // Handle the events of the creation of the database connection handlers.IDBOpenDBRequest(req).then( function (args /*db, e*/) { var db = args[0]; var e = args[1]; // Database connection established // Handle the events on the database. handlers.IDBDatabase(db).then( function (/*result, event*/) { // No done present. }, function (args1/*error, event*/) { // Database error or abort linq2indexedDB.prototype.core.closeDatabaseConnection(args1[1].target); // When an error occures the result will already be resolved. This way calling the reject won't case a thing }, function (args1 /*result, event*/) { var event = args1[1]; if (event) { // Sending a notify won't have any effect because the result is already resolved. There is nothing more to do than close the current connection. if (event.type === "versionchange") { if (event.version != event.target.db.version) { // If the version is changed and the current version is different from the requested version, the connection needs to get closed. linq2indexedDB.prototype.core.closeDatabaseConnection(event.target); } } } }); var currentVersion = internal.getDatabaseVersion(db); if (currentVersion < version || (version == -1) || currentVersion == "") { // Current version deferres from the requested version, database upgrade needed linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "DB Promise upgradeneeded", this, db, e, db.connectionId); internal.changeDatabaseStructure(db, version || 1).then( function (args1 /*txn, event*/) { var txn = args1[0]; var event = args1[1]; // Fake the onupgrade event. var context = txn.db; context.transaction = txn; var upgardeEvent = {}; upgardeEvent.type = "upgradeneeded"; upgardeEvent.newVersion = version; upgardeEvent.oldVersion = currentVersion; upgardeEvent.originalEvent = event; linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseUpgrade, data: upgardeEvent }); pw.progress(context, [txn, upgardeEvent]); handlers.IDBTransaction(txn).then(function (/*trans, args*/) { // When completed return the db + event of the original request. pw.complete(this, args); }, function (args2 /*err, ev*/) { //txn error or abort pw.error(this, args2); }); }, function (args1 /*err, event*/) { // txn error or abort pw.error(this, args1); }, function (args1 /*result, event*/) { // txn blocked linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseBlocked, data: args1 }); pw.progress(this, args1); }); } else if (version && version < currentVersion) { linq2indexedDB.prototype.core.closeDatabaseConnection(db); var err = { severity: linq2indexedDB.prototype.utilities.severity.error, type: "VersionError", message: "You are trying to open the database in a lower version (" + version + ") than the current version of the database", method: "db" }; linq2indexedDB.prototype.utilities.logError(err); pw.error(this, err); } else { // Database Connection resolved. linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseOpened, data: db }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "DB Promise resolved", db, e); pw.complete(this, [db, e]); } }, function (args /*error, e*/) { // Database connection error or abort var err = internal.wrapError(args[1], "db"); // Fix for firefox & chrome if (args[1].target && args[1].target.errorCode == 12) { err.type = "VersionError"; } if (err.type == "VersionError") { err.message = "You are trying to open the database in a lower version (" + version + ") than the current version of the database"; } // Fix for firefox & chrome if (args[1].target && args[1].target.errorCode == 8) { err.type = "AbortError"; } if (err.type == "AbortError") { err.message = "The VERSION_CHANGE transaction was aborted."; } // For old firefox implementations linq2indexedDB.prototype.utilities.logError(err); pw.error(this, err); }, function (args /*result, e*/) { // Database upgrade + db blocked if (args[1].type == "blocked") { linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseBlocked, data: args }); } else if (args[1].type == "upgradeneeded") { linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseUpgrade, data: args }); } pw.progress(this, args); } ); } catch (ex) { var error = internal.wrapException(ex, "db"); if ((ex.INVALID_ACCESS_ERR && ex.code == ex.INVALID_ACCESS_ERR) || ex.name == "InvalidAccessError") { error.type = "InvalidAccessError"; error.message = "You are trying to open a database with a negative version number."; } linq2indexedDB.prototype.utilities.logError(error); pw.error(this, error); } }, transaction: function (pw, db, objectStoreNames, transactionType, autoGenerateAllowed) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction promise started", db, objectStoreNames, transactionType); // Initialize defaults if (!linq2indexedDB.prototype.utilities.isArray(objectStoreNames)) objectStoreNames = [objectStoreNames]; transactionType = transactionType || linq2indexedDB.prototype.core.transactionTypes.READ_ONLY; var nonExistingObjectStores = []; try { // Check for non-existing object stores for (var i = 0; i < objectStoreNames.length; i++) { if (!db.objectStoreNames || !db.objectStoreNames.contains(objectStoreNames[i])) { nonExistingObjectStores.push(objectStoreNames[i]); } } // When non-existing object stores are found and the autoGenerateAllowed is true. // Then create these object stores if (nonExistingObjectStores.length > 0 && autoGenerateAllowed) { // setTimeout is necessary when multiple request to generate an index come together. // This can result in a deadlock situation, there for the setTimeout setTimeout(function() { upgradingDatabase = true; var version = internal.getDatabaseVersion(db) + 1; var dbName = db.name; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction database upgrade needed: ", db); // Closing the current connections so it won't block the upgrade. linq2indexedDB.prototype.core.closeDatabaseConnection(db); // Open a new connection with the new version linq2indexedDB.prototype.core.db(dbName, version).then(function (args /*dbConnection, event*/) { upgradingDatabase = false; // Necessary for getting it work in WIN 8, WinJS promises have troubles with nesting promises var txn = args[0].transaction(objectStoreNames, transactionType); // Handle transaction events handlers.IDBTransaction(txn).then(function(args1 /*result, event*/) { // txn completed linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction completed.", txn); pw.complete(this, args1); }, function(args1 /*err, event*/) { // txn error or abort linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Transaction error/abort.", args1); pw.error(this, args1); }); // txn created linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction created.", txn); pw.progress(txn, [txn]); }, function(args /*error, event*/) { // When an error occures, bubble up. linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Transaction error.", args); pw.error(this, args); }, function(args /*txn, event*/) { var event = args[1]; // When an upgradeneeded event is thrown, create the non-existing object stores if (event.type == "upgradeneeded") { for (var j = 0; j < nonExistingObjectStores.length; j++) { linq2indexedDB.prototype.core.createObjectStore(args[0], nonExistingObjectStores[j], { keyPath: "Id", autoIncrement: true }); } } }); }, upgradingDatabase ? 10 : 1); } else { // If no non-existing object stores are found, create the transaction. var transaction = db.transaction(objectStoreNames, transactionType); // Handle transaction events handlers.IDBTransaction(transaction).then(function(args /*result, event*/) { // txn completed linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction completed.", args); pw.complete(this, args); }, function (args /*err, event*/) { var err = internal.wrapError(args[1], "transaction"); if (args[1].type == "abort") { err.type = "abort"; err.severity = "abort"; err.message = "Transaction was aborted"; } // Fix for firefox & chrome if (args[1].target && args[1].target.errorCode == 4) { err.type = "ConstraintError"; } if (err.type == "ConstraintError") { err.message = "A mutation operation in the transaction failed. For more details look at the error on the instert, update, remove or clear statement."; } // txn error or abort linq2indexedDB.prototype.utilities.logError(err); pw.error(this, err); }); // txn created linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction transaction created.", transaction); pw.progress(transaction, [transaction]); } } catch (ex) { var error = internal.wrapException(ex, "transaction"); if ((ex.INVALID_ACCESS_ERR && ex.code == ex.INVALID_ACCESS_ERR) || ex.name == "InvalidAccessError") { error.type = "InvalidAccessError"; error.message = "You are trying to open a transaction without providing an object store as scope."; } if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") { var objectStores = ""; for (var m = 0; m < nonExistingObjectStores.length; m++) { if (m > 0) { objectStores += ", "; } objectStores += nonExistingObjectStores[m]; } error.type = "NotFoundError"; error.message = "You are trying to open a transaction for object stores (" + objectStores + "), that doesn't exist."; } if ((ex.QUOTA_ERR && ex.code == ex.QUOTA_ERR) || ex.name == "QuotaExceededError") { error.type = "QuotaExceededError"; error.message = "The size quota of the indexedDB database is reached."; } if ((ex.UNKNOWN_ERR && ex.code == ex.UNKNOWN_ERR) || ex.name == "UnknownError") { error.type = "UnknownError"; error.message = "An I/O exception occured."; } linq2indexedDB.prototype.utilities.logError(error); pw.error(this, error); } }, changeDatabaseStructure: function (db, version) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "changeDatabaseStructure started", db, version); handlers.IDBBlockedRequest(db.setVersion(version)).then(function (args /*txn, event*/) { // txn created pw.complete(this, args); }, function (args /*error, event*/) { // txn error or abort pw.error(this, args); }, function (args /*txn, event*/) { // txn blocked pw.progress(this, args); }); }); }, objectStore: function (pw, transaction, objectStoreName) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "objectStore started", transaction, objectStoreName); try { var store = transaction.objectStore(objectStoreName); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "objectStore completed", transaction, store); pw.complete(store, [transaction, store]); } catch (ex) { var error = internal.wrapException(ex, "objectStore"); if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") { error.type = "NotFoundError"; error.message = "You are trying to open an object store (" + objectStoreName + "), that doesn't exist or isn't in side the transaction scope."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to open an object store (" + objectStoreName + ") outside a transaction."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(transaction); pw.error(this, error); } }, createObjectStore: function (pw, transaction, objectStoreName, objectStoreOptions) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createObjectStore started", transaction, objectStoreName, objectStoreOptions); try { if (!transaction.db.objectStoreNames.contains(objectStoreName)) { // If the object store doesn't exists, create it var options = new Object(); if (objectStoreOptions) { if (objectStoreOptions.keyPath) options.keyPath = objectStoreOptions.keyPath; options.autoIncrement = objectStoreOptions.autoIncrement; } else { options.autoIncrement = true; } var store = transaction.db.createObjectStore(objectStoreName, options, options.autoIncrement); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "ObjectStore Created", transaction, store); linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.objectStoreCreated, data: store }); pw.complete(store, [transaction, store]); } else { // If the object store exists, retrieve it linq2indexedDB.prototype.core.objectStore(transaction, objectStoreName).then(function (args /*trans, store*/) { // store resolved linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "ObjectStore Found", args[1], objectStoreName); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createObjectStore Promise", args[0], args[1]); pw.complete(store, args); }, function (args /*error, event*/) { // store error pw.error(this, args); }); } } catch (ex) { // store exception var error = internal.wrapException(ex, "createObjectStore"); if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to create an object store in a readonly or readwrite transaction."; } if ((ex.INVALID_ACCESS_ERR && ex.code == ex.INVALID_ACCESS_ERR) || ex.name == "InvalidAccessError") { error.type = "InvalidAccessError"; error.message = "The object store can't have autoIncrement on and an empty string or an array with an empty string as keyPath."; } linq2indexedDB.prototype.utilities.logError(error); if (error.type != "InvalidStateError") { linq2indexedDB.prototype.core.abortTransaction(transaction); } pw.error(this, error); } }, deleteObjectStore: function (pw, transaction, objectStoreName) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteObjectStore Promise started", transaction, objectStoreName); try { transaction.db.deleteObjectStore(objectStoreName); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "ObjectStore Deleted", objectStoreName); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteObjectStore completed", objectStoreName); linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.objectStoreRemoved, data: objectStoreName }); pw.complete(this, [transaction, objectStoreName]); } catch (ex) { var error = internal.wrapException(ex, "deleteObjectStore"); if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") { error.type = "NotFoundError"; error.message = "You are trying to delete an object store (" + objectStoreName + "), that doesn't exist."; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to delete an object store in a readonly or readwrite transaction."; } // store exception linq2indexedDB.prototype.utilities.logError(error); if (error.type != "InvalidStateError") { linq2indexedDB.prototype.core.abortTransaction(transaction); } pw.error(this, error); } }, index: function (pw, objectStore, propertyName, autoGenerateAllowed) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Index started", objectStore, propertyName, autoGenerateAllowed); var indexName = propertyName; if (propertyName.indexOf(linq2indexedDB.prototype.core.indexSuffix) == -1) { indexName = indexName + linq2indexedDB.prototype.core.indexSuffix; } try { if (!objectStore.indexNames.contains(indexName) && autoGenerateAllowed) { // setTimeout is necessary when multiple request to generate an index come together. // This can result in a deadlock situation, there for the setTimeout setTimeout((function(objStore) { upgradingDatabase = true; // If index doesn't exists, create it if autoGenerateAllowed var version = internal.getDatabaseVersion(objStore.transaction.db) + 1; var dbName = objStore.transaction.db.name; var transactionType = objStore.transaction.mode; var objectStoreNames = [objStore.name]; //transaction.objectStoreNames; var objectStoreName = objStore.name; // Close the currenct database connections so it won't block linq2indexedDB.prototype.core.closeDatabaseConnection(objStore); // Open a new connection with the new version linq2indexedDB.prototype.core.db(dbName, version).then(function (args /*dbConnection, event*/) { upgradingDatabase = false; // When the upgrade is completed, the index can be resolved. linq2indexedDB.prototype.core.transaction(args[0], objectStoreNames, transactionType, autoGenerateAllowed).then(function(/*transaction, ev*/) { // txn completed // TODO: what to do in this case }, function(args1 /*error, ev*/) { // txn error or abort pw.error(this, args1); }, function(args1 /*transaction*/) { // txn created linq2indexedDB.prototype.core.index(linq2indexedDB.prototype.core.objectStore(args1[0], objectStoreName), propertyName).then(function(args2 /*trans, index, store*/) { pw.complete(this, args2); }, function(args2 /*error, ev*/) { // txn error or abort pw.error(this, args2); }); }); }, function(args /*error, event*/) { // When an error occures, bubble up. pw.error(this, args); }, function(args /*trans, event*/) { var trans = args[0]; var event = args[1]; // When an upgradeneeded event is thrown, create the non-existing object stores if (event.type == "upgradeneeded") { linq2indexedDB.prototype.core.createIndex(linq2indexedDB.prototype.core.objectStore(trans, objectStoreName), propertyName).then(function (/*index, store, transaction*/) { // index created }, function(args1 /*error, ev*/) { // When an error occures, bubble up. pw.error(this, args1); }); } }); })(objectStore), upgradingDatabase ? 10 : 1); } else { // If index exists, resolve it var index = objectStore.index(indexName); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Index completed", objectStore.transaction, index, objectStore); pw.complete(this, [objectStore.transaction, index, objectStore]); } } catch (ex) { var error = internal.wrapException(ex, "index"); if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") { error.type = "NotFoundError"; error.message = "You are trying to open an index (" + indexName + "), that doesn't exist."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to open an object store (" + indexName + ") outside a transaction."; } // index exception linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); pw.error(this, error); } }, createIndex: function (pw, objectStore, propertyName, indexOptions) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createIndex started", objectStore, propertyName, indexOptions); try { var indexName = propertyName; if (propertyName.indexOf(linq2indexedDB.prototype.core.indexSuffix) == -1) { indexName = indexName + linq2indexedDB.prototype.core.indexSuffix; } if (!objectStore.indexNames.contains(indexName)) { var index = objectStore.createIndex(indexName, propertyName, { unique: indexOptions ? indexOptions.unique : false, multiRow: indexOptions ? indexOptions.multirow : false, multiEntry: indexOptions ? indexOptions.multirow : false }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createIndex completed", objectStore.transaction, index, objectStore); linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.indexCreated, data: index }); pw.complete(this, [objectStore.transaction, index, objectStore]); } else { // if the index exists retrieve it linq2indexedDB.prototype.core.index(objectStore, propertyName, false).then(function (args) { pw.complete(this, args); }); } } catch (ex) { // store exception var error = internal.wrapException(ex, "createIndex"); if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to create an index in a readonly or readwrite transaction."; } if (error.type != "InvalidStateError") { linq2indexedDB.prototype.core.abortTransaction(transaction); } linq2indexedDB.prototype.utilities.logError(error); pw.error(this, error); } }, deleteIndex: function (pw, objectStore, propertyName) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteIndex started", objectStore, propertyName); var indexName = propertyName; if (propertyName.indexOf(linq2indexedDB.prototype.core.indexSuffix) == -1) { indexName = indexName + linq2indexedDB.prototype.core.indexSuffix; } try { objectStore.deleteIndex(indexName); linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.indexRemoved, data: indexName }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteIndex completed", objectStore.transaction, propertyName, objectStore); pw.complete(this, [objectStore.transaction, propertyName, objectStore]); } catch (ex) { var error = internal.wrapException(ex, "deleteIndex"); if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") { error.type = "NotFoundError"; error.message = "You are trying to delete an index (" + indexName + ", propertyName: " + propertyName + " ), that doesn't exist."; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to delete an index in a readonly or readwrite transaction."; } // store exception linq2indexedDB.prototype.utilities.logError(error); if (error.type != "InvalidStateError") { linq2indexedDB.prototype.core.abortTransaction(transaction); } pw.error(this, error); } }, cursor: function (pw, source, range, direction) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor Promise Started", source); var keyRange; var returnData = []; var request; try { keyRange = range; if (!keyRange) { if (implementation != implementations.GOOGLE) { keyRange = IDBKeyRange.lowerBound(0); } else { keyRange = IDBKeyRange.lowerBound(parseFloat(0)); } } // direction can not be null when passed. if (direction) { request = handlers.IDBCursorRequest(source.openCursor(keyRange, direction)); } else if (keyRange) { request = handlers.IDBCursorRequest(source.openCursor(keyRange)); } else { request = handlers.IDBCursorRequest(source.openCursor()); } request.then(function (args1 /*result, e*/) { var e = args1[1]; var transaction = source.transaction || source.objectStore.transaction; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor completed", returnData, transaction, e); pw.complete(this, [returnData, transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Cursor error", args); pw.error(this, args); }, function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor progress", result, e); if (result.value) { var progressObj = { data: result.value, key: result.primaryKey, skip: function(number) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor skip", result, e); try { result.advance(number); } catch (advanceEx) { var advanceErr = internal.wrapException(advanceEx, "cursor - skip"); if ((advanceEx.DATA_ERR && advanceEx.code == advanceEx.DATA_ERR) || advanceEx.name == "DataError") { advanceErr.type = "DataError"; advanceErr.message = "The provided range parameter isn't a valid key or key range."; } if (advanceEx.name == "TypeError") { advanceErr.type = "TypeError"; advanceErr.message = "The provided count parameter is zero or a negative number."; } if ((advanceEx.INVALID_STATE_ERR && advanceEx.code == advanceEx.INVALID_STATE_ERR) || advanceEx.name == "InvalidStateError") { advanceErr.type = "InvalidStateError"; advanceErr.message = "You are trying to skip data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(advanceErr); linq2indexedDB.prototype.core.abortTransaction(txn); pw.error(this, advanceErr); } }, update: function(obj) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor update", result, e); try { result.update(obj); } catch (updateEx) { var updateError = internal.wrapException(updateEx, "cursor - update"); if ((updateEx.DATA_ERR && updateEx.code == updateEx.DATA_ERR) || updateEx.name == "DataError") { updateError.type = "DataError"; updateError.message = "The underlying object store uses in-line keys and the property in value at the object store's key path does not match the key in this cursor's position."; } if ((updateEx.READ_ONLY_ERR && ex.code == updateEx.READ_ONLY_ERR) || updateEx.name == "ReadOnlyError") { updateError.type = "ReadOnlyError"; updateError.message = "You are trying to update data in a readonly transaction."; } if (updateEx.name == "TransactionInactiveError") { updateError.type = "TransactionInactiveError"; updateError.message = "You are trying to update data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((updateEx.DATA_CLONE_ERR && updateEx.code == updateEx.DATA_CLONE_ERR) || updateEx.name == "DataCloneError") { updateError.type = "DataCloneError"; updateError.message = "The data you are trying to update could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to update the data."; } if ((updateEx.INVALID_STATE_ERR && updateEx.code == updateEx.INVALID_STATE_ERR) || updateEx.name == "InvalidStateError") { updateError.type = "InvalidStateError"; updateError.message = "You are trying to update data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(updateError); linq2indexedDB.prototype.core.abortTransaction(txn); pw.error(this, updateError); } }, remove: function() { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor remove", result, e); try { result["delete"](); } catch (deleteEx) { var deleteError = internal.wrapException(deleteEx, "cursor - delete"); if ((deleteEx.READ_ONLY_ERR && deleteEx.code == deleteEx.READ_ONLY_ERR) || deleteEx.name == "ReadOnlyError") { deleteError.type = "ReadOnlyError"; deleteError.message = "You are trying to remove data in a readonly transaction."; } if (deleteEx.name == "TransactionInactiveError") { deleteError.type = "TransactionInactiveError"; deleteError.message = "You are trying to remove data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((deleteEx.INVALID_STATE_ERR && deleteEx.code == deleteEx.INVALID_STATE_ERR) || deleteEx.name == "InvalidStateError") { deleteError.type = "InvalidStateError"; deleteError.message = "You are trying to remove data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(deleteError); linq2indexedDB.prototype.core.abortTransaction(txn); pw.error(this, deleteError); } } }; pw.progress(this, [progressObj, result, e]); returnData.push({data: progressObj.data ,key: progressObj.key }); } result["continue"](); }); } catch (ex) { var txn = source.transaction || source.objectStore.transaction; var error = internal.wrapException(ex, "cursor"); if ((ex.DATA_ERR && error.code == ex.DATA_ERR) || ex.name == "DataError") { error.type = "DataError"; error.message = "The provided range parameter isn't a valid key or key range."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to retrieve data on an inactieve transaction. (The transaction was already aborted or committed)"; } if (ex.name == "TypeError") { error.type = "TypeError"; error.message = "The provided directory parameter is invalid"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to insert data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(txn); pw.error(this, error); } finally { keyRange = null; } }, keyCursor: function (pw, index, range, direction) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor Started", index, range, direction); var returnData = []; try { var request; var keyRange = range; if (!keyRange) { keyRange = IDBKeyRange.lowerBound(0); } // direction can not be null when passed. if (direction) { request = handlers.IDBCursorRequest(source.openKeyCursor(keyRange, direction)); } else { request = handlers.IDBCursorRequest(source.openKeyCursor(keyRange)); } request.then(function (args /*result, e*/) { var e = args[1]; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor completed", returnData, index.objectStore.transaction, e); pw.complete(this, [returnData, index.objectStore.transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "keyCursor error", args); pw.error(this, args); }, function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor progress", result, e); if (result.value) { var progressObj = { data: result.value, key: result.primaryKey, skip: function (number) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor skip", result, e); try { result.advance(number); } catch (advanceEx) { var advanceErr = internal.wrapException(advanceEx, "keyCursor - skip"); if ((advanceEx.DATA_ERR && advanceEx.code == advanceEx.DATA_ERR) || advanceEx.name == "DataError") { advanceErr.type = "DataError"; advanceErr.message = "The provided range parameter isn't a valid key or key range."; } if (advanceEx.name == "TypeError") { advanceErr.type = "TypeError"; advanceErr.message = "The provided count parameter is zero or a negative number."; } if ((advanceEx.INVALID_STATE_ERR && advanceEx.code == advanceEx.INVALID_STATE_ERR) || advanceEx.name == "InvalidStateError") { advanceErr.type = "InvalidStateError"; advanceErr.message = "You are trying to skip data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(advanceErr); linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction); pw.error(this, advanceErr); } }, update: function (obj) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor update", result, e); try { result.update(obj); } catch (updateEx) { var updateError = internal.wrapException(updateEx, "keyCursor - update"); if ((updateEx.DATA_ERR && updateEx.code == updateEx.DATA_ERR) || updateEx.name == "DataError") { updateError.type = "DataError"; updateError.message = "The underlying object store uses in-line keys and the property in value at the object store's key path does not match the key in this cursor's position."; } if ((updateEx.READ_ONLY_ERR && ex.code == updateEx.READ_ONLY_ERR) || updateEx.name == "ReadOnlyError") { updateError.type = "ReadOnlyError"; updateError.message = "You are trying to update data in a readonly transaction."; } if (updateEx.name == "TransactionInactiveError") { updateError.type = "TransactionInactiveError"; updateError.message = "You are trying to update data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((updateEx.DATA_CLONE_ERR && updateEx.code == updateEx.DATA_CLONE_ERR) || updateEx.name == "DataCloneError") { updateError.type = "DataCloneError"; updateError.message = "The data you are trying to update could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to update the data."; } if ((updateEx.INVALID_STATE_ERR && updateEx.code == updateEx.INVALID_STATE_ERR) || updateEx.name == "InvalidStateError") { updateError.type = "InvalidStateError"; updateError.message = "You are trying to update data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(updateError); linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction); pw.error(this, updateError); } }, remove: function () { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor remove", result, e); try { result["delete"](); } catch (deleteEx) { var deleteError = internal.wrapException(deleteEx, "keyCursor - delete"); if ((deleteEx.READ_ONLY_ERR && deleteEx.code == deleteEx.READ_ONLY_ERR) || deleteEx.name == "ReadOnlyError") { deleteError.type = "ReadOnlyError"; deleteError.message = "You are trying to remove data in a readonly transaction."; } if (deleteEx.name == "TransactionInactiveError") { deleteError.type = "TransactionInactiveError"; deleteError.message = "You are trying to remove data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((deleteEx.INVALID_STATE_ERR && deleteEx.code == deleteEx.INVALID_STATE_ERR) || deleteEx.name == "InvalidStateError") { deleteError.type = "InvalidStateError"; deleteError.message = "You are trying to remove data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(deleteError); linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction); pw.error(this, deleteError); } } }; pw.progress(this, [progressObj, result, e]); returnData.push(progressObj.data); } result["continue"](); }); } catch (ex) { var error = internal.wrapException(ex, "keyCursor"); if ((ex.DATA_ERR && error.code == ex.DATA_ERR) || ex.name == "DataError") { error.type = "DataError"; error.message = "The provided range parameter isn't a valid key or key range."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to retrieve data on an inactieve transaction. (The transaction was already aborted or committed)"; } if (ex.name == "TypeError") { error.type = "TypeError"; error.message = "The provided directory parameter is invalid"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to insert data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction); pw.error(this, error); } }, get: function (pw, source, key) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Get Started", source); try { handlers.IDBRequest(source.get(key)).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; var transaction = source.transaction || source.objectStore.transaction; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Get completed", result, transaction, e); pw.complete(this, [result, transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Get error", args); pw.error(this, args); }); } catch (ex) { var txn = source.transaction || source.objectStore.transaction; var error = internal.wrapException(ex, "get"); if (error.code == ex.DATA_ERR || ex.name == "DataError") { error.message = "The provided key isn't a valid key (must be an array, string, date or number)."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to retrieve data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to retrieve data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(txn); pw.error(this, error); } }, count: function (pw, source, key) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Count Started", source); try { var req; if (key) { req = source.count(key); } else { req = source.count(); } handlers.IDBRequest(req).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; var transaction = source.transaction || source.objectStore.transaction; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Count completed", result, transaction, e); pw.complete(this, [result, transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Count error", args); pw.error(this, args); }); } catch (ex) { var txn = source.transaction || source.objectStore.transaction; var error = internal.wrapException(ex, "count"); if (error.code == ex.DATA_ERR || ex.name == "DataError") { error.type = "DataError"; error.message = "The provided key isn't a valid key or keyRange."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to count data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to count data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(txn); pw.error(this, error); } }, getKey: function (pw, index, key) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "GetKey Started", index, key); try { handlers.IDBRequest(index.getKey(key)).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "GetKey completed", result, index.objectStore.transaction, e); pw.complete(this, [result, index.objectStore.transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "GetKey error", args); pw.error(this, args); }); } catch (ex) { var error = internal.wrapException(ex, "getKey"); if (error.code == ex.DATA_ERR || ex.name == "DataError") { error.type = "DataError"; error.message = "The provided key isn't a valid key or keyRange."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to getKey data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to getKey data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction); pw.error(this, error); } }, insert: function (pw, objectStore, data, key) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Insert Started", objectStore, data, key); try { var req; if (key /*&& !store.keyPath*/) { req = handlers.IDBRequest(objectStore.add(data, key)); } else { /*if (key) linq2indexedDB.prototype.utilities.log("Key can't be provided when a keyPath is defined on the object store", store, key, data);*/ req = handlers.IDBRequest(objectStore.add(data)); } req.then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; // Add key to the object if a keypath exists if (objectStore.keyPath) { data[objectStore.keyPath] = result; } linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.dataInserted, data: data, objectStore: objectStore }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Insert completed", data, result, objectStore.transaction, e); pw.complete(this, [data, result, objectStore.transaction, e]); }, function (args /*error, e*/) { var err = internal.wrapError(args[1], "insert"); // Fix for firefox & chrome if (args[1].target && args[1].target.errorCode == 4) { err.type = "ConstraintError"; } if (err.type == "ConstraintError") { var duplicateKey = key; if (!duplicateKey && objectStore.keyPath) { duplicateKey = data[objectStore.keyPath]; } err.message = "A record for the key (" + duplicateKey + ") already exists in the database or one of the properties of the provided data has a unique index declared."; } linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); linq2indexedDB.prototype.utilities.logError(err); pw.error(this, err); }); } catch (ex) { var error = internal.wrapException(ex, "insert"); if (error.code == ex.DATA_ERR || ex.name == "DataError") { error.type = "DataError"; var possibleKey = key; if (!possibleKey && objectStore.keyPath) { possibleKey = data[objectStore.keyPath]; } if (!possibleKey) { error.message = "There is no key provided for the data you want to insert for an object store without autoIncrement."; } else if (key && objectStore.keyPath) { error.message = "An external key is provided while the object store expects a keyPath key."; } else if (typeof possibleKey !== "string" && typeof possibleKey !== "number" && typeof possibleKey !== "Date" && !linq2indexedDB.prototype.utilities.isArray(possibleKey)) { error.message = "The provided key isn't a valid key (must be an array, string, date or number)."; } } if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") { error.type = "ReadOnlyError"; error.message = "You are trying to insert data in a readonly transaction."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to insert data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.DATA_CLONE_ERR && ex.code == ex.DATA_CLONE_ERR) || ex.name == "DataCloneError") { error.type = "DataCloneError"; error.message = "The data you are trying to insert could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to insert the data."; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to insert data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); pw.error(this, error); } }, update: function (pw, objectStore, data, key) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Update Started", objectStore, data, key); try { var req; if (key /*&& !store.keyPath*/) { req = handlers.IDBRequest(objectStore.put(data, key)); } else { /*if (key) linq2indexedDB.prototype.utilities.log("Key can't be provided when a keyPath is defined on the object store", store, key, data);*/ req = handlers.IDBRequest(objectStore.put(data)); } req.then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; if (objectStore.keyPath && data[objectStore.keyPath] === undefined) { data[objectStore.keyPath] = result; } linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.dataUpdated, data: data, objectStore: objectStore }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Update completed", data, result, objectStore.transaction, e); pw.complete(this, [data, result, objectStore.transaction, e]); }, function (args /*error, e*/) { var err = internal.wrapError(args[1], "update"); linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); linq2indexedDB.prototype.utilities.logError(err); pw.error(this, err); }); } catch (ex) { var error = internal.wrapException(ex, "update"); if (error.code == ex.DATA_ERR || ex.name == "DataError") { error.type = "DataError"; var possibleKey = key; if (!possibleKey && objectStore.keyPath) { possibleKey = data[objectStore.keyPath]; } if (!possibleKey) { error.message = "There is no key provided for the data you want to update for an object store without autoIncrement."; } else if (key && objectStore.keyPath) { error.message = "An external key is provided while the object store expects a keyPath key."; } else if (typeof possibleKey !== "string" && typeof possibleKey !== "number" && typeof possibleKey !== "Date" && !linq2indexedDB.prototype.utilities.isArray(possibleKey)) { error.message = "The provided key isn't a valid key (must be an array, string, date or number)."; } } if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") { error.type = "ReadOnlyError"; error.message = "You are trying to update data in a readonly transaction."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to update data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.DATA_CLONE_ERR && ex.code == ex.DATA_CLONE_ERR) || ex.name == "DataCloneError") { error.type = "DataCloneError"; error.message = "The data you are trying to update could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to update the data."; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to update data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); pw.error(this, error); } }, remove: function (pw, objectStore, key) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Remove Started", objectStore, key); try { handlers.IDBRequest(objectStore["delete"](key)).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.dataRemoved, data: key, objectStore: objectStore }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Remove completed", result, objectStore.transaction, e); pw.complete(this, [result, objectStore.transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Remove error", args); pw.error(this, args); }); } catch (ex) { var error = internal.wrapException(ex, "delete"); if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") { error.type = "ReadOnlyError"; error.message = "You are trying to remove data in a readonly transaction."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to remove data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to remove data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); pw.error(this, error); } }, clear: function (pw, objectStore) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Clear Started", objectStore); try { handlers.IDBRequest(objectStore.clear()).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.objectStoreCleared, objectStore: objectStore }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Clear completed", result, objectStore.transaction, e); pw.complete(this, [result, objectStore.transaction, e]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Clear error", args); pw.error(this, args); }); } catch (ex) { var error = internal.wrapException(ex, "clear"); if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") { error.type = "ReadOnlyError"; error.message = "You are trying to clear data in a readonly transaction."; } if (ex.name == "TransactionInactiveError") { error.type = "TransactionInactiveError"; error.message = "You are trying to clear data on an inactieve transaction. (The transaction was already aborted or committed)"; } if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") { error.type = "InvalidStateError"; error.message = "You are trying to clear data on a removed object store."; } linq2indexedDB.prototype.utilities.logError(error); linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction); pw.error(this, error); } }, deleteDb: function (pw, name) { try { if (typeof (window.indexedDB.deleteDatabase) != "undefined") { handlers.IDBBlockedRequest(window.indexedDB.deleteDatabase(name)).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved }); linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Delete Database Promise completed", result, e, name); pw.complete(this, [result, e, name]); }, function (args /*error, e*/) { var error = args[0]; var e = args[1]; // added for FF, If a db gets deleted that doesn't exist an errorCode 6 ('NOT_ALLOWED_ERR') is given if (e.currentTarget && e.currentTarget.errorCode == 6) { linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved }); pw.complete(this, [error, e, name]); } else if (implementation == implementations.SHIM && e.message == "Database does not exist") { linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved }); pw.complete(this, [error, e, name]); } else { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Delete Database Promise error", error, e); pw.error(this, [error, e]); } }, function (args /*result, e*/) { if (args[0] == "blocked") { linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseBlocked }); } linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Delete Database Promise blocked", args /*result*/); pw.progress(this, args /*[result, e]*/); }); } else { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Delete Database function not found", name); // Workaround for older versions of chrome and FireFox // Doesn't delete the database, but clears him linq2indexedDB.prototype.core.db(name, -1).then(function (args /*result, e*/) { var result = args[0]; var e = args[1]; linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved }); pw.complete(this, [result, e, name]); }, function (args /*error, e*/) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Clear Promise error", args /*error, e*/); pw.error(this, args /*[error, e]*/); }, function (args /*dbConnection, event*/) { var dbConnection = args[0]; var event = args[1]; // When an upgradeneeded event is thrown, create the non-existing object stores if (event.type == "upgradeneeded") { for (var i = 0; i < dbConnection.objectStoreNames.length; i++) { linq2indexedDB.prototype.core.deleteObjectStore(dbConnection.txn, dbConnection.objectStoreNames[i]); } linq2indexedDB.prototype.core.closeDatabaseConnection(dbConnection); } }); } } catch (ex) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.exception, "Delete Database Promise exception", ex); pw.error(this, [ex.message, ex]); } }, getDatabaseVersion: function (db) { var dbVersion = parseInt(db.version); if (isNaN(dbVersion) || dbVersion < 0) { return 0; } else { return dbVersion; } }, wrapException: function (exception, method) { return { code: exception.code, severity: linq2indexedDB.prototype.utilities.severity.exception, orignialError: exception, method: method, type: "unknown" }; }, wrapError: function (error, method) { return { severity: linq2indexedDB.prototype.utilities.severity.error, orignialError: error, type: (error.target && error.target.error && error.target.error.name) ? error.target.error.name : "unknown", method: method }; } }; linq2indexedDB.prototype.core = { db: function (name, version) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { internal.db(pw, name, version); }); }, transaction: function (db, objectStoreNames, transactionType, autoGenerateAllowed) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (db.then) { db.then(function (args /*db, e*/) { // Timeout necessary for letting it work on win8. If not, progress event triggers before listeners are coupled if (isMetroApp) { setTimeout(function () { internal.transaction(pw, args[0], objectStoreNames, transactionType, autoGenerateAllowed); }, 1); } else { internal.transaction(pw, args[0], objectStoreNames, transactionType, autoGenerateAllowed); } }, function (args /*error, e*/) { pw.error(this, args); }, function (args /**/) { pw.progress(this, args); }); } else { if (isMetroApp) { // Timeout necessary for letting it work on win8. If not, progress event triggers before listeners are coupled setTimeout(function() { internal.transaction(pw, db, objectStoreNames, transactionType, autoGenerateAllowed); }, 1); } else { internal.transaction(pw, db, objectStoreNames, transactionType, autoGenerateAllowed); } } }); }, objectStore: function (transaction, objectStoreName) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (transaction.then) { transaction.then(function (/*txn, e*/) { // transaction completed // TODO: what todo in this case? }, function (args /*error, e*/) { pw.error(this, args); }, function (args /*txn, e*/) { internal.objectStore(pw, args[0], objectStoreName); }); } else { internal.objectStore(pw, transaction, objectStoreName); } }); }, createObjectStore: function (transaction, objectStoreName, objectStoreOptions) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (transaction.then) { transaction.then(function (/*txn, e*/) { // txn completed // TODO: what todo in this case? }, function (args /*error, e*/) { // txn error or abort pw.error(this, args); }, function (args /*txn, e*/) { internal.createObjectStore(pw, args[0], objectStoreName, objectStoreOptions); }); } else { internal.createObjectStore(pw, transaction, objectStoreName, objectStoreOptions); } }); }, deleteObjectStore: function (transaction, objectStoreName) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (transaction.then) { transaction.then(function (/*txn, e*/) { // txn completed // TODO: what todo in this case? }, function (args /*error, e*/) { // txn error pw.error(this, args); }, function (args /*txn, e*/) { internal.deleteObjectStore(pw, args[0], objectStoreName); }); } else { internal.deleteObjectStore(pw, transaction, objectStoreName); } }); }, index: function (objectStore, propertyName, autoGenerateAllowed) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args /*txn, objectStore*/) { internal.index(pw, args[1], propertyName, autoGenerateAllowed); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.index(pw, objectStore, propertyName, autoGenerateAllowed); } }); }, createIndex: function (objectStore, propertyName, indexOptions) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args/*txn, objectStore*/) { internal.createIndex(pw, args[1], propertyName, indexOptions); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.createIndex(pw, objectStore, propertyName, indexOptions); } }); }, deleteIndex: function (objectStore, propertyName) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args/*txn, objectStore*/) { internal.deleteIndex(pw, args[1], propertyName); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.deleteIndex(pw, objectStore, propertyName); } }); }, cursor: function (source, range, direction) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (source.then) { source.then(function (args /*txn, source*/) { internal.cursor(pw, args[1], range, direction); }, function (args /*error, e*/) { // store or index error pw.error(this, args); }); } else { internal.cursor(pw, source, range, direction); } }); }, keyCursor: function (index, range, direction) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (index.then) { index.then(function (args /*txn, index, store*/) { internal.keyCursor(pw, args[1], range, direction); }, function (args /*error, e*/) { // index error pw.error(this, args); }); } else { internal.keyCursor(pw, index, range, direction); } }); }, get: function (source, key) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (source.then) { source.then(function (args /*txn, source*/) { internal.get(pw, args[1], key); }, function (args /*error, e*/) { // store or index error pw.error(this, args); }); } else { internal.get(pw, source, key); } }); }, count: function (source) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (source.then) { source.then(function (args /*txn, source*/) { internal.count(pw, args[1]); }, function (args /*error, e*/) { // store or index error pw.error(this, args); }); } else { internal.count(pw, source); } }); }, getKey: function (index, key) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (index.then) { index.then(function (args /*txn, index, objectStore*/) { internal.getKey(pw, args[1], key); }, function (args /*error, e*/) { // index error pw.error(this, args); }); } else { internal.getKey(pw, index, key); } }); }, insert: function (objectStore, data, key) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args /*txn, store*/) { internal.insert(pw, args[1], data, key); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.insert(pw, objectStore, data, key); } }); }, update: function (objectStore, data, key) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args /*txn, store*/) { internal.update(pw, args[1], data, key); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.update(pw, objectStore, data, key); } }); }, remove: function (objectStore, key) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args /*txn, store*/) { internal.remove(pw, args[1], key); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.remove(pw, objectStore, key); } }); }, clear: function (objectStore) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { if (objectStore.then) { objectStore.then(function (args /*txn, store*/) { internal.clear(pw, args[1]); }, function (args /*error, e*/) { // store error pw.error(this, args); }); } else { internal.clear(pw, objectStore); } }); }, deleteDb: function (name) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { internal.deleteDb(pw, name); }); }, closeDatabaseConnection: function (target) { var db; if (target instanceof IDBCursor) { target = target.source; } if (target instanceof IDBDatabase) { db = target; } else if (target instanceof IDBTransaction) { db = target.db; } else if (target instanceof IDBObjectStore || target instanceof IDBRequest) { db = target.transaction.db; } else if (target instanceof IDBIndex) { db = target.objectStore.transaction.db; } linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Close database Connection: ", db); db.close(); }, abortTransaction: function (transaction) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Abort transaction: " + transaction); // Calling the abort, blocks the database in IE10 if (implementation != implementations.MICROSOFT) { transaction.abort(); linq2indexedDB.prototype.core.closeDatabaseConnection(transaction); } }, transactionTypes: transactionTypes, dbStructureChanged: new eventTarget(), dbDataChanged: new eventTarget(), databaseEvents: dbEvents, dataEvents: dataEvents, implementation: implementation, implementations: implementations }; if (implementation == implementations.SHIM) { linq2indexedDB.prototype.core.indexSuffix = "IIndex"; } else { linq2indexedDB.prototype.core.indexSuffix = "-Index"; } // Region Functions function initializeIndexedDb() { if (window === 'undefined') { return implementations.NONE; } if (window.indexedDB) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Native implementation", window.indexedDB); return implementations.NATIVE; } else { // Initialising the window.indexedDB Object for FireFox if (window.mozIndexedDB) { window.indexedDB = window.mozIndexedDB; if (typeof window.IDBTransaction.READ_ONLY === "number" && typeof window.IDBTransaction.READ_WRITE === "number" && typeof window.IDBTransaction.VERSION_CHANGE === "number") { transactionTypes.READ_ONLY = window.IDBTransaction.READ_ONLY; transactionTypes.READ_WRITE = window.IDBTransaction.READ_WRITE; transactionTypes.VERSION_CHANGE = window.IDBTransaction.VERSION_CHANGE; } linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "FireFox Initialized", window.indexedDB); return implementations.MOZILLA; } // Initialising the window.indexedDB Object for Chrome else if (window.webkitIndexedDB) { if (!window.indexedDB) window.indexedDB = window.webkitIndexedDB; if (!window.IDBCursor) window.IDBCursor = window.webkitIDBCursor; if (!window.IDBDatabase) window.IDBDatabase = window.webkitIDBDatabase; //if (!window.IDBDatabaseError) window.IDBDatabaseError = window.webkitIDBDatabaseError if (!window.IDBDatabaseException) window.IDBDatabaseException = window.webkitIDBDatabaseException; if (!window.IDBFactory) window.IDBFactory = window.webkitIDBFactory; if (!window.IDBIndex) window.IDBIndex = window.webkitIDBIndex; if (!window.IDBKeyRange) window.IDBKeyRange = window.webkitIDBKeyRange; if (!window.IDBObjectStore) window.IDBObjectStore = window.webkitIDBObjectStore; if (!window.IDBRequest) window.IDBRequest = window.webkitIDBRequest; if (!window.IDBTransaction) window.IDBTransaction = window.webkitIDBTransaction; if (!window.IDBOpenDBRequest) window.IDBOpenDBRequest = window.webkitIDBOpenDBRequest; if (typeof window.IDBTransaction.READ_ONLY === "number" && typeof window.IDBTransaction.READ_WRITE === "number" && typeof window.IDBTransaction.VERSION_CHANGE === "number") { transactionTypes.READ_ONLY = window.IDBTransaction.READ_ONLY; transactionTypes.READ_WRITE = window.IDBTransaction.READ_WRITE; transactionTypes.VERSION_CHANGE = window.IDBTransaction.VERSION_CHANGE; } linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Chrome Initialized", window.indexedDB); return implementations.GOOGLE; } // Initialiseing the window.indexedDB Object for IE 10 preview 3+ else if (window.msIndexedDB) { window.indexedDB = window.msIndexedDB; transactionTypes.READ_ONLY = 0; transactionTypes.READ_WRITE = 1; transactionTypes.VERSION_CHANGE = 2; linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "IE10+ Initialized", window.indexedDB); return implementations.MICROSOFT; } // Initialising the window.indexedDB Object for IE 8 & 9 else if (navigator.appName == 'Microsoft Internet Explorer') { try { window.indexedDB = new ActiveXObject("SQLCE.Factory.4.0"); window.indexedDBSync = new ActiveXObject("SQLCE.FactorySync.4.0"); } catch (ex) { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Initializing IE prototype exception", ex); } if (window.JSON) { window.indexedDB.json = window.JSON; window.indexedDBSync.json = window.JSON; } else { var jsonObject = { parse: function (txt) { if (txt === "[]") return []; if (txt === "{}") return {}; throw { message: "Unrecognized JSON to parse: " + txt }; } }; window.indexedDB.json = jsonObject; window.indexedDBSync.json = jsonObject; } // Add some interface-level constants and methods. window.IDBDatabaseException = { UNKNOWN_ERR: 0, NON_TRANSIENT_ERR: 1, NOT_FOUND_ERR: 2, CONSTRAINT_ERR: 3, DATA_ERR: 4, NOT_ALLOWED_ERR: 5, SERIAL_ERR: 11, RECOVERABLE_ERR: 21, TRANSIENT_ERR: 31, TIMEOUT_ERR: 32, DEADLOCK_ERR: 33 }; window.IDBKeyRange = { SINGLE: 0, LEFT_OPEN: 1, RIGHT_OPEN: 2, LEFT_BOUND: 4, RIGHT_BOUND: 8 }; window.IDBRequest = { INITIAL: 0, LOADING: 1, DONE: 2 }; window.IDBTransaction = { READ_ONLY: 0, READ_WRITE: 1, VERSION_CHANGE: 2 }; transactionTypes.READ_ONLY = 0; transactionTypes.READ_WRITE = 1; transactionTypes.VERSION_CHANGE = 2; window.IDBKeyRange.only = function (value) { return window.indexedDB.range.only(value); }; window.IDBKeyRange.leftBound = function (bound, open) { return window.indexedDB.range.lowerBound(bound, open); }; window.IDBKeyRange.rightBound = function (bound, open) { return window.indexedDB.range.upperBound(bound, open); }; window.IDBKeyRange.bound = function (left, right, openLeft, openRight) { return window.indexedDB.range.bound(left, right, openLeft, openRight); }; window.IDBKeyRange.lowerBound = function (left, openLeft) { return window.IDBKeyRange.leftBound(left, openLeft); }; return implementations.MICROSOFTPROTOTYPE; } else if (window.shimIndexedDB) { window.indexedDB = window.shimIndexedDB; return implementations.SHIM; } else { linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Your browser doesn't support indexedDB."); return implementations.NONE; } } }; function deferredHandler(handler, request) { return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) { try { handler(pw, request); } catch (e) { e.type = "exception"; pw.error(request, [e.message, e]); } finally { request = null; } }); }; function IDBSuccessHandler(pw, request) { request.onsuccess = function (e) { pw.complete(e.target, [e.target.result, e]); }; }; function IDBErrorHandler(pw, request) { request.onerror = function (e) { pw.error(e.target, [e.target.errorCode, e]); }; }; function IDBAbortHandler(pw, request) { request.onabort = function (e) { pw.error(e.target, [e.target.errorCode, e]); }; }; function IDBVersionChangeHandler(pw, request) { request.onversionchange = function (e) { pw.progress(e.target, [e.target.result, e]); }; }; function IDBCompleteHandler(pw, request) { request.oncomplete = function (e) { pw.complete(e.target, [e.target, e]); }; }; function IDBRequestHandler(pw, request) { IDBSuccessHandler(pw, request); IDBErrorHandler(pw, request); }; function IDBCursorRequestHandler(pw, request) { request.onsuccess = function (e) { if (!e.target.result) { pw.complete(e.target, [e.target.result, e]); } else { pw.progress(e.target, [e.target.result, e]); } }; IDBErrorHandler(pw, request); }; function IDBBlockedRequestHandler(pw, request) { IDBRequestHandler(pw, request); request.onblocked = function (e) { pw.progress(e.target, ["blocked", e]); }; }; function IDBOpenDbRequestHandler(pw, request) { IDBBlockedRequestHandler(pw, request); request.onupgradeneeded = function (e) { pw.progress(e.target, [e.target.transaction, e]); }; }; function IDBDatabaseHandler(pw, database) { IDBAbortHandler(pw, database); IDBErrorHandler(pw, database); IDBVersionChangeHandler(pw, database); }; function IDBTransactionHandler(pw, txn) { IDBCompleteHandler(pw, txn); IDBAbortHandler(pw, txn); IDBErrorHandler(pw, txn); }; })(window, typeof Windows !== "undefined"); window.linq2indexedDB = linq2indexedDB; } else { // Web Worker Thread onmessage = function (event) { var data = event.data.data; var filtersString = event.data.filters || "[]"; var sortClauses = event.data.sortClauses || []; var filters = JSON.parse(filtersString, linq2indexedDB.prototype.utilities.deserialize); var returnData = linq2indexedDB.prototype.utilities.filterSort(data, filters, sortClauses); postMessage(returnData); return; }; } // Extend array for Opera //Array.prototype.contains = function (obj) { // return this.indexOf(obj) > -1; //};