react-native-sqlcipher-storage
Version:
SQLCipher bindings for React Native (Android & iOS)
427 lines (383 loc) • 15.9 kB
JavaScript
var SQLite3JS = (function () {
"use strict";
var SQLite3JS, Database, ItemDataSource, GroupDataSource;
SQLite3JS = {
/// Set this to true to get some more logging output
debug: false,
logger: {
trace: console.log.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
info: console.info.bind(console)
}
};
function PromiseQueue() {
this._items = [];
this._busy = false;
}
PromiseQueue.prototype.append = function (createPromise) {
var wrappingPromise,
queueItem = { createPromise: createPromise },
_this = this;
wrappingPromise = new WinJS.Promise(function (complete, error) {
queueItem.complete = complete;
queueItem.error = error;
}, function () {
if (queueItem.promise) {
queueItem.promise.cancel();
} else {
queueItem.cancelled = true;
}
_this._handleNext();
});
this._items.push(queueItem);
if (!this._busy) {
this._handleNext();
}
return wrappingPromise;
};
PromiseQueue.prototype._handleNext = function () {
var _this = this;
/* shorten call stack */
this._busy = true;
setImmediate(function () {
if (_this._items.length > 0) {
var nextItem = _this._items[0];
_this._items = _this._items.slice(1);
_this._handleItem(nextItem);
} else {
_this._busy = false;
}
});
};
PromiseQueue.prototype._handleItem = function (queueItem) {
var _this = this;
if (!queueItem.cancelled) {
queueItem.promise = queueItem.createPromise();
queueItem.promise.done(function (result) {
_this._handleNext();
queueItem.complete(result);
}, function (error) {
_this._handleNext();
queueItem.error(error);
});
} else {
this._handleNext();
}
};
function toPropertySet(object) {
var key, propertySet = new Windows.Foundation.Collections.PropertySet();
for (key in object) {
if (object.hasOwnProperty(key)) {
propertySet.insert(key, object[key]);
}
}
return propertySet;
}
function prepareArgs(args) {
args = args || [];
return (args instanceof Array) ? args : toPropertySet(args);
}
function formatStatementAndArgs(sql, args) {
var argString = args ? ' ' + args.toString() : '';
return '"' + sql + '", ' + argString;
}
function wrapException(exception, detailedMessage, functionName, sql, args) {
var error, message, resultCode, number;
if (exception.hasOwnProperty('number')) {
// Convert the COM error to an unsigned hex value that we can check in JS like E_FAIL == 0x80004005
number = 0xffffffff + exception.number + 1;
resultCode = number & 0x20000000 ? exception.number & 0xffff : 0;
message = (resultCode > 0 ? resultCode : "0x" + number.toString(16)) + ": ";
if (functionName) {
message += functionName;
if (sql) {
message += '(' + formatStatementAndArgs(sql, args) + ') ';
} else {
message += " ";
}
}
if (detailedMessage) {
message += detailedMessage;
}
error = new WinJS.ErrorFromName("SQLiteError", message);
error.resultCode = resultCode;
error.number = number;
error.sql = sql;
error.args = args;
error.functionName = functionName;
} else {
error = exception;
}
return WinJS.Promise.wrapError(error);
}
function wrapDatabase(connection) {
var that, queue = new PromiseQueue();
function callNativeAsync(funcName, sql, args, callback) {
var argString, preparedArgs, fullFuncName;
return queue.append(function () {
if (SQLite3JS.debug) {
SQLite3JS.logger.trace(funcName + ': ' + formatStatementAndArgs(sql, args));
}
preparedArgs = prepareArgs(args);
fullFuncName =
preparedArgs instanceof Windows.Foundation.Collections.PropertySet
? funcName + "Map"
: funcName + "Vector";
return connection[fullFuncName](sql, preparedArgs, callback).then(null, function (error) {
return wrapException(error, that.lastError, funcName, sql, args);
});
});
}
that = {
runAsync: function (sql, args) {
return callNativeAsync('runAsync', sql, args).then(function (affectedRowCount) {
return affectedRowCount;
});
},
oneAsync: function (sql, args) {
return callNativeAsync('oneAsync', sql, args).then(function (row) {
return row ? JSON.parse(row) : null;
});
},
allAsync: function (sql, args) {
return callNativeAsync('allAsync', sql, args).then(function (rows) {
return rows ? JSON.parse(rows) : null;
});
},
eachAsync: function (sql, args, callback) {
if (!callback && typeof args === 'function') {
callback = args;
args = undefined;
}
return callNativeAsync('eachAsync', sql, args, function (row) {
callback(JSON.parse(row));
}).then(function () {
return that;
});
},
mapAsync: function (sql, args, callback) {
if (!callback && typeof args === 'function') {
callback = args;
args = undefined;
}
var results = [];
return that.eachAsync(sql, args, function (row) {
results.push(callback(row));
}).then(function () {
return results;
});
},
itemDataSource: function (sql, args, keyColumnName, groupKeyColumnName) {
if (typeof args === 'string') {
groupKeyColumnName = keyColumnName;
keyColumnName = args;
args = undefined;
}
return new ItemDataSource(that, sql, args, keyColumnName, groupKeyColumnName);
},
groupDataSource: function (sql, args, keyColumnName, sizeColumnName) {
if (typeof args === 'string') {
sizeColumnName = keyColumnName;
keyColumnName = args;
args = undefined;
}
return new GroupDataSource(that, sql, args, keyColumnName, sizeColumnName);
},
close: function () {
connection.close();
},
vacuumAsync: function () {
return new WinJS.Promise(function (complete) {
connection.vacuumAsync();
complete();
});
},
totalChanges: function () {
return connection.totalChanges;
},
addEventListener: connection.addEventListener.bind(connection),
removeEventListener: connection.removeEventListener.bind(connection)
};
Object.defineProperties(
that,
WinJS.Utilities.createEventProperties('update', 'delete', 'insert')
);
Object.defineProperties(that, {
"collationLanguage": {
set: function (value) { connection.collationLanguage = value; },
get: function () { return connection.collationLanguage; },
enumerable: true
},
"fireEvents": {
set: function (value) { connection.fireEvents = value; },
get: function () { return connection.fireEvents; },
enumerable: true
},
"lastError": {
get: function () { return connection.lastError; },
enumerable: true
},
"autoCommit": {
get: function () { return connection.autoCommit; },
enumerable: true
},
"lastInsertRowId": {
get: function () { return connection.lastInsertRowId; },
enumerable: true
}
});
return that;
}
ItemDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource,
function (db, sql, args, keyColumnName, groupKeyColumnName) {
this._dataAdapter = {
setQuery: function (sql, args) {
this._sql = sql;
this._args = args;
},
getCount: function () {
return db.oneAsync('SELECT COUNT(*) AS cnt FROM (' + this._sql + ')', this._args)
.then(function (row) { return row.cnt; });
},
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
var items,
limit = countBefore + 1 + countAfter,
offset = requestIndex - countBefore,
that = this;
return this.getCount().then(function (totalCount) {
return db.mapAsync(
'SELECT * FROM (' + that._sql + ') LIMIT ' + limit + ' OFFSET ' + offset,
that._args,
function (row) {
var item = {
key: row[keyColumnName].toString(),
data: row
};
if (groupKeyColumnName) {
if (!row.hasOwnProperty(groupKeyColumnName) || row[groupKeyColumnName] === null) {
throw "Group key property not found: " + groupKeyColumnName;
}
item.groupKey = row[groupKeyColumnName].toString();
}
return item;
}).then(function (items) {
return {
items: items,
offset: countBefore,
totalCount: totalCount
};
});
});
},
setNotificationHandler: function (notificationHandler) {
this._notificationHandler = notificationHandler;
},
getNotificationHandler: function () {
return this._notificationHandler;
}
};
this._dataAdapter.setQuery(sql, args);
this._baseDataSourceConstructor(this._dataAdapter);
}, {
setQuery: function (sql, args) {
this._dataAdapter.setQuery(sql, args);
this.invalidateAll();
},
getNotificationHandler: function () {
return this._dataAdapter.getNotificationHandler();
}
}
);
GroupDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource,
function (db, sql, args, keyColumnName, sizeColumnName) {
var dataAdapter = {
_keyIndexMap: {},
_ensureGroupsAsync: function () {
if (dataAdapter._groups) {
return WinJS.Promise.wrap();
}
var groupIndex = 0,
firstItemIndex = 0;
return db.mapAsync(sql, args, function (row) {
var item = {
key: row[keyColumnName].toString(),
groupSize: row[sizeColumnName],
firstItemIndexHint: firstItemIndex,
data: row
};
dataAdapter._keyIndexMap[item.key] = groupIndex;
groupIndex += 1;
firstItemIndex += item.groupSize;
return item;
}).then(function (groups) {
dataAdapter._groups = groups.filter(function (group) { return group.groupSize > 0; });
});
},
getCount: function () {
return dataAdapter._ensureGroupsAsync().then(function () {
return dataAdapter._groups.length;
});
},
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
return dataAdapter._ensureGroupsAsync().then(function () {
return {
items: dataAdapter._groups.slice(),
offset: requestIndex,
absoluteIndex: requestIndex,
totalCount: dataAdapter._groups.length,
atStart: true,
atEnd: true
};
});
},
itemsFromKey: function (key, countBefore, countAfter) {
return dataAdapter._ensureGroupsAsync().then(function () {
return dataAdapter.itemsFromIndex(dataAdapter._keyIndexMap[key], countBefore, countAfter);
});
},
setNotificationHandler: function (notificationHandler) {
this._notificationHandler = notificationHandler;
},
getNotificationHandler: function () {
return this._notificationHandler;
}
};
this._dataAdapter = dataAdapter;
this._baseDataSourceConstructor(dataAdapter);
}, {
getNotificationHandler: function () {
return this._dataAdapter.getNotificationHandler();
}
}
);
SQLite3JS.openAsync = function (dbPath, options) {
/// <summary>
/// Opens a database from disk or in memory.
/// </summary>
/// <param name="dbPath" type="String">
/// Path to a file that is located in your apps local/temp/roaming storage or the string ":memory:"
/// to create a database in memory
/// </param>
/// <returns>Database object upon completion of the promise</returns>
var openWithKey = !!options.key && options.key.length !== 0;
var promise = openWithKey ? SQLite3.Database.openAsyncWithKey(dbPath, options.key) : SQLite3.Database.openAsync(dbpath);
return promise.then(
connection => {
var db = wrapDatabase(connection);
if (SQLite3JS.version) {
return db;
}
return db.oneAsync("SELECT sqlite_version() || ' (' || sqlite_source_id() || ')' as version")
.then(
result => {
SQLite3JS.logger.info("SQLite3 version: " + (SQLite3JS.version = result.version));
return db;
}
);
}, error => {
return wrapException(error, 'Could not open database "' + dbPath + '"', "openAsync");
});
};
return SQLite3JS;
}());