UNPKG

nativescript-sqlite

Version:

A sqlite NativeScript module for Android and iOS

1,022 lines (905 loc) 32.7 kB
/************************************************************************************ * (c) 2015-2021 Master Technology * Licensed under the MIT license or contact me for a support, changes, enhancements, * and/or if you require a commercial licensing * * Any questions please feel free to email me or put a issue up on github * Nathan@master-technology.com http://nativescript.tools * Version 2.7.0 - iOS ***********************************************************************************/ /* global global, require, module */ "use strict"; const fs = require('@nativescript/core/file-system'); /* jshint undef: true, camelcase: false */ /* global Promise, NSFileManager, NSBundle, NSString, interop, sqlite3_open_v2, sqlite3_close, sqlite3_prepare_v2, sqlite3_step, sqlite3_finalize, sqlite3_bind_null, sqlite3_bind_text, sqlite3_column_type, sqlite3_column_int64, sqlite3_column_double, sqlite3_column_text, sqlite3_column_count, sqlite3_column_name, sqlite3_bind_blob */ let _DatabasePluginInits = []; const TRANSIENT = new interop.Pointer(-1); /*** * Converts a string to a UTF-8 char array and wraps it in an adopted pointer * (which is GC managed and kept alive until it's referenced by a variable). * The reason for doing it is that iOS runtime marshals JS string arguments via * temporary buffers which are deallocated immediately after the call returns. In * some cases however, we need them to stay alive for subsequent native calls * because otherwise attempts to read the freed memory may lead to unpredictable * app crashes. * E.g. `sqlite3_step` function happens to use the stored `char *` passed as * `dbname` to `sqlite3_open_v2`. * @param str * @returns {AdoptedPointer} object */ function toCharPtr(str) { const objcStr = NSString.stringWithString(str); // UTF8 strings can be encoded in 4 byte representing each character // We are wasting a few bytes of memory, just to make sure we have enough room to copy it... const bufferSize = ((str.length) * 4) + 1; const buffer = interop.alloc(bufferSize); objcStr.getCStringMaxLengthEncoding(buffer, bufferSize, NSUTF8StringEncoding); return buffer; } /*** * Creates a Cursor Tracking Statement for reading result sets * @param statement * @param resultType * @param valuesType * @constructor */ function CursorStatement(statement, resultType, valuesType) { this.statement = statement; this.resultType = resultType; this.valuesType = valuesType; this.built = false; this.columns = []; } //noinspection JSValidateJSDoc /*** * Database Constructor * @param dbname - Database Name * @param options - options * @param callback - Callback when Done * @returns {Promise} object * @constructor */ function Database(dbname, options, callback) { if (!(this instanceof Database)) { // jshint ignore:line //noinspection JSValidateTypes return new Database(dbname, options, callback); } this._messageHandlers = []; this._isOpen = false; this._resultType = Database.RESULTSASARRAY; this._valuesType = Database.VALUESARENATIVE; if (typeof options === 'function') { callback = options; options = {}; } else { options = options || {}; } this._options = options; // Check to see if it has a path, or if it is a relative dbname // DBNAME = "" - is a Temporary Database // DBNAME = ":memory:" - is a Memory only database if (dbname !== "" && dbname !== ":memory:") { let path; if (dbname.indexOf('/') === -1) { // noinspection JSUnresolvedVariable, JSUnresolvedFunction path = fs.knownFolders.documents().path; dbname = path + '/' + dbname; } else { path = dbname.substr(0, dbname.lastIndexOf('/') + 1); } // Create "databases" folder if it is missing. This causes issues on Emulators if it is missing // So we create it if it is missing try { // noinspection JSUnresolvedVariable if (!fs.File.exists(path)) { //noinspection JSUnresolvedFunction const fileManager = iosProperty(NSFileManager, NSFileManager.defaultManager); //noinspection JSUnresolvedFunction if (!fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(path, true, null, null)) { console.warn("SQLITE.CONSTRUCTOR - Creating DB Folder Error"); } } } catch (err) { console.warn("SQLITE.CONSTRUCTOR - Creating DB Folder Error", err); } } this._dbnamePtr = toCharPtr(dbname); const self = this; //noinspection JSUnresolvedFunction return new Promise(function (resolve, reject) { let error; try { let flags = 0; if (typeof options.iosFlags !== 'undefined') { flags = options.iosFlags; } self._db = new interop.Reference(); if (options && options.readOnly) { // SQLITE_OPEN_FULLMUTEX = 65536, SQLITE_OPEN_READONLY = 1 ---- 1 | 65536 = 65537 error = sqlite3_open_v2(self._dbnamePtr, self._db, 65537 | flags, null); } else { // SQLITE_OPEN_FULLMUTEX = 65536, SQLITE_OPEN_CREATE = 4, SQLITE_OPEN_READWRITE = 2 --- 4 | 2 | 65536 = 65542 error = sqlite3_open_v2(self._dbnamePtr, self._db, 65542 | flags, null); } self._db = self._db.value; } catch (err) { if (callback) { callback(err, null); } reject(err); return; } if (error) { if (callback) { callback(error, null); } reject(error); return; } self._isOpen = true; let doneCnt = _DatabasePluginInits.length, doneHandled = 0; const done = function(err) { if (err) { doneHandled = doneCnt; // We don't want any more triggers after this if (callback) { callback(err, null); } reject(err); return; } doneHandled++; if (doneHandled === doneCnt) { if (callback) { callback(null, self); } resolve(self); } }; if (doneCnt) { try { for (let i = 0; i < doneCnt; i++) { _DatabasePluginInits[i].call(self, options, done); } } catch (err) { done(err); } } else { if (callback) { callback(null, self); } resolve(self); } }); } /*** * Constant that this structure is a sqlite structure * @type {boolean} */ Database.prototype._isSqlite = true; /*** * This gets or sets the database version * @param valueOrCallback to set or callback(err, version) * @returns Promise */ Database.prototype.version = function(valueOrCallback) { return new Promise((resolve, reject) => { if (typeof valueOrCallback === 'function') { this.get('PRAGMA user_version', function (err, data) { const value = data && parseInt(data[0], 10); valueOrCallback(err, value); if (err) { reject(err); } else resolve(value); }, Database.RESULTSASARRAY); } else if (!isNaN(valueOrCallback + 0)) { this.execSQL('PRAGMA user_version=' + (valueOrCallback + 0).toString()).then(resolve, reject); } else { this.get('PRAGMA user_version', undefined, undefined, Database.RESULTSASARRAY).then((data) => { resolve(data && parseInt(data[0], 10)) }).catch(reject); } }); }; /*** * Is the database currently open * @returns {boolean} - true if the db is open */ Database.prototype.isOpen = function() { return this._isOpen; }; /*** * Gets/Sets whether you get Arrays or Objects for the row values * @param value - Database.RESULTSASARRAY or Database.RESULTSASOBJECT * @returns {number} - Database.RESULTSASARRAY or Database.RESULTSASOBJECT */ Database.prototype.resultType = function(value) { if (value === Database.RESULTSASARRAY) { this._resultType = Database.RESULTSASARRAY; } else if (value === Database.RESULTSASOBJECT) { this._resultType = Database.RESULTSASOBJECT; } return this._resultType; }; /*** * Gets/Sets whether you get Native or Strings for the row values * @param value - Database.VALUESARENATIVE or Database.VALUESARESTRINGS * @returns {number} - Database.VALUESARENATIVE or Database.VALUESARESTRINGS */ Database.prototype.valueType = function(value) { if (value === Database.VALUESARENATIVE) { this._valuesType = Database.VALUESARENATIVE; } else if (value === Database.VALUESARESTRINGS) { this._valuesType = Database.VALUESARESTRINGS; } return this._valuesType; }; /** * Dummy transaction function for public version * @param callback * @returns {Promise<T>} */ Database.prototype.begin = function(callback) { throw new Error("Transactions are a Commercial version feature."); }; /** * Dummy prepare function for public version * @param sql * @returns {*} */ Database.prototype.prepare = function(sql) { throw new Error("Prepared statements are a Commercial version feature."); }; // noinspection JSUnusedLocalSymbols /** * Dummy sync enable tracking function for public version * @returns {*} */ Database.prototype.enableTracking = function(tables, options, callback) { throw new Error("Table sync is a Commercial version feature."); }; /*** * Closes this database, any queries after this will fail with an error * @param callback * @returns Promise */ Database.prototype.close = function(callback) { const self = this; return new Promise( function (resolve, reject) { if (!self._isOpen) { if (callback) { callback('SQLITE.CLOSE - Database is already closed'); } reject('SQLITE.CLOSE - Database is already closed'); return; } sqlite3_close(self._db); self._db = null; self._isOpen = false; if (self._dbnamePtr != null) { interop.free(self._dbnamePtr); self._dbnamePtr = null; } if (callback) { callback(null, null); } resolve(); }); }; /*** * Exec SQL * @param sql - sql to use * @param params - optional array of parameters * @param callback - (err, result) - can be last_row_id for insert, and rows affected for update/delete * @returns Promise */ Database.prototype.execSQL = function(sql, params, callback) { if (typeof params === 'function') { callback = params; params = undefined; } const self = this; return new Promise( function(resolve, reject) { let hasCallback = true; if (typeof callback !== 'function') { callback = reject; hasCallback = false; } if (!self._isOpen) { callback("SQLITE.EXECSQL - Database is not open"); return; } // Need to see if we have to run any status queries afterwords let flags = 0; let test = sql.trim().substr(0, 7).toLowerCase(); if (test === 'insert ') { flags = 1; } else if (test === 'update ' || test === 'delete ') { flags = 2; } let res; try { let statement = new interop.Reference(); res = sqlite3_prepare_v2(self._db, sql, -1, statement, null); statement = statement.value; if (res) { callback("SQLITE.ExecSQL Failed Prepare: " + res); return; } if (params !== undefined) { if (!self._bind(statement, params)) { sqlite3_finalize(statement); callback("SQLITE.ExecSQL Bind Error"); return; } } let result = sqlite3_step(statement); sqlite3_finalize(statement); if (result && result !== 100 && result !== 101) { callback("SQLITE.ExecSQL Failed " + result); return; } } catch (Err) { callback(Err, null); return; } switch (flags) { case 0: if (hasCallback) { callback(); } resolve(); break; case 1: self.get('select last_insert_rowid()', function (err, data) { if (hasCallback) { callback(err, data && data[0]); } if (err) { reject(err); } else { resolve(data && data[0]); } }, Database.RESULTSASARRAY | Database.VALUESARENATIVE); break; case 2: self.get('select changes()', function (err, data) { if (hasCallback) { callback(err, data && data[0]); } if (err) { reject(err); } else { resolve(data && data[0]); } }, Database.RESULTSASARRAY | Database.VALUESARENATIVE); break; default: if (hasCallback) { callback(); } resolve(); } }); }; /*** * Get the first record result set * @param sql - sql to run * @param params - optional * @param callback - callback (error, results) * @param mode - allows you to manually override the results set to be a array or object * @returns Promise */ Database.prototype.get = function(sql, params, callback, mode) { if (typeof params === 'function') { mode = callback; callback = params; params = undefined; } const self = this; return new Promise( function (resolve, reject) { let hasCallback = true; if (typeof callback !== 'function') { callback = reject; hasCallback = false; } if (!self._isOpen) { callback("SQLITE.GET - Database is not open"); return; } let cursor; try { let statement = new interop.Reference(); let res = sqlite3_prepare_v2(self._db, sql, -1, statement, null); if (res) { callback("SQLITE.GET Failed Prepare: " + res); return; } statement = statement.value; let cursorStatement = new CursorStatement(statement, self._resultType, self._valuesType); if (params !== undefined) { if (!self._bind(statement, params)) { sqlite3_finalize(statement); callback("SQLITE.GET Bind Error"); return; } } let result = sqlite3_step(statement); if (result === 100) { cursor = self._getResults(cursorStatement, mode); } sqlite3_finalize(statement); if (result && result !== 100 && result !== 101) { callback("SQLITE.GET - Step Error" + result); return; } } catch (err) { callback(err); return; } // No Records if (!cursor) { if (hasCallback) { callback(null, null); } resolve(null); return; } if (hasCallback) { callback(null, cursor); } resolve(cursor); }); }; /*** * This returns the entire result set in a array of rows * @param sql - Sql to run * @param params - optional * @param callback - (err, results) * @param mode - set a specific return mode * @returns Promise */ Database.prototype.all = function(sql, params, callback, mode) { if (typeof params === 'function') { callback = params; params = undefined; } const self = this; return new Promise(function(resolve, reject) { let hasCallback = true; if (typeof callback !== 'function') { callback = reject; hasCallback = false; } if (!self._isOpen) { callback("SQLITE.ALL - Database is not open"); return; } let rows = [], res; try { let statement = new interop.Reference(); res = sqlite3_prepare_v2(self._db, sql, -1, statement, null); if (res) { callback("SQLITE.ALL - Prepare Error " + res); return; } statement = statement.value; let cursorStatement = new CursorStatement(statement, self._resultType, self._valuesType); if (params !== undefined) { if (!self._bind(statement, params)) { sqlite3_finalize(statement); callback("SQLITE.ALL Bind Error"); return; } } let result; do { result = sqlite3_step(statement); if (result === 100) { let cursor = self._getResults(cursorStatement, mode); if (cursor) { rows.push(cursor); } } else if (result && result !== 101) { sqlite3_finalize(statement); callback("SQLITE.ALL - Database Error" + result); return; } } while (result === 100); sqlite3_finalize(statement); } catch (err) { callback(err, null); return; } // No Records if (rows.length === 0) { if (hasCallback) { callback(null, []); } resolve([]); return; } if (hasCallback) { callback(null, rows); } resolve(rows); }); }; /*** * This sends each row of the result set to the "Callback" and at the end calls the complete callback upon completion * @param sql - sql to run * @param params - optional * @param callback - callback (err, rowsResult) * @param complete - callback (err, recordCount) * @returns {Promise} */ Database.prototype.each = function(sql, params, callback, complete) { if (typeof params === 'function') { complete = callback; callback = params; params = undefined; } // Callback is required if (typeof callback !== 'function') { throw new Error("SQLITE.EACH - requires a callback"); } const self = this; return new Promise (function(resolve, reject) { // Set the error Callback let errorCB = complete || callback; let count = 0, res; try { let statement = new interop.Reference(); res = sqlite3_prepare_v2(self._db, sql, -1, statement, null); if (res) { errorCB("SQLITE.EACH Error in Prepare" + res); reject("SQLITE.EACH Error in Prepare" + res); return; } statement = statement.value; let cursorStatement = new CursorStatement(statement, self._resultType, self._valuesType); if (params !== undefined) { if (!self._bind(statement, params)) { sqlite3_finalize(statement); errorCB("SQLITE.EACH Bind Error"); reject("SQLITE.EACH Bind Error"); return; } } let result; do { result = sqlite3_step(statement); if (result === 100) { let cursor = self._getResults(cursorStatement); if (cursor) { count++; callback(null, cursor); } } else if (result && result !== 101) { sqlite3_finalize(statement); errorCB("SQLITE.EACH - Database Error " + result); reject("SQLITE.EACH - Database Error " + result); return; } } while (result === 100); sqlite3_finalize(statement); } catch (err) { errorCB(err, null); reject(err); return; } if (complete) { complete(null, count); } resolve(count); }); }; /** * Binds the Parameters in a Statement * @param statement * @param params * @private */ Database.prototype._bind = function(statement, params) { let param, res; let autoDetect = -1; if (Array.isArray(params)) { const count = params.length; for (let i=0; i<count; ++i) { if (params[i] == null) { // jshint ignore:line res = sqlite3_bind_null(statement, i + 1); } else if (params[i].isKindOfClass && (params[i].isKindOfClass(NSData.class()))) { const obj = params[i]; res = sqlite3_bind_blob(statement, i + 1, obj.bytes, obj.length, TRANSIENT); } else { param = params[i].toString(); if (param.length === 0) { autoDetect = 0; } else { autoDetect = -1; } res = sqlite3_bind_text(statement, i+1, param, autoDetect, TRANSIENT ); } if (res) { console.error("SQLITE.Binding Error ", res); return false; } } } else { if (params == null) { // jshint ignore:line res = sqlite3_bind_null(statement, 1); } else if (params.isKindOfClass && (params.isKindOfClass(NSData.class()))) { const obj = params; res = sqlite3_bind_blob(statement, 1, obj.bytes, obj.length, TRANSIENT); } else { param = params.toString(); res = sqlite3_bind_text(statement, 1, param, -1, TRANSIENT ); } if (res) { console.error("SQLITE.Binding Error ", res); return false; } } return true; }; Database.prototype._getNativeResult = function(statement, column) { const resultType = sqlite3_column_type(statement, column); switch (resultType) { case 1: // Int return sqlite3_column_int64(statement, column); case 2: // Float return sqlite3_column_double(statement, column); case 3: // Text //noinspection JSUnresolvedFunction return NSString.stringWithUTF8String(sqlite3_column_text(statement, column)).toString(); case 4: // Blob return NSData.dataWithBytesLength(sqlite3_column_blob(statement, column), sqlite3_column_bytes(statement, column)); case 5: // Null return null; default: //noinspection JSUnresolvedFunction return NSString.stringWithUTF8String(sqlite3_column_text(statement, column)).toString(); } }; Database.prototype._getStringResult = function(statement, column) { const resultType = sqlite3_column_type(statement, column); switch (resultType) { case 1: // Int //return sqlite3_column_int(statement, column).toString(); return NSString.stringWithUTF8String(sqlite3_column_text(statement, column)).toString(); case 2: // Float //return sqlite3_column_double(statement, column).toString(); return NSString.stringWithUTF8String(sqlite3_column_text(statement, column)).toString(); case 3: // Text //noinspection JSUnresolvedFunction return NSString.stringWithUTF8String(sqlite3_column_text(statement, column)).toString(); case 4: // Blob return NSData.dataWithBytesLength(sqlite3_column_blob(statement, column), sqlite3_column_bytes(statement, column)); case 5: // Null return null; default: //noinspection JSUnresolvedFunction return NSString.stringWithUTF8String(sqlite3_column_text(statement, column)).toString(); } }; Database.prototype._getResults = function(cursorStatement, mode) { let resultType, valueType; let statement = cursorStatement.statement; let i; if (!mode) { resultType = cursorStatement.resultType; valueType = cursorStatement.valuesType; } else { resultType = (mode & (Database.RESULTSASARRAY | Database.RESULTSASOBJECT)); valueType = (mode & (Database.VALUESARENATIVE | Database.VALUESARESTRINGS)); if (resultType <= 0) { resultType = cursorStatement.resultType; } if (valueType <= 0) { valueType = cursorStatement.valuesType; } } // Track this statements information so we don't have to build it each time if (!cursorStatement.built) { cursorStatement.count = sqlite3_column_count(statement); if (resultType === Database.RESULTSASOBJECT) { for (i=0;i<cursorStatement.count;i++) { //noinspection JSUnresolvedFunction let cn = NSString.stringWithUTF8String(sqlite3_column_name(statement, i)).toString(); if (!cn || cursorStatement.columns.indexOf(cn) >= 0) { cn = "column"+i; } cursorStatement.columns.push(cn); } } cursorStatement.built=true; } let cnt = cursorStatement.count, data; if (cnt === 0) { return null; } if (resultType === Database.RESULTSASARRAY) { data = []; if (valueType === Database.VALUESARESTRINGS) { for (i = 0; i < cnt; i++) { data.push(this._getStringResult(statement, i)); } } else { for (i = 0; i < cnt; i++) { data.push(this._getNativeResult(statement, i)); } } return data; } else { let colName = cursorStatement.columns; data = {}; if (valueType === Database.VALUESARESTRINGS) { for (i = 0; i < cnt; i++) { data[colName[i]] = this._getStringResult(statement, i); } } else { for (i = 0; i < cnt; i++) { data[colName[i]] = this._getNativeResult(statement, i); } } return data; } }; Database.prototype.notify = function(type, message) { if (typeof global.postMessage === 'function') { postMessage({id: -2, type: type, message: message}); } else { console.error("SQLite: Not in a thread"); // Local Notify this._notify(type, message); } }; Database.prototype._notify = function(type, message) { if (type == null || typeof this._messageHandlers[type] === "undefined") { return; } let handlers = this._messageHandlers[type]; try { for (let i = 0; i < handlers; i++) { handlers[i](message, type, this); } } catch (err) { console.error("SQLite: Error in user code ", err, err.stack); } } Database.prototype.addMessageHandler = function(type, callback) { if (typeof this._messageHandlers[type] === 'undefined') { this._messageHandlers[type] = []; } this._messageHandlers[type].push(callback); }; Database.prototype.removeMessageHandler = function(type, callback) { if (type != null && typeof this._messageHandlers[type] === "undefined") { console.error("SQLite: This message handler " + type + " does not exist."); return; } if (callback) { // Remove all message handles that match this callback & this db... for (let i = 0; i < this._messageHandlers[type].length; i++) { if (this._messageHandlers[type][i].callback === callback) { this._messageHandlers[type].splice(i, 1); i--; } } } else if (type != null) { // Remove all message handlers for this type this._messageHandlers[type] = []; } else { // Remove all message handlers for this database this._messageHandlers = []; } }; /*** * Is this a SQLite object * @param obj - possible sqlite object to check * @returns {boolean} */ Database.isSqlite = function(obj) { return obj && obj._isSqlite; }; Database.exists = function(name) { if (name.indexOf('/') === -1) { name = fs.knownFolders.documents().path + '/' + name; } //noinspection JSUnresolvedFunction const fileManager = iosProperty(NSFileManager, NSFileManager.defaultManager); return fileManager.fileExistsAtPath(name); }; Database.deleteDatabase = function(name) { //noinspection JSUnresolvedFunction const fileManager = iosProperty(NSFileManager, NSFileManager.defaultManager); let path; if (name.indexOf('/') === -1) { path = fs.knownFolders.documents().path + '/'; } else { path = name.substr(0, name.lastIndexOf('/') + 1); name = name.substr(path.length); } if (!fileManager.fileExistsAtPath(path + name)) { return; } // Need to remove the trailing .sqlite let idx = name.lastIndexOf('.'); if (idx) { name = name.substr(0,idx); } let files = fileManager.contentsOfDirectoryAtPathError(path, null); if (!files) { return; } for (let i = 0; i < files.count; i++) { const fileName = files.objectAtIndex(i); if (fileName.indexOf(name) !== -1) { fileManager.removeItemAtPathError(path + fileName, null); } } }; Database.copyDatabase = function (name, destName) { //noinspection JSUnresolvedFunction const fileManager = iosProperty(NSFileManager, NSFileManager.defaultManager); let path; if (name.indexOf('/') === -1) { path = fs.knownFolders.documents().path + '/'; } else { path = name.substr(0, name.lastIndexOf('/') + 1); name = name.substr(path.length); } let source = fs.knownFolders.currentApp().path + '/' + name; if (!destName) { destName = name; } else if (destName.indexOf("/") >= 0) { destName = destName.substring(destName.lastIndexOf('/') + 1); } let destination = path + destName; fileManager.copyItemAtPathToPathError(source, destination, null); }; function UsePlugin(loadedSrc, DBModule) { if (loadedSrc.prototypes) { for (let key in loadedSrc.prototypes) { if (!loadedSrc.prototypes.hasOwnProperty(key)) { continue; } if (DBModule.prototype[key]) { DBModule.prototype["_"+key] = DBModule.prototype[key]; } DBModule.prototype[key] = loadedSrc.prototypes[key]; } } if (loadedSrc.statics) { for (let key in loadedSrc.statics) { if (!loadedSrc.statics.hasOwnProperty(key)) { continue; } DBModule[key] = loadedSrc.statics[key]; } } if (typeof loadedSrc.init === 'function') { _DatabasePluginInits.push(loadedSrc.init); } } function iosProperty(_this, property) { if (typeof property === "function") { // xCode < 8 return property.call(_this); } else { // xCode >= 8 return property; } } // Literal Defines Database.prototype.RESULTSASARRAY = Database.RESULTSASARRAY = 1; Database.prototype.RESULTSASOBJECT = Database.RESULTSASOBJECT = 2; Database.prototype.VALUESARENATIVE = Database.VALUESARENATIVE = 4; Database.prototype.VALUESARESTRINGS = Database.VALUESARESTRINGS = 8; /** These are optional plugins, must have a static require statement for webpack **/ TryLoadingCommercialPlugin(); TryLoadingEncryptionPlugin(); TryLoadingSyncPlugin(); function TryLoadingCommercialPlugin() { try { const sqlCom = require('nativescript-sqlite-commercial'); UsePlugin(sqlCom, Database); } catch (e) { /* Do Nothing if it doesn't exist as it is an optional plugin */ } } function TryLoadingEncryptionPlugin() { try { const sqlEnc = require('nativescript-sqlite-encrypted'); UsePlugin(sqlEnc, Database); } catch (e) { /* Do Nothing if it doesn't exist as it is an optional plugin */ } } function TryLoadingSyncPlugin() { try { const sqlSync = require('nativescript-sqlite-sync'); UsePlugin(sqlSync, Database); } catch (e) { /* Do Nothing if it doesn't exist as it is an optional plugin */ } } module.exports = Database;