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
- Add `DataClassName.extending` to control the superclass of generated row

View File

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

View File

@ -12,6 +12,7 @@ dependencies:
async: ^2.5.0
convert: ^3.0.0
collection: ^1.15.0
js: ^0.6.4
meta: ^1.3.0
stream_channel: ^2.1.0
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
```
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

View File

@ -1,4 +1,17 @@
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:
builders:
drift_dev:
@ -9,3 +22,8 @@ targets:
generate_values_in_copy_with: true
named_parameters: true
new_sql_code_generation: true
build_web_compilers|entrypoint:
generate_for:
# This one is compiled in the other target
exclude:
- "web/worker.dart"