lib/store/indexeddb.js (276 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ 'use strict'; /** @module store/indexeddb */ var utils = require('./../utils.js'); // Imports. var throwErrorCallback = utils.throwErrorCallback; var delay = utils.delay; var indexedDB = utils.inBrowser() ? window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB : undefined; var IDBKeyRange = utils.inBrowser() ? window.IDBKeyRange || window.webkitIDBKeyRange : undefined; var IDBTransaction = utils.inBrowser() ? window.IDBTransaction || window.webkitIDBTransaction || {} : {} ; var IDBT_READ_ONLY = IDBTransaction.READ_ONLY || "readonly"; var IDBT_READ_WRITE = IDBTransaction.READ_WRITE || "readwrite"; /** Returns either a specific error handler or the default error handler * @param {Function} error - The specific error handler * @param {Function} defaultError - The default error handler * @returns {Function} The error callback */ function getError(error, defaultError) { return function (e) { var errorFunc = error || defaultError; if (!errorFunc) { return; } // Old api quota exceeded error support. if (Object.prototype.toString.call(e) === "[object IDBDatabaseException]") { if (e.code === 11 /* IndexedDb disk quota exceeded */) { errorFunc({ name: "QuotaExceededError", error: e }); return; } errorFunc(e); return; } var errName; try { var errObj = e.target.error || e; errName = errObj.name; } catch (ex) { errName = (e.type === "blocked") ? "IndexedDBBlocked" : "UnknownError"; } errorFunc({ name: errName, error: e }); }; } /** Opens the store object's indexed db database. * @param {IndexedDBStore} store - The store object * @param {Function} success - The success callback * @param {Function} error - The error callback */ function openStoreDb(store, success, error) { var storeName = store.name; var dbName = "_odatajs_" + storeName; var request = indexedDB.open(dbName); request.onblocked = error; request.onerror = error; request.onupgradeneeded = function () { var db = request.result; if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName); } }; request.onsuccess = function (event) { var db = request.result; if (!db.objectStoreNames.contains(storeName)) { // Should we use the old style api to define the database schema? if ("setVersion" in db) { var versionRequest = db.setVersion("1.0"); versionRequest.onsuccess = function () { var transaction = versionRequest.transaction; transaction.oncomplete = function () { success(db); }; db.createObjectStore(storeName, null, false); }; versionRequest.onerror = error; versionRequest.onblocked = error; return; } // The database doesn't have the expected store. // Fabricate an error object for the event for the schema mismatch // and error out. event.target.error = { name: "DBSchemaMismatch" }; error(event); return; } db.onversionchange = function(event) { event.target.close(); }; success(db); }; } /** Opens a new transaction to the store * @param {IndexedDBStore} store - The store object * @param {Integer} mode - The read/write mode of the transaction (constants from IDBTransaction) * @param {Function} success - The success callback * @param {Function} error - The error callback */ function openTransaction(store, mode, success, error) { var storeName = store.name; var storeDb = store.db; var errorCallback = getError(error, store.defaultError); if (storeDb) { success(storeDb.transaction(storeName, mode)); return; } openStoreDb(store, function (db) { store.db = db; success(db.transaction(storeName, mode)); }, errorCallback); } /** Creates a new IndexedDBStore. * @class IndexedDBStore * @constructor * @param {String} name - The name of the store. * @returns {Object} The new IndexedDBStore. */ function IndexedDBStore(name) { this.name = name; } /** Creates a new IndexedDBStore. * @method module:store/indexeddb~IndexedDBStore.create * @param {String} name - The name of the store. * @returns {Object} The new IndexedDBStore. */ IndexedDBStore.create = function (name) { if (IndexedDBStore.isSupported()) { return new IndexedDBStore(name); } throw { message: "IndexedDB is not supported on this browser" }; }; /** Returns whether IndexedDB is supported. * @method module:store/indexeddb~IndexedDBStore.isSupported * @returns {Boolean} True if IndexedDB is supported, false otherwise. */ IndexedDBStore.isSupported = function () { return !!indexedDB; }; /** Adds a key/value pair to the store * @method module:store/indexeddb~IndexedDBStore#add * @param {String} key - The key * @param {Object} value - The value * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.add = function (key, value, success, error) { var name = this.name; var defaultError = this.defaultError; var keys = []; var values = []; if (key instanceof Array) { keys = key; values = value; } else { keys = [key]; values = [value]; } openTransaction(this, IDBT_READ_WRITE, function (transaction) { transaction.onabort = getError(error, defaultError, key, "add"); transaction.oncomplete = function () { if (key instanceof Array) { success(keys, values); } else { success(key, value); } }; for (var i = 0; i < keys.length && i < values.length; i++) { transaction.objectStore(name).add({ v: values[i] }, keys[i]); } }, error); }; /** Adds or updates a key/value pair in the store * @method module:store/indexeddb~IndexedDBStore#addOrUpdate * @param {String} key - The key * @param {Object} value - The value * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.addOrUpdate = function (key, value, success, error) { var name = this.name; var defaultError = this.defaultError; var keys = []; var values = []; if (key instanceof Array) { keys = key; values = value; } else { keys = [key]; values = [value]; } openTransaction(this, IDBT_READ_WRITE, function (transaction) { transaction.onabort = getError(error, defaultError); transaction.oncomplete = function () { if (key instanceof Array) { success(keys, values); } else { success(key, value); } }; for (var i = 0; i < keys.length && i < values.length; i++) { var record = { v: values[i] }; transaction.objectStore(name).put(record, keys[i]); } }, error); }; /** Clears the store * @method module:store/indexeddb~IndexedDBStore#clear * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.clear = function (success, error) { var name = this.name; var defaultError = this.defaultError; openTransaction(this, IDBT_READ_WRITE, function (transaction) { transaction.onerror = getError(error, defaultError); transaction.oncomplete = function () { success(); }; transaction.objectStore(name).clear(); }, error); }; /** Closes the connection to the database * @method module:store/indexeddb~IndexedDBStore#close */ IndexedDBStore.prototype.close = function () { if (this.db) { this.db.close(); this.db = null; } }; /** Returns whether the store contains a key * @method module:store/indexeddb~IndexedDBStore#contains * @param {String} key - The key * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.contains = function (key, success, error) { var name = this.name; var defaultError = this.defaultError; openTransaction(this, IDBT_READ_ONLY, function (transaction) { var objectStore = transaction.objectStore(name); var request = objectStore.get(key); transaction.oncomplete = function () { success(!!request.result); }; transaction.onerror = getError(error, defaultError); }, error); }; IndexedDBStore.prototype.defaultError = throwErrorCallback; /** Gets all the keys from the store * @method module:store/indexeddb~IndexedDBStore#getAllKeys * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.getAllKeys = function (success, error) { var name = this.name; var defaultError = this.defaultError; openTransaction(this, IDBT_READ_WRITE, function (transaction) { var results = []; transaction.oncomplete = function () { success(results); }; var request = transaction.objectStore(name).openCursor(); request.onerror = getError(error, defaultError); request.onsuccess = function (event) { var cursor = event.target.result; if (cursor) { results.push(cursor.key); // Some tools have issues because continue is a javascript reserved word. cursor["continue"].call(cursor); } }; }, error); }; /** Identifies the underlying mechanism used by the store. */ IndexedDBStore.prototype.mechanism = "indexeddb"; /** Reads the value for the specified key * @method module:store/indexeddb~IndexedDBStore#read * @param {String} key - The key * @param {Function} success - The success callback * @param {Function} error - The error callback * If the key does not exist, the success handler will be called with value = undefined */ IndexedDBStore.prototype.read = function (key, success, error) { var name = this.name; var defaultError = this.defaultError; var keys = (key instanceof Array) ? key : [key]; openTransaction(this, IDBT_READ_ONLY, function (transaction) { var values = []; transaction.onerror = getError(error, defaultError, key, "read"); transaction.oncomplete = function () { if (key instanceof Array) { success(keys, values); } else { success(keys[0], values[0]); } }; for (var i = 0; i < keys.length; i++) { // Some tools have issues because get is a javascript reserved word. var objectStore = transaction.objectStore(name); var request = objectStore.get.call(objectStore, keys[i]); request.onsuccess = function (event) { var record = event.target.result; values.push(record ? record.v : undefined); }; } }, error); }; /** Removes the specified key from the store * @method module:store/indexeddb~IndexedDBStore#remove * @param {String} key - The key * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.remove = function (key, success, error) { var name = this.name; var defaultError = this.defaultError; var keys = (key instanceof Array) ? key : [key]; openTransaction(this, IDBT_READ_WRITE, function (transaction) { transaction.onerror = getError(error, defaultError); transaction.oncomplete = function () { success(); }; for (var i = 0; i < keys.length; i++) { // Some tools have issues because continue is a javascript reserved word. var objectStore = transaction.objectStore(name); objectStore["delete"].call(objectStore, keys[i]); } }, error); }; /** Updates a key/value pair in the store * @method module:store/indexeddb~IndexedDBStore#update * @param {String} key - The key * @param {Object} value - The value * @param {Function} success - The success callback * @param {Function} error - The error callback */ IndexedDBStore.prototype.update = function (key, value, success, error) { var name = this.name; var defaultError = this.defaultError; var keys = []; var values = []; if (key instanceof Array) { keys = key; values = value; } else { keys = [key]; values = [value]; } openTransaction(this, IDBT_READ_WRITE, function (transaction) { transaction.onabort = getError(error, defaultError); transaction.oncomplete = function () { if (key instanceof Array) { success(keys, values); } else { success(key, value); } }; for (var i = 0; i < keys.length && i < values.length; i++) { var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i])); var record = { v: values[i] }; request.pair = { key: keys[i], value: record }; request.onsuccess = function (event) { var cursor = event.target.result; if (cursor) { cursor.update(event.target.pair.value); } else { transaction.abort(); } } } }, error); }; module.exports = IndexedDBStore;