diff --git a/apps/test-suite/TestModules.js b/apps/test-suite/TestModules.js index 05cd92c13f68c..8e9631d7e2735 100644 --- a/apps/test-suite/TestModules.js +++ b/apps/test-suite/TestModules.js @@ -69,7 +69,8 @@ export function getTestModules() { require('./tests/HTML'), require('./tests/FirebaseCore'), require('./tests/FirebaseAnalytics'), - require('./tests/FirebaseRecaptcha') + require('./tests/FirebaseRecaptcha'), + optionalRequire(() => require('./tests/SQLite')) ); if (Platform.OS === 'android') { @@ -117,7 +118,6 @@ export function getTestModules() { optionalRequire(() => require('./tests/Network')), optionalRequire(() => require('./tests/SecureStore')), optionalRequire(() => require('./tests/Segment')), - optionalRequire(() => require('./tests/SQLite')), optionalRequire(() => require('./tests/Speech')), optionalRequire(() => require('./tests/Recording')), optionalRequire(() => require('./tests/ScreenOrientation')), diff --git a/apps/test-suite/tests/SQLite.js b/apps/test-suite/tests/SQLite.js index 4538294efdf28..5eb8470e066e4 100644 --- a/apps/test-suite/tests/SQLite.js +++ b/apps/test-suite/tests/SQLite.js @@ -1,5 +1,6 @@ 'use strict'; +import { Platform } from '@unimodules/core'; import { Asset } from 'expo-asset'; import * as FS from 'expo-file-system'; import * as SQLite from 'expo-sqlite'; @@ -48,7 +49,7 @@ export function test(t) { [], (tx, results) => { t.expect(results.rows.length).toEqual(3); - t.expect(results.rows._array[0].j).toBeCloseTo(23.4); + t.expect(results.rows.item(0).j).toBeCloseTo(23.4); }, onError ); @@ -58,41 +59,44 @@ export function test(t) { ); }); - const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`); - t.expect(exists).toBeTruthy(); + if (Platform.OS !== 'web') { + const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`); + t.expect(exists).toBeTruthy(); + } }); - t.it( - 'should work with a downloaded .db file', - async () => { - await FS.downloadAsync( - Asset.fromModule(require('../assets/asset-db.db')).uri, - `${FS.documentDirectory}SQLite/downloaded.db` - ); - - const db = SQLite.openDatabase('downloaded.db'); - await new Promise((resolve, reject) => { - db.transaction( - tx => { - const nop = () => {}; - const onError = (tx, error) => reject(error); - tx.executeSql( - 'SELECT * FROM Users', - [], - (tx, results) => { - t.expect(results.rows.length).toEqual(3); - t.expect(results.rows._array[0].j).toBeCloseTo(23.4); - }, - onError - ); - }, - reject, - resolve + if (Platform.OS !== 'web') { + t.it( + 'should work with a downloaded .db file', + async () => { + await FS.downloadAsync( + Asset.fromModule(require('../assets/asset-db.db')).uri, + `${FS.documentDirectory}SQLite/downloaded.db` ); - }); - }, - 30000 - ); + + const db = SQLite.openDatabase('downloaded.db'); + await new Promise((resolve, reject) => { + db.transaction( + tx => { + const onError = (tx, error) => reject(error); + tx.executeSql( + 'SELECT * FROM Users', + [], + (tx, results) => { + t.expect(results.rows.length).toEqual(3); + t.expect(results.rows._array[0].j).toBeCloseTo(23.4); + }, + onError + ); + }, + reject, + resolve + ); + }); + }, + 30000 + ); + } t.it('should be able to recreate db from scratch by deleting file', async () => { { @@ -132,12 +136,12 @@ export function test(t) { }); } - { + if (Platform.OS !== 'web') { const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`); t.expect(exists).toBeTruthy(); } - { + if (Platform.OS !== 'web') { await FS.deleteAsync(`${FS.documentDirectory}SQLite/test.db`); const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`); t.expect(exists).toBeFalsy(); @@ -211,10 +215,10 @@ export function test(t) { 'SELECT * FROM Nulling', [], (tx, results) => { - t.expect(results.rows._array[0].x).toBeNull(); - t.expect(results.rows._array[0].y).toBeNull(); - t.expect(results.rows._array[1].x).toBeNull(); - t.expect(results.rows._array[1].y).toBeNull(); + t.expect(results.rows.item(0).x).toBeNull(); + t.expect(results.rows.item(0).y).toBeNull(); + t.expect(results.rows.item(1).x).toBeNull(); + t.expect(results.rows.item(1).y).toBeNull(); }, onError ); @@ -224,54 +228,61 @@ export function test(t) { ); }); - const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`); - t.expect(exists).toBeTruthy(); + if (Platform.OS !== 'web') { + const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`); + t.expect(exists).toBeTruthy(); + } }); - t.it('should support PRAGMA statements', async () => { - const db = SQLite.openDatabase('test.db'); - await new Promise((resolve, reject) => { - db.transaction( - tx => { - const nop = () => {}; - const onError = (tx, error) => reject(error); + // Do not try to test PRAGMA statements support in web + // as it is expected to not be working. + // See https://stackoverflow.com/a/10298712 + if (Platform.OS !== 'web') { + t.it('should support PRAGMA statements', async () => { + const db = SQLite.openDatabase('test.db'); + await new Promise((resolve, reject) => { + db.transaction( + tx => { + const nop = () => {}; + const onError = (tx, error) => reject(error); - tx.executeSql('DROP TABLE IF EXISTS SomeTable;', [], nop, onError); - tx.executeSql( - 'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));', - [], - nop, - onError - ); - // a result-returning pragma - tx.executeSql( - 'PRAGMA table_info(SomeTable);', - [], - (tx, results) => { - t.expect(results.rows.length).toEqual(2); - t.expect(results.rows._array[0].name).toEqual('id'); - t.expect(results.rows._array[1].name).toEqual('name'); - }, - onError - ); - // a no-result pragma - tx.executeSql('PRAGMA case_sensitive_like = true;', [], nop, onError); - // a setter/getter pragma - tx.executeSql('PRAGMA user_version = 123;', [], nop, onError); - tx.executeSql( - 'PRAGMA user_version;', - [], - (tx, results) => { - t.expect(results.rows.length).toEqual(1); - t.expect(results.rows._array[0].user_version).toEqual(123); - }, - onError - ); - }, - reject, - resolve - ); + tx.executeSql('DROP TABLE IF EXISTS SomeTable;', [], nop, onError); + tx.executeSql( + 'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));', + [], + nop, + onError + ); + // a result-returning pragma + tx.executeSql( + 'PRAGMA table_info(SomeTable);', + [], + (tx, results) => { + t.expect(results.rows.length).toEqual(2); + t.expect(results.rows.item(0).name).toEqual('id'); + t.expect(results.rows.item(1).name).toEqual('name'); + }, + onError + ); + // a no-result pragma + tx.executeSql('PRAGMA case_sensitive_like = true;', [], nop, onError); + // a setter/getter pragma + tx.executeSql('PRAGMA user_version = 123;', [], nop, onError); + tx.executeSql( + 'PRAGMA user_version;', + [], + (tx, results) => { + t.expect(results.rows.length).toEqual(1); + t.expect(results.rows.item(0).user_version).toEqual(123); + }, + onError + ); + }, + reject, + resolve + ); + }); }); - }); + } }); } diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index bea04155f08bc..7c7eba4f9fcfb 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Fixed support for using `expo-sqlite` on Web ([#8518](https://github.com/expo/expo/pull/8518) by [@sjchmiela](https://github.com/sjchmiela)) + ## 8.2.0 — 2020-05-27 *This version does not introduce any user-facing changes.* diff --git a/packages/expo-sqlite/build/SQLite.types.d.ts b/packages/expo-sqlite/build/SQLite.types.d.ts index bcb64f961c53e..327cf9061c1f3 100644 --- a/packages/expo-sqlite/build/SQLite.types.d.ts +++ b/packages/expo-sqlite/build/SQLite.types.d.ts @@ -1,3 +1,6 @@ +export interface Window { + openDatabase?: (name: string, version: string, displayName: string, estimatedSize: number, creationCallback?: DatabaseCallback) => Database; +} export interface DatabaseCallback { (database: Database): void; } diff --git a/packages/expo-sqlite/build/SQLite.types.js b/packages/expo-sqlite/build/SQLite.types.js index 0be5bb01b29f7..e45e4b14b53a3 100644 --- a/packages/expo-sqlite/build/SQLite.types.js +++ b/packages/expo-sqlite/build/SQLite.types.js @@ -1,2 +1,7 @@ -// Definitions by: TeamworkGuy2 +// Definitions copied from `@types/websql` as we want +// to expose a custom version of the API that: +// - uses primitive `string` instead of `String` +// - excludes some methods that are not exposed by our API. +// +// Original definitions by: TeamworkGuy2 //# sourceMappingURL=SQLite.types.js.map \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLite.types.js.map b/packages/expo-sqlite/build/SQLite.types.js.map index d5c238e96dcd4..43a60cf2c451e 100644 --- a/packages/expo-sqlite/build/SQLite.types.js.map +++ b/packages/expo-sqlite/build/SQLite.types.js.map @@ -1 +1 @@ -{"version":3,"file":"SQLite.types.js","sourceRoot":"","sources":["../src/SQLite.types.ts"],"names":[],"mappings":"AAAA,iEAAiE","sourcesContent":["// Definitions by: TeamworkGuy2 \n\nexport interface DatabaseCallback {\n (database: Database): void;\n}\n\nexport interface Database {\n version: string;\n\n transaction(\n callback: SQLTransactionCallback,\n errorCallback?: SQLTransactionErrorCallback,\n successCallback?: SQLVoidCallback\n ): void;\n\n readTransaction(\n callback: SQLTransactionCallback,\n errorCallback?: SQLTransactionErrorCallback,\n successCallback?: SQLVoidCallback\n ): void;\n}\n\nexport interface SQLVoidCallback {\n (): void;\n}\n\nexport interface SQLTransactionCallback {\n (transaction: SQLTransaction): void;\n}\n\nexport interface SQLTransactionErrorCallback {\n (error: SQLError): void;\n}\n\nexport interface SQLTransaction {\n executeSql(\n sqlStatement: string,\n args?: any[],\n callback?: SQLStatementCallback,\n errorCallback?: SQLStatementErrorCallback\n ): void;\n}\n\nexport interface SQLStatementCallback {\n (transaction: SQLTransaction, resultSet: SQLResultSet): void;\n}\n\nexport interface SQLStatementErrorCallback {\n (transaction: SQLTransaction, error: SQLError): boolean;\n}\n\nexport interface SQLResultSet {\n insertId: number;\n rowsAffected: number;\n rows: SQLResultSetRowList;\n}\n\nexport interface SQLResultSetRowList {\n length: number;\n item(index: number): any;\n}\n\nexport declare class SQLError {\n static UNKNOWN_ERR: number;\n static DATABASE_ERR: number;\n static VERSION_ERR: number;\n static TOO_LARGE_ERR: number;\n static QUOTA_ERR: number;\n static SYNTAX_ERR: number;\n static CONSTRAINT_ERR: number;\n static TIMEOUT_ERR: number;\n\n code: number;\n message: string;\n}\n\nexport interface WebSQLDatabase extends Database {\n exec(queries: Query[], readOnly: boolean, callback: SQLiteCallback): void;\n}\n\nexport type Query = { sql: string; args: unknown[] };\n\nexport interface ResultSetError {\n error: Error;\n}\nexport interface ResultSet {\n insertId?: number;\n rowsAffected: number;\n rows: { [column: string]: any }[];\n}\n\nexport type SQLiteCallback = (\n error?: Error | null,\n resultSet?: (ResultSetError | ResultSet)[]\n) => void;\n"]} \ No newline at end of file +{"version":3,"file":"SQLite.types.js","sourceRoot":"","sources":["../src/SQLite.types.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,8CAA8C;AAC9C,gDAAgD;AAChD,2DAA2D;AAC3D,EAAE;AACF,0EAA0E","sourcesContent":["// Definitions copied from `@types/websql` as we want\n// to expose a custom version of the API that:\n// - uses primitive `string` instead of `String`\n// - excludes some methods that are not exposed by our API.\n//\n// Original definitions by: TeamworkGuy2 \n\nexport interface Window {\n openDatabase?: (\n name: string,\n version: string,\n displayName: string,\n estimatedSize: number,\n creationCallback?: DatabaseCallback\n ) => Database;\n}\n\nexport interface DatabaseCallback {\n (database: Database): void;\n}\n\nexport interface Database {\n version: string;\n\n transaction(\n callback: SQLTransactionCallback,\n errorCallback?: SQLTransactionErrorCallback,\n successCallback?: SQLVoidCallback\n ): void;\n\n readTransaction(\n callback: SQLTransactionCallback,\n errorCallback?: SQLTransactionErrorCallback,\n successCallback?: SQLVoidCallback\n ): void;\n}\n\nexport interface SQLVoidCallback {\n (): void;\n}\n\nexport interface SQLTransactionCallback {\n (transaction: SQLTransaction): void;\n}\n\nexport interface SQLTransactionErrorCallback {\n (error: SQLError): void;\n}\n\nexport interface SQLTransaction {\n executeSql(\n sqlStatement: string,\n args?: any[],\n callback?: SQLStatementCallback,\n errorCallback?: SQLStatementErrorCallback\n ): void;\n}\n\nexport interface SQLStatementCallback {\n (transaction: SQLTransaction, resultSet: SQLResultSet): void;\n}\n\nexport interface SQLStatementErrorCallback {\n (transaction: SQLTransaction, error: SQLError): boolean;\n}\n\nexport interface SQLResultSet {\n insertId: number;\n rowsAffected: number;\n rows: SQLResultSetRowList;\n}\n\nexport interface SQLResultSetRowList {\n length: number;\n item(index: number): any;\n}\n\nexport declare class SQLError {\n static UNKNOWN_ERR: number;\n static DATABASE_ERR: number;\n static VERSION_ERR: number;\n static TOO_LARGE_ERR: number;\n static QUOTA_ERR: number;\n static SYNTAX_ERR: number;\n static CONSTRAINT_ERR: number;\n static TIMEOUT_ERR: number;\n\n code: number;\n message: string;\n}\n\nexport interface WebSQLDatabase extends Database {\n exec(queries: Query[], readOnly: boolean, callback: SQLiteCallback): void;\n}\n\nexport type Query = { sql: string; args: unknown[] };\n\nexport interface ResultSetError {\n error: Error;\n}\nexport interface ResultSet {\n insertId?: number;\n rowsAffected: number;\n rows: { [column: string]: any }[];\n}\n\nexport type SQLiteCallback = (\n error?: Error | null,\n resultSet?: (ResultSetError | ResultSet)[]\n) => void;\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLite.web.d.ts b/packages/expo-sqlite/build/SQLite.web.d.ts index 29ace601838ab..5ef5162b3c574 100644 --- a/packages/expo-sqlite/build/SQLite.web.d.ts +++ b/packages/expo-sqlite/build/SQLite.web.d.ts @@ -1,5 +1,2 @@ -export declare const openDatabase: any; -declare const _default: { - openDatabase: any; -}; -export default _default; +import { DatabaseCallback } from './SQLite.types'; +export declare function openDatabase(name: string, version?: string, description?: string, size?: number, callback?: DatabaseCallback): import("./SQLite.types").Database; diff --git a/packages/expo-sqlite/build/SQLite.web.js b/packages/expo-sqlite/build/SQLite.web.js index 1f3c09d4d3f3e..d1bd4ac53848b 100644 --- a/packages/expo-sqlite/build/SQLite.web.js +++ b/packages/expo-sqlite/build/SQLite.web.js @@ -1,3 +1,9 @@ -export const { openDatabase } = global; -export default { openDatabase }; +import { UnavailabilityError } from '@unimodules/core'; +export function openDatabase(name, version = '1.0', description = name, size = 1, callback) { + const typedWindow = window; + if ('openDatabase' in typedWindow && typedWindow.openDatabase) { + return typedWindow.openDatabase(name, version, description, size, callback); + } + throw new UnavailabilityError('window', 'openDatabase'); +} //# sourceMappingURL=SQLite.web.js.map \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLite.web.js.map b/packages/expo-sqlite/build/SQLite.web.js.map index 208f08f1df09c..7440a7cea00bb 100644 --- a/packages/expo-sqlite/build/SQLite.web.js.map +++ b/packages/expo-sqlite/build/SQLite.web.js.map @@ -1 +1 @@ -{"version":3,"file":"SQLite.web.js","sourceRoot":"","sources":["../src/SQLite.web.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;AACvC,eAAe,EAAE,YAAY,EAAE,CAAC","sourcesContent":["export const { openDatabase } = global;\nexport default { openDatabase };\n"]} \ No newline at end of file +{"version":3,"file":"SQLite.web.js","sourceRoot":"","sources":["../src/SQLite.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAIvD,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,UAAkB,KAAK,EACvB,cAAsB,IAAI,EAC1B,OAAe,CAAC,EAChB,QAA2B;IAE3B,MAAM,WAAW,GAAW,MAAgB,CAAC;IAC7C,IAAI,cAAc,IAAI,WAAW,IAAI,WAAW,CAAC,YAAY,EAAE;QAC7D,OAAO,WAAW,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;KAC7E;IACD,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["import { UnavailabilityError } from '@unimodules/core';\n\nimport { Window, DatabaseCallback } from './SQLite.types';\n\nexport function openDatabase(\n name: string,\n version: string = '1.0',\n description: string = name,\n size: number = 1,\n callback?: DatabaseCallback\n) {\n const typedWindow: Window = window as Window;\n if ('openDatabase' in typedWindow && typedWindow.openDatabase) {\n return typedWindow.openDatabase(name, version, description, size, callback);\n }\n throw new UnavailabilityError('window', 'openDatabase');\n}\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/package.json b/packages/expo-sqlite/package.json index 881862d8b5410..98c418f696703 100644 --- a/packages/expo-sqlite/package.json +++ b/packages/expo-sqlite/package.json @@ -44,7 +44,6 @@ }, "dependencies": { "@expo/websql": "^1.0.1", - "@types/websql": "^0.0.27", "lodash": "^4.17.15" }, "devDependencies": { diff --git a/packages/expo-sqlite/src/SQLite.types.ts b/packages/expo-sqlite/src/SQLite.types.ts index 3d82ceba84a1d..b35283a83ca14 100644 --- a/packages/expo-sqlite/src/SQLite.types.ts +++ b/packages/expo-sqlite/src/SQLite.types.ts @@ -1,4 +1,19 @@ -// Definitions by: TeamworkGuy2 +// Definitions copied from `@types/websql` as we want +// to expose a custom version of the API that: +// - uses primitive `string` instead of `String` +// - excludes some methods that are not exposed by our API. +// +// Original definitions by: TeamworkGuy2 + +export interface Window { + openDatabase?: ( + name: string, + version: string, + displayName: string, + estimatedSize: number, + creationCallback?: DatabaseCallback + ) => Database; +} export interface DatabaseCallback { (database: Database): void; diff --git a/packages/expo-sqlite/src/SQLite.web.ts b/packages/expo-sqlite/src/SQLite.web.ts index 4d70f972ecb3b..ea081ed9021d4 100644 --- a/packages/expo-sqlite/src/SQLite.web.ts +++ b/packages/expo-sqlite/src/SQLite.web.ts @@ -1,2 +1,17 @@ -export const { openDatabase } = global; -export default { openDatabase }; +import { UnavailabilityError } from '@unimodules/core'; + +import { Window, DatabaseCallback } from './SQLite.types'; + +export function openDatabase( + name: string, + version: string = '1.0', + description: string = name, + size: number = 1, + callback?: DatabaseCallback +) { + const typedWindow: Window = window as Window; + if ('openDatabase' in typedWindow && typedWindow.openDatabase) { + return typedWindow.openDatabase(name, version, description, size, callback); + } + throw new UnavailabilityError('window', 'openDatabase'); +} diff --git a/packages/expo-sqlite/src/ts-declarations/global.d.ts b/packages/expo-sqlite/src/ts-declarations/global.d.ts deleted file mode 100644 index afa75c4cc3be0..0000000000000 --- a/packages/expo-sqlite/src/ts-declarations/global.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare const global: any; diff --git a/packages/expo-sqlite/src/ts-declarations/process.d.ts b/packages/expo-sqlite/src/ts-declarations/process.d.ts index 74f4418232e07..327ee25e5d21f 100644 --- a/packages/expo-sqlite/src/ts-declarations/process.d.ts +++ b/packages/expo-sqlite/src/ts-declarations/process.d.ts @@ -1,6 +1 @@ -declare const process: { - env: { - NODE_ENV: string; - }; - [key: string]: any; -}; +declare const process: Record; diff --git a/yarn.lock b/yarn.lock index 15fc89b050d6b..5a3043bcbbb16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2949,11 +2949,6 @@ "@types/webpack-sources" "*" source-map "^0.6.0" -"@types/websql@^0.0.27": - version "0.0.27" - resolved "https://registry.yarnpkg.com/@types/websql/-/websql-0.0.27.tgz#621a666a7f02018e7cbb4abab956a25736c27d71" - integrity sha1-Yhpman8CAY58u0q6uVaiVzbCfXE= - "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"