From 63dcfa57895c4db0fe8c7fb994bd6c2048ff3b7b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 15:40:25 +0200 Subject: [PATCH 01/12] Avoid URIs to make workers smaller --- examples/multiplatform/db/sqlite3/web.dart | 2 +- sqlite3/README.md | 4 ++-- sqlite3/doc/common.md | 2 +- sqlite3/example/web/main.dart | 4 +--- sqlite3/lib/src/in_memory_vfs.dart | 6 +++-- sqlite3/lib/src/path_utils_fallback.dart | 5 ++++ sqlite3/lib/src/wasm/path_utils.dart | 17 ++++++++++++++ sqlite3/lib/src/wasm/sqlite3.dart | 21 ++++++++++++++--- .../lib/src/wasm/vfs/async_opfs/client.dart | 11 ++++----- .../lib/src/wasm/vfs/async_opfs/worker.dart | 23 +++++++++---------- sqlite3/lib/src/wasm/vfs/simple_opfs.dart | 6 ++--- sqlite3_web/CHANGELOG.md | 3 ++- sqlite3_web/README.md | 4 ++-- sqlite3_web/benchmark/benchmark.dart | 4 ++-- sqlite3_web/benchmark/worker.dart | 5 ++-- sqlite3_web/example/main.dart | 4 ++-- sqlite3_web/lib/src/client.dart | 6 ++--- sqlite3_web/lib/src/database.dart | 8 +++---- sqlite3_web/lib/src/worker.dart | 19 ++++++++------- sqlite3_web/lib/src/worker_connector.dart | 14 ++++++----- sqlite3_web/test/worker_test.dart | 4 ++-- sqlite3_web/web/main.dart | 4 ++-- 22 files changed, 109 insertions(+), 67 deletions(-) create mode 100644 sqlite3/lib/src/path_utils_fallback.dart create mode 100644 sqlite3/lib/src/wasm/path_utils.dart diff --git a/examples/multiplatform/db/sqlite3/web.dart b/examples/multiplatform/db/sqlite3/web.dart index 8f269223..e0adbdb2 100644 --- a/examples/multiplatform/db/sqlite3/web.dart +++ b/examples/multiplatform/db/sqlite3/web.dart @@ -5,7 +5,7 @@ Future openSqliteDb() async { const name = 'my_app'; // Please download `sqlite3.wasm` from https://github.com/simolus3/sqlite3.dart/releases // into the `web/` dir of your Flutter app. See `README.md` for details. - final sqlite = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm')); + final sqlite = await WasmSqlite3.loadFromUrlString('sqlite3.wasm'); final fileSystem = await IndexedDbFileSystem.open(dbName: name); sqlite.registerVirtualFileSystem(fileSystem, makeDefault: true); return sqlite.open(name); diff --git a/sqlite3/README.md b/sqlite3/README.md index e9b56a2e..6f3636c2 100644 --- a/sqlite3/README.md +++ b/sqlite3/README.md @@ -85,7 +85,7 @@ import 'package:sqlite3/common.dart'; import 'package:sqlite3/wasm.dart'; Future loadSqlite() async { - final sqlite = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm')); + final sqlite = await WasmSqlite3.loadFromUrlString('sqlite3.wasm'); final fileSystem = await IndexedDbFileSystem.open(dbName: 'my_app'); sqlite.registerVirtualFileSystem(fileSystem, makeDefault: true); return sqlite; @@ -123,7 +123,7 @@ To test the encryption integration, download `sqlite3mc.wasm` from the [releases of this package and use that as a URL to load sqlite3 on the web: ```dart -final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3mc.wasm')); +final sqlite3 = await WasmSqlite3.loadFromUrlString('sqlite3mc.wasm'); sqlite3.registerVirtualFileSystem(InMemoryFileSystem(), makeDefault: true); final database = sqlite3.open('/database') diff --git a/sqlite3/doc/common.md b/sqlite3/doc/common.md index 66dc7f8e..79987285 100644 --- a/sqlite3/doc/common.md +++ b/sqlite3/doc/common.md @@ -38,7 +38,7 @@ Future openDatabase() async { import 'package:sqlite3/wasm.dart'; Future openDatabase() async { - final sqlite = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm')); + final sqlite = await WasmSqlite3.loadFromUrlString('sqlite3.wasm'); final fs = await IndexedDbFileSystem.open(dbName: 'app.db'); sqlite.registerVirtualFileSystem(fs, makeDefault: true); return sqlite.open('/app.db'); diff --git a/sqlite3/example/web/main.dart b/sqlite3/example/web/main.dart index d165ab5b..6567b8b2 100644 --- a/sqlite3/example/web/main.dart +++ b/sqlite3/example/web/main.dart @@ -11,9 +11,7 @@ Future main() async { startIndexedDb.onClick.listen((_) async { startIndexedDb.remove(); - final sqlite3 = await WasmSqlite3.loadFromUrl( - Uri.parse('sqlite3.debug.wasm'), - ); + final sqlite3 = await WasmSqlite3.loadFromUrlString('sqlite3.debug.wasm'); print(sqlite3.version); diff --git a/sqlite3/lib/src/in_memory_vfs.dart b/sqlite3/lib/src/in_memory_vfs.dart index dcf9de36..828ea363 100644 --- a/sqlite3/lib/src/in_memory_vfs.dart +++ b/sqlite3/lib/src/in_memory_vfs.dart @@ -1,9 +1,11 @@ import 'dart:math'; import 'dart:typed_data'; -import 'package:path/path.dart' as p; import 'package:typed_data/typed_buffers.dart'; +import 'path_utils_fallback.dart' + if (dart.library.js_interop) 'wasm/path_utils.dart'; + import 'constants.dart'; import 'vfs.dart'; import 'utils.dart'; @@ -32,7 +34,7 @@ final class InMemoryFileSystem extends BaseVirtualFileSystem { @override String xFullPathName(String path) { - return p.url.normalize('/$path'); + return pathToAbsoluteAndNormalize(path); } @override diff --git a/sqlite3/lib/src/path_utils_fallback.dart b/sqlite3/lib/src/path_utils_fallback.dart new file mode 100644 index 00000000..20fcb5e2 --- /dev/null +++ b/sqlite3/lib/src/path_utils_fallback.dart @@ -0,0 +1,5 @@ +import 'package:path/path.dart' as p; + +String pathToAbsoluteAndNormalize(String source) { + return p.url.normalize('/$source'); +} diff --git a/sqlite3/lib/src/wasm/path_utils.dart b/sqlite3/lib/src/wasm/path_utils.dart new file mode 100644 index 00000000..45d52279 --- /dev/null +++ b/sqlite3/lib/src/wasm/path_utils.dart @@ -0,0 +1,17 @@ +/// Path helpers implemented with direct interop to web APIs instead of +/// `package:path`. +/// +/// In compiled web workers, Dart's [Uri.parse] implementation takes up a good +/// chunk of the total file size. By using [URL] directly (which is good enough +/// for our use case), we can avoid that implementation. +library; + +import 'package:web/web.dart'; + +String pathToAbsoluteAndNormalize(String source) { + return URL(source, 'file:///').pathname; +} + +Iterable pathComponents(String path) { + return URL(path, 'file:///').pathname.split('/').where((e) => e.isNotEmpty); +} diff --git a/sqlite3/lib/src/wasm/sqlite3.dart b/sqlite3/lib/src/wasm/sqlite3.dart index e3de81a1..1ce132a7 100644 --- a/sqlite3/lib/src/wasm/sqlite3.dart +++ b/sqlite3/lib/src/wasm/sqlite3.dart @@ -51,6 +51,23 @@ final class WasmSqlite3 extends Sqlite3Implementation { Uri uri, { Map? headers, WasmModuleLoader? loader, + }) { + return loadFromUrlString(uri.toString()); + } + + /// Loads a web version of the sqlite3 libraries. + /// + /// The native wasm library for sqlite3 is loaded from the [url] with the + /// desired [headers] through a `fetch` request. + /// + /// Using this over [loadFromUrl] might reduce compiled JS sizes for apps + /// which otherwise don't use URLs. + /// + /// [pgk release]: https://github.com/simolus3/sqlite3.dart/releases + static Future loadFromUrlString( + String url, { + Map? headers, + WasmModuleLoader? loader, }) async { web.RequestInit? options; @@ -63,9 +80,7 @@ final class WasmSqlite3 extends Sqlite3Implementation { options = web.RequestInit(headers: headersJs); } - final jsUri = uri.isAbsolute - ? web.URL(uri.toString()) - : web.URL(uri.toString(), Uri.base.toString()); + final jsUri = web.URL(url, (globalContext['location'] as web.URL).href); final response = await fetch(jsUri, options).toDart; return _load(response, loader); } diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/client.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/client.dart index 537da34e..d995a4aa 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/client.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/client.dart @@ -3,7 +3,7 @@ import 'dart:js_interop_unsafe'; import 'dart:math'; import 'dart:typed_data'; -import 'package:path/path.dart' as p; +import 'package:web/web.dart' show URL; import '../../../constants.dart'; import '../../../vfs.dart'; @@ -42,7 +42,6 @@ final class WasmVfs extends BaseVirtualFileSystem { final MessageSerializer serializer; final String chroot; - final p.Context pathContext; WasmVfs({ super.random, @@ -53,7 +52,6 @@ final class WasmVfs extends BaseVirtualFileSystem { workerOptions.synchronizationBuffer, ), serializer = MessageSerializer(workerOptions.communicationBuffer), - pathContext = p.Context(style: p.Style.url, current: chroot), super(name: vfsName); Res _runInWorker( @@ -89,12 +87,13 @@ final class WasmVfs extends BaseVirtualFileSystem { @override String xFullPathName(String path) { - final resolved = pathContext.absolute(path); - if (!p.isWithin(chroot, resolved)) { + final normalized = URL(path, 'file://$chroot').pathname; + + if (!normalized.startsWith(chroot)) { throw const VfsException(SqlError.SQLITE_CANTOPEN); } - return resolved; + return normalized; } @override diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart index 614d0f9b..24c87e20 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart @@ -4,7 +4,7 @@ library; import 'dart:js_interop'; -import 'package:path/path.dart' as p show url; +import 'package:sqlite3/src/wasm/path_utils.dart'; import 'package:web/web.dart' show FileSystemDirectoryHandle, @@ -61,12 +61,12 @@ extension type WorkerOptions._raw(JSObject _) implements JSObject { } class _ResolvedPath { - final String fullPath; + final String debugPath; final FileSystemDirectoryHandle directory; final String filename; - _ResolvedPath(this.fullPath, this.directory, this.filename); + _ResolvedPath(this.debugPath, this.directory, this.filename); Future openFile({bool create = false}) { return directory.openFile(filename, create: create); @@ -101,7 +101,7 @@ class VfsWorker { static Future create(WorkerOptions options) async { var root = await storageManager!.directory; - final split = p.url.split(options.root); + final split = pathComponents(options.root); for (final directory in split) { root = await root.getDirectory(directory, create: true); @@ -114,8 +114,7 @@ class VfsWorker { String absolutePath, { bool createDirectories = false, }) async { - final fullPath = p.url.relative(absolutePath, from: '/'); - final [...directories, file] = p.url.split(fullPath); + final [...directories, file] = [...pathComponents(absolutePath)]; var dirHandle = root; for (final entry in directories) { @@ -125,7 +124,7 @@ class VfsWorker { ); } - return _ResolvedPath(fullPath, dirHandle, file); + return _ResolvedPath(absolutePath, dirHandle, file); } Future _xAccess(NameAndInt32Flags flags) async { @@ -169,7 +168,7 @@ class VfsWorker { final opened = _OpenedFileHandle( fd: _fdCounter++, directory: resolved.directory, - fullPath: resolved.fullPath, + debugPath: resolved.debugPath, filename: resolved.filename, file: fileHandle, deleteOnClose: (flags & SqlFlag.SQLITE_OPEN_DELETEONCLOSE) != 0, @@ -422,7 +421,7 @@ class VfsWorker { // across requests. if (!file.explicitlyLocked) { _implicitlyHeldLocks.add(file); - _log('Acquired implicit lock for ${file.fullPath}'); + _log('Acquired implicit lock for ${file.debugPath}'); } return handle; } catch (e) { @@ -447,7 +446,7 @@ class VfsWorker { void _closeSyncHandle(_OpenedFileHandle handle) { final syncHandle = handle.syncHandle; if (syncHandle != null) { - _log('Closing sync handle for ${handle.fullPath}'); + _log('Closing sync handle for ${handle.debugPath}'); handle.syncHandle = null; _implicitlyHeldLocks.remove(handle); handle.explicitlyLocked = false; @@ -461,7 +460,7 @@ class _OpenedFileHandle { final bool readonly; final bool deleteOnClose; - final String fullPath; + final String debugPath; final FileSystemDirectoryHandle directory; final String filename; final FileSystemFileHandle file; @@ -473,7 +472,7 @@ class _OpenedFileHandle { required this.fd, required this.readonly, required this.deleteOnClose, - required this.fullPath, + required this.debugPath, required this.directory, required this.filename, required this.file, diff --git a/sqlite3/lib/src/wasm/vfs/simple_opfs.dart b/sqlite3/lib/src/wasm/vfs/simple_opfs.dart index 7c027a02..c645d04e 100644 --- a/sqlite3/lib/src/wasm/vfs/simple_opfs.dart +++ b/sqlite3/lib/src/wasm/vfs/simple_opfs.dart @@ -2,7 +2,6 @@ import 'dart:js_interop'; import 'dart:typed_data'; import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; import 'package:web/web.dart' show FileSystemDirectoryHandle, @@ -15,6 +14,7 @@ import '../../constants.dart'; import '../../vfs.dart'; import '../js_interop.dart'; import '../../in_memory_vfs.dart'; +import '../path_utils.dart'; @internal enum FileType { @@ -82,7 +82,7 @@ final class SimpleOpfsFileSystem extends BaseVirtualFileSystem { FileSystemDirectoryHandle? parent; var opfsDirectory = await storage.directory; - for (final segment in p.split(path)) { + for (final segment in pathComponents(path)) { parent = opfsDirectory; opfsDirectory = await opfsDirectory.getDirectory(segment, create: create); } @@ -210,7 +210,7 @@ final class SimpleOpfsFileSystem extends BaseVirtualFileSystem { @override String xFullPathName(String path) { - return p.url.normalize('/$path'); + return pathToAbsoluteAndNormalize(path); } @override diff --git a/sqlite3_web/CHANGELOG.md b/sqlite3_web/CHANGELOG.md index e4f588bc..c260616a 100644 --- a/sqlite3_web/CHANGELOG.md +++ b/sqlite3_web/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.7.2 (unreleased) +## 0.8.0 (unreleased) - Remove `stream_channel` dependency with custom implementation, slightly decreasing compiled size. +- Use strings instead of URLs to reduce code size. ## 0.7.1 diff --git a/sqlite3_web/README.md b/sqlite3_web/README.md index a1ec4daa..7903e3e3 100644 --- a/sqlite3_web/README.md +++ b/sqlite3_web/README.md @@ -63,8 +63,8 @@ that are transparently hosted in the worker: ```dart Future connectToDatabase() async { final sqlite = await WebSqlite.open( - workers: WorkerConnector.defaultWorkers(Uri.parse('worker.dart.js')), - wasmModule: Uri.parse('sqlite3.wasm'), + workers: WorkerConnector.defaultWorkers('worker.dart.js'), + wasmModule: 'sqlite3.wasm', ); final features = await sqlite.runFeatureDetection(); diff --git a/sqlite3_web/benchmark/benchmark.dart b/sqlite3_web/benchmark/benchmark.dart index 236e4774..32f898c9 100644 --- a/sqlite3_web/benchmark/benchmark.dart +++ b/sqlite3_web/benchmark/benchmark.dart @@ -237,7 +237,7 @@ final class ClientStateNotifier extends Notifier { final sqlite3 = Provider((ref) { return WebSqlite.open( workers: _EncapsulatedWorkerConnector(), - wasmModule: Uri.parse('sqlite3.wasm'), + wasmModule: 'sqlite3.wasm', ); }); @@ -252,7 +252,7 @@ final selectedTarget = StateProvider((ref) { final class _EncapsulatedWorkerConnector implements WorkerConnector { final WorkerConnector _inner = WorkerConnector.defaultWorkers( - Uri.parse('worker.dart.js'), + 'worker.dart.js', ); @override diff --git a/sqlite3_web/benchmark/worker.dart b/sqlite3_web/benchmark/worker.dart index a54402b4..271b516c 100644 --- a/sqlite3_web/benchmark/worker.dart +++ b/sqlite3_web/benchmark/worker.dart @@ -14,15 +14,16 @@ void main() { WorkerEnvironment environment; if (globalContext.instanceOfString('SharedWorkerGlobalScope')) { + final scope = globalContext as SharedWorkerGlobalScope; + // This shared worker is used both to hand out database access and to // coordinate multiple tabs running concurrency benchmarks. We encapsulate // messages from the sqlite3_async package in a WorkerMessage struct, which // allows this worker to speak both protocols. final fakeEnvironment = environment = FakeWorkerEnvironment( - WorkerConnector.defaultWorkers(Uri.base), + WorkerConnector.defaultWorkers(scope.location.href), ); - final scope = globalContext as SharedWorkerGlobalScope; final clients = ClientsDriver(); clients.run(); diff --git a/sqlite3_web/example/main.dart b/sqlite3_web/example/main.dart index 83b94d70..918a04f3 100644 --- a/sqlite3_web/example/main.dart +++ b/sqlite3_web/example/main.dart @@ -5,8 +5,8 @@ import 'package:sqlite3_web/sqlite3_web.dart'; void main() async { final sqlite = WebSqlite.open( - workers: WorkerConnector.defaultWorkers(Uri.parse('worker.dart.js')), - wasmModule: Uri.parse('sqlite3.wasm'), + workers: WorkerConnector.defaultWorkers('worker.dart.js'), + wasmModule: 'sqlite3.wasm', ); final features = await sqlite.runFeatureDetection(); diff --git a/sqlite3_web/lib/src/client.dart b/sqlite3_web/lib/src/client.dart index b76b55c4..e680217b 100644 --- a/sqlite3_web/lib/src/client.dart +++ b/sqlite3_web/lib/src/client.dart @@ -369,7 +369,7 @@ final class WorkerConnection extends ProtocolChannel { } Future requestDatabase({ - required Uri wasmUri, + required String wasmUri, required String databaseName, required DatabaseImplementation implementation, required bool onlyOpenVfs, @@ -378,7 +378,7 @@ final class WorkerConnection extends ProtocolChannel { final response = await sendRequest( newOpenRequest( requestId: 0, - wasmUri: wasmUri.toString(), + wasmUri: wasmUri, databaseName: databaseName, storageMode: implementation.resolveToVfs().toJS, onlyOpenVfs: onlyOpenVfs, @@ -401,7 +401,7 @@ final class WorkerConnection extends ProtocolChannel { final class DatabaseClient implements WebSqlite { final WorkerConnector workers; - final Uri wasmUri; + final String wasmUri; final DatabaseController _localController; final Future Function(JSAny?) _handleCustomRequest; diff --git a/sqlite3_web/lib/src/database.dart b/sqlite3_web/lib/src/database.dart index dd16bb0d..7f069996 100644 --- a/sqlite3_web/lib/src/database.dart +++ b/sqlite3_web/lib/src/database.dart @@ -17,10 +17,10 @@ abstract base class DatabaseController { /// Loads a wasm module from the given [uri] with the specified [headers]. Future loadWasmModule( - Uri uri, { + String uri, { Map? headers, }) async { - return WasmSqlite3.loadFromUrl(uri, headers: headers); + return WasmSqlite3.loadFromUrlString(uri, headers: headers); } /// Opens a database in the pre-configured [sqlite3] instance under the @@ -358,7 +358,7 @@ abstract class WebSqlite { /// If it's absent, the default is to throw an exception when called. static WebSqlite open({ required WorkerConnector workers, - required Uri wasmModule, + required String wasmModule, DatabaseController? controller, Future Function(JSAny?)? handleCustomRequest, }) { @@ -391,7 +391,7 @@ abstract class WebSqlite { }) { final client = DatabaseClient( const WorkerConnector.unsupported(), - Uri.base, + (globalContext as Window).location.href, const _DefaultDatabaseController(), handleCustomRequest, ); diff --git a/sqlite3_web/lib/src/worker.dart b/sqlite3_web/lib/src/worker.dart index 98b638e4..544236c0 100644 --- a/sqlite3_web/lib/src/worker.dart +++ b/sqlite3_web/lib/src/worker.dart @@ -5,13 +5,14 @@ import 'dart:typed_data'; import 'package:sqlite3/wasm.dart'; import 'package:web/web.dart' show + AbortController, AbortSignal, DedicatedWorkerGlobalScope, EventStreamProviders, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemSyncAccessHandle, - AbortController; + URL; // ignore: implementation_imports import 'package:sqlite3/src/wasm/js_interop/new_file_system_access.dart'; @@ -265,7 +266,7 @@ final class _ClientConnection extends ProtocolChannel AbortSignal abortSignal, ) async { return await _runner.openLock.withCriticalSection(() async { - await _runner.loadWasmModule(Uri.parse(request.wasmUri)); + await _runner.loadWasmModule(request.wasmUri); DatabaseState? database; _ConnectionDatabase? connectionDatabase; @@ -723,7 +724,7 @@ final class WorkerRunner { var _nextDatabaseId = 0; Future? _sqlite3; - Uri? _wasmUri; + String? _wasmUri; final Mutex _compatibilityCheck = Mutex(); @@ -839,18 +840,20 @@ final class WorkerRunner { }); } - Future loadWasmModule(Uri uri) async { + Future loadWasmModule(String url) async { + final resolved = URL(url, (globalContext['location'] as URL).href).href; + if (_sqlite3 != null) { - if (_wasmUri != uri) { + if (_wasmUri != resolved) { throw StateError( 'Workers only support a single sqlite3 wasm module, provided ' - 'different URI (has $_wasmUri, got $uri)', + 'different URI (has $_wasmUri, got $resolved)', ); } await _sqlite3; } else { - final future = _sqlite3 = _controller.loadWasmModule(uri).onError(( + final future = _sqlite3 = _controller.loadWasmModule(resolved).onError(( error, stackTrace, ) { @@ -858,7 +861,7 @@ final class WorkerRunner { throw error!; }); await future; - _wasmUri = uri; + _wasmUri = resolved; } } diff --git a/sqlite3_web/lib/src/worker_connector.dart b/sqlite3_web/lib/src/worker_connector.dart index f2b04c6e..035ed29b 100644 --- a/sqlite3_web/lib/src/worker_connector.dart +++ b/sqlite3_web/lib/src/worker_connector.dart @@ -39,7 +39,9 @@ abstract class _WorkerEnvironment final T scope; @override - final WorkerConnector connector = WorkerConnector.defaultWorkers(Uri.base); + final WorkerConnector connector = WorkerConnector.defaultWorkers( + (globalContext as WorkerGlobalScope).location.href, + ); _WorkerEnvironment() : scope = globalContext as T; } @@ -141,7 +143,7 @@ abstract interface class WorkerConnector { /// /// The [uri] must point to a compiled Dart program using /// [WebSqlite.workerEntrypoint] to receive messages. - factory WorkerConnector.defaultWorkers(Uri uri) { + factory WorkerConnector.defaultWorkers(String uri) { return _DefaultWorkerConnector(uri); } @@ -167,9 +169,9 @@ abstract interface class WorkerHandle { } final class _DefaultWorkerConnector implements WorkerConnector { - final Uri _worker; + final String _workerUrl; - _DefaultWorkerConnector(this._worker); + _DefaultWorkerConnector(this._workerUrl); @override WorkerHandle? spawnDedicatedWorker() { @@ -178,7 +180,7 @@ final class _DefaultWorkerConnector implements WorkerConnector { } return _DedicatedWorker( - Worker(_worker.toString().toJS, WorkerOptions(name: 'sqlite3_worker')), + Worker(_workerUrl.toJS, WorkerOptions(name: 'sqlite3_worker')), ); } @@ -188,7 +190,7 @@ final class _DefaultWorkerConnector implements WorkerConnector { return null; } - final worker = SharedWorker(_worker.toString().toJS); + final worker = SharedWorker(_workerUrl.toJS); worker.port.start(); return _SharedWorker(worker); } diff --git a/sqlite3_web/test/worker_test.dart b/sqlite3_web/test/worker_test.dart index d215083f..3933f22f 100644 --- a/sqlite3_web/test/worker_test.dart +++ b/sqlite3_web/test/worker_test.dart @@ -11,13 +11,13 @@ import 'package:sqlite3_web/src/client.dart'; import 'package:test/test.dart'; void main() { - late Uri sqlite3WasmUri; + late String sqlite3WasmUri; late FakeWorkerEnvironment fakeWorkers; setUpAll(() async { final channel = spawnHybridUri('/test/asset_server.dart'); final port = (await channel.stream.first as double).toInt(); - sqlite3WasmUri = Uri.parse('http://localhost:$port/web/sqlite3.wasm'); + sqlite3WasmUri = 'http://localhost:$port/web/sqlite3.wasm'; }); setUp(() { diff --git a/sqlite3_web/web/main.dart b/sqlite3_web/web/main.dart index 86e2f6db..c672369a 100644 --- a/sqlite3_web/web/main.dart +++ b/sqlite3_web/web/main.dart @@ -9,8 +9,8 @@ import 'package:web/web.dart'; import 'controller.dart'; -final sqlite3WasmUri = Uri.parse('sqlite3.wasm'); -final workerUri = Uri.parse('worker.dart.js'); +const sqlite3WasmUri = 'sqlite3.wasm'; +const workerUri = 'worker.dart.js'; const databaseName = 'database'; const additionalOptions = 'test-additional-options'; From 70b0e90a99591bf00632d0766b1058467bb917a4 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 16:11:35 +0200 Subject: [PATCH 02/12] Reformat with Dart 3.12 --- sqlite3_web/lib/src/protocol/helper.g.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sqlite3_web/lib/src/protocol/helper.g.dart b/sqlite3_web/lib/src/protocol/helper.g.dart index 1662ae9c..189f3796 100644 --- a/sqlite3_web/lib/src/protocol/helper.g.dart +++ b/sqlite3_web/lib/src/protocol/helper.g.dart @@ -687,8 +687,7 @@ SharedCompatibilityCheck newSharedCompatibilityCheck({ @anonymous extension type _DedicatedInSharedCompatibilityCheck._( DedicatedInSharedCompatibilityCheck _ -) - implements DedicatedInSharedCompatibilityCheck { +) implements DedicatedInSharedCompatibilityCheck { external factory _DedicatedInSharedCompatibilityCheck({ @JS('d') required String? databaseName, @JS('i') required int requestId, From aee10bf320ff62f85223277df2fe820c6768e8fb Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 16:20:36 +0200 Subject: [PATCH 03/12] Upgrade to Dart 3.12 --- sqlite3/lib/src/implementation/database.dart | 10 ++++------ sqlite3/lib/src/in_memory_vfs.dart | 4 ++-- .../fallback.dart} | 0 .../path_utils.dart => platform/web.dart} | 4 ++-- .../lib/src/wasm/vfs/async_opfs/worker.dart | 2 +- sqlite3/lib/src/wasm/vfs/simple_opfs.dart | 18 +++++++++++------- sqlite3/pubspec.yaml | 3 +-- 7 files changed, 21 insertions(+), 20 deletions(-) rename sqlite3/lib/src/{path_utils_fallback.dart => platform/fallback.dart} (100%) rename sqlite3/lib/src/{wasm/path_utils.dart => platform/web.dart} (81%) diff --git a/sqlite3/lib/src/implementation/database.dart b/sqlite3/lib/src/implementation/database.dart index 02ffcae4..6c39ea88 100644 --- a/sqlite3/lib/src/implementation/database.dart +++ b/sqlite3/lib/src/implementation/database.dart @@ -640,12 +640,10 @@ final class _StreamHandlers { Stream get syncStream => _syncStream ??= _generateStream(true); _StreamHandlers({ - required DatabaseImplementation database, - required void Function() register, - required void Function() unregister, - }) : _database = database, - _register = register, - _unregister = unregister; + required this._database, + required this._register, + required this._unregister, + }); Stream _generateStream(bool dispatchSynchronously) { return Stream.multi((newListener) { diff --git a/sqlite3/lib/src/in_memory_vfs.dart b/sqlite3/lib/src/in_memory_vfs.dart index 828ea363..d790a942 100644 --- a/sqlite3/lib/src/in_memory_vfs.dart +++ b/sqlite3/lib/src/in_memory_vfs.dart @@ -3,8 +3,8 @@ import 'dart:typed_data'; import 'package:typed_data/typed_buffers.dart'; -import 'path_utils_fallback.dart' - if (dart.library.js_interop) 'wasm/path_utils.dart'; +import 'platform/fallback.dart' + if (dart.library.js_interop) 'platform/web.dart'; import 'constants.dart'; import 'vfs.dart'; diff --git a/sqlite3/lib/src/path_utils_fallback.dart b/sqlite3/lib/src/platform/fallback.dart similarity index 100% rename from sqlite3/lib/src/path_utils_fallback.dart rename to sqlite3/lib/src/platform/fallback.dart diff --git a/sqlite3/lib/src/wasm/path_utils.dart b/sqlite3/lib/src/platform/web.dart similarity index 81% rename from sqlite3/lib/src/wasm/path_utils.dart rename to sqlite3/lib/src/platform/web.dart index 45d52279..9863a1c8 100644 --- a/sqlite3/lib/src/wasm/path_utils.dart +++ b/sqlite3/lib/src/platform/web.dart @@ -1,5 +1,5 @@ -/// Path helpers implemented with direct interop to web APIs instead of -/// `package:path`. +/// Helpers implemented with direct interop to web APIs instead of +/// cross-platform Dart packages or the SDK. /// /// In compiled web workers, Dart's [Uri.parse] implementation takes up a good /// chunk of the total file size. By using [URL] directly (which is good enough diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart index 24c87e20..f6ced312 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/worker.dart @@ -4,7 +4,6 @@ library; import 'dart:js_interop'; -import 'package:sqlite3/src/wasm/path_utils.dart'; import 'package:web/web.dart' show FileSystemDirectoryHandle, @@ -13,6 +12,7 @@ import 'package:web/web.dart' FileSystemReadWriteOptions; import '../../../constants.dart'; +import '../../../platform/web.dart'; import '../../../vfs.dart'; import '../../js_interop.dart'; import 'sync_channel.dart'; diff --git a/sqlite3/lib/src/wasm/vfs/simple_opfs.dart b/sqlite3/lib/src/wasm/vfs/simple_opfs.dart index c645d04e..656e749e 100644 --- a/sqlite3/lib/src/wasm/vfs/simple_opfs.dart +++ b/sqlite3/lib/src/wasm/vfs/simple_opfs.dart @@ -14,7 +14,7 @@ import '../../constants.dart'; import '../../vfs.dart'; import '../js_interop.dart'; import '../../in_memory_vfs.dart'; -import '../path_utils.dart'; +import '../../platform/web.dart'; @internal enum FileType { @@ -127,13 +127,17 @@ final class SimpleOpfsFileSystem extends BaseVirtualFileSystem { try { (parent, handle) = await _resolveDir(path, create: false); - } on DOMException catch (e) { - if (e.name == 'NotFoundError' || e.name == 'TypeMismatchError') { - // Directory doesn't exist, ignore. - return; - } else { - rethrow; + } catch (e) { + if (e.isA()) { + final asDomException = e as DOMException; + if (asDomException.name == 'NotFoundError' || + asDomException.name == 'TypeMismatchError') { + // Directory doesn't exist, ignore. + return; + } } + + rethrow; } if (parent != null) { diff --git a/sqlite3/pubspec.yaml b/sqlite3/pubspec.yaml index dab25c83..1cfab15e 100644 --- a/sqlite3/pubspec.yaml +++ b/sqlite3/pubspec.yaml @@ -6,8 +6,7 @@ issue_tracker: https://github.com/simolus3/sqlite3.dart/issues resolution: workspace environment: - # We want >=3.10, but Flutter 3.38 somehow ships with a beta Dart SDK that rejects >=3.10 deps. - sdk: '>=3.9.999 <4.0.0' + sdk: '>=3.12.0 <4.0.0' # This package supports all platforms listed below. platforms: From f7dc26f07436a96948469327ebe1252036c06414 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 16:24:40 +0200 Subject: [PATCH 04/12] Revert 3.12 upgrade --- sqlite3/lib/src/implementation/database.dart | 10 ++++++---- sqlite3/lib/src/wasm/vfs/simple_opfs.dart | 4 +++- sqlite3/pubspec.yaml | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/sqlite3/lib/src/implementation/database.dart b/sqlite3/lib/src/implementation/database.dart index 6c39ea88..02ffcae4 100644 --- a/sqlite3/lib/src/implementation/database.dart +++ b/sqlite3/lib/src/implementation/database.dart @@ -640,10 +640,12 @@ final class _StreamHandlers { Stream get syncStream => _syncStream ??= _generateStream(true); _StreamHandlers({ - required this._database, - required this._register, - required this._unregister, - }); + required DatabaseImplementation database, + required void Function() register, + required void Function() unregister, + }) : _database = database, + _register = register, + _unregister = unregister; Stream _generateStream(bool dispatchSynchronously) { return Stream.multi((newListener) { diff --git a/sqlite3/lib/src/wasm/vfs/simple_opfs.dart b/sqlite3/lib/src/wasm/vfs/simple_opfs.dart index 656e749e..6672c471 100644 --- a/sqlite3/lib/src/wasm/vfs/simple_opfs.dart +++ b/sqlite3/lib/src/wasm/vfs/simple_opfs.dart @@ -127,7 +127,9 @@ final class SimpleOpfsFileSystem extends BaseVirtualFileSystem { try { (parent, handle) = await _resolveDir(path, create: false); - } catch (e) { + // ignore: invalid_runtime_check_with_js_interop_types + } on JSAny catch (e) { + // TODO: Remove type clause (needs Dart 3.12 as a minimum version) if (e.isA()) { final asDomException = e as DOMException; if (asDomException.name == 'NotFoundError' || diff --git a/sqlite3/pubspec.yaml b/sqlite3/pubspec.yaml index 1cfab15e..dab25c83 100644 --- a/sqlite3/pubspec.yaml +++ b/sqlite3/pubspec.yaml @@ -6,7 +6,8 @@ issue_tracker: https://github.com/simolus3/sqlite3.dart/issues resolution: workspace environment: - sdk: '>=3.12.0 <4.0.0' + # We want >=3.10, but Flutter 3.38 somehow ships with a beta Dart SDK that rejects >=3.10 deps. + sdk: '>=3.9.999 <4.0.0' # This package supports all platforms listed below. platforms: From 3ec99fccddc4a52c64f0fd466eccf547a8fe92c7 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 16:34:56 +0200 Subject: [PATCH 05/12] Avoid Dart utf-8 decoder on the web --- sqlite3/lib/src/implementation/database.dart | 5 ++++- sqlite3/lib/src/jsonb.dart | 4 +++- sqlite3/lib/src/platform/fallback.dart | 7 +++++++ sqlite3/lib/src/platform/platform.dart | 1 + sqlite3/lib/src/platform/web.dart | 11 +++++++++++ sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart | 3 ++- sqlite3/lib/src/wasm/wasm_interop.dart | 5 +++-- 7 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 sqlite3/lib/src/platform/platform.dart diff --git a/sqlite3/lib/src/implementation/database.dart b/sqlite3/lib/src/implementation/database.dart index 02ffcae4..4f017540 100644 --- a/sqlite3/lib/src/implementation/database.dart +++ b/sqlite3/lib/src/implementation/database.dart @@ -9,6 +9,7 @@ import '../constants.dart'; import '../database.dart'; import '../exception.dart'; import '../functions.dart'; +import '../platform/platform.dart'; import '../result_set.dart'; import '../statement.dart'; import 'bindings.dart'; @@ -393,7 +394,9 @@ base class DatabaseImplementation implements CommonDatabase { // or comments were parsed. That's fine, just skip over it then. final stmt = result.result; if (stmt != null) { - final stmtSql = utf8.decoder.convert(bytes, offset, endOffset); + final stmtSql = utf8Decode( + Uint8List.sublistView(bytes, offset, endOffset), + ); createdStatements.add(wrapStatement(stmtSql, stmt)); } diff --git a/sqlite3/lib/src/jsonb.dart b/sqlite3/lib/src/jsonb.dart index fec53af8..32d38503 100644 --- a/sqlite3/lib/src/jsonb.dart +++ b/sqlite3/lib/src/jsonb.dart @@ -3,6 +3,8 @@ import 'dart:typed_data'; import 'package:typed_data/typed_buffers.dart'; +import 'platform/platform.dart'; + /// A [Codec] capable of converting Dart objects from and to the [JSONB] format /// used by sqlite3. /// @@ -176,7 +178,7 @@ final class _JsonbDecodingState { } String payloadString() { - return utf8.decode(payloadBytes()); + return utf8Decode(payloadBytes()); } final value = switch (type) { diff --git a/sqlite3/lib/src/platform/fallback.dart b/sqlite3/lib/src/platform/fallback.dart index 20fcb5e2..ad6e8eb7 100644 --- a/sqlite3/lib/src/platform/fallback.dart +++ b/sqlite3/lib/src/platform/fallback.dart @@ -1,5 +1,12 @@ +import 'dart:convert'; +import 'dart:typed_data'; + import 'package:path/path.dart' as p; String pathToAbsoluteAndNormalize(String source) { return p.url.normalize('/$source'); } + +String utf8Decode(Uint8List bytes) { + return utf8.decode(bytes); +} diff --git a/sqlite3/lib/src/platform/platform.dart b/sqlite3/lib/src/platform/platform.dart new file mode 100644 index 00000000..e28950d2 --- /dev/null +++ b/sqlite3/lib/src/platform/platform.dart @@ -0,0 +1 @@ +export 'fallback.dart' if (dart.library.js_interop) 'web.dart'; diff --git a/sqlite3/lib/src/platform/web.dart b/sqlite3/lib/src/platform/web.dart index 9863a1c8..10981753 100644 --- a/sqlite3/lib/src/platform/web.dart +++ b/sqlite3/lib/src/platform/web.dart @@ -4,8 +4,13 @@ /// In compiled web workers, Dart's [Uri.parse] implementation takes up a good /// chunk of the total file size. By using [URL] directly (which is good enough /// for our use case), we can avoid that implementation. +/// +/// Additionally, we can avoid Dart's utf-8 decoder. library; +import 'dart:js_interop'; +import 'dart:typed_data'; + import 'package:web/web.dart'; String pathToAbsoluteAndNormalize(String source) { @@ -15,3 +20,9 @@ String pathToAbsoluteAndNormalize(String source) { Iterable pathComponents(String path) { return URL(path, 'file:///').pathname.split('/').where((e) => e.isNotEmpty); } + +String utf8Decode(Uint8List bytes) { + return _decoder.decode(bytes.toJS); +} + +final _decoder = TextDecoder(); diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart index 5c02d76a..fc0955f7 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import '../../../platform/platform.dart'; import '../../js_interop.dart'; const protocolVersion = 1; @@ -121,7 +122,7 @@ class MessageSerializer { String _readString(int offset) { final length = dataView.getInt32(offset); - return utf8.decode(buffer.asUint8ListSlice(offset + 4, length)); + return utf8Decode(buffer.asUint8ListSlice(offset + 4, length)); } void _writeString(int offset, String data) { diff --git a/sqlite3/lib/src/wasm/wasm_interop.dart b/sqlite3/lib/src/wasm/wasm_interop.dart index b1cdd4d7..2a0a7b01 100644 --- a/sqlite3/lib/src/wasm/wasm_interop.dart +++ b/sqlite3/lib/src/wasm/wasm_interop.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:web/web.dart'; import '../implementation/bindings.dart'; +import '../platform/web.dart'; import 'injected_values.dart'; import 'js_interop.dart'; @@ -475,7 +476,7 @@ extension WrappedMemory on Memory { String readString(int address, [int? length]) { assert(address != 0, 'Null pointer dereference'); - return utf8.decode( + return utf8Decode( dartBuffer.asUint8List(address, length ?? strlen(address)), ); } @@ -483,7 +484,7 @@ extension WrappedMemory on Memory { String? readNullableString(int address, [int? length]) { if (address == 0) return null; - return utf8.decode( + return utf8Decode( dartBuffer.asUint8List(address, length ?? strlen(address)), ); } From fb89febad2deb0efe0a4e2e0b7269543d21348f8 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 16:43:51 +0200 Subject: [PATCH 06/12] Remove utf8 encoder --- sqlite3/lib/src/implementation/database.dart | 5 ++--- sqlite3/lib/src/jsonb.dart | 6 +++--- sqlite3/lib/src/platform/fallback.dart | 4 ++++ sqlite3/lib/src/platform/web.dart | 5 +++++ sqlite3/lib/src/wasm/bindings.dart | 8 ++++---- sqlite3/lib/src/wasm/injected_values.dart | 4 ++-- sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart | 3 +-- sqlite3/lib/src/wasm/wasm_interop.dart | 3 +-- 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/sqlite3/lib/src/implementation/database.dart b/sqlite3/lib/src/implementation/database.dart index 4f017540..0f262ce8 100644 --- a/sqlite3/lib/src/implementation/database.dart +++ b/sqlite3/lib/src/implementation/database.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:convert'; import 'dart:typed_data'; import 'package:meta/meta.dart'; @@ -125,7 +124,7 @@ base class DatabaseImplementation implements CommonDatabase { } Uint8List _validateAndEncodeFunctionName(String functionName) { - final functionNameBytes = utf8.encode(functionName); + final functionNameBytes = utf8Encode(functionName); if (functionNameBytes.length > 255) { throw ArgumentError.value( @@ -349,7 +348,7 @@ base class DatabaseImplementation implements CommonDatabase { }) { _ensureOpen(); - final bytes = utf8.encode(sql); + final bytes = utf8Encode(sql); final compiler = database.newCompiler(bytes); var prepFlags = 0; diff --git a/sqlite3/lib/src/jsonb.dart b/sqlite3/lib/src/jsonb.dart index 32d38503..e3b09924 100644 --- a/sqlite3/lib/src/jsonb.dart +++ b/sqlite3/lib/src/jsonb.dart @@ -307,7 +307,7 @@ final class _JsonbEncodingOperation { } void writeInt(int value) { - final encoded = utf8.encode(value.toString()); + final encoded = utf8Encode(value.toString()); writeHeader(encoded.length, _ElementType._int); _buffer.addAll(encoded); } @@ -319,7 +319,7 @@ final class _JsonbEncodingOperation { throw JsonUnsupportedObjectError(value); } - final encoded = utf8.encode(value.toString()); + final encoded = utf8Encode(value.toString()); // RFC 8259 does not support infinity or NaN. writeHeader( encoded.length, @@ -329,7 +329,7 @@ final class _JsonbEncodingOperation { } void writeString(String value) { - final encoded = utf8.encode(value); + final encoded = utf8Encode(value); writeHeader(encoded.length, _ElementType._textraw); _buffer.addAll(encoded); } diff --git a/sqlite3/lib/src/platform/fallback.dart b/sqlite3/lib/src/platform/fallback.dart index ad6e8eb7..cb9fbc00 100644 --- a/sqlite3/lib/src/platform/fallback.dart +++ b/sqlite3/lib/src/platform/fallback.dart @@ -10,3 +10,7 @@ String pathToAbsoluteAndNormalize(String source) { String utf8Decode(Uint8List bytes) { return utf8.decode(bytes); } + +Uint8List utf8Encode(String str) { + return utf8.encode(str); +} diff --git a/sqlite3/lib/src/platform/web.dart b/sqlite3/lib/src/platform/web.dart index 10981753..dee29c21 100644 --- a/sqlite3/lib/src/platform/web.dart +++ b/sqlite3/lib/src/platform/web.dart @@ -25,4 +25,9 @@ String utf8Decode(Uint8List bytes) { return _decoder.decode(bytes.toJS); } +Uint8List utf8Encode(String str) { + return _encoder.encode(str).toDart; +} + final _decoder = TextDecoder(); +final _encoder = TextEncoder(); diff --git a/sqlite3/lib/src/wasm/bindings.dart b/sqlite3/lib/src/wasm/bindings.dart index 23609cf6..b73c1b53 100644 --- a/sqlite3/lib/src/wasm/bindings.dart +++ b/sqlite3/lib/src/wasm/bindings.dart @@ -1,5 +1,4 @@ import 'dart:collection'; -import 'dart:convert'; import 'dart:js_interop'; import 'dart:typed_data'; @@ -9,6 +8,7 @@ import '../constants.dart'; import '../functions.dart'; import '../implementation/bindings.dart'; import '../implementation/exception.dart'; +import '../platform/web.dart'; import 'injected_values.dart'; import 'wasm_interop.dart' as wasm; import 'sqlite3_wasm.g.dart'; @@ -508,7 +508,7 @@ final class WasmStatement implements RawSqliteStatement { @override int sqlite3_bind_text(int index, String value) { - final encoded = utf8.encode(value); + final encoded = utf8Encode(value); final ptr = bindings.allocateBytes(encoded); return bindings.sqlite3_bind_text_finalizerFree( @@ -654,7 +654,7 @@ final class WasmContext implements RawSqliteContext { @override void sqlite3_result_error(String message) { - final encoded = utf8.encode(message); + final encoded = utf8Encode(message); final ptr = bindings.allocateBytes(encoded); bindings.sqlite3_result_error(context, ptr, encoded.length); @@ -678,7 +678,7 @@ final class WasmContext implements RawSqliteContext { @override void sqlite3_result_text(String text) { - final encoded = utf8.encode(text); + final encoded = utf8Encode(text); final ptr = bindings.allocateBytes(encoded); bindings.sqlite3_result_text( diff --git a/sqlite3/lib/src/wasm/injected_values.dart b/sqlite3/lib/src/wasm/injected_values.dart index e76e12d9..5ce7ab03 100644 --- a/sqlite3/lib/src/wasm/injected_values.dart +++ b/sqlite3/lib/src/wasm/injected_values.dart @@ -1,7 +1,6 @@ // Dart functions that are injected into the SQLite WebAssembly module. For // details, see sqlite3_wasm_build/bridge.h -import 'dart:convert'; import 'dart:js_interop'; import 'package:web/web.dart'; @@ -9,6 +8,7 @@ import 'package:web/web.dart'; import '../constants.dart'; import '../functions.dart'; import '../implementation/bindings.dart'; +import '../platform/web.dart'; import '../vfs.dart'; import 'bindings.dart'; import 'js_interop.dart'; @@ -125,7 +125,7 @@ final class DartBridgeCallbacks { return _runVfs(() { final fullPath = vfs.toDartObject.xFullPathName(path); - final encoded = utf8.encode(fullPath); + final encoded = utf8Encode(fullPath); if (encoded.length > nOut) { throw VfsException(SqlError.SQLITE_CANTOPEN); diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart index fc0955f7..2510a78d 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:typed_data'; import '../../../platform/platform.dart'; @@ -126,7 +125,7 @@ class MessageSerializer { } void _writeString(int offset, String data) { - final encoded = utf8.encode(data); + final encoded = utf8Encode(data); dataView.setInt32(offset, encoded.length); byteView.setAll(offset + 4, encoded); } diff --git a/sqlite3/lib/src/wasm/wasm_interop.dart b/sqlite3/lib/src/wasm/wasm_interop.dart index 2a0a7b01..97059c3e 100644 --- a/sqlite3/lib/src/wasm/wasm_interop.dart +++ b/sqlite3/lib/src/wasm/wasm_interop.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:typed_data'; @@ -53,7 +52,7 @@ class WasmBindings { } Pointer allocateZeroTerminated(String string) { - return allocateBytes(utf8.encode(string), additionalLength: 1); + return allocateBytes(utf8Encode(string), additionalLength: 1); } Pointer malloc(int size) { From 0536aa9071ed9054354efb0f8ccfcb7efac66638 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 17:37:19 +0200 Subject: [PATCH 07/12] Fix decoding strings from shared array buffers --- sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart index 2510a78d..e9d9d228 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart @@ -121,7 +121,12 @@ class MessageSerializer { String _readString(int offset) { final length = dataView.getInt32(offset); - return utf8Decode(buffer.asUint8ListSlice(offset + 4, length)); + // Browsers don't allow TextDecoder.decode on shared array buffers + // (https://github.com/whatwg/encoding/issues/172). So, we copy. + final originalSlice = buffer.asUint8ListSlice(offset + 4, length); + final copy = Uint8List.fromList(originalSlice); + + return utf8Decode(copy); } void _writeString(int offset, String data) { From fe7e4066213bfb923739f83c6cf98b7c5acf65b9 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 17:45:02 +0200 Subject: [PATCH 08/12] Listen on stderr/stdout of web drivers --- sqlite3_web/test/integration_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sqlite3_web/test/integration_test.dart b/sqlite3_web/test/integration_test.dart index 96a573ab..6bbe2bfb 100644 --- a/sqlite3_web/test/integration_test.dart +++ b/sqlite3_web/test/integration_test.dart @@ -83,6 +83,12 @@ void main() { setUpAll(() async { final process = driverProcess = await browser.spawnDriver(); + + // On macOS, geckodriver seems to time out if there's no consumer on + // these. + process.stderr.listen((_) {}); + process.stdout.listen((_) {}); + process.exitCode.then((code) { if (!isStoppingProcess) { throw 'Webdriver stopped (code $code) before tearing down tests.'; From b3c011df3e995d34dfac3f6095843400817c2764 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 18:36:05 +0200 Subject: [PATCH 09/12] Avoid dartify/jsify --- sqlite3/lib/src/constants.dart | 3 --- sqlite3/lib/src/implementation/utils.dart | 3 +++ sqlite3_web/CHANGELOG.md | 2 ++ sqlite3_web/lib/src/protocol/extensions.dart | 13 ++++--------- sqlite3_web/lib/src/protocol/messages.dart | 7 +++---- sqlite3_web/test/protocol_test.dart | 12 +----------- 6 files changed, 13 insertions(+), 27 deletions(-) diff --git a/sqlite3/lib/src/constants.dart b/sqlite3/lib/src/constants.dart index 77b8069e..2addb14e 100644 --- a/sqlite3/lib/src/constants.dart +++ b/sqlite3/lib/src/constants.dart @@ -544,9 +544,6 @@ const SQLITE_DELETE = 9; const SQLITE_INSERT = 18; const SQLITE_UPDATE = 23; -final bigIntMinValue64 = BigInt.parse('-9223372036854775808'); -final bigIntMaxValue64 = BigInt.parse('9223372036854775807'); - // Connection config options https://www.sqlite.org/c3ref/c_dbconfig_defensive.html const SQLITE_DBCONFIG_DQS_DML = 1013; const SQLITE_DBCONFIG_DQS_DDL = 1014; diff --git a/sqlite3/lib/src/implementation/utils.dart b/sqlite3/lib/src/implementation/utils.dart index d62229f8..6b6cfe3f 100644 --- a/sqlite3/lib/src/implementation/utils.dart +++ b/sqlite3/lib/src/implementation/utils.dart @@ -8,6 +8,9 @@ extension BigIntRangeCheck on BigInt { } return this; } + + static final bigIntMinValue64 = -(BigInt.one << 63); + static final bigIntMaxValue64 = (BigInt.one << 63) - BigInt.one; } int eTextRep(bool deterministic, bool directOnly, bool subtype) { diff --git a/sqlite3_web/CHANGELOG.md b/sqlite3_web/CHANGELOG.md index c260616a..c8b32b53 100644 --- a/sqlite3_web/CHANGELOG.md +++ b/sqlite3_web/CHANGELOG.md @@ -2,6 +2,8 @@ - Remove `stream_channel` dependency with custom implementation, slightly decreasing compiled size. - Use strings instead of URLs to reduce code size. +- Custom Dart objects can no longer be serialized via `serializeParameters` and `serializeResultSet`. + Only SQLite values are supported. ## 0.7.1 diff --git a/sqlite3_web/lib/src/protocol/extensions.dart b/sqlite3_web/lib/src/protocol/extensions.dart index 3f0a1a0c..15a3fcaa 100644 --- a/sqlite3_web/lib/src/protocol/extensions.dart +++ b/sqlite3_web/lib/src/protocol/extensions.dart @@ -146,14 +146,14 @@ extension RowsResponseUtils on RowsResponse { required bool autoCommit, required int lastInsertRowId, }) { - final jsRows = []; + final jsRows = JSArray>.withLength(resultSet.length); final columns = resultSet.columnNames.length; final typeVector = Uint8List(resultSet.length * columns); for (var i = 0; i < resultSet.length; i++) { final row = resultSet.rows[i]; assert(row.length == columns); - final jsRow = List.filled(row.length, null); + final jsRow = JSArray.withLength(row.length); for (var j = 0; j < columns; j++) { final (code, value) = TypeCode.encodeValue(row[j]); @@ -162,14 +162,9 @@ extension RowsResponseUtils on RowsResponse { typeVector[i * columns + j] = code.index; } - jsRows.add(jsRow.toJS); + jsRows[i] = jsRow; } - final rows = [ - for (final row in resultSet.rows) - [for (final column in row) column.jsify()].toJS, - ].toJS; - JSArray? tableNames; if (resultSet.tableNames case var dartTableNames?) { tableNames = [ @@ -183,7 +178,7 @@ extension RowsResponseUtils on RowsResponse { ].toJS, tableNames: tableNames, typeVector: typeVector.buffer.toJS, - rows: rows, + rows: jsRows, autoCommit: autoCommit, lastInsertRowId: lastInsertRowId, requestId: requestId, diff --git a/sqlite3_web/lib/src/protocol/messages.dart b/sqlite3_web/lib/src/protocol/messages.dart index 6527c0f7..c6c149c7 100644 --- a/sqlite3_web/lib/src/protocol/messages.dart +++ b/sqlite3_web/lib/src/protocol/messages.dart @@ -208,7 +208,7 @@ enum TypeCode { const hasNativeInts = !identical(0, 0.0); return switch (this) { - TypeCode.unknown => column.dartify(), + TypeCode.unknown => throw ArgumentError('Unsupported type code'), TypeCode.integer => (column as JSNumber).toDartInt, TypeCode.bigInt => hasNativeInts @@ -257,9 +257,8 @@ enum TypeCode { case final bool boolean: value = boolean.toJS; code = TypeCode.boolean; - case final other: - value = other.jsify(); - code = TypeCode.unknown; + default: + throw ArgumentError('Unsupported value: $dart'); } return (code, value); diff --git a/sqlite3_web/test/protocol_test.dart b/sqlite3_web/test/protocol_test.dart index 26a9eabf..834458a2 100644 --- a/sqlite3_web/test/protocol_test.dart +++ b/sqlite3_web/test/protocol_test.dart @@ -30,15 +30,7 @@ void main() { group('TypeCode', () { test('is compatible with dartify()', () { - for (final value in [ - 1, - 3.4, - true, - null, - {'custom': 'object'}, - 'string', - Uint8List(10), - ]) { + for (final value in [1, 3.4, true, null, 'string', Uint8List(10)]) { final (_, jsified) = TypeCode.encodeValue(value); expect(jsified.dartify(), value); } @@ -59,7 +51,6 @@ void main() { isA().having((e) => e.length, 'length', 10), isDart2Wasm ? 100 : BigInt.from(100), null, - {'custom': 'object'}, ]); if (isDart2Wasm) { // Make sure we don't loose type information in the js conversion across @@ -83,7 +74,6 @@ void main() { Uint8List(10), BigInt.from(100), null, - {'custom': 'object'}, ]); await client.sendRequest( newRunQuery( From 36a13198d79e049f36a73316ae681bd5def5505f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 19:03:24 +0200 Subject: [PATCH 10/12] Allow disabling bigints --- sqlite3/lib/src/compile_options.dart | 15 +++++++++++++++ sqlite3/lib/src/implementation/statement.dart | 6 +++++- sqlite3/lib/src/platform/web.dart | 17 +++++++++++++++++ sqlite3/lib/src/wasm/bindings.dart | 7 ++++++- sqlite3/lib/src/wasm/js_interop/core.dart | 11 ++++++++++- sqlite3/lib/src/wasm/wasm_interop.dart | 8 ++------ sqlite3/pubspec.yaml | 2 +- sqlite3_web/build.yaml | 4 +++- sqlite3_web/lib/src/protocol/messages.dart | 9 ++++++++- sqlite3_web/pubspec.yaml | 2 +- 10 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 sqlite3/lib/src/compile_options.dart diff --git a/sqlite3/lib/src/compile_options.dart b/sqlite3/lib/src/compile_options.dart new file mode 100644 index 00000000..b795bfba --- /dev/null +++ b/sqlite3/lib/src/compile_options.dart @@ -0,0 +1,15 @@ +/// Whether to support binding [BigInt] values. +/// +/// This is enabled by default, and disabling it is experimental. This is mainly +/// relevant for web workers, which don't really benefit from using [BigInt] +/// values internally: +/// +/// - Over the wire, we receive values as `JSBigInt`. +/// - To bind values to SQLite, we need to use `JSBigInt` as well. +/// +/// By removing support for [BigInt]s and their conversion to `JSBigInt`, we +/// save around 20kb from the default worker with `dart compile js -O4`. +const supportDartBigInts = bool.fromEnvironment( + 'sqlite3.dartbigints', + defaultValue: true, +); diff --git a/sqlite3/lib/src/implementation/statement.dart b/sqlite3/lib/src/implementation/statement.dart index 9c65e51f..0cceabaf 100644 --- a/sqlite3/lib/src/implementation/statement.dart +++ b/sqlite3/lib/src/implementation/statement.dart @@ -1,3 +1,4 @@ +import '../compile_options.dart'; import '../constants.dart'; import '../result_set.dart'; import '../statement.dart'; @@ -210,7 +211,10 @@ base class StatementImplementation extends CommonPreparedStatement { final rc = switch (param) { null => statement.sqlite3_bind_null(i), int() => statement.sqlite3_bind_int64(i, param), - BigInt() => statement.sqlite3_bind_int64BigInt(i, param.checkRange), + BigInt() when supportDartBigInts => statement.sqlite3_bind_int64BigInt( + i, + param.checkRange, + ), bool() => statement.sqlite3_bind_int64(i, param ? 1 : 0), double() => statement.sqlite3_bind_double(i, param), String() => statement.sqlite3_bind_text(i, param), diff --git a/sqlite3/lib/src/platform/web.dart b/sqlite3/lib/src/platform/web.dart index dee29c21..b999026f 100644 --- a/sqlite3/lib/src/platform/web.dart +++ b/sqlite3/lib/src/platform/web.dart @@ -13,6 +13,10 @@ import 'dart:typed_data'; import 'package:web/web.dart'; +import '../implementation/statement.dart'; +import '../statement.dart'; +import '../wasm/bindings.dart'; + String pathToAbsoluteAndNormalize(String source) { return URL(source, 'file:///').pathname; } @@ -31,3 +35,16 @@ Uint8List utf8Encode(String str) { final _decoder = TextDecoder(); final _encoder = TextEncoder(); + +final class BoxedJavaScriptBigInt implements CustomStatementParameter { + final JSBigInt value; + + BoxedJavaScriptBigInt(this.value); + + @override + void applyTo(CommonPreparedStatement statement, int index) { + final raw = + (statement as StatementImplementation).statement as WasmStatement; + raw.bindings.sqlite3_bind_int64(raw.stmt, index, value); + } +} diff --git a/sqlite3/lib/src/wasm/bindings.dart b/sqlite3/lib/src/wasm/bindings.dart index b73c1b53..3aac0486 100644 --- a/sqlite3/lib/src/wasm/bindings.dart +++ b/sqlite3/lib/src/wasm/bindings.dart @@ -10,6 +10,7 @@ import '../implementation/bindings.dart'; import '../implementation/exception.dart'; import '../platform/web.dart'; import 'injected_values.dart'; +import 'js_interop/core.dart'; import 'wasm_interop.dart' as wasm; import 'sqlite3_wasm.g.dart'; import 'wasm_interop.dart'; @@ -473,7 +474,11 @@ final class WasmStatement implements RawSqliteStatement { @override int sqlite3_bind_int64BigInt(int index, BigInt value) { - return bindings.sqlite3_bind_int64(stmt, index, value); + return bindings.sqlite3_bind_int64( + stmt, + index, + JsBigInt.fromBigInt(value).jsObject, + ); } @override diff --git a/sqlite3/lib/src/wasm/js_interop/core.dart b/sqlite3/lib/src/wasm/js_interop/core.dart index 9285547d..861faf3d 100644 --- a/sqlite3/lib/src/wasm/js_interop/core.dart +++ b/sqlite3/lib/src/wasm/js_interop/core.dart @@ -2,6 +2,9 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; +import '../../compile_options.dart'; +import '../../platform/web.dart'; + @JS('BigInt') external JSBigInt _bigInt(JSAny? s); @@ -27,7 +30,13 @@ extension type JsBigInt(JSBigInt _jsBigInt) implements JSBigInt { int get asDartInt => _number(_jsBigInt).toDartInt; - BigInt get asDartBigInt => BigInt.parse(jsToString()); + Object get asDartBigInt { + if (!supportDartBigInts) { + return BoxedJavaScriptBigInt(this); + } + + return BigInt.parse(jsToString()); + } JSBigInt get jsObject => _jsBigInt; diff --git a/sqlite3/lib/src/wasm/wasm_interop.dart b/sqlite3/lib/src/wasm/wasm_interop.dart index 97059c3e..659413ed 100644 --- a/sqlite3/lib/src/wasm/wasm_interop.dart +++ b/sqlite3/lib/src/wasm/wasm_interop.dart @@ -164,12 +164,8 @@ class WasmBindings { return sqlite3.sqlite3_bind_null(stmt, index); } - int sqlite3_bind_int64(Pointer stmt, int index, BigInt value) { - return sqlite3.sqlite3_bind_int64( - stmt, - index, - JsBigInt.fromBigInt(value).jsObject, - ); + int sqlite3_bind_int64(Pointer stmt, int index, JSBigInt value) { + return sqlite3.sqlite3_bind_int64(stmt, index, value); } int sqlite3_bind_int(Pointer stmt, int index, int value) { diff --git a/sqlite3/pubspec.yaml b/sqlite3/pubspec.yaml index dab25c83..f20cba06 100644 --- a/sqlite3/pubspec.yaml +++ b/sqlite3/pubspec.yaml @@ -1,6 +1,6 @@ name: sqlite3 description: Provides lightweight yet convenient bindings to SQLite by using dart:ffi -version: 3.3.2-wip +version: 3.3.2 homepage: https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3 issue_tracker: https://github.com/simolus3/sqlite3.dart/issues resolution: workspace diff --git a/sqlite3_web/build.yaml b/sqlite3_web/build.yaml index 4b223f6b..3cf86c28 100644 --- a/sqlite3_web/build.yaml +++ b/sqlite3_web/build.yaml @@ -24,7 +24,9 @@ targets: generate_for: - "**/worker.dart" options: - compiler: dart2js + compilers: + dart2js: + args: [-O4, -Dsqlite3.dartbigints=false] build_web_compilers:dart2js_archive_extractor: enabled: false diff --git a/sqlite3_web/lib/src/protocol/messages.dart b/sqlite3_web/lib/src/protocol/messages.dart index c6c149c7..d7ea12b6 100644 --- a/sqlite3_web/lib/src/protocol/messages.dart +++ b/sqlite3_web/lib/src/protocol/messages.dart @@ -4,6 +4,10 @@ import 'dart:typed_data'; import 'package:sqlite3/wasm.dart'; // ignore: implementation_imports import 'package:sqlite3/src/wasm/js_interop/core.dart'; +// ignore: implementation_imports +import 'package:sqlite3/src/platform/web.dart'; +// ignore: implementation_imports +import 'package:sqlite3/src/compile_options.dart'; import '../channel.dart'; import 'dsl.dart'; @@ -242,9 +246,12 @@ enum TypeCode { case final int integer: value = integer.toJS; code = TypeCode.integer; - case final BigInt bi: + case final BigInt bi when supportDartBigInts: value = JsBigInt.fromBigInt(bi); code = TypeCode.bigInt; + case final BoxedJavaScriptBigInt bi when !supportDartBigInts: + value = bi.value; + code = TypeCode.bigInt; case final double d: value = d.toJS; code = TypeCode.float; diff --git a/sqlite3_web/pubspec.yaml b/sqlite3_web/pubspec.yaml index 9ce43596..716ac495 100644 --- a/sqlite3_web/pubspec.yaml +++ b/sqlite3_web/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: meta: ^1.11.0 - sqlite3: ^3.0.0 + sqlite3: ^3.3.2 web: ^1.0.0 dev_dependencies: From c77a750957fc47f573b18ff092754f6655fc3cf4 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 19:52:38 +0200 Subject: [PATCH 11/12] Revert utf-8 changes --- sqlite3/CHANGELOG.md | 2 ++ sqlite3/README.md | 4 +++- sqlite3/lib/src/implementation/database.dart | 8 ++++---- sqlite3/lib/src/jsonb.dart | 10 ++++------ sqlite3/lib/src/platform/fallback.dart | 11 ----------- sqlite3/lib/src/platform/web.dart | 14 -------------- sqlite3/lib/src/wasm/bindings.dart | 8 ++++---- sqlite3/lib/src/wasm/injected_values.dart | 4 ++-- .../lib/src/wasm/vfs/async_opfs/sync_channel.dart | 6 +++--- sqlite3/lib/src/wasm/wasm_interop.dart | 8 ++++---- 10 files changed, 26 insertions(+), 49 deletions(-) diff --git a/sqlite3/CHANGELOG.md b/sqlite3/CHANGELOG.md index a0c25052..92417ee9 100644 --- a/sqlite3/CHANGELOG.md +++ b/sqlite3/CHANGELOG.md @@ -2,6 +2,8 @@ - Support `native_toolchain_c` versions `0.18.x`. - Upgrade to SQLite version 3.53.1. +- Allow disabling `BigInt` support via `-Dsqlite3.dartbigints=false`, which can be used to + reduce code size on the web if nothing else uses `BigInt` values. ## 3.3.1 diff --git a/sqlite3/README.md b/sqlite3/README.md index 6f3636c2..30da44ae 100644 --- a/sqlite3/README.md +++ b/sqlite3/README.md @@ -51,7 +51,9 @@ databases. The [hook options page](./doc/hook.md) describe this setup. When binding parameters to queries, the supported types are `ìnt`, `double`, `String`, `List` (for `BLOB`) and `null`. Result sets will use the same set of types. -On the web (but only on the web), `BigInt` is supported as well. + +On the web (when compiled with `dart2js`), `BigInt` is supported as well to represent 64bit integers. +Support for this can be disabled with `-Dsqlite3.dartbigints=false`. ## WASM (web support) diff --git a/sqlite3/lib/src/implementation/database.dart b/sqlite3/lib/src/implementation/database.dart index 0f262ce8..2501ad06 100644 --- a/sqlite3/lib/src/implementation/database.dart +++ b/sqlite3/lib/src/implementation/database.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:convert'; import 'dart:typed_data'; import 'package:meta/meta.dart'; @@ -8,7 +9,6 @@ import '../constants.dart'; import '../database.dart'; import '../exception.dart'; import '../functions.dart'; -import '../platform/platform.dart'; import '../result_set.dart'; import '../statement.dart'; import 'bindings.dart'; @@ -124,7 +124,7 @@ base class DatabaseImplementation implements CommonDatabase { } Uint8List _validateAndEncodeFunctionName(String functionName) { - final functionNameBytes = utf8Encode(functionName); + final functionNameBytes = utf8.encode(functionName); if (functionNameBytes.length > 255) { throw ArgumentError.value( @@ -348,7 +348,7 @@ base class DatabaseImplementation implements CommonDatabase { }) { _ensureOpen(); - final bytes = utf8Encode(sql); + final bytes = utf8.encode(sql); final compiler = database.newCompiler(bytes); var prepFlags = 0; @@ -393,7 +393,7 @@ base class DatabaseImplementation implements CommonDatabase { // or comments were parsed. That's fine, just skip over it then. final stmt = result.result; if (stmt != null) { - final stmtSql = utf8Decode( + final stmtSql = utf8.decode( Uint8List.sublistView(bytes, offset, endOffset), ); diff --git a/sqlite3/lib/src/jsonb.dart b/sqlite3/lib/src/jsonb.dart index e3b09924..fec53af8 100644 --- a/sqlite3/lib/src/jsonb.dart +++ b/sqlite3/lib/src/jsonb.dart @@ -3,8 +3,6 @@ import 'dart:typed_data'; import 'package:typed_data/typed_buffers.dart'; -import 'platform/platform.dart'; - /// A [Codec] capable of converting Dart objects from and to the [JSONB] format /// used by sqlite3. /// @@ -178,7 +176,7 @@ final class _JsonbDecodingState { } String payloadString() { - return utf8Decode(payloadBytes()); + return utf8.decode(payloadBytes()); } final value = switch (type) { @@ -307,7 +305,7 @@ final class _JsonbEncodingOperation { } void writeInt(int value) { - final encoded = utf8Encode(value.toString()); + final encoded = utf8.encode(value.toString()); writeHeader(encoded.length, _ElementType._int); _buffer.addAll(encoded); } @@ -319,7 +317,7 @@ final class _JsonbEncodingOperation { throw JsonUnsupportedObjectError(value); } - final encoded = utf8Encode(value.toString()); + final encoded = utf8.encode(value.toString()); // RFC 8259 does not support infinity or NaN. writeHeader( encoded.length, @@ -329,7 +327,7 @@ final class _JsonbEncodingOperation { } void writeString(String value) { - final encoded = utf8Encode(value); + final encoded = utf8.encode(value); writeHeader(encoded.length, _ElementType._textraw); _buffer.addAll(encoded); } diff --git a/sqlite3/lib/src/platform/fallback.dart b/sqlite3/lib/src/platform/fallback.dart index cb9fbc00..20fcb5e2 100644 --- a/sqlite3/lib/src/platform/fallback.dart +++ b/sqlite3/lib/src/platform/fallback.dart @@ -1,16 +1,5 @@ -import 'dart:convert'; -import 'dart:typed_data'; - import 'package:path/path.dart' as p; String pathToAbsoluteAndNormalize(String source) { return p.url.normalize('/$source'); } - -String utf8Decode(Uint8List bytes) { - return utf8.decode(bytes); -} - -Uint8List utf8Encode(String str) { - return utf8.encode(str); -} diff --git a/sqlite3/lib/src/platform/web.dart b/sqlite3/lib/src/platform/web.dart index b999026f..6c9db275 100644 --- a/sqlite3/lib/src/platform/web.dart +++ b/sqlite3/lib/src/platform/web.dart @@ -4,12 +4,9 @@ /// In compiled web workers, Dart's [Uri.parse] implementation takes up a good /// chunk of the total file size. By using [URL] directly (which is good enough /// for our use case), we can avoid that implementation. -/// -/// Additionally, we can avoid Dart's utf-8 decoder. library; import 'dart:js_interop'; -import 'dart:typed_data'; import 'package:web/web.dart'; @@ -25,17 +22,6 @@ Iterable pathComponents(String path) { return URL(path, 'file:///').pathname.split('/').where((e) => e.isNotEmpty); } -String utf8Decode(Uint8List bytes) { - return _decoder.decode(bytes.toJS); -} - -Uint8List utf8Encode(String str) { - return _encoder.encode(str).toDart; -} - -final _decoder = TextDecoder(); -final _encoder = TextEncoder(); - final class BoxedJavaScriptBigInt implements CustomStatementParameter { final JSBigInt value; diff --git a/sqlite3/lib/src/wasm/bindings.dart b/sqlite3/lib/src/wasm/bindings.dart index 3aac0486..af62ef2d 100644 --- a/sqlite3/lib/src/wasm/bindings.dart +++ b/sqlite3/lib/src/wasm/bindings.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:convert'; import 'dart:js_interop'; import 'dart:typed_data'; @@ -8,7 +9,6 @@ import '../constants.dart'; import '../functions.dart'; import '../implementation/bindings.dart'; import '../implementation/exception.dart'; -import '../platform/web.dart'; import 'injected_values.dart'; import 'js_interop/core.dart'; import 'wasm_interop.dart' as wasm; @@ -513,7 +513,7 @@ final class WasmStatement implements RawSqliteStatement { @override int sqlite3_bind_text(int index, String value) { - final encoded = utf8Encode(value); + final encoded = utf8.encode(value); final ptr = bindings.allocateBytes(encoded); return bindings.sqlite3_bind_text_finalizerFree( @@ -659,7 +659,7 @@ final class WasmContext implements RawSqliteContext { @override void sqlite3_result_error(String message) { - final encoded = utf8Encode(message); + final encoded = utf8.encode(message); final ptr = bindings.allocateBytes(encoded); bindings.sqlite3_result_error(context, ptr, encoded.length); @@ -683,7 +683,7 @@ final class WasmContext implements RawSqliteContext { @override void sqlite3_result_text(String text) { - final encoded = utf8Encode(text); + final encoded = utf8.encode(text); final ptr = bindings.allocateBytes(encoded); bindings.sqlite3_result_text( diff --git a/sqlite3/lib/src/wasm/injected_values.dart b/sqlite3/lib/src/wasm/injected_values.dart index 5ce7ab03..e76e12d9 100644 --- a/sqlite3/lib/src/wasm/injected_values.dart +++ b/sqlite3/lib/src/wasm/injected_values.dart @@ -1,6 +1,7 @@ // Dart functions that are injected into the SQLite WebAssembly module. For // details, see sqlite3_wasm_build/bridge.h +import 'dart:convert'; import 'dart:js_interop'; import 'package:web/web.dart'; @@ -8,7 +9,6 @@ import 'package:web/web.dart'; import '../constants.dart'; import '../functions.dart'; import '../implementation/bindings.dart'; -import '../platform/web.dart'; import '../vfs.dart'; import 'bindings.dart'; import 'js_interop.dart'; @@ -125,7 +125,7 @@ final class DartBridgeCallbacks { return _runVfs(() { final fullPath = vfs.toDartObject.xFullPathName(path); - final encoded = utf8Encode(fullPath); + final encoded = utf8.encode(fullPath); if (encoded.length > nOut) { throw VfsException(SqlError.SQLITE_CANTOPEN); diff --git a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart index e9d9d228..742127cb 100644 --- a/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart +++ b/sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart @@ -1,6 +1,6 @@ +import 'dart:convert'; import 'dart:typed_data'; -import '../../../platform/platform.dart'; import '../../js_interop.dart'; const protocolVersion = 1; @@ -126,11 +126,11 @@ class MessageSerializer { final originalSlice = buffer.asUint8ListSlice(offset + 4, length); final copy = Uint8List.fromList(originalSlice); - return utf8Decode(copy); + return utf8.decode(copy); } void _writeString(int offset, String data) { - final encoded = utf8Encode(data); + final encoded = utf8.encode(data); dataView.setInt32(offset, encoded.length); byteView.setAll(offset + 4, encoded); } diff --git a/sqlite3/lib/src/wasm/wasm_interop.dart b/sqlite3/lib/src/wasm/wasm_interop.dart index 659413ed..2b3f09f9 100644 --- a/sqlite3/lib/src/wasm/wasm_interop.dart +++ b/sqlite3/lib/src/wasm/wasm_interop.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:typed_data'; @@ -5,7 +6,6 @@ import 'dart:typed_data'; import 'package:web/web.dart'; import '../implementation/bindings.dart'; -import '../platform/web.dart'; import 'injected_values.dart'; import 'js_interop.dart'; @@ -52,7 +52,7 @@ class WasmBindings { } Pointer allocateZeroTerminated(String string) { - return allocateBytes(utf8Encode(string), additionalLength: 1); + return allocateBytes(utf8.encode(string), additionalLength: 1); } Pointer malloc(int size) { @@ -471,7 +471,7 @@ extension WrappedMemory on Memory { String readString(int address, [int? length]) { assert(address != 0, 'Null pointer dereference'); - return utf8Decode( + return utf8.decode( dartBuffer.asUint8List(address, length ?? strlen(address)), ); } @@ -479,7 +479,7 @@ extension WrappedMemory on Memory { String? readNullableString(int address, [int? length]) { if (address == 0) return null; - return utf8Decode( + return utf8.decode( dartBuffer.asUint8List(address, length ?? strlen(address)), ); } From 6bbe5bbcc9722d4078768db13179fbfd74cf664f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 May 2026 19:58:37 +0200 Subject: [PATCH 12/12] Simplify --- sqlite3/lib/src/implementation/database.dart | 4 +--- sqlite3/lib/src/in_memory_vfs.dart | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/sqlite3/lib/src/implementation/database.dart b/sqlite3/lib/src/implementation/database.dart index 2501ad06..02ffcae4 100644 --- a/sqlite3/lib/src/implementation/database.dart +++ b/sqlite3/lib/src/implementation/database.dart @@ -393,9 +393,7 @@ base class DatabaseImplementation implements CommonDatabase { // or comments were parsed. That's fine, just skip over it then. final stmt = result.result; if (stmt != null) { - final stmtSql = utf8.decode( - Uint8List.sublistView(bytes, offset, endOffset), - ); + final stmtSql = utf8.decoder.convert(bytes, offset, endOffset); createdStatements.add(wrapStatement(stmtSql, stmt)); } diff --git a/sqlite3/lib/src/in_memory_vfs.dart b/sqlite3/lib/src/in_memory_vfs.dart index d790a942..b0bf3e28 100644 --- a/sqlite3/lib/src/in_memory_vfs.dart +++ b/sqlite3/lib/src/in_memory_vfs.dart @@ -3,10 +3,8 @@ import 'dart:typed_data'; import 'package:typed_data/typed_buffers.dart'; -import 'platform/fallback.dart' - if (dart.library.js_interop) 'platform/web.dart'; - import 'constants.dart'; +import 'platform/platform.dart'; import 'vfs.dart'; import 'utils.dart';