diff --git a/.github/workflows/prepare_wasm.yml b/.github/workflows/prepare_wasm.yml
index b38aa7bc..5da977c1 100644
--- a/.github/workflows/prepare_wasm.yml
+++ b/.github/workflows/prepare_wasm.yml
@@ -25,7 +25,7 @@ jobs:
uses: dart-lang/setup-dart@v1
- name: Setup macOS build dependencies
if: steps.cache_build.outputs.cache-hit != 'true'
- run: brew install cmake llvm lld binaryen wasi-libc wasi-runtimes
+ run: brew install llvm lld binaryen wasi-libc wasi-runtimes
- name: Compile sqlite3.wasm on macOS
if: steps.cache_build.outputs.cache-hit != 'true'
working-directory: packages/sqlite3_wasm_build
diff --git a/.gitignore b/.gitignore
index c47e4141..95fba1e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@ pubspec_overrides.yaml
.flutter-plugins-dependencies
.flutter-plugins
build
+**/doc/api
+.build
# Shared assets
assets/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8ffa2cc..e76e3a6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,159 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## 2025-10-06
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync` - `v1.16.1`](#powersync---v1161)
+ - [`powersync_core` - `v1.6.1`](#powersync_core---v161)
+ - [`powersync_sqlcipher` - `v0.1.13`](#powersync_sqlcipher---v0113)
+
+---
+
+#### `powersync` - `v1.16.1`
+
+ - Web: Fix decoding sync streams on status.
+
+#### `powersync_core` - `v1.6.1`
+
+ - Web: Fix decoding sync streams on status.
+
+ - **DOCS**: Point to uses in example. ([4f4da24e](https://github.com/powersync-ja/powersync.dart/commit/4f4da24e580dec6b1d29a5e0907b83ba7c55e3d8))
+
+#### `powersync_sqlcipher` - `v0.1.13`
+
+ - Web: Fix decoding sync streams on status.
+
+
+## 2025-10-02
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync_attachments_helper` - `v0.6.20`](#powersync_attachments_helper---v0620)
+ - [`powersync` - `v1.16.0`](#powersync---v1160)
+ - [`powersync_core` - `v1.6.0`](#powersync_core---v160)
+ - [`powersync_flutter_libs` - `v0.4.12`](#powersync_flutter_libs---v0412)
+ - [`powersync_sqlcipher` - `v0.1.12`](#powersync_sqlcipher---v0112)
+
+---
+
+#### `powersync_attachments_helper` - `v0.6.20`
+
+ - Add note about new attachment queue system in core package.
+
+#### `powersync` - `v1.16.0`
+#### `powersync_core` - `v1.6.0`
+#### `powersync_sqlcipher` - `v0.1.12`
+
+- Add `getCrudTransactions()` returning a stream of completed transactions for uploads.
+- Add experimental support for [sync streams](https://docs.powersync.com/usage/sync-streams).
+- Add new attachments helper implementation in `package:powersync_core/attachments/attachments.dart`.
+- Add SwiftPM support.
+- Add support for compiling `powersync_core` with `build_web_compilers`.
+
+#### `powersync_flutter_libs` - `v0.4.12`
+
+ - Update core extension.
+ - Add support for SwiftPM.
+
+## 2025-08-18
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync_attachments_helper` - `v0.6.19`](#powersync_attachments_helper---v0619)
+
+---
+
+#### `powersync_attachments_helper` - `v0.6.19`
+
+ - Remove direct dependency on `sqlite_async`.
+
+
+## 2025-08-14
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync_core` - `v1.5.2`](#powersync_core---v152)
+ - [`powersync` - `v1.15.2`](#powersync---v1152)
+ - [`powersync_sqlcipher` - `v0.1.11+1`](#powersync_sqlcipher---v01111)
+
+---
+
+#### `powersync_core` - `v1.5.2`
+
+ - Fix excessive memory consumption during large sync.
+
+#### `powersync` - `v1.15.2`
+
+ - Fix excessive memory consumption during large sync.
+
+#### `powersync_sqlcipher` - `v0.1.11+1`
+
+ - Fix excessive memory consumption during large sync.
+
+
+## 2025-08-11
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync_core` - `v1.5.1`](#powersync_core---v151)
+ - [`powersync` - `v1.15.1`](#powersync---v1151)
+ - [`powersync_sqlcipher` - `v0.1.11`](#powersync_sqlcipher---v0111)
+ - [`powersync_flutter_libs` - `v0.4.11`](#powersync_flutter_libs---v0411)
+
+---
+
+#### `powersync_core` - `v1.5.1`
+#### `powersync` - `v1.15.1`
+#### `powersync_sqlcipher` - `v0.1.11`
+#### `powersync_flutter_libs` - `v0.4.11`
+
+ - Support latest versions of `package:sqlite3` and `package:sqlite_async`.
+ - Stream client: Improve `disconnect()` while a connection is being opened.
+ - Stream client: Support binary sync lines with Rust client and compatible PowerSync service versions.
+ - Sync client: Improve parsing error responses.
+
## 2025-07-17
### Changes
diff --git a/demos/benchmarks/ios/Podfile b/demos/benchmarks/ios/Podfile
index d97f17e2..3e44f9c6 100644
--- a/demos/benchmarks/ios/Podfile
+++ b/demos/benchmarks/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/benchmarks/ios/Podfile.lock b/demos/benchmarks/ios/Podfile.lock
index 7a167d34..9d0fed8c 100644
--- a/demos/benchmarks/ios/Podfile.lock
+++ b/demos/benchmarks/ios/Podfile.lock
@@ -3,10 +3,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- sqlite3 (3.49.2):
- sqlite3/common (= 3.49.2)
- sqlite3/common (3.49.2)
@@ -52,13 +52,13 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
SPEC CHECKSUMS:
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
-PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
+PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5
COCOAPODS: 1.16.2
diff --git a/demos/benchmarks/macos/Podfile b/demos/benchmarks/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/benchmarks/macos/Podfile
+++ b/demos/benchmarks/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/benchmarks/macos/Podfile.lock b/demos/benchmarks/macos/Podfile.lock
index b1925cd1..76db1f9b 100644
--- a/demos/benchmarks/macos/Podfile.lock
+++ b/demos/benchmarks/macos/Podfile.lock
@@ -3,10 +3,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- sqlite3 (3.49.2):
- sqlite3/common (= 3.49.2)
- sqlite3/common (3.49.2)
@@ -52,13 +52,13 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
SPEC CHECKSUMS:
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/benchmarks/pubspec.lock b/demos/benchmarks/pubspec.lock
index 78420348..b45942df 100644
--- a/demos/benchmarks/pubspec.lock
+++ b/demos/benchmarks/pubspec.lock
@@ -111,10 +111,10 @@ packages:
dependency: "direct main"
description:
name: http
- sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
+ sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
url: "https://pub.dev"
source: hosted
- version: "1.4.0"
+ version: "1.5.0"
http_parser:
dependency: transitive
description:
@@ -135,26 +135,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
- sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
+ sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev"
source: hosted
- version: "10.0.9"
+ version: "11.0.1"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
- sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
- version: "3.0.9"
+ version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
- sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "3.0.2"
lints:
dependency: transitive
description:
@@ -281,21 +281,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.2"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.2"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.11"
pub_semver:
dependency: transitive
description:
@@ -337,10 +337,10 @@ packages:
dependency: transitive
description:
name: sqlite3
- sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e"
+ sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924
url: "https://pub.dev"
source: hosted
- version: "2.7.5"
+ version: "2.9.0"
sqlite3_flutter_libs:
dependency: transitive
description:
@@ -353,18 +353,18 @@ packages:
dependency: transitive
description:
name: sqlite3_web
- sha256: "967e076442f7e1233bd7241ca61f3efe4c7fc168dac0f38411bdb3bdf471eb3c"
+ sha256: "0f6ebcb4992d1892ac5c8b5ecd22a458ab9c5eb6428b11ae5ecb5d63545844da"
url: "https://pub.dev"
source: hosted
- version: "0.3.1"
+ version: "0.3.2"
sqlite_async:
dependency: "direct main"
description:
name: sqlite_async
- sha256: a60e8d5c8df8e694933bd5a312c38393e79ad77d784bb91c6f38ba627bfb7aec
+ sha256: "6116bfc6aef6ce77730b478385ba4a58873df45721f6a9bc6ffabf39b6576e36"
url: "https://pub.dev"
source: hosted
- version: "0.11.4"
+ version: "0.12.1"
stack_trace:
dependency: transitive
description:
@@ -401,10 +401,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
+ sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
- version: "0.7.4"
+ version: "0.7.6"
typed_data:
dependency: transitive
description:
@@ -433,10 +433,10 @@ packages:
dependency: transitive
description:
name: vector_math
- sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
- version: "2.1.4"
+ version: "2.2.0"
vm_service:
dependency: transitive
description:
@@ -470,5 +470,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
- dart: ">=3.7.0 <4.0.0"
+ dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.27.0"
diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml
index d2bb82f6..aea63c20 100644
--- a/demos/benchmarks/pubspec.yaml
+++ b/demos/benchmarks/pubspec.yaml
@@ -10,12 +10,12 @@ environment:
dependencies:
flutter:
sdk: flutter
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
path: ^1.8.3
logging: ^1.2.0
universal_io: ^2.2.2
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
http: ^1.2.2
dev_dependencies:
diff --git a/demos/django-todolist/ios/Podfile b/demos/django-todolist/ios/Podfile
index e9f73048..2c1e086a 100644
--- a/demos/django-todolist/ios/Podfile
+++ b/demos/django-todolist/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/django-todolist/ios/Podfile.lock b/demos/django-todolist/ios/Podfile.lock
index 92649cb7..8bfa9ae3 100644
--- a/demos/django-todolist/ios/Podfile.lock
+++ b/demos/django-todolist/ios/Podfile.lock
@@ -3,10 +3,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -58,14 +58,14 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
SPEC CHECKSUMS:
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
-PODFILE CHECKSUM: f7b3cb7384a2d5da4b22b090e1f632de7f377987
+PODFILE CHECKSUM: 2c1730c97ea13f1ea48b32e9c79de785b4f2f02f
COCOAPODS: 1.16.2
diff --git a/demos/django-todolist/lib/widgets/guard_by_sync.dart b/demos/django-todolist/lib/widgets/guard_by_sync.dart
index 000b5c8d..b65986b0 100644
--- a/demos/django-todolist/lib/widgets/guard_by_sync.dart
+++ b/demos/django-todolist/lib/widgets/guard_by_sync.dart
@@ -7,9 +7,9 @@ import 'package:powersync_django_todolist_demo/powersync.dart';
class GuardBySync extends StatelessWidget {
final Widget child;
- /// When set, wait only for a complete sync within the [BucketPriority]
+ /// When set, wait only for a complete sync within the [StreamPriority]
/// instead of a full sync.
- final BucketPriority? priority;
+ final StreamPriority? priority;
const GuardBySync({
super.key,
diff --git a/demos/django-todolist/macos/Podfile b/demos/django-todolist/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/django-todolist/macos/Podfile
+++ b/demos/django-todolist/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/django-todolist/macos/Podfile.lock b/demos/django-todolist/macos/Podfile.lock
index 249a5b49..c6edd223 100644
--- a/demos/django-todolist/macos/Podfile.lock
+++ b/demos/django-todolist/macos/Podfile.lock
@@ -3,10 +3,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -58,14 +58,14 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
SPEC CHECKSUMS:
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/django-todolist/pubspec.lock b/demos/django-todolist/pubspec.lock
index e627551d..881d6d47 100644
--- a/demos/django-todolist/pubspec.lock
+++ b/demos/django-todolist/pubspec.lock
@@ -294,21 +294,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
pub_semver:
dependency: transitive
description:
diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml
index 77d13739..4c1cacff 100644
--- a/demos/django-todolist/pubspec.yaml
+++ b/demos/django-todolist/pubspec.yaml
@@ -10,11 +10,11 @@ environment:
dependencies:
flutter:
sdk: flutter
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
path: ^1.8.3
logging: ^1.2.0
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
http: ^1.2.1
shared_preferences: ^2.2.3
diff --git a/demos/firebase-nodejs-todolist/ios/Podfile.lock b/demos/firebase-nodejs-todolist/ios/Podfile.lock
index e3b3429e..f5b33ded 100644
--- a/demos/firebase-nodejs-todolist/ios/Podfile.lock
+++ b/demos/firebase-nodejs-todolist/ios/Podfile.lock
@@ -58,10 +58,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- RecaptchaInterop (101.0.0)
- shared_preferences_foundation (0.0.1):
- Flutter
@@ -148,12 +148,12 @@ SPEC CHECKSUMS:
FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac
FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
diff --git a/demos/firebase-nodejs-todolist/macos/Podfile b/demos/firebase-nodejs-todolist/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/firebase-nodejs-todolist/macos/Podfile
+++ b/demos/firebase-nodejs-todolist/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/firebase-nodejs-todolist/macos/Podfile.lock b/demos/firebase-nodejs-todolist/macos/Podfile.lock
index f971793a..fc4bb3aa 100644
--- a/demos/firebase-nodejs-todolist/macos/Podfile.lock
+++ b/demos/firebase-nodejs-todolist/macos/Podfile.lock
@@ -1,28 +1,91 @@
PODS:
- app_links (1.0.0):
- FlutterMacOS
+ - Firebase/Auth (11.10.0):
+ - Firebase/CoreOnly
+ - FirebaseAuth (~> 11.10.0)
+ - Firebase/CoreOnly (11.10.0):
+ - FirebaseCore (~> 11.10.0)
+ - firebase_auth (5.5.3):
+ - Firebase/Auth (~> 11.10.0)
+ - Firebase/CoreOnly (~> 11.10.0)
+ - firebase_core
+ - FlutterMacOS
+ - firebase_core (3.13.0):
+ - Firebase/CoreOnly (~> 11.10.0)
+ - FlutterMacOS
+ - FirebaseAppCheckInterop (11.15.0)
+ - FirebaseAuth (11.10.0):
+ - FirebaseAppCheckInterop (~> 11.0)
+ - FirebaseAuthInterop (~> 11.0)
+ - FirebaseCore (~> 11.10.0)
+ - FirebaseCoreExtension (~> 11.10.0)
+ - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
+ - GoogleUtilities/Environment (~> 8.0)
+ - GTMSessionFetcher/Core (< 5.0, >= 3.4)
+ - RecaptchaInterop (~> 101.0)
+ - FirebaseAuthInterop (11.15.0)
+ - FirebaseCore (11.10.0):
+ - FirebaseCoreInternal (~> 11.10.0)
+ - GoogleUtilities/Environment (~> 8.0)
+ - GoogleUtilities/Logger (~> 8.0)
+ - FirebaseCoreExtension (11.10.0):
+ - FirebaseCore (~> 11.10.0)
+ - FirebaseCoreInternal (11.10.0):
+ - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FlutterMacOS (1.0.0)
+ - GoogleUtilities/AppDelegateSwizzler (8.1.0):
+ - GoogleUtilities/Environment
+ - GoogleUtilities/Logger
+ - GoogleUtilities/Network
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Environment (8.1.0):
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Logger (8.1.0):
+ - GoogleUtilities/Environment
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Network (8.1.0):
+ - GoogleUtilities/Logger
+ - "GoogleUtilities/NSData+zlib"
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Reachability
+ - "GoogleUtilities/NSData+zlib (8.1.0)":
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Privacy (8.1.0)
+ - GoogleUtilities/Reachability (8.1.0):
+ - GoogleUtilities/Logger
+ - GoogleUtilities/Privacy
+ - GTMSessionFetcher/Core (4.5.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
+ - powersync-sqlite-core (0.4.5)
+ - powersync_flutter_libs (0.0.1):
+ - FlutterMacOS
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - sign_in_with_apple (0.0.1):
- - FlutterMacOS
- - sqlite3 (3.41.2):
- - sqlite3/common (= 3.41.2)
- - sqlite3/common (3.41.2)
- - sqlite3/fts5 (3.41.2):
+ - sqlite3 (3.49.2):
+ - sqlite3/common (= 3.49.2)
+ - sqlite3/common (3.49.2)
+ - sqlite3/dbstatvtab (3.49.2):
- sqlite3/common
- - sqlite3/perf-threadsafe (3.41.2):
+ - sqlite3/fts5 (3.49.2):
- sqlite3/common
- - sqlite3/rtree (3.41.2):
+ - sqlite3/math (3.49.2):
+ - sqlite3/common
+ - sqlite3/perf-threadsafe (3.49.2):
+ - sqlite3/common
+ - sqlite3/rtree (3.49.2):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
+ - Flutter
- FlutterMacOS
- - sqlite3 (~> 3.41.2)
+ - sqlite3 (~> 3.49.1)
+ - sqlite3/dbstatvtab
- sqlite3/fts5
+ - sqlite3/math
- sqlite3/perf-threadsafe
- sqlite3/rtree
- url_launcher_macos (0.0.1):
@@ -30,43 +93,71 @@ PODS:
DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
+ - firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
+ - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
+ - powersync_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`)
- - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
+ - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
SPEC REPOS:
trunk:
+ - Firebase
+ - FirebaseAppCheckInterop
+ - FirebaseAuth
+ - FirebaseAuthInterop
+ - FirebaseCore
+ - FirebaseCoreExtension
+ - FirebaseCoreInternal
+ - GoogleUtilities
+ - GTMSessionFetcher
+ - powersync-sqlite-core
- sqlite3
EXTERNAL SOURCES:
app_links:
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
+ firebase_auth:
+ :path: Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos
+ firebase_core:
+ :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
+ powersync_flutter_libs:
+ :path: Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
- sign_in_with_apple:
- :path: Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos
sqlite3_flutter_libs:
- :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos
+ :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS:
- app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
- path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
- shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
- sign_in_with_apple: a9e97e744e8edc36aefc2723111f652102a7a727
- sqlite3: fd89671d969f3e73efe503ce203e28b016b58f68
- sqlite3_flutter_libs: 00a50503d69f7ab0fe85a5ff25b33082f4df4ce9
- url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
+ app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
+ Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
+ firebase_auth: d9a868727e64a42540f791ffb5a656afa0c29a58
+ firebase_core: efd50ad8177dc489af1b9163a560359cf1b30597
+ FirebaseAppCheckInterop: 06fe5a3799278ae4667e6c432edd86b1030fa3df
+ FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32
+ FirebaseAuthInterop: 7087d7a4ee4bc4de019b2d0c240974ed5d89e2fd
+ FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
+ FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac
+ FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
+ GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
+ GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf
+ path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
+ shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
+ sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
+ sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
+ url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
-COCOAPODS: 1.12.1
+COCOAPODS: 1.16.2
diff --git a/demos/firebase-nodejs-todolist/pubspec.lock b/demos/firebase-nodejs-todolist/pubspec.lock
index 96bd7e21..5eead021 100644
--- a/demos/firebase-nodejs-todolist/pubspec.lock
+++ b/demos/firebase-nodejs-todolist/pubspec.lock
@@ -430,21 +430,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
pub_semver:
dependency: transitive
description:
diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml
index e86b714a..de6ea1fe 100644
--- a/demos/firebase-nodejs-todolist/pubspec.yaml
+++ b/demos/firebase-nodejs-todolist/pubspec.yaml
@@ -11,7 +11,7 @@ dependencies:
flutter:
sdk: flutter
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
supabase_flutter: ^2.0.1
path: ^1.8.3
diff --git a/demos/supabase-anonymous-auth/ios/Podfile b/demos/supabase-anonymous-auth/ios/Podfile
index 656de635..2c1e086a 100644
--- a/demos/supabase-anonymous-auth/ios/Podfile
+++ b/demos/supabase-anonymous-auth/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-anonymous-auth/ios/Podfile.lock b/demos/supabase-anonymous-auth/ios/Podfile.lock
index 6553952e..77f7310a 100644
--- a/demos/supabase-anonymous-auth/ios/Podfile.lock
+++ b/demos/supabase-anonymous-auth/ios/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-PODFILE CHECKSUM: 13e359f40c4925bcdf0c1bfa13aeba35011fde30
+PODFILE CHECKSUM: 2c1730c97ea13f1ea48b32e9c79de785b4f2f02f
COCOAPODS: 1.16.2
diff --git a/demos/supabase-anonymous-auth/macos/Podfile b/demos/supabase-anonymous-auth/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/supabase-anonymous-auth/macos/Podfile
+++ b/demos/supabase-anonymous-auth/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-anonymous-auth/macos/Podfile.lock b/demos/supabase-anonymous-auth/macos/Podfile.lock
index f32a7411..6983b2da 100644
--- a/demos/supabase-anonymous-auth/macos/Podfile.lock
+++ b/demos/supabase-anonymous-auth/macos/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/supabase-anonymous-auth/pubspec.lock b/demos/supabase-anonymous-auth/pubspec.lock
index e3838774..48c25b82 100644
--- a/demos/supabase-anonymous-auth/pubspec.lock
+++ b/demos/supabase-anonymous-auth/pubspec.lock
@@ -374,21 +374,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
pub_semver:
dependency: transitive
description:
diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml
index 7d4604d3..b4f44bcb 100644
--- a/demos/supabase-anonymous-auth/pubspec.yaml
+++ b/demos/supabase-anonymous-auth/pubspec.yaml
@@ -11,12 +11,12 @@ dependencies:
flutter:
sdk: flutter
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
supabase_flutter: ^2.0.2
path: ^1.8.3
logging: ^1.2.0
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
universal_io: ^2.2.2
dev_dependencies:
diff --git a/demos/supabase-edge-function-auth/ios/Podfile b/demos/supabase-edge-function-auth/ios/Podfile
index 656de635..2c1e086a 100644
--- a/demos/supabase-edge-function-auth/ios/Podfile
+++ b/demos/supabase-edge-function-auth/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-edge-function-auth/ios/Podfile.lock b/demos/supabase-edge-function-auth/ios/Podfile.lock
index 6553952e..77f7310a 100644
--- a/demos/supabase-edge-function-auth/ios/Podfile.lock
+++ b/demos/supabase-edge-function-auth/ios/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-PODFILE CHECKSUM: 13e359f40c4925bcdf0c1bfa13aeba35011fde30
+PODFILE CHECKSUM: 2c1730c97ea13f1ea48b32e9c79de785b4f2f02f
COCOAPODS: 1.16.2
diff --git a/demos/supabase-edge-function-auth/macos/Podfile b/demos/supabase-edge-function-auth/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/supabase-edge-function-auth/macos/Podfile
+++ b/demos/supabase-edge-function-auth/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-edge-function-auth/macos/Podfile.lock b/demos/supabase-edge-function-auth/macos/Podfile.lock
index f32a7411..6983b2da 100644
--- a/demos/supabase-edge-function-auth/macos/Podfile.lock
+++ b/demos/supabase-edge-function-auth/macos/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/supabase-edge-function-auth/pubspec.lock b/demos/supabase-edge-function-auth/pubspec.lock
index e3838774..48c25b82 100644
--- a/demos/supabase-edge-function-auth/pubspec.lock
+++ b/demos/supabase-edge-function-auth/pubspec.lock
@@ -374,21 +374,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
pub_semver:
dependency: transitive
description:
diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml
index 4d681973..f1f5ddcb 100644
--- a/demos/supabase-edge-function-auth/pubspec.yaml
+++ b/demos/supabase-edge-function-auth/pubspec.yaml
@@ -11,12 +11,12 @@ dependencies:
flutter:
sdk: flutter
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
supabase_flutter: ^2.0.2
path: ^1.8.3
logging: ^1.2.0
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
universal_io: ^2.2.2
dev_dependencies:
diff --git a/demos/supabase-simple-chat/ios/Podfile b/demos/supabase-simple-chat/ios/Podfile
index 164df534..3e44f9c6 100644
--- a/demos/supabase-simple-chat/ios/Podfile
+++ b/demos/supabase-simple-chat/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-simple-chat/ios/Podfile.lock b/demos/supabase-simple-chat/ios/Podfile.lock
index 12271547..6108d2f1 100644
--- a/demos/supabase-simple-chat/ios/Podfile.lock
+++ b/demos/supabase-simple-chat/ios/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-PODFILE CHECKSUM: 7be2f5f74864d463a8ad433546ed1de7e0f29aef
+PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5
COCOAPODS: 1.16.2
diff --git a/demos/supabase-simple-chat/macos/Podfile b/demos/supabase-simple-chat/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/supabase-simple-chat/macos/Podfile
+++ b/demos/supabase-simple-chat/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-simple-chat/macos/Podfile.lock b/demos/supabase-simple-chat/macos/Podfile.lock
index f32a7411..6983b2da 100644
--- a/demos/supabase-simple-chat/macos/Podfile.lock
+++ b/demos/supabase-simple-chat/macos/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/supabase-simple-chat/pubspec.lock b/demos/supabase-simple-chat/pubspec.lock
index 8ea11c3d..d65ac498 100644
--- a/demos/supabase-simple-chat/pubspec.lock
+++ b/demos/supabase-simple-chat/pubspec.lock
@@ -390,21 +390,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
pub_semver:
dependency: transitive
description:
diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml
index 15afa2fd..463278d2 100644
--- a/demos/supabase-simple-chat/pubspec.yaml
+++ b/demos/supabase-simple-chat/pubspec.yaml
@@ -37,7 +37,7 @@ dependencies:
supabase_flutter: ^2.0.2
timeago: ^3.6.0
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
path: ^1.8.3
logging: ^1.2.0
diff --git a/demos/supabase-todolist-drift/ios/Flutter/AppFrameworkInfo.plist b/demos/supabase-todolist-drift/ios/Flutter/AppFrameworkInfo.plist
index 7c569640..1dc6cf76 100644
--- a/demos/supabase-todolist-drift/ios/Flutter/AppFrameworkInfo.plist
+++ b/demos/supabase-todolist-drift/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/demos/supabase-todolist-drift/ios/Podfile b/demos/supabase-todolist-drift/ios/Podfile
deleted file mode 100644
index d97f17e2..00000000
--- a/demos/supabase-todolist-drift/ios/Podfile
+++ /dev/null
@@ -1,44 +0,0 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
- target 'RunnerTests' do
- inherit! :search_paths
- end
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- end
-end
diff --git a/demos/supabase-todolist-drift/ios/Podfile.lock b/demos/supabase-todolist-drift/ios/Podfile.lock
deleted file mode 100644
index 1af06a74..00000000
--- a/demos/supabase-todolist-drift/ios/Podfile.lock
+++ /dev/null
@@ -1,89 +0,0 @@
-PODS:
- - app_links (0.0.2):
- - Flutter
- - camera_avfoundation (0.0.1):
- - Flutter
- - Flutter (1.0.0)
- - path_provider_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- - powersync-sqlite-core (0.4.2)
- - powersync_flutter_libs (0.0.1):
- - Flutter
- - powersync-sqlite-core (~> 0.4.2)
- - shared_preferences_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- - sqlite3 (3.49.2):
- - sqlite3/common (= 3.49.2)
- - sqlite3/common (3.49.2)
- - sqlite3/dbstatvtab (3.49.2):
- - sqlite3/common
- - sqlite3/fts5 (3.49.2):
- - sqlite3/common
- - sqlite3/math (3.49.2):
- - sqlite3/common
- - sqlite3/perf-threadsafe (3.49.2):
- - sqlite3/common
- - sqlite3/rtree (3.49.2):
- - sqlite3/common
- - sqlite3_flutter_libs (0.0.1):
- - Flutter
- - FlutterMacOS
- - sqlite3 (~> 3.49.1)
- - sqlite3/dbstatvtab
- - sqlite3/fts5
- - sqlite3/math
- - sqlite3/perf-threadsafe
- - sqlite3/rtree
- - url_launcher_ios (0.0.1):
- - Flutter
-
-DEPENDENCIES:
- - app_links (from `.symlinks/plugins/app_links/ios`)
- - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- - Flutter (from `Flutter`)
- - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- - powersync_flutter_libs (from `.symlinks/plugins/powersync_flutter_libs/ios`)
- - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
-
-SPEC REPOS:
- trunk:
- - powersync-sqlite-core
- - sqlite3
-
-EXTERNAL SOURCES:
- app_links:
- :path: ".symlinks/plugins/app_links/ios"
- camera_avfoundation:
- :path: ".symlinks/plugins/camera_avfoundation/ios"
- Flutter:
- :path: Flutter
- path_provider_foundation:
- :path: ".symlinks/plugins/path_provider_foundation/darwin"
- powersync_flutter_libs:
- :path: ".symlinks/plugins/powersync_flutter_libs/ios"
- shared_preferences_foundation:
- :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
- sqlite3_flutter_libs:
- :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
- url_launcher_ios:
- :path: ".symlinks/plugins/url_launcher_ios/ios"
-
-SPEC CHECKSUMS:
- app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
- camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
- path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
- shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
- sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
- sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
- url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-
-PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
-
-COCOAPODS: 1.16.2
diff --git a/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.pbxproj b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.pbxproj
index 94547304..ad2605c8 100644
--- a/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.pbxproj
+++ b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.pbxproj
@@ -8,11 +8,10 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 2AA95F4BD1106733AA65B3D1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7F26FE8A2AE7EB1DCD03A63 /* Pods_RunnerTests.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 6B669B18D5922DF2E9FF3231 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DC37C372644D968F6312083 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -44,16 +43,12 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 15AA756A195A0498929C7AB8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 4ABD541213112FCC0D84BBA6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
- 53F1849573A471693AAF7757 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
- 5AB1DDF473ECE5996A267CFC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
- 5DC37C372644D968F6312083 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
@@ -62,9 +57,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- A15106F56CD7643EC0938932 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
- AC1EC468FE1100F19B83C528 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
- C7F26FE8A2AE7EB1DCD03A63 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -72,7 +64,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 2AA95F4BD1106733AA65B3D1 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -80,7 +71,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 6B669B18D5922DF2E9FF3231 /* Pods_Runner.framework in Frameworks */,
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -95,23 +86,10 @@
path = RunnerTests;
sourceTree = "";
};
- 775FDDC091D1B0F1643D5E75 /* Pods */ = {
- isa = PBXGroup;
- children = (
- 5AB1DDF473ECE5996A267CFC /* Pods-Runner.debug.xcconfig */,
- A15106F56CD7643EC0938932 /* Pods-Runner.release.xcconfig */,
- AC1EC468FE1100F19B83C528 /* Pods-Runner.profile.xcconfig */,
- 53F1849573A471693AAF7757 /* Pods-RunnerTests.debug.xcconfig */,
- 15AA756A195A0498929C7AB8 /* Pods-RunnerTests.release.xcconfig */,
- 4ABD541213112FCC0D84BBA6 /* Pods-RunnerTests.profile.xcconfig */,
- );
- name = Pods;
- path = Pods;
- sourceTree = "";
- };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -127,8 +105,6 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
- 775FDDC091D1B0F1643D5E75 /* Pods */,
- CD2D4C39902602D7512B2E46 /* Frameworks */,
);
sourceTree = "";
};
@@ -156,15 +132,6 @@
path = Runner;
sourceTree = "";
};
- CD2D4C39902602D7512B2E46 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 5DC37C372644D968F6312083 /* Pods_Runner.framework */,
- C7F26FE8A2AE7EB1DCD03A63 /* Pods_RunnerTests.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -172,7 +139,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 1BFE7CDE33D6F794EB1D704F /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
2127B9F9BB1BBDC30AACA133 /* Frameworks */,
@@ -191,20 +157,21 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 7978750E9AC7A32658AF3F71 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 25F5BBE07D381880BF0BA164 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
@@ -238,6 +205,9 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -270,45 +240,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 1BFE7CDE33D6F794EB1D704F /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 25F5BBE07D381880BF0BA164 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -325,28 +256,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 7978750E9AC7A32658AF3F71 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -455,7 +364,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -488,7 +397,6 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 53F1849573A471693AAF7757 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -506,7 +414,6 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 15AA756A195A0498929C7AB8 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -522,7 +429,6 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4ABD541213112FCC0D84BBA6 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -585,7 +491,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -636,7 +542,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -726,6 +632,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
diff --git a/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..8e5eb05f
--- /dev/null
+++ b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,22 @@
+{
+ "pins" : [
+ {
+ "identity" : "csqlite",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/simolus3/CSQLite.git",
+ "state" : {
+ "revision" : "a8d28afef08ad8faa4ee9ef7845f61c2e8ac5810"
+ }
+ },
+ {
+ "identity" : "powersync-sqlite-core-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
+ "state" : {
+ "revision" : "00776db5157c8648671b00e6673603144fafbfeb",
+ "version" : "0.4.5"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/demos/supabase-todolist-drift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 8e3ca5df..c3fedb29 100644
--- a/demos/supabase-todolist-drift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -5,6 +5,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..0c12c1e5
--- /dev/null
+++ b/demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,22 @@
+{
+ "pins" : [
+ {
+ "identity" : "csqlite",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/simolus3/CSQLite.git",
+ "state" : {
+ "revision" : "a268235ae86718e66d6a29feef3bd22c772eb82b"
+ }
+ },
+ {
+ "identity" : "powersync-sqlite-core-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
+ "state" : {
+ "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6",
+ "version" : "0.4.6"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/demos/supabase-todolist-drift/ios/Runner/AppDelegate.swift b/demos/supabase-todolist-drift/ios/Runner/AppDelegate.swift
index 9074fee9..62666446 100644
--- a/demos/supabase-todolist-drift/ios/Runner/AppDelegate.swift
+++ b/demos/supabase-todolist-drift/ios/Runner/AppDelegate.swift
@@ -1,7 +1,7 @@
import Flutter
import UIKit
-@UIApplicationMain
+@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
diff --git a/demos/supabase-todolist-drift/lib/powersync/powersync.dart b/demos/supabase-todolist-drift/lib/powersync/powersync.dart
index cfd73336..cb327bfd 100644
--- a/demos/supabase-todolist-drift/lib/powersync/powersync.dart
+++ b/demos/supabase-todolist-drift/lib/powersync/powersync.dart
@@ -47,18 +47,19 @@ Future powerSyncInstance(Ref ref) async {
return db;
}
-final _syncStatusInternal = StreamProvider((ref) {
+final _syncStatusInternal = StreamProvider((ref) {
return Stream.fromFuture(
ref.watch(powerSyncInstanceProvider.future),
- ).asyncExpand((db) => db.statusStream).startWith(const SyncStatus());
+ ).asyncExpand((db) => db.statusStream).startWith(null);
});
final syncStatus = Provider((ref) {
+ // ignore: invalid_use_of_internal_member
return ref.watch(_syncStatusInternal).value ?? const SyncStatus();
});
@riverpod
-bool didCompleteSync(Ref ref, [BucketPriority? priority]) {
+bool didCompleteSync(Ref ref, [StreamPriority? priority]) {
final status = ref.watch(syncStatus);
if (priority != null) {
return status.statusForPriority(priority).hasSynced ?? false;
diff --git a/demos/supabase-todolist-drift/lib/screens/lists.dart b/demos/supabase-todolist-drift/lib/screens/lists.dart
index ebaa0857..c995a275 100644
--- a/demos/supabase-todolist-drift/lib/screens/lists.dart
+++ b/demos/supabase-todolist-drift/lib/screens/lists.dart
@@ -35,7 +35,7 @@ final class _ListsWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final lists = ref.watch(listsNotifierProvider);
- final didSync = ref.watch(didCompleteSyncProvider(BucketPriority(1)));
+ final didSync = ref.watch(didCompleteSyncProvider(StreamPriority(1)));
if (!didSync) {
return const Text('Busy with sync...');
diff --git a/demos/supabase-todolist-drift/macos/Podfile b/demos/supabase-todolist-drift/macos/Podfile
deleted file mode 100644
index c795730d..00000000
--- a/demos/supabase-todolist-drift/macos/Podfile
+++ /dev/null
@@ -1,43 +0,0 @@
-platform :osx, '10.14'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_macos_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
- target 'RunnerTests' do
- inherit! :search_paths
- end
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_macos_build_settings(target)
- end
-end
diff --git a/demos/supabase-todolist-drift/macos/Podfile.lock b/demos/supabase-todolist-drift/macos/Podfile.lock
deleted file mode 100644
index f32a7411..00000000
--- a/demos/supabase-todolist-drift/macos/Podfile.lock
+++ /dev/null
@@ -1,83 +0,0 @@
-PODS:
- - app_links (1.0.0):
- - FlutterMacOS
- - FlutterMacOS (1.0.0)
- - path_provider_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- - powersync-sqlite-core (0.4.2)
- - powersync_flutter_libs (0.0.1):
- - FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
- - shared_preferences_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- - sqlite3 (3.49.2):
- - sqlite3/common (= 3.49.2)
- - sqlite3/common (3.49.2)
- - sqlite3/dbstatvtab (3.49.2):
- - sqlite3/common
- - sqlite3/fts5 (3.49.2):
- - sqlite3/common
- - sqlite3/math (3.49.2):
- - sqlite3/common
- - sqlite3/perf-threadsafe (3.49.2):
- - sqlite3/common
- - sqlite3/rtree (3.49.2):
- - sqlite3/common
- - sqlite3_flutter_libs (0.0.1):
- - Flutter
- - FlutterMacOS
- - sqlite3 (~> 3.49.1)
- - sqlite3/dbstatvtab
- - sqlite3/fts5
- - sqlite3/math
- - sqlite3/perf-threadsafe
- - sqlite3/rtree
- - url_launcher_macos (0.0.1):
- - FlutterMacOS
-
-DEPENDENCIES:
- - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- - FlutterMacOS (from `Flutter/ephemeral`)
- - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- - powersync_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/macos`)
- - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
-
-SPEC REPOS:
- trunk:
- - powersync-sqlite-core
- - sqlite3
-
-EXTERNAL SOURCES:
- app_links:
- :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
- FlutterMacOS:
- :path: Flutter/ephemeral
- path_provider_foundation:
- :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
- powersync_flutter_libs:
- :path: Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/macos
- shared_preferences_foundation:
- :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
- sqlite3_flutter_libs:
- :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
- url_launcher_macos:
- :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
-
-SPEC CHECKSUMS:
- app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
- path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
- shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
- sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
- sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
- url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
-
-COCOAPODS: 1.16.2
diff --git a/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.pbxproj b/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.pbxproj
index f1568b4e..5c87fe22 100644
--- a/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.pbxproj
+++ b/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.pbxproj
@@ -27,8 +27,7 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
- 4C45D24842D87F346303A231 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 405D516BBE4D2F56B5B3E250 /* Pods_RunnerTests.framework */; };
- E4DB7943952B9B89759CECF4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D3ADAAD4C9C2AA56D71B37 /* Pods_Runner.framework */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -62,9 +61,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 04376F92F675FFC32D91401C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
- 0EF7B02F8BCD6AB8E685141E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
- 149DFD888D02B6A66AC60434 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
@@ -81,12 +77,8 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
- 405D516BBE4D2F56B5B3E250 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 42B0BA50A36508E850C0AA8E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
- 53D3ADAAD4C9C2AA56D71B37 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
- 805FB9B52EBDEA42A8809DDA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
- 92A34CE639216754BD3BD6F0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
/* End PBXFileReference section */
@@ -95,7 +87,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 4C45D24842D87F346303A231 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -103,7 +94,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- E4DB7943952B9B89759CECF4 /* Pods_Runner.framework in Frameworks */,
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -136,8 +127,6 @@
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
- D73912EC22F37F3D000D13A0 /* Frameworks */,
- 78DDA3D3919F4F3973A93CA1 /* Pods */,
);
sourceTree = "";
};
@@ -164,6 +153,7 @@
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
@@ -185,29 +175,6 @@
path = Runner;
sourceTree = "";
};
- 78DDA3D3919F4F3973A93CA1 /* Pods */ = {
- isa = PBXGroup;
- children = (
- 42B0BA50A36508E850C0AA8E /* Pods-Runner.debug.xcconfig */,
- 805FB9B52EBDEA42A8809DDA /* Pods-Runner.release.xcconfig */,
- 04376F92F675FFC32D91401C /* Pods-Runner.profile.xcconfig */,
- 0EF7B02F8BCD6AB8E685141E /* Pods-RunnerTests.debug.xcconfig */,
- 149DFD888D02B6A66AC60434 /* Pods-RunnerTests.release.xcconfig */,
- 92A34CE639216754BD3BD6F0 /* Pods-RunnerTests.profile.xcconfig */,
- );
- name = Pods;
- path = Pods;
- sourceTree = "";
- };
- D73912EC22F37F3D000D13A0 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 53D3ADAAD4C9C2AA56D71B37 /* Pods_Runner.framework */,
- 405D516BBE4D2F56B5B3E250 /* Pods_RunnerTests.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -215,7 +182,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- D4E4B54F7D911A322DFD8C50 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@@ -234,13 +200,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 9D63958A36C1A2768E03404F /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
- 853434743D1185FBE69475FF /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -248,6 +212,9 @@
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* supabase_todolist_drift.app */;
productType = "com.apple.product-type.application";
@@ -292,6 +259,9 @@
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -361,67 +331,6 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
- 853434743D1185FBE69475FF /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 9D63958A36C1A2768E03404F /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- D4E4B54F7D911A322DFD8C50 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -473,7 +382,6 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 0EF7B02F8BCD6AB8E685141E /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@@ -488,7 +396,6 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 149DFD888D02B6A66AC60434 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@@ -503,7 +410,6 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 92A34CE639216754BD3BD6F0 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@@ -557,7 +463,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -639,7 +545,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -689,7 +595,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -796,6 +702,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}
diff --git a/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..759d5a05
--- /dev/null
+++ b/demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,22 @@
+{
+ "pins" : [
+ {
+ "identity" : "csqlite",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/simolus3/CSQLite.git",
+ "state" : {
+ "revision" : "a8d28afef08ad8faa4ee9ef7845f61c2e8ac5810"
+ }
+ },
+ {
+ "identity" : "powersync-sqlite-core-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
+ "state" : {
+ "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6",
+ "version" : "0.4.6"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/demos/supabase-todolist-drift/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demos/supabase-todolist-drift/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 0b9a896d..6b3005f0 100644
--- a/demos/supabase-todolist-drift/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/demos/supabase-todolist-drift/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -5,6 +5,24 @@
+
+
+
+
+
+
+
+
+
+
=3.7.0 <4.0.0"
+ dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.27.0"
diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml
index 3773b612..051931b2 100644
--- a/demos/supabase-todolist-drift/pubspec.yaml
+++ b/demos/supabase-todolist-drift/pubspec.yaml
@@ -9,8 +9,8 @@ environment:
dependencies:
flutter:
sdk: flutter
- powersync_attachments_helper: ^0.6.18+11
- powersync: ^1.15.0
+ powersync_attachments_helper: ^0.6.20
+ powersync: ^1.16.1
path_provider: ^2.1.1
supabase_flutter: ^2.0.1
path: ^1.8.3
@@ -18,7 +18,7 @@ dependencies:
camera: ^0.10.5+7
image: ^4.1.3
universal_io: ^2.2.2
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
drift: ^2.20.2
drift_sqlite_async: ^0.2.0
riverpod_annotation: ^2.6.1
@@ -41,3 +41,5 @@ dev_dependencies:
flutter:
uses-material-design: true
+ config:
+ enable-swift-package-manager: true
diff --git a/demos/supabase-todolist-optional-sync/ios/Podfile b/demos/supabase-todolist-optional-sync/ios/Podfile
index e9f73048..2c1e086a 100644
--- a/demos/supabase-todolist-optional-sync/ios/Podfile
+++ b/demos/supabase-todolist-optional-sync/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-todolist-optional-sync/ios/Podfile.lock b/demos/supabase-todolist-optional-sync/ios/Podfile.lock
index aac7e03f..146ca38d 100644
--- a/demos/supabase-todolist-optional-sync/ios/Podfile.lock
+++ b/demos/supabase-todolist-optional-sync/ios/Podfile.lock
@@ -7,10 +7,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -75,15 +75,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-PODFILE CHECKSUM: f7b3cb7384a2d5da4b22b090e1f632de7f377987
+PODFILE CHECKSUM: 2c1730c97ea13f1ea48b32e9c79de785b4f2f02f
COCOAPODS: 1.16.2
diff --git a/demos/supabase-todolist-optional-sync/macos/Podfile b/demos/supabase-todolist-optional-sync/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/supabase-todolist-optional-sync/macos/Podfile
+++ b/demos/supabase-todolist-optional-sync/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-todolist-optional-sync/macos/Podfile.lock b/demos/supabase-todolist-optional-sync/macos/Podfile.lock
index f32a7411..6983b2da 100644
--- a/demos/supabase-todolist-optional-sync/macos/Podfile.lock
+++ b/demos/supabase-todolist-optional-sync/macos/Podfile.lock
@@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -69,15 +69,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/supabase-todolist-optional-sync/pubspec.lock b/demos/supabase-todolist-optional-sync/pubspec.lock
index c7877841..26909e2d 100644
--- a/demos/supabase-todolist-optional-sync/pubspec.lock
+++ b/demos/supabase-todolist-optional-sync/pubspec.lock
@@ -462,21 +462,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
pub_semver:
dependency: transitive
description:
diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml
index 57ddebec..477f3d02 100644
--- a/demos/supabase-todolist-optional-sync/pubspec.yaml
+++ b/demos/supabase-todolist-optional-sync/pubspec.yaml
@@ -10,7 +10,7 @@ environment:
dependencies:
flutter:
sdk: flutter
- powersync: ^1.15.0
+ powersync: ^1.16.1
path_provider: ^2.1.1
supabase_flutter: ^2.0.1
path: ^1.8.3
@@ -18,7 +18,7 @@ dependencies:
camera: ^0.10.5+7
image: ^4.1.3
universal_io: ^2.2.2
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
dev_dependencies:
flutter_test:
diff --git a/demos/supabase-todolist/.metadata b/demos/supabase-todolist/.metadata
new file mode 100644
index 00000000..6a623a4e
--- /dev/null
+++ b/demos/supabase-todolist/.metadata
@@ -0,0 +1,45 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "d7b523b356d15fb81e7d340bbe52b47f93937323"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ - platform: android
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ - platform: ios
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ - platform: linux
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ - platform: macos
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ - platform: web
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ - platform: windows
+ create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+ base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/demos/supabase-todolist/android/.gitignore b/demos/supabase-todolist/android/.gitignore
index 6f568019..be3943c9 100644
--- a/demos/supabase-todolist/android/.gitignore
+++ b/demos/supabase-todolist/android/.gitignore
@@ -5,9 +5,10 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
+.cxx/
# Remember to never publicly share your keystore.
-# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
diff --git a/demos/supabase-todolist/android/app/build.gradle b/demos/supabase-todolist/android/app/build.gradle
deleted file mode 100644
index 9daa778b..00000000
--- a/demos/supabase-todolist/android/app/build.gradle
+++ /dev/null
@@ -1,70 +0,0 @@
-def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
-if (localPropertiesFile.exists()) {
- localPropertiesFile.withReader('UTF-8') { reader ->
- localProperties.load(reader)
- }
-}
-
-def flutterRoot = localProperties.getProperty('flutter.sdk')
-if (flutterRoot == null) {
- throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
-}
-
-def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
-if (flutterVersionCode == null) {
- flutterVersionCode = '1'
-}
-
-def flutterVersionName = localProperties.getProperty('flutter.versionName')
-if (flutterVersionName == null) {
- flutterVersionName = '1.0'
-}
-
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
-android {
- compileSdkVersion flutter.compileSdkVersion
- ndkVersion flutter.ndkVersion
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = '1.8'
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
-
- defaultConfig {
- applicationId "co.powersync.demotodolist"
- // You can update the following values to match your application needs.
- // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
- minSdkVersion 24
- targetSdkVersion flutter.targetSdkVersion
- versionCode flutterVersionCode.toInteger()
- versionName flutterVersionName
- }
-
- buildTypes {
- release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig signingConfigs.debug
- }
- }
-}
-
-flutter {
- source '../..'
-}
-
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
diff --git a/demos/supabase-todolist/android/app/build.gradle.kts b/demos/supabase-todolist/android/app/build.gradle.kts
new file mode 100644
index 00000000..2fc63bb4
--- /dev/null
+++ b/demos/supabase-todolist/android/app/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "com.powersync.demo.flutter.supabase_todolist.powersync_flutter_demo"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11.toString()
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.powersync.demo.flutter.supabase_todolist.powersync_flutter_demo"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/demos/supabase-todolist/android/app/src/debug/AndroidManifest.xml b/demos/supabase-todolist/android/app/src/debug/AndroidManifest.xml
index f19dd7d6..399f6981 100644
--- a/demos/supabase-todolist/android/app/src/debug/AndroidManifest.xml
+++ b/demos/supabase-todolist/android/app/src/debug/AndroidManifest.xml
@@ -1,4 +1,7 @@
-
+
+
diff --git a/demos/supabase-todolist/android/app/src/main/AndroidManifest.xml b/demos/supabase-todolist/android/app/src/main/AndroidManifest.xml
index 55e175c4..e85f03e3 100644
--- a/demos/supabase-todolist/android/app/src/main/AndroidManifest.xml
+++ b/demos/supabase-todolist/android/app/src/main/AndroidManifest.xml
@@ -1,13 +1,13 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/demos/supabase-todolist/android/app/src/main/kotlin/co/powersync/demotodolist/MainActivity.kt b/demos/supabase-todolist/android/app/src/main/kotlin/co/powersync/demotodolist/MainActivity.kt
deleted file mode 100644
index 88fda765..00000000
--- a/demos/supabase-todolist/android/app/src/main/kotlin/co/powersync/demotodolist/MainActivity.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package co.powersync.demotodolist
-
-import io.flutter.embedding.android.FlutterActivity
-
-class MainActivity: FlutterActivity() {
-}
diff --git a/demos/supabase-todolist/android/app/src/main/kotlin/com/powersync/demo/flutter/supabase_todolist/powersync_flutter_demo/MainActivity.kt b/demos/supabase-todolist/android/app/src/main/kotlin/com/powersync/demo/flutter/supabase_todolist/powersync_flutter_demo/MainActivity.kt
new file mode 100644
index 00000000..70c9dd12
--- /dev/null
+++ b/demos/supabase-todolist/android/app/src/main/kotlin/com/powersync/demo/flutter/supabase_todolist/powersync_flutter_demo/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.powersync.demo.flutter.supabase_todolist.powersync_flutter_demo
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/demos/supabase-todolist/android/app/src/profile/AndroidManifest.xml b/demos/supabase-todolist/android/app/src/profile/AndroidManifest.xml
index f19dd7d6..399f6981 100644
--- a/demos/supabase-todolist/android/app/src/profile/AndroidManifest.xml
+++ b/demos/supabase-todolist/android/app/src/profile/AndroidManifest.xml
@@ -1,4 +1,7 @@
-
+
+
diff --git a/demos/supabase-todolist/android/build.gradle b/demos/supabase-todolist/android/build.gradle
deleted file mode 100644
index 713d7f6e..00000000
--- a/demos/supabase-todolist/android/build.gradle
+++ /dev/null
@@ -1,31 +0,0 @@
-buildscript {
- ext.kotlin_version = '1.7.10'
- repositories {
- google()
- mavenCentral()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:7.2.0'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
-allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-rootProject.buildDir = '../build'
-subprojects {
- project.buildDir = "${rootProject.buildDir}/${project.name}"
-}
-subprojects {
- project.evaluationDependsOn(':app')
-}
-
-tasks.register("clean", Delete) {
- delete rootProject.buildDir
-}
diff --git a/demos/supabase-todolist/android/build.gradle.kts b/demos/supabase-todolist/android/build.gradle.kts
new file mode 100644
index 00000000..89176ef4
--- /dev/null
+++ b/demos/supabase-todolist/android/build.gradle.kts
@@ -0,0 +1,21 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/demos/supabase-todolist/android/gradle.properties b/demos/supabase-todolist/android/gradle.properties
index 94adc3a3..f018a618 100644
--- a/demos/supabase-todolist/android/gradle.properties
+++ b/demos/supabase-todolist/android/gradle.properties
@@ -1,3 +1,3 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
diff --git a/demos/supabase-todolist/android/gradle/wrapper/gradle-wrapper.properties b/demos/supabase-todolist/android/gradle/wrapper/gradle-wrapper.properties
index 3c472b99..ac3b4792 100644
--- a/demos/supabase-todolist/android/gradle/wrapper/gradle-wrapper.properties
+++ b/demos/supabase-todolist/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
diff --git a/demos/supabase-todolist/android/settings.gradle b/demos/supabase-todolist/android/settings.gradle
deleted file mode 100644
index 44e62bcf..00000000
--- a/demos/supabase-todolist/android/settings.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-include ':app'
-
-def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
-def properties = new Properties()
-
-assert localPropertiesFile.exists()
-localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
-
-def flutterSdkPath = properties.getProperty("flutter.sdk")
-assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
-apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/demos/supabase-todolist/android/settings.gradle.kts b/demos/supabase-todolist/android/settings.gradle.kts
new file mode 100644
index 00000000..ab39a10a
--- /dev/null
+++ b/demos/supabase-todolist/android/settings.gradle.kts
@@ -0,0 +1,25 @@
+pluginManagement {
+ val flutterSdkPath = run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.7.3" apply false
+ id("org.jetbrains.kotlin.android") version "2.1.0" apply false
+}
+
+include(":app")
diff --git a/demos/supabase-todolist/ios/Flutter/AppFrameworkInfo.plist b/demos/supabase-todolist/ios/Flutter/AppFrameworkInfo.plist
index 7c569640..1dc6cf76 100644
--- a/demos/supabase-todolist/ios/Flutter/AppFrameworkInfo.plist
+++ b/demos/supabase-todolist/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/demos/supabase-todolist/ios/Podfile b/demos/supabase-todolist/ios/Podfile
index e9f73048..2c1e086a 100644
--- a/demos/supabase-todolist/ios/Podfile
+++ b/demos/supabase-todolist/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-todolist/ios/Podfile.lock b/demos/supabase-todolist/ios/Podfile.lock
index aac7e03f..73dadf31 100644
--- a/demos/supabase-todolist/ios/Podfile.lock
+++ b/demos/supabase-todolist/ios/Podfile.lock
@@ -1,5 +1,5 @@
PODS:
- - app_links (0.0.2):
+ - app_links (6.4.1):
- Flutter
- camera_avfoundation (0.0.1):
- Flutter
@@ -7,35 +7,39 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.6)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
+ - FlutterMacOS
+ - powersync-sqlite-core (~> 0.4.6)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - sqlite3 (3.49.2):
- - sqlite3/common (= 3.49.2)
- - sqlite3/common (3.49.2)
- - sqlite3/dbstatvtab (3.49.2):
+ - sqlite3 (3.50.4):
+ - sqlite3/common (= 3.50.4)
+ - sqlite3/common (3.50.4)
+ - sqlite3/dbstatvtab (3.50.4):
+ - sqlite3/common
+ - sqlite3/fts5 (3.50.4):
- sqlite3/common
- - sqlite3/fts5 (3.49.2):
+ - sqlite3/math (3.50.4):
- sqlite3/common
- - sqlite3/math (3.49.2):
+ - sqlite3/perf-threadsafe (3.50.4):
- sqlite3/common
- - sqlite3/perf-threadsafe (3.49.2):
+ - sqlite3/rtree (3.50.4):
- sqlite3/common
- - sqlite3/rtree (3.49.2):
+ - sqlite3/session (3.50.4):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- - sqlite3 (~> 3.49.1)
+ - sqlite3 (~> 3.50.4)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/math
- sqlite3/perf-threadsafe
- sqlite3/rtree
+ - sqlite3/session
- url_launcher_ios (0.0.1):
- Flutter
@@ -44,7 +48,7 @@ DEPENDENCIES:
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- - powersync_flutter_libs (from `.symlinks/plugins/powersync_flutter_libs/ios`)
+ - powersync_flutter_libs (from `.symlinks/plugins/powersync_flutter_libs/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -64,7 +68,7 @@ EXTERNAL SOURCES:
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
powersync_flutter_libs:
- :path: ".symlinks/plugins/powersync_flutter_libs/ios"
+ :path: ".symlinks/plugins/powersync_flutter_libs/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqlite3_flutter_libs:
@@ -73,17 +77,17 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
- app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
+ app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
+ powersync-sqlite-core: 42c4a42a692b3b770a5488778789430d67a39b49
+ powersync_flutter_libs: 19fc6b96ff8155ffea72a08990f6c9f2e712b8a6
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
- sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
- sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
+ sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
+ sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-PODFILE CHECKSUM: f7b3cb7384a2d5da4b22b090e1f632de7f377987
+PODFILE CHECKSUM: 2c1730c97ea13f1ea48b32e9c79de785b4f2f02f
COCOAPODS: 1.16.2
diff --git a/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj b/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj
index e32e9fa6..f6a8fba5 100644
--- a/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj
+++ b/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj
@@ -342,7 +342,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -419,7 +419,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -468,7 +468,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index c53e2b31..9c12df59 100644
--- a/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
localAttachmentStorage() async {
+ final appDocDir = await getApplicationDocumentsDirectory();
+ return IOLocalStorage(appDocDir);
+}
diff --git a/demos/supabase-todolist/lib/attachments/local_storage_unsupported.dart b/demos/supabase-todolist/lib/attachments/local_storage_unsupported.dart
new file mode 100644
index 00000000..811fa7d3
--- /dev/null
+++ b/demos/supabase-todolist/lib/attachments/local_storage_unsupported.dart
@@ -0,0 +1,7 @@
+import 'package:powersync_core/attachments/attachments.dart';
+
+Future localAttachmentStorage() async {
+ // This file is imported on the web, where we don't currently have a
+ // persistent local storage implementation.
+ return LocalStorage.inMemory();
+}
diff --git a/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart b/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart
index 38838dd7..89660808 100644
--- a/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart
+++ b/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart
@@ -1,11 +1,9 @@
import 'dart:async';
-
+import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
-import 'package:powersync/powersync.dart' as powersync;
+import 'package:logging/logging.dart';
import 'package:powersync_flutter_demo/attachments/queue.dart';
-import 'package:powersync_flutter_demo/models/todo_item.dart';
-import 'package:powersync_flutter_demo/powersync.dart';
class TakePhotoWidget extends StatefulWidget {
final String todoId;
@@ -23,6 +21,7 @@ class TakePhotoWidget extends StatefulWidget {
class _TakePhotoWidgetState extends State {
late CameraController _cameraController;
late Future _initializeControllerFuture;
+ final log = Logger('TakePhotoWidget');
@override
void initState() {
@@ -37,7 +36,6 @@ class _TakePhotoWidgetState extends State {
}
@override
- // Dispose of the camera controller when the widget is disposed
void dispose() {
_cameraController.dispose();
super.dispose();
@@ -45,25 +43,26 @@ class _TakePhotoWidgetState extends State {
Future _takePhoto(context) async {
try {
- // Ensure the camera is initialized before taking a photo
+ log.info('Taking photo for todo: ${widget.todoId}');
await _initializeControllerFuture;
-
final XFile photo = await _cameraController.takePicture();
- // copy photo to new directory with ID as name
- String photoId = powersync.uuid.v4();
- String storageDirectory = await attachmentQueue.getStorageDirectory();
- await attachmentQueue.localStorage
- .copyFile(photo.path, '$storageDirectory/$photoId.jpg');
- int photoSize = await photo.length();
+ // Read the photo data as bytes
+ final photoFile = File(photo.path);
+ if (!await photoFile.exists()) {
+ log.warning('Photo file does not exist: ${photo.path}');
+ return;
+ }
+
+ final photoData = photoFile.openRead();
- TodoItem.addPhoto(photoId, widget.todoId);
- attachmentQueue.saveFile(photoId, photoSize);
+ // Save the photo attachment with the byte data
+ final attachment = await savePhotoAttachment(photoData, widget.todoId);
+
+ log.info('Photo attachment saved with ID: ${attachment.id}');
} catch (e) {
- log.info('Error taking photo: $e');
+ log.severe('Error taking photo: $e');
}
-
- // After taking the photo, navigate back to the previous screen
Navigator.pop(context);
}
diff --git a/demos/supabase-todolist/lib/attachments/photo_widget.dart b/demos/supabase-todolist/lib/attachments/photo_widget.dart
index f034ef5b..f41bc0b0 100644
--- a/demos/supabase-todolist/lib/attachments/photo_widget.dart
+++ b/demos/supabase-todolist/lib/attachments/photo_widget.dart
@@ -1,12 +1,14 @@
import 'dart:io';
+import 'package:path_provider/path_provider.dart';
+import 'package:path/path.dart' as p;
import 'package:flutter/material.dart';
-import 'package:powersync_attachments_helper/powersync_attachments_helper.dart';
+import 'package:powersync_core/attachments/attachments.dart';
import 'package:powersync_flutter_demo/attachments/camera_helpers.dart';
import 'package:powersync_flutter_demo/attachments/photo_capture_widget.dart';
-import 'package:powersync_flutter_demo/attachments/queue.dart';
import '../models/todo_item.dart';
+import '../powersync.dart';
class PhotoWidget extends StatefulWidget {
final TodoItem todo;
@@ -37,11 +39,12 @@ class _PhotoWidgetState extends State {
if (photoId == null) {
return _ResolvedPhotoState(photoPath: null, fileExists: false);
}
- photoPath = await attachmentQueue.getLocalUri('$photoId.jpg');
+ final appDocDir = await getApplicationDocumentsDirectory();
+ photoPath = p.join(appDocDir.path, '$photoId.jpg');
bool fileExists = await File(photoPath).exists();
- final row = await attachmentQueue.db
+ final row = await db
.getOptional('SELECT * FROM attachments_queue WHERE id = ?', [photoId]);
if (row != null) {
@@ -98,7 +101,7 @@ class _PhotoWidgetState extends State {
String? filePath = data.photoPath;
bool fileIsDownloading = !data.fileExists;
bool fileArchived =
- data.attachment?.state == AttachmentState.archived.index;
+ data.attachment?.state == AttachmentState.archived;
if (fileArchived) {
return Column(
diff --git a/demos/supabase-todolist/lib/attachments/queue.dart b/demos/supabase-todolist/lib/attachments/queue.dart
index 2a8dd9ca..8f036c85 100644
--- a/demos/supabase-todolist/lib/attachments/queue.dart
+++ b/demos/supabase-todolist/lib/attachments/queue.dart
@@ -1,90 +1,55 @@
import 'dart:async';
+import 'package:logging/logging.dart';
import 'package:powersync/powersync.dart';
-import 'package:powersync_attachments_helper/powersync_attachments_helper.dart';
-import 'package:powersync_flutter_demo/app_config.dart';
+import 'package:powersync_core/attachments/attachments.dart';
+
import 'package:powersync_flutter_demo/attachments/remote_storage_adapter.dart';
-import 'package:powersync_flutter_demo/models/schema.dart';
+import 'local_storage_unsupported.dart'
+ if (dart.library.io) 'local_storage_native.dart';
-/// Global reference to the queue
-late final PhotoAttachmentQueue attachmentQueue;
+late AttachmentQueue attachmentQueue;
final remoteStorage = SupabaseStorageAdapter();
-
-/// Function to handle errors when downloading attachments
-/// Return false if you want to archive the attachment
-Future onDownloadError(Attachment attachment, Object exception) async {
- if (exception.toString().contains('Object not found')) {
- return false;
- }
- return true;
-}
-
-class PhotoAttachmentQueue extends AbstractAttachmentQueue {
- PhotoAttachmentQueue(db, remoteStorage)
- : super(
- db: db,
- remoteStorage: remoteStorage,
- onDownloadError: onDownloadError);
-
- @override
- init() async {
- if (AppConfig.supabaseStorageBucket.isEmpty) {
- log.info(
- 'No Supabase bucket configured, skip setting up PhotoAttachmentQueue watches');
- return;
- }
-
- await super.init();
- }
-
- @override
- Future saveFile(String fileId, int size,
- {mediaType = 'image/jpeg'}) async {
- String filename = '$fileId.jpg';
-
- Attachment photoAttachment = Attachment(
- id: fileId,
- filename: filename,
- state: AttachmentState.queuedUpload.index,
- mediaType: mediaType,
- localUri: getLocalFilePathSuffix(filename),
- size: size,
- );
-
- return attachmentsService.saveAttachment(photoAttachment);
- }
-
- @override
- Future deleteFile(String fileId) async {
- String filename = '$fileId.jpg';
-
- Attachment photoAttachment = Attachment(
- id: fileId,
- filename: filename,
- state: AttachmentState.queuedDelete.index);
-
- return attachmentsService.saveAttachment(photoAttachment);
- }
-
- @override
- StreamSubscription watchIds({String fileExtension = 'jpg'}) {
- log.info('Watching photos in $todosTable...');
- return db.watch('''
- SELECT photo_id FROM $todosTable
- WHERE photo_id IS NOT NULL
- ''').map((results) {
- return results.map((row) => row['photo_id'] as String).toList();
- }).listen((ids) async {
- List idsInQueue = await attachmentsService.getAttachmentIds();
- List relevantIds =
- ids.where((element) => !idsInQueue.contains(element)).toList();
- syncingService.processIds(relevantIds, fileExtension);
- });
- }
+final logger = Logger('AttachmentQueue');
+
+Future initializeAttachmentQueue(PowerSyncDatabase db) async {
+ attachmentQueue = AttachmentQueue(
+ db: db,
+ remoteStorage: remoteStorage,
+ logger: logger,
+ localStorage: await localAttachmentStorage(),
+ watchAttachments: () => db.watch('''
+ SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL
+ ''').map(
+ (results) => [
+ for (final row in results)
+ WatchedAttachmentItem(
+ id: row['id'] as String,
+ fileExtension: 'jpg',
+ )
+ ],
+ ),
+ );
+
+ await attachmentQueue.startSync();
}
-initializeAttachmentQueue(PowerSyncDatabase db) async {
- attachmentQueue = PhotoAttachmentQueue(db, remoteStorage);
- await attachmentQueue.init();
+Future savePhotoAttachment(
+ Stream> photoData, String todoId,
+ {String mediaType = 'image/jpeg'}) async {
+ // Save the file using the AttachmentQueue API
+ return await attachmentQueue.saveFile(
+ data: photoData,
+ mediaType: mediaType,
+ fileExtension: 'jpg',
+ metaData: 'Photo attachment for todo: $todoId',
+ updateHook: (context, attachment) async {
+ // Update the todo item to reference this attachment
+ await context.execute(
+ 'UPDATE todos SET photo_id = ? WHERE id = ?',
+ [attachment.id, todoId],
+ );
+ },
+ );
}
diff --git a/demos/supabase-todolist/lib/attachments/remote_storage_adapter.dart b/demos/supabase-todolist/lib/attachments/remote_storage_adapter.dart
index 596c5da5..5b711b83 100644
--- a/demos/supabase-todolist/lib/attachments/remote_storage_adapter.dart
+++ b/demos/supabase-todolist/lib/attachments/remote_storage_adapter.dart
@@ -1,49 +1,96 @@
import 'dart:io';
import 'dart:typed_data';
-import 'package:powersync_attachments_helper/powersync_attachments_helper.dart';
+
+import 'package:powersync_core/attachments/attachments.dart';
import 'package:powersync_flutter_demo/app_config.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
-import 'package:image/image.dart' as img;
+import 'package:logging/logging.dart';
+
+class SupabaseStorageAdapter implements RemoteStorage {
+ static final _log = Logger('SupabaseStorageAdapter');
-class SupabaseStorageAdapter implements AbstractRemoteStorageAdapter {
@override
- Future uploadFile(String filename, File file,
- {String mediaType = 'text/plain'}) async {
+ Future uploadFile(
+ Stream> fileData, Attachment attachment) async {
_checkSupabaseBucketIsConfigured();
+ // Check if attachment size is specified (required for buffer allocation)
+ final byteSize = attachment.size;
+ if (byteSize == null) {
+ throw Exception('Cannot upload a file with no byte size specified');
+ }
+
+ _log.info('uploadFile: ${attachment.filename} (size: $byteSize bytes)');
+
+ // Collect all stream data into a single Uint8List buffer
+ final buffer = Uint8List(byteSize);
+ var position = 0;
+
+ await for (final chunk in fileData) {
+ if (position + chunk.length > byteSize) {
+ throw Exception('File data exceeds specified size');
+ }
+ buffer.setRange(position, position + chunk.length, chunk);
+ position += chunk.length;
+ }
+
+ if (position != byteSize) {
+ throw Exception(
+ 'File data size ($position) does not match specified size ($byteSize)');
+ }
+
+ // Create a temporary file from the buffer for upload
+ final tempFile =
+ File('${Directory.systemTemp.path}/${attachment.filename}');
try {
+ await tempFile.writeAsBytes(buffer);
+
await Supabase.instance.client.storage
.from(AppConfig.supabaseStorageBucket)
- .upload(filename, file,
- fileOptions: FileOptions(contentType: mediaType));
+ .upload(attachment.filename, tempFile,
+ fileOptions: FileOptions(
+ contentType:
+ attachment.mediaType ?? 'application/octet-stream'));
+
+ _log.info('Successfully uploaded ${attachment.filename}');
} catch (error) {
+ _log.severe('Error uploading ${attachment.filename}', error);
throw Exception(error);
+ } finally {
+ if (await tempFile.exists()) {
+ await tempFile.delete();
+ }
}
}
@override
- Future downloadFile(String filePath) async {
+ Future>> downloadFile(Attachment attachment) async {
_checkSupabaseBucketIsConfigured();
try {
+ _log.info('downloadFile: ${attachment.filename}');
+
Uint8List fileBlob = await Supabase.instance.client.storage
.from(AppConfig.supabaseStorageBucket)
- .download(filePath);
- final image = img.decodeImage(fileBlob);
- Uint8List blob = img.JpegEncoder().encode(image!);
- return blob;
+ .download(attachment.filename);
+
+ _log.info(
+ 'Successfully downloaded ${attachment.filename} (${fileBlob.length} bytes)');
+
+ // Return the raw file data as a stream
+ return Stream.value(fileBlob);
} catch (error) {
+ _log.severe('Error downloading ${attachment.filename}', error);
throw Exception(error);
}
}
@override
- Future deleteFile(String filename) async {
+ Future deleteFile(Attachment attachment) async {
_checkSupabaseBucketIsConfigured();
-
try {
await Supabase.instance.client.storage
.from(AppConfig.supabaseStorageBucket)
- .remove([filename]);
+ .remove([attachment.filename]);
} catch (error) {
throw Exception(error);
}
diff --git a/demos/supabase-todolist/lib/models/schema.dart b/demos/supabase-todolist/lib/models/schema.dart
index 89b69b0c..5a6a261b 100644
--- a/demos/supabase-todolist/lib/models/schema.dart
+++ b/demos/supabase-todolist/lib/models/schema.dart
@@ -1,9 +1,9 @@
import 'package:powersync/powersync.dart';
-import 'package:powersync_attachments_helper/powersync_attachments_helper.dart';
+import 'package:powersync_core/attachments/attachments.dart';
const todosTable = 'todos';
-Schema schema = Schema(([
+Schema schema = Schema([
const Table(todosTable, [
Column.text('list_id'),
Column.text('photo_id'),
@@ -22,6 +22,5 @@ Schema schema = Schema(([
Column.text('name'),
Column.text('owner_id')
]),
- AttachmentsQueueTable(
- attachmentsQueueTableName: defaultAttachmentsQueueTableName)
-]));
+ AttachmentsQueueTable()
+]);
diff --git a/demos/supabase-todolist/lib/widgets/guard_by_sync.dart b/demos/supabase-todolist/lib/widgets/guard_by_sync.dart
index d55ed4e3..6d4d12b9 100644
--- a/demos/supabase-todolist/lib/widgets/guard_by_sync.dart
+++ b/demos/supabase-todolist/lib/widgets/guard_by_sync.dart
@@ -7,9 +7,9 @@ import 'package:powersync_flutter_demo/powersync.dart';
class GuardBySync extends StatelessWidget {
final Widget child;
- /// When set, wait only for a complete sync within the [BucketPriority]
+ /// When set, wait only for a complete sync within the [StreamPriority]
/// instead of a full sync.
- final BucketPriority? priority;
+ final StreamPriority? priority;
const GuardBySync({
super.key,
diff --git a/demos/supabase-todolist/lib/widgets/list_item_sync_stream.dart b/demos/supabase-todolist/lib/widgets/list_item_sync_stream.dart
new file mode 100644
index 00000000..e400f8aa
--- /dev/null
+++ b/demos/supabase-todolist/lib/widgets/list_item_sync_stream.dart
@@ -0,0 +1,78 @@
+import 'package:flutter/material.dart';
+
+import '../powersync.dart';
+import './todo_list_page.dart';
+import '../models/todo_list.dart';
+
+/// A variant of the `ListItem` that only shows a summary of completed and
+/// pending items when the respective list has an active sync stream.
+class SyncStreamsAwareListItem extends StatelessWidget {
+ SyncStreamsAwareListItem({
+ required this.list,
+ }) : super(key: ObjectKey(list));
+
+ final TodoList list;
+
+ Future delete() async {
+ // Server will take care of deleting related todos
+ await list.delete();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ viewList() {
+ var navigator = Navigator.of(context);
+
+ navigator.push(
+ MaterialPageRoute(builder: (context) => TodoListPage(list: list)));
+ }
+
+ return StreamBuilder(
+ stream: db.statusStream,
+ initialData: db.currentStatus,
+ builder: (context, asyncSnapshot) {
+ final status = asyncSnapshot.requireData;
+ final stream =
+ status.forStream(db.syncStream('todos', {'list': list.id}));
+
+ String subtext;
+ if (stream == null || !stream.subscription.active) {
+ subtext = 'Items not loaded - click to fetch.';
+ } else {
+ subtext =
+ '${list.pendingCount} pending, ${list.completedCount} completed';
+ }
+
+ return Card(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ListTile(
+ onTap: viewList,
+ leading: const Icon(Icons.list),
+ title: Text(list.name),
+ subtitle: Text(subtext),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ IconButton(
+ iconSize: 30,
+ icon: const Icon(
+ Icons.delete,
+ color: Colors.red,
+ ),
+ tooltip: 'Delete List',
+ alignment: Alignment.centerRight,
+ onPressed: delete,
+ ),
+ const SizedBox(width: 8),
+ ],
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/demos/supabase-todolist/lib/widgets/lists_page.dart b/demos/supabase-todolist/lib/widgets/lists_page.dart
index c41aabbe..564e03ee 100644
--- a/demos/supabase-todolist/lib/widgets/lists_page.dart
+++ b/demos/supabase-todolist/lib/widgets/lists_page.dart
@@ -1,11 +1,13 @@
import 'package:flutter/material.dart';
import 'package:powersync/powersync.dart';
+import '../app_config.dart';
import './list_item.dart';
import './list_item_dialog.dart';
import '../main.dart';
import '../models/todo_list.dart';
import 'guard_by_sync.dart';
+import 'list_item_sync_stream.dart';
void _showAddDialog(BuildContext context) async {
return showDialog(
@@ -55,7 +57,9 @@ class ListsWidget extends StatelessWidget {
return ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: todoLists.map((list) {
- return ListItemWidget(list: list);
+ return AppConfig.hasSyncStreams
+ ? SyncStreamsAwareListItem(list: list)
+ : ListItemWidget(list: list);
}).toList(),
);
} else {
@@ -66,5 +70,5 @@ class ListsWidget extends StatelessWidget {
);
}
- static final _listsPriority = BucketPriority(1);
+ static final _listsPriority = StreamPriority(1);
}
diff --git a/demos/supabase-todolist/lib/widgets/todo_item_widget.dart b/demos/supabase-todolist/lib/widgets/todo_item_widget.dart
index a59812ed..700a869a 100644
--- a/demos/supabase-todolist/lib/widgets/todo_item_widget.dart
+++ b/demos/supabase-todolist/lib/widgets/todo_item_widget.dart
@@ -23,7 +23,13 @@ class TodoItemWidget extends StatelessWidget {
Future deleteTodo(TodoItem todo) async {
if (todo.photoId != null) {
- attachmentQueue.deleteFile(todo.photoId!);
+ await attachmentQueue.deleteFile(
+ attachmentId: todo.photoId!,
+ updateHook: (context, attachment) async {
+ await context.execute(
+ "UPDATE todos SET photo_id = NULL WHERE id = ?", [todo.id]);
+ },
+ );
}
await todo.delete();
}
diff --git a/demos/supabase-todolist/lib/widgets/todo_list_page.dart b/demos/supabase-todolist/lib/widgets/todo_list_page.dart
index 7e28238e..b1245321 100644
--- a/demos/supabase-todolist/lib/widgets/todo_list_page.dart
+++ b/demos/supabase-todolist/lib/widgets/todo_list_page.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:powersync/powersync.dart';
+import '../app_config.dart';
import '../powersync.dart';
import './status_app_bar.dart';
import './todo_item_dialog.dart';
@@ -32,9 +34,12 @@ class TodoListPage extends StatelessWidget {
);
return Scaffold(
- appBar: StatusAppBar(title: Text(list.name)),
- floatingActionButton: button,
- body: TodoListWidget(list: list));
+ appBar: StatusAppBar(title: Text(list.name)),
+ floatingActionButton: button,
+ body: AppConfig.hasSyncStreams
+ ? _SyncStreamTodoListWidget(list: list)
+ : TodoListWidget(list: list),
+ );
}
}
@@ -66,3 +71,84 @@ class TodoListWidget extends StatelessWidget {
);
}
}
+
+class _SyncStreamTodoListWidget extends StatefulWidget {
+ final TodoList list;
+
+ const _SyncStreamTodoListWidget({required this.list});
+
+ @override
+ State<_SyncStreamTodoListWidget> createState() => _SyncStreamTodosState();
+}
+
+class _SyncStreamTodosState extends State<_SyncStreamTodoListWidget> {
+ SyncStreamSubscription? _listSubscription;
+
+ void _subscribe(String listId) {
+ db
+ .syncStream('todos', {'list': listId})
+ .subscribe(ttl: const Duration(hours: 1))
+ .then((sub) {
+ if (mounted && widget.list.id == listId) {
+ setState(() {
+ _listSubscription = sub;
+ });
+ } else {
+ sub.unsubscribe();
+ }
+ });
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _subscribe(widget.list.id);
+ }
+
+ @override
+ void didUpdateWidget(covariant _SyncStreamTodoListWidget oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ _subscribe(widget.list.id);
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ _listSubscription?.unsubscribe();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return StreamBuilder(
+ stream: db.statusStream,
+ initialData: db.currentStatus,
+ builder: (context, snapshot) {
+ final hasSynced = switch (_listSubscription) {
+ null => null,
+ final sub => snapshot.requireData.forStream(sub),
+ }
+ ?.subscription
+ .hasSynced ??
+ false;
+
+ if (!hasSynced) {
+ return const Center(child: CircularProgressIndicator());
+ } else {
+ return StreamBuilder(
+ stream: widget.list.watchItems(),
+ builder: (context, snapshot) {
+ final items = snapshot.data ?? const [];
+
+ return ListView(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ children: items.map((todo) {
+ return TodoItemWidget(todo: todo);
+ }).toList(),
+ );
+ },
+ );
+ }
+ },
+ );
+ }
+}
diff --git a/demos/supabase-todolist/linux/runner/CMakeLists.txt b/demos/supabase-todolist/linux/runner/CMakeLists.txt
new file mode 100644
index 00000000..e97dabc7
--- /dev/null
+++ b/demos/supabase-todolist/linux/runner/CMakeLists.txt
@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.13)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME}
+ "main.cc"
+ "my_application.cc"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the application ID.
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
diff --git a/demos/supabase-todolist/linux/runner/main.cc b/demos/supabase-todolist/linux/runner/main.cc
new file mode 100644
index 00000000..e7c5c543
--- /dev/null
+++ b/demos/supabase-todolist/linux/runner/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+ g_autoptr(MyApplication) app = my_application_new();
+ return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/demos/supabase-todolist/linux/runner/my_application.cc b/demos/supabase-todolist/linux/runner/my_application.cc
new file mode 100644
index 00000000..dde6b0ac
--- /dev/null
+++ b/demos/supabase-todolist/linux/runner/my_application.cc
@@ -0,0 +1,130 @@
+#include "my_application.h"
+
+#include
+#ifdef GDK_WINDOWING_X11
+#include
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+ GtkApplication parent_instance;
+ char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+ MyApplication* self = MY_APPLICATION(application);
+ GtkWindow* window =
+ GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+ // Use a header bar when running in GNOME as this is the common style used
+ // by applications and is the setup most users will be using (e.g. Ubuntu
+ // desktop).
+ // If running on X and not using GNOME then just use a traditional title bar
+ // in case the window manager does more exotic layout, e.g. tiling.
+ // If running on Wayland assume the header bar will work (may need changing
+ // if future cases occur).
+ gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+ GdkScreen* screen = gtk_window_get_screen(window);
+ if (GDK_IS_X11_SCREEN(screen)) {
+ const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+ if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+ use_header_bar = FALSE;
+ }
+ }
+#endif
+ if (use_header_bar) {
+ GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "powersync_flutter_demo");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ } else {
+ gtk_window_set_title(window, "powersync_flutter_demo");
+ }
+
+ gtk_window_set_default_size(window, 1280, 720);
+ gtk_widget_show(GTK_WIDGET(window));
+
+ g_autoptr(FlDartProject) project = fl_dart_project_new();
+ fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+ FlView* view = fl_view_new(project);
+ gtk_widget_show(GTK_WIDGET(view));
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+ fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+ gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
+ MyApplication* self = MY_APPLICATION(application);
+ // Strip out the first argument as it is the binary name.
+ self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+ g_autoptr(GError) error = nullptr;
+ if (!g_application_register(application, nullptr, &error)) {
+ g_warning("Failed to register: %s", error->message);
+ *exit_status = 1;
+ return TRUE;
+ }
+
+ g_application_activate(application);
+ *exit_status = 0;
+
+ return TRUE;
+}
+
+// Implements GApplication::startup.
+static void my_application_startup(GApplication* application) {
+ //MyApplication* self = MY_APPLICATION(object);
+
+ // Perform any actions required at application startup.
+
+ G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
+}
+
+// Implements GApplication::shutdown.
+static void my_application_shutdown(GApplication* application) {
+ //MyApplication* self = MY_APPLICATION(object);
+
+ // Perform any actions required at application shutdown.
+
+ G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+ MyApplication* self = MY_APPLICATION(object);
+ g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+ G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+ G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+ G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+ G_APPLICATION_CLASS(klass)->startup = my_application_startup;
+ G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
+ G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+ // Set the program name to the application ID, which helps various systems
+ // like GTK and desktop environments map this running application to its
+ // corresponding .desktop file. This ensures better integration by allowing
+ // the application to be recognized beyond its binary name.
+ g_set_prgname(APPLICATION_ID);
+
+ return MY_APPLICATION(g_object_new(my_application_get_type(),
+ "application-id", APPLICATION_ID,
+ "flags", G_APPLICATION_NON_UNIQUE,
+ nullptr));
+}
diff --git a/demos/supabase-todolist/linux/runner/my_application.h b/demos/supabase-todolist/linux/runner/my_application.h
new file mode 100644
index 00000000..72271d5e
--- /dev/null
+++ b/demos/supabase-todolist/linux/runner/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+ GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/demos/supabase-todolist/macos/Podfile b/demos/supabase-todolist/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/supabase-todolist/macos/Podfile
+++ b/demos/supabase-todolist/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-todolist/macos/Podfile.lock b/demos/supabase-todolist/macos/Podfile.lock
index f32a7411..51b6fdad 100644
--- a/demos/supabase-todolist/macos/Podfile.lock
+++ b/demos/supabase-todolist/macos/Podfile.lock
@@ -1,39 +1,43 @@
PODS:
- - app_links (1.0.0):
+ - app_links (6.4.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.6)
- powersync_flutter_libs (0.0.1):
+ - Flutter
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.6)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - sqlite3 (3.49.2):
- - sqlite3/common (= 3.49.2)
- - sqlite3/common (3.49.2)
- - sqlite3/dbstatvtab (3.49.2):
+ - sqlite3 (3.50.4):
+ - sqlite3/common (= 3.50.4)
+ - sqlite3/common (3.50.4)
+ - sqlite3/dbstatvtab (3.50.4):
+ - sqlite3/common
+ - sqlite3/fts5 (3.50.4):
- sqlite3/common
- - sqlite3/fts5 (3.49.2):
+ - sqlite3/math (3.50.4):
- sqlite3/common
- - sqlite3/math (3.49.2):
+ - sqlite3/perf-threadsafe (3.50.4):
- sqlite3/common
- - sqlite3/perf-threadsafe (3.49.2):
+ - sqlite3/rtree (3.50.4):
- sqlite3/common
- - sqlite3/rtree (3.49.2):
+ - sqlite3/session (3.50.4):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- - sqlite3 (~> 3.49.1)
+ - sqlite3 (~> 3.50.4)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/math
- sqlite3/perf-threadsafe
- sqlite3/rtree
+ - sqlite3/session
- url_launcher_macos (0.0.1):
- FlutterMacOS
@@ -41,7 +45,7 @@ DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- - powersync_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/macos`)
+ - powersync_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/darwin`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
@@ -59,7 +63,7 @@ EXTERNAL SOURCES:
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
powersync_flutter_libs:
- :path: Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/macos
+ :path: Flutter/ephemeral/.symlinks/plugins/powersync_flutter_libs/darwin
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqlite3_flutter_libs:
@@ -68,16 +72,16 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS:
- app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 42c4a42a692b3b770a5488778789430d67a39b49
+ powersync_flutter_libs: 19fc6b96ff8155ffea72a08990f6c9f2e712b8a6
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
- sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
- sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
+ sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
+ sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/supabase-todolist/macos/Runner.xcodeproj/project.pbxproj b/demos/supabase-todolist/macos/Runner.xcodeproj/project.pbxproj
index 5581abf6..8c3f956d 100644
--- a/demos/supabase-todolist/macos/Runner.xcodeproj/project.pbxproj
+++ b/demos/supabase-todolist/macos/Runner.xcodeproj/project.pbxproj
@@ -508,7 +508,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -587,7 +587,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -634,7 +634,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
diff --git a/demos/supabase-todolist/macos/RunnerTests/RunnerTests.swift b/demos/supabase-todolist/macos/RunnerTests/RunnerTests.swift
new file mode 100644
index 00000000..61f3bd1f
--- /dev/null
+++ b/demos/supabase-todolist/macos/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Cocoa
+import FlutterMacOS
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/demos/supabase-todolist/pubspec.lock b/demos/supabase-todolist/pubspec.lock
index 7522f0ed..24fad347 100644
--- a/demos/supabase-todolist/pubspec.lock
+++ b/demos/supabase-todolist/pubspec.lock
@@ -1,14 +1,30 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ _fe_analyzer_shared:
+ dependency: transitive
+ description:
+ name: _fe_analyzer_shared
+ sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
+ url: "https://pub.dev"
+ source: hosted
+ version: "85.0.0"
+ analyzer:
+ dependency: transitive
+ description:
+ name: analyzer
+ sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.7.1"
app_links:
dependency: transitive
description:
name: app_links
- sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
+ sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8"
url: "https://pub.dev"
source: hosted
- version: "6.4.0"
+ version: "6.4.1"
app_links_linux:
dependency: transitive
description:
@@ -77,18 +93,18 @@ packages:
dependency: transitive
description:
name: camera_android
- sha256: "08808be7e26fc3c7426c81b3fa387564b8e9c22e6fe9cb5675ce3ab7017d8203"
+ sha256: "4db8a27da163130d913ab4360297549ead1c7f9a6a88e71c44e5f4d10081a3d4"
url: "https://pub.dev"
source: hosted
- version: "0.10.10+3"
+ version: "0.10.10+6"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
- sha256: ca36181194f429eef3b09de3c96280f2400693f9735025f90d1f4a27465fdd72
+ sha256: "951ef122d01ebba68b7a54bfe294e8b25585635a90465c311b2f875ae72c412f"
url: "https://pub.dev"
source: hosted
- version: "0.9.19"
+ version: "0.9.21+2"
camera_platform_interface:
dependency: transitive
description:
@@ -117,10 +133,18 @@ packages:
dependency: transitive
description:
name: checked_yaml
- sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
- version: "2.0.3"
+ version: "2.0.4"
+ cli_config:
+ dependency: transitive
+ description:
+ name: cli_config
+ sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.0"
clock:
dependency: transitive
description:
@@ -137,6 +161,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ coverage:
+ dependency: transitive
+ description:
+ name: coverage
+ sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.15.0"
cross_file:
dependency: transitive
description:
@@ -202,10 +242,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
- sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e
+ sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
url: "https://pub.dev"
source: hosted
- version: "2.0.28"
+ version: "2.0.30"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -216,22 +256,38 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ frontend_server_client:
+ dependency: transitive
+ description:
+ name: frontend_server_client
+ sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.0"
functions_client:
dependency: transitive
description:
name: functions_client
- sha256: b410e4d609522357396cd84bb9a8f6e3a4561b5f7d3ce82267f6f1c2af42f16b
+ sha256: "38e5049d4ca5b3482c606d8bfe82183aa24c9650ef1fa0582ab5957a947b937f"
url: "https://pub.dev"
source: hosted
- version: "2.4.2"
+ version: "2.4.4"
+ glob:
+ dependency: transitive
+ description:
+ name: glob
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
gotrue:
dependency: transitive
description:
name: gotrue
- sha256: "04a6efacffd42773ed96dc752f19bb20a1fbc383e81ba82659072b775cf62912"
+ sha256: "4ed944bfa31cca12e6d224ed07557ccf1bb604959de3c7d5282cd11314e7655b"
url: "https://pub.dev"
source: hosted
- version: "2.12.0"
+ version: "2.14.0"
gtk:
dependency: transitive
description:
@@ -244,10 +300,18 @@ packages:
dependency: transitive
description:
name: http
- sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
+ sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
url: "https://pub.dev"
source: hosted
- version: "1.4.0"
+ version: "1.5.0"
+ http_multi_server:
+ dependency: transitive
+ description:
+ name: http_multi_server
+ sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
http_parser:
dependency: transitive
description:
@@ -264,6 +328,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.4"
+ io:
+ dependency: transitive
+ description:
+ name: io
+ sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.2"
json_annotation:
dependency: transitive
description:
@@ -284,26 +364,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
- sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
+ sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev"
source: hosted
- version: "10.0.9"
+ version: "11.0.1"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
- sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
- version: "3.0.9"
+ version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
- sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "3.0.2"
lints:
dependency: transitive
description:
@@ -360,6 +440,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
+ node_preamble:
+ dependency: transitive
+ description:
+ name: node_preamble
+ sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ package_config:
+ dependency: transitive
+ description:
+ name: package_config
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
path:
dependency: "direct main"
description:
@@ -380,18 +476,18 @@ packages:
dependency: transitive
description:
name: path_provider_android
- sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
+ sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
url: "https://pub.dev"
source: hosted
- version: "2.2.17"
+ version: "2.2.18"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
- sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
+ sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
url: "https://pub.dev"
source: hosted
- version: "2.4.1"
+ version: "2.4.2"
path_provider_linux:
dependency: transitive
description:
@@ -420,10 +516,10 @@ packages:
dependency: transitive
description:
name: petitparser
- sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
+ sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
url: "https://pub.dev"
source: hosted
- version: "6.1.0"
+ version: "7.0.1"
platform:
dependency: transitive
description:
@@ -440,14 +536,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
+ pool:
+ dependency: transitive
+ description:
+ name: pool
+ sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.1"
posix:
dependency: transitive
description:
name: posix
- sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62
+ sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
url: "https://pub.dev"
source: hosted
- version: "6.0.2"
+ version: "6.0.3"
postgrest:
dependency: transitive
description:
@@ -462,28 +566,28 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.2"
powersync_attachments_helper:
- dependency: "direct main"
+ dependency: "direct overridden"
description:
path: "../../packages/powersync_attachments_helper"
relative: true
source: path
- version: "0.6.18+7"
+ version: "0.6.19"
powersync_core:
- dependency: "direct overridden"
+ dependency: "direct main"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.2"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.11"
pub_semver:
dependency: transitive
description:
@@ -504,10 +608,10 @@ packages:
dependency: transitive
description:
name: realtime_client
- sha256: "3a0a99b5bd0fc3b35e8ee846d9a22fa2c2117f7ef1cb73d1e5f08f6c3d09c4e9"
+ sha256: "025b7e690e8dcf27844f37d140cca47da5ab31d6fe8d78347fb16763f0a4beb6"
url: "https://pub.dev"
source: hosted
- version: "2.5.0"
+ version: "2.5.2"
retry:
dependency: transitive
description:
@@ -536,10 +640,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
- sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
+ sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
url: "https://pub.dev"
source: hosted
- version: "2.4.10"
+ version: "2.4.12"
shared_preferences_foundation:
dependency: transitive
description:
@@ -580,11 +684,59 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.1"
+ shelf:
+ dependency: transitive
+ description:
+ name: shelf
+ sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.2"
+ shelf_packages_handler:
+ dependency: transitive
+ description:
+ name: shelf_packages_handler
+ sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ shelf_static:
+ dependency: transitive
+ description:
+ name: shelf_static
+ sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
+ shelf_web_socket:
+ dependency: transitive
+ description:
+ name: shelf_web_socket
+ sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
+ source_map_stack_trace:
+ dependency: transitive
+ description:
+ name: source_map_stack_trace
+ sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ source_maps:
+ dependency: transitive
+ description:
+ name: source_maps
+ sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.13"
source_span:
dependency: transitive
description:
@@ -605,33 +757,34 @@ packages:
dependency: transitive
description:
name: sqlite3
- sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e"
+ sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924
url: "https://pub.dev"
source: hosted
- version: "2.7.5"
+ version: "2.9.0"
sqlite3_flutter_libs:
dependency: transitive
description:
name: sqlite3_flutter_libs
- sha256: "1a96b59227828d9eb1463191d684b37a27d66ee5ed7597fcf42eee6452c88a14"
+ sha256: "2b03273e71867a8a4d030861fc21706200debe5c5858a4b9e58f4a1c129586a4"
url: "https://pub.dev"
source: hosted
- version: "0.5.32"
+ version: "0.5.39"
sqlite3_web:
dependency: transitive
description:
name: sqlite3_web
- sha256: "967e076442f7e1233bd7241ca61f3efe4c7fc168dac0f38411bdb3bdf471eb3c"
+ sha256: "0f6ebcb4992d1892ac5c8b5ecd22a458ab9c5eb6428b11ae5ecb5d63545844da"
url: "https://pub.dev"
source: hosted
- version: "0.3.1"
+ version: "0.3.2"
sqlite_async:
dependency: "direct main"
description:
- path: "/Users/simon/src/sqlite_async.dart/packages/sqlite_async"
- relative: false
- source: path
- version: "0.11.5"
+ name: sqlite_async
+ sha256: "6116bfc6aef6ce77730b478385ba4a58873df45721f6a9bc6ffabf39b6576e36"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.1"
stack_trace:
dependency: transitive
description:
@@ -644,10 +797,10 @@ packages:
dependency: transitive
description:
name: storage_client
- sha256: "09bac4d75eea58e8113ca928e6655a09cc8059e6d1b472ee801f01fde815bcfc"
+ sha256: "1c61b19ed9e78f37fdd1ca8b729ab8484e6c8fe82e15c87e070b861951183657"
url: "https://pub.dev"
source: hosted
- version: "2.4.0"
+ version: "2.4.1"
stream_channel:
dependency: transitive
description:
@@ -676,18 +829,18 @@ packages:
dependency: transitive
description:
name: supabase
- sha256: f00172f5f0b2148ea1c573f52862d50cacb6f353f579f741fa35e51704845958
+ sha256: da2afea0f06b06fd0ebb23b916af05df2ef6d56dccae5c0112a2171e668bf9b2
url: "https://pub.dev"
source: hosted
- version: "2.7.0"
+ version: "2.9.0"
supabase_flutter:
dependency: "direct main"
description:
name: supabase_flutter
- sha256: d88eccf9e46e57129725a08e72a3109b6f780921fdc27fe3d7669a11ae80906b
+ sha256: f7eefb065f00f947a8f2d0fbb4be3e687ed7165e80c798bc8c4f0d4295855c51
url: "https://pub.dev"
source: hosted
- version: "2.9.0"
+ version: "2.10.0"
term_glyph:
dependency: transitive
description:
@@ -696,14 +849,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.2"
+ test:
+ dependency: "direct dev"
+ description:
+ name: test
+ sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.26.2"
test_api:
dependency: transitive
description:
name: test_api
- sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
+ sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
- version: "0.7.4"
+ version: "0.7.6"
+ test_core:
+ dependency: transitive
+ description:
+ name: test_core
+ sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.11"
typed_data:
dependency: transitive
description:
@@ -724,26 +893,26 @@ packages:
dependency: transitive
description:
name: url_launcher
- sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
+ sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
- version: "6.3.1"
+ version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
- sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
+ sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
url: "https://pub.dev"
source: hosted
- version: "6.3.16"
+ version: "6.3.18"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
- sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
+ sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
url: "https://pub.dev"
source: hosted
- version: "6.3.3"
+ version: "6.3.4"
url_launcher_linux:
dependency: transitive
description:
@@ -756,10 +925,10 @@ packages:
dependency: transitive
description:
name: url_launcher_macos
- sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
+ sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
url: "https://pub.dev"
source: hosted
- version: "3.2.2"
+ version: "3.2.3"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -796,18 +965,26 @@ packages:
dependency: transitive
description:
name: vector_math
- sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
- version: "2.1.4"
+ version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
- sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
+ sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
- version: "15.0.0"
+ version: "15.0.2"
+ watcher:
+ dependency: transitive
+ description:
+ name: watcher
+ sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
web:
dependency: transitive
description:
@@ -832,6 +1009,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
+ webkit_inspection_protocol:
+ dependency: transitive
+ description:
+ name: webkit_inspection_protocol
+ sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
xdg_directories:
dependency: transitive
description:
@@ -844,10 +1029,10 @@ packages:
dependency: transitive
description:
name: xml
- sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
- version: "6.5.0"
+ version: "6.6.1"
yaml:
dependency: transitive
description:
@@ -865,5 +1050,5 @@ packages:
source: hosted
version: "2.1.0"
sdks:
- dart: ">=3.7.0 <4.0.0"
- flutter: ">=3.27.0"
+ dart: ">=3.8.0 <4.0.0"
+ flutter: ">=3.29.0"
diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml
index 725e4d64..2175e2b4 100644
--- a/demos/supabase-todolist/pubspec.yaml
+++ b/demos/supabase-todolist/pubspec.yaml
@@ -10,8 +10,8 @@ environment:
dependencies:
flutter:
sdk: flutter
- powersync_attachments_helper: ^0.6.18+11
- powersync: ^1.15.0
+ powersync: ^1.16.1
+ powersync_core: ^1.6.1
path_provider: ^2.1.1
supabase_flutter: ^2.0.1
path: ^1.8.3
@@ -19,13 +19,18 @@ dependencies:
camera: ^0.10.5+7
image: ^4.1.3
universal_io: ^2.2.2
- sqlite_async: ^0.11.0
+ sqlite_async: ^0.12.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
+ test: ^1.25.15
flutter:
uses-material-design: true
+ config:
+ # PowerSync supports SPM, but we want to disable it in this demo so that we can keep testing
+ # the CocoaPods parts of powersync_flutter_libs.
+ enable-swift-package-manager: false
diff --git a/demos/supabase-trello/ios/Podfile b/demos/supabase-trello/ios/Podfile
index 885313b0..a8c080ff 100644
--- a/demos/supabase-trello/ios/Podfile
+++ b/demos/supabase-trello/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-trello/ios/Podfile.lock b/demos/supabase-trello/ios/Podfile.lock
index 79306fa5..c47be6d3 100644
--- a/demos/supabase-trello/ios/Podfile.lock
+++ b/demos/supabase-trello/ios/Podfile.lock
@@ -41,13 +41,13 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.2)
- - SDWebImage (5.21.1):
- - SDWebImage/Core (= 5.21.1)
- - SDWebImage/Core (5.21.1)
+ - powersync-sqlite-core (~> 0.4.5)
+ - SDWebImage (5.21.2):
+ - SDWebImage/Core (= 5.21.2)
+ - SDWebImage/Core (5.21.2)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -122,18 +122,18 @@ SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618
- SDWebImage: f29024626962457f3470184232766516dee8dfea
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e
+ SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
-PODFILE CHECKSUM: 9d8d1770d47a428fcb57a5d5f228a34e6d072ae7
+PODFILE CHECKSUM: 681bf989b1752c26661df140f63f5aad6922ddbb
COCOAPODS: 1.16.2
diff --git a/demos/supabase-trello/lib/features/offlineboards/presentation/index.dart b/demos/supabase-trello/lib/features/offlineboards/presentation/index.dart
index 39b8be28..ae59e8e7 100644
--- a/demos/supabase-trello/lib/features/offlineboards/presentation/index.dart
+++ b/demos/supabase-trello/lib/features/offlineboards/presentation/index.dart
@@ -78,7 +78,7 @@ class _OfflineBoardsState extends State with Service {
onTap: () {},
trailing: Switch(
value: brd[j].availableOffline ?? false,
- activeColor: brandColor,
+ activeThumbColor: brandColor,
onChanged: (bool value) {
setState(() {
brd[j].availableOffline = value;
diff --git a/demos/supabase-trello/macos/Podfile b/demos/supabase-trello/macos/Podfile
index c795730d..b52666a1 100644
--- a/demos/supabase-trello/macos/Podfile
+++ b/demos/supabase-trello/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/demos/supabase-trello/macos/Podfile.lock b/demos/supabase-trello/macos/Podfile.lock
index 0eed3cbb..93449db4 100644
--- a/demos/supabase-trello/macos/Podfile.lock
+++ b/demos/supabase-trello/macos/Podfile.lock
@@ -9,10 +9,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.2)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.2)
+ - powersync-sqlite-core (~> 0.4.5)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -81,15 +81,15 @@ SPEC CHECKSUMS:
app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
- FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801
- powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b
+ powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8
+ powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
-PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
diff --git a/demos/supabase-trello/pubspec.lock b/demos/supabase-trello/pubspec.lock
index d101da0a..191e94d1 100644
--- a/demos/supabase-trello/pubspec.lock
+++ b/demos/supabase-trello/pubspec.lock
@@ -542,21 +542,21 @@ packages:
path: "../../packages/powersync"
relative: true
source: path
- version: "1.13.0"
+ version: "1.15.0"
powersync_core:
dependency: "direct overridden"
description:
path: "../../packages/powersync_core"
relative: true
source: path
- version: "1.3.0"
+ version: "1.5.0"
powersync_flutter_libs:
dependency: "direct overridden"
description:
path: "../../packages/powersync_flutter_libs"
relative: true
source: path
- version: "0.4.8"
+ version: "0.4.10"
provider:
dependency: "direct main"
description:
diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml
index afaae23f..a609124e 100644
--- a/demos/supabase-trello/pubspec.yaml
+++ b/demos/supabase-trello/pubspec.yaml
@@ -36,8 +36,8 @@ dependencies:
random_name_generator: ^1.5.0
flutter_dotenv: ^5.2.1
logging: ^1.3.0
- powersync: ^1.15.0
- sqlite_async: ^0.11.0
+ powersync: ^1.16.1
+ sqlite_async: ^0.12.0
path_provider: ^2.1.5
supabase_flutter: ^2.8.3
path: ^1.9.0
diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md
index b901657d..4f30cb92 100644
--- a/packages/powersync/CHANGELOG.md
+++ b/packages/powersync/CHANGELOG.md
@@ -1,3 +1,25 @@
+## 1.16.1
+
+ - Web: Fix decoding sync streams on status.
+
+## 1.16.0
+
+- Add `getCrudTransactions()` returning a stream of completed transactions for uploads.
+- Add experimental support for [sync streams](https://docs.powersync.com/usage/sync-streams).
+- Add new attachments helper implementation in `package:powersync_core/attachments/attachments.dart`.
+- Add SwiftPM support.
+
+## 1.15.2
+
+ - Fix excessive memory consumption during large sync.
+
+## 1.15.1
+
+ - Support latest versions of `package:sqlite3` and `package:sqlite_async`.
+ - Stream client: Improve `disconnect()` while a connection is being opened.
+ - Stream client: Support binary sync lines with Rust client and compatible PowerSync service versions.
+ - Sync client: Improve parsing error responses.
+
## 1.15.0
- Update the PowerSync core extension to `0.4.2`.
diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml
index 268cf642..26fffc25 100644
--- a/packages/powersync/pubspec.yaml
+++ b/packages/powersync/pubspec.yaml
@@ -1,5 +1,5 @@
name: powersync
-version: 1.15.0
+version: 1.16.1
homepage: https://powersync.com
repository: https://github.com/powersync-ja/powersync.dart
description: PowerSync Flutter SDK. Sync Postgres, MongoDB or MySQL with SQLite in your Flutter app
@@ -11,9 +11,9 @@ dependencies:
flutter:
sdk: flutter
- sqlite3_flutter_libs: ^0.5.23
- powersync_core: ^1.5.0
- powersync_flutter_libs: ^0.4.10
+ sqlite3_flutter_libs: ^0.5.39
+ powersync_core: ^1.6.1
+ powersync_flutter_libs: ^0.4.12
collection: ^1.17.0
dev_dependencies:
diff --git a/packages/powersync_attachments_helper/CHANGELOG.md b/packages/powersync_attachments_helper/CHANGELOG.md
index ab5b00e6..f80b8798 100644
--- a/packages/powersync_attachments_helper/CHANGELOG.md
+++ b/packages/powersync_attachments_helper/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.6.20
+
+ - Add note about new attachment queue system in core package.
+
+## 0.6.19
+
+ - Remove direct dependency on `sqlite_async`.
+
## 0.6.18+11
- Update a dependency to the latest release.
diff --git a/packages/powersync_attachments_helper/README.md b/packages/powersync_attachments_helper/README.md
index dbd65bef..c9b5f532 100644
--- a/packages/powersync_attachments_helper/README.md
+++ b/packages/powersync_attachments_helper/README.md
@@ -1,6 +1,20 @@
# PowerSync Attachments Helper for Dart/Flutter
-[PowerSync Attachments Helper](https://pub.dev/packages/powersync_attachments_helper) is a package that assist in keeping files in sync with local and remote storage.
+[PowerSync Attachments Helper](https://pub.dev/packages/powersync_attachments_helper) is a package that assists in keeping files in sync between local and remote storage.
+
+> [!WARNING]
+> This package will eventually be replaced by a new attachments helper library in the core PowerSync package, available through:
+> ```dart
+> package:powersync_core/attachments/attachments.dart
+> ```
+>
+> The `powersync_core/attachments` library is in alpha and brings improved APIs and functionality that is more in line with our other SDKs, such as the ability to write your own local storage implementation.
+>
+> Check out the [docs here](https://pub.dev/documentation/powersync_core/latest/topics/attachments-topic.html) to get started.
+>
+> While the `powersync_attachments_helper` package will still get bug fixes if you need them,
+> new features will only be developed on `powersync_core/attachments`.
+
## Features
@@ -83,5 +97,3 @@ initializeAttachmentQueue(PowerSyncDatabase db) async {
await attachmentQueue.init();
}
```
-
-See our [Supabase Flutter To-Do List example app](../../demos/supabase-todolist/README.md) for a concrete implementation of the above.
diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml
index c920068a..971228f9 100644
--- a/packages/powersync_attachments_helper/pubspec.yaml
+++ b/packages/powersync_attachments_helper/pubspec.yaml
@@ -1,6 +1,6 @@
name: powersync_attachments_helper
description: A helper library for handling attachments when using PowerSync.
-version: 0.6.18+11
+version: 0.6.20
repository: https://github.com/powersync-ja/powersync.dart
homepage: https://www.powersync.com/
environment:
@@ -10,9 +10,8 @@ dependencies:
flutter:
sdk: flutter
- powersync_core: ^1.5.0
+ powersync_core: ^1.6.1
logging: ^1.2.0
- sqlite_async: ^0.11.0
path_provider: ^2.0.13
dev_dependencies:
diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md
index ee22df4d..4ba23772 100644
--- a/packages/powersync_core/CHANGELOG.md
+++ b/packages/powersync_core/CHANGELOG.md
@@ -1,3 +1,28 @@
+## 1.6.1
+
+ - Web: Fix decoding sync streams on status.
+
+ - **DOCS**: Point to uses in example. ([4f4da24e](https://github.com/powersync-ja/powersync.dart/commit/4f4da24e580dec6b1d29a5e0907b83ba7c55e3d8))
+
+## 1.6.0
+
+- Add `getCrudTransactions()` returning a stream of completed transactions for uploads.
+- Add experimental support for [sync streams](https://docs.powersync.com/usage/sync-streams).
+- Add new attachments helper implementation in `package:powersync_core/attachments/attachments.dart`.
+- Add SwiftPM support.
+- Add support for compiling `powersync_core` with `build_web_compilers`.
+
+## 1.5.2
+
+ - Fix excessive memory consumption during large sync.
+
+## 1.5.1
+
+ - Support latest versions of `package:sqlite3` and `package:sqlite_async`.
+ - Stream client: Improve `disconnect()` while a connection is being opened.
+ - Stream client: Support binary sync lines with Rust client and compatible PowerSync service versions.
+ - Sync client: Improve parsing error responses.
+
## 1.5.0
- Update the PowerSync core extension to `0.4.2`.
diff --git a/packages/powersync_core/dartdoc_options.yaml b/packages/powersync_core/dartdoc_options.yaml
new file mode 100644
index 00000000..4fe2c4e0
--- /dev/null
+++ b/packages/powersync_core/dartdoc_options.yaml
@@ -0,0 +1,5 @@
+dartdoc:
+ categories:
+ attachments:
+ name: Attachments
+ markdown: doc/attachments.md
diff --git a/packages/powersync_core/doc/attachments.md b/packages/powersync_core/doc/attachments.md
new file mode 100644
index 00000000..6919430f
--- /dev/null
+++ b/packages/powersync_core/doc/attachments.md
@@ -0,0 +1,135 @@
+## Attachments
+
+In many cases, you might want to sync large binary data (like images) along with the data synced by
+PowerSync.
+Embedding this data directly in your source databases is [inefficient and not recommended](https://docs.powersync.com/usage/use-case-examples/attachments).
+
+Instead, the PowerSync SDK for Dart and Flutter provides utilities you can use to _reference_ this binary data
+in your regular database schema, and then download it from a secondary data store such as Supabase Storage or S3.
+Because binary data is not directly stored in the source database in this model, we call these files _attachments_.
+
+
+> [!NOTE]
+> These attachment utilities are recommended over our legacy [PowerSync Attachments Helper](https://pub.dev/packages/powersync_attachments_helper) package. The new utilities provide cleaner APIs that are more aligned with similar helpers in our other SDKs, and include improved features such as:
+> - Support for writing your own local storage implementation
+> - Support for dynamic nested directories and custom per-attachment file extensions out of the box
+> - Ability to add optional `metaData` to attachments
+> - No longer depends on `dart:io`
+>
+> If you are new to handling attachments, we recommend starting with these utilities. If you currently use the legacy `powersync_attachments_helper` package, a fairly simple migration would be to adopt the new utilities with a different table name and drop the legacy package. This means existing attachments are lost, but they should be downloaded again.
+
+## Alpha release
+
+The attachment utilities described in this document are currently in an alpha state, intended for testing.
+Expect breaking changes and instability as development continues.
+The attachments API is marked as `@experimental` for this reason.
+
+Do not rely on these utilities for production use.
+
+## Usage
+
+An `AttachmentQueue` instance is used to manage and sync attachments in your app.
+The attachments' state is stored in a local-only attachments table.
+
+### Key assumptions
+
+- Each attachment is identified by a unique id.
+- Attachments are immutable once created.
+- Relational data should reference attachments using a foreign key column.
+- Relational data should reflect the holistic state of attachments at any given time. Any existing local attachment
+ will be deleted locally if no relational data references it.
+
+### Example implementation
+
+See the [supabase-todolist](https://github.com/powersync-ja/powersync.dart/tree/main/demos/supabase-todolist) demo for a basic example of attachment syncing.
+
+In particular, relevant snippets from that example are:
+
+- The attachment queue is set up [here](https://github.com/powersync-ja/powersync.dart/blob/98d73e2f157a697786373fef755576505abc74a5/demos/supabase-todolist/lib/attachments/queue.dart#L16-L36), using conditional imports to store attachments in the file system on native platforms and in-memory for web.
+- When a new attachment is added, `saveFile` is called [here](https://github.com/powersync-ja/powersync.dart/blob/98d73e2f157a697786373fef755576505abc74a5/demos/supabase-todolist/lib/attachments/queue.dart#L38-L55) and automatically updates references in the main schema to reference the attachment.
+
+### Setup
+
+First, add a table storing local attachment state to your database schema.
+
+```dart
+final schema = Schema([
+ AttachmentsQueueTable(),
+ // In this document, we assume the photo_id column of the todos table references an optional photo
+ // stored as an attachment.
+ Table('todos', [
+ Column.text('list_id'),
+ Column.text('photo_id'),
+ Column.text('description'),
+ Column.integer('completed'),
+ ]),
+]);
+```
+
+Next, create an `AttachmentQueue` instance. This class provides default syncing utilities and implements a default
+sync strategy. This class can be extended for custom functionality, if needed.
+
+```dart
+final directory = await getApplicationDocumentsDirectory();
+
+final attachmentQueue = AttachmentQueue(
+ db: db,
+ remoteStorage: SupabaseStorageAdapter(), // instance responsible for uploads and downloads
+ logger: logger,
+ localStorage: IOLocalStorage(appDocDir), // IOLocalStorage requires `dart:io` and is not available on the web
+ watchAttachments: () => db.watch('''
+ SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL
+ ''').map((results) => [
+ for (final row in results)
+ WatchedAttachmentItem(
+ id: row['id'] as String,
+ fileExtension: 'jpg',
+ )
+ ],
+ ),
+);
+```
+
+Here,
+ - An instance of `LocalStorageAdapter`, such as the `IOLocalStorage` provided by the SDK, is responsible for storing
+ attachment contents locally.
+ - An instance of `RemoteStorageAdapter` is responsible for downloading and uploading attachment contents to the secondary
+ service, such as S3, Firebase cloud storage or Supabase storage.
+ - `watchAttachments` is a function emitting a stream of attachment items that are considered to be referenced from
+ the current database state. In this example, `todos.photo_id` is the only column referencing attachments.
+
+Next, start the sync process by calling `attachmentQueue.startSync()`.
+
+## Storing attachments
+
+To create a new attachment locally, call `AttachmentQueue.saveFile`. To represent the attachment, this method takes
+the contents to store, the media type, an optional file extension and id.
+The queue will store the contents in a local file and mark it as queued for upload. It also invokes a callback
+responsible for referencing the id of the generated attachment in the primary data model:
+
+```dart
+Future savePhotoAttachment(
+ Stream> photoData, String todoId,
+ {String mediaType = 'image/jpeg'}) async {
+ // Save the file using the AttachmentQueue API
+ return await attachmentQueue.saveFile(
+ data: photoData,
+ mediaType: mediaType,
+ fileExtension: 'jpg',
+ metaData: 'Photo attachment for todo: $todoId',
+ updateHook: (context, attachment) async {
+ // Update the todo item to reference this attachment
+ await context.execute(
+ 'UPDATE todos SET photo_id = ? WHERE id = ?',
+ [attachment.id, todoId],
+ );
+ },
+ );
+}
+```
+
+## Deleting attachments
+
+To delete attachments, it is sufficient to stop referencing them in the data model, e.g. via
+`UPDATE todos SET photo_id = NULL` in this example. The attachment sync implementation will eventually
+delete orphaned attachments from the local storage.
diff --git a/packages/powersync_core/lib/attachments/attachments.dart b/packages/powersync_core/lib/attachments/attachments.dart
new file mode 100644
index 00000000..a69f9409
--- /dev/null
+++ b/packages/powersync_core/lib/attachments/attachments.dart
@@ -0,0 +1,12 @@
+/// Imports for attachments that are available on all platforms.
+///
+/// For more details on using attachments, see the documentation for the topic.
+///
+/// {@category attachments}
+library;
+
+export '../src/attachments/attachment.dart';
+export '../src/attachments/attachment_queue_service.dart';
+export '../src/attachments/local_storage.dart';
+export '../src/attachments/remote_storage.dart';
+export '../src/attachments/sync_error_handler.dart';
diff --git a/packages/powersync_core/lib/attachments/io.dart b/packages/powersync_core/lib/attachments/io.dart
new file mode 100644
index 00000000..142abb26
--- /dev/null
+++ b/packages/powersync_core/lib/attachments/io.dart
@@ -0,0 +1,12 @@
+/// A platform-specific import supporting attachments on native platforms.
+///
+/// This library exports the [IOLocalStorage] class, implementing the
+/// [LocalStorage] interface by storing files under a root directory.
+///
+/// {@category attachments}
+library;
+
+import '../src/attachments/io_local_storage.dart';
+import '../src/attachments/local_storage.dart';
+
+export '../src/attachments/io_local_storage.dart';
diff --git a/packages/powersync_core/lib/powersync_core.dart b/packages/powersync_core/lib/powersync_core.dart
index b4dfe35d..ef2e97c7 100644
--- a/packages/powersync_core/lib/powersync_core.dart
+++ b/packages/powersync_core/lib/powersync_core.dart
@@ -11,6 +11,7 @@ export 'src/log.dart';
export 'src/open_factory.dart';
export 'src/schema.dart';
export 'src/sync/options.dart' hide ResolvedSyncOptions;
+export 'src/sync/stream.dart' hide CoreActiveStreamSubscription;
export 'src/sync/sync_status.dart'
- hide BucketProgress, InternalSyncDownloadProgress;
+ hide BucketProgress, InternalSyncDownloadProgress, InternalSyncStatusAccess;
export 'src/uuid.dart';
diff --git a/packages/powersync_core/lib/src/attachments/attachment.dart b/packages/powersync_core/lib/src/attachments/attachment.dart
new file mode 100644
index 00000000..00f0032a
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/attachment.dart
@@ -0,0 +1,197 @@
+/// Defines attachment states and the Attachment model for the PowerSync
+/// attachments system.
+///
+/// Includes metadata, state, and utility methods for working with attachments.
+library;
+
+import 'package:meta/meta.dart';
+import 'package:powersync_core/sqlite3_common.dart' show Row;
+import 'package:powersync_core/powersync_core.dart';
+
+/// Represents the state of an attachment.
+///
+/// {@category attachments}
+@experimental
+enum AttachmentState {
+ /// The attachment is queued for download from the remote storage.
+ queuedDownload,
+
+ /// The attachment is queued for upload to the remote storage.
+ queuedUpload,
+
+ /// The attachment is queued for deletion from the remote storage.
+ queuedDelete,
+
+ /// The attachment is fully synchronized with the remote storage.
+ synced,
+
+ /// The attachment is archived and no longer actively synchronized.
+ archived;
+
+ /// Constructs an [AttachmentState] from the corresponding integer value.
+ ///
+ /// Throws [ArgumentError] if the value does not match any [AttachmentState].
+ static AttachmentState fromInt(int value) {
+ if (value < 0 || value >= AttachmentState.values.length) {
+ throw ArgumentError('Invalid value for AttachmentState: $value');
+ }
+ return AttachmentState.values[value];
+ }
+
+ /// Returns the ordinal value of this [AttachmentState].
+ int toInt() => index;
+}
+
+/// Represents an attachment with metadata and state information.
+///
+/// {@category Attachments}
+///
+/// Properties:
+/// - [id]: Unique identifier for the attachment.
+/// - [timestamp]: Timestamp of the last record update.
+/// - [filename]: Name of the attachment file, e.g., `[id].jpg`.
+/// - [state]: Current state of the attachment, represented as an ordinal of [AttachmentState].
+/// - [localUri]: Local URI pointing to the attachment file, if available.
+/// - [mediaType]: Media type of the attachment, typically represented as a MIME type.
+/// - [size]: Size of the attachment in bytes, if available.
+/// - [hasSynced]: Indicates whether the attachment has been synced locally before.
+/// - [metaData]: Additional metadata associated with the attachment.
+///
+/// {@category attachments}
+@experimental
+final class Attachment {
+ /// Unique identifier for the attachment.
+ final String id;
+
+ /// Timestamp of the last record update.
+ final int timestamp;
+
+ /// Name of the attachment file, e.g., `[id].jpg`.
+ final String filename;
+
+ /// Current state of the attachment, represented as an ordinal of [AttachmentState].
+ final AttachmentState state;
+
+ /// Local URI pointing to the attachment file, if available.
+ final String? localUri;
+
+ /// Media type of the attachment, typically represented as a MIME type.
+ final String? mediaType;
+
+ /// Size of the attachment in bytes, if available.
+ final int? size;
+
+ /// Indicates whether the attachment has been synced locally before.
+ final bool hasSynced;
+
+ /// Additional metadata associated with the attachment.
+ final String? metaData;
+
+ /// Creates an [Attachment] instance.
+ const Attachment({
+ required this.id,
+ this.timestamp = 0,
+ required this.filename,
+ this.state = AttachmentState.queuedDownload,
+ this.localUri,
+ this.mediaType,
+ this.size,
+ this.hasSynced = false,
+ this.metaData,
+ });
+
+ /// Creates an [Attachment] instance from a database row.
+ ///
+ /// [row]: The [Row] containing attachment data.
+ /// Returns an [Attachment] instance populated with data from the row.
+ factory Attachment.fromRow(Row row) {
+ return Attachment(
+ id: row['id'] as String,
+ timestamp: row['timestamp'] as int? ?? 0,
+ filename: row['filename'] as String,
+ localUri: row['local_uri'] as String?,
+ mediaType: row['media_type'] as String?,
+ size: row['size'] as int?,
+ state: AttachmentState.fromInt(row['state'] as int),
+ hasSynced: (row['has_synced'] as int? ?? 0) > 0,
+ metaData: row['meta_data']?.toString(),
+ );
+ }
+
+ /// Returns a copy of this attachment with the given fields replaced.
+ Attachment copyWith({
+ String? id,
+ int? timestamp,
+ String? filename,
+ AttachmentState? state,
+ String? localUri,
+ String? mediaType,
+ int? size,
+ bool? hasSynced,
+ String? metaData,
+ }) {
+ return Attachment(
+ id: id ?? this.id,
+ timestamp: timestamp ?? this.timestamp,
+ filename: filename ?? this.filename,
+ state: state ?? this.state,
+ localUri: localUri ?? this.localUri,
+ mediaType: mediaType ?? this.mediaType,
+ size: size ?? this.size,
+ hasSynced: hasSynced ?? this.hasSynced,
+ metaData: metaData ?? this.metaData,
+ );
+ }
+
+ Attachment markAsUnavailableLocally(AttachmentState newState) {
+ return Attachment(
+ id: id,
+ timestamp: timestamp,
+ filename: filename,
+ state: newState,
+ localUri: null,
+ mediaType: mediaType,
+ size: size,
+ hasSynced: false,
+ metaData: metaData,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'Attachment(id: $id, state: $state, localUri: $localUri, metadata: $metaData)';
+ }
+}
+
+/// Table definition for the attachments queue.
+///
+/// The columns in this table are used by the attachments implementation to
+/// store which attachments have been download and tracks metadata for state.
+///
+/// {@category attachments}
+@experimental
+final class AttachmentsQueueTable extends Table {
+ AttachmentsQueueTable({
+ String attachmentsQueueTableName = defaultTableName,
+ List additionalColumns = const [],
+ List indexes = const [],
+ String? viewName,
+ }) : super.localOnly(
+ attachmentsQueueTableName,
+ [
+ const Column.text('filename'),
+ const Column.text('local_uri'),
+ const Column.integer('timestamp'),
+ const Column.integer('size'),
+ const Column.text('media_type'),
+ const Column.integer('state'),
+ const Column.integer('has_synced'),
+ const Column.text('meta_data'),
+ ...additionalColumns,
+ ],
+ viewName: viewName,
+ indexes: indexes,
+ );
+
+ static const defaultTableName = 'attachments_queue';
+}
diff --git a/packages/powersync_core/lib/src/attachments/attachment_queue_service.dart b/packages/powersync_core/lib/src/attachments/attachment_queue_service.dart
new file mode 100644
index 00000000..0ec673e0
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/attachment_queue_service.dart
@@ -0,0 +1,443 @@
+// Implements the attachment queue for PowerSync attachments.
+//
+// This class manages the lifecycle of attachment records, including watching for new attachments,
+// syncing with remote storage, handling uploads, downloads, and deletes, and managing local storage.
+// It provides hooks for error handling, cache management, and custom filename resolution.
+
+import 'dart:async';
+
+import 'package:logging/logging.dart';
+import 'package:meta/meta.dart';
+import 'package:powersync_core/powersync_core.dart';
+import 'package:sqlite_async/sqlite_async.dart';
+
+import 'attachment.dart';
+import 'implementations/attachment_context.dart';
+import 'local_storage.dart';
+import 'remote_storage.dart';
+import 'sync_error_handler.dart';
+import 'implementations/attachment_service.dart';
+import 'sync/syncing_service.dart';
+
+/// A watched attachment record item.
+///
+/// This is usually returned from watching all relevant attachment IDs.
+///
+/// - [id]: Id for the attachment record.
+/// - [fileExtension]: File extension used to determine an internal filename for storage if no [filename] is provided.
+/// - [filename]: Filename to store the attachment with.
+/// - [metaData]: Optional metadata for the attachment record.
+///
+/// {@category attachments}
+@experimental
+final class WatchedAttachmentItem {
+ /// Id for the attachment record.
+ final String id;
+
+ /// File extension used to determine an internal filename for storage if no [filename] is provided.
+ final String? fileExtension;
+
+ /// Filename to store the attachment with.
+ final String? filename;
+
+ /// Optional metadata for the attachment record.
+ final String? metaData;
+
+ /// Creates a [WatchedAttachmentItem].
+ ///
+ /// Either [fileExtension] or [filename] must be provided.
+ const WatchedAttachmentItem({
+ required this.id,
+ this.fileExtension,
+ this.filename,
+ this.metaData,
+ }) : assert(
+ fileExtension != null || filename != null,
+ 'Either fileExtension or filename must be provided.',
+ );
+}
+
+/// Class used to implement the attachment queue.
+///
+/// Manages the lifecycle of attachment records, including watching for new attachments,
+/// syncing with remote storage, handling uploads, downloads, and deletes, and managing local storage.
+///
+/// {@category attachments}
+@experimental
+base class AttachmentQueue {
+ final PowerSyncDatabase _db;
+ final Stream> Function() _watchAttachments;
+ final LocalStorage _localStorage;
+ final bool _downloadAttachments;
+ final Logger _logger;
+
+ final Mutex _mutex = Mutex();
+ bool _closed = false;
+ StreamSubscription? _syncStatusSubscription;
+ StreamSubscription? _watchedAttachmentsSubscription;
+ final AttachmentService _attachmentsService;
+ final SyncingService _syncingService;
+
+ AttachmentQueue._(
+ {required PowerSyncDatabase db,
+ required Stream> Function() watchAttachments,
+ required LocalStorage localStorage,
+ required bool downloadAttachments,
+ required Logger logger,
+ required AttachmentService attachmentsService,
+ required SyncingService syncingService})
+ : _db = db,
+ _watchAttachments = watchAttachments,
+ _localStorage = localStorage,
+ _downloadAttachments = downloadAttachments,
+ _logger = logger,
+ _attachmentsService = attachmentsService,
+ _syncingService = syncingService;
+
+ /// Creates a new attachment queue.
+ ///
+ /// Parameters:
+ ///
+ /// - [db]: PowerSync database client.
+ /// - [remoteStorage]: Adapter which interfaces with the remote storage backend.
+ /// - [watchAttachments]: A stream generator for the current state of local attachments.
+ /// - [localStorage]: Provides access to local filesystem storage methods.
+ /// - [attachmentsQueueTableName]: SQLite table where attachment state will be recorded.
+ /// - [errorHandler]: Attachment operation error handler. Specifies if failed attachment operations should be retried.
+ /// - [syncInterval]: Periodic interval to trigger attachment sync operations.
+ /// - [archivedCacheLimit]: Defines how many archived records are retained as a cache.
+ /// - [syncThrottleDuration]: Throttles remote sync operations triggering.
+ /// - [downloadAttachments]: Should attachments be downloaded.
+ /// - [logger]: Logging interface used for all log operations.
+ factory AttachmentQueue({
+ required PowerSyncDatabase db,
+ required RemoteStorage remoteStorage,
+ required Stream> Function() watchAttachments,
+ required LocalStorage localStorage,
+ String attachmentsQueueTableName = AttachmentsQueueTable.defaultTableName,
+ AttachmentErrorHandler? errorHandler,
+ Duration syncInterval = const Duration(seconds: 30),
+ int archivedCacheLimit = 100,
+ Duration syncThrottleDuration = const Duration(seconds: 1),
+ bool downloadAttachments = true,
+ Logger? logger,
+ }) {
+ final resolvedLogger = logger ?? db.logger;
+
+ final attachmentsService = AttachmentService(
+ db: db,
+ logger: resolvedLogger,
+ maxArchivedCount: archivedCacheLimit,
+ attachmentsQueueTableName: attachmentsQueueTableName,
+ );
+ final syncingService = SyncingService(
+ remoteStorage: remoteStorage,
+ localStorage: localStorage,
+ attachmentsService: attachmentsService,
+ errorHandler: errorHandler,
+ syncThrottle: syncThrottleDuration,
+ period: syncInterval,
+ logger: resolvedLogger,
+ );
+
+ return AttachmentQueue._(
+ db: db,
+ watchAttachments: watchAttachments,
+ localStorage: localStorage,
+ downloadAttachments: downloadAttachments,
+ logger: resolvedLogger,
+ attachmentsService: attachmentsService,
+ syncingService: syncingService,
+ );
+ }
+
+ /// Initialize the attachment queue by:
+ /// 1. Creating the attachments directory.
+ /// 2. Adding watches for uploads, downloads, and deletes.
+ /// 3. Adding a trigger to run uploads, downloads, and deletes when the device is online after being offline.
+ Future startSync() async {
+ await _mutex.lock(() async {
+ if (_closed) {
+ throw StateError('Attachment queue has been closed');
+ }
+
+ await _stopSyncingInternal();
+
+ await _localStorage.initialize();
+
+ await _attachmentsService.withContext((context) async {
+ await _verifyAttachments(context);
+ });
+
+ // Listen for connectivity changes and watched attachments
+ await _syncingService.startSync();
+
+ _watchedAttachmentsSubscription =
+ _watchAttachments().listen((items) async {
+ await _processWatchedAttachments(items);
+ });
+
+ var previouslyConnected = _db.currentStatus.connected;
+ _syncStatusSubscription = _db.statusStream.listen((status) {
+ if (!previouslyConnected && status.connected) {
+ _syncingService.triggerSync();
+ }
+
+ previouslyConnected = status.connected;
+ });
+ _watchAttachments().listen((items) async {
+ await _processWatchedAttachments(items);
+ });
+
+ _logger.info('AttachmentQueue started syncing.');
+ });
+ }
+
+ /// Stops syncing. Syncing may be resumed with [startSync].
+ Future stopSyncing() async {
+ await _mutex.lock(() async {
+ await _stopSyncingInternal();
+ });
+ }
+
+ Future _stopSyncingInternal() async {
+ if (_closed ||
+ _syncStatusSubscription == null ||
+ _watchedAttachmentsSubscription == null) {
+ return;
+ }
+
+ await (
+ _syncStatusSubscription!.cancel(),
+ _watchedAttachmentsSubscription!.cancel(),
+ ).wait;
+
+ _syncStatusSubscription = null;
+ _watchedAttachmentsSubscription = null;
+ await _syncingService.stopSync();
+
+ _logger.info('AttachmentQueue stopped syncing.');
+ }
+
+ /// Closes the queue. The queue cannot be used after closing.
+ Future close() async {
+ await _mutex.lock(() async {
+ if (_closed) return;
+
+ await _stopSyncingInternal();
+ _closed = true;
+ _logger.info('AttachmentQueue closed.');
+ });
+ }
+
+ /// Resolves the filename for new attachment items.
+ /// Concatenates the attachment ID and extension by default.
+ Future resolveNewAttachmentFilename(
+ String attachmentId,
+ String? fileExtension,
+ ) async {
+ return '$attachmentId.${fileExtension ?? 'dat'}';
+ }
+
+ /// Processes attachment items returned from `watchAttachments`.
+ ///
+ /// The default implementation asserts the items returned from
+ /// `watchAttachments` as the definitive state for local attachments.
+ Future _processWatchedAttachments(
+ List items,
+ ) async {
+ await _attachmentsService.withContext((context) async {
+ final currentAttachments = await context.getAttachments();
+ final List attachmentUpdates = [];
+
+ for (final item in items) {
+ final existingQueueItem =
+ currentAttachments.where((a) => a.id == item.id).firstOrNull;
+
+ if (existingQueueItem == null) {
+ if (!_downloadAttachments) continue;
+
+ // This item should be added to the queue.
+ // This item is assumed to be coming from an upstream sync.
+ final String filename = item.filename ??
+ await resolveNewAttachmentFilename(item.id, item.fileExtension);
+
+ attachmentUpdates.add(
+ Attachment(
+ id: item.id,
+ filename: filename,
+ state: AttachmentState.queuedDownload,
+ metaData: item.metaData,
+ ),
+ );
+ } else if (existingQueueItem.state == AttachmentState.archived) {
+ // The attachment is present again. Need to queue it for sync.
+ if (existingQueueItem.hasSynced) {
+ // No remote action required, we can restore the record (avoids deletion).
+ attachmentUpdates.add(
+ existingQueueItem.copyWith(state: AttachmentState.synced),
+ );
+ } else {
+ // The localURI should be set if the record was meant to be downloaded
+ // and has been synced. If it's missing and hasSynced is false then
+ // it must be an upload operation.
+ attachmentUpdates.add(
+ existingQueueItem.copyWith(
+ state: existingQueueItem.localUri == null
+ ? AttachmentState.queuedDownload
+ : AttachmentState.queuedUpload,
+ ),
+ );
+ }
+ }
+ }
+
+ // Archive any items not specified in the watched items.
+ // For queuedDelete or queuedUpload states, archive only if hasSynced is true.
+ // For other states, archive if the record is not found in the items.
+ for (final attachment in currentAttachments) {
+ final notInWatchedItems = items.every(
+ (update) => update.id != attachment.id,
+ );
+
+ if (notInWatchedItems) {
+ switch (attachment.state) {
+ case AttachmentState.queuedDelete:
+ case AttachmentState.queuedUpload:
+ if (attachment.hasSynced) {
+ attachmentUpdates.add(
+ attachment.copyWith(state: AttachmentState.archived),
+ );
+ }
+ default:
+ attachmentUpdates.add(
+ attachment.copyWith(state: AttachmentState.archived),
+ );
+ }
+ }
+ }
+
+ await context.saveAttachments(attachmentUpdates);
+ });
+ }
+
+ /// Generates a random attachment id.
+ Future generateAttachmentId() async {
+ final row = await _db.get('SELECT uuid() as id');
+ return row['id'] as String;
+ }
+
+ /// Creates a new attachment locally and queues it for upload.
+ /// The filename is resolved using [resolveNewAttachmentFilename].
+ Future saveFile({
+ required Stream> data,
+ required String mediaType,
+ String? fileExtension,
+ String? metaData,
+ String? id,
+ required Future Function(
+ SqliteWriteContext context, Attachment attachment)
+ updateHook,
+ }) async {
+ final resolvedId = id ?? await generateAttachmentId();
+
+ final filename = await resolveNewAttachmentFilename(
+ resolvedId,
+ fileExtension,
+ );
+
+ // Write the file to the filesystem.
+ final fileSize = await _localStorage.saveFile(filename, data);
+
+ return await _attachmentsService.withContext((attachmentContext) async {
+ return await _db.writeTransaction((tx) async {
+ final attachment = Attachment(
+ id: resolvedId,
+ filename: filename,
+ size: fileSize,
+ mediaType: mediaType,
+ state: AttachmentState.queuedUpload,
+ localUri: filename,
+ metaData: metaData,
+ );
+
+ // Allow consumers to set relationships to this attachment ID.
+ await updateHook(tx, attachment);
+
+ return await attachmentContext.upsertAttachment(attachment, tx);
+ });
+ });
+ }
+
+ /// Queues an attachment for delete.
+ /// The default implementation assumes the attachment record already exists locally.
+ Future deleteFile({
+ required String attachmentId,
+ required Future Function(
+ SqliteWriteContext context, Attachment attachment)
+ updateHook,
+ }) async {
+ return await _attachmentsService.withContext((attachmentContext) async {
+ final attachment = await attachmentContext.getAttachment(attachmentId);
+ if (attachment == null) {
+ throw Exception(
+ 'Attachment record with id $attachmentId was not found.',
+ );
+ }
+
+ return await _db.writeTransaction((tx) async {
+ await updateHook(tx, attachment);
+ return await attachmentContext.upsertAttachment(
+ attachment.copyWith(
+ state: AttachmentState.queuedDelete,
+ hasSynced: false,
+ ),
+ tx,
+ );
+ });
+ });
+ }
+
+ /// Removes all archived items.
+ Future expireCache() async {
+ await _attachmentsService.withContext((context) async {
+ bool done;
+ do {
+ done = await _syncingService.deleteArchivedAttachments(context);
+ } while (!done);
+ });
+ }
+
+ /// Clears the attachment queue and deletes all attachment files.
+ Future clearQueue() async {
+ await _attachmentsService.withContext((context) async {
+ await context.clearQueue();
+ });
+ await _localStorage.clear();
+ }
+
+ /// Cleans up stale attachments.
+ Future _verifyAttachments(AttachmentContext context) async {
+ final attachments = await context.getActiveAttachments();
+ final List updates = [];
+
+ for (final attachment in attachments) {
+ // Only check attachments that should have local files
+ if (attachment.localUri == null) {
+ // Skip attachments that don't have localUri (like queued downloads)
+ continue;
+ }
+
+ final exists = await _localStorage.fileExists(attachment.localUri!);
+ if ((attachment.state == AttachmentState.synced ||
+ attachment.state == AttachmentState.queuedUpload) &&
+ !exists) {
+ updates.add(
+ attachment.markAsUnavailableLocally(AttachmentState.archived),
+ );
+ }
+ }
+
+ await context.saveAttachments(updates);
+ }
+}
diff --git a/packages/powersync_core/lib/src/attachments/implementations/attachment_context.dart b/packages/powersync_core/lib/src/attachments/implementations/attachment_context.dart
new file mode 100644
index 00000000..9428d79c
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/implementations/attachment_context.dart
@@ -0,0 +1,160 @@
+import 'package:powersync_core/powersync_core.dart';
+import 'package:powersync_core/sqlite3_common.dart';
+import 'package:logging/logging.dart';
+import 'package:sqlite_async/sqlite_async.dart';
+import 'package:meta/meta.dart';
+
+import '../attachment.dart';
+
+@internal
+final class AttachmentContext {
+ final PowerSyncDatabase db;
+ final Logger log;
+ final int maxArchivedCount;
+ final String attachmentsQueueTableName;
+
+ AttachmentContext(
+ this.db,
+ this.log,
+ this.maxArchivedCount,
+ this.attachmentsQueueTableName,
+ );
+
+ /// Table used for storing attachments in the attachment queue.
+ String get table {
+ return attachmentsQueueTableName;
+ }
+
+ Future deleteAttachment(String id) async {
+ log.info('deleteAttachment: $id');
+ await db.writeTransaction((tx) async {
+ await tx.execute('DELETE FROM $table WHERE id = ?', [id]);
+ });
+ }
+
+ Future ignoreAttachment(String id) async {
+ await db.execute(
+ 'UPDATE $table SET state = ${AttachmentState.archived.index} WHERE id = ?',
+ [id],
+ );
+ }
+
+ Future getAttachment(String id) async {
+ final row = await db.getOptional('SELECT * FROM $table WHERE id = ?', [id]);
+ if (row == null) {
+ return null;
+ }
+ return Attachment.fromRow(row);
+ }
+
+ Future saveAttachment(Attachment attachment) async {
+ return await db.writeLock((ctx) async {
+ return await upsertAttachment(attachment, ctx);
+ });
+ }
+
+ Future saveAttachments(List attachments) async {
+ if (attachments.isEmpty) {
+ log.finer('No attachments to save.');
+ return;
+ }
+ await db.writeTransaction((tx) async {
+ for (final attachment in attachments) {
+ await upsertAttachment(attachment, tx);
+ }
+ });
+ }
+
+ Future> getAttachmentIds() async {
+ ResultSet results = await db.getAll(
+ 'SELECT id FROM $table WHERE id IS NOT NULL',
+ );
+
+ List ids = results.map((row) => row['id'] as String).toList();
+
+ return ids;
+ }
+
+ Future> getAttachments() async {
+ final results = await db.getAll('SELECT * FROM $table');
+ return results.map((row) => Attachment.fromRow(row)).toList();
+ }
+
+ Future> getActiveAttachments() async {
+ // Return all attachments that are not archived (i.e., state != AttachmentState.archived)
+ final results = await db.getAll('SELECT * FROM $table WHERE state != ?', [
+ AttachmentState.archived.index,
+ ]);
+ return results.map((row) => Attachment.fromRow(row)).toList();
+ }
+
+ Future clearQueue() async {
+ log.info('Clearing attachment queue...');
+ await db.execute('DELETE FROM $table');
+ }
+
+ Future deleteArchivedAttachments(
+ Future Function(List) callback,
+ ) async {
+ // Only delete archived attachments exceeding the maxArchivedCount, ordered by timestamp DESC
+ const limit = 1000;
+
+ final results = await db.getAll(
+ 'SELECT * FROM $table WHERE state = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?',
+ [
+ AttachmentState.archived.index,
+ limit,
+ maxArchivedCount,
+ ],
+ );
+ final archivedAttachments =
+ results.map((row) => Attachment.fromRow(row)).toList();
+
+ if (archivedAttachments.isEmpty) {
+ return false;
+ }
+
+ log.info(
+ 'Deleting ${archivedAttachments.length} archived attachments (exceeding maxArchivedCount=$maxArchivedCount)...');
+ // Call the callback with the list of archived attachments before deletion
+ await callback(archivedAttachments);
+
+ // Delete the archived attachments from the table
+ final ids = archivedAttachments.map((a) => a.id).toList();
+ if (ids.isNotEmpty) {
+ await db.executeBatch('DELETE FROM $table WHERE id = ?', [
+ for (final id in ids) [id],
+ ]);
+ }
+
+ log.info('Deleted ${archivedAttachments.length} archived attachments.');
+ return archivedAttachments.length < limit;
+ }
+
+ Future upsertAttachment(
+ Attachment attachment,
+ SqliteWriteContext context,
+ ) async {
+ log.finest('Updating attachment ${attachment.id}: ${attachment.state}');
+
+ await context.execute(
+ '''INSERT OR REPLACE INTO
+ $table (id, timestamp, filename, local_uri, media_type, size, state, has_synced, meta_data)
+ VALUES
+ (?, ?, ?, ?, ?, ?, ?, ?, ?)''',
+ [
+ attachment.id,
+ attachment.timestamp,
+ attachment.filename,
+ attachment.localUri,
+ attachment.mediaType,
+ attachment.size,
+ attachment.state.index,
+ attachment.hasSynced ? 1 : 0,
+ attachment.metaData,
+ ],
+ );
+
+ return attachment;
+ }
+}
diff --git a/packages/powersync_core/lib/src/attachments/implementations/attachment_service.dart b/packages/powersync_core/lib/src/attachments/implementations/attachment_service.dart
new file mode 100644
index 00000000..0ccafd75
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/implementations/attachment_service.dart
@@ -0,0 +1,75 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:logging/logging.dart';
+import 'package:powersync_core/powersync_core.dart';
+import 'package:sqlite_async/sqlite_async.dart';
+
+import '../attachment.dart';
+import 'attachment_context.dart';
+
+@internal
+final class AttachmentService {
+ final PowerSyncDatabase db;
+ final Logger logger;
+ final int maxArchivedCount;
+ final String attachmentsQueueTableName;
+ final Mutex _mutex = Mutex();
+
+ late final AttachmentContext _context;
+
+ AttachmentService({
+ required this.db,
+ required this.logger,
+ required this.maxArchivedCount,
+ required this.attachmentsQueueTableName,
+ }) {
+ _context = AttachmentContext(
+ db,
+ logger,
+ maxArchivedCount,
+ attachmentsQueueTableName,
+ );
+ }
+
+ Stream watchActiveAttachments({Duration? throttle}) async* {
+ logger.info('Watching attachments...');
+
+ // Watch for attachments with active states (queued for upload, download, or delete)
+ final stream = db.watch(
+ '''
+ SELECT
+ id
+ FROM
+ $attachmentsQueueTableName
+ WHERE
+ state = ?
+ OR state = ?
+ OR state = ?
+ ORDER BY
+ timestamp ASC
+ ''',
+ parameters: [
+ AttachmentState.queuedUpload.index,
+ AttachmentState.queuedDownload.index,
+ AttachmentState.queuedDelete.index,
+ ],
+ throttle: throttle ?? const Duration(milliseconds: 30),
+ );
+
+ yield* stream;
+ }
+
+ Future withContext(
+ Future Function(AttachmentContext ctx) action,
+ ) async {
+ return await _mutex.lock(() async {
+ try {
+ return await action(_context);
+ } catch (e, stackTrace) {
+ // Re-throw the error to be handled by the caller
+ Error.throwWithStackTrace(e, stackTrace);
+ }
+ });
+ }
+}
diff --git a/packages/powersync_core/lib/src/attachments/io_local_storage.dart b/packages/powersync_core/lib/src/attachments/io_local_storage.dart
new file mode 100644
index 00000000..67d1b578
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/io_local_storage.dart
@@ -0,0 +1,93 @@
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+import 'local_storage.dart';
+
+/// Implements [LocalStorage] for device filesystem using Dart IO.
+///
+/// Handles file and directory operations for attachments. The database only
+/// stores relative paths for attachments that this implementation resolves
+/// against the root path provided as a constructor argument. For that reason,
+/// it's important that the root directory stays consistent, as data may be lost
+/// otherwise.
+///
+/// {@category attachments}
+@experimental
+final class IOLocalStorage implements LocalStorage {
+ final Directory _root;
+
+ const IOLocalStorage(this._root);
+
+ File _fileFor(String filePath) => File(p.join(_root.path, filePath));
+
+ @override
+ Future saveFile(String filePath, Stream> data) async {
+ final file = _fileFor(filePath);
+ await file.parent.create(recursive: true);
+ return (await data.pipe(_LengthTrackingSink(file.openWrite()))) as int;
+ }
+
+ @override
+ Stream readFile(String filePath, {String? mediaType}) async* {
+ final file = _fileFor(filePath);
+ if (!await file.exists()) {
+ throw FileSystemException('File does not exist', filePath);
+ }
+ final source = file.openRead();
+ await for (final chunk in source) {
+ yield chunk is Uint8List ? chunk : Uint8List.fromList(chunk);
+ }
+ }
+
+ @override
+ Future deleteFile(String filePath) async {
+ final file = _fileFor(filePath);
+ if (await file.exists()) {
+ await file.delete();
+ }
+ }
+
+ @override
+ Future fileExists(String filePath) async {
+ return await _fileFor(filePath).exists();
+ }
+
+ /// Creates a directory and all necessary parent directories dynamically if they do not exist.
+ @override
+ Future initialize() async {
+ await _root.create(recursive: true);
+ }
+
+ @override
+ Future clear() async {
+ if (await _root.exists()) {
+ await _root.delete(recursive: true);
+ }
+ await _root.create(recursive: true);
+ }
+}
+
+final class _LengthTrackingSink implements StreamConsumer> {
+ final StreamConsumer> inner;
+ var bytesWritten = 0;
+
+ _LengthTrackingSink(this.inner);
+
+ @override
+ Future addStream(Stream> stream) {
+ return inner.addStream(stream.map((event) {
+ bytesWritten += event.length;
+ return event;
+ }));
+ }
+
+ @override
+ Future close() async {
+ await inner.close();
+ return bytesWritten;
+ }
+}
diff --git a/packages/powersync_core/lib/src/attachments/local_storage.dart b/packages/powersync_core/lib/src/attachments/local_storage.dart
new file mode 100644
index 00000000..c43db1ef
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/local_storage.dart
@@ -0,0 +1,102 @@
+/// @docImport 'package:powersync_core/attachments/io.dart';
+library;
+
+import 'dart:typed_data';
+
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+/// An interface responsible for storing attachment data locally.
+///
+/// This interface is only responsible for storing attachment content,
+/// essentially acting as a key-value store of virtual paths to blobs.
+///
+/// On native platforms, you can use the [IOLocalStorage] implemention. On the
+/// web, no default implementation is available at the moment.
+///
+/// {@category attachments}
+@experimental
+abstract interface class LocalStorage {
+ /// Returns an in-memory [LocalStorage] implementation, suitable for testing.
+ factory LocalStorage.inMemory() = _InMemoryStorage;
+
+ /// Saves binary data stream to storage at the specified file path
+ ///
+ /// [filePath] - Path where the file will be stored
+ /// [data] - List of binary data to store
+ /// Returns the total size of the written data in bytes
+ Future saveFile(String filePath, Stream> data);
+
+ /// Retrieves binary data stream from storage at the specified file path
+ ///
+ /// [filePath] - Path of the file to read
+ ///
+ /// Returns a stream of binary data
+ Stream readFile(String filePath);
+
+ /// Deletes a file at the specified path
+ ///
+ /// [filePath] - Path of the file to delete
+ Future deleteFile(String filePath);
+
+ /// Checks if a file exists at the specified path
+ ///
+ /// [filePath] - Path to check
+ ///
+ /// Returns true if the file exists, false otherwise
+ Future fileExists(String filePath);
+
+ /// Initializes the storage, performing any necessary setup.
+ Future initialize();
+
+ /// Clears all data from the storage.
+ Future clear();
+}
+
+final class _InMemoryStorage implements LocalStorage {
+ final Map content = {};
+
+ String _keyForPath(String path) {
+ return p.normalize(path);
+ }
+
+ @override
+ Future clear() async {
+ content.clear();
+ }
+
+ @override
+ Future deleteFile(String filePath) async {
+ content.remove(_keyForPath(filePath));
+ }
+
+ @override
+ Future fileExists(String filePath) async {
+ return content.containsKey(_keyForPath(filePath));
+ }
+
+ @override
+ Future initialize() async {}
+
+ @override
+ Stream readFile(String filePath) {
+ return switch (content[_keyForPath(filePath)]) {
+ null =>
+ Stream.error('file at $filePath does not exist in in-memory storage'),
+ final contents => Stream.value(contents),
+ };
+ }
+
+ @override
+ Future saveFile(String filePath, Stream> data) async {
+ var length = 0;
+ final builder = BytesBuilder(copy: false);
+ await for (final chunk in data) {
+ length += chunk.length;
+ builder.add(chunk);
+ }
+
+ content[_keyForPath(filePath)] = builder.takeBytes();
+ return length;
+ }
+}
diff --git a/packages/powersync_core/lib/src/attachments/remote_storage.dart b/packages/powersync_core/lib/src/attachments/remote_storage.dart
new file mode 100644
index 00000000..35818b9b
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/remote_storage.dart
@@ -0,0 +1,34 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:meta/meta.dart';
+
+import 'attachment.dart';
+
+/// An interface responsible for uploading and downloading attachments from a
+/// remote source, like e.g. S3 or Firebase cloud storage.
+///
+/// {@category attachments}
+@experimental
+abstract interface class RemoteStorage {
+ /// Uploads a file to remote storage.
+ ///
+ /// [fileData] is a stream of byte arrays representing the file data.
+ /// [attachment] is the attachment record associated with the file.
+ Future uploadFile(
+ Stream fileData,
+ Attachment attachment,
+ );
+
+ /// Downloads a file from remote storage.
+ ///
+ /// [attachment] is the attachment record associated with the file.
+ ///
+ /// Returns a stream of byte arrays representing the file data.
+ Future>> downloadFile(Attachment attachment);
+
+ /// Deletes a file from remote storage.
+ ///
+ /// [attachment] is the attachment record associated with the file.
+ Future deleteFile(Attachment attachment);
+}
diff --git a/packages/powersync_core/lib/src/attachments/sync/syncing_service.dart b/packages/powersync_core/lib/src/attachments/sync/syncing_service.dart
new file mode 100644
index 00000000..4bc1a266
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/sync/syncing_service.dart
@@ -0,0 +1,281 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:logging/logging.dart';
+import 'package:async/async.dart';
+
+import '../attachment.dart';
+import '../implementations/attachment_context.dart';
+import '../implementations/attachment_service.dart';
+import '../local_storage.dart';
+import '../remote_storage.dart';
+import '../sync_error_handler.dart';
+
+/// SyncingService is responsible for syncing attachments between local and remote storage.
+///
+/// This service handles downloading, uploading, and deleting attachments, as well as
+/// periodically syncing attachment states. It ensures proper lifecycle management
+/// of sync operations and provides mechanisms for error handling and retries.
+///
+/// Properties:
+/// - [remoteStorage]: The remote storage implementation for handling file operations.
+/// - [localStorage]: The local storage implementation for managing files locally.
+/// - [attachmentsService]: The service for managing attachment states and operations.
+/// - [errorHandler]: Optional error handler for managing sync-related errors.
+@internal
+final class SyncingService {
+ final RemoteStorage remoteStorage;
+ final LocalStorage localStorage;
+ final AttachmentService attachmentsService;
+ final AttachmentErrorHandler? errorHandler;
+ final Duration syncThrottle;
+ final Duration period;
+ final Logger logger;
+
+ StreamSubscription? _syncSubscription;
+ StreamSubscription? _periodicSubscription;
+ bool _isClosed = false;
+ final _syncTriggerController = StreamController.broadcast();
+
+ SyncingService({
+ required this.remoteStorage,
+ required this.localStorage,
+ required this.attachmentsService,
+ this.errorHandler,
+ this.syncThrottle = const Duration(seconds: 5),
+ this.period = const Duration(seconds: 30),
+ required this.logger,
+ });
+
+ /// Starts the syncing process, including periodic and event-driven sync operations.
+ Future startSync() async {
+ if (_isClosed) return;
+
+ _syncSubscription?.cancel();
+ _periodicSubscription?.cancel();
+
+ // Create a merged stream of manual triggers and attachment changes
+ final attachmentChanges = attachmentsService.watchActiveAttachments(
+ throttle: syncThrottle,
+ );
+ final manualTriggers = _syncTriggerController.stream;
+
+ late StreamSubscription sub;
+ final syncStream =
+ StreamGroup.merge([attachmentChanges, manualTriggers])
+ .takeWhile((_) => sub == _syncSubscription)
+ .asyncMap((_) async {
+ await attachmentsService.withContext((context) async {
+ final attachments = await context.getActiveAttachments();
+ logger.info('Found ${attachments.length} active attachments');
+ await handleSync(attachments, context);
+ await deleteArchivedAttachments(context);
+ });
+ });
+
+ _syncSubscription = sub = syncStream.listen(null);
+
+ // Start periodic sync using instance period
+ _periodicSubscription = Stream.periodic(period, (_) {}).listen((
+ _,
+ ) {
+ logger.info('Periodically syncing attachments');
+ triggerSync();
+ });
+ }
+
+ /// Enqueues a sync operation (manual trigger).
+ void triggerSync() {
+ if (!_isClosed) _syncTriggerController.add(null);
+ }
+
+ /// Stops all ongoing sync operations.
+ Future stopSync() async {
+ await _periodicSubscription?.cancel();
+
+ final subscription = _syncSubscription;
+ // Add a trigger event after clearing the subscription, which will make
+ // the takeWhile() callback cancel. This allows us to use asFuture() here,
+ // ensuring that we only complete this future when the stream is actually
+ // done.
+ _syncSubscription = null;
+ _syncTriggerController.add(null);
+ await subscription?.asFuture();
+ }
+
+ /// Closes the syncing service, stopping all operations and releasing resources.
+ Future close() async {
+ _isClosed = true;
+ await stopSync();
+ await _syncTriggerController.close();
+ }
+
+ /// Handles syncing operations for a list of attachments, including downloading,
+ /// uploading, and deleting files based on their states.
+ ///
+ /// [attachments]: The list of attachments to process.
+ /// [context]: The attachment context used for managing attachment states.
+ Future handleSync(
+ List attachments,
+ AttachmentContext context,
+ ) async {
+ logger.info('Starting handleSync with ${attachments.length} attachments');
+ final updatedAttachments = [];
+
+ for (final attachment in attachments) {
+ logger.info(
+ 'Processing attachment ${attachment.id} with state: ${attachment.state}',
+ );
+ try {
+ switch (attachment.state) {
+ case AttachmentState.queuedDownload:
+ logger.info('Downloading [${attachment.filename}]');
+ updatedAttachments.add(await downloadAttachment(attachment));
+ break;
+ case AttachmentState.queuedUpload:
+ logger.info('Uploading [${attachment.filename}]');
+ updatedAttachments.add(await uploadAttachment(attachment));
+ break;
+ case AttachmentState.queuedDelete:
+ logger.info('Deleting [${attachment.filename}]');
+ updatedAttachments.add(await deleteAttachment(attachment, context));
+ break;
+ case AttachmentState.synced:
+ logger.info('Attachment ${attachment.id} is already synced');
+ break;
+ case AttachmentState.archived:
+ logger.info('Attachment ${attachment.id} is archived');
+ break;
+ }
+ } catch (e, st) {
+ logger.warning('Error during sync for ${attachment.id}', e, st);
+ }
+ }
+
+ if (updatedAttachments.isNotEmpty) {
+ logger.info('Saving ${updatedAttachments.length} updated attachments');
+ await context.saveAttachments(updatedAttachments);
+ }
+ }
+
+ /// Uploads an attachment from local storage to remote storage.
+ ///
+ /// [attachment]: The attachment to upload.
+ /// Returns the updated attachment with its new state.
+ Future uploadAttachment(Attachment attachment) async {
+ logger.info('Starting upload for attachment ${attachment.id}');
+ try {
+ if (attachment.localUri == null) {
+ throw Exception('No localUri for attachment $attachment');
+ }
+ await remoteStorage.uploadFile(
+ localStorage.readFile(attachment.localUri!),
+ attachment,
+ );
+ logger.info(
+ 'Successfully uploaded attachment "${attachment.id}" to Cloud Storage',
+ );
+ return attachment.copyWith(
+ state: AttachmentState.synced,
+ hasSynced: true,
+ );
+ } catch (e, st) {
+ logger.warning(
+ 'Upload attachment error for attachment $attachment',
+ e,
+ st,
+ );
+ if (errorHandler != null) {
+ final shouldRetry =
+ await errorHandler!.onUploadError(attachment, e, st);
+ if (!shouldRetry) {
+ logger.info('Attachment with ID ${attachment.id} has been archived');
+ return attachment.copyWith(state: AttachmentState.archived);
+ }
+ }
+ return attachment;
+ }
+ }
+
+ /// Downloads an attachment from remote storage and saves it to local storage.
+ ///
+ /// [attachment]: The attachment to download.
+ /// Returns the updated attachment with its new state.
+ Future downloadAttachment(Attachment attachment) async {
+ logger.info('Starting download for attachment ${attachment.id}');
+ final attachmentPath = attachment.filename;
+ try {
+ final fileStream = await remoteStorage.downloadFile(attachment);
+ await localStorage.saveFile(attachmentPath, fileStream);
+ logger.info('Successfully downloaded file "${attachment.id}"');
+
+ return attachment.copyWith(
+ localUri: attachmentPath,
+ state: AttachmentState.synced,
+ hasSynced: true,
+ );
+ } catch (e, st) {
+ if (errorHandler != null) {
+ final shouldRetry =
+ await errorHandler!.onDownloadError(attachment, e, st);
+ if (!shouldRetry) {
+ logger.info('Attachment with ID ${attachment.id} has been archived');
+ return attachment.copyWith(state: AttachmentState.archived);
+ }
+ }
+ logger.warning(
+ 'Download attachment error for attachment $attachment',
+ e,
+ st,
+ );
+ return attachment;
+ }
+ }
+
+ /// Deletes an attachment from remote and local storage, and removes it from the queue.
+ ///
+ /// [attachment]: The attachment to delete.
+ /// Returns the updated attachment with its new state.
+ Future deleteAttachment(
+ Attachment attachment, AttachmentContext context) async {
+ try {
+ logger.info('Deleting attachment ${attachment.id} from remote storage');
+ await remoteStorage.deleteFile(attachment);
+
+ if (attachment.localUri != null &&
+ await localStorage.fileExists(attachment.localUri!)) {
+ await localStorage.deleteFile(attachment.localUri!);
+ }
+ // Remove the attachment record from the queue in a transaction.
+ await context.deleteAttachment(attachment.id);
+ return attachment.copyWith(state: AttachmentState.archived);
+ } catch (e, st) {
+ if (errorHandler != null) {
+ final shouldRetry =
+ await errorHandler!.onDeleteError(attachment, e, st);
+ if (!shouldRetry) {
+ logger.info('Attachment with ID ${attachment.id} has been archived');
+ return attachment.copyWith(state: AttachmentState.archived);
+ }
+ }
+ logger.warning('Error deleting attachment: $e', e, st);
+ return attachment;
+ }
+ }
+
+ /// Deletes archived attachments from local storage.
+ ///
+ /// [context]: The attachment context used to retrieve and manage archived attachments.
+ /// Returns `true` if all archived attachments were successfully deleted, `false` otherwise.
+ Future deleteArchivedAttachments(
+ AttachmentContext context,
+ ) async {
+ return context.deleteArchivedAttachments((pendingDelete) async {
+ for (final attachment in pendingDelete) {
+ if (attachment.localUri == null) continue;
+ if (!await localStorage.fileExists(attachment.localUri!)) continue;
+ await localStorage.deleteFile(attachment.localUri!);
+ }
+ });
+ }
+}
diff --git a/packages/powersync_core/lib/src/attachments/sync_error_handler.dart b/packages/powersync_core/lib/src/attachments/sync_error_handler.dart
new file mode 100644
index 00000000..30aafcf4
--- /dev/null
+++ b/packages/powersync_core/lib/src/attachments/sync_error_handler.dart
@@ -0,0 +1,102 @@
+import 'package:meta/meta.dart';
+
+import 'attachment.dart';
+
+/// The signature of a function handling an exception when uploading,
+/// downloading or deleting an exception.
+///
+/// It returns `true` if the operation should be retried.
+///
+/// {@category attachments}
+typedef AttachmentExceptionHandler = Future Function(
+ Attachment attachment,
+ Object exception,
+ StackTrace stackTrace,
+);
+
+/// Interface for handling errors during attachment operations.
+/// Implementations determine whether failed operations should be retried.
+/// Attachment records are archived if an operation fails and should not be retried.
+///
+/// {@category attachments}
+@experimental
+abstract interface class AttachmentErrorHandler {
+ /// Creates an implementation of an error handler by delegating to the
+ /// individual functions for delete, download and upload errors.
+ const factory AttachmentErrorHandler({
+ required AttachmentExceptionHandler onDeleteError,
+ required AttachmentExceptionHandler onDownloadError,
+ required AttachmentExceptionHandler onUploadError,
+ }) = _FunctionBasedErrorHandler;
+
+ /// Determines whether the provided attachment download operation should be retried.
+ ///
+ /// [attachment] The attachment involved in the failed download operation.
+ /// [exception] The exception that caused the download failure.
+ /// [stackTrace] The [StackTrace] when the exception was caught.
+ ///
+ /// Returns `true` if the download operation should be retried, `false` otherwise.
+ Future onDownloadError(
+ Attachment attachment,
+ Object exception,
+ StackTrace stackTrace,
+ );
+
+ /// Determines whether the provided attachment upload operation should be retried.
+ ///
+ /// [attachment] The attachment involved in the failed upload operation.
+ /// [exception] The exception that caused the upload failure.
+ /// [stackTrace] The [StackTrace] when the exception was caught.
+ ///
+ /// Returns `true` if the upload operation should be retried, `false` otherwise.
+ Future onUploadError(
+ Attachment attachment,
+ Object exception,
+ StackTrace stackTrace,
+ );
+
+ /// Determines whether the provided attachment delete operation should be retried.
+ ///
+ /// [attachment] The attachment involved in the failed delete operation.
+ /// [exception] The exception that caused the delete failure.
+ /// [stackTrace] The [StackTrace] when the exception was caught.
+ ///
+ /// Returns `true` if the delete operation should be retried, `false` otherwise.
+ Future onDeleteError(
+ Attachment attachment,
+ Object exception,
+ StackTrace stackTrace,
+ );
+}
+
+final class _FunctionBasedErrorHandler implements AttachmentErrorHandler {
+ final AttachmentExceptionHandler _onDeleteError;
+ final AttachmentExceptionHandler _onDownloadError;
+ final AttachmentExceptionHandler _onUploadError;
+
+ const _FunctionBasedErrorHandler(
+ {required AttachmentExceptionHandler onDeleteError,
+ required AttachmentExceptionHandler onDownloadError,
+ required AttachmentExceptionHandler onUploadError})
+ : _onDeleteError = onDeleteError,
+ _onDownloadError = onDownloadError,
+ _onUploadError = onUploadError;
+
+ @override
+ Future onDeleteError(
+ Attachment attachment, Object exception, StackTrace stackTrace) {
+ return _onDeleteError(attachment, exception, stackTrace);
+ }
+
+ @override
+ Future onDownloadError(
+ Attachment attachment, Object exception, StackTrace stackTrace) {
+ return _onDownloadError(attachment, exception, stackTrace);
+ }
+
+ @override
+ Future onUploadError(
+ Attachment attachment, Object exception, StackTrace stackTrace) {
+ return _onUploadError(attachment, exception, stackTrace);
+ }
+}
diff --git a/packages/powersync_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart
index f0156078..1a3ca3da 100644
--- a/packages/powersync_core/lib/src/database/core_version.dart
+++ b/packages/powersync_core/lib/src/database/core_version.dart
@@ -57,10 +57,13 @@ extension type const PowerSyncCoreVersion((int, int, int) _tuple) {
/// The minimum version of the sqlite core extensions we support. We check
/// this version when opening databases to fail early and with an actionable
/// error message.
- // Note: When updating this, also update the download URL in
- // scripts/init_powersync_core_binary.dart and the version ref in
- // packages/sqlite3_wasm_build/build.sh
- static const minimum = PowerSyncCoreVersion((0, 4, 2));
+ // Note: When updating this, also update:
+ //
+ // - scripts/init_powersync_core_binary.dart
+ // - scripts/download_core_binary_demos.dart
+ // - packages/sqlite3_wasm_build/build.sh
+ // - Android and Darwin (CocoaPods and SwiftPM) in powersync_flutter_libs
+ static const minimum = PowerSyncCoreVersion((0, 4, 6));
/// The first version of the core extensions that this version of the Dart
/// SDK doesn't support.
diff --git a/packages/powersync_core/lib/src/database/native/native_powersync_database.dart b/packages/powersync_core/lib/src/database/native/native_powersync_database.dart
index d55b69db..40489365 100644
--- a/packages/powersync_core/lib/src/database/native/native_powersync_database.dart
+++ b/packages/powersync_core/lib/src/database/native/native_powersync_database.dart
@@ -133,6 +133,8 @@ class PowerSyncDatabaseImpl
Future connectInternal({
required PowerSyncBackendConnector connector,
required ResolvedSyncOptions options,
+ required List initiallyActiveStreams,
+ required Stream> activeStreams,
required AbortController abort,
required Zone asyncWorkZone,
}) async {
@@ -140,6 +142,7 @@ class PowerSyncDatabaseImpl
bool triedSpawningIsolate = false;
StreamSubscription? crudUpdateSubscription;
+ StreamSubscription? activeStreamsSubscription;
final receiveMessages = ReceivePort();
final receiveUnhandledErrors = ReceivePort();
final receiveExit = ReceivePort();
@@ -157,6 +160,7 @@ class PowerSyncDatabaseImpl
// Cleanup
crudUpdateSubscription?.cancel();
+ activeStreamsSubscription?.cancel();
receiveMessages.close();
receiveUnhandledErrors.close();
receiveExit.close();
@@ -198,6 +202,10 @@ class PowerSyncDatabaseImpl
crudUpdateSubscription = crudStream.listen((event) {
port.send(['update']);
});
+
+ activeStreamsSubscription = activeStreams.listen((streams) {
+ port.send(['changed_subscriptions', streams]);
+ });
} else if (action == 'uploadCrud') {
await (data[1] as PortCompleter).handle(() async {
await connector.uploadData(this);
@@ -366,6 +374,9 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async {
}
} else if (action == 'close') {
await shutdown();
+ } else if (action == 'changed_subscriptions') {
+ openedStreamingSync
+ ?.updateSubscriptions(message[1] as List);
}
}
});
@@ -438,7 +449,7 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async {
}
}
- localUpdatesSubscription = db!.updates.listen((event) {
+ localUpdatesSubscription = db!.updatesSync.listen((event) {
updatedTables.add(event.tableName);
updateDebouncer ??=
diff --git a/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart b/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart
index a4f0b419..ae891cb7 100644
--- a/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart
+++ b/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart
@@ -7,6 +7,7 @@ import 'package:powersync_core/src/abort_controller.dart';
import 'package:powersync_core/src/database/powersync_db_mixin.dart';
import 'package:powersync_core/src/open_factory/abstract_powersync_open_factory.dart';
import '../sync/options.dart';
+import '../sync/streaming_sync.dart';
import 'powersync_database.dart';
import '../connector.dart';
@@ -115,6 +116,8 @@ class PowerSyncDatabaseImpl
Future connectInternal({
required PowerSyncBackendConnector connector,
required AbortController abort,
+ required List initiallyActiveStreams,
+ required Stream> activeStreams,
required Zone asyncWorkZone,
required ResolvedSyncOptions options,
}) {
diff --git a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart
index dc4b2ddb..fd722a2a 100644
--- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart
+++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart
@@ -1,5 +1,6 @@
import 'dart:async';
+import 'package:async/async.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:powersync_core/sqlite3_common.dart';
@@ -13,9 +14,13 @@ import 'package:powersync_core/src/powersync_update_notification.dart';
import 'package:powersync_core/src/schema.dart';
import 'package:powersync_core/src/schema_logic.dart';
import 'package:powersync_core/src/schema_logic.dart' as schema_logic;
+import 'package:powersync_core/src/sync/connection_manager.dart';
import 'package:powersync_core/src/sync/options.dart';
import 'package:powersync_core/src/sync/sync_status.dart';
+import '../sync/stream.dart';
+import '../sync/streaming_sync.dart';
+
mixin PowerSyncDatabaseMixin implements SqliteConnection {
/// Schema used for the local database.
Schema get schema;
@@ -41,16 +46,13 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
@Deprecated("This field is unused, pass params to connect() instead")
Map? clientParams;
+ late final ConnectionManager _connections;
+
/// Current connection status.
- SyncStatus currentStatus =
- const SyncStatus(connected: false, lastSyncedAt: null);
+ SyncStatus get currentStatus => _connections.currentStatus;
/// Use this stream to subscribe to connection status updates.
- late final Stream statusStream;
-
- @protected
- StreamController statusStreamController =
- StreamController.broadcast();
+ Stream get statusStream => _connections.statusStream;
late final ActiveDatabaseGroup _activeGroup;
@@ -80,15 +82,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
@protected
Future get isInitialized;
- /// The abort controller for the current sync iteration.
- ///
- /// null when disconnected, present when connecting or connected.
- ///
- /// The controller must only be accessed from within a critical section of the
- /// sync mutex.
- @protected
- AbortController? _abortActiveSync;
-
@protected
Future baseInit() async {
String identifier = 'memory';
@@ -106,15 +99,14 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
'instantiation logic if this is not intentional',
);
}
-
- statusStream = statusStreamController.stream;
+ _connections = ConnectionManager(this);
updates = powerSyncUpdateNotifications(database.updates);
await database.initialize();
await _checkVersion();
await database.execute('SELECT powersync_init()');
await updateSchema(schema);
- await _updateHasSynced();
+ await _connections.resolveOfflineSyncStatus();
}
/// Check that a supported version of the powersync extension is loaded.
@@ -140,55 +132,15 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
return isInitialized;
}
- Future _updateHasSynced() async {
- // Query the database to see if any data has been synced.
- final result = await database.getAll(
- 'SELECT priority, last_synced_at FROM ps_sync_state ORDER BY priority;',
- );
- const prioritySentinel = 2147483647;
- var hasSynced = false;
- DateTime? lastCompleteSync;
- final priorityStatusEntries = [];
-
- DateTime parseDateTime(String sql) {
- return DateTime.parse('${sql}Z').toLocal();
- }
-
- for (final row in result) {
- final priority = row.columnAt(0) as int;
- final lastSyncedAt = parseDateTime(row.columnAt(1) as String);
-
- if (priority == prioritySentinel) {
- hasSynced = true;
- lastCompleteSync = lastSyncedAt;
- } else {
- priorityStatusEntries.add((
- hasSynced: true,
- lastSyncedAt: lastSyncedAt,
- priority: BucketPriority(priority)
- ));
- }
- }
-
- if (hasSynced != currentStatus.hasSynced) {
- final status = SyncStatus(
- hasSynced: hasSynced,
- lastSyncedAt: lastCompleteSync,
- priorityStatusEntries: priorityStatusEntries,
- );
- setStatus(status);
- }
- }
-
/// Returns a [Future] which will resolve once at least one full sync cycle
/// has completed (meaninng that the first consistent checkpoint has been
/// reached across all buckets).
///
/// When [priority] is null (the default), this method waits for the first
- /// full sync checkpoint to complete. When set to a [BucketPriority] however,
+ /// full sync checkpoint to complete. When set to a [StreamPriority] however,
/// it completes once all buckets within that priority (as well as those in
/// higher priorities) have been synchronized at least once.
- Future waitForFirstSync({BucketPriority? priority}) async {
+ Future waitForFirstSync({StreamPriority? priority}) async {
bool matches(SyncStatus status) {
if (priority == null) {
return status.hasSynced == true;
@@ -197,46 +149,13 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
}
}
- if (matches(currentStatus)) {
- return;
- }
- await for (final result in statusStream) {
- if (matches(result)) {
- break;
- }
- }
+ return _connections.firstStatusMatching(matches);
}
@protected
@visibleForTesting
void setStatus(SyncStatus status) {
- if (status != currentStatus) {
- final newStatus = SyncStatus(
- connected: status.connected,
- downloading: status.downloading,
- uploading: status.uploading,
- connecting: status.connecting,
- uploadError: status.uploadError,
- downloadError: status.downloadError,
- priorityStatusEntries: status.priorityStatusEntries,
- downloadProgress: status.downloadProgress,
- // Note that currently the streaming sync implementation will never set
- // hasSynced. lastSyncedAt implies that syncing has completed at some
- // point (hasSynced = true).
- // The previous values of hasSynced should be preserved here.
- lastSyncedAt: status.lastSyncedAt ?? currentStatus.lastSyncedAt,
- hasSynced: status.lastSyncedAt != null
- ? true
- : status.hasSynced ?? currentStatus.hasSynced,
- );
-
- // If the absence of hasSynced was the only difference, the new states
- // would be equal and don't require an event. So, check again.
- if (newStatus != currentStatus) {
- currentStatus = newStatus;
- statusStreamController.add(currentStatus);
- }
- }
+ _connections.manuallyChangeSyncStatus(status);
}
@override
@@ -261,9 +180,9 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
// Now we can close the database
await database.close();
- // If there are paused subscriptionso n the status stream, don't delay
+ // If there are paused subscriptions on the status stream, don't delay
// closing the database because of that.
- unawaited(statusStreamController.close());
+ _connections.close();
await _activeGroup.close();
}
}
@@ -297,67 +216,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
params: params,
);
- if (schema.rawTables.isNotEmpty &&
- resolvedOptions.source.syncImplementation !=
- SyncClientImplementation.rust) {
- throw UnsupportedError(
- 'Raw tables are only supported by the Rust client.');
- }
-
- // ignore: deprecated_member_use_from_same_package
- clientParams = params;
- var thisConnectAborter = AbortController();
- final zone = Zone.current;
-
- late void Function() retryHandler;
-
- Future connectWithSyncLock() async {
- // Ensure there has not been a subsequent connect() call installing a new
- // sync client.
- assert(identical(_abortActiveSync, thisConnectAborter));
- assert(!thisConnectAborter.aborted);
-
- await connectInternal(
- connector: connector,
- options: resolvedOptions,
- abort: thisConnectAborter,
- // Run follow-up async tasks in the parent zone, a new one is introduced
- // while we hold the lock (and async tasks won't hold the sync lock).
- asyncWorkZone: zone,
- );
-
- thisConnectAborter.onCompletion.whenComplete(retryHandler);
- }
-
- // If the sync encounters a failure without being aborted, retry
- retryHandler = Zone.current.bindCallback(() async {
- _activeGroup.syncConnectMutex.lock(() async {
- // Is this still supposed to be active? (abort is only called within
- // mutex)
- if (!thisConnectAborter.aborted) {
- // We only change _abortActiveSync after disconnecting, which resets
- // the abort controller.
- assert(identical(_abortActiveSync, thisConnectAborter));
-
- // We need a new abort controller for this attempt
- _abortActiveSync = thisConnectAborter = AbortController();
-
- logger.warning('Sync client failed, retrying...');
- await connectWithSyncLock();
- }
- });
- });
-
- await _activeGroup.syncConnectMutex.lock(() async {
- // Disconnect a previous sync client, if one is active.
- await _abortCurrentSync();
- assert(_abortActiveSync == null);
-
- // Install the abort controller for this particular connect call, allowing
- // it to be disconnected.
- _abortActiveSync = thisConnectAborter;
- await connectWithSyncLock();
- });
+ await _connections.connect(connector: connector, options: resolvedOptions);
}
/// Internal method to establish a sync client connection.
@@ -371,6 +230,8 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
Future connectInternal({
required PowerSyncBackendConnector connector,
required ResolvedSyncOptions options,
+ required List initiallyActiveStreams,
+ required Stream> activeStreams,
required AbortController abort,
required Zone asyncWorkZone,
});
@@ -379,27 +240,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
///
/// Use [connect] to connect again.
Future disconnect() async {
- // Also wrap this in the sync mutex to ensure there's no race between us
- // connecting and disconnecting.
- await _activeGroup.syncConnectMutex.lock(_abortCurrentSync);
-
- setStatus(
- SyncStatus(connected: false, lastSyncedAt: currentStatus.lastSyncedAt));
- }
-
- Future _abortCurrentSync() async {
- if (_abortActiveSync case final disconnector?) {
- /// Checking `disconnecter.aborted` prevents race conditions
- /// where multiple calls to `disconnect` can attempt to abort
- /// the controller more than once before it has finished aborting.
- if (disconnector.aborted == false) {
- await disconnector.abort();
- _abortActiveSync = null;
- } else {
- /// Wait for the abort to complete. Continue updating the sync status after completed
- await disconnector.onCompletion;
- }
- }
+ await _connections.disconnect();
}
/// Disconnect and clear the database.
@@ -417,8 +258,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
await tx.execute('select powersync_clear(?)', [clearLocal ? 1 : 0]);
});
// The data has been deleted - reset these
- currentStatus = SyncStatus(lastSyncedAt: null, hasSynced: false);
- statusStreamController.add(currentStatus);
+ setStatus(SyncStatus(lastSyncedAt: null, hasSynced: false));
}
@Deprecated('Use [disconnectAndClear] instead.')
@@ -440,9 +280,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
schema.validate();
await _activeGroup.syncConnectMutex.lock(() async {
- if (_abortActiveSync != null) {
- throw AssertionError('Cannot update schema while connected');
- }
+ _connections.checkNotConnected();
this.schema = schema;
await database.writeLock((tx) => schema_logic.updateSchema(tx, schema));
@@ -508,23 +346,10 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
}
final last = all[all.length - 1];
return CrudBatch(
- crud: all,
- haveMore: haveMore,
- complete: ({String? writeCheckpoint}) async {
- await writeTransaction((db) async {
- await db
- .execute('DELETE FROM ps_crud WHERE id <= ?', [last.clientId]);
- if (writeCheckpoint != null &&
- await db.getOptional('SELECT 1 FROM ps_crud LIMIT 1') == null) {
- await db.execute(
- 'UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name=\'\$local\'',
- [writeCheckpoint]);
- } else {
- await db.execute(
- 'UPDATE ps_buckets SET target_op = $maxOpId WHERE name=\'\$local\'');
- }
- });
- });
+ crud: all,
+ haveMore: haveMore,
+ complete: _crudCompletionCallback(last.clientId),
+ );
}
/// Get the next recorded transaction to upload.
@@ -538,46 +363,95 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
///
/// Unlike [getCrudBatch], this only returns data from a single transaction at a time.
/// All data for the transaction is loaded into memory.
- Future getNextCrudTransaction() async {
- return await readTransaction((tx) async {
- final first = await tx.getOptional(
- 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1');
- if (first == null) {
- return null;
- }
- final txId = first['tx_id'] as int?;
- List all;
- if (txId == null) {
- all = [CrudEntry.fromRow(first)];
- } else {
- final rows = await tx.getAll(
- 'SELECT id, tx_id, data FROM ps_crud WHERE tx_id = ? ORDER BY id ASC',
- [txId]);
- all = [for (var row in rows) CrudEntry.fromRow(row)];
+ Future getNextCrudTransaction() {
+ return getCrudTransactions().firstOrNull;
+ }
+
+ /// Returns a stream of completed transactions with local writes against the
+ /// database.
+ ///
+ /// This is typically used from the [PowerSyncBackendConnector.uploadData]
+ /// method. Each entry emitted by the stream is a full transaction containing
+ /// all local writes made while that transaction was active.
+ ///
+ /// Unlike [getNextCrudTransaction], which always returns the oldest
+ /// transaction that hasn't been [CrudTransaction.complete]d yet, this stream
+ /// can be used to receive multiple transactions. Calling
+ /// [CrudTransaction.complete] will mark that transaction and all prior
+ /// transactions emitted by the stream as completed.
+ ///
+ /// This can be used to upload multiple transactions in a single batch, e.g.
+ /// with:
+ ///
+ /// ```dart
+ /// CrudTransaction? lastTransaction;
+ /// final batch = [];
+ ///
+ /// await for (final transaction in powersync.nextCrudTransactions()) {
+ /// batch.addAll(transaction.crud);
+ /// lastTransaction = transaction;
+ ///
+ /// if (batch.length > 100) {
+ /// break;
+ /// }
+ /// }
+ ///
+ /// if (batch.isNotEmpty) {
+ /// await uploadBatch(batch);
+ /// lastTransaction!.complete();
+ /// }
+ /// ```
+ ///
+ /// If there is no local data to upload, the stream emits a single `onDone`
+ /// event.
+ Stream getCrudTransactions() async* {
+ var lastCrudItemId = -1;
+ const sql = '''
+WITH RECURSIVE crud_entries AS (
+ SELECT id, tx_id, data FROM ps_crud WHERE id = (SELECT min(id) FROM ps_crud WHERE id > ?)
+ UNION ALL
+ SELECT ps_crud.id, ps_crud.tx_id, ps_crud.data FROM ps_crud
+ INNER JOIN crud_entries ON crud_entries.id + 1 = rowid
+ WHERE crud_entries.tx_id = ps_crud.tx_id
+)
+SELECT * FROM crud_entries;
+''';
+
+ while (true) {
+ final nextTransaction = await getAll(sql, [lastCrudItemId]);
+ if (nextTransaction.isEmpty) {
+ break;
}
- final last = all[all.length - 1];
-
- return CrudTransaction(
- transactionId: txId,
- crud: all,
- complete: ({String? writeCheckpoint}) async {
- await writeTransaction((db) async {
- await db.execute(
- 'DELETE FROM ps_crud WHERE id <= ?', [last.clientId]);
- if (writeCheckpoint != null &&
- await db.getOptional('SELECT 1 FROM ps_crud LIMIT 1') ==
- null) {
- await db.execute(
- 'UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name=\'\$local\'',
- [writeCheckpoint]);
- } else {
- await db.execute(
- 'UPDATE ps_buckets SET target_op = $maxOpId WHERE name=\'\$local\'');
- }
- });
- });
- });
+ final items = [for (var row in nextTransaction) CrudEntry.fromRow(row)];
+ final last = items.last;
+ final txId = last.transactionId;
+
+ yield CrudTransaction(
+ crud: items,
+ complete: _crudCompletionCallback(last.clientId),
+ transactionId: txId,
+ );
+ lastCrudItemId = last.clientId;
+ }
+ }
+
+ Future Function({String? writeCheckpoint}) _crudCompletionCallback(
+ int lastClientId) {
+ return ({String? writeCheckpoint}) async {
+ await writeTransaction((db) async {
+ await db.execute('DELETE FROM ps_crud WHERE id <= ?', [lastClientId]);
+ if (writeCheckpoint != null &&
+ await db.getOptional('SELECT 1 FROM ps_crud LIMIT 1') == null) {
+ await db.execute(
+ 'UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name=\'\$local\'',
+ [writeCheckpoint]);
+ } else {
+ await db.execute(
+ 'UPDATE ps_buckets SET target_op = $maxOpId WHERE name=\'\$local\'');
+ }
+ });
+ };
}
/// Takes a read lock, without starting a transaction.
@@ -629,6 +503,13 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
Future refreshSchema() async {
await database.refreshSchema();
}
+
+ /// Create a [SyncStream] instance for the given [name] and [parameters].
+ ///
+ /// Use [SyncStream.subscribe] to subscribe to the returned stream.
+ SyncStream syncStream(String name, [Map? parameters]) {
+ return _connections.syncStream(name, parameters);
+ }
}
Stream powerSyncUpdateNotifications(
diff --git a/packages/powersync_core/lib/src/database/web/web_powersync_database.dart b/packages/powersync_core/lib/src/database/web/web_powersync_database.dart
index 4af2821e..15a83c7d 100644
--- a/packages/powersync_core/lib/src/database/web/web_powersync_database.dart
+++ b/packages/powersync_core/lib/src/database/web/web_powersync_database.dart
@@ -128,6 +128,8 @@ class PowerSyncDatabaseImpl
Future connectInternal({
required PowerSyncBackendConnector connector,
required AbortController abort,
+ required List initiallyActiveStreams,
+ required Stream> activeStreams,
required Zone asyncWorkZone,
required ResolvedSyncOptions options,
}) async {
@@ -141,6 +143,7 @@ class PowerSyncDatabaseImpl
connector: connector,
options: options.source,
workerUri: Uri.base.resolve('/powersync_sync.worker.js'),
+ subscriptions: initiallyActiveStreams,
);
} catch (e) {
logger.warning(
@@ -157,6 +160,7 @@ class PowerSyncDatabaseImpl
crudUpdateTriggerStream: crudStream,
options: options,
client: BrowserClient(),
+ activeSubscriptions: initiallyActiveStreams,
// Only allows 1 sync implementation to run at a time per database
// This should be global (across tabs) when using Navigator locks.
identifier: database.openFactory.path,
@@ -168,7 +172,10 @@ class PowerSyncDatabaseImpl
});
sync.streamingSync();
+ final subscriptions = activeStreams.listen(sync.updateSubscriptions);
+
abort.onAbort.then((_) async {
+ subscriptions.cancel();
await sync.abort();
abort.completeAbort();
}).ignore();
diff --git a/packages/powersync_core/lib/src/exceptions.dart b/packages/powersync_core/lib/src/exceptions.dart
index e4f7c864..bc35df4a 100644
--- a/packages/powersync_core/lib/src/exceptions.dart
+++ b/packages/powersync_core/lib/src/exceptions.dart
@@ -61,7 +61,12 @@ class SyncResponseException implements Exception {
static SyncResponseException _fromResponseBody(
http.BaseResponse response, String body) {
final decoded = convert.jsonDecode(body);
- final details = _stringOrFirst(decoded['error']?['details']) ?? body;
+ final details = switch (decoded['error']) {
+ final Map details => _errorDescription(details),
+ _ => null,
+ } ??
+ body;
+
final message = '${response.reasonPhrase ?? "Request failed"}: $details';
return SyncResponseException(response.statusCode, message);
}
@@ -73,6 +78,37 @@ class SyncResponseException implements Exception {
);
}
+ /// Extracts an error description from an error resonse looking like
+ /// `{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError"}`.
+ static String? _errorDescription(Map raw) {
+ final code = raw['code']; // Required, string
+ final description = raw['description']; // Required, string
+
+ final name = raw['name']; // Optional, string
+ final details = raw['details']; // Optional, string
+
+ if (code is! String || description is! String) {
+ return null;
+ }
+
+ final fullDescription = StringBuffer(code);
+ if (name is String) {
+ fullDescription.write('($name)');
+ }
+
+ fullDescription
+ ..write(': ')
+ ..write(description);
+
+ if (details is String) {
+ fullDescription
+ ..write(', ')
+ ..write(details);
+ }
+
+ return fullDescription.toString();
+ }
+
int statusCode;
String description;
@@ -84,18 +120,6 @@ class SyncResponseException implements Exception {
}
}
-String? _stringOrFirst(Object? details) {
- if (details == null) {
- return null;
- } else if (details is String) {
- return details;
- } else if (details case [final String first, ...]) {
- return first;
- } else {
- return null;
- }
-}
-
class PowersyncNotReadyException implements Exception {
/// @nodoc
PowersyncNotReadyException(this.message);
diff --git a/packages/powersync_core/lib/src/open_factory/abstract_powersync_open_factory.dart b/packages/powersync_core/lib/src/open_factory/abstract_powersync_open_factory.dart
index d5666fad..cb88af22 100644
--- a/packages/powersync_core/lib/src/open_factory/abstract_powersync_open_factory.dart
+++ b/packages/powersync_core/lib/src/open_factory/abstract_powersync_open_factory.dart
@@ -1,7 +1,7 @@
import 'dart:async';
-import 'package:universal_io/io.dart';
import 'dart:math';
+import 'package:meta/meta.dart';
import 'package:powersync_core/sqlite_async.dart';
import 'package:powersync_core/src/open_factory/common_db_functions.dart';
import 'package:sqlite_async/sqlite3_common.dart';
@@ -70,6 +70,11 @@ abstract class AbstractPowerSyncOpenFactory extends DefaultSqliteOpenFactory {
/// Returns the library name for the current platform.
/// [path] is optional and is used when the library is not in the default location.
String getLibraryForPlatform({String? path});
+
+ /// On native platforms, synchronously pauses the current isolate for the
+ /// given [Duration].
+ @visibleForOverriding
+ void sleep(Duration duration) {}
}
/// Advanced: Define custom setup for each SQLite connection.
diff --git a/packages/powersync_core/lib/src/open_factory/native/native_open_factory.dart b/packages/powersync_core/lib/src/open_factory/native/native_open_factory.dart
index f564d349..775e59e8 100644
--- a/packages/powersync_core/lib/src/open_factory/native/native_open_factory.dart
+++ b/packages/powersync_core/lib/src/open_factory/native/native_open_factory.dart
@@ -1,8 +1,9 @@
+import 'dart:io';
+import 'dart:io' as io;
import 'dart:ffi';
import 'package:powersync_core/src/exceptions.dart';
import 'package:powersync_core/src/log.dart';
-import 'package:universal_io/io.dart';
import 'dart:isolate';
import 'package:powersync_core/src/open_factory/abstract_powersync_open_factory.dart';
import 'package:sqlite_async/sqlite3.dart' as sqlite;
@@ -109,4 +110,9 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory {
);
}
}
+
+ @override
+ void sleep(Duration duration) {
+ io.sleep(duration);
+ }
}
diff --git a/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart b/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart
index 6f5f156f..db099cb8 100644
--- a/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart
+++ b/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart
@@ -23,6 +23,7 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
wasmModule: Uri.parse(sqliteOptions.webSqliteOptions.wasmUri),
worker: Uri.parse(sqliteOptions.webSqliteOptions.workerUri),
controller: PowerSyncAsyncSqliteController(),
+ handleCustomRequest: handleCustomRequest,
);
}
diff --git a/packages/powersync_core/lib/src/sync/connection_manager.dart b/packages/powersync_core/lib/src/sync/connection_manager.dart
new file mode 100644
index 00000000..8a326642
--- /dev/null
+++ b/packages/powersync_core/lib/src/sync/connection_manager.dart
@@ -0,0 +1,396 @@
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:meta/meta.dart';
+import 'package:powersync_core/src/abort_controller.dart';
+import 'package:powersync_core/src/connector.dart';
+import 'package:powersync_core/src/database/active_instances.dart';
+import 'package:powersync_core/src/database/powersync_db_mixin.dart';
+import 'package:powersync_core/src/sync/options.dart';
+import 'package:powersync_core/src/sync/stream.dart';
+import 'package:powersync_core/src/sync/sync_status.dart';
+
+import 'instruction.dart';
+import 'mutable_sync_status.dart';
+import 'streaming_sync.dart';
+
+/// A (stream name, JSON parameters) pair that uniquely identifies a stream
+/// instantiation to subscribe to.
+typedef _RawStreamKey = (String, String);
+
+@internal
+final class ConnectionManager {
+ final PowerSyncDatabaseMixin db;
+ final ActiveDatabaseGroup _activeGroup;
+
+ /// All streams (with parameters) for which a subscription has been requested
+ /// explicitly.
+ final Map<_RawStreamKey, _ActiveSubscription> _locallyActiveSubscriptions =
+ {};
+
+ final StreamController _statusController =
+ StreamController.broadcast();
+
+ /// Fires when an entry is added or removed from [_locallyActiveSubscriptions]
+ /// while we're connected.
+ StreamController? _subscriptionsChanged;
+
+ SyncStatus _currentStatus =
+ const SyncStatus(connected: false, lastSyncedAt: null);
+
+ SyncStatus get currentStatus => _currentStatus;
+ Stream get statusStream => _statusController.stream;
+
+ /// The abort controller for the current sync iteration.
+ ///
+ /// null when disconnected, present when connecting or connected.
+ ///
+ /// The controller must only be accessed from within a critical section of the
+ /// sync mutex.
+ AbortController? _abortActiveSync;
+
+ ConnectionManager(this.db) : _activeGroup = db.group;
+
+ void checkNotConnected() {
+ if (_abortActiveSync != null) {
+ throw StateError('Cannot update schema while connected');
+ }
+ }
+
+ Future _abortCurrentSync() async {
+ if (_abortActiveSync case final disconnector?) {
+ /// Checking `disconnecter.aborted` prevents race conditions
+ /// where multiple calls to `disconnect` can attempt to abort
+ /// the controller more than once before it has finished aborting.
+ if (disconnector.aborted == false) {
+ await disconnector.abort();
+ _abortActiveSync = null;
+ } else {
+ /// Wait for the abort to complete. Continue updating the sync status after completed
+ await disconnector.onCompletion;
+ }
+ }
+ }
+
+ Future disconnect() async {
+ // Also wrap this in the sync mutex to ensure there's no race between us
+ // connecting and disconnecting.
+ await _activeGroup.syncConnectMutex.lock(() async {
+ await _abortCurrentSync();
+ _subscriptionsChanged?.close();
+ _subscriptionsChanged = null;
+ });
+
+ manuallyChangeSyncStatus(
+ SyncStatus(connected: false, lastSyncedAt: currentStatus.lastSyncedAt));
+ }
+
+ Future firstStatusMatching(bool Function(SyncStatus) predicate) async {
+ if (predicate(currentStatus)) {
+ return;
+ }
+ await for (final result in statusStream) {
+ if (predicate(result)) {
+ break;
+ }
+ }
+ }
+
+ List get _subscribedStreams => [
+ for (final active in _locallyActiveSubscriptions.values)
+ (name: active.name, parameters: active.encodedParameters)
+ ];
+
+ Future connect({
+ required PowerSyncBackendConnector connector,
+ required ResolvedSyncOptions options,
+ }) async {
+ if (db.schema.rawTables.isNotEmpty &&
+ options.source.syncImplementation != SyncClientImplementation.rust) {
+ throw UnsupportedError(
+ 'Raw tables are only supported by the Rust client.');
+ }
+
+ var thisConnectAborter = AbortController();
+ final zone = Zone.current;
+
+ late void Function() retryHandler;
+
+ final subscriptionsChanged = StreamController();
+
+ Future connectWithSyncLock() async {
+ // Ensure there has not been a subsequent connect() call installing a new
+ // sync client.
+ assert(identical(_abortActiveSync, thisConnectAborter));
+ assert(!thisConnectAborter.aborted);
+
+ // ignore: invalid_use_of_protected_member
+ await db.connectInternal(
+ connector: connector,
+ options: options,
+ abort: thisConnectAborter,
+ initiallyActiveStreams: _subscribedStreams,
+ activeStreams: subscriptionsChanged.stream.map((_) {
+ return _subscribedStreams;
+ }),
+ // Run follow-up async tasks in the parent zone, a new one is introduced
+ // while we hold the lock (and async tasks won't hold the sync lock).
+ asyncWorkZone: zone,
+ );
+
+ thisConnectAborter.onCompletion.whenComplete(retryHandler);
+ }
+
+ // If the sync encounters a failure without being aborted, retry
+ retryHandler = Zone.current.bindCallback(() async {
+ _activeGroup.syncConnectMutex.lock(() async {
+ // Is this still supposed to be active? (abort is only called within
+ // mutex)
+ if (!thisConnectAborter.aborted) {
+ // We only change _abortActiveSync after disconnecting, which resets
+ // the abort controller.
+ assert(identical(_abortActiveSync, thisConnectAborter));
+
+ // We need a new abort controller for this attempt
+ _abortActiveSync = thisConnectAborter = AbortController();
+
+ db.logger.warning('Sync client failed, retrying...');
+ await connectWithSyncLock();
+ }
+ });
+ });
+
+ await _activeGroup.syncConnectMutex.lock(() async {
+ // Disconnect a previous sync client, if one is active.
+ await _abortCurrentSync();
+ assert(_abortActiveSync == null);
+ _subscriptionsChanged = subscriptionsChanged;
+
+ // Install the abort controller for this particular connect call, allowing
+ // it to be disconnected.
+ _abortActiveSync = thisConnectAborter;
+ await connectWithSyncLock();
+ });
+ }
+
+ void manuallyChangeSyncStatus(SyncStatus status) {
+ if (status != currentStatus) {
+ final newStatus = SyncStatus(
+ connected: status.connected,
+ downloading: status.downloading,
+ uploading: status.uploading,
+ connecting: status.connecting,
+ uploadError: status.uploadError,
+ downloadError: status.downloadError,
+ priorityStatusEntries: status.priorityStatusEntries,
+ downloadProgress: status.downloadProgress,
+ // Note that currently the streaming sync implementation will never set
+ // hasSynced. lastSyncedAt implies that syncing has completed at some
+ // point (hasSynced = true).
+ // The previous values of hasSynced should be preserved here.
+ lastSyncedAt: status.lastSyncedAt ?? currentStatus.lastSyncedAt,
+ hasSynced: status.lastSyncedAt != null
+ ? true
+ : status.hasSynced ?? currentStatus.hasSynced,
+ streamSubscriptions: status.internalSubscriptions,
+ );
+
+ // If the absence of hasSynced was the only difference, the new states
+ // would be equal and don't require an event. So, check again.
+ if (newStatus != currentStatus) {
+ _currentStatus = newStatus;
+ _statusController.add(_currentStatus);
+ }
+ }
+ }
+
+ _SyncStreamSubscriptionHandle _referenceStreamSubscription(
+ String stream, Map? parameters) {
+ final key = (stream, json.encode(parameters));
+ _ActiveSubscription active;
+
+ if (_locallyActiveSubscriptions[key] case final current?) {
+ active = current;
+ } else {
+ active = _ActiveSubscription(this,
+ name: stream, parameters: parameters, encodedParameters: key.$2);
+ _locallyActiveSubscriptions[key] = active;
+ _subscriptionsChanged?.add(null);
+ }
+
+ return _SyncStreamSubscriptionHandle(active);
+ }
+
+ void _clearSubscription(_ActiveSubscription subscription) {
+ assert(subscription.refcount == 0);
+ _locallyActiveSubscriptions
+ .remove((subscription.name, subscription.encodedParameters));
+ _subscriptionsChanged?.add(null);
+ }
+
+ Future _subscriptionsCommand(Object? command) async {
+ await db.writeTransaction((tx) {
+ return tx.execute(
+ 'SELECT powersync_control(?, ?)',
+ ['subscriptions', json.encode(command)],
+ );
+ });
+ _subscriptionsChanged?.add(null);
+ }
+
+ Future subscribe({
+ required String stream,
+ required Map? parameters,
+ Duration? ttl,
+ StreamPriority? priority,
+ }) async {
+ await _subscriptionsCommand({
+ 'subscribe': {
+ 'stream': {
+ 'name': stream,
+ 'params': parameters,
+ },
+ 'ttl': ttl?.inSeconds,
+ 'priority': priority,
+ },
+ });
+
+ await _activeGroup.syncConnectMutex.lock(() async {
+ if (_abortActiveSync == null) {
+ // Since we're not connected, update the offline sync status to reflect
+ // the new subscription.
+ // With a connection, the sync client would include it in its state.
+ await resolveOfflineSyncStatus();
+ }
+ });
+ }
+
+ Future unsubscribeAll({
+ required String stream,
+ required Object? parameters,
+ }) async {
+ await _subscriptionsCommand({
+ 'unsubscribe': {
+ 'name': stream,
+ 'params': parameters,
+ },
+ });
+ }
+
+ Future resolveOfflineSyncStatus() async {
+ final row = await db.database.get(
+ 'SELECT powersync_offline_sync_status() AS r;',
+ );
+
+ final status = CoreSyncStatus.fromJson(
+ json.decode(row['r'] as String) as Map);
+
+ manuallyChangeSyncStatus((MutableSyncStatus()..applyFromCore(status))
+ .immutableSnapshot(setLastSynced: true));
+ }
+
+ SyncStream syncStream(String name, Map? parameters) {
+ return _SyncStreamImplementation(this, name, parameters);
+ }
+
+ void close() {
+ _statusController.close();
+ }
+}
+
+final class _SyncStreamImplementation implements SyncStream {
+ @override
+ final String name;
+
+ @override
+ final Map? parameters;
+
+ final ConnectionManager _connections;
+
+ _SyncStreamImplementation(this._connections, this.name, this.parameters);
+
+ @override
+ Future