diff --git a/packages/sqlite_async/build.yaml b/packages/sqlite_async/build.yaml
new file mode 100644
index 0000000..0774cc7
--- /dev/null
+++ b/packages/sqlite_async/build.yaml
@@ -0,0 +1,7 @@
+targets:
+ $default:
+ builders:
+ build_web_compilers:entrypoint:
+ options:
+ # Workers can't be compiled with dartdevc, so use dart2js for the example
+ compiler: dart2js
diff --git a/packages/sqlite_async/example/web/index.html b/packages/sqlite_async/example/web/index.html
new file mode 100644
index 0000000..4bc69de
--- /dev/null
+++ b/packages/sqlite_async/example/web/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ sqlite_async web demo
+
+
+
+
+
+sqlite_async demo
+
+
+This page is used to test the sqlite_async package on the web.
+Use the console to open and interact with databases.
+
+
+
+const db = await open('test.db');
+const lock = await write_lock(db);
+release_lock(lock);
+
+
+
+
+
+
+
diff --git a/packages/sqlite_async/example/web/main.dart b/packages/sqlite_async/example/web/main.dart
new file mode 100644
index 0000000..95bd1f1
--- /dev/null
+++ b/packages/sqlite_async/example/web/main.dart
@@ -0,0 +1,41 @@
+import 'dart:async';
+import 'dart:js_interop';
+import 'dart:js_interop_unsafe';
+
+import 'package:sqlite_async/sqlite_async.dart';
+
+void main() {
+ globalContext['open'] = (String path) {
+ return Future(() async {
+ final db = SqliteDatabase(
+ path: path,
+ options: SqliteOptions(
+ webSqliteOptions: WebSqliteOptions(
+ wasmUri:
+ 'https://cdn.jsdelivr.net/npm/@powersync/dart-wasm-bundles@latest/dist/sqlite3.wasm',
+ workerUri: 'worker.dart.js',
+ ),
+ ),
+ );
+ await db.initialize();
+ return db.toJSBox;
+ }).toJS;
+ }.toJS;
+
+ globalContext['write_lock'] = (JSBoxedDartObject db) {
+ final hasLock = Completer();
+ final completer = Completer();
+
+ (db.toDart as SqliteDatabase).writeLock((_) async {
+ print('has write lock!');
+ hasLock.complete();
+ await completer.future;
+ });
+
+ return hasLock.future.then((_) => completer.toJSBox).toJS;
+ }.toJS;
+
+ globalContext['release_lock'] = (JSBoxedDartObject db) {
+ (db.toDart as Completer).complete();
+ }.toJS;
+}
diff --git a/packages/sqlite_async/example/web/worker.dart b/packages/sqlite_async/example/web/worker.dart
new file mode 100644
index 0000000..481455d
--- /dev/null
+++ b/packages/sqlite_async/example/web/worker.dart
@@ -0,0 +1,6 @@
+import 'package:sqlite_async/sqlite3_web.dart';
+import 'package:sqlite_async/sqlite3_web_worker.dart';
+
+void main() {
+ WebSqlite.workerEntrypoint(controller: AsyncSqliteController());
+}
diff --git a/packages/sqlite_async/lib/src/web/worker/worker_utils.dart b/packages/sqlite_async/lib/src/web/worker/worker_utils.dart
index af39747..3ecb257 100644
--- a/packages/sqlite_async/lib/src/web/worker/worker_utils.dart
+++ b/packages/sqlite_async/lib/src/web/worker/worker_utils.dart
@@ -56,9 +56,27 @@ class AsyncSqliteDatabase extends WorkerDatabase {
// these requests for shared workers, so we can assume each database is only
// opened once and we don't need web locks here.
final mutex = ReadWriteMutex();
+ final Map _state = {};
AsyncSqliteDatabase({required this.database});
+ _ConnectionState _findState(ClientConnection connection) {
+ return _state.putIfAbsent(connection, _ConnectionState.new);
+ }
+
+ void _markHoldsMutex(ClientConnection connection) {
+ final state = _findState(connection);
+ state.holdsMutex = true;
+ if (!state.hasOnCloseListener) {
+ state.hasOnCloseListener = true;
+ connection.closed.then((_) {
+ if (state.holdsMutex) {
+ mutex.release();
+ }
+ });
+ }
+ }
+
@override
Future handleCustomRequest(
ClientConnection connection, JSAny? request) async {
@@ -67,9 +85,12 @@ class AsyncSqliteDatabase extends WorkerDatabase {
switch (message.kind) {
case CustomDatabaseMessageKind.requestSharedLock:
await mutex.acquireRead();
+ _markHoldsMutex(connection);
case CustomDatabaseMessageKind.requestExclusiveLock:
await mutex.acquireWrite();
+ _markHoldsMutex(connection);
case CustomDatabaseMessageKind.releaseLock:
+ _findState(connection).holdsMutex = false;
mutex.release();
case CustomDatabaseMessageKind.lockObtained:
throw UnsupportedError('This is a response, not a request');
@@ -123,3 +144,8 @@ class AsyncSqliteDatabase extends WorkerDatabase {
return resultSetMap;
}
}
+
+final class _ConnectionState {
+ bool hasOnCloseListener = false;
+ bool holdsMutex = false;
+}