Skip to content

Commit 490e5c3

Browse files
mugikhansimolus3
andauthored
Feat: Expose worker connection (#72)
* Add methods for exchanging database connections * Expose lock name as well * Also implement interface in sqlite database impl * Add changelog entry * Web: Return when database is closed --------- Co-authored-by: Simon Binder <[email protected]>
1 parent 3d46e54 commit 490e5c3

File tree

6 files changed

+113
-9
lines changed

6 files changed

+113
-9
lines changed

packages/sqlite_async/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.10.0
2+
3+
- Add the `exposeEndpoint()` method available on web databases. It returns a serializable
4+
description of the database endpoint that can be sent across workers.
5+
This allows sharing an opened database connection across workers.
6+
17
## 0.9.1
28

39
- Support version ^0.2.0 of package:sqlite3_web

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import 'package:sqlite3/common.dart';
55
import 'package:sqlite3_web/sqlite3_web.dart';
66
import 'package:sqlite_async/sqlite_async.dart';
77
import 'package:sqlite_async/src/utils/shared_utils.dart';
8+
import 'package:sqlite_async/web.dart';
89
import 'protocol.dart';
10+
import 'web_mutex.dart';
911

1012
class WebDatabase
1113
with SqliteQueries, SqliteDatabaseMixin
12-
implements SqliteDatabase {
14+
implements SqliteDatabase, WebSqliteConnection {
1315
final Database _database;
1416
final Mutex? _mutex;
1517

@@ -24,6 +26,9 @@ class WebDatabase
2426
closed = true;
2527
}
2628

29+
@override
30+
Future<void> get closedFuture => _database.closed;
31+
2732
@override
2833
Future<bool> getAutoCommit() async {
2934
final response = await _database.customRequest(
@@ -56,6 +61,20 @@ class WebDatabase
5661
/// Not relevant for web.
5762
Never get openFactory => throw UnimplementedError();
5863

64+
@override
65+
Future<WebDatabaseEndpoint> exposeEndpoint() async {
66+
final endpoint = await _database.additionalConnection();
67+
68+
return (
69+
connectPort: endpoint.$1,
70+
connectName: endpoint.$2,
71+
lockName: switch (_mutex) {
72+
MutexImpl(:final resolvedIdentifier) => resolvedIdentifier,
73+
_ => null,
74+
},
75+
);
76+
}
77+
5978
@override
6079
Future<T> readLock<T>(Future<T> Function(SqliteReadContext tx) callback,
6180
{Duration? lockTimeout, String? debugContext}) async {

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

+15-4
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,23 @@ import 'package:sqlite_async/src/sqlite_options.dart';
1010
import 'package:sqlite_async/src/update_notification.dart';
1111
import 'package:sqlite_async/src/web/web_mutex.dart';
1212
import 'package:sqlite_async/src/web/web_sqlite_open_factory.dart';
13+
import 'package:sqlite_async/web.dart';
14+
15+
import '../database.dart';
1316

1417
/// Web implementation of [SqliteDatabase]
1518
/// Uses a web worker for SQLite connection
1619
class SqliteDatabaseImpl
1720
with SqliteQueries, SqliteDatabaseMixin
18-
implements SqliteDatabase {
21+
implements SqliteDatabase, WebSqliteConnection {
1922
@override
2023
bool get closed {
2124
return _connection.closed;
2225
}
2326

27+
@override
28+
Future<void> get closedFuture => _connection.closedFuture;
29+
2430
final StreamController<UpdateNotification> updatesController =
2531
StreamController.broadcast();
2632

@@ -38,7 +44,7 @@ class SqliteDatabaseImpl
3844
AbstractDefaultSqliteOpenFactory openFactory;
3945

4046
late final Mutex mutex;
41-
late final SqliteConnection _connection;
47+
late final WebDatabase _connection;
4248

4349
/// Open a SqliteDatabase.
4450
///
@@ -78,8 +84,8 @@ class SqliteDatabaseImpl
7884

7985
Future<void> _init() async {
8086
_connection = await openFactory.openConnection(SqliteOpenOptions(
81-
primaryConnection: true, readOnly: false, mutex: mutex));
82-
_connection.updates!.forEach((update) {
87+
primaryConnection: true, readOnly: false, mutex: mutex)) as WebDatabase;
88+
_connection.updates.forEach((update) {
8389
updatesController.add(update);
8490
});
8591
}
@@ -139,4 +145,9 @@ class SqliteDatabaseImpl
139145
await isInitialized;
140146
return _connection.getAutoCommit();
141147
}
148+
149+
@override
150+
Future<WebDatabaseEndpoint> exposeEndpoint() async {
151+
return await _connection.exposeEndpoint();
152+
}
142153
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ external Navigator get _navigator;
1818
class MutexImpl implements Mutex {
1919
late final mutex.Mutex fallback;
2020
String? identifier;
21-
final String _resolvedIdentifier;
21+
final String resolvedIdentifier;
2222

2323
MutexImpl({this.identifier})
2424

@@ -29,7 +29,7 @@ class MutexImpl implements Mutex {
2929
/// - The uuid package could be added for better uniqueness if required.
3030
/// This would add another package dependency to `sqlite_async` which is potentially unnecessary at this point.
3131
/// An identifier should be supplied for better exclusion.
32-
: _resolvedIdentifier = identifier ??
32+
: resolvedIdentifier = identifier ??
3333
"${DateTime.now().microsecondsSinceEpoch}-${Random().nextDouble()}" {
3434
fallback = mutex.Mutex();
3535
}
@@ -125,7 +125,7 @@ class MutexImpl implements Mutex {
125125
final lockOptions = JSObject();
126126
lockOptions['signal'] = controller.signal;
127127
final promise = _navigator.locks
128-
.request(_resolvedIdentifier, lockOptions, jsCallback.toJS);
128+
.request(resolvedIdentifier, lockOptions, jsCallback.toJS);
129129
// A timeout abort will throw an exception which needs to be handled.
130130
// There should not be any other unhandled lock errors.
131131
js_util.promiseToFuture(promise).catchError((error) {});

packages/sqlite_async/lib/web.dart

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/// Exposes interfaces implemented by database implementations on the web.
2+
///
3+
/// These expose methods allowing database instances to be shared across web
4+
/// workers.
5+
library sqlite_async.web;
6+
7+
import 'package:sqlite3_web/sqlite3_web.dart';
8+
import 'package:web/web.dart';
9+
import 'sqlite_async.dart';
10+
import 'src/web/database.dart';
11+
12+
/// An endpoint that can be used, by any running JavaScript context in the same
13+
/// website, to connect to an existing [WebSqliteConnection].
14+
///
15+
/// These endpoints are created by calling [WebSqliteConnection.exposeEndpoint]
16+
/// and consist of a [MessagePort] and two [String]s internally identifying the
17+
/// connection. Both objects can be transferred over send ports towards another
18+
/// worker or context. That context can then use
19+
/// [WebSqliteConnection.connectToEndpoint] to connect to the port already
20+
/// opened.
21+
typedef WebDatabaseEndpoint = ({
22+
MessagePort connectPort,
23+
String connectName,
24+
String? lockName,
25+
});
26+
27+
/// A [SqliteConnection] interface implemented by opened connections when
28+
/// running on the web.
29+
///
30+
/// This adds the [exposeEndpoint], which uses `dart:js_interop` types not
31+
/// supported on native Dart platforms. The method can be used to access an
32+
/// opened database across different JavaScript contexts
33+
/// (e.g. document windows and workers).
34+
abstract class WebSqliteConnection implements SqliteConnection {
35+
/// Returns a future that completes when this connection is closed.
36+
///
37+
/// This usually only happens when calling [close], but on the web
38+
/// specifically, it can also happen when a remote context closes a database
39+
/// accessed via [connectToEndpoint].
40+
Future<void> get closedFuture;
41+
42+
/// Returns a [WebDatabaseEndpoint] - a structure that consists only of types
43+
/// that can be transferred across a [MessagePort] in JavaScript.
44+
///
45+
/// After transferring this endpoint to another JavaScript context (e.g. a
46+
/// worker), the worker can call [connectToEndpoint] to obtain a connection to
47+
/// the same sqlite database.
48+
Future<WebDatabaseEndpoint> exposeEndpoint();
49+
50+
/// Connect to an endpoint obtained through [exposeEndpoint].
51+
///
52+
/// The endpoint is transferrable in JavaScript, allowing multiple JavaScript
53+
/// contexts to exchange opened database connections.
54+
static Future<WebSqliteConnection> connectToEndpoint(
55+
WebDatabaseEndpoint endpoint) async {
56+
final rawSqlite = await WebSqlite.connectToPort(
57+
(endpoint.connectPort, endpoint.connectName));
58+
59+
final database = WebDatabase(
60+
rawSqlite,
61+
switch (endpoint.lockName) {
62+
var lock? => Mutex(identifier: lock),
63+
null => null,
64+
},
65+
);
66+
return database;
67+
}
68+
}

packages/sqlite_async/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: sqlite_async
22
description: High-performance asynchronous interface for SQLite on Dart and Flutter.
3-
version: 0.9.1
3+
version: 0.10.0
44
repository: https://github.com/powersync-ja/sqlite_async.dart
55
environment:
66
sdk: ">=3.4.0 <4.0.0"

0 commit comments

Comments
 (0)