Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions drift/lib/wasm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ library;

import 'dart:async';
import 'dart:js_interop';
import 'dart:typed_data';

import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:sqlite3/wasm.dart';
import 'package:web/web.dart'
show DedicatedWorkerGlobalScope, SharedWorkerGlobalScope;
import 'package:sqlite3/wasm.dart';

import 'backends.dart';
import 'src/sqlite3/database.dart';
Expand Down Expand Up @@ -138,13 +138,17 @@ class WasmDatabase extends DelegatedDatabase {
/// When [enableMigrations] is set to `false`, drift will not check the
/// `user_version` pragma when opening the database or run migrations.
///
/// If a [preferedImplemetation] is defined and available in the browser the
/// data will automatically be migrated to it.
///
/// For more detailed information, see https://drift.simonbinder.eu/web.
static Future<WasmDatabaseResult> open({
required String databaseName,
required Uri sqlite3Uri,
required Uri driftWorkerUri,
FutureOr<Uint8List?> Function()? initializeDatabase,
WasmDatabaseSetup? localSetup,
WasmStorageImplementation? preferedImplemetation,
bool enableMigrations = true,
}) async {
final probed = await probe(
Expand All @@ -159,6 +163,14 @@ class WasmDatabase extends DelegatedDatabase {
// to consider migrating between storage implementations as well.
final availableImplementations = probed.availableStorages.toList();

// Enum values are ordered by preferrability, so just pick the best option
// left.
availableImplementations.sortBy<num>((element) => element.index);

final preferedAvailable =
availableImplementations.contains(preferedImplemetation);
ExistingDatabase? currentDatabase;

checkExisting:
for (final (location, name) in probed.existingDatabases) {
if (name == databaseName) {
Expand All @@ -178,22 +190,26 @@ class WasmDatabase extends DelegatedDatabase {
if (implementationsForStorage.any(availableImplementations.contains)) {
availableImplementations
.removeWhere((i) => !implementationsForStorage.contains(i));
currentDatabase = (location, name);
break checkExisting;
}
}
}

// Enum values are ordered by preferrability, so just pick the best option
// left.
availableImplementations.sortBy<num>((element) => element.index);

final bestImplementation = availableImplementations.firstOrNull ??
WasmStorageImplementation.inMemory;

final needsMigration = preferedAvailable &&
currentDatabase != null &&
bestImplementation != preferedImplemetation;

final connection = await probed.open(
bestImplementation,
needsMigration ? preferedImplemetation! : bestImplementation,
databaseName,
localSetup: localSetup,
initializeDatabase: initializeDatabase,
initializeDatabase: needsMigration
? () => probed.exportDatabase(currentDatabase!)
: initializeDatabase,
enableMigrations: enableMigrations,
);

Expand Down
2 changes: 2 additions & 0 deletions extras/integration_tests/web_wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ To run the tests automatically (with us managing a browser driver), just run `da
To manually debug issues, it might make sense to trigger some functionality manually.
You can run `dart run tool/serve_manually.dart` to start a web server hosting the test
content on <http://localhost:8080>.

Make sure to install [chromedriver](https://googlechromelabs.github.io/chrome-for-testing) and [geckodriver](https://github.com/mozilla/geckodriver/releases) for the test to use.
15 changes: 9 additions & 6 deletions extras/integration_tests/web_wasm/lib/driver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import 'package:build_daemon/constants.dart';
import 'package:build_daemon/data/build_status.dart';
import 'package:build_daemon/data/build_target.dart';
import 'package:collection/collection.dart';
// ignore: implementation_imports
import 'package:drift/src/web/wasm_setup/types.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import 'package:shelf/shelf_io.dart';
import 'package:shelf_proxy/shelf_proxy.dart';
import 'package:web_wasm/initialization_mode.dart';
import 'package:webdriver/async_io.dart';
// ignore: implementation_imports
import 'package:drift/src/web/wasm_setup/types.dart';
import 'package:webdriver/support/async.dart';

class TestAssetServer {
Expand All @@ -36,7 +36,7 @@ class TestAssetServer {
await loadPackageConfigUri((await Isolate.packageConfig)!);
final ownPackage = packageConfig['web_wasm']!.root;
var packageDir = ownPackage.toFilePath(windows: Platform.isWindows);
if (packageDir.endsWith('/')) {
if (packageDir.endsWith('/') || packageDir.endsWith('\\')) {
packageDir = packageDir.substring(0, packageDir.length - 1);
}

Expand Down Expand Up @@ -146,9 +146,12 @@ class DriftWebDriver {
);
}

Future<void> openDatabase([WasmStorageImplementation? implementation]) async {
await driver.executeAsync(
'open(arguments[0], arguments[1])', [implementation?.name]);
Future<void> openDatabase(
[WasmStorageImplementation? implementation,
WasmStorageImplementation? preferedImplemetation]) async {
await driver.executeAsync('open(arguments[0], arguments[1])', [
json.encode([implementation?.name, preferedImplemetation?.name])
]);
}

Future<void> closeDatabase() async {
Expand Down
23 changes: 23 additions & 0 deletions extras/integration_tests/web_wasm/test/drift_wasm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,29 @@ final class _TestConfiguration {
},
);

test(
'switch from IndexedDB database to OPFS if it is the prefered implementation',
() async {
// Open an IndexedDB database first
await driver.openDatabase(WasmStorageImplementation.unsafeIndexedDb);
await driver.insertIntoDatabase();
await Future.delayed(const Duration(seconds: 2));
await driver.driver.refresh(); // Reset JS state
await driver.waitReady();

// Open the database again, this time specifying opfs as the
// preferred implementation.
await driver.openDatabase(null, WasmStorageImplementation.opfsLocks);
expect(await driver.amountOfRows, 1);
await Future.delayed(const Duration(seconds: 2));
await driver.driver.refresh(); // Reset JS state
await driver.waitReady();

await driver.openDatabase(WasmStorageImplementation.opfsLocks);
expect(await driver.amountOfRows, 1);
},
);

if (!browser.supports(WasmStorageImplementation.opfsShared)) {
test('uses indexeddb after OPFS becomes unavailable', () async {
// This browser only supports OPFS with the right headers. If they
Expand Down
12 changes: 9 additions & 3 deletions extras/integration_tests/web_wasm/web/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import 'package:async/async.dart';
import 'package:drift/drift.dart';
import 'package:drift/wasm.dart';
import 'package:http/http.dart' as http;
import 'package:sqlite3/wasm.dart';
import 'package:web/web.dart'
show document, EventStreamProviders, HTMLDivElement;
import 'package:web_wasm/initialization_mode.dart';
import 'package:web_wasm/src/database.dart';
import 'package:sqlite3/wasm.dart';

const dbName = 'drift_test';
final sqlite3WasmUri = Uri.parse('sqlite3.wasm');
Expand All @@ -32,7 +32,10 @@ void main() {
driftWorkerUri = Uri.parse('wrong.js');
return _detectImplementations(arg);
});
_addCallbackForWebDriver('open', _open);
_addCallbackForWebDriver('open', (arg) {
final decoded = json.decode(arg!);
return _open(decoded[0] as String?, decoded[1] as String?);
});
_addCallbackForWebDriver('close', (arg) async {
await tableUpdates?.cancel();
await openedDatabase?.close();
Expand Down Expand Up @@ -178,7 +181,8 @@ Future<JSString> _detectImplementations(String? _) async {
}).toJS;
}

Future<JSAny?> _open(String? implementationName) async {
Future<JSAny?> _open(
String? implementationName, String? preferedImplemetationName) async {
DatabaseConnection connection;

if (implementationName != null) {
Expand All @@ -201,6 +205,8 @@ Future<JSAny?> _open(String? implementationName) async {
initializeDatabase: _initializeDatabase,
enableMigrations:
initializationMode != InitializationMode.noneAndDisableMigrations,
preferedImplemetation: WasmStorageImplementation.values
.asNameMap()[preferedImplemetationName],
localSetup: (db) {
// The worker has a similar setup call that will make database_host
// return `worker` instead.
Expand Down
Loading