Skip to content

[Feature] Web Support #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 4, 2024
Merged
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
50 changes: 50 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This triggers whenever a tagged release is pushed
name: Compile Assets and Create Draft Release

on:
push:
tags:
# Trigger on tags beginning with 'v'
- 'v*'

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # Needed to create releases

steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Need to fetch the tags

- uses: dart-lang/setup-dart@v1

- name: Install dependencies
run: dart pub get

- name: Compile WebWorker
run: dart compile js -o assets/db_worker.js -O0 lib/src/web/worker/worker.dart

- name: Set tag name
id: tag
run: |
tag=$(basename "${{ github.ref }}")
echo "tag=$tag" >> $GITHUB_OUTPUT

- name: Create Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="${{ steps.tag.outputs.tag }}"
body="Release $tag"
gh release create --draft "$tag" --title "$tag" --notes "$body"

- name: Upload Worker
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
gh release upload "${{ steps.tag.outputs.tag }}" assets/db_worker.js
9 changes: 7 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,14 @@ jobs:
run: dart pub get

- name: Install SQLite
run: ./scripts/install_sqlite.sh ${{ matrix.sqlite_version }} ${{ matrix.sqlite_url }}
run: |
./scripts/install_sqlite.sh ${{ matrix.sqlite_version }} ${{ matrix.sqlite_url }}
mkdir -p assets && curl -LJ https://github.com/simolus3/sqlite3.dart/releases/download/sqlite3-2.4.3/sqlite3.wasm -o assets/sqlite3.wasm

- name: Compile WebWorker
run: dart compile js -o assets/db_worker.js -O0 lib/src/web/worker/worker.dart

- name: Run Tests
run: |
export LD_LIBRARY_PATH=./sqlite-autoconf-${{ matrix.sqlite_version }}/.libs
dart test
dart test -p vm,chrome
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

# Test assets
assets

.idea
.vscode
.devcontainer
*.db
*.db-*
test-db
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
## 0.8.0

- Added web support (web functionality is in beta)

## 0.7.0

- BREAKING CHANGE: Update all Database types to use a `CommonDatabase` interface.
- Update `openDB` and `open` methods to be synchronous.
- Fix `ArgumentError (Invalid argument(s): argument value for 'return_value' is null)` in sqlite3 when closing the database connection by upgrading to version 2.4.4.

## 0.7.0-alpha.5

- The dependency for the `Drift` package is now removed in favour of using the new `sqlite3_web` package.
- A new implementation for WebDatabase is used for SQL database connections on web.
- New exports are added for downstream consumers of this package to extended custom workers with custom SQLite function capabilities.
- Update minimum Dart SDK to 3.4.0

## 0.7.0-alpha.4

- Add latest changes from master

## 0.7.0-alpha.3

- Add latest changes from master

## 0.7.0-alpha.2

- Fix re-using a shared Mutex from <https://github.com/powersync-ja/sqlite_async.dart/pull/31>

## 0.7.0-alpha.1

- Added initial support for web platform.

## 0.6.1

- Fix errors when closing a `SqliteDatabase`.
Expand Down
13 changes: 13 additions & 0 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Developing Instructions

## Testing

Running tests for the `web` platform requires some preparation to be executed. The `sqlite3.wasm` and `db_worker.js` files need to be available in the Git ignored `./assets` folder.

See the [test action](./.github/workflows/test.yaml) for the latest steps.

On your local machine run the commands from the `Install SQLite`, `Compile WebWorker` and `Run Tests` steps.

## Releases

Web worker files are compiled and uploaded to draft Github releases whenever tags matching `v*` are pushed. These tags are created when versioning. Releases should be manually finalized and published when releasing new package versions.
42 changes: 33 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# sqlite_async
# sqlite_async

High-performance asynchronous interface for SQLite on Dart & Flutter.

Expand All @@ -15,13 +15,13 @@ query access.

## Features

* All operations are asynchronous by default - does not block the main isolate.
* Watch a query to automatically re-run on changes to the underlying data.
* Concurrent transactions supported by default - one write transaction and many multiple read transactions.
* Uses WAL mode for fast writes and running read transactions concurrently with a write transaction.
* Direct synchronous access in an isolate is supported for performance-sensitive use cases.
* Automatically convert query args to JSON where applicable, making JSON1 operations simple.
* Direct SQL queries - no wrapper classes or code generation required.
- All operations are asynchronous by default - does not block the main isolate.
- Watch a query to automatically re-run on changes to the underlying data.
- Concurrent transactions supported by default - one write transaction and many multiple read transactions.
- Uses WAL mode for fast writes and running read transactions concurrently with a write transaction.
- Direct synchronous access in an isolate is supported for performance-sensitive use cases.
- Automatically convert query args to JSON where applicable, making JSON1 operations simple.
- Direct SQL queries - no wrapper classes or code generation required.

See this [blog post](https://www.powersync.co/blog/sqlite-optimizations-for-ultra-high-performance),
explaining why these features are important for using SQLite.
Expand Down Expand Up @@ -74,4 +74,28 @@ void main() async {

await db.close();
}
```
```

# Web

Note: Web support is currently in Beta.

Web support requires Sqlite3 WASM and web worker Javascript files to be accessible via configurable URIs.

Default URIs are shown in the example below. URIs only need to be specified if they differ from default values.

The compiled web worker files can be found in our Github [releases](https://github.com/powersync-ja/sqlite_async.dart/releases)
The `sqlite3.wasm` asset can be found [here](https://github.com/simolus3/sqlite3.dart/releases)

Setup

```Dart
import 'package:sqlite_async/sqlite_async.dart';

final db = SqliteDatabase(
path: 'test.db',
options: SqliteOptions(
webSqliteOptions: WebSqliteOptions(
wasmUri: 'sqlite3.wasm', workerUri: 'db_worker.js')));

```
5 changes: 5 additions & 0 deletions lib/sqlite3_wasm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Re-exports [sqlite3 WASM](https://pub.dev/packages/sqlite3) to expose sqlite3 without
/// adding it as a direct dependency.
library;

export 'package:sqlite3/wasm.dart';
5 changes: 5 additions & 0 deletions lib/sqlite3_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Re-exports [sqlite3_web](https://pub.dev/packages/sqlite3_web) to expose sqlite3_web without
/// adding it as a direct dependency.
library;

export 'package:sqlite3_web/sqlite3_web.dart';
3 changes: 3 additions & 0 deletions lib/sqlite3_web_worker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Items required for extending custom web workers
export './src/web/worker/worker_utils.dart';
export './src/web/protocol.dart';
3 changes: 0 additions & 3 deletions lib/src/common/sqlite_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ mixin SqliteDatabaseMixin implements SqliteConnection, SqliteQueries {
@override
Stream<UpdateNotification> get updates;

final StreamController<UpdateNotification> updatesController =
StreamController.broadcast();

@protected
Future<void> get isInitialized;

Expand Down
4 changes: 3 additions & 1 deletion lib/src/impl/isolate_connection_factory_impl.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export 'stub_isolate_connection_factory.dart'
// ignore: uri_does_not_exist
if (dart.library.io) '../native/native_isolate_connection_factory.dart';
if (dart.library.io) '../native/native_isolate_connection_factory.dart'
// ignore: uri_does_not_exist
if (dart.library.html) '../web/web_isolate_connection_factory.dart';
4 changes: 3 additions & 1 deletion lib/src/impl/mutex_impl.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export 'stub_mutex.dart'
// ignore: uri_does_not_exist
if (dart.library.io) '../native/native_isolate_mutex.dart';
if (dart.library.io) '../native/native_isolate_mutex.dart'
// ignore: uri_does_not_exist
if (dart.library.html) '../web/web_mutex.dart';
4 changes: 3 additions & 1 deletion lib/src/impl/open_factory_impl.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export 'stub_sqlite_open_factory.dart'
// ignore: uri_does_not_exist
if (dart.library.io) '../native/native_sqlite_open_factory.dart';
if (dart.library.io) '../native/native_sqlite_open_factory.dart'
// ignore: uri_does_not_exist
if (dart.library.html) '../web/web_sqlite_open_factory.dart';
4 changes: 3 additions & 1 deletion lib/src/impl/sqlite_database_impl.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export 'stub_sqlite_database.dart'
// ignore: uri_does_not_exist
if (dart.library.io) '../native/database/native_sqlite_database.dart';
if (dart.library.io) '../native/database/native_sqlite_database.dart'
// ignore: uri_does_not_exist
if (dart.library.html) '../web/database/web_sqlite_database.dart';
4 changes: 2 additions & 2 deletions lib/src/native/database/connection_pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import 'dart:async';
import 'dart:collection';

import 'package:sqlite_async/sqlite_async.dart';
import '../database/native_sqlite_connection_impl.dart';
import '../native_isolate_mutex.dart';
import 'package:sqlite_async/src/native/database/native_sqlite_connection_impl.dart';
import 'package:sqlite_async/src/native/native_isolate_mutex.dart';

/// A connection pool with a single write connection and multiple read connections.
class SqliteConnectionPool with SqliteQueries implements SqliteConnection {
Expand Down
18 changes: 9 additions & 9 deletions lib/src/native/database/native_sqlite_connection_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import 'dart:isolate';

import 'package:sqlite3/sqlite3.dart' as sqlite;
import 'package:sqlite_async/sqlite3_common.dart';
import '../../common/abstract_open_factory.dart';
import '../../common/mutex.dart';
import '../../common/port_channel.dart';
import '../../native/native_isolate_mutex.dart';
import '../../sqlite_connection.dart';
import '../../sqlite_queries.dart';
import '../../update_notification.dart';
import '../../utils/shared_utils.dart';
import 'package:sqlite_async/src/common/abstract_open_factory.dart';
import 'package:sqlite_async/src/common/mutex.dart';
import 'package:sqlite_async/src/common/port_channel.dart';
import 'package:sqlite_async/src/native/native_isolate_mutex.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'package:sqlite_async/src/sqlite_queries.dart';
import 'package:sqlite_async/src/update_notification.dart';
import 'package:sqlite_async/src/utils/shared_utils.dart';

import 'upstream_updates.dart';

Expand Down Expand Up @@ -42,6 +42,7 @@ class SqliteConnectionImpl
this.readOnly = false,
bool primary = false})
: _writeMutex = mutex {
isInitialized = _isolateClient.ready;
this.upstreamPort = upstreamPort ?? listenForEvents();
// Accept an incoming stream of updates, or expose one if not given.
this.updates = updates ?? updatesController.stream;
Expand Down Expand Up @@ -88,7 +89,6 @@ class SqliteConnectionImpl
paused: true);
_isolateClient.tieToIsolate(_isolate);
_isolate.resume(_isolate.pauseCapability!);
isInitialized = _isolateClient.ready;
await _isolateClient.ready;
});
}
Expand Down
25 changes: 14 additions & 11 deletions lib/src/native/database/native_sqlite_database.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import 'dart:async';

import 'package:meta/meta.dart';
import '../../common/abstract_open_factory.dart';
import '../../common/sqlite_database.dart';
import '../../native/database/connection_pool.dart';
import '../../native/database/native_sqlite_connection_impl.dart';
import '../../native/native_isolate_connection_factory.dart';
import '../../native/native_isolate_mutex.dart';
import '../../native/native_sqlite_open_factory.dart';
import '../../sqlite_connection.dart';
import '../../sqlite_options.dart';
import '../../sqlite_queries.dart';
import '../../update_notification.dart';
import 'package:sqlite_async/src/common/abstract_open_factory.dart';
import 'package:sqlite_async/src/common/sqlite_database.dart';
import 'package:sqlite_async/src/native/database/connection_pool.dart';
import 'package:sqlite_async/src/native/database/native_sqlite_connection_impl.dart';
import 'package:sqlite_async/src/native/native_isolate_connection_factory.dart';
import 'package:sqlite_async/src/native/native_isolate_mutex.dart';
import 'package:sqlite_async/src/native/native_sqlite_open_factory.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'package:sqlite_async/src/sqlite_options.dart';
import 'package:sqlite_async/src/sqlite_queries.dart';
import 'package:sqlite_async/src/update_notification.dart';

/// A SQLite database instance.
///
Expand Down Expand Up @@ -40,6 +40,9 @@ class SqliteDatabaseImpl
late final SqliteConnectionImpl _internalConnection;
late final SqliteConnectionPool _pool;

final StreamController<UpdateNotification> updatesController =
StreamController.broadcast();

/// Open a SqliteDatabase.
///
/// Only a single SqliteDatabase per [path] should be opened at a time.
Expand Down
8 changes: 4 additions & 4 deletions lib/src/native/database/upstream_updates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import 'dart:async';
import 'dart:isolate';

import 'package:meta/meta.dart';
import '../../common/port_channel.dart';
import '../../update_notification.dart';
import '../../utils/native_database_utils.dart';
import '../../utils/shared_utils.dart';
import 'package:sqlite_async/src/common/port_channel.dart';
import 'package:sqlite_async/src/update_notification.dart';
import 'package:sqlite_async/src/utils/native_database_utils.dart';
import 'package:sqlite_async/src/utils/shared_utils.dart';

mixin UpStreamTableUpdates {
final StreamController<UpdateNotification> updatesController =
Expand Down
2 changes: 1 addition & 1 deletion lib/src/sqlite_migrations.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';

import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite3/common.dart';

import 'sqlite_connection.dart';

Expand Down
16 changes: 16 additions & 0 deletions lib/src/sqlite_options.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
class WebSqliteOptions {
final String workerUri;
final String wasmUri;

const WebSqliteOptions.defaults()
: workerUri = 'db_worker.js',
wasmUri = 'sqlite3.wasm';

const WebSqliteOptions(
{this.wasmUri = 'sqlite3.wasm', this.workerUri = 'db_worker.js'});
}

class SqliteOptions {
/// SQLite journal mode. Defaults to [SqliteJournalMode.wal].
final SqliteJournalMode? journalMode;
Expand All @@ -11,6 +23,8 @@ class SqliteOptions {
/// attempt to truncate the file afterwards.
final int? journalSizeLimit;

final WebSqliteOptions webSqliteOptions;

/// Timeout waiting for locks to be released by other connections.
/// Defaults to 30 seconds.
/// Set to null or [Duration.zero] to fail immediately when the database is locked.
Expand All @@ -20,12 +34,14 @@ class SqliteOptions {
: journalMode = SqliteJournalMode.wal,
journalSizeLimit = 6 * 1024 * 1024, // 1.5x the default checkpoint size
synchronous = SqliteSynchronous.normal,
webSqliteOptions = const WebSqliteOptions.defaults(),
lockTimeout = const Duration(seconds: 30);

const SqliteOptions(
{this.journalMode = SqliteJournalMode.wal,
this.journalSizeLimit = 6 * 1024 * 1024,
this.synchronous = SqliteSynchronous.normal,
this.webSqliteOptions = const WebSqliteOptions.defaults(),
this.lockTimeout = const Duration(seconds: 30)});
}

Expand Down
Loading
Loading