Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add expo-sqlite, the ability to test it, and a promisifying wrapper #5184

Merged
merged 18 commits into from Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .flowconfig
Expand Up @@ -115,6 +115,9 @@ munge_underscores=true

module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
# This lets us write .js.flow files instead of libdefs.
# Add more libraries as needed to this pattern with `\|`: `foo\|bar\|…`.
module.name_mapper='^\(sqlite3\)$' -> '<PROJECT_ROOT>/types/\0'

suppress_type=$FlowIssue
suppress_type=$FlowFixMe
Expand Down
Expand Up @@ -12,6 +12,7 @@ public List<Package> getPackageList() {
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.screenorientation.ScreenOrientationPackage(),
new expo.modules.sqlite.SQLitePackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
}
Expand Down
33 changes: 33 additions & 0 deletions docs/howto/libdefs.md
Expand Up @@ -17,6 +17,39 @@ dependencies well-typed.

-----

## Normal type definitions (`.js.flow`): `sqlite3`
Copy link
Contributor

@chrisbobbe chrisbobbe Jan 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat! I guess from now on we should reach for this first when a library doesn't have its own Flow or TypeScript types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! I'm pretty happy with how this turned out.

I think for any of our existing libdefs, too, if we find ourselves spending any time writing changes to it then it'll probably make sense to first move it into this format (because it makes iteration so much more pleasant.) Should be a matter of (a) moving the file, (b) dropping the declare module 'foo' { and its }, and (c) adding to the flowconfig line. And (a') splitting the file up into one per module, if there are several modules in the libdef file.

That might even go for the libdef files we generate with flowgen. I think even some of the quirks of its output that we've had to fix up manually are because it's intended to be used to generate .js.flow files, not libdef files, so that could simplify our use of flowgen.


This library doesn't have its own Flow types, and doesn't have its own
TypeScript types so it's not suited for Flowgen. We wrote type
definitions from scratch.

For doing that, an option that's much more convenient than a libdef in
`flow-typed` is to write a `.js.flow` file. In particular iterating
on changes is much faster and easier, because Flow does its usual
incremental thing; whereas with a libdef it restarts from scratch on
each edit.

The major difference in structure between a libdef and a `.js.flow`
file is that the latter doesn't have `declare module` blocks.
Instead the whole file describes one module, identified by where it is
in the filesystem -- just like a normal JS file does. The contents of
the file are very much like the body of any given `declare module`
block in a libdef: `export type Foo = …` and `declare export class
Bar { … }` and so on.

In order to be able to write a `.js.flow` file for the library in our
tree (rather than under `node_modules/`) and have Flow find it, we
added the following line to `.flowconfig`:

module.name_mapper='^\(sqlite3\)$' -> '<PROJECT_ROOT>/types/\0'

Then the file goes at `types/sqlite3.js.flow`.

The actual contents of the `.js.flow` file were based on the library's
API documentation, and consulting its implementation for points where
the documentation wasn't clear.


## `remotedev-serialize`

Some assembly was required for a libdef for `remotedev-serialize`, but
Expand Down
199 changes: 199 additions & 0 deletions flow-typed/expo-sqlite_vx.x.x.js
@@ -0,0 +1,199 @@
// `flowgen --no-inexact --interface-records node_modules/expo-sqlite/build/SQLite.types.d.ts`
// from expo-sqlite 10.0.3
// with s/export type/declare export type/g, and as noted with "Zulip fix" below
declare module 'expo-sqlite/build/SQLite.types' {
/**
* Flowtype definitions for SQLite.types
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.14.1
*/

declare export type Window = {|
openDatabase?: (
name: string,
version: string,
displayName: string,
estimatedSize: number,
creationCallback?: DatabaseCallback
) => Database,
|};
declare export type DatabaseCallback = (database: Database) => void;
/**
* `Database` objects are returned by calls to `SQLite.openDatabase()`. Such an object represents a
* connection to a database on your device.
*/
declare export type Database = {|
version: string,

/**
* Execute a database transaction.
* @param callback A function representing the transaction to perform. Takes a Transaction
* (see below) as its only parameter, on which it can add SQL statements to execute.
* @param errorCallback Called if an error occurred processing this transaction. Takes a single
* parameter describing the error.
* @param successCallback Called when the transaction has completed executing on the database.
*/
transaction(
callback: SQLTransactionCallback,
errorCallback?: SQLTransactionErrorCallback,
successCallback?: () => void
): void,
readTransaction(
callback: SQLTransactionCallback,
errorCallback?: SQLTransactionErrorCallback,
successCallback?: () => void
): void,
|};
declare export type SQLTransactionCallback = (transaction: SQLTransaction) => void;
declare export type SQLTransactionErrorCallback = (error: SQLError) => void;
/**
* A `SQLTransaction` object is passed in as a parameter to the `callback` parameter for the
* `db.transaction()` method on a `Database` (see above). It allows enqueuing SQL statements to
* perform in a database transaction.
*/
declare export type SQLTransaction = {|
/**
* Enqueue a SQL statement to execute in the transaction. Authors are strongly recommended to make
* use of the `?` placeholder feature of the method to avoid against SQL injection attacks, and to
* never construct SQL statements on the fly.
* @param sqlStatement A string containing a database query to execute expressed as SQL. The string
* may contain `?` placeholders, with values to be substituted listed in the `arguments` parameter.
* @param args An array of values (numbers or strings) to substitute for `?` placeholders in the
* SQL statement.
* @param callback Called when the query is successfully completed during the transaction. Takes
* two parameters: the transaction itself, and a `ResultSet` object (see below) with the results
* of the query.
* @param errorCallback Called if an error occurred executing this particular query in the
* transaction. Takes two parameters: the transaction itself, and the error object.
*/
executeSql(
sqlStatement: string,
args?: $ReadOnlyArray<number | string>, // Zulip fix
callback?: SQLStatementCallback,
errorCallback?: SQLStatementErrorCallback
): void,
|};
declare export type SQLStatementCallback = (
transaction: SQLTransaction,
resultSet: SQLResultSet
) => void;
declare export type SQLStatementErrorCallback = (
transaction: SQLTransaction,
error: SQLError
) => boolean;
declare export type SQLResultSet = {|
/**
* The row ID of the row that the SQL statement inserted into the database, if a row was inserted.
*/
insertId?: number,

/**
* The number of rows that were changed by the SQL statement.
*/
rowsAffected: number,
rows: SQLResultSetRowList,
|};
declare export type SQLResultSetRowList = {|
/**
* The number of rows returned by the query.
*/
length: number,

/**
* Returns the row with the given `index`. If there is no such row, returns `null`.
* @param index Index of row to get.
*/
item(index: number): any,

/**
* The actual array of rows returned by the query. Can be used directly instead of
* getting rows through rows.item().
*/
_array: any[],
|};
declare export class SQLError {
static UNKNOWN_ERR: number;
static DATABASE_ERR: number;
static VERSION_ERR: number;
static TOO_LARGE_ERR: number;
static QUOTA_ERR: number;
static SYNTAX_ERR: number;
static CONSTRAINT_ERR: number;
static TIMEOUT_ERR: number;
code: number;
message: string;
}
declare export type WebSQLDatabase = {|
...$Exact<Database>,

exec(queries: Query[], readOnly: boolean, callback: SQLiteCallback): void,
|};
declare export type Query = {|
sql: string,
args: mixed[],
|};
declare export type ResultSetError = {|
error: Error,
|};
/**
* `ResultSet` objects are returned through second parameter of the `success` callback for the
* `tx.executeSql()` method on a `SQLTransaction` (see above).
*/
declare export type ResultSet = {|
/**
* The row ID of the row that the SQL statement inserted into the database, if a row was inserted.
*/
insertId?: number,

/**
* The number of rows that were changed by the SQL statement.
*/
rowsAffected: number,
rows: {|
[column: string]: any,
|}[],
|};
declare export type SQLiteCallback = (
error?: Error | null,
resultSet?: (ResultSetError | ResultSet)[]
) => void;
}

// `flowgen --no-inexact --interface-records node_modules/expo-sqlite/build/SQLite.d.ts`
// from expo-sqlite 10.0.3
// with imports fixed up
declare module 'expo-sqlite/build/SQLite' {
/**
* Flowtype definitions for SQLite
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.14.1
*/

import type { WebSQLDatabase } from "expo-sqlite/build/SQLite.types";

/**
* Open a database, creating it if it doesn't exist, and return a `Database` object. On disk,
* the database will be created under the app's [documents directory](../filesystem), i.e.
* `${FileSystem.documentDirectory}/SQLite/${name}`.
* > The `version`, `description` and `size` arguments are ignored, but are accepted by the function
* for compatibility with the WebSQL specification.
* @param name Name of the database file to open.
* @param version
* @param description
* @param size
* @param callback
* @return
*/
declare export function openDatabase(
name: string,
version?: string,
description?: string,
size?: number,
callback?: (db: WebSQLDatabase) => void
): WebSQLDatabase;
}

declare module 'expo-sqlite' {
declare export * from 'expo-sqlite/build/SQLite';
declare export * from 'expo-sqlite/build/SQLite.types';
}
7 changes: 7 additions & 0 deletions ios/Podfile.lock
Expand Up @@ -28,6 +28,9 @@ PODS:
- EXScreenOrientation (3.3.0):
- React-Core
- UMCore
- EXSQLite (9.2.1):
- ExpoModulesCore
- UMCore
- EXWebBrowser (9.2.0):
- UMCore
- FBLazyVector (0.64.3)
Expand Down Expand Up @@ -430,6 +433,7 @@ DEPENDENCIES:
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core/ios`)
- EXScreenOrientation (from `../node_modules/expo-screen-orientation/ios`)
- EXSQLite (from `../node_modules/expo-sqlite/ios`)
- EXWebBrowser (from `../node_modules/expo-web-browser/ios`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
Expand Down Expand Up @@ -534,6 +538,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-modules-core/ios"
EXScreenOrientation:
:path: "../node_modules/expo-screen-orientation/ios"
EXSQLite:
:path: "../node_modules/expo-sqlite/ios"
EXWebBrowser:
:path: "../node_modules/expo-web-browser/ios"
FBLazyVector:
Expand Down Expand Up @@ -642,6 +648,7 @@ SPEC CHECKSUMS:
EXImageLoader: d3531a3fe530b22925c19977cb53bb43e3821fe6
ExpoModulesCore: 2734852616127a6c1fc23012197890a6f3763dc7
EXScreenOrientation: 09fe6b6b87899ae0c9320255bda7b7513cdfc8ec
EXSQLite: cddb3b3f4c6999761c157b88dc8b55c7af2cd86b
EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5
FBLazyVector: c71c5917ec0ad2de41d5d06a5855f6d5eda06971
FBReactNativeSpec: ddd61666683147c613f5c89e2ce4e6c5e4f90d63
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Expand Up @@ -5,6 +5,7 @@
const transformModulesWhitelist = [
'expo-apple-authentication',
'expo-application',
'expo-sqlite',
'expo-web-browser',
'react-native',
'@react-native',
Expand Down
4 changes: 4 additions & 0 deletions jest/jestSetup.js
Expand Up @@ -67,6 +67,10 @@ jest.mock('react-native', () => {
return ReactNative;
});

jest.mock('immediate', () => require('./mock-immediate').default);

jest.mock('expo-sqlite', () => require('./mock-expo-sqlite'));

/**
* Boring mocks
*
Expand Down