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; +}