1import './polyfillNextTick'; 2import customOpenDatabase from '@expo/websql/custom'; 3import { requireNativeModule } from 'expo-modules-core'; 4import { Platform } from 'react-native'; 5const ExpoSQLite = requireNativeModule('ExpoSQLite'); 6function zipObject(keys, values) { 7 const result = {}; 8 for (let i = 0; i < keys.length; i++) { 9 result[keys[i]] = values[i]; 10 } 11 return result; 12} 13/** The database returned by `openDatabase()` */ 14export class SQLiteDatabase { 15 _name; 16 _closed = false; 17 constructor(name) { 18 this._name = name; 19 } 20 /** 21 * Executes the SQL statement and returns a callback resolving with the result. 22 */ 23 exec(queries, readOnly, callback) { 24 if (this._closed) { 25 throw new Error(`The SQLite database is closed`); 26 } 27 ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then((nativeResultSets) => { 28 callback(null, nativeResultSets.map(_deserializeResultSet)); 29 }, (error) => { 30 // TODO: make the native API consistently reject with an error, not a string or other type 31 callback(error instanceof Error ? error : new Error(error)); 32 }); 33 } 34 /** 35 * Executes the SQL statement and returns a Promise resolving with the result. 36 */ 37 async execAsync(queries, readOnly) { 38 if (this._closed) { 39 throw new Error(`The SQLite database is closed`); 40 } 41 const nativeResultSets = await ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly); 42 return nativeResultSets.map(_deserializeResultSet); 43 } 44 /** 45 * @deprecated Use `closeAsync()` instead. 46 */ 47 close = this.closeAsync; 48 /** 49 * Close the database. 50 */ 51 closeAsync() { 52 this._closed = true; 53 return ExpoSQLite.close(this._name); 54 } 55 /** 56 * Delete the database file. 57 * > The database has to be closed prior to deletion. 58 */ 59 deleteAsync() { 60 if (!this._closed) { 61 throw new Error(`Unable to delete '${this._name}' database that is currently open. Close it prior to deletion.`); 62 } 63 return ExpoSQLite.deleteAsync(this._name); 64 } 65 /** 66 * Creates a new transaction with Promise support. 67 * @param asyncCallback A `SQLTransactionAsyncCallback` function that can perform SQL statements in a transaction. 68 * @param readOnly true if all the SQL statements in the callback are read only. 69 */ 70 async transactionAsync(asyncCallback, readOnly = false) { 71 await this.execAsync([{ sql: 'BEGIN;', args: [] }], false); 72 try { 73 const transaction = new ExpoSQLTransactionAsync(this, readOnly); 74 await asyncCallback(transaction); 75 await this.execAsync([{ sql: 'END;', args: [] }], false); 76 } 77 catch (e) { 78 await this.execAsync([{ sql: 'ROLLBACK;', args: [] }], false); 79 throw e; 80 } 81 } 82} 83function _serializeQuery(query) { 84 return Platform.OS === 'android' 85 ? { 86 sql: query.sql, 87 args: query.args.map(_escapeBlob), 88 } 89 : [query.sql, query.args]; 90} 91function _deserializeResultSet(nativeResult) { 92 const [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult; 93 // TODO: send more structured error information from the native module so we can better construct 94 // a SQLException object 95 if (errorMessage !== null) { 96 return { error: new Error(errorMessage) }; 97 } 98 return { 99 insertId, 100 rowsAffected, 101 rows: rows.map((row) => zipObject(columns, row)), 102 }; 103} 104function _escapeBlob(data) { 105 if (typeof data === 'string') { 106 /* eslint-disable no-control-regex */ 107 return data 108 .replace(/\u0002/g, '\u0002\u0002') 109 .replace(/\u0001/g, '\u0001\u0002') 110 .replace(/\u0000/g, '\u0001\u0001'); 111 /* eslint-enable no-control-regex */ 112 } 113 else { 114 return data; 115 } 116} 117const _openExpoSQLiteDatabase = customOpenDatabase(SQLiteDatabase); 118// @needsAudit @docsMissing 119/** 120 * Open a database, creating it if it doesn't exist, and return a `Database` object. On disk, 121 * the database will be created under the app's [documents directory](./filesystem), i.e. 122 * `${FileSystem.documentDirectory}/SQLite/${name}`. 123 * > The `version`, `description` and `size` arguments are ignored, but are accepted by the function 124 * for compatibility with the WebSQL specification. 125 * @param name Name of the database file to open. 126 * @param version 127 * @param description 128 * @param size 129 * @param callback 130 * @return 131 */ 132export function openDatabase(name, version = '1.0', description = name, size = 1, callback) { 133 if (name === undefined) { 134 throw new TypeError(`The database name must not be undefined`); 135 } 136 const db = _openExpoSQLiteDatabase(name, version, description, size, callback); 137 db.exec = db._db.exec.bind(db._db); 138 db.execAsync = db._db.execAsync.bind(db._db); 139 db.closeAsync = db._db.closeAsync.bind(db._db); 140 db.deleteAsync = db._db.deleteAsync.bind(db._db); 141 db.transactionAsync = db._db.transactionAsync.bind(db._db); 142 return db; 143} 144/** 145 * Internal data structure for the async transaction API. 146 * @internal 147 */ 148export class ExpoSQLTransactionAsync { 149 db; 150 readOnly; 151 constructor(db, readOnly) { 152 this.db = db; 153 this.readOnly = readOnly; 154 } 155 async executeSqlAsync(sqlStatement, args) { 156 const resultSets = await this.db.execAsync([{ sql: sqlStatement, args: args ?? [] }], this.readOnly); 157 return resultSets[0]; 158 } 159} 160//# sourceMappingURL=SQLite.js.map