1import './polyfillNextTick'; 2 3import zipObject from 'lodash/zipObject'; 4import { Platform } from 'react-native'; 5import { NativeModulesProxy } from '@unimodules/core'; 6import customOpenDatabase from '@expo/websql/custom'; 7import { Query, SQLiteCallback, ResultSet, ResultSetError, WebSQLDatabase } from './SQLite.types'; 8 9const { ExponentSQLite } = NativeModulesProxy; 10 11class SQLiteDatabase { 12 _name: string; 13 _closed: boolean = false; 14 15 constructor(name: string) { 16 this._name = name; 17 } 18 19 exec(queries: Query[], readOnly: boolean, callback: SQLiteCallback): void { 20 if (this._closed) { 21 throw new Error(`The SQLite database is closed`); 22 } 23 24 ExponentSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then( 25 nativeResultSets => { 26 callback(null, nativeResultSets.map(_deserializeResultSet)); 27 }, 28 error => { 29 // TODO: make the native API consistently reject with an error, not a string or other type 30 callback(error instanceof Error ? error : new Error(error)); 31 } 32 ); 33 } 34 35 close() { 36 this._closed = true; 37 ExponentSQLite.close(this._name); 38 } 39} 40 41function _serializeQuery(query: Query): [string, unknown[]] { 42 return [query.sql, Platform.OS === 'android' ? query.args.map(_escapeBlob) : query.args]; 43} 44 45function _deserializeResultSet(nativeResult): ResultSet | ResultSetError { 46 let [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult; 47 // TODO: send more structured error information from the native module so we can better construct 48 // a SQLException object 49 if (errorMessage !== null) { 50 return { error: new Error(errorMessage) } as ResultSetError; 51 } 52 53 return { 54 insertId, 55 rowsAffected, 56 rows: rows.map(row => zipObject(columns, row)), 57 }; 58} 59 60function _escapeBlob<T>(data: T): T { 61 if (typeof data === 'string') { 62 /* eslint-disable no-control-regex */ 63 return data 64 .replace(/\u0002/g, '\u0002\u0002') 65 .replace(/\u0001/g, '\u0001\u0002') 66 .replace(/\u0000/g, '\u0001\u0001') as any; 67 /* eslint-enable no-control-regex */ 68 } else { 69 return data; 70 } 71} 72 73const _openExpoSQLiteDatabase = customOpenDatabase(SQLiteDatabase); 74 75function addExecMethod(db: any): WebSQLDatabase { 76 db.exec = (queries: Query[], readOnly: boolean, callback: SQLiteCallback): void => { 77 db._db.exec(queries, readOnly, callback); 78 }; 79 return db; 80} 81 82export function openDatabase( 83 name: string, 84 version: string = '1.0', 85 description: string = name, 86 size: number = 1, 87 callback?: (db: WebSQLDatabase) => void 88): WebSQLDatabase { 89 if (name === undefined) { 90 throw new TypeError(`The database name must not be undefined`); 91 } 92 const db = _openExpoSQLiteDatabase(name, version, description, size, callback); 93 const dbWithExec = addExecMethod(db); 94 return dbWithExec; 95} 96