diff --git a/README.md b/README.md index b1545a7..d7efef5 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,32 @@ [![npm](https://img.shields.io/npm/dm/localforage-cordovasqlitedriver.svg)](https://www.npmjs.com/package/localforage-cordovasqlitedriver) SQLite driver for [Cordova](https://cordova.apache.org/) apps using [localForage](https://github.com/mozilla/localForage). -## upgrade warning - -v1.2 *BREAKING CHANGE* -The default storage location for SQLite has changed in newer versions of [Cordova SQLite storage plugin](https://github.com/litehelpers/Cordova-sqlite-storage/). - -*WARNING*: The new "default" location value is NOT the same as the old default location and would break an upgrade for an app that was using the old default value (0) on iOS. - -If you are upgrading to a newer version of `localForage-cordovaSQLiteDriver` you need to verify where your previous storage location was and update the `location` property of the localForage database. Otherwise the default is `'default'`. This is to avoid breaking the iCloud Design Guide. See [here](https://github.com/litehelpers/Cordova-sqlite-storage#important-icloud-backup-of-sqlite-database-is-not-allowed) for further details. - -## requirements +## Requirements * [Cordova](https://cordova.apache.org/)/[ionic](http://ionicframework.com/) * [Cordova SQLite storage plugin](https://github.com/litehelpers/Cordova-sqlite-storage/) -* [localForage](https://github.com/mozilla/localForage) v1.1.1+ +* [localForage](https://github.com/mozilla/localForage) v1.4.0+ + * for earlier versions of localforage, please use the v1.2.x releases -## install dependencies +## Install Dependencies * install Cordova-sqlite-storage plugin `cordova plugin add https://github.com/litehelpers/Cordova-sqlite-storage.git` * install localForage-cordovaSQLiteDriver `bower install --save localForage-cordovaSQLiteDriver` -## setup your project +## CHANGELOG + +### v1.3 +Reduce driver size (almost by 50%) by "inheriting" the method implementations of the `localforage.WEBSQL` driver. + +### v1.2 *BREAKING CHANGE* +Add support for newer versions of [Cordova SQLite storage plugin](https://github.com/litehelpers/Cordova-sqlite-storage/) (v0.8.x & v1.2.x). + +*UPGRADE WARNING*: The default storage location for SQLite has changed in newer versions of [Cordova SQLite storage plugin](https://github.com/litehelpers/Cordova-sqlite-storage/). The new "`default`" location value is NOT the same as the old "`default`" location and will break an upgrade for an app that was using the old default value (0) on iOS. If you are upgrading to a newer version of `localForage-cordovaSQLiteDriver` you need to verify where your previous storage location was and update the `location` property of the localForage database. Otherwise the default is `'default'`. This is to avoid breaking the iCloud Design Guide. See [here](https://github.com/litehelpers/Cordova-sqlite-storage#important-icloud-backup-of-sqlite-database-is-not-allowed) for further details. + +### v1.1 +Try using the `getSerializer()` (available in localforage v1.3) as the prefered way to retrieve the serializer. + +## Setup Your Project * Include localforage and localForage-cordovaSQLiteDriver in your main html page, after the cordova include. * Call `defineDriver` and `setDriver` to make localForage use the cordovaSQLiteDriver. diff --git a/bower.json b/bower.json index c2aae4c..f090417 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "localforage-cordovasqlitedriver", - "version": "1.2.1", + "version": "1.3.0", "main": [ "src/localforage-cordovasqlitedriver.js" ], @@ -18,7 +18,7 @@ "site*" ], "dependencies": { - "localforage": "^1.2.1" + "localforage": "^1.4.0" }, "devDependencies": { "es6-promise": "~1.0.0", diff --git a/component.json b/component.json index d590eb3..abddaf4 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name": "localForage-cordovaSQLiteDriver", - "version": "1.2.1", + "version": "1.3.0", "dependencies": { "then/promise": "5.0.0" }, diff --git a/package.json b/package.json index b8fac17..236c49a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "driver", "cordova" ], - "version": "1.2.1", + "version": "1.3.0", "homepage": "https://github.com/thgreasi/localForage-cordovaSQLiteDriver", "repository": { "type": "git", diff --git a/src/localforage-cordovasqlitedriver.js b/src/localforage-cordovasqlitedriver.js index c3f621d..96f1b91 100644 --- a/src/localforage-cordovasqlitedriver.js +++ b/src/localforage-cordovasqlitedriver.js @@ -7,19 +7,11 @@ * Copyright (c) 2015 Mozilla * Licensed under Apache 2.0 license. * - * ====================================== - * - * base64-arraybuffer - * https://github.com/niklasvh/base64-arraybuffer - * - * Copyright (c) 2012 Niklas von Hertzen - * Licensed under the MIT license. */ (function() { 'use strict'; var globalObject = this; - var serializer = null; // // If cordova is not present, we can stop now. // if (!globalObject.cordova) { @@ -72,6 +64,32 @@ }); }); + function getSerializerPromise(localForageInstance) { + if (getSerializerPromise.result) { + return getSerializerPromise.result; + } + if (!localForageInstance || typeof localForageInstance.getSerializer !== 'function') { + Promise.reject(new Error( + 'localforage.getSerializer() was not available! ' + + 'localforage-cordovasqlitedriver required localforage v1.4+')); + } + getSerializerPromise.result = localForageInstance.getSerializer(); + return getSerializerPromise.result; + } + + function getWebSqlDriverPromise(localForageInstance) { + if (getWebSqlDriverPromise.result) { + return getWebSqlDriverPromise.result; + } + if (!localForageInstance || typeof localForageInstance.getDriver !== 'function') { + Promise.reject(new Error( + 'localforage.getDriver() was not available! ' + + 'localforage-cordovasqlitedriver requires localforage v1.4+')); + } + getWebSqlDriverPromise.result = localForageInstance.getDriver(localForageInstance.WEBSQL); + return getWebSqlDriverPromise.result; + } + // Open the cordova sqlite plugin database (automatically creates one if one didn't // previously exist), using any options set in the config. function _initStorage(options) { @@ -87,27 +105,6 @@ } } - var serializerPromise = new Promise(function(resolve, reject) { - - // add support for localforage v1.3.x - if (typeof self.getSerializer === 'function') { - self.getSerializer().then(resolve, reject); - return; - } - - // We allow localForage to be declared as a module or as a - // library available without AMD/require.js. - if (moduleType === ModuleType.DEFINE) { - require(['localforageSerializer'], resolve); - } else if (moduleType === ModuleType.EXPORT) { - // I guess bower installed localforage next to this plugin. - // Making it browserify friendly - resolve(require('./../../localforage/src/utils/serializer')); - } else { - resolve(globalObject.localforageSerializer); - } - }); - var dbInfoPromise = openDatabasePromise.then(function(openDatabase){ return new Promise(function(resolve, reject) { // Open the database; the openDatabase API will automatically @@ -139,302 +136,17 @@ }); }); - return serializerPromise.then(function(lib) { - serializer = lib; - return dbInfoPromise; - }); - } - - function getItem(key, callback) { - var self = this; - - // Cast the key to a string, as that's all we can set as a key. - if (typeof key !== 'string') { - window.console.warn(key + - ' used as a key, but it is not a string.'); - key = String(key); - } - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - t.executeSql('SELECT * FROM ' + dbInfo.storeName + - ' WHERE key = ? LIMIT 1', [key], - function(t, results) { - var result = results.rows.length ? - results.rows.item(0).value : null; - - // Check to see if this is serialized content we need to - // unpack. - if (result) { - result = serializer.deserialize(result); - } - - resolve(result); - }, function(t, error) { - - reject(error); - }); - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - function iterate(iterator, callback) { - var self = this; - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - - dbInfo.db.transaction(function(t) { - t.executeSql('SELECT * FROM ' + dbInfo.storeName, [], - function(t, results) { - var rows = results.rows; - var length = rows.length; - - for (var i = 0; i < length; i++) { - var item = rows.item(i); - var result = item.value; - - // Check to see if this is serialized content - // we need to unpack. - if (result) { - result = serializer.deserialize(result); - } - - result = iterator(result, item.key, i + 1); - - // void(0) prevents problems with redefinition - // of `undefined`. - if (result !== void(0)) { - resolve(result); - return; - } - } - - resolve(); - }, function(t, error) { - reject(error); - }); - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - function setItem(key, value, callback) { - var self = this; - - // Cast the key to a string, as that's all we can set as a key. - if (typeof key !== 'string') { - window.console.warn(key + - ' used as a key, but it is not a string.'); - key = String(key); - } - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - // The localStorage API doesn't return undefined values in an - // "expected" way, so undefined is always cast to null in all - // drivers. See: https://github.com/mozilla/localForage/pull/42 - if (value === undefined) { - value = null; - } - - // Save the original value to pass to the callback. - var originalValue = value; - - serializer.serialize(value, function(value, error) { - if (error) { - reject(error); - } else { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - t.executeSql('INSERT OR REPLACE INTO ' + - dbInfo.storeName + - ' (key, value) VALUES (?, ?)', - [key, value], function() { - resolve(originalValue); - }, function(t, error) { - reject(error); - }); - }, function(sqlError) { - // The transaction failed; check - // to see if it's a quota error. - if (sqlError.code === sqlError.QUOTA_ERR) { - // We reject the callback outright for now, but - // it's worth trying to re-run the transaction. - // Even if the user accepts the prompt to use - // more storage on Safari, this error will - // be called. - // - // TODO: Try to re-run the transaction. - reject(sqlError); - } - }); - } - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - function removeItem(key, callback) { - var self = this; - - // Cast the key to a string, as that's all we can set as a key. - if (typeof key !== 'string') { - window.console.warn(key + - ' used as a key, but it is not a string.'); - key = String(key); - } - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - t.executeSql('DELETE FROM ' + dbInfo.storeName + - ' WHERE key = ?', [key], - function() { - resolve(); - }, function(t, error) { - - reject(error); - }); - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - // Deletes every item in the table. - // TODO: Find out if this resets the AUTO_INCREMENT number. - function clear(callback) { - var self = this; - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - t.executeSql('DELETE FROM ' + dbInfo.storeName, [], - function() { - resolve(); - }, function(t, error) { - reject(error); - }); - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - // Does a simple `COUNT(key)` to get the number of items stored in - // localForage. - function length(callback) { - var self = this; - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - // Ahhh, SQL makes this one soooooo easy. - t.executeSql('SELECT COUNT(key) as c FROM ' + - dbInfo.storeName, [], function(t, results) { - var result = results.rows.item(0).c; - - resolve(result); - }, function(t, error) { - - reject(error); - }); - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - // Return the key located at key index X; essentially gets the key from a - // `WHERE id = ?`. This is the most efficient way I can think to implement - // this rarely-used (in my experience) part of the API, but it can seem - // inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so - // the ID of each key will change every time it's updated. Perhaps a stored - // procedure for the `setItem()` SQL would solve this problem? - // TODO: Don't change ID on `setItem()`. - function key(n, callback) { - var self = this; - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - t.executeSql('SELECT key FROM ' + dbInfo.storeName + - ' WHERE id = ? LIMIT 1', [n + 1], - function(t, results) { - var result = results.rows.length ? - results.rows.item(0).key : null; - resolve(result); - }, function(t, error) { - reject(error); - }); - }); - }).catch(reject); - }); - - executeCallback(promise, callback); - return promise; - } - - function keys(callback) { - var self = this; - - var promise = new Promise(function(resolve, reject) { - self.ready().then(function() { - var dbInfo = self._dbInfo; - dbInfo.db.transaction(function(t) { - t.executeSql('SELECT key FROM ' + dbInfo.storeName, [], - function(t, results) { - var keys = []; - - for (var i = 0; i < results.rows.length; i++) { - keys.push(results.rows.item(i).key); - } - - resolve(keys); - }, function(t, error) { + var serializerPromise = getSerializerPromise(self); + var webSqlDriverPromise = getWebSqlDriverPromise(self); - reject(error); - }); - }); - }).catch(reject); + return Promise.all([ + serializerPromise, + webSqlDriverPromise, + dbInfoPromise + ]).then(function(results) { + dbInfo.serializer = results[0]; + return dbInfoPromise; }); - - executeCallback(promise, callback); - return promise; - } - - function executeCallback(promise, callback) { - if (callback) { - promise.then(function(result) { - callback(null, result); - }, function(error) { - callback(error); - }); - } } var cordovaSQLiteDriver = { @@ -444,17 +156,39 @@ return openDatabasePromise.then(function(openDatabase) { return !!openDatabase; }).catch(function(){ return false; }); - }, - iterate: iterate, - getItem: getItem, - setItem: setItem, - removeItem: removeItem, - clear: clear, - length: length, - key: key, - keys: keys + } }; + function wireUpDriverMethods(driver) { + var LibraryMethods = [ + 'clear', + 'getItem', + 'iterate', + 'key', + 'keys', + 'length', + 'removeItem', + 'setItem' + ]; + + function wireUpDriverMethod(driver, methodName) { + driver[methodName] = function () { + var localForageInstance = this; + var args = arguments; + return getWebSqlDriverPromise(localForageInstance).then(function (webSqlDriver) { + return webSqlDriver[methodName].apply(localForageInstance, args); + }); + }; + } + + for (var i = 0, len = LibraryMethods.length; i < len; i++) { + var methodName = LibraryMethods[i]; + wireUpDriverMethod(driver, LibraryMethods[i]); + } + } + + wireUpDriverMethods(cordovaSQLiteDriver); + if (moduleType === ModuleType.DEFINE) { define('cordovaSQLiteDriver', function() { return cordovaSQLiteDriver;