Skip to content

Commit 1c039a3

Browse files
authored
Merge pull request #82 from powersync-ja/encryption-support
Package maintenance, prepare encryption and dart2wasm support
2 parents b20b3b3 + dbc501b commit 1c039a3

34 files changed

+228
-97
lines changed

.github/workflows/test.yaml

+5-5
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,19 @@ jobs:
3535
include:
3636
- sqlite_version: "3440200"
3737
sqlite_url: "https://www.sqlite.org/2023/sqlite-autoconf-3440200.tar.gz"
38-
dart_sdk: 3.5.0
38+
dart_sdk: stable
3939
- sqlite_version: "3430200"
4040
sqlite_url: "https://www.sqlite.org/2023/sqlite-autoconf-3430200.tar.gz"
41-
dart_sdk: 3.5.0
41+
dart_sdk: stable
4242
- sqlite_version: "3420000"
4343
sqlite_url: "https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz"
44-
dart_sdk: 3.5.0
44+
dart_sdk: stable
4545
- sqlite_version: "3410100"
4646
sqlite_url: "https://www.sqlite.org/2023/sqlite-autoconf-3410100.tar.gz"
47-
dart_sdk: 3.5.0
47+
dart_sdk: stable
4848
- sqlite_version: "3380000"
4949
sqlite_url: "https://www.sqlite.org/2022/sqlite-autoconf-3380000.tar.gz"
50-
dart_sdk: 3.5.0
50+
dart_sdk: stable
5151
steps:
5252
- uses: actions/checkout@v3
5353
- uses: dart-lang/setup-dart@v1

melos.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ scripts:
4141

4242
test:
4343
description: Run tests in a specific package.
44-
run: dart test -p chrome,vm
44+
run: dart test -p chrome,vm --compiler dart2js,dart2wasm
4545
exec:
4646
concurrency: 1
4747
packageFilters:

packages/drift_sqlite_async/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.2.1
2+
3+
- Fix lints.
4+
15
## 0.2.0
26

37
- Automatically run Drift migrations
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
library drift_sqlite_async;
2-
31
export './src/connection.dart';
42
export './src/executor.dart';

packages/drift_sqlite_async/lib/src/executor.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'dart:async';
22

33
import 'package:drift/backends.dart';
4-
import 'package:drift/src/runtime/query_builder/query_builder.dart';
4+
import 'package:drift/drift.dart';
55
import 'package:sqlite_async/sqlite3_common.dart';
66
import 'package:sqlite_async/sqlite_async.dart';
77

@@ -19,6 +19,7 @@ class _SqliteAsyncDelegate extends _SqliteAsyncQueryDelegate
1919

2020
_SqliteAsyncDelegate(this.db) : super(db, db.writeLock);
2121

22+
@override
2223
bool isInTransaction = false; // unused
2324

2425
@override

packages/drift_sqlite_async/pubspec.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: drift_sqlite_async
2-
version: 0.2.0
2+
version: 0.2.1
33
homepage: https://github.com/powersync-ja/sqlite_async.dart
44
repository: https://github.com/powersync-ja/sqlite_async.dart
55
description: Use Drift with a sqlite_async database, allowing both to be used in the same application.
@@ -16,10 +16,12 @@ environment:
1616
dependencies:
1717
drift: ">=2.19.0 <3.0.0"
1818
sqlite_async: ^0.11.0
19+
1920
dev_dependencies:
2021
build_runner: ^2.4.8
2122
drift_dev: ">=2.19.0 <3.0.0"
2223
glob: ^2.1.2
24+
lints: ^5.0.0
2325
sqlite3: ^2.4.0
2426
test: ^1.25.2
2527
test_api: ^0.7.0

packages/drift_sqlite_async/test/basic_test.dart

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// TODO
22
@TestOn('!browser')
3+
library;
4+
35
import 'dart:async';
46

57
import 'package:drift/drift.dart';

packages/drift_sqlite_async/test/db_test.dart

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// TODO
22
@TestOn('!browser')
3+
library;
4+
35
import 'package:drift/drift.dart';
46
import 'package:sqlite_async/sqlite_async.dart';
57
import 'package:test/test.dart';

packages/drift_sqlite_async/test/generated/database.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class TodoDatabase extends _$TodoDatabase {
2121
}
2222

2323
class TodosMigrationDatabase extends TodoDatabase {
24-
TodosMigrationDatabase(SqliteConnection db) : super(db);
24+
TodosMigrationDatabase(super.db);
2525

2626
@override
2727
MigrationStrategy get migration {

packages/drift_sqlite_async/test/migration_test.dart

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
@TestOn('!browser')
2+
library;
3+
24
import 'package:sqlite_async/sqlite_async.dart';
35
import 'package:test/test.dart';
46

packages/sqlite_async/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.11.2
2+
3+
- Support latest version of `package:sqlite3_web`.
4+
- Support `dart2wasm`.
5+
16
## 0.11.1
27

38
- Remove remaining `dart:js_util` imports in favor of new interop APIs.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include: package:lints/recommended.yaml

packages/sqlite_async/lib/src/web/database.dart

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'dart:async';
22
import 'dart:js_interop';
3+
import 'dart:js_interop_unsafe';
34

45
import 'package:sqlite3/common.dart';
56
import 'package:sqlite3_web/sqlite3_web.dart';
7+
import 'package:sqlite3_web/protocol_utils.dart' as proto;
68
import 'package:sqlite_async/sqlite_async.dart';
79
import 'package:sqlite_async/src/utils/shared_utils.dart';
810
import 'package:sqlite_async/src/web/database/broadcast_updates.dart';
@@ -256,9 +258,15 @@ class _ExclusiveTransactionContext extends _ExclusiveContext {
256258
// JavaScript object. This is the converted into a Dart ResultSet.
257259
return await wrapSqliteException(() async {
258260
var res = await _database._database.customRequest(CustomDatabaseMessage(
259-
CustomDatabaseMessageKind.executeInTransaction, sql, parameters));
260-
var result =
261-
Map<String, dynamic>.from((res as JSObject).dartify() as Map);
261+
CustomDatabaseMessageKind.executeInTransaction, sql, parameters))
262+
as JSObject;
263+
264+
if (res.has('format') && (res['format'] as JSNumber).toDartInt == 2) {
265+
// Newer workers use a serialization format more efficient than dartify().
266+
return proto.deserializeResultSet(res['r'] as JSObject);
267+
}
268+
269+
var result = Map<String, dynamic>.from(res.dartify() as Map);
262270
final columnNames = [
263271
for (final entry in result['columnNames']) entry as String
264272
];
@@ -303,9 +311,14 @@ Future<T> wrapSqliteException<T>(Future<T> Function() callback) async {
303311
try {
304312
return await callback();
305313
} on RemoteException catch (ex) {
314+
if (ex.exception case final serializedCause?) {
315+
throw serializedCause;
316+
}
317+
318+
// Older versions of package:sqlite_web reported SqliteExceptions as strings
319+
// only.
306320
if (ex.toString().contains('SqliteException')) {
307321
RegExp regExp = RegExp(r'SqliteException\((\d+)\)');
308-
// The SQLite Web package wraps these in remote errors
309322
throw SqliteException(
310323
int.parse(regExp.firstMatch(ex.message)?.group(1) ?? '0'),
311324
ex.message);

packages/sqlite_async/lib/src/web/protocol.dart

+17-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
library;
44

55
import 'dart:js_interop';
6+
import 'package:sqlite3_web/protocol_utils.dart' as proto;
67

78
enum CustomDatabaseMessageKind {
89
requestSharedLock,
@@ -19,15 +20,24 @@ extension type CustomDatabaseMessage._raw(JSObject _) implements JSObject {
1920
required JSString rawKind,
2021
JSString rawSql,
2122
JSArray rawParameters,
23+
JSArrayBuffer typeInfo,
2224
});
2325

2426
factory CustomDatabaseMessage(CustomDatabaseMessageKind kind,
2527
[String? sql, List<Object?> parameters = const []]) {
26-
final rawSql = sql?.toJS ?? ''.toJS;
27-
final rawParameters =
28-
<JSAny?>[for (final parameter in parameters) parameter.jsify()].toJS;
28+
final rawSql = (sql ?? '').toJS;
29+
// Serializing parameters this way is backwards-compatible with dartify()
30+
// on the other end, but a bit more efficient while also suppporting sound
31+
// communcation between dart2js workers and dart2wasm clients.
32+
// Older workers ignore the typeInfo, but that's not a problem.
33+
final (rawParameters, typeInfo) = proto.serializeParameters(parameters);
34+
2935
return CustomDatabaseMessage._(
30-
rawKind: kind.name.toJS, rawSql: rawSql, rawParameters: rawParameters);
36+
rawKind: kind.name.toJS,
37+
rawSql: rawSql,
38+
rawParameters: rawParameters,
39+
typeInfo: typeInfo,
40+
);
3141
}
3242

3343
external JSString get rawKind;
@@ -36,6 +46,9 @@ extension type CustomDatabaseMessage._raw(JSObject _) implements JSObject {
3646

3747
external JSArray get rawParameters;
3848

49+
/// Not set in earlier versions of this package.
50+
external JSArrayBuffer? get typeInfo;
51+
3952
CustomDatabaseMessageKind get kind {
4053
return CustomDatabaseMessageKind.values.byName(rawKind.toDart);
4154
}

packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Map<String, FutureOr<WebSqlite>> webSQLiteImplementations = {};
1515
/// Web implementation of [AbstractDefaultSqliteOpenFactory]
1616
class DefaultSqliteOpenFactory
1717
extends AbstractDefaultSqliteOpenFactory<CommonDatabase>
18-
implements WebSqliteOpenFactory {
18+
with WebSqliteOpenFactory {
1919
late final Future<WebSqlite> _initialized = Future.sync(() {
2020
final cacheKey = sqliteOptions.webSqliteOptions.wasmUri +
2121
sqliteOptions.webSqliteOptions.workerUri;
@@ -45,9 +45,8 @@ class DefaultSqliteOpenFactory
4545
);
4646
}
4747

48-
@override
49-
5048
/// This is currently not supported on web
49+
@override
5150
CommonDatabase openDB(SqliteOpenOptions options) {
5251
throw UnimplementedError(
5352
'Direct access to CommonDatabase is not available on web.');
@@ -61,7 +60,7 @@ class DefaultSqliteOpenFactory
6160
/// Due to being asynchronous, the under laying CommonDatabase is not accessible
6261
Future<SqliteConnection> openConnection(SqliteOpenOptions options) async {
6362
final workers = await _initialized;
64-
final connection = await workers.connectToRecommended(path);
63+
final connection = await connectToWorker(workers, path);
6564

6665
// When the database is accessed through a shared worker, we implement
6766
// mutexes over custom messages sent through the shared worker. In other

packages/sqlite_async/lib/src/web/worker/throttled_common_database.dart

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class ThrottledCommonDatabase extends CommonDatabase {
107107
@override
108108
VoidPredicate? get commitFilter => _db.commitFilter;
109109

110+
@override
110111
set commitFilter(VoidPredicate? filter) => _db.commitFilter = filter;
111112

112113
@override

packages/sqlite_async/lib/src/web/worker/worker_utils.dart

+37-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import 'dart:js_interop';
2+
import 'dart:js_interop_unsafe';
23

4+
import 'package:meta/meta.dart';
35
import 'package:mutex/mutex.dart';
46
import 'package:sqlite3/wasm.dart';
57
import 'package:sqlite3_web/sqlite3_web.dart';
8+
import 'package:sqlite3_web/protocol_utils.dart' as proto;
9+
610
import 'throttled_common_database.dart';
711

812
import '../protocol.dart';
@@ -12,9 +16,9 @@ import '../protocol.dart';
1216
/// can be extended to perform custom requests.
1317
base class AsyncSqliteController extends DatabaseController {
1418
@override
15-
Future<WorkerDatabase> openDatabase(
16-
WasmSqlite3 sqlite3, String path, String vfs) async {
17-
final db = sqlite3.open(path, vfs: vfs);
19+
Future<WorkerDatabase> openDatabase(WasmSqlite3 sqlite3, String path,
20+
String vfs, JSAny? additionalData) async {
21+
final db = openUnderlying(sqlite3, path, vfs, additionalData);
1822

1923
// Register any custom functions here if needed
2024

@@ -23,6 +27,18 @@ base class AsyncSqliteController extends DatabaseController {
2327
return AsyncSqliteDatabase(database: throttled);
2428
}
2529

30+
/// Opens a database with the `sqlite3` package that will be wrapped in a
31+
/// [ThrottledCommonDatabase] for [openDatabase].
32+
@visibleForOverriding
33+
CommonDatabase openUnderlying(
34+
WasmSqlite3 sqlite3,
35+
String path,
36+
String vfs,
37+
JSAny? additionalData,
38+
) {
39+
return sqlite3.open(path, vfs: vfs);
40+
}
41+
2642
@override
2743
Future<JSAny?> handleCustomRequest(
2844
ClientConnection connection, JSAny? request) {
@@ -61,25 +77,32 @@ class AsyncSqliteDatabase extends WorkerDatabase {
6177
return database.autocommit.toJS;
6278
case CustomDatabaseMessageKind.executeInTransaction:
6379
final sql = message.rawSql.toDart;
64-
final parameters = [
65-
for (final raw in (message.rawParameters).toDart) raw.dartify()
66-
];
80+
final hasTypeInfo = message.typeInfo.isDefinedAndNotNull;
81+
final parameters = proto.deserializeParameters(
82+
message.rawParameters, message.typeInfo);
6783
if (database.autocommit) {
6884
throw SqliteException(0,
6985
"Transaction rolled back by earlier statement. Cannot execute: $sql");
7086
}
71-
var res = database.select(sql, parameters);
7287

73-
var dartMap = resultSetToMap(res);
74-
75-
var jsObject = dartMap.jsify();
88+
var res = database.select(sql, parameters);
89+
if (hasTypeInfo) {
90+
// If the client is sending a request that has parameters with type
91+
// information, it will also support a newer serialization format for
92+
// result sets.
93+
return JSObject()
94+
..['format'] = 2.toJS
95+
..['r'] = proto.serializeResultSet(res);
96+
} else {
97+
var dartMap = resultSetToMap(res);
98+
var jsObject = dartMap.jsify();
99+
return jsObject;
100+
}
76101

77-
return jsObject;
78102
case CustomDatabaseMessageKind.executeBatchInTransaction:
79103
final sql = message.rawSql.toDart;
80-
final parameters = [
81-
for (final raw in (message.rawParameters).toDart) raw.dartify()
82-
];
104+
final parameters = proto.deserializeParameters(
105+
message.rawParameters, message.typeInfo);
83106
if (database.autocommit) {
84107
throw SqliteException(0,
85108
"Transaction rolled back by earlier statement. Cannot execute: $sql");

0 commit comments

Comments
 (0)