From 7ff4bfee8db1f0e111134738a73cfe4c4db10c4e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 3 Feb 2025 10:40:54 +0100 Subject: [PATCH] Support build_web_compilers --- .github/workflows/test.yaml | 1 + melos.yaml | 8 + packages/sqlite_async/CHANGELOG.md | 4 + .../lib/src/common/port_channel.dart | 354 +----------------- .../lib/src/common/port_channel_native.dart | 352 +++++++++++++++++ .../lib/src/common/port_channel_stub.dart | 149 ++++++++ packages/sqlite_async/pubspec.yaml | 5 +- 7 files changed, 520 insertions(+), 353 deletions(-) create mode 100644 packages/sqlite_async/lib/src/common/port_channel_native.dart create mode 100644 packages/sqlite_async/lib/src/common/port_channel_stub.dart diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c1bcd0a..cb1814b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -68,3 +68,4 @@ jobs: run: | export LD_LIBRARY_PATH=$(pwd)/sqlite-autoconf-${{ matrix.sqlite_version }}/.libs melos test + melos test_build diff --git a/melos.yaml b/melos.yaml index bd27884..4555e43 100644 --- a/melos.yaml +++ b/melos.yaml @@ -51,3 +51,11 @@ scripts: # as they could change the behaviour of how tests filter packages. env: MELOS_TEST: true + + test_build: + description: Runs tests with build_test + run: dart run build_runner test -- -p chrome + exec: + concurrency: 1 + packageFilters: + dependsOn: build_test diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index ea77953..f3395b1 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.3 + +- Support being compiled with `package:build_web_compilers`. + ## 0.11.2 - Support latest version of `package:sqlite3_web`. diff --git a/packages/sqlite_async/lib/src/common/port_channel.dart b/packages/sqlite_async/lib/src/common/port_channel.dart index 8b05feb..fc5e69c 100644 --- a/packages/sqlite_async/lib/src/common/port_channel.dart +++ b/packages/sqlite_async/lib/src/common/port_channel.dart @@ -1,352 +1,2 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:isolate'; - -abstract class PortClient { - Future post(Object message); - void fire(Object message); - - factory PortClient.parent() { - return ParentPortClient(); - } - - factory PortClient.child(SendPort upstream) { - return ChildPortClient(upstream); - } -} - -class ParentPortClient implements PortClient { - late Future sendPortFuture; - SendPort? sendPort; - final ReceivePort _receivePort = ReceivePort(); - final ReceivePort _errorPort = ReceivePort(); - bool closed = false; - Object? _closeError; - String? _isolateDebugName; - int _nextId = 1; - - Map> handlers = HashMap(); - - ParentPortClient() { - final initCompleter = Completer.sync(); - sendPortFuture = initCompleter.future; - sendPortFuture.then((value) { - sendPort = value; - }); - _receivePort.listen((message) { - if (message is _InitMessage) { - assert(!initCompleter.isCompleted); - initCompleter.complete(message.port); - } else if (message is _PortChannelResult) { - final handler = handlers.remove(message.requestId); - assert(handler != null); - if (message.success) { - handler!.complete(message.result); - } else { - handler!.completeError(message.error, message.stackTrace); - } - } else if (message == _closeMessage) { - close(); - } - }, onError: (e) { - if (!initCompleter.isCompleted) { - initCompleter.completeError(e); - } - - close(); - }, onDone: () { - if (!initCompleter.isCompleted) { - initCompleter.completeError(ClosedException()); - } - close(); - }); - _errorPort.listen((message) { - final [error, stackTraceString] = message; - final stackTrace = stackTraceString == null - ? null - : StackTrace.fromString(stackTraceString); - if (!initCompleter.isCompleted) { - initCompleter.completeError(error, stackTrace); - } - _close(IsolateError(cause: error, isolateDebugName: _isolateDebugName), - stackTrace); - }); - } - - Future get ready async { - await sendPortFuture; - } - - void _cancelAll(Object error, [StackTrace? stackTrace]) { - var handlers = this.handlers; - this.handlers = {}; - for (var message in handlers.values) { - message.completeError(error, stackTrace); - } - } - - @override - Future post(Object message) async { - if (closed) { - throw _closeError ?? const ClosedException(); - } - var completer = Completer.sync(); - var id = _nextId++; - handlers[id] = completer; - final port = sendPort ?? await sendPortFuture; - port.send(_RequestMessage(id, message, null)); - return await completer.future; - } - - @override - void fire(Object message) async { - if (closed) { - throw _closeError ?? ClosedException(); - } - final port = sendPort ?? await sendPortFuture; - port.send(_FireMessage(message)); - } - - RequestPortServer server() { - return RequestPortServer(_receivePort.sendPort); - } - - void _close([Object? error, StackTrace? stackTrace]) { - if (!closed) { - closed = true; - - _receivePort.close(); - _errorPort.close(); - if (error == null) { - _cancelAll(const ClosedException()); - } else { - _closeError = error; - _cancelAll(error, stackTrace); - } - } - } - - void close() { - _close(); - } - - tieToIsolate(Isolate isolate) { - _isolateDebugName = isolate.debugName; - isolate.addErrorListener(_errorPort.sendPort); - isolate.addOnExitListener(_receivePort.sendPort, response: _closeMessage); - } -} - -class SerializedPortClient { - final SendPort sendPort; - - SerializedPortClient(this.sendPort); - - ChildPortClient open() { - return ChildPortClient(sendPort); - } -} - -class ChildPortClient implements PortClient { - final SendPort sendPort; - final ReceivePort receivePort = ReceivePort(); - int _nextId = 1; - bool closed = false; - - final Map> handlers = HashMap(); - - ChildPortClient(this.sendPort) { - receivePort.listen((message) { - if (message is _PortChannelResult) { - final handler = handlers.remove(message.requestId); - assert(handler != null); - if (message.success) { - handler!.complete(message.result); - } else { - handler!.completeError(message.error, message.stackTrace); - } - } - }); - } - - @override - Future post(Object message) async { - if (closed) { - throw const ClosedException(); - } - var completer = Completer.sync(); - var id = _nextId++; - handlers[id] = completer; - sendPort.send(_RequestMessage(id, message, receivePort.sendPort)); - return await completer.future; - } - - @override - void fire(Object message) { - if (closed) { - throw ClosedException(); - } - sendPort.send(_FireMessage(message)); - } - - void _cancelAll(Object error) { - var handlers = HashMap>.from(this.handlers); - this.handlers.clear(); - for (var message in handlers.values) { - message.completeError(error); - } - } - - void close() { - closed = true; - _cancelAll(const ClosedException()); - receivePort.close(); - } -} - -class RequestPortServer { - final SendPort port; - - RequestPortServer(this.port); - - open(Future Function(Object? message) handle) { - return PortServer.forSendPort(port, handle); - } -} - -class PortServer { - final ReceivePort _receivePort = ReceivePort(); - final Future Function(Object? message) handle; - final SendPort? replyPort; - - PortServer(this.handle) : replyPort = null { - _init(); - } - - PortServer.forSendPort(SendPort port, this.handle) : replyPort = port { - port.send(_InitMessage(_receivePort.sendPort)); - _init(); - } - - SendPort get sendPort { - return _receivePort.sendPort; - } - - SerializedPortClient client() { - return SerializedPortClient(sendPort); - } - - void close() { - _receivePort.close(); - } - - void _init() { - _receivePort.listen((request) async { - if (request is _FireMessage) { - handle(request.message); - } else if (request is _RequestMessage) { - if (request.id == 0) { - // Fire and forget - handle(request.message); - } else { - final replyPort = request.reply ?? this.replyPort; - try { - var result = await handle(request.message); - replyPort!.send(_PortChannelResult.success(request.id, result)); - } catch (e, stacktrace) { - replyPort! - .send(_PortChannelResult.error(request.id, e, stacktrace)); - } - } - } - }); - } -} - -const _closeMessage = '_Close'; - -class _InitMessage { - final SendPort port; - - _InitMessage(this.port); -} - -class _FireMessage { - final Object message; - - const _FireMessage(this.message); -} - -class _RequestMessage { - final int id; - final Object message; - final SendPort? reply; - - _RequestMessage(this.id, this.message, this.reply); -} - -class ClosedException implements Exception { - const ClosedException(); - - @override - String toString() { - return 'ClosedException'; - } -} - -class IsolateError extends Error { - final Object cause; - final String? isolateDebugName; - - IsolateError({required this.cause, this.isolateDebugName}); - - @override - String toString() { - if (isolateDebugName != null) { - return 'IsolateError in $isolateDebugName: $cause'; - } else { - return 'IsolateError: $cause'; - } - } -} - -class _PortChannelResult { - final int requestId; - final bool success; - final T? _result; - final Object? _error; - final StackTrace? stackTrace; - - const _PortChannelResult.success(this.requestId, T result) - : success = true, - _error = null, - stackTrace = null, - _result = result; - const _PortChannelResult.error(this.requestId, Object error, - [this.stackTrace]) - : success = false, - _result = null, - _error = error; - - T get value { - if (success) { - return _result as T; - } else { - if (_error != null && stackTrace != null) { - Error.throwWithStackTrace(_error, stackTrace!); - } else { - throw _error!; - } - } - } - - T get result { - assert(success); - return _result as T; - } - - Object get error { - assert(!success); - return _error!; - } -} +export 'port_channel_native.dart' + if (dart.library.js_interop) 'port_channel_stub.dart'; diff --git a/packages/sqlite_async/lib/src/common/port_channel_native.dart b/packages/sqlite_async/lib/src/common/port_channel_native.dart new file mode 100644 index 0000000..8b05feb --- /dev/null +++ b/packages/sqlite_async/lib/src/common/port_channel_native.dart @@ -0,0 +1,352 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:isolate'; + +abstract class PortClient { + Future post(Object message); + void fire(Object message); + + factory PortClient.parent() { + return ParentPortClient(); + } + + factory PortClient.child(SendPort upstream) { + return ChildPortClient(upstream); + } +} + +class ParentPortClient implements PortClient { + late Future sendPortFuture; + SendPort? sendPort; + final ReceivePort _receivePort = ReceivePort(); + final ReceivePort _errorPort = ReceivePort(); + bool closed = false; + Object? _closeError; + String? _isolateDebugName; + int _nextId = 1; + + Map> handlers = HashMap(); + + ParentPortClient() { + final initCompleter = Completer.sync(); + sendPortFuture = initCompleter.future; + sendPortFuture.then((value) { + sendPort = value; + }); + _receivePort.listen((message) { + if (message is _InitMessage) { + assert(!initCompleter.isCompleted); + initCompleter.complete(message.port); + } else if (message is _PortChannelResult) { + final handler = handlers.remove(message.requestId); + assert(handler != null); + if (message.success) { + handler!.complete(message.result); + } else { + handler!.completeError(message.error, message.stackTrace); + } + } else if (message == _closeMessage) { + close(); + } + }, onError: (e) { + if (!initCompleter.isCompleted) { + initCompleter.completeError(e); + } + + close(); + }, onDone: () { + if (!initCompleter.isCompleted) { + initCompleter.completeError(ClosedException()); + } + close(); + }); + _errorPort.listen((message) { + final [error, stackTraceString] = message; + final stackTrace = stackTraceString == null + ? null + : StackTrace.fromString(stackTraceString); + if (!initCompleter.isCompleted) { + initCompleter.completeError(error, stackTrace); + } + _close(IsolateError(cause: error, isolateDebugName: _isolateDebugName), + stackTrace); + }); + } + + Future get ready async { + await sendPortFuture; + } + + void _cancelAll(Object error, [StackTrace? stackTrace]) { + var handlers = this.handlers; + this.handlers = {}; + for (var message in handlers.values) { + message.completeError(error, stackTrace); + } + } + + @override + Future post(Object message) async { + if (closed) { + throw _closeError ?? const ClosedException(); + } + var completer = Completer.sync(); + var id = _nextId++; + handlers[id] = completer; + final port = sendPort ?? await sendPortFuture; + port.send(_RequestMessage(id, message, null)); + return await completer.future; + } + + @override + void fire(Object message) async { + if (closed) { + throw _closeError ?? ClosedException(); + } + final port = sendPort ?? await sendPortFuture; + port.send(_FireMessage(message)); + } + + RequestPortServer server() { + return RequestPortServer(_receivePort.sendPort); + } + + void _close([Object? error, StackTrace? stackTrace]) { + if (!closed) { + closed = true; + + _receivePort.close(); + _errorPort.close(); + if (error == null) { + _cancelAll(const ClosedException()); + } else { + _closeError = error; + _cancelAll(error, stackTrace); + } + } + } + + void close() { + _close(); + } + + tieToIsolate(Isolate isolate) { + _isolateDebugName = isolate.debugName; + isolate.addErrorListener(_errorPort.sendPort); + isolate.addOnExitListener(_receivePort.sendPort, response: _closeMessage); + } +} + +class SerializedPortClient { + final SendPort sendPort; + + SerializedPortClient(this.sendPort); + + ChildPortClient open() { + return ChildPortClient(sendPort); + } +} + +class ChildPortClient implements PortClient { + final SendPort sendPort; + final ReceivePort receivePort = ReceivePort(); + int _nextId = 1; + bool closed = false; + + final Map> handlers = HashMap(); + + ChildPortClient(this.sendPort) { + receivePort.listen((message) { + if (message is _PortChannelResult) { + final handler = handlers.remove(message.requestId); + assert(handler != null); + if (message.success) { + handler!.complete(message.result); + } else { + handler!.completeError(message.error, message.stackTrace); + } + } + }); + } + + @override + Future post(Object message) async { + if (closed) { + throw const ClosedException(); + } + var completer = Completer.sync(); + var id = _nextId++; + handlers[id] = completer; + sendPort.send(_RequestMessage(id, message, receivePort.sendPort)); + return await completer.future; + } + + @override + void fire(Object message) { + if (closed) { + throw ClosedException(); + } + sendPort.send(_FireMessage(message)); + } + + void _cancelAll(Object error) { + var handlers = HashMap>.from(this.handlers); + this.handlers.clear(); + for (var message in handlers.values) { + message.completeError(error); + } + } + + void close() { + closed = true; + _cancelAll(const ClosedException()); + receivePort.close(); + } +} + +class RequestPortServer { + final SendPort port; + + RequestPortServer(this.port); + + open(Future Function(Object? message) handle) { + return PortServer.forSendPort(port, handle); + } +} + +class PortServer { + final ReceivePort _receivePort = ReceivePort(); + final Future Function(Object? message) handle; + final SendPort? replyPort; + + PortServer(this.handle) : replyPort = null { + _init(); + } + + PortServer.forSendPort(SendPort port, this.handle) : replyPort = port { + port.send(_InitMessage(_receivePort.sendPort)); + _init(); + } + + SendPort get sendPort { + return _receivePort.sendPort; + } + + SerializedPortClient client() { + return SerializedPortClient(sendPort); + } + + void close() { + _receivePort.close(); + } + + void _init() { + _receivePort.listen((request) async { + if (request is _FireMessage) { + handle(request.message); + } else if (request is _RequestMessage) { + if (request.id == 0) { + // Fire and forget + handle(request.message); + } else { + final replyPort = request.reply ?? this.replyPort; + try { + var result = await handle(request.message); + replyPort!.send(_PortChannelResult.success(request.id, result)); + } catch (e, stacktrace) { + replyPort! + .send(_PortChannelResult.error(request.id, e, stacktrace)); + } + } + } + }); + } +} + +const _closeMessage = '_Close'; + +class _InitMessage { + final SendPort port; + + _InitMessage(this.port); +} + +class _FireMessage { + final Object message; + + const _FireMessage(this.message); +} + +class _RequestMessage { + final int id; + final Object message; + final SendPort? reply; + + _RequestMessage(this.id, this.message, this.reply); +} + +class ClosedException implements Exception { + const ClosedException(); + + @override + String toString() { + return 'ClosedException'; + } +} + +class IsolateError extends Error { + final Object cause; + final String? isolateDebugName; + + IsolateError({required this.cause, this.isolateDebugName}); + + @override + String toString() { + if (isolateDebugName != null) { + return 'IsolateError in $isolateDebugName: $cause'; + } else { + return 'IsolateError: $cause'; + } + } +} + +class _PortChannelResult { + final int requestId; + final bool success; + final T? _result; + final Object? _error; + final StackTrace? stackTrace; + + const _PortChannelResult.success(this.requestId, T result) + : success = true, + _error = null, + stackTrace = null, + _result = result; + const _PortChannelResult.error(this.requestId, Object error, + [this.stackTrace]) + : success = false, + _result = null, + _error = error; + + T get value { + if (success) { + return _result as T; + } else { + if (_error != null && stackTrace != null) { + Error.throwWithStackTrace(_error, stackTrace!); + } else { + throw _error!; + } + } + } + + T get result { + assert(success); + return _result as T; + } + + Object get error { + assert(!success); + return _error!; + } +} diff --git a/packages/sqlite_async/lib/src/common/port_channel_stub.dart b/packages/sqlite_async/lib/src/common/port_channel_stub.dart new file mode 100644 index 0000000..6c6e5cc --- /dev/null +++ b/packages/sqlite_async/lib/src/common/port_channel_stub.dart @@ -0,0 +1,149 @@ +import 'dart:async'; +import 'dart:collection'; + +typedef SendPort = Never; +typedef ReceivePort = Never; +typedef Isolate = Never; + +Never _stub() { + throw UnsupportedError('Isolates are not supported on this platform'); +} + +abstract class PortClient { + Future post(Object message); + void fire(Object message); + + factory PortClient.parent() { + return ParentPortClient(); + } + + factory PortClient.child(SendPort upstream) { + return ChildPortClient(upstream); + } +} + +class ParentPortClient implements PortClient { + late Future sendPortFuture; + SendPort? sendPort; + bool closed = false; + + Map> handlers = HashMap(); + + ParentPortClient(); + + Future get ready async { + await sendPortFuture; + } + + @override + Future post(Object message) async { + _stub(); + } + + @override + void fire(Object message) async { + _stub(); + } + + RequestPortServer server() { + _stub(); + } + + void close() { + _stub(); + } + + tieToIsolate(Isolate isolate) { + _stub(); + } +} + +class SerializedPortClient { + final SendPort sendPort; + + SerializedPortClient(this.sendPort); + + ChildPortClient open() { + return ChildPortClient(sendPort); + } +} + +class ChildPortClient implements PortClient { + final SendPort sendPort; + ReceivePort get receivePort => _stub(); + bool closed = false; + + final Map> handlers = HashMap(); + + ChildPortClient(this.sendPort); + + @override + Future post(Object message) async { + _stub(); + } + + @override + void fire(Object message) { + _stub(); + } + + void close() { + _stub(); + } +} + +class RequestPortServer { + final SendPort port; + + RequestPortServer(this.port); + + open(Future Function(Object? message) handle) { + _stub(); + } +} + +class PortServer { + final Future Function(Object? message) handle; + final SendPort? replyPort; + + PortServer(this.handle) : replyPort = null; + + PortServer.forSendPort(SendPort port, this.handle) : replyPort = port; + + SendPort get sendPort { + _stub(); + } + + SerializedPortClient client() { + return SerializedPortClient(sendPort); + } + + void close() { + _stub(); + } +} + +class ClosedException implements Exception { + const ClosedException(); + + @override + String toString() { + return 'ClosedException'; + } +} + +class IsolateError extends Error { + final Object cause; + final String? isolateDebugName; + + IsolateError({required this.cause, this.isolateDebugName}); + + @override + String toString() { + if (isolateDebugName != null) { + return 'IsolateError in $isolateDebugName: $cause'; + } else { + return 'IsolateError: $cause'; + } + } +} diff --git a/packages/sqlite_async/pubspec.yaml b/packages/sqlite_async/pubspec.yaml index e4ea5ce..8264912 100644 --- a/packages/sqlite_async/pubspec.yaml +++ b/packages/sqlite_async/pubspec.yaml @@ -1,6 +1,6 @@ name: sqlite_async description: High-performance asynchronous interface for SQLite on Dart and Flutter. -version: 0.11.2 +version: 0.11.3 repository: https://github.com/powersync-ja/sqlite_async.dart environment: sdk: ">=3.5.0 <4.0.0" @@ -21,6 +21,9 @@ dependencies: web: ^1.0.0 dev_dependencies: + build_runner: ^2.4.14 + build_web_compilers: ^4.1.1 + build_test: ^2.2.3 lints: ^5.0.0 test: ^1.21.0 test_api: ^0.7.0