Use `package:js` to wrap sql.js library

This commit is contained in:
Simon Binder 2022-04-01 23:42:52 +02:00
parent fc0d30583a
commit 6e79a5b58c
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
5 changed files with 94 additions and 51 deletions

View File

@ -1,3 +1,7 @@
## 1.6.0-dev
- Internally use `package:js` to wrap sql.js.
## 1.5.0 ## 1.5.0
- Add `DataClassName.extending` to control the superclass of generated row - Add `DataClassName.extending` to control the superclass of generated row

View File

@ -1,7 +1,17 @@
@JS()
import 'dart:async'; import 'dart:async';
import 'dart:js'; import 'dart:js';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
@JS('initSqlJs')
external Object /*Promise<_SqlJs>*/ _initSqlJs();
@JS('undefined')
external Null get _undefined;
// We write our own mapping code to js instead of depending on package:js // We write our own mapping code to js instead of depending on package:js
// This way, projects using drift can run on flutter as long as they don't // This way, projects using drift can run on flutter as long as they don't
// import this file. // import this file.
@ -21,24 +31,53 @@ Future<SqlJsModule> initSqlJs() {
'The drift documentation contains instructions on how to setup drift ' 'The drift documentation contains instructions on how to setup drift '
'the web, which might help you fix this.')); 'the web, which might help you fix this.'));
} else { } else {
(context.callMethod('initSqlJs') as JsObject) completer
.callMethod('then', [allowInterop(_handleModuleResolved)]); .complete(promiseToFuture<_SqlJs>(_initSqlJs()).then(SqlJsModule._));
} }
return _moduleCompleter!.future; return _moduleCompleter!.future;
} }
// We're extracting this into its own method so that we don't have to call @JS()
// [allowInterop] on this method or a lambda. @anonymous
// todo figure out why dart2js generates invalid js when wrapping this in class _SqlJs {
// allowInterop // ignore: non_constant_identifier_names
void _handleModuleResolved(dynamic module) { external Object get Database;
_moduleCompleter!.complete(SqlJsModule._(module as JsObject)); }
@JS()
@anonymous
class _SqlJsDatabase {
external int getRowsModified();
external void run(String sql, List<Object?>? args);
external List<_QueryExecResult> exec(String sql, List<Object?>? params);
external _SqlJsStatement prepare(String sql);
external Uint8List export();
external void close();
}
@JS()
@anonymous
class _QueryExecResult {
external List<String> get columns;
external List<List<Object?>> get values;
}
@JS()
@anonymous
class _SqlJsStatement {
external void bind(List<Object?> values);
external bool step();
external List<Object?> get();
external List<String> getColumnNames();
external void free();
} }
/// `sql.js` module from the underlying library /// `sql.js` module from the underlying library
class SqlJsModule { class SqlJsModule {
final JsObject _obj; final _SqlJs _obj;
SqlJsModule._(this._obj); SqlJsModule._(this._obj);
/// Constructs a new [SqlJsDatabase], optionally from the [data] blob. /// Constructs a new [SqlJsDatabase], optionally from the [data] blob.
@ -53,20 +92,18 @@ class SqlJsModule {
return SqlJsDatabase._(dbObj); return SqlJsDatabase._(dbObj);
} }
JsObject _createInternally(Uint8List? data) { _SqlJsDatabase _createInternally(Uint8List? data) {
final constructor = _obj['Database'] as JsFunction;
if (data != null) { if (data != null) {
return JsObject(constructor, [data]); return callConstructor<_SqlJsDatabase>(_obj.Database, [data]);
} else { } else {
return JsObject(constructor); return callConstructor<_SqlJsDatabase>(_obj.Database, const []);
} }
} }
} }
/// Dart wrapper around a sql database provided by the sql.js library. /// Dart wrapper around a sql database provided by the sql.js library.
class SqlJsDatabase { class SqlJsDatabase {
final JsObject _obj; final _SqlJsDatabase _obj;
SqlJsDatabase._(this._obj); SqlJsDatabase._(this._obj);
/// Returns the `user_version` pragma from sqlite. /// Returns the `user_version` pragma from sqlite.
@ -81,13 +118,12 @@ class SqlJsDatabase {
/// Calls `prepare` on the underlying js api /// Calls `prepare` on the underlying js api
PreparedStatement prepare(String sql) { PreparedStatement prepare(String sql) {
final obj = _obj.callMethod('prepare', [sql]) as JsObject; return PreparedStatement._(_obj.prepare(sql));
return PreparedStatement._(obj);
} }
/// Calls `run(sql)` on the underlying js api /// Calls `run(sql)` on the underlying js api
void run(String sql) { void run(String sql) {
_obj.callMethod('run', [sql]); _obj.run(sql, _undefined);
} }
/// Calls `run(sql, args)` on the underlying js api /// Calls `run(sql, args)` on the underlying js api
@ -96,18 +132,15 @@ class SqlJsDatabase {
// Call run without providing arguments. sql.js will then use sqlite3_exec // Call run without providing arguments. sql.js will then use sqlite3_exec
// internally, which supports running multiple statements at once. This // internally, which supports running multiple statements at once. This
// matches the behavior from a `NativeDatabase`. // matches the behavior from a `NativeDatabase`.
_obj.callMethod('run', [sql]); _obj.run(sql, _undefined);
} else { } else {
final ar = JsArray.from(args); _obj.run(sql, args);
_obj.callMethod('run', [sql, ar]);
} }
} }
/// Returns the amount of rows affected by the most recent INSERT, UPDATE or /// Returns the amount of rows affected by the most recent INSERT, UPDATE or
/// DELETE statement. /// DELETE statement.
int lastModifiedRows() { int lastModifiedRows() => _obj.getRowsModified();
return _obj.callMethod('getRowsModified') as int;
}
/// The row id of the last inserted row. This counter is reset when calling /// The row id of the last inserted row. This counter is reset when calling
/// [export]. /// [export].
@ -117,52 +150,39 @@ class SqlJsDatabase {
} }
dynamic _selectSingleRowAndColumn(String sql) { dynamic _selectSingleRowAndColumn(String sql) {
final results = _obj.callMethod('exec', [sql]) as JsArray; final results = _obj.exec(sql, _undefined);
final row = results.first as JsObject; final result = results.first;
final data = (row['values'] as JsArray).first as JsArray; final row = result.values.first;
return data.first;
return row.first;
} }
/// Runs `export` on the underlying js api /// Runs `export` on the underlying js api
Uint8List export() { Uint8List export() => _obj.export();
return _obj.callMethod('export') as Uint8List;
}
/// Runs `close` on the underlying js api /// Runs `close` on the underlying js api
void close() { void close() => _obj.close();
_obj.callMethod('close');
}
} }
/// Dart api wrapping an underlying prepared statement object from the sql.js /// Dart api wrapping an underlying prepared statement object from the sql.js
/// library. /// library.
class PreparedStatement { class PreparedStatement {
final JsObject _obj; final _SqlJsStatement _obj;
PreparedStatement._(this._obj); PreparedStatement._(this._obj);
/// Executes this statement with the bound [args]. /// Executes this statement with the bound [args].
void executeWith(List<dynamic> args) { void executeWith(List<dynamic> args) => _obj.bind(args);
_obj.callMethod('bind', [JsArray.from(args)]);
}
/// Performs `step` on the underlying js api /// Performs `step` on the underlying js api
bool step() { bool step() => _obj.step();
return _obj.callMethod('step') as bool;
}
/// Reads the current from the underlying js api /// Reads the current from the underlying js api
List<dynamic> currentRow() { List<dynamic> currentRow() => _obj.get();
return _obj.callMethod('get') as JsArray;
}
/// The columns returned by this statement. This will only be available after /// The columns returned by this statement. This will only be available after
/// [step] has been called once. /// [step] has been called once.
List<String> columnNames() { List<String> columnNames() => _obj.getColumnNames();
return (_obj.callMethod('getColumnNames') as JsArray).cast<String>();
}
/// Calls `free` on the underlying js api /// Calls `free` on the underlying js api
void free() { void free() => _obj.free();
_obj.callMethod('free');
}
} }

View File

@ -12,6 +12,7 @@ dependencies:
async: ^2.5.0 async: ^2.5.0
convert: ^3.0.0 convert: ^3.0.0
collection: ^1.15.0 collection: ^1.15.0
js: ^0.6.4
meta: ^1.3.0 meta: ^1.3.0
stream_channel: ^2.1.0 stream_channel: ^2.1.0
sqlite3: ^1.5.1 sqlite3: ^1.5.1

View File

@ -3,7 +3,7 @@ This example demonstrates how a shared web worker can be used with drift.
To view this example, run To view this example, run
``` ```
dart run build_runner serve --release dart run build_runner serve
``` ```
Then, open `localhost:8080` in different tabs and note how changes propagate across tabs Then, open `localhost:8080` in different tabs and note how changes propagate across tabs

View File

@ -1,4 +1,17 @@
targets: targets:
# We use dartdevc by default, but the worker should always be compiled with dart2js
worker:
auto_apply_builders: false
dependencies: [":$default"]
builders:
build_web_compilers|entrypoint:
enabled: true
generate_for:
- web/worker.dart
options:
compiler: dart2js
$default: $default:
builders: builders:
drift_dev: drift_dev:
@ -9,3 +22,8 @@ targets:
generate_values_in_copy_with: true generate_values_in_copy_with: true
named_parameters: true named_parameters: true
new_sql_code_generation: true new_sql_code_generation: true
build_web_compilers|entrypoint:
generate_for:
# This one is compiled in the other target
exclude:
- "web/worker.dart"