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 aac86024..e76e3a6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,230 @@
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
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync_core` - `v1.5.0`](#powersync_core---v150)
+ - [`powersync` - `v1.15.0`](#powersync---v1150)
+ - [`powersync_sqlcipher` - `v0.1.10`](#powersync_sqlcipher---v0110)
+ - [`powersync_flutter_libs` - `v0.4.10`](#powersync_flutter_libs---v0410)
+ - [`powersync_attachments_helper` - `v0.6.18+11`](#powersync_attachments_helper---v061811)
+
+Packages with dependency updates only:
+
+> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
+
+ - `powersync_attachments_helper` - `v0.6.18+11`
+
+---
+
+#### `powersync_flutter_libs` - `v0.4.10`.
+
+ - Update the PowerSync core extension to `0.4.2`.
+
+#### `powersync_core` - `v1.5.0`
+#### `powersync` - `v1.15.0`
+#### `powersync_sqlcipher` - `v0.1.10`
+
+ - Add support for [raw tables](https://docs.powersync.com/usage/use-case-examples/raw-tables), which are user-managed
+ regular SQLite tables instead of the JSON-based views managed by PowerSync.
+
+
+## 2025-07-07
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`powersync_core` - `v1.4.1`](#powersync_core---v141)
+ - [`powersync` - `v1.14.1`](#powersync---v1141)
+ - [`powersync_sqlcipher` - `v0.1.9`](#powersync_sqlcipher---v019)
+ - [`powersync_attachments_helper` - `v0.6.18+10`](#powersync_attachments_helper---v061810)
+
+Packages with dependency updates only:
+
+> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
+
+ - `powersync_attachments_helper` - `v0.6.18+10`
+
+---
+
+#### `powersync_core` - `v1.4.1`
+#### `powersync` - `v1.14.1`
+#### `powersync_sqlcipher` - `v0.1.9`
+
+ - Rust client: Fix uploading local writes after reconnect.
+ - `PowerSyncDatabase.withDatabase`: Rename `loggers` parameter to `logger` for consistency.
+ - Fix parsing HTTP errors for sync service unavailability.
+
## 2025-06-19
### 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 c527349b..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 3c73b5ff..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 696e01f3..aea63c20 100644
--- a/demos/benchmarks/pubspec.yaml
+++ b/demos/benchmarks/pubspec.yaml
@@ -10,12 +10,12 @@ environment:
dependencies:
flutter:
sdk: flutter
- powersync: ^1.14.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 b89d8b6f..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 350d2ebf..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 873e8f9b..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.14.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 d7822ea8..f5b33ded 100644
--- a/demos/firebase-nodejs-todolist/ios/Podfile.lock
+++ b/demos/firebase-nodejs-todolist/ios/Podfile.lock
@@ -13,7 +13,7 @@ PODS:
- firebase_core (3.13.0):
- Firebase/CoreOnly (= 11.10.0)
- Flutter
- - FirebaseAppCheckInterop (11.14.0)
+ - FirebaseAppCheckInterop (11.15.0)
- FirebaseAuth (11.10.0):
- FirebaseAppCheckInterop (~> 11.0)
- FirebaseAuthInterop (~> 11.0)
@@ -23,7 +23,7 @@ PODS:
- GoogleUtilities/Environment (~> 8.0)
- GTMSessionFetcher/Core (< 5.0, >= 3.4)
- RecaptchaInterop (~> 101.0)
- - FirebaseAuthInterop (11.14.0)
+ - FirebaseAuthInterop (11.15.0)
- FirebaseCore (11.10.0):
- FirebaseCoreInternal (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
@@ -58,10 +58,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - powersync-sqlite-core (0.4.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - powersync-sqlite-core (~> 0.4.5)
- RecaptchaInterop (101.0.0)
- shared_preferences_foundation (0.0.1):
- Flutter
@@ -142,18 +142,18 @@ SPEC CHECKSUMS:
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
firebase_auth: 83bf106e5ac670dd3a0af27a86be6cba16a85723
firebase_core: 2d4534e7b489907dcede540c835b48981d890943
- FirebaseAppCheckInterop: a92ba81d0ee3c4cddb1a2e52c668ea51dc63c3ae
+ FirebaseAppCheckInterop: 06fe5a3799278ae4667e6c432edd86b1030fa3df
FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32
- FirebaseAuthInterop: e25b58ecb90f3285085fa2118861a3c9dfdc62ad
+ FirebaseAuthInterop: 7087d7a4ee4bc4de019b2d0c240974ed5d89e2fd
FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac
FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
- powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 2a71839a..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.14.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 6f22f823..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 541a3302..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 e8e83f66..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.14.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 6f22f823..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 541a3302..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 3b6ae5e8..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.14.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 387459c0..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 541a3302..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 5d9f9066..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.14.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 a41f0845..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.0)
- - powersync_flutter_libs (0.0.1):
- - Flutter
- - powersync-sqlite-core (~> 0.4.0)
- - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
- 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 541a3302..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.0)
- - powersync_flutter_libs (0.0.1):
- - FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
- - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
- 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 2a1a3111..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+9
- powersync: ^1.14.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 234add6f..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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/lib/models/schema.dart b/demos/supabase-todolist-optional-sync/lib/models/schema.dart
index de0887b3..7ce1cfd6 100644
--- a/demos/supabase-todolist-optional-sync/lib/models/schema.dart
+++ b/demos/supabase-todolist-optional-sync/lib/models/schema.dart
@@ -16,7 +16,7 @@ import 'package:powersync_flutter_supabase_todolist_optional_sync_demo/models/sy
const todosTable = 'todos';
const listsTable = 'lists';
-Schema makeSchema({synced = bool}) {
+Schema makeSchema({required bool synced}) {
String syncedName(String table) {
if (synced) {
// results in lists, todos
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 541a3302..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 eb582d6a..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.14.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 234add6f..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.0)
+ - powersync-sqlite-core (0.4.6)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
+ 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 541a3302..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.0)
+ - powersync-sqlite-core (0.4.6)
- powersync_flutter_libs (0.0.1):
+ - Flutter
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 d867dad1..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+9
- powersync: ^1.14.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 d1a2490f..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- Flutter
- - powersync-sqlite-core (~> 0.4.0)
- - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9
- 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 b81a026d..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.0)
+ - powersync-sqlite-core (0.4.5)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- - powersync-sqlite-core (~> 0.4.0)
+ - 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: 3bfe9a3c210e130583496871b404f18d4cfbe366
- powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145
+ 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 3921a07f..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.14.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/docs/update_core.md b/docs/update_core.md
new file mode 100644
index 00000000..0916fb86
--- /dev/null
+++ b/docs/update_core.md
@@ -0,0 +1,10 @@
+To update the version of the PowerSync core extension used in the Dart SDK, update the following locations:
+
+1. `scripts/download_core_binary_demos.dart` and `scripts/init_powersync_core_binary.dart`.
+2. `build.gradle` for `powersync_flutter_libs`.
+3. `powersync_flutter_libs` (iOS and macOS) for `powersync_flutter_libs`.
+4. `POWERSYNC_CORE_VERSION` in `sqlite3_wasm_build/build.sh`.
+
+After updating, run `podfile:update` to update the podfile locks for demo projects.
+If you've updated the core version to use a new feature, also update the minimum
+version in `core_version.dart` to reflect that requirement.
diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md
index 4023252b..4f30cb92 100644
--- a/packages/powersync/CHANGELOG.md
+++ b/packages/powersync/CHANGELOG.md
@@ -1,3 +1,37 @@
+## 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`.
+ - Add support for [raw tables](https://docs.powersync.com/usage/use-case-examples/raw-tables), which are user-managed
+ regular SQLite tables instead of the JSON-based views managed by PowerSync.
+
+## 1.14.1
+
+ - Rust client: Fix uploading local writs after reconnect.
+ - `PowerSyncDatabase.withDatabase`: Rename `loggers` parameter to `logger` for consistency.
+ - Fix parsing HTTP errors for sync service unavailability.
+
## 1.14.0
Add a new sync client implementation written in Rust instead of Dart. While
diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml
index 98fbdb80..26fffc25 100644
--- a/packages/powersync/pubspec.yaml
+++ b/packages/powersync/pubspec.yaml
@@ -1,5 +1,5 @@
name: powersync
-version: 1.14.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.4.0
- powersync_flutter_libs: ^0.4.9
+ 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 82c237a4..f80b8798 100644
--- a/packages/powersync_attachments_helper/CHANGELOG.md
+++ b/packages/powersync_attachments_helper/CHANGELOG.md
@@ -1,3 +1,19 @@
+## 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.
+
+## 0.6.18+10
+
+ - Update a dependency to the latest release.
+
## 0.6.18+9
- 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 70ba230e..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+9
+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.4.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 a952d9a3..4ba23772 100644
--- a/packages/powersync_core/CHANGELOG.md
+++ b/packages/powersync_core/CHANGELOG.md
@@ -1,3 +1,40 @@
+## 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`.
+ - Add support for [raw tables](https://docs.powersync.com/usage/use-case-examples/raw-tables), which are user-managed
+ regular SQLite tables instead of the JSON-based views managed by PowerSync.
+
+## 1.4.1
+
+ - Rust client: Fix uploading local writes after reconnect.
+ - `PowerSyncDatabase.withDatabase`: Rename `loggers` parameter to `logger` for consistency.
+ - Fix parsing HTTP errors for sync service unavailability.
+
## 1.4.0
Add a new sync client implementation written in Rust instead of Dart. While
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 63b8c0e8..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, 0));
+ // 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 2f846fd5..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
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:convert';
import 'dart:isolate';
import 'package:meta/meta.dart';
@@ -83,8 +84,12 @@ class PowerSyncDatabaseImpl
DefaultSqliteOpenFactory factory =
// ignore: deprecated_member_use_from_same_package
PowerSyncOpenFactory(path: path, sqliteSetup: sqliteSetup);
- return PowerSyncDatabaseImpl.withFactory(factory,
- schema: schema, maxReaders: maxReaders, logger: logger);
+ return PowerSyncDatabaseImpl.withFactory(
+ factory,
+ schema: schema,
+ maxReaders: maxReaders,
+ logger: logger,
+ );
}
/// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory].
@@ -96,13 +101,17 @@ class PowerSyncDatabaseImpl
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
factory PowerSyncDatabaseImpl.withFactory(
- DefaultSqliteOpenFactory openFactory,
- {required Schema schema,
- int maxReaders = SqliteDatabase.defaultMaxReaders,
- Logger? logger}) {
+ DefaultSqliteOpenFactory openFactory, {
+ required Schema schema,
+ int maxReaders = SqliteDatabase.defaultMaxReaders,
+ Logger? logger,
+ }) {
final db = SqliteDatabase.withFactory(openFactory, maxReaders: maxReaders);
return PowerSyncDatabaseImpl.withDatabase(
- schema: schema, database: db, logger: logger);
+ schema: schema,
+ database: db,
+ logger: logger,
+ );
}
/// Open a PowerSyncDatabase on an existing [SqliteDatabase].
@@ -110,8 +119,11 @@ class PowerSyncDatabaseImpl
/// Migrations are run on the database when this constructor is called.
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.s
- PowerSyncDatabaseImpl.withDatabase(
- {required this.schema, required this.database, Logger? logger}) {
+ PowerSyncDatabaseImpl.withDatabase({
+ required this.schema,
+ required this.database,
+ Logger? logger,
+ }) {
this.logger = logger ?? autoLogger;
isInitialized = baseInit();
}
@@ -121,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 {
@@ -128,6 +142,7 @@ class PowerSyncDatabaseImpl
bool triedSpawningIsolate = false;
StreamSubscription? crudUpdateSubscription;
+ StreamSubscription? activeStreamsSubscription;
final receiveMessages = ReceivePort();
final receiveUnhandledErrors = ReceivePort();
final receiveExit = ReceivePort();
@@ -145,6 +160,7 @@ class PowerSyncDatabaseImpl
// Cleanup
crudUpdateSubscription?.cancel();
+ activeStreamsSubscription?.cancel();
receiveMessages.close();
receiveUnhandledErrors.close();
receiveExit.close();
@@ -186,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);
@@ -247,6 +267,7 @@ class PowerSyncDatabaseImpl
options,
crudMutex.shared,
syncMutex.shared,
+ jsonEncode(schema),
),
debugName: 'Sync ${database.openFactory.path}',
onError: receiveUnhandledErrors.sendPort,
@@ -290,6 +311,7 @@ class _PowerSyncDatabaseIsolateArgs {
final ResolvedSyncOptions options;
final SerializedMutex crudMutex;
final SerializedMutex syncMutex;
+ final String schemaJson;
_PowerSyncDatabaseIsolateArgs(
this.sPort,
@@ -297,6 +319,7 @@ class _PowerSyncDatabaseIsolateArgs {
this.options,
this.crudMutex,
this.syncMutex,
+ this.schemaJson,
);
}
@@ -351,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);
}
}
});
@@ -392,6 +418,7 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async {
final storage = BucketStorage(connection);
final sync = StreamingSyncImplementation(
adapter: storage,
+ schemaJson: args.schemaJson,
connector: InternalConnector(
getCredentialsCached: getCredentialsCached,
prefetchCredentials: prefetchCredentials,
@@ -422,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.dart b/packages/powersync_core/lib/src/database/powersync_database.dart
index 95543ce8..4de7ea92 100644
--- a/packages/powersync_core/lib/src/database/powersync_database.dart
+++ b/packages/powersync_core/lib/src/database/powersync_database.dart
@@ -32,19 +32,21 @@ abstract class PowerSyncDatabase
/// A maximum of [maxReaders] concurrent read transactions are allowed.
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
- factory PowerSyncDatabase(
- {required Schema schema,
- required String path,
- Logger? logger,
- @Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
- // ignore: deprecated_member_use_from_same_package
- SqliteConnectionSetup? sqliteSetup}) {
+ factory PowerSyncDatabase({
+ required Schema schema,
+ required String path,
+ Logger? logger,
+ @Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
+ // ignore: deprecated_member_use_from_same_package
+ SqliteConnectionSetup? sqliteSetup,
+ }) {
return PowerSyncDatabaseImpl(
- schema: schema,
- path: path,
- logger: logger,
- // ignore: deprecated_member_use_from_same_package
- sqliteSetup: sqliteSetup);
+ schema: schema,
+ path: path,
+ logger: logger,
+ // ignore: deprecated_member_use_from_same_package
+ sqliteSetup: sqliteSetup,
+ );
}
/// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory].
@@ -55,12 +57,18 @@ abstract class PowerSyncDatabase
/// Subclass [PowerSyncOpenFactory] to add custom logic to this process.
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
- factory PowerSyncDatabase.withFactory(DefaultSqliteOpenFactory openFactory,
- {required Schema schema,
- int maxReaders = SqliteDatabase.defaultMaxReaders,
- Logger? logger}) {
- return PowerSyncDatabaseImpl.withFactory(openFactory,
- schema: schema, maxReaders: maxReaders, logger: logger);
+ factory PowerSyncDatabase.withFactory(
+ DefaultSqliteOpenFactory openFactory, {
+ required Schema schema,
+ int maxReaders = SqliteDatabase.defaultMaxReaders,
+ Logger? logger,
+ }) {
+ return PowerSyncDatabaseImpl.withFactory(
+ openFactory,
+ schema: schema,
+ maxReaders: maxReaders,
+ logger: logger,
+ );
}
/// Open a PowerSyncDatabase on an existing [SqliteDatabase].
@@ -68,11 +76,16 @@ abstract class PowerSyncDatabase
/// Migrations are run on the database when this constructor is called.
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
- factory PowerSyncDatabase.withDatabase(
- {required Schema schema,
- required SqliteDatabase database,
- Logger? loggers}) {
+ factory PowerSyncDatabase.withDatabase({
+ required Schema schema,
+ required SqliteDatabase database,
+ Logger? logger,
+ @Deprecated("Use [logger] instead") Logger? loggers,
+ }) {
return PowerSyncDatabaseImpl.withDatabase(
- schema: schema, database: database, logger: loggers);
+ schema: schema,
+ database: database,
+ logger: loggers ?? logger,
+ );
}
}
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 2a795497..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';
@@ -82,10 +83,11 @@ class PowerSyncDatabaseImpl
/// Migrations are run on the database when this constructor is called.
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.s
- factory PowerSyncDatabaseImpl.withDatabase(
- {required Schema schema,
- required SqliteDatabase database,
- Logger? logger}) {
+ factory PowerSyncDatabaseImpl.withDatabase({
+ required Schema schema,
+ required SqliteDatabase database,
+ Logger? logger,
+ }) {
throw UnimplementedError();
}
@@ -114,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 808efc71..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,60 +216,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
params: params,
);
- // 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.
@@ -364,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,
});
@@ -372,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.
@@ -410,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.')
@@ -433,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));
@@ -501,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.
@@ -531,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.
@@ -622,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 6b40a6a2..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
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:convert';
import 'package:meta/meta.dart';
import 'package:http/browser_client.dart';
import 'package:logging/logging.dart';
@@ -75,8 +76,12 @@ class PowerSyncDatabaseImpl
SqliteConnectionSetup? sqliteSetup}) {
// ignore: deprecated_member_use_from_same_package
DefaultSqliteOpenFactory factory = PowerSyncOpenFactory(path: path);
- return PowerSyncDatabaseImpl.withFactory(factory,
- maxReaders: maxReaders, logger: logger, schema: schema);
+ return PowerSyncDatabaseImpl.withFactory(
+ factory,
+ maxReaders: maxReaders,
+ logger: logger,
+ schema: schema,
+ );
}
/// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory].
@@ -94,7 +99,10 @@ class PowerSyncDatabaseImpl
Logger? logger}) {
final db = SqliteDatabase.withFactory(openFactory, maxReaders: 1);
return PowerSyncDatabaseImpl.withDatabase(
- schema: schema, logger: logger, database: db);
+ schema: schema,
+ logger: logger,
+ database: db,
+ );
}
/// Open a PowerSyncDatabase on an existing [SqliteDatabase].
@@ -102,8 +110,11 @@ class PowerSyncDatabaseImpl
/// Migrations are run on the database when this constructor is called.
///
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
- PowerSyncDatabaseImpl.withDatabase(
- {required this.schema, required this.database, Logger? logger}) {
+ PowerSyncDatabaseImpl.withDatabase({
+ required this.schema,
+ required this.database,
+ Logger? logger,
+ }) {
if (logger != null) {
this.logger = logger;
} else {
@@ -117,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 {
@@ -130,6 +143,7 @@ class PowerSyncDatabaseImpl
connector: connector,
options: options.source,
workerUri: Uri.base.resolve('/powersync_sync.worker.js'),
+ subscriptions: initiallyActiveStreams,
);
} catch (e) {
logger.warning(
@@ -141,10 +155,12 @@ class PowerSyncDatabaseImpl
sync = StreamingSyncImplementation(
adapter: storage,
+ schemaJson: jsonEncode(schema),
connector: InternalConnector.wrap(connector, this),
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,
@@ -156,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 983876a2..bc35df4a 100644
--- a/packages/powersync_core/lib/src/exceptions.dart
+++ b/packages/powersync_core/lib/src/exceptions.dart
@@ -38,15 +38,11 @@ class SyncResponseException implements Exception {
http.StreamedResponse response) async {
try {
final body = await response.stream.bytesToString();
- final decoded = convert.jsonDecode(body);
- final details = _stringOrFirst(decoded['error']?['details']) ?? body;
- final message = '${response.reasonPhrase ?? "Request failed"}: $details';
- return SyncResponseException(response.statusCode, message);
- } on Error catch (_) {
- return SyncResponseException(
- response.statusCode,
- response.reasonPhrase ?? "Request failed",
- );
+ return _fromResponseBody(response, body);
+ } on Exception catch (_) {
+ // Could be FormatException, stream issues, or possibly other exceptions.
+ // Fallback to just using the response header.
+ return _fromResponseHeader(response);
}
}
@@ -54,23 +50,65 @@ class SyncResponseException implements Exception {
static SyncResponseException fromResponse(http.Response response) {
try {
final body = response.body;
- final decoded = convert.jsonDecode(body);
- final details = _stringOrFirst(decoded['error']?['details']) ?? body;
- final message = '${response.reasonPhrase ?? "Request failed"}: $details';
- return SyncResponseException(response.statusCode, message);
- } on FormatException catch (_) {
- return SyncResponseException(
- response.statusCode,
- response.reasonPhrase ?? "Request failed",
- );
- } on Error catch (_) {
- return SyncResponseException(
- response.statusCode,
- response.reasonPhrase ?? "Request failed",
- );
+ return _fromResponseBody(response, body);
+ } on Exception catch (_) {
+ // Could be FormatException, or possibly other exceptions.
+ // Fallback to just using the response header.
+ return _fromResponseHeader(response);
}
}
+ static SyncResponseException _fromResponseBody(
+ http.BaseResponse response, String body) {
+ final decoded = convert.jsonDecode(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);
+ }
+
+ static SyncResponseException _fromResponseHeader(http.BaseResponse response) {
+ return SyncResponseException(
+ response.statusCode,
+ response.reasonPhrase ?? "Request failed",
+ );
+ }
+
+ /// 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;
@@ -82,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/powersync_update_notification.dart b/packages/powersync_core/lib/src/powersync_update_notification.dart
index b97a27ce..44b03078 100644
--- a/packages/powersync_core/lib/src/powersync_update_notification.dart
+++ b/packages/powersync_core/lib/src/powersync_update_notification.dart
@@ -45,8 +45,7 @@ class PowerSyncUpdateNotification extends UpdateNotification {
Set _friendlyTableNames(Iterable originalTables) {
Set tables = {};
for (var table in originalTables) {
- var friendlyName = friendlyTableName(table);
- if (friendlyName != null) {
+ if (friendlyTableName(table) case final friendlyName?) {
tables.add(friendlyName);
} else if (!table.startsWith('ps_')) {
tables.add(table);
diff --git a/packages/powersync_core/lib/src/schema.dart b/packages/powersync_core/lib/src/schema.dart
index 4892ee6c..1289cae0 100644
--- a/packages/powersync_core/lib/src/schema.dart
+++ b/packages/powersync_core/lib/src/schema.dart
@@ -7,11 +7,23 @@ import 'schema_logic.dart';
/// No migrations are required on the client.
class Schema {
/// List of tables in the schema.
+ ///
+ /// When opening a PowerSync database, these tables will be created and
+ /// migrated automatically.
final List tables;
- const Schema(this.tables);
+ /// A list of [RawTable]s in addition to PowerSync-managed [tables].
+ ///
+ /// Raw tables give users full control over the SQLite tables, but that
+ /// includes the responsibility to create those tables and to write migrations
+ /// for them.
+ ///
+ /// For more information on raw tables, see [RawTable] and [the documentation](https://docs.powersync.com/usage/use-case-examples/raw-tables).
+ final List rawTables;
+
+ const Schema(this.tables, {this.rawTables = const []});
- Map toJson() => {'tables': tables};
+ Map toJson() => {'raw_tables': rawTables, 'tables': tables};
void validate() {
Set tableNames = {};
@@ -315,6 +327,120 @@ class Column {
Map toJson() => {'name': name, 'type': type.sqlite};
}
+/// A raw table, defined by the user instead of being managed by PowerSync.
+///
+/// Any ordinary SQLite table can be defined as a raw table, which enables:
+///
+/// - More performant queries, since data is stored in typed rows instead of the
+/// schemaless JSON view PowerSync uses by default.
+/// - More control over the table, since custom column constraints can be used
+/// in its definition.
+///
+/// PowerSync doesn't know anything about the internal structure of raw tables -
+/// instead, it relies on user-defined [put] and [delete] statements to sync
+/// data into them.
+///
+/// When using raw tables, you are responsible for creating and migrating them
+/// when they've changed. Further, triggers are necessary to collect local
+/// writes to those tables. For more information, see
+/// [the documentation](https://docs.powersync.com/usage/use-case-examples/raw-tables).
+///
+/// Note that raw tables are only supported by the Rust sync client, which needs
+/// to be enabled when connecting with raw tables.
+final class RawTable {
+ /// The name of the table as used by the sync service.
+ ///
+ /// This doesn't necessarily have to match the name of the SQLite table that
+ /// [put] and [delete] write to. Instead, it's used by the sync client to
+ /// identify which statements to use when it encounters sync operations for
+ /// this table.
+ final String name;
+
+ /// A statement responsible for inserting or updating a row in this raw table
+ /// based on data from the sync service.
+ ///
+ /// See [PendingStatement] for details.
+ final PendingStatement put;
+
+ /// A statement responsible for deleting a row based on its PowerSync id.
+ ///
+ /// See [PendingStatement] for details. Note that [PendingStatementValue]s
+ /// used here must all be [PendingStatementValue.id].
+ final PendingStatement delete;
+
+ const RawTable({
+ required this.name,
+ required this.put,
+ required this.delete,
+ });
+
+ Map toJson() => {
+ 'name': name,
+ 'put': put,
+ 'delete': delete,
+ };
+}
+
+/// An SQL statement to be run by the sync client against raw tables.
+///
+/// Since raw tables are managed by the user, PowerSync can't know how to apply
+/// serverside changes to them. These statements bridge raw tables and PowerSync
+/// by providing upserts and delete statements.
+///
+/// For more information, see [the documentation](https://docs.powersync.com/usage/use-case-examples/raw-tables)
+final class PendingStatement {
+ /// The SQL statement to run to upsert or delete data from a raw table.
+ final String sql;
+
+ /// A list of value identifiers for parameters in [sql].
+ ///
+ /// Put statements can use both [PendingStatementValue.id] and
+ /// [PendingStatementValue.column], whereas delete statements can only use
+ /// [PendingStatementValue.id].
+ final List params;
+
+ PendingStatement({required this.sql, required this.params});
+
+ Map toJson() => {
+ 'sql': sql,
+ 'params': params,
+ };
+}
+
+/// A description of a value that will be resolved in the sync client when
+/// running a [PendingStatement] for a [RawTable].
+sealed class PendingStatementValue {
+ /// A value that is bound to the textual id used in the PowerSync protocol.
+ factory PendingStatementValue.id() = _PendingStmtValueId;
+
+ /// A value that is bound to the value of a column in a replace (`PUT`)
+ /// operation of the PowerSync protocol.
+ factory PendingStatementValue.column(String column) = _PendingStmtValueColumn;
+
+ dynamic toJson();
+}
+
+class _PendingStmtValueColumn implements PendingStatementValue {
+ final String column;
+ const _PendingStmtValueColumn(this.column);
+
+ @override
+ dynamic toJson() {
+ return {
+ 'Column': column,
+ };
+ }
+}
+
+class _PendingStmtValueId implements PendingStatementValue {
+ const _PendingStmtValueId();
+
+ @override
+ dynamic toJson() {
+ return 'Id';
+ }
+}
+
/// Type of column.
enum ColumnType {
/// TEXT column.
diff --git a/packages/powersync_core/lib/src/schema_logic.dart b/packages/powersync_core/lib/src/schema_logic.dart
index 93296c5a..e9064ea9 100644
--- a/packages/powersync_core/lib/src/schema_logic.dart
+++ b/packages/powersync_core/lib/src/schema_logic.dart
@@ -21,8 +21,14 @@ Future updateSchemaInIsolate(
}
String? friendlyTableName(String table) {
- final re = RegExp(r"^ps_data__(.+)$");
- final re2 = RegExp(r"^ps_data_local__(.+)$");
- final match = re.firstMatch(table) ?? re2.firstMatch(table);
- return match?.group(1);
+ const prefix1 = 'ps_data__';
+ const prefix2 = 'ps_data_local__';
+
+ if (table.startsWith(prefix2)) {
+ return table.substring(prefix2.length);
+ } else if (table.startsWith(prefix1)) {
+ return table.substring(prefix1.length);
+ } else {
+ return null;
+ }
}
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