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