Update new web documentation

This commit is contained in:
Simon Binder 2023-06-15 00:06:18 +02:00
parent fef35d9f20
commit 1e74aae972
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
18 changed files with 357 additions and 242 deletions

View File

@ -97,7 +97,7 @@ targets:
dependencies: ['$default']
auto_apply_builders: false
sources:
- "web/worker.dart"
- "web/drift_worker.dart"
builders:
build_web_compilers:entrypoint:
options:
@ -121,7 +121,7 @@ targets:
build_web_compilers:entrypoint:
generate_for:
exclude:
- "web/worker.dart"
- "web/drift_worker.dart"
release_options:
# Turn of null assertions for release builds, it looks like this
# makes generated code slightly smaller.

View File

@ -0,0 +1,101 @@
import 'package:drift/drift.dart';
import 'package:drift/wasm.dart';
import 'package:drift/web.dart';
import 'package:sqlite3/wasm.dart';
// #docregion connect
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.dart.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;
}));
}
// #enddocregion connect
// #docregion migrate-wasm
// If you've previously opened your database like this
Future<WasmDatabase> customDatabase() async {
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('/sqlite3.wasm'));
final fs = await IndexedDbFileSystem.open(dbName: 'my_app');
sqlite3.registerVirtualFileSystem(fs, makeDefault: true);
return WasmDatabase(
sqlite3: sqlite3,
path: '/app.db',
);
}
// #enddocregion migrate-wasm
DatabaseConnection migrateAndConnect() {
return DatabaseConnection.delayed(Future(() async {
// #docregion migrate-wasm
// Then you can migrate like this
final result = await WasmDatabase.open(
databaseName: 'my_app',
sqlite3Uri: Uri.parse('/sqlite3.wasm'),
driftWorkerUri: Uri.parse('/drift_worker.dart.js'),
initializeDatabase: () async {
// Manually open the file system previously used
final fs = await IndexedDbFileSystem.open(dbName: 'my_app');
const oldPath = '/app.db'; // The path passed to WasmDatabase before
Uint8List? oldDatabase;
// Check if the old database exists
if (fs.xAccess(oldPath, 0) != 0) {
// It does, then copy the old file
final (file: file, outFlags: _) =
fs.xOpen(Sqlite3Filename(oldPath), 0);
final blob = Uint8List(file.xFileSize());
file.xRead(blob, 0);
file.xClose();
fs.xDelete(oldPath, 0);
oldDatabase = blob;
}
await fs.close();
return oldDatabase;
},
);
// #enddocregion migrate-wasm
return result.resolvedExecutor;
}));
}
DatabaseConnection migrateFromLegacy() {
return DatabaseConnection.delayed(Future(() async {
// #docregion migrate-legacy
final result = await WasmDatabase.open(
databaseName: 'my_app',
sqlite3Uri: Uri.parse('/sqlite3.wasm'),
driftWorkerUri: Uri.parse('/drift_worker.dart.js'),
initializeDatabase: () async {
final storage = await DriftWebStorage.indexedDbIfSupported('old_db');
await storage.open();
final blob = await storage.restore();
await storage.close();
return blob;
},
);
// #enddocregion migrate-legacy
return result.resolvedExecutor;
}));
}

View File

@ -1,18 +1,24 @@
---
data:
title: Web
description: Experimental support for drift and webapps.
description: Drift support in Flutter and Dart web apps.
template: layouts/docs/single
path: web/
---
You can experimentally use drift in Dart webapps. Drift web supports
Flutter Web, AngularDart, plain `dart:html` or any other web framework.
{% assign snippets = "package:drift_docs/snippets/engines/web.dart.excerpt.json" | readString | json_decode %}
## Compatibility check
To quickly check which storage implementation drift would chose, the drift worker is also embedded
on this page. You can click on this button to see the results.
Using modern browser APIs such as WebAssembly and the Origin-Private File System API,
you can use drift databases when compiling your apps to the web.
Just like the core drift APIs, web support is platform-agnostic:
Drift web supports Flutter Web, AngularDart, plain `dart:html` or any other Dart web framework.
{% block "blocks/alert" title="Compatibility check" %}
This page includes a tiny drift database compiled to JavaScript.
You can use it to verify drift works in the Browsers you want to target.
Clicking on the button will start a feature detection run, so you can see which file system
implementation drift would pick on this browser and which web APIs are missing.
<button class="btn btn-light" id="drift-compat-btn">Check compatibility</button>
@ -20,49 +26,75 @@ on this page. You can click on this button to see the results.
Compatibility check not started yet
</pre>
More information about these results is available [below](#storages).
{% endblock %}
## Getting started
### Prerequisites
On all platforms, drift requires access to [sqlite3](https://sqlite.org/index.html), the popular
datase system written as a C library.
On native platforms, drift can use the sqlite3 library from your operating system. Flutter apps
typically include a more recent version of that library with the `sqlite3_flutter_libs` package too.
Web browsers don't have builtin access to the sqlite3 library, so it needs to be included with your app.
The `sqlite3` Dart package (used by drift internally) contains a toolchain to compile sqlite3 to WebAssembly
so that it can be used in browsers. You can grab a `sqlite3.wasm` file from [its releases page](https://github.com/simolus3/sqlite3.dart/releases).
This file needs to be put into the `web/` directory of your app.
Drift on the web also requires you to include a portion of drift as a web worker. This worker will be used to
host your database in a background thread, improving performance of your website. In some [storage implementations](#storages),
the worker is also responsible for sharing your database between different tabs in real-time.
You can compile this worker yourself, [grab one from drift releases](https://github.com/simolus3/drift/releases) or take
the [latest one powering this website]({{ '/worker.dart.js' | absUrl }}).
In the end, your `web/` directory may look like this:
```
web/
├── favicon.png
├── index.html
├── manifest.json
├── drift_worker.dart.js
└── sqlite3.wasm
```
#### Additional headers
On browsers that support it, drift uses the origin-private part of the [FileSystem Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) to store databases efficiently.
As parts of that API are asynchronous, and since sqlite3 expectes a synchronous file system, we need to
use two workers with shared memory and `Atomics.wait`/`notify`.
Just like the official sqlite3 port to the web, __this requires your website to be served with two special headers__:
- `Cross-Origin-Opener-Policy`: Needs to be set to `same-origin`.
- `Cross-Origin-Embedder-Policy`: Needs to be set to `require-corp` or `credentialless`.
For more details, see the [security requirements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) explained by MDN, and the [documentation on web.dev](https://web.dev/coop-coep/).
Unfortunately, there's no way (that I'm aware of) to add these headers onto `flutter run`'s web server.
Drift will fall back to a less reliable implementation in that case (see [storages](#storages)),
but we recommend researching and enabling these headers in production if possible.
### Setup in Dart
From a perspective of the Dart code used, drift on the web is similar to drift on other platforms.
You can follow the [getting started guide]({{ '../Getting started/index.md' | pageUrl }}) for general
information on using drift.
Instead of using a `NativeDatabase` in your database classes, you can use a `WebDatabase` executor:
Instead of using a `NativeDatabase` in your database classes, you can use `WasmDatabase` optimized for
the web:
```dart
import 'package:drift/web.dart';
{% include "blocks/snippet" snippets = snippets name = "connect" %}
@DriftDatabase(tables: [Todos, Categories])
class MyDatabase extends _$MyDatabase {
// here, "app" is the name of the database - you can choose any name you want
MyDatabase() : super(WebDatabase('app'));
```
When you call `WasmDatabase.open`, drift will automatically find a suitable persistence implementation
supported by the current browser.
Drift web is built on top of the [sql.js](https://github.com/sql-js/sql.js/) library, which you need to include:
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script defer src="sql-wasm.js"></script>
<script defer src="main.dart.js" type="application/javascript"></script>
</head>
<body></body>
</html>
```
You can grab the latest version of `sql-wasm.js` and `sql-wasm.wasm` [here](https://github.com/sql-js/sql.js/releases)
and copy them into your `web` folder.
A full example that works on the web (and all other platforms) is available
A full example that works on the web (and all other platforms supported by drift) is available
[here](https://github.com/simolus3/drift/tree/develop/examples/app).
## Gotchas
The database implementation uses WebAssembly, which needs to be supported by your browser.
Also, make sure that your webserver serves the `.wasm` file as `application/wasm`, browsers
won't accept it otherwise.
## Sharing code between native apps and web
If you want to share your database code between native applications and webapps, just import the
If you want to share your database code between native applications and web apps, just import the
basic `drift/drift.dart` library into your database file.
And instead of passing a `NativeDatabase` or `WebDatabase` to the `super` constructor, make the
`QueryExecutor` customizable:
@ -100,7 +132,7 @@ On the web, you can use
import 'package:drift/web.dart';
SharedDatabase constructDb() {
return SharedDatabase(WebDatabase('db'));
return SharedDatabase(connectOnWeb());
}
```
@ -124,10 +156,81 @@ export 'unsupported.dart'
A ready example of this construct can also be found [here](https://github.com/simolus3/drift/blob/develop/examples/app/lib/database/connection/connection.dart).
## Debugging
## Supported storage implementations {#storages}
When opening a database, drift determines the state of a number of web APIs in the current browser.
It then picks a suitable database storage based on what the current browser supports.
The following implementation strategies are supported by drift (listed in order of descending preference).
You can view the chosen strategy via the `WasmDatabaseResult.chosenImplementation` returned by
`WasmDatabase.open`.
1. `opfsShared`: Uses the origin-private filesystem access API in a shared web worker.
As this API is only available in dedicated web workers, this requires shared workers to be able to
spawn dedicated workers. While allowed by the web standards, this is only implemented by Firefox.
2. `opfsLocks`: Uses the origin-private filesystem access API, but without shared workers.
This requires the [COOP and COEP headers](#additional-headers).
3. `sharedIndexedDb`: Uses IndexedDB to store chunks of data, the database is hosted in a shared web worker.
4. `unsafeIndexedDb`: This also uses the IndexedDB database, but without a shared worker or another
means of synchronization between tabs. In this mode, __it is not safe for multiple tabs of your app
to access the same database__.
While this was the default mode used by earlier implementations of drift for the web, we now try to
avoid it and it will not be used on modern browsers.
5. `inMemory`: When no persistence API is available, drift will fall back to an in-memory database.
The missing browser APIs contributing to a specific implementation being chosen are available in
`WasmDatabaseResult.missingFeatures`.
There's nothing your app or drift could do about them. If persistence is important to your app
and drift has chosen the `unsafeIndexedDb` or the `inMemory` implementation due to a lack of proper
persistence support, you may want to show a warning to the user explaining that they have to upgrade
their browser.
### Migrating from existing web databases
`WasmDatabase.open` is drift's stable web API, which has been built with the lessons learned from previous
web APIs available in drift (that have always been marked as `@experimental`). You can use `WasmDatabase.open`
to replace the following drift APIs:
1. The sql.js-based implementation in `package:drift/web.dart`.
2. Custom `WasmDatabase` constructions that manually load sqlite3.
3. Custom worker setups created with `package:drift/web/worker.dart`.
Let's start with the good news: After migrating to `WasmDatabase.open`, drift will manage workers for you.
It automatically uses the best worker setup supported by the current browsers, enabling the use of shared
and dedicated web workers where appropriate.
So the migration from `package:drift/web/worker.dart` is to just stop using that API, since you get all of
its features out of the box with `WasmDatabase.open`.
#### Migrating from custom `WasmDatabase`s
In older drift versios, you may have used a custom setup that loaded the WASM binary manually, created
a `CommonSqlite3` instance with it and passed that to `WasmDatabase`.
{% include "blocks/snippet" snippets = snippets name = "migrate-wasm" %}
#### Migrating from `package:drift/web.dart`
To migrate from a `WebDatabase` to the new setup, you can use the `initializeDatabase` callback.
It is invoked when opening the database if no file exists yet. By loading the old database there,
it is migrated to the new format without data loss:
{% include "blocks/snippet" snippets = snippets name = "migrate-legacy" %}
In that snippet, `old_db` is the name previously passed to the `WebDatabase`.
## Legacy web support
Drift first gained its initial web support in 2019 by wrapping the sql.js JavaScript library.
This implementation, which is still supported today, relies on keeping an in-memory database that is periodically saved to local storage.
In the last years, development in web browsers and the Dart ecosystem enabled more performant approaches that are
unfortunately impossible to implement with the original drift web API.
This is the reason the original API is still considered experimental - while it will continue to be supported, it is now obvious that the new approach by `WasmDatabase.open` is sound and more efficient
than these implementations.
The original APIs are still documented on this page for your reference.
### Debugging
You can see all queries sent from drift to the underlying database engine by enabling the `logStatements`
parameter on the `WebDatabase` - they will appear in the console.
When you have assertions enabled (e.g. in debug mode), drift will expose the underlying
When you have assertions enabled (e.g. in debug mode), drift will expose the underlying
[database](https://sql.js.org/documentation/Database.html)
object via `window.db`. If you need to quickly run a query to check the state of the database, you can use
`db.exec(sql)`.
@ -135,7 +238,7 @@ If you need to delete your databases, there stored using local storage. You can
Web support is experimental at the moment, so please [report all issues](https://github.com/simolus3/drift/issues/new) you find.
## Using IndexedDb
### Using IndexedDb
The default `WebDatabase` uses local storage to store the raw sqlite database file. On browsers that support it, you can also
use `IndexedDb` to store that blob. In general, browsers allow a larger size for `IndexedDb`. The implementation is also more
@ -149,7 +252,8 @@ WebDatabase.withStorage(await DriftWebStorage.indexedDbIfSupported(name))
Drift will automatically migrate data from local storage to `IndexedDb` when it is available.
### Using web workers
#### Using web workers
You can offload the database to a background thread by using
[Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).
@ -184,7 +288,7 @@ For more information on the `DatabaseConnection` class, see the documentation on
A small, but working example is available under [examples/web_worker_example](https://github.com/simolus3/drift/tree/develop/examples/web_worker_example)
in the drift repository.
### Flutter
#### Flutter
Flutter users will have to use a different approach to compile service workers.
Flutter web doesn't compile `.dart` files in web folder and won't use `.js` files generated by
`build_web_compilers` either. Instead, we'll use Dart's build system to manually compile the worker to a
@ -253,7 +357,7 @@ DatabaseConnection connectToWorker(String databaseName) {
You can pass that DatabaseConnection to your database by enabling the `generate_connect_constructor` build option.
## New web backend {#drift-wasm}
### New web backend {#drift-wasm}
In recent versions, drift added support for a new backend exposed by the `package:drift/wasm.dart` library.
Unlike sql.js or the official sqlite3 WASM edition which both use Emscripten, this backend does not need any

View File

@ -1,153 +0,0 @@
---
data:
title: Web draft
description: Draft for upcoming stable Drift web support.
hidden: true
template: layouts/docs/single
---
This draft document describes different approaches allowing drift to run on the
web.
After community feedback, this restructured page will replace the [existing web documentation]({{ 'web.md' | pageUrl }}).
## Introduction
Drift first gained its initial web support in 2019 by wrapping the sql.js JavaScript library.
This implementation, which is still supported today, relies on keeping an in-memory database that is periodically saved to local storage.
In the last years, development in web browsers and the Dart ecosystem enabled more performant approaches that are
unfortunately impossible to implement with the original drift web API.
This is the reason the original API is still considered experimental - while it will continue to be supported, it is now obvious
that there are better approaches coming up.
This page describes the fundamental challenges and required browser features used to efficiently run drift on the web.
It presents a guide on the current and most reliable approach to bring sqlite3 to the web, but older implementations
and approaches to migrate between them are still supported and documented as well.
## Setup
The recommended solution to run drift on the web is to use
- The File System Access API with an Origin-private File System (OPFS) for storing data, and
- shared web workers to share the database between multiple tabs.
Drift and the `sqlite3` Dart package provide helpers to use those OPFS and shared web workers
easily.
However, even though both web APIs are suppported in most browsers, they are still relatively new and your app
should handle them not being available. Drift provides a feature-detection API which you can use to warn your
users if persistence is unavailable - see the caveats section for details.
{% block "blocks/alert" title="Caveats" color = "warning" %}
Most browsers support both APIs today, with two notable exceptions:
- Chrome on Android does not support shared web workers.
- The stable version of Safari currently implements an older verison of the File System Access Standard.
This has been fixed in Technology Preview builds.
The File System Access API, or other persistence APIs are sometimes disabled in private or incognito tabs too.
You need to consider different fallbacks that you may want to support:
- If the File System Access API is not available, you may want to fall back to a different persistence layer like IndexedDb, silently use an in-memory database
only or warn the user about these circumstances. Note that, even in modern browsers, persistence may be blocked in private/incognito tabs.
- If shared workers are not available, you can still safely use the database, but not if multiple tabs of your web app are opened.
You could use [Web Locks](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) to detect whether another instance of your
database is currently open and inform the user about this.
The [Flutter app example](https://github.com/simolus3/drift/tree/develop/examples/app) which is part of the Drift repository implements all
of these fallbacks.
Snippets to detect these error conditions are provided on this website, but the integration with fallbacks or user-visible warnings depends
on the structure of your app in the end.
{% endblock %}
### Resources
First, you'll need a version of sqlite3 that has been compiled to WASM and is ready to use Dart bindings for its IO work.
You can grab this `sqlite3.wasm` file from the [GitHub releases](https://github.com/simolus3/sqlite3.dart/releases) of the sqlite3 package,
or [compile it yourself](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3#compiling).
You can host this file on a CDN, or just put it in the `web/` folder of your Flutter app so that it is part of the final bundle.
It is important that your web server serves the file with `Content-Type: application/wasm`. Browsers will refuse to load it otherwise.
### Drift web worker
Since OPFS is only available in dedicated web workers, you need to define a worker responsible for hosting the database in its thread.
The main tab will connect to that worker to access the database with a communication protocol handled by drift.
In its `web/worker.dart` library, Drift provies a suitable entrypoint for both shared and dedicated web workers hosting a sqlite3
database. It takes a callback creating the actual database connection. Drift will be responsible for creating the worker in the
right configuration.
But since the worker depends on the way you set up the database, we can't ship a precompiled worker JavaScript file. You need to
write the worker yourself and compile it to JavaScript.
The worker's source could be put into `web/database_worker.dart` and have a structure like the following:
{% include "blocks/snippet" snippets = worker %}
Drift will detect whether the worker is running as a shared or as a dedicated worker and call the callback to open the
database at a suitable time.
How to compile the worker depends on your build setup:
1. With regular Dart web apps, you're likely using `build_web_compilers` with `build_runner` or `webdev` already.
This build system can compile workers too.
[This build configuration](https://github.com/simolus3/drift/blob/develop/examples/web_worker_example/build.yaml) shows
how to configure `build_web_compilers` to always compile a worker with `dart2js`.
2. With Flutter wep apps, you can either use `build_web_compilers` too (since you're already using `build_runner` for
drift), or compile the worker with `dart compile js`. When using `build_web_compilers`, explicitly enable `dart2js`
or run the build with `--release`.
Make sure to always use `dart2js` (and not `dartdevc`) to compile a web worker, since modules emitted by `dartdevc` are
not directly supported in web workers.
#### Worker mode
Depending on the storage implementation you use in your app, different worker topologies can be used.
when in doubt, `DriftWorkerMode.dedicatedInShared` is a good default.
1. If you don't need support for multiple tabs accessing the database at the same time,
you can use `DriftWorkerMode.dedicated` which does not spawn a shared web worker.
2. The File System Acccess API can only be accessed in dedicated workers, which is why `DriftWorkerMode.dedicatedInShared`
is used. If you use a different file system implementation (like one based on IndexedDB), `DriftWorkerMode.shared`
is sufficient.
| Dedicated | Shared | Dedicated in shared |
|-----------|--------|---------------------|
| ![](dedicated.png) | ![](shared.png) | ![](dedicated_in_shared.png) |
| Each tab uses its own worker with an independent database. | A single worker hosting the database is used across tabs | Like "shared", except that the shared worker forwards requests to a dedicated worker. |
### Using the database
To spawn and connect to such a web worker, drift provides the `connectToDriftWorker` method:
{% include "blocks/snippet" snippets = snippets name = "approach1" %}
The returned `DatabaseConnection` can be passed to the constructor of a generated database class.
## Technology challenges
Drift wraps [sqlite3](https://sqlite.org/index.html), a popular relational database written as a C library.
On native platforms, we can use `dart:ffi` to efficiently bind to C libraries. This is what a `NativeDatabase` does internally,
it gives us efficient and synchronous access to sqlite3.
On the web, C libraries can be compiled to [WebAssembly](https://webassembly.org/), a native-like low-level language.
While C code can be compiled to WebAssembly, there is no builtin support for file IO which would be required for a database.
This functionality needs to be implemented in JavaScript (or, in our case, in Dart).
For a long time, the web platform lacked a suitable persistence solution that could be used to give sqlite3 access to the
file system:
- Local storage is synchronous, but can't efficiently store binary data. Further, we can't efficiently change a portion of the
data stored in local storage. A one byte write to a 10MB database file requires writing everything again.
- IndexedDb supports binary data and could be used to store chunks of a file in rows. However, it is asynchronous and sqlite3,
being a C library, expects a synchronous IO layer.
- Finally, the newer File System Access API supports synchronous access to app data _and_ synchronous writes.
However, it is only supported in web workers.
Further, a file in this API can only be opened by one JavaScript context at a time.
While we can support asynchronous persistence APIs by keeping an in-memory cache for synchronous reads and simply not awaiting
writes, the direct File System Access API is more promising due to its synchronous nature that doesn't require caching the entire database in memory.
In addition to the persistence problem, there is an issue of concurrency when a user opens multiple tabs of your web app.
Natively, locks in the file system allow sqlite3 to guarantee that multiple processes can access the same database without causing
conflicts. On the web, no synchronous lock API exists between tabs.
## Legacy approaches
### sql.js {#sqljs}

View File

@ -24,8 +24,8 @@ This table list all supported drift implementations and on which platforms they
|----------------|---------------------|-------|
| `SqfliteQueryExecutor` from `package:drift_sqflite` | Android, iOS | Uses platform channels, Flutter only, no isolate support, doesn't support `flutter test`. Formerly known as `moor_flutter` |
| `NativeDatabase` from `package:drift/native.dart` | Android, iOS, Windows, Linux, macOS | No further setup is required for Flutter users. For support outside of Flutter, or in `flutter test`, see the [desktop](#desktop) section below. Usage in a [isolate]({{ 'Advanced Features/isolates.md' | pageUrl }}) is recommended. Formerly known as `package:moor/ffi.dart`. |
| `WebDatabase` from `package:drift/web.dart` | Web | Works with or without Flutter. A bit of [additional setup]({{ 'Other engines/web.md' | pageUrl }}) is required. |
| `WasmDatabase` from `package:drift/web.dart` | Web | Potentially faster than a `WebDatabase`, but still experimental and not yet production ready. See [this]({{ 'Other engines/web2.md' | pageUrl }}) for details. |
| `WasmDatabase` from `package:drift/wasm.dart` | Web | Works with or without Flutter. A bit of [additional setup]({{ 'Other engines/web.md' | pageUrl }}) is required. |
| `WebDatabase` from `package:drift/web.dart` | Web | Deprecated in favor of `WasmDatabase`. |
To support all platforms in a shared codebase, you only need to change how you open your database, all other usages can stay the same.
[This repository](https://github.com/simolus3/drift/tree/develop/examples/app) gives an example on how to do that with conditional imports.

View File

@ -3,7 +3,7 @@ description: Documentation website for the drift project.
publish_to: none
environment:
sdk: '>=2.19.0 <4.0.0'
sdk: '>=3.0.0 <4.0.0'
dependencies:
drift:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -14,7 +14,7 @@ void main() async {
final db = await WasmDatabase.open(
databaseName: 'test_db',
sqlite3Uri: Uri.parse('/sqlite3.wasm'),
driftWorkerUri: Uri.parse('/worker.dart.js'),
driftWorkerUri: Uri.parse('/drift_worker.dart.js'),
);
results.innerText += '''

View File

@ -41,7 +41,7 @@ class WasmDatabaseOpener {
final Uri sqlite3WasmUri;
final Uri driftWorkerUri;
final String databaseName;
FutureOr<Uint8List> Function()? initializeDatabase;
FutureOr<Uint8List?> Function()? initializeDatabase;
final Set<MissingBrowserFeature> missingFeatures = {};
final List<WasmStorageImplementation> availableImplementations = [
@ -133,11 +133,13 @@ class WasmDatabaseOpener {
if (initializer != null) {
final blob = await initializer();
final (file: file, outFlags: _) = inMemory.xOpen(
Sqlite3Filename('/database'), SqlFlag.SQLITE_OPEN_CREATE);
file
..xWrite(blob, 0)
..xClose();
if (blob != null) {
final (file: file, outFlags: _) = inMemory.xOpen(
Sqlite3Filename('/database'), SqlFlag.SQLITE_OPEN_CREATE);
file
..xWrite(blob, 0)
..xClose();
}
}
return WasmDatabaseResult(

View File

@ -95,7 +95,7 @@ class WasmDatabase extends DelegatedDatabase {
required String databaseName,
required Uri sqlite3Uri,
required Uri driftWorkerUri,
FutureOr<Uint8List> Function()? initializeDatabase,
FutureOr<Uint8List?> Function()? initializeDatabase,
}) {
return WasmDatabaseOpener(
databaseName: databaseName,

View File

@ -11,6 +11,7 @@ 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';
@ -132,10 +133,14 @@ class DriftWebDriver {
await driver.executeAsync('wait_for_update("", arguments[0])', []);
}
Future<void> enableInitialization(bool enabled) async {
await driver.executeAsync(
Future<void> enableInitialization(InitializationMode mode) async {
final result = await driver.executeAsync(
'enable_initialization(arguments[0], arguments[1])',
[enabled.toString()],
[mode.name],
);
if (result != true) {
throw 'Could not set initialization mode';
}
}
}

View File

@ -0,0 +1,5 @@
enum InitializationMode {
none,
loadAsset,
migrateCustomWasmDatabase,
}

View File

@ -17,6 +17,7 @@ dependencies:
package_config: ^2.1.0
async: ^2.11.0
http: ^1.0.0
sqlite3: ^2.0.0-dev.3
dev_dependencies:
build_runner: ^2.4.5

View File

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:drift/src/web/wasm_setup/types.dart';
import 'package:test/test.dart';
import 'package:web_wasm/driver.dart';
import 'package:web_wasm/initialization_mode.dart';
import 'package:webdriver/async_io.dart';
enum Browser {
@ -84,9 +85,9 @@ void main() {
expect(result.storages, expectedImplementations);
});
group('supports', () {
for (final entry in browser.availableImplementations) {
test(entry.name, () async {
for (final entry in browser.availableImplementations) {
group(entry.name, () {
test('basic', () async {
await driver.openDatabase(entry);
await driver.insertIntoDatabase();
@ -117,24 +118,35 @@ void main() {
}
});
test(
'initializing ${entry.name} from blob',
() async {
await driver.enableInitialization(true);
await driver.openDatabase(entry);
group(
'initialization from ',
() {
test('static blob', () async {
await driver.enableInitialization(InitializationMode.loadAsset);
await driver.openDatabase(entry);
expect(await driver.amountOfRows, 1);
await driver.insertIntoDatabase();
expect(await driver.amountOfRows, 2);
if (entry != WasmStorageImplementation.inMemory) {
await Future.delayed(const Duration(seconds: 1));
await driver.driver.refresh();
await driver.enableInitialization(true);
await driver.openDatabase();
expect(await driver.amountOfRows, 1);
await driver.insertIntoDatabase();
expect(await driver.amountOfRows, 2);
}
if (entry != WasmStorageImplementation.inMemory) {
await Future.delayed(const Duration(seconds: 1));
await driver.driver.refresh();
await driver
.enableInitialization(InitializationMode.loadAsset);
await driver.openDatabase();
expect(await driver.amountOfRows, 2);
}
});
test('custom wasmdatabase', () async {
await driver.enableInitialization(
InitializationMode.migrateCustomWasmDatabase);
await driver.openDatabase(entry);
expect(await driver.amountOfRows, 1);
});
},
skip: browser == Browser.firefox &&
entry == WasmStorageImplementation.opfsLocks
@ -142,8 +154,8 @@ void main() {
'reproduced by manually running the steps of this test.'
: null,
);
}
});
});
}
if (browser.supports(WasmStorageImplementation.unsafeIndexedDb) &&
browser.supports(WasmStorageImplementation.opfsLocks)) {

View File

@ -8,13 +8,15 @@ import 'package:drift/wasm.dart';
// ignore: invalid_use_of_internal_member
import 'package:drift/src/web/wasm_setup.dart';
import 'package:http/http.dart' as http;
import 'package:web_wasm/initialization_mode.dart';
import 'package:web_wasm/src/database.dart';
import 'package:sqlite3/wasm.dart';
const dbName = 'drift_test';
TestDatabase? openedDatabase;
StreamQueue<void>? tableUpdates;
bool _loadFromInitializer = false;
InitializationMode initializationMode = InitializationMode.none;
void main() {
_addCallbackForWebDriver('detectImplementations', _detectImplementations);
@ -22,8 +24,10 @@ void main() {
_addCallbackForWebDriver('insert', _insert);
_addCallbackForWebDriver('get_rows', _getRows);
_addCallbackForWebDriver('wait_for_update', _waitForUpdate);
_addCallbackForWebDriver('enable_initialization',
(arg) async => _loadFromInitializer = bool.parse(arg!));
_addCallbackForWebDriver('enable_initialization', (arg) async {
initializationMode = InitializationMode.values.byName(arg!);
return true;
});
document.getElementById('selfcheck')?.onClick.listen((event) async {
print('starting');
@ -51,16 +55,50 @@ void _addCallbackForWebDriver(String name, Future Function(String?) impl) {
}
WasmDatabaseOpener get _opener {
Future<Uint8List> Function()? initializeDatabase;
switch (initializationMode) {
case InitializationMode.loadAsset:
initializeDatabase = () async {
final response = await http.get(Uri.parse('/initial.db'));
return response.bodyBytes;
};
case InitializationMode.migrateCustomWasmDatabase:
initializeDatabase = () async {
// Let's first open a custom WasmDatabase, the way it would have been
// done before WasmDatabase.open.
final sqlite3 =
await WasmSqlite3.loadFromUrl(Uri.parse('/sqlite3.wasm'));
final fs = await IndexedDbFileSystem.open(dbName: dbName);
sqlite3.registerVirtualFileSystem(fs, makeDefault: true);
final wasmDb = WasmDatabase(sqlite3: sqlite3, path: '/app.db');
final db = TestDatabase(wasmDb);
await db
.into(db.testTable)
.insert(TestTableCompanion.insert(content: 'from old database'));
await db.close();
final (file: file, outFlags: _) =
fs.xOpen(Sqlite3Filename('/app.db'), 0);
final blob = Uint8List(file.xFileSize());
file.xRead(blob, 0);
file.xClose();
fs.xDelete('/app.db', 0);
await fs.close();
return blob;
};
break;
case InitializationMode.none:
break;
}
return WasmDatabaseOpener(
databaseName: dbName,
sqlite3WasmUri: Uri.parse('/sqlite3.wasm'),
driftWorkerUri: Uri.parse('/worker.dart.js'),
initializeDatabase: _loadFromInitializer
? () async {
final response = await http.get(Uri.parse('/initial.db'));
return response.bodyBytes;
}
: null,
initializeDatabase: initializeDatabase,
);
}