We see log "initialize drift database" end everything works fine.
Actual results
We see log "initialize drift database" and error:
main.dart.wasm:0x149921 Uncaught
RuntimeError: illegal cast
at DriftCommunication._handleMessage (main.dart.wasm:0x149921)
at DriftCommunication._handleMessage tear-off trampoline (main.dart.wasm:0x149b3c)
at _RootZone.runUnaryGuarded (main.dart.wasm:0xd475e)
at _BufferingStreamSubscription._sendData (main.dart.wasm:0xd87a6)
at _BufferingStreamSubscription._add (main.dart.wasm:0xd8974)
at _SyncStreamController._sendData (main.dart.wasm:0x14fe16)
at _StreamController._add (main.dart.wasm:0x146cc2)
at _StreamController.add (main.dart.wasm:0x146c76)
at _StreamController.add tear-off trampoline (main.dart.wasm:0x150957)
at _RootZone.runUnaryGuarded (main.dart.wasm:0xd475e)
Code sample
Code sample
**main.dart**
```dart
import 'package:drift_wasm_example/database/dao/drift_actor_dao.dart';
import 'package:drift_wasm_example/database/drift_database.dart';
import 'package:drift_wasm_example/database/entities/drift_actor_entity.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int _counter = 0;
late final DriftDatabaseImpl _database;
late final DriftActorDao _actorDao;
@override
void initState() {
super.initState();
_initializeDatabase();
}
Future _initializeDatabase() async {
print("initialize drift database");
_database = DriftDatabaseImpl();
_actorDao = _database.driftActorDao;
}
Future _insertActors() async {
final generatedActors = List.generate(
100,
(index) => DriftActorEntity(
actorID: "$index",
parentActorID: "$index",
actorDescription: "Actor #$index description",
actorDistributorId: "$index",
actorTypeID: index,
actorStatus: index,
actorMachinesCount: index * 10,
),
);
await _actorDao.insertActors(generatedActors);
}
Future _printActors() async {
final savedActors = await _actorDao.getActors();
for(final actor in savedActors) {
print(actor);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
persistentFooterButtons: [
FloatingActionButton(
onPressed: _insertActors,
tooltip: 'Insert 100 actors',
child: const Icon(Icons.add),
),
const SizedBox(width: 20.0),
FloatingActionButton(
onPressed: _printActors,
tooltip: 'Print actors',
child: const Icon(Icons.print),
),
], // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
```
**drift_database.dart**
```dart
DatabaseConnection connectOnWeb() {
return DatabaseConnection.delayed(Future(() async {
final result = await WasmDatabase.open(
databaseName: 'my_app_db', // prefer to only use valid identifiers here
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.js'),
);
if (result.missingFeatures.isNotEmpty) {
// Depending how central local persistence is to your app, you may want
// to show a warning to the user if only unrealiable implemetentations
// are available.
print('Using ${result.chosenImplementation} due to missing browser '
'features: ${result.missingFeatures}');
}
return result.resolvedExecutor;
}));
}
@DriftDatabase(tables: [
DriftActorTable,
], daos: [
DriftActorDao
])
class DriftDatabaseImpl extends _$DriftDatabaseImpl {
DriftDatabaseImpl._(super.e);
factory DriftDatabaseImpl() => DriftDatabaseImpl._(connectOnWeb());
@override
int get schemaVersion => 1;
}
```
**drift_actor_entity.dart**
```dart
class DriftActorEntity implements Insertable {
String? actorID;
String? parentActorID;
String? actorDescription;
String? actorDistributorId;
int? actorTypeID;
int? actorStatus;
int? actorMachinesCount;
DriftActorEntity(
{this.parentActorID,
this.actorID,
this.actorDescription,
this.actorDistributorId,
this.actorTypeID,
this.actorStatus,
this.actorMachinesCount});
@override
Map toColumns(bool nullToAbsent) {
return DriftActorTableCompanion(
actorID: Value(actorID),
parentActorID: Value(parentActorID),
actorDescription: Value(actorDescription),
actorDistributorId: Value(actorDistributorId),
actorTypeID: Value(actorTypeID),
actorStatus: Value(actorStatus),
actorMachinesCount: Value(actorMachinesCount),
).toColumns(nullToAbsent);
}
@override
String toString() {
return 'DriftActorEntity{actorID: $actorID, parentActorID: $parentActorID, actorDescription: $actorDescription, actorDistributorId: $actorDistributorId, actorTypeID: $actorTypeID, actorStatus: $actorStatus, actorMachinesCount: $actorMachinesCount}';
}
}
```
**DriftActorTable**
```dart
@UseRowClass(DriftActorEntity)
class DriftActorTable extends Table {
@override
String get tableName => 'actor';
TextColumn get actorID => text().named("actorID").nullable()();
TextColumn get parentActorID => text().named("parentActorID").nullable()();
TextColumn get actorDescription => text().named("actorDescription").nullable()();
TextColumn get actorDistributorId => text().named("actorDistributorId").nullable()();
IntColumn get actorTypeID => integer().named("actorTypeID").nullable()();
IntColumn get actorStatus => integer().named("actorStatus").nullable()();
IntColumn get actorMachinesCount => integer().named("actorMachinesCount").nullable()();
@override
Set? get primaryKey => {actorID};
}
```
**DriftActorDao**
```dart
@DriftAccessor(tables: [DriftActorTable])
class DriftActorDao extends DatabaseAccessor
with _$DriftActorDaoMixin {
DriftActorDao(DriftDatabaseImpl db) : super(db);
@override
Future insertActors(List actors) {
return batch((batch) {
batch.insertAll(driftActorTable, actors,
mode: InsertMode.insertOrReplace);
});
}
@override
Future> getActors() {
return (select(driftActorTable)).get();
}
}
```
Flutter Doctor output
Flutter Doctor output
```
% flutter doctor -v
[✓] Flutter (Channel stable, 3.22.3, on macOS 13.6.7 22G720 darwin-x64, locale en-IL)
• Flutter version 3.22.3 on channel stable at /Users/olegs/Documents/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision b0850beeb2 (8 days ago), 2024-07-16 21:43:41 -0700
• Engine revision 235db911ba
• Dart version 3.4.4
• DevTools version 2.34.3
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0-rc5)
• Android SDK at /Users/olegs/Library/Android/sdk
• Platform android-34, build-tools 31.0.0-rc5
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 15.0)
• Xcode at /Users/olegs/Downloads/Xcode.app/Contents/Developer
• Build 15A240d
• CocoaPods version 1.15.2
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2022.3)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
[✓] VS Code (version 1.58.2)
• VS Code at /Users/olegs/Downloads/Visual Studio Code.app/Contents
• Flutter extension can be installed from:
🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] VS Code (version 1.91.1)
• VS Code at /Applications/Visual Studio Code 2.app/Contents
• Flutter extension can be installed from:
🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] Connected device (2 available)
• macOS (desktop) • macos • darwin-x64 • macOS 13.6.7 22G720 darwin-x64
• Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.183
[✓] Network resources
• All expected network resources are available.
```
I'll write down some notes I've gathered while investigating the same problem. The issue is reproducible for example app hosted under drift/examples/app here in the repository as well.
This is caused by DriftProtocol.deserialize() method doing as int conversion of the message[0] and message[1] arguments and receiving doubles instead - trying to cast double as int fails and the exception thus arises.
You may replace the following lines:
final tag = message[0];
final id = message[1] as int;
with:
final tag = message[0] is double ? (message[0] as double).toInt() : message[0] as int;
final id = message[1] is double ? (message[1] as double).toInt() : message[1] as int;
And also do the same thing for decodePayload() function, which does some as int as well. This will fix the initial RuntimeError: illegal cast issue, and drift will connect to the WasmDatabase. However, there seems to be more work needed to be done, as SELECT/INSERT/DELETE statements don't seem to work, they all throw the same exceptions with illegal cast (perhaps due to those queries expecting last ROW_ID to be int and converting it as such, despite being double?):
Running customStatement for INSERTs indicate that items are indeed being added. Then customSelect may be executed to further ensure that items are indeed added, yet due to conversion issues it still fails, you can see at the attached screenshot that items are indeed there, yet Invalid radix-10 number is thrown at DateTime being stored as the microseconds (double instead of int, I guess, as drift expects?)
After modification that you mentioned. I getting another error
main.dart.wasm:0x5161fd Uncaught RuntimeError: illegal cast
at DeleteStatement.go closure at file:///Users/olegs/.pub-cache/hosted/pub.dev/drift-2.19.1/lib/src/runtime/query_builder/statements/delete.dart:52:41 inner (main.dart.wasm:0x5161fd)
at _awaitHelperWithTypeCheck closure at org-dartlang-sdk:///dart-sdk/lib/_internal/wasm/lib/async_patch.dart:97:16 (main.dart.wasm:0x481ddb)
at closure wrapper at org-dartlang-sdk:///dart-sdk/lib/_internal/wasm/lib/async_patch.dart:97:16 trampoline (main.dart.wasm:0x481ecd)
at _RootZone.runUnary (main.dart.wasm:0x482a28)
at _Future._propagateToListeners (main.dart.wasm:0x48260f)
at _Future._completeWithValue (main.dart.wasm:0x482cc9)
at _Future._asyncCompleteWithValue closure at org-dartlang-sdk:///dart-sdk/lib/async/future_impl.dart:721:29 (main.dart.wasm:0x49626f)
at closure wrapper at org-dartlang-sdk:///dart-sdk/lib/async/future_impl.dart:721:29 trampoline (main.dart.wasm:0x496286)
at _startMicrotaskLoop (main.dart.wasm:0x481190)
at _startMicrotaskLoop tear-off trampoline (main.dart.wasm:0x4811fb)
@OlegShNayax, yep, that's what I'm stuck at with too. Seems like the as int conversion story gets deeper into drift and even perhaps sqlite3 packages and the DriftProtocol fixes I've provided earlier do not suffice. If there's anything more you'll discover, please, write down your thoughts here - it'll surely help the maintainer to investigate the problem and fix it.
This is pretty hard to test at the moment because there is no good way to write integration tests for dart2wasm outside of Flutter yet. Nothing in drift is aware of dart2wasm at all, and we're not running any tests compiled to wasm.
package:sqlite3 has experimental support for dart2wasm (https://github.com/simolus3/sqlite3.dart/issues/230 is the issue to follow).
One of the issues here appears to be a compiler bug (https://github.com/dart-lang/sdk/issues/56321), I'll fix the casts as well.
As a short-term solution I'll look into running unit tests with dart2wasm as well, but that doesn't say too much because most of the subtle bugs are usually around the dart:js_interop layer which we can only test with integration tests. I've opened PRs to the SDK and build_web_compilers to support the test architecture drift is using for that, but it will definitely take a while for drift to have well-tested wasm support.
I've added CI steps running unit tests for drift with both dartdevc and dart2wasm (dart2js unfortunately is way too slow on the runners, even with shards enabled).
We're also running integration tests with dart2js as part of the CI now. Running them with dart2wasm is blocked on https://github.com/dart-lang/build/issues/3621 (which I am also working on).
Steps to reproduce
flutter build web --wasm --no-strip-wasm
dhttpd '--headers=Cross-Origin-Embedder-Policy=credentialless;Cross-Origin-Opener-Policy=same-origin' --path=build/web --port=8085
Developer Tools > Console
Expected results
We see log "initialize drift database" end everything works fine.
Actual results
We see log "initialize drift database" and error:
Code sample
Code sample
**main.dart** ```dart import 'package:drift_wasm_example/database/dao/drift_actor_dao.dart'; import 'package:drift_wasm_example/database/drift_database.dart'; import 'package:drift_wasm_example/database/entities/drift_actor_entity.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State
> getActors() { return (select(driftActorTable)).get(); } } ```
Flutter Doctor output
Flutter Doctor output
``` % flutter doctor -v [✓] Flutter (Channel stable, 3.22.3, on macOS 13.6.7 22G720 darwin-x64, locale en-IL) • Flutter version 3.22.3 on channel stable at /Users/olegs/Documents/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision b0850beeb2 (8 days ago), 2024-07-16 21:43:41 -0700 • Engine revision 235db911ba • Dart version 3.4.4 • DevTools version 2.34.3 [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0-rc5) • Android SDK at /Users/olegs/Library/Android/sdk • Platform android-34, build-tools 31.0.0-rc5 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.0) • Xcode at /Users/olegs/Downloads/Xcode.app/Contents/Developer • Build 15A240d • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231) [✓] VS Code (version 1.58.2) • VS Code at /Users/olegs/Downloads/Visual Studio Code.app/Contents • Flutter extension can be installed from: 🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [✓] VS Code (version 1.91.1) • VS Code at /Applications/Visual Studio Code 2.app/Contents • Flutter extension can be installed from: 🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-x64 • macOS 13.6.7 22G720 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.183 [✓] Network resources • All expected network resources are available. ```
I'll write down some notes I've gathered while investigating the same problem. The issue is reproducible for example app hosted under
drift/examples/app
here in the repository as well.This is caused by
DriftProtocol.deserialize()
method doingas int
conversion of themessage[0]
andmessage[1]
arguments and receivingdouble
s instead - trying to castdouble
asint
fails and the exception thus arises.You may replace the following lines:
with:
And also do the same thing for
decodePayload()
function, which does someas int
as well. This will fix the initialRuntimeError: illegal cast
issue, anddrift
will connect to theWasmDatabase
. However, there seems to be more work needed to be done, as SELECT/INSERT/DELETE statements don't seem to work, they all throw the same exceptions with illegal cast (perhaps due to those queries expecting last ROW_ID to beint
and converting it as such, despite beingdouble
?):Running
customStatement
for INSERTs indicate that items are indeed being added. ThencustomSelect
may be executed to further ensure that items are indeed added, yet due to conversion issues it still fails, you can see at the attached screenshot that items are indeed there, yetInvalid radix-10 number
is thrown atDateTime
being stored as the microseconds (double
instead ofint
, I guess, asdrift
expects?)@SleepySquash Thank you for really helpful notes.
After modification that you mentioned. I getting another error
@OlegShNayax, yep, that's what I'm stuck at with too. Seems like the
as int
conversion story gets deeper intodrift
and even perhapssqlite3
packages and theDriftProtocol
fixes I've provided earlier do not suffice. If there's anything more you'll discover, please, write down your thoughts here - it'll surely help the maintainer to investigate the problem and fix it.This is pretty hard to test at the moment because there is no good way to write integration tests for dart2wasm outside of Flutter yet. Nothing in drift is aware of dart2wasm at all, and we're not running any tests compiled to wasm.
package:sqlite3
has experimental support for dart2wasm (https://github.com/simolus3/sqlite3.dart/issues/230 is the issue to follow). One of the issues here appears to be a compiler bug (https://github.com/dart-lang/sdk/issues/56321), I'll fix the casts as well.As a short-term solution I'll look into running unit tests with dart2wasm as well, but that doesn't say too much because most of the subtle bugs are usually around the
dart:js_interop
layer which we can only test with integration tests. I've opened PRs to the SDK andbuild_web_compilers
to support the test architecture drift is using for that, but it will definitely take a while for drift to have well-tested wasm support.The problems are fixed on
develop
.I've added CI steps running unit tests for
drift
with both dartdevc and dart2wasm (dart2js
unfortunately is way too slow on the runners, even with shards enabled). We're also running integration tests withdart2js
as part of the CI now. Running them withdart2wasm
is blocked on https://github.com/dart-lang/build/issues/3621 (which I am also working on).How to use develop branch? I can't start my app using wasm with current pub version
There are some notes on that here