From c389794871ddc985c2de127cc53d4e83d31f8d95 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 24 Jun 2025 12:56:14 -0400 Subject: [PATCH 01/62] Simplify friendly table name implementation --- .../src/powersync_update_notification.dart | 3 +-- .../powersync_core/lib/src/schema_logic.dart | 14 +++++++---- packages/powersync_core/test/watch_test.dart | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) 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_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/test/watch_test.dart b/packages/powersync_core/test/watch_test.dart index afd1a115..00e7ea49 100644 --- a/packages/powersync_core/test/watch_test.dart +++ b/packages/powersync_core/test/watch_test.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:async/async.dart'; import 'package:powersync_core/powersync_core.dart'; import 'package:sqlite_async/sqlite_async.dart'; import 'package:test/test.dart'; @@ -138,5 +139,28 @@ void main() { UpdateNotification.single('assets') ])); }); + + test('emits update events with friendly names', () async { + final powersync = await testUtils.setupPowerSync( + path: path, + schema: Schema([ + Table.localOnly('users', [ + Column.text('name'), + ]), + Table('assets', [ + Column.text('name'), + ]), + ]), + ); + + final updates = StreamQueue(powersync.updates); + await powersync + .execute('INSERT INTO users (id, name) VALUES (uuid(), ?)', ['test']); + await expectLater(updates, emits(UpdateNotification({'users'}))); + + await powersync.execute( + 'INSERT INTO assets (id, name) VALUES (uuid(), ?)', ['test']); + await expectLater(updates, emits(UpdateNotification({'assets'}))); + }); }); } From 1bf59b3234b4d0438f49de21ed5b585ad5d0a183 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 24 Jun 2025 13:04:42 -0400 Subject: [PATCH 02/62] Proper type for `makeSchema` --- demos/supabase-todolist-optional-sync/lib/models/schema.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From a3df9c54230d5a35100d9c3ab58132e7795b7b32 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 30 Jun 2025 15:00:42 -0600 Subject: [PATCH 03/62] Rust: Crud upload on reconnect --- .../lib/src/sync/streaming_sync.dart | 14 +++++ .../test/in_memory_sync_test.dart | 56 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 08401374..1f8091b3 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -586,6 +586,7 @@ typedef BucketDescription = ({ final class _ActiveRustStreamingIteration { final StreamingSyncImplementation sync; var _isActive = true; + var _hadSyncLine = false; StreamSubscription? _completedUploads; final Completer _completedStream = Completer(); @@ -621,8 +622,10 @@ final class _ActiveRustStreamingIteration { switch (event) { case ReceivedLine(line: final Uint8List line): + _triggerCrudUploadOnFirstLine(); await _control('line_binary', line); case ReceivedLine(line: final line as String): + _triggerCrudUploadOnFirstLine(); await _control('line_text', line); case UploadCompleted(): await _control('completed_upload'); @@ -634,6 +637,17 @@ final class _ActiveRustStreamingIteration { } } + /// Triggers a local CRUD upload when the first sync line has been received. + /// + /// This allows uploading local changes that have been made while offline or + /// disconnected. + void _triggerCrudUploadOnFirstLine() { + if (!_hadSyncLine) { + sync._internalCrudTriggerController.add(null); + _hadSyncLine = true; + } + } + Future _stop() { return _control('stop'); } diff --git a/packages/powersync_core/test/in_memory_sync_test.dart b/packages/powersync_core/test/in_memory_sync_test.dart index 8bce8130..b6432014 100644 --- a/packages/powersync_core/test/in_memory_sync_test.dart +++ b/packages/powersync_core/test/in_memory_sync_test.dart @@ -104,8 +104,8 @@ void _declareTests(String name, SyncOptions options) { addTearDown(status.cancel); syncService.addKeepAlive(); - await expectLater( - status, emits(isSyncStatus(connected: true, hasSynced: false))); + await expectLater(status, + emitsThrough(isSyncStatus(connected: true, hasSynced: false))); return status; } @@ -774,6 +774,58 @@ void _declareTests(String name, SyncOptions options) { await Future.delayed(const Duration(milliseconds: 500)); expect(syncService.controller.hasListener, isTrue); }); + + test('uploads writes made while offline', () async { + // Local write while not connected + await database.execute( + 'insert into customers (id, name) values (uuid(), ?)', + ['local customer']); + uploadData = (db) async { + final batch = await db.getNextCrudTransaction(); + if (batch != null) { + await batch.complete(); + } + }; + syncService.writeCheckpoint = () => { + 'data': {'write_checkpoint': '1'} + }; + + final query = StreamQueue(database + .watch('SELECT name FROM customers') + .map((e) => e.single['name'])); + expect(await query.next, 'local customer'); + + await waitForConnection(); + + syncService + ..addLine({ + 'checkpoint': Checkpoint( + lastOpId: '1', + writeCheckpoint: '1', + checksums: [BucketChecksum(bucket: 'a', priority: 3, checksum: 0)], + ) + }) + ..addLine({ + 'data': { + 'bucket': 'a', + 'data': >[ + { + 'op_id': '1', + 'op': 'PUT', + 'object_type': 'customers', + 'object_id': '1', + 'checksum': 0, + 'data': json.encode({'name': 'from server'}), + } + ], + } + }) + ..addLine({ + 'checkpoint_complete': {'last_op_id': '1'} + }); + + expect(await query.next, 'from server'); + }); }); } From 38ed119b0f42159209298513889f311c209b3931 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Wed, 2 Jul 2025 18:14:14 +0200 Subject: [PATCH 04/62] Fix http error parsing (#295) * Fix http error parsing. * Extract common logic. --- .../powersync_core/lib/src/exceptions.dart | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/powersync_core/lib/src/exceptions.dart b/packages/powersync_core/lib/src/exceptions.dart index 983876a2..e4f7c864 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,29 @@ 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 = _stringOrFirst(decoded['error']?['details']) ?? 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", + ); + } + int statusCode; String description; From 809aebe49b7e4fef15d51edf7c701748d1edb446 Mon Sep 17 00:00:00 2001 From: David Martos Date: Mon, 7 Jul 2025 11:37:48 +0200 Subject: [PATCH 05/62] Fix typo in method parameter: loggers -> logger --- .../lib/src/database/powersync_database.dart | 15 ++++++++++----- .../test/utils/abstract_test_utils.dart | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/powersync_core/lib/src/database/powersync_database.dart b/packages/powersync_core/lib/src/database/powersync_database.dart index 95543ce8..796bdefc 100644 --- a/packages/powersync_core/lib/src/database/powersync_database.dart +++ b/packages/powersync_core/lib/src/database/powersync_database.dart @@ -68,11 +68,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/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index 95f71211..5cfb8405 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -74,7 +74,7 @@ abstract mixin class TestPowerSyncFactory implements PowerSyncOpenFactory { schema: schema, database: SqliteDatabase.singleConnection( SqliteConnection.synchronousWrapper(raw)), - loggers: logger, + logger: logger, ); } } From 187525002ab4184e959dcd37b40953d9b8186d45 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 7 Jul 2025 13:41:32 +0200 Subject: [PATCH 06/62] chore(release): publish packages - powersync_core@1.4.1 - powersync@1.14.1 - powersync_sqlcipher@0.1.9 - powersync_attachments_helper@0.6.18+10 --- CHANGELOG.md | 33 +++++++++++++++++++ demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/pubspec.yaml | 2 +- demos/firebase-nodejs-todolist/pubspec.yaml | 2 +- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/pubspec.yaml | 2 +- demos/supabase-todolist-drift/pubspec.yaml | 4 +-- .../pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 4 +-- demos/supabase-trello/pubspec.yaml | 2 +- packages/powersync/CHANGELOG.md | 6 ++++ packages/powersync/pubspec.yaml | 4 +-- .../powersync_attachments_helper/CHANGELOG.md | 4 +++ .../powersync_attachments_helper/pubspec.yaml | 4 +-- packages/powersync_core/CHANGELOG.md | 6 ++++ packages/powersync_core/lib/src/version.dart | 2 +- packages/powersync_core/pubspec.yaml | 2 +- packages/powersync_sqlcipher/CHANGELOG.md | 6 ++++ .../powersync_sqlcipher/example/pubspec.yaml | 2 +- packages/powersync_sqlcipher/pubspec.yaml | 4 +-- 21 files changed, 76 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aac86024..f209e57f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 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/pubspec.yaml b/demos/benchmarks/pubspec.yaml index 696e01f3..930b5222 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.14.0 + powersync: ^1.14.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index 873e8f9b..9afbba59 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.14.0 + powersync: ^1.14.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml index 2a71839a..659d7002 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.14.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index e8e83f66..2432eb04 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.14.0 + powersync: ^1.14.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 3b6ae5e8..1b3dd80a 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.14.0 + powersync: ^1.14.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml index 5d9f9066..dfd00018 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.14.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 2a1a3111..0f25ddf5 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.18+10 + powersync: ^1.14.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index eb582d6a..09c95e16 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.14.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index d867dad1..9230244e 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_attachments_helper: ^0.6.18+10 + powersync: ^1.14.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index 3921a07f..4e066ad6 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: random_name_generator: ^1.5.0 flutter_dotenv: ^5.2.1 logging: ^1.3.0 - powersync: ^1.14.0 + powersync: ^1.14.1 sqlite_async: ^0.11.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md index 4023252b..c2bcd63b 100644 --- a/packages/powersync/CHANGELOG.md +++ b/packages/powersync/CHANGELOG.md @@ -1,3 +1,9 @@ +## 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..4b6edf3b 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync -version: 1.14.0 +version: 1.14.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 @@ -12,7 +12,7 @@ dependencies: sdk: flutter sqlite3_flutter_libs: ^0.5.23 - powersync_core: ^1.4.0 + powersync_core: ^1.4.1 powersync_flutter_libs: ^0.4.9 collection: ^1.17.0 diff --git a/packages/powersync_attachments_helper/CHANGELOG.md b/packages/powersync_attachments_helper/CHANGELOG.md index 82c237a4..637e059d 100644 --- a/packages/powersync_attachments_helper/CHANGELOG.md +++ b/packages/powersync_attachments_helper/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index 70ba230e..96989012 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.18+10 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ environment: @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.4.0 + powersync_core: ^1.4.1 logging: ^1.2.0 sqlite_async: ^0.11.0 path_provider: ^2.0.13 diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index a952d9a3..22ae6ca2 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,9 @@ +## 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/lib/src/version.dart b/packages/powersync_core/lib/src/version.dart index 2ef54c1e..b797baa0 100644 --- a/packages/powersync_core/lib/src/version.dart +++ b/packages/powersync_core/lib/src/version.dart @@ -1 +1 @@ -const String libraryVersion = '1.4.0'; +const String libraryVersion = '1.4.1'; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index fe6c4bfa..0a01853c 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_core -version: 1.4.0 +version: 1.4.1 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Dart SDK - sync engine for building local-first apps. diff --git a/packages/powersync_sqlcipher/CHANGELOG.md b/packages/powersync_sqlcipher/CHANGELOG.md index 9dac49a1..aafa82a1 100644 --- a/packages/powersync_sqlcipher/CHANGELOG.md +++ b/packages/powersync_sqlcipher/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.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. + ## 0.1.8 Add a new sync client implementation written in Rust instead of Dart. While diff --git a/packages/powersync_sqlcipher/example/pubspec.yaml b/packages/powersync_sqlcipher/example/pubspec.yaml index 94ed083f..ea7ad7bb 100644 --- a/packages/powersync_sqlcipher/example/pubspec.yaml +++ b/packages/powersync_sqlcipher/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - powersync_sqlcipher: ^0.1.8 + powersync_sqlcipher: ^0.1.9 dev_dependencies: flutter_test: diff --git a/packages/powersync_sqlcipher/pubspec.yaml b/packages/powersync_sqlcipher/pubspec.yaml index 86faebc4..5951158a 100644 --- a/packages/powersync_sqlcipher/pubspec.yaml +++ b/packages/powersync_sqlcipher/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_sqlcipher -version: 0.1.8 +version: 0.1.9 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Flutter SDK - sync engine for building local-first apps. @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.4.0 + powersync_core: ^1.4.1 powersync_flutter_libs: ^0.4.9 sqlcipher_flutter_libs: ^0.6.4 sqlite3_web: ^0.3.0 From 74d204335621dc9834a094d805305d71db3b90f4 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 11 Jul 2025 16:59:06 +0200 Subject: [PATCH 07/62] Update core extension to 0.4.2 --- demos/benchmarks/ios/Podfile.lock | 8 ++++---- demos/benchmarks/macos/Podfile.lock | 8 ++++---- demos/django-todolist/ios/Podfile.lock | 8 ++++---- demos/django-todolist/macos/Podfile.lock | 8 ++++---- demos/firebase-nodejs-todolist/ios/Podfile.lock | 16 ++++++++-------- demos/supabase-anonymous-auth/ios/Podfile.lock | 8 ++++---- demos/supabase-anonymous-auth/macos/Podfile.lock | 8 ++++---- .../supabase-edge-function-auth/ios/Podfile.lock | 8 ++++---- .../macos/Podfile.lock | 8 ++++---- demos/supabase-simple-chat/ios/Podfile.lock | 8 ++++---- demos/supabase-simple-chat/macos/Podfile.lock | 8 ++++---- demos/supabase-todolist-drift/ios/Podfile.lock | 8 ++++---- demos/supabase-todolist-drift/macos/Podfile.lock | 8 ++++---- .../ios/Podfile.lock | 8 ++++---- .../macos/Podfile.lock | 8 ++++---- demos/supabase-todolist/ios/Podfile.lock | 8 ++++---- demos/supabase-todolist/macos/Podfile.lock | 8 ++++---- demos/supabase-trello/ios/Podfile.lock | 8 ++++---- demos/supabase-trello/macos/Podfile.lock | 8 ++++---- docs/update_core.md | 8 ++++++++ .../powersync_flutter_libs/android/build.gradle | 2 +- .../ios/powersync_flutter_libs.podspec | 2 +- .../macos/powersync_flutter_libs.podspec | 2 +- packages/sqlite3_wasm_build/build.sh | 2 +- scripts/download_core_binary_demos.dart | 2 +- scripts/init_powersync_core_binary.dart | 2 +- 26 files changed, 94 insertions(+), 86 deletions(-) create mode 100644 docs/update_core.md diff --git a/demos/benchmarks/ios/Podfile.lock b/demos/benchmarks/ios/Podfile.lock index c527349b..7a167d34 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - sqlite3 (3.49.2): - sqlite3/common (= 3.49.2) - sqlite3/common (3.49.2) @@ -54,8 +54,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/benchmarks/macos/Podfile.lock b/demos/benchmarks/macos/Podfile.lock index 3c73b5ff..b1925cd1 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - sqlite3 (3.49.2): - sqlite3/common (= 3.49.2) - sqlite3/common (3.49.2) @@ -54,8 +54,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/django-todolist/ios/Podfile.lock b/demos/django-todolist/ios/Podfile.lock index b89d8b6f..92649cb7 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -60,8 +60,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/django-todolist/macos/Podfile.lock b/demos/django-todolist/macos/Podfile.lock index 350d2ebf..249a5b49 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -60,8 +60,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/firebase-nodejs-todolist/ios/Podfile.lock b/demos/firebase-nodejs-todolist/ios/Podfile.lock index d7822ea8..e3b3429e 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - RecaptchaInterop (101.0.0) - shared_preferences_foundation (0.0.1): - Flutter @@ -142,9 +142,9 @@ 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 @@ -152,8 +152,8 @@ SPEC CHECKSUMS: GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 diff --git a/demos/supabase-anonymous-auth/ios/Podfile.lock b/demos/supabase-anonymous-auth/ios/Podfile.lock index 6f22f823..6553952e 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-anonymous-auth/macos/Podfile.lock b/demos/supabase-anonymous-auth/macos/Podfile.lock index 541a3302..f32a7411 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-edge-function-auth/ios/Podfile.lock b/demos/supabase-edge-function-auth/ios/Podfile.lock index 6f22f823..6553952e 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-edge-function-auth/macos/Podfile.lock b/demos/supabase-edge-function-auth/macos/Podfile.lock index 541a3302..f32a7411 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-simple-chat/ios/Podfile.lock b/demos/supabase-simple-chat/ios/Podfile.lock index 387459c0..12271547 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-simple-chat/macos/Podfile.lock b/demos/supabase-simple-chat/macos/Podfile.lock index 541a3302..f32a7411 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-drift/ios/Podfile.lock b/demos/supabase-todolist-drift/ios/Podfile.lock index a41f0845..1af06a74 100644 --- a/demos/supabase-todolist-drift/ios/Podfile.lock +++ b/demos/supabase-todolist-drift/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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -77,8 +77,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-drift/macos/Podfile.lock b/demos/supabase-todolist-drift/macos/Podfile.lock index 541a3302..f32a7411 100644 --- a/demos/supabase-todolist-drift/macos/Podfile.lock +++ b/demos/supabase-todolist-drift/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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-optional-sync/ios/Podfile.lock b/demos/supabase-todolist-optional-sync/ios/Podfile.lock index 234add6f..aac7e03f 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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -77,8 +77,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-optional-sync/macos/Podfile.lock b/demos/supabase-todolist-optional-sync/macos/Podfile.lock index 541a3302..f32a7411 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist/ios/Podfile.lock b/demos/supabase-todolist/ios/Podfile.lock index 234add6f..aac7e03f 100644 --- a/demos/supabase-todolist/ios/Podfile.lock +++ b/demos/supabase-todolist/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.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -77,8 +77,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist/macos/Podfile.lock b/demos/supabase-todolist/macos/Podfile.lock index 541a3302..f32a7411 100644 --- a/demos/supabase-todolist/macos/Podfile.lock +++ b/demos/supabase-todolist/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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-trello/ios/Podfile.lock b/demos/supabase-trello/ios/Podfile.lock index d1a2490f..79306fa5 100644 --- a/demos/supabase-trello/ios/Podfile.lock +++ b/demos/supabase-trello/ios/Podfile.lock @@ -41,10 +41,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.0) + - powersync-sqlite-core (0.4.2) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - SDWebImage (5.21.1): - SDWebImage/Core (= 5.21.1) - SDWebImage/Core (5.21.1) @@ -125,8 +125,8 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: f7069f8801cbb3bf870caf726c3bd5ac105c0ad9 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 SDWebImage: f29024626962457f3470184232766516dee8dfea shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 diff --git a/demos/supabase-trello/macos/Podfile.lock b/demos/supabase-trello/macos/Podfile.lock index b81a026d..0eed3cbb 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.2) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.0) + - powersync-sqlite-core (~> 0.4.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -83,8 +83,8 @@ SPEC CHECKSUMS: file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 3bfe9a3c210e130583496871b404f18d4cfbe366 - powersync_flutter_libs: 31df4f212f2ee3bb0c0ba96214690eb719d83145 + powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 + powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/docs/update_core.md b/docs/update_core.md new file mode 100644 index 00000000..3b5b0d87 --- /dev/null +++ b/docs/update_core.md @@ -0,0 +1,8 @@ +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. diff --git a/packages/powersync_flutter_libs/android/build.gradle b/packages/powersync_flutter_libs/android/build.gradle index 5c52e329..91f9cb27 100644 --- a/packages/powersync_flutter_libs/android/build.gradle +++ b/packages/powersync_flutter_libs/android/build.gradle @@ -50,5 +50,5 @@ android { } dependencies { - implementation 'co.powersync:powersync-sqlite-core:0.4.0' + implementation 'com.powersync:powersync-sqlite-core:0.4.2' } diff --git a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec index f89b5152..d83fc2c8 100644 --- a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec @@ -22,7 +22,7 @@ A new Flutter FFI plugin project. s.dependency 'Flutter' s.platform = :ios, '11.0' - s.dependency "powersync-sqlite-core", "~> 0.4.0" + s.dependency "powersync-sqlite-core", "~> 0.4.2" # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec index 9dfe4b3f..4519dcb7 100644 --- a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec @@ -21,7 +21,7 @@ A new Flutter FFI plugin project. s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.dependency "powersync-sqlite-core", "~> 0.4.0" + s.dependency "powersync-sqlite-core", "~> 0.4.2" s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index 15bb8b71..fc414ec7 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -2,7 +2,7 @@ set -e SQLITE_VERSION="2.7.6" -POWERSYNC_CORE_VERSION="0.4.0" +POWERSYNC_CORE_VERSION="0.4.2" SQLITE_PATH="sqlite3.dart" if [ -d "$SQLITE_PATH" ]; then diff --git a/scripts/download_core_binary_demos.dart b/scripts/download_core_binary_demos.dart index 0e165fd2..e5cb52de 100644 --- a/scripts/download_core_binary_demos.dart +++ b/scripts/download_core_binary_demos.dart @@ -3,7 +3,7 @@ import 'dart:io'; final coreUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.0'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.2'; void main() async { final powersyncLibsLinuxPath = "packages/powersync_flutter_libs/linux"; diff --git a/scripts/init_powersync_core_binary.dart b/scripts/init_powersync_core_binary.dart index f9a136a4..1374f441 100644 --- a/scripts/init_powersync_core_binary.dart +++ b/scripts/init_powersync_core_binary.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:melos/melos.dart'; final sqliteUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.0'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.2'; void main() async { final sqliteCoreFilename = getLibraryForPlatform(); From 7718221aa8f931ce07b96cf87348598193a67dc7 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 11 Jul 2025 17:00:16 +0200 Subject: [PATCH 08/62] Also update minimum version --- docs/update_core.md | 2 ++ packages/powersync_core/lib/src/database/core_version.dart | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/update_core.md b/docs/update_core.md index 3b5b0d87..0916fb86 100644 --- a/docs/update_core.md +++ b/docs/update_core.md @@ -6,3 +6,5 @@ To update the version of the PowerSync core extension used in the Dart SDK, upda 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_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart index 63b8c0e8..f0156078 100644 --- a/packages/powersync_core/lib/src/database/core_version.dart +++ b/packages/powersync_core/lib/src/database/core_version.dart @@ -60,7 +60,7 @@ extension type const PowerSyncCoreVersion((int, int, int) _tuple) { // 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)); + static const minimum = PowerSyncCoreVersion((0, 4, 2)); /// The first version of the core extensions that this version of the Dart /// SDK doesn't support. From 4fa37752c203ec11c6d61ad34adb8149261c7f9b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 11 Jul 2025 17:17:04 +0200 Subject: [PATCH 09/62] Fix crud tests --- packages/powersync_core/test/crud_test.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/powersync_core/test/crud_test.dart b/packages/powersync_core/test/crud_test.dart index fefa2c1a..4c18a4b2 100644 --- a/packages/powersync_core/test/crud_test.dart +++ b/packages/powersync_core/test/crud_test.dart @@ -29,7 +29,7 @@ void main() { equals([ { 'data': - '{"op":"PUT","type":"assets","id":"$testId","data":{"description":"test"}}' + '{"op":"PUT","id":"$testId","type":"assets","data":{"description":"test"}}' } ])); @@ -59,7 +59,7 @@ void main() { equals([ { 'data': - '{"op":"PUT","type":"assets","id":"$testId","data":{"description":"test2"}}' + '{"op":"PUT","id":"$testId","type":"assets","data":{"description":"test2"}}' } ])); @@ -91,7 +91,7 @@ void main() { equals([ { 'data': - '{"op":"PATCH","type":"assets","id":"$testId","data":{"description":"test2"}}' + '{"op":"PATCH","id":"$testId","type":"assets","data":{"description":"test2"}}' } ])); @@ -116,7 +116,7 @@ void main() { expect( await powersync.getAll('SELECT data FROM ps_crud ORDER BY id'), equals([ - {'data': '{"op":"DELETE","type":"assets","id":"$testId"}'} + {'data': '{"op":"DELETE","id":"$testId","type":"assets"}'} ])); var tx = (await powersync.getNextCrudTransaction())!; @@ -213,7 +213,7 @@ void main() { equals([ { 'data': - '{"op":"PUT","type":"assets","id":"$testId","data":{"quantity":"$bigNumber"}}' + '{"op":"PUT","id":"$testId","type":"assets","data":{"quantity":"$bigNumber"}}' } ])); @@ -227,7 +227,7 @@ void main() { equals([ { 'data': - '{"op":"PATCH","type":"assets","id":"$testId","data":{"quantity":${bigNumber + 1}}}' + '{"op":"PATCH","id":"$testId","type":"assets","data":{"quantity":${bigNumber + 1}}}' } ])); }); From 3b7343585705577f039c4bd8727aaaf3f8694f20 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 15 May 2025 09:24:04 +0200 Subject: [PATCH 10/62] Start integrating Rust extension --- packages/powersync_core/lib/src/sync/mutable_sync_status.dart | 2 +- packages/powersync_core/lib/src/sync/streaming_sync.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/powersync_core/lib/src/sync/mutable_sync_status.dart b/packages/powersync_core/lib/src/sync/mutable_sync_status.dart index 23e3becb..df49e1c2 100644 --- a/packages/powersync_core/lib/src/sync/mutable_sync_status.dart +++ b/packages/powersync_core/lib/src/sync/mutable_sync_status.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:collection/collection.dart'; +import 'package:powersync_core/src/sync/instruction.dart'; -import 'instruction.dart'; import 'sync_status.dart'; import 'bucket_storage.dart'; import 'protocol.dart'; diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 1f8091b3..452c461d 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -248,6 +248,7 @@ class StreamingSyncImplementation implements StreamingSync { } assert(identical(_activeCrudUpload, completer)); + _nonLineSyncEvents.add(const UploadCompleted()); _activeCrudUpload = null; completer.complete(); }); From 4d171a4873e31f5931f11ad6e57adc1b317fee82 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 15 May 2025 14:29:19 +0200 Subject: [PATCH 11/62] Add new sync implementation --- .../powersync_core/lib/src/sync/options.dart | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/powersync_core/lib/src/sync/options.dart b/packages/powersync_core/lib/src/sync/options.dart index 6ae94b25..2b98730f 100644 --- a/packages/powersync_core/lib/src/sync/options.dart +++ b/packages/powersync_core/lib/src/sync/options.dart @@ -74,6 +74,32 @@ enum SyncClientImplementation { static const defaultClient = dart; } +/// The PowerSync SDK offers two different implementations for receiving sync +/// lines: One handling most logic in Dart, and a newer one offloading that work +/// to the native PowerSync extension. +enum SyncClientImplementation { + /// A sync implementation that decodes and handles sync lines in Dart. + @Deprecated( + "Don't use SyncClientImplementation.dart directly, " + "use SyncClientImplementation.defaultClient instead.", + ) + dart, + + /// An experimental sync implementation that parses and handles sync lines in + /// the native PowerSync core extensions. + /// + /// This implementation can be more performant than the Dart implementation, + /// and supports receiving sync lines in a more efficient format. + /// + /// Note that this option is currently experimental. + @experimental + rust; + + /// The default sync client implementation to use. + // ignore: deprecated_member_use_from_same_package + static const defaultClient = dart; +} + @internal extension type ResolvedSyncOptions(SyncOptions source) { factory ResolvedSyncOptions.resolve( From 1a79bd6596835f6a6d0b6b199c68ae402105b1ea Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 20 May 2025 18:15:42 +0200 Subject: [PATCH 12/62] Properly copy options --- .../powersync_core/lib/src/sync/options.dart | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/packages/powersync_core/lib/src/sync/options.dart b/packages/powersync_core/lib/src/sync/options.dart index 2b98730f..6ae94b25 100644 --- a/packages/powersync_core/lib/src/sync/options.dart +++ b/packages/powersync_core/lib/src/sync/options.dart @@ -74,32 +74,6 @@ enum SyncClientImplementation { static const defaultClient = dart; } -/// The PowerSync SDK offers two different implementations for receiving sync -/// lines: One handling most logic in Dart, and a newer one offloading that work -/// to the native PowerSync extension. -enum SyncClientImplementation { - /// A sync implementation that decodes and handles sync lines in Dart. - @Deprecated( - "Don't use SyncClientImplementation.dart directly, " - "use SyncClientImplementation.defaultClient instead.", - ) - dart, - - /// An experimental sync implementation that parses and handles sync lines in - /// the native PowerSync core extensions. - /// - /// This implementation can be more performant than the Dart implementation, - /// and supports receiving sync lines in a more efficient format. - /// - /// Note that this option is currently experimental. - @experimental - rust; - - /// The default sync client implementation to use. - // ignore: deprecated_member_use_from_same_package - static const defaultClient = dart; -} - @internal extension type ResolvedSyncOptions(SyncOptions source) { factory ResolvedSyncOptions.resolve( From eba06b1be8bc00b2b5557a945cd2131f7e446924 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 09:43:08 +0200 Subject: [PATCH 13/62] Revert intended changes --- packages/powersync_core/lib/src/sync/mutable_sync_status.dart | 2 +- packages/powersync_core/lib/src/sync/streaming_sync.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/powersync_core/lib/src/sync/mutable_sync_status.dart b/packages/powersync_core/lib/src/sync/mutable_sync_status.dart index df49e1c2..23e3becb 100644 --- a/packages/powersync_core/lib/src/sync/mutable_sync_status.dart +++ b/packages/powersync_core/lib/src/sync/mutable_sync_status.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:powersync_core/src/sync/instruction.dart'; +import 'instruction.dart'; import 'sync_status.dart'; import 'bucket_storage.dart'; import 'protocol.dart'; diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 452c461d..1f8091b3 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -248,7 +248,6 @@ class StreamingSyncImplementation implements StreamingSync { } assert(identical(_activeCrudUpload, completer)); - _nonLineSyncEvents.add(const UploadCompleted()); _activeCrudUpload = null; completer.complete(); }); From d1fac5f90587ab82962396e2f6899e9f43504975 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 4 Jun 2025 20:38:52 +0200 Subject: [PATCH 14/62] Fix start invocation --- packages/powersync_core/lib/src/sync/streaming_sync.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 1f8091b3..52e8eaef 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -596,7 +596,12 @@ final class _ActiveRustStreamingIteration { Future syncIteration() async { try { await _control( - 'start', convert.json.encode({'parameters': sync.options.params})); + 'start', + convert.json.encode({ + 'parameters': sync.options.params, + 'schema': 'TODO: Pass-through schema (probably in serialized form)', + }), + ); assert(_completedStream.isCompleted, 'Should have started streaming'); await _completedStream.future; } finally { From 5b27f0e7600b59c3fc999329102a914e18b43c67 Mon Sep 17 00:00:00 2001 From: Jorge Sardina Date: Fri, 6 Jun 2025 14:19:43 +0200 Subject: [PATCH 15/62] raw tables in schema --- .../native/native_powersync_database.dart | 4 ++ .../database/web/web_powersync_database.dart | 1 + packages/powersync_core/lib/src/schema.dart | 59 ++++++++++++++++++- .../lib/src/sync/streaming_sync.dart | 5 +- .../lib/src/web/sync_worker.dart | 1 + .../test/utils/abstract_test_utils.dart | 1 + 6 files changed, 68 insertions(+), 3 deletions(-) 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..af1ac201 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 @@ -247,6 +247,7 @@ class PowerSyncDatabaseImpl options, crudMutex.shared, syncMutex.shared, + schema, ), debugName: 'Sync ${database.openFactory.path}', onError: receiveUnhandledErrors.sendPort, @@ -290,6 +291,7 @@ class _PowerSyncDatabaseIsolateArgs { final ResolvedSyncOptions options; final SerializedMutex crudMutex; final SerializedMutex syncMutex; + final Schema schema; _PowerSyncDatabaseIsolateArgs( this.sPort, @@ -297,6 +299,7 @@ class _PowerSyncDatabaseIsolateArgs { this.options, this.crudMutex, this.syncMutex, + this.schema, ); } @@ -392,6 +395,7 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async { final storage = BucketStorage(connection); final sync = StreamingSyncImplementation( adapter: storage, + schema: args.schema, connector: InternalConnector( getCredentialsCached: getCredentialsCached, prefetchCredentials: prefetchCredentials, 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..fb410caa 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 @@ -141,6 +141,7 @@ class PowerSyncDatabaseImpl sync = StreamingSyncImplementation( adapter: storage, + schema: schema, connector: InternalConnector.wrap(connector, this), crudUpdateTriggerStream: crudStream, options: options, diff --git a/packages/powersync_core/lib/src/schema.dart b/packages/powersync_core/lib/src/schema.dart index 4892ee6c..3f8722ef 100644 --- a/packages/powersync_core/lib/src/schema.dart +++ b/packages/powersync_core/lib/src/schema.dart @@ -8,10 +8,11 @@ import 'schema_logic.dart'; class Schema { /// List of tables in the schema. final List tables; + final List rawTables; - const Schema(this.tables); + const Schema(this.tables, {this.rawTables = const []}); - Map toJson() => {'tables': tables}; + Map toJson() => {'raw_tables': rawTables, 'tables': tables}; void validate() { Set tableNames = {}; @@ -315,6 +316,60 @@ class Column { Map toJson() => {'name': name, 'type': type.sqlite}; } +class RawTable { + final String + name; // TODO: it does not need to be the same name as the raw table + final PendingStatement put; + final PendingStatement delete; + + const RawTable( + this.name, + this.put, + this.delete, + ); + + Map toJson() => { + 'name': name, + 'put': put, + 'delete': delete, + }; +} + +class PendingStatement { + final String sql; + final List params; + + PendingStatement({required this.sql, required this.params}); + + Map toJson() => { + 'sql': sql, + 'params': params, + }; +} + +sealed class PendingStatementValue { + dynamic toJson(); +} + +class PendingStmtValueColumn extends PendingStatementValue { + final String column; + PendingStmtValueColumn(this.column); + + @override + dynamic toJson() { + return { + 'Column': column, + }; + } +} + +class PendingStmtValueId extends PendingStatementValue { + @override + dynamic toJson() { + return 'Id'; + } +} + /// Type of column. enum ColumnType { /// TEXT column. diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 52e8eaef..67572ba9 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; +import 'package:powersync_core/powersync_core.dart'; import 'package:powersync_core/src/abort_controller.dart'; import 'package:powersync_core/src/exceptions.dart'; import 'package:powersync_core/src/log_internal.dart'; @@ -32,6 +33,7 @@ abstract interface class StreamingSync { @internal class StreamingSyncImplementation implements StreamingSync { + final Schema? schema; //TODO(SkillDevs): pass in all implementations final BucketStorage adapter; final InternalConnector connector; final ResolvedSyncOptions options; @@ -62,6 +64,7 @@ class StreamingSyncImplementation implements StreamingSync { String? clientId; StreamingSyncImplementation({ + required this.schema, required this.adapter, required this.connector, required this.crudUpdateTriggerStream, @@ -599,7 +602,7 @@ final class _ActiveRustStreamingIteration { 'start', convert.json.encode({ 'parameters': sync.options.params, - 'schema': 'TODO: Pass-through schema (probably in serialized form)', + 'schema': sync.schema, }), ); assert(_completedStream.isCompleted, 'Should have started streaming'); diff --git a/packages/powersync_core/lib/src/web/sync_worker.dart b/packages/powersync_core/lib/src/web/sync_worker.dart index b5e8ed63..5ee3c4ca 100644 --- a/packages/powersync_core/lib/src/web/sync_worker.dart +++ b/packages/powersync_core/lib/src/web/sync_worker.dart @@ -264,6 +264,7 @@ class _SyncRunner { sync = StreamingSyncImplementation( adapter: WebBucketStorage(database), + schema: null, connector: InternalConnector( getCredentialsCached: client.channel.credentialsCallback, prefetchCredentials: ({required bool invalidate}) async { diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index 5cfb8405..6d282840 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -154,6 +154,7 @@ extension MockSync on PowerSyncDatabase { }) { final impl = StreamingSyncImplementation( adapter: BucketStorage(this), + schema: null, client: client, options: ResolvedSyncOptions(options), connector: InternalConnector.wrap(connector, this), From 2173809811d6dd57b9cde9005ab91b200e632b12 Mon Sep 17 00:00:00 2001 From: David Martos Date: Sat, 7 Jun 2025 01:00:40 +0200 Subject: [PATCH 16/62] fix tests --- packages/powersync_core/test/utils/abstract_test_utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index 6d282840..4529fd51 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -154,7 +154,7 @@ extension MockSync on PowerSyncDatabase { }) { final impl = StreamingSyncImplementation( adapter: BucketStorage(this), - schema: null, + schema: schema, client: client, options: ResolvedSyncOptions(options), connector: InternalConnector.wrap(connector, this), From 64a2e054d8d0ee6009644dc386f4c2019557b689 Mon Sep 17 00:00:00 2001 From: David Martos Date: Sun, 8 Jun 2025 01:21:29 +0200 Subject: [PATCH 17/62] manual schema management --- .../native/native_powersync_database.dart | 37 ++++++++++---- .../lib/src/database/powersync_database.dart | 48 ++++++++++++------- .../powersync_database_impl_stub.dart | 15 ++++-- .../lib/src/database/powersync_db_mixin.dart | 38 ++++++++++++++- .../database/web/web_powersync_database.dart | 28 +++++++++-- 5 files changed, 129 insertions(+), 37 deletions(-) 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 af1ac201..f4be54e3 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 @@ -44,6 +44,9 @@ class PowerSyncDatabaseImpl @override SqliteDatabase database; + @override + bool manualSchemaManagement; + @override @protected late Future isInitialized; @@ -76,6 +79,7 @@ class PowerSyncDatabaseImpl required String path, int maxReaders = SqliteDatabase.defaultMaxReaders, Logger? logger, + bool manualSchemaManagement = false, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package SqliteConnectionSetup? sqliteSetup}) { @@ -83,8 +87,13 @@ 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, + manualSchemaManagement: manualSchemaManagement, + ); } /// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory]. @@ -96,13 +105,19 @@ 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, + bool manualSchemaManagement = false, + }) { final db = SqliteDatabase.withFactory(openFactory, maxReaders: maxReaders); return PowerSyncDatabaseImpl.withDatabase( - schema: schema, database: db, logger: logger); + schema: schema, + database: db, + logger: logger, + manualSchemaManagement: manualSchemaManagement, + ); } /// Open a PowerSyncDatabase on an existing [SqliteDatabase]. @@ -110,8 +125,12 @@ 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.manualSchemaManagement = false, + }) { this.logger = logger ?? autoLogger; isInitialized = baseInit(); } diff --git a/packages/powersync_core/lib/src/database/powersync_database.dart b/packages/powersync_core/lib/src/database/powersync_database.dart index 796bdefc..916e355d 100644 --- a/packages/powersync_core/lib/src/database/powersync_database.dart +++ b/packages/powersync_core/lib/src/database/powersync_database.dart @@ -32,19 +32,23 @@ 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, + bool manualSchemaManagement = false, + @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, + manualSchemaManagement: manualSchemaManagement, + logger: logger, + // ignore: deprecated_member_use_from_same_package + sqliteSetup: sqliteSetup, + ); } /// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory]. @@ -55,12 +59,20 @@ 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, + bool manualSchemaManagement = false, + Logger? logger, + }) { + return PowerSyncDatabaseImpl.withFactory( + openFactory, + schema: schema, + maxReaders: maxReaders, + manualSchemaManagement: manualSchemaManagement, + logger: logger, + ); } /// Open a PowerSyncDatabase on an existing [SqliteDatabase]. 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..ee3ab2af 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 @@ -32,6 +32,9 @@ class PowerSyncDatabaseImpl @override SqliteDatabase get database => throw UnimplementedError(); + @override + bool get manualSchemaManagement => throw UnimplementedError(); + @override Future get isInitialized => throw UnimplementedError(); @@ -53,6 +56,7 @@ class PowerSyncDatabaseImpl {required Schema schema, required String path, int maxReaders = SqliteDatabase.defaultMaxReaders, + bool manualSchemaManagement = false, Logger? logger, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package @@ -72,6 +76,7 @@ class PowerSyncDatabaseImpl DefaultSqliteOpenFactory openFactory, { required Schema schema, int maxReaders = SqliteDatabase.defaultMaxReaders, + bool manualSchemaManagement = false, Logger? logger, }) { throw UnimplementedError(); @@ -82,10 +87,12 @@ 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, + bool manualSchemaManagement = false, + Logger? logger, + }) { throw UnimplementedError(); } 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..ae63823e 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -38,6 +38,10 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Use [attachedLogger] to propagate logs to [Logger.root] for custom logging. Logger get logger; + bool get manualSchemaManagement; + + bool _manualSchemaManagementCompleted = false; + @Deprecated("This field is unused, pass params to connect() instead") Map? clientParams; @@ -110,10 +114,36 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { statusStream = statusStreamController.stream; updates = powerSyncUpdateNotifications(database.updates); + _manualSchemaManagementCompleted = false; + await database.initialize(); await _checkVersion(); await database.execute('SELECT powersync_init()'); - await updateSchema(schema); + + if (!manualSchemaManagement) { + // Create the internal db schema + await updateSchema(schema); + await _afterSchemaReady(); + } + } + + Future markSchemaAsReady() async { + await isInitialized; + _manualSchemaManagementCompleted = true; + + await _afterSchemaReady(); + } + + void _assertSchemaIsReady() { + if (!manualSchemaManagement || _manualSchemaManagementCompleted) { + return; + } + + throw AssertionError( + 'In manual schema management mode, you need to mark the powersync database as ready'); + } + + Future _afterSchemaReady() async { await _updateHasSynced(); } @@ -289,6 +319,8 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { // the lock for the connection. await initialize(); + _assertSchemaIsReady(); + final resolvedOptions = ResolvedSyncOptions.resolve( options, crudThrottleTime: crudThrottleTime, @@ -452,6 +484,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get an unique id for this client. /// This id is only reset when the database is deleted. Future getClientId() async { + _assertSchemaIsReady(); // TODO(skilldevs): Needed? final row = await get('SELECT powersync_client_id() as client_id'); return row['client_id'] as String; } @@ -459,6 +492,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get upload queue size estimate and count. Future getUploadQueueStats( {bool includeSize = false}) async { + _assertSchemaIsReady(); if (includeSize) { final row = await getOptional( 'SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ps_crud'); @@ -486,6 +520,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// data by transaction. One batch may contain data from multiple transactions, /// and a single transaction may be split over multiple batches. Future getCrudBatch({int limit = 100}) async { + _assertSchemaIsReady(); final rows = await getAll( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?', [limit + 1]); @@ -532,6 +567,7 @@ 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 { + _assertSchemaIsReady(); return await readTransaction((tx) async { final first = await tx.getOptional( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1'); 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 fb410caa..8132d8ce 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 @@ -38,6 +38,9 @@ class PowerSyncDatabaseImpl @override SqliteDatabase database; + @override + bool manualSchemaManagement; + @override @protected late Future isInitialized; @@ -69,14 +72,20 @@ class PowerSyncDatabaseImpl {required Schema schema, required String path, int maxReaders = SqliteDatabase.defaultMaxReaders, + bool manualSchemaManagement = false, Logger? logger, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package 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, + manualSchemaManagement: manualSchemaManagement, + ); } /// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory]. @@ -91,10 +100,15 @@ class PowerSyncDatabaseImpl DefaultSqliteOpenFactory openFactory, {required Schema schema, int maxReaders = SqliteDatabase.defaultMaxReaders, + bool manualSchemaManagement = false, Logger? logger}) { final db = SqliteDatabase.withFactory(openFactory, maxReaders: 1); return PowerSyncDatabaseImpl.withDatabase( - schema: schema, logger: logger, database: db); + schema: schema, + manualSchemaManagement: manualSchemaManagement, + logger: logger, + database: db, + ); } /// Open a PowerSyncDatabase on an existing [SqliteDatabase]. @@ -102,8 +116,12 @@ 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, + this.manualSchemaManagement = false, + Logger? logger, + }) { if (logger != null) { this.logger = logger; } else { From d49d567564d31aff53a23bd33bc50738c192c633 Mon Sep 17 00:00:00 2001 From: David Martos Date: Sun, 8 Jun 2025 12:12:20 +0200 Subject: [PATCH 18/62] name params --- packages/powersync_core/lib/src/schema.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/powersync_core/lib/src/schema.dart b/packages/powersync_core/lib/src/schema.dart index 3f8722ef..575aacc6 100644 --- a/packages/powersync_core/lib/src/schema.dart +++ b/packages/powersync_core/lib/src/schema.dart @@ -322,11 +322,11 @@ class RawTable { final PendingStatement put; final PendingStatement delete; - const RawTable( - this.name, - this.put, - this.delete, - ); + const RawTable({ + required this.name, + required this.put, + required this.delete, + }); Map toJson() => { 'name': name, From 0d5b5c9d802bd36978257a178fb7c16e8bd95677 Mon Sep 17 00:00:00 2001 From: Jorge Sardina Date: Wed, 11 Jun 2025 12:22:20 +0200 Subject: [PATCH 19/62] pass schema to sync worker --- .../native/native_powersync_database.dart | 9 ++++--- .../database/web/web_powersync_database.dart | 3 ++- .../lib/src/sync/streaming_sync.dart | 7 +++-- .../lib/src/web/sync_controller.dart | 5 +++- .../lib/src/web/sync_worker.dart | 27 ++++++++++++------- .../lib/src/web/sync_worker_protocol.dart | 6 ++++- .../test/utils/abstract_test_utils.dart | 4 ++- 7 files changed, 40 insertions(+), 21 deletions(-) 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 f4be54e3..94e3669b 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'; @@ -266,7 +267,7 @@ class PowerSyncDatabaseImpl options, crudMutex.shared, syncMutex.shared, - schema, + jsonEncode(schema), ), debugName: 'Sync ${database.openFactory.path}', onError: receiveUnhandledErrors.sendPort, @@ -310,7 +311,7 @@ class _PowerSyncDatabaseIsolateArgs { final ResolvedSyncOptions options; final SerializedMutex crudMutex; final SerializedMutex syncMutex; - final Schema schema; + final String schemaJson; _PowerSyncDatabaseIsolateArgs( this.sPort, @@ -318,7 +319,7 @@ class _PowerSyncDatabaseIsolateArgs { this.options, this.crudMutex, this.syncMutex, - this.schema, + this.schemaJson, ); } @@ -414,7 +415,7 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async { final storage = BucketStorage(connection); final sync = StreamingSyncImplementation( adapter: storage, - schema: args.schema, + schemaJson: args.schemaJson, connector: InternalConnector( getCredentialsCached: getCredentialsCached, prefetchCredentials: prefetchCredentials, 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 8132d8ce..a879d9e0 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'; @@ -159,7 +160,7 @@ class PowerSyncDatabaseImpl sync = StreamingSyncImplementation( adapter: storage, - schema: schema, + schemaJson: jsonEncode(schema), connector: InternalConnector.wrap(connector, this), crudUpdateTriggerStream: crudStream, options: options, diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 67572ba9..bbdcb85a 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -5,7 +5,6 @@ import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:powersync_core/powersync_core.dart'; import 'package:powersync_core/src/abort_controller.dart'; import 'package:powersync_core/src/exceptions.dart'; import 'package:powersync_core/src/log_internal.dart'; @@ -33,7 +32,7 @@ abstract interface class StreamingSync { @internal class StreamingSyncImplementation implements StreamingSync { - final Schema? schema; //TODO(SkillDevs): pass in all implementations + final String schemaJson; final BucketStorage adapter; final InternalConnector connector; final ResolvedSyncOptions options; @@ -64,7 +63,7 @@ class StreamingSyncImplementation implements StreamingSync { String? clientId; StreamingSyncImplementation({ - required this.schema, + required this.schemaJson, required this.adapter, required this.connector, required this.crudUpdateTriggerStream, @@ -602,7 +601,7 @@ final class _ActiveRustStreamingIteration { 'start', convert.json.encode({ 'parameters': sync.options.params, - 'schema': sync.schema, + 'schema': convert.json.decode(sync.schemaJson), }), ); assert(_completedStream.isCompleted, 'Should have started streaming'); diff --git a/packages/powersync_core/lib/src/web/sync_controller.dart b/packages/powersync_core/lib/src/web/sync_controller.dart index 0c26252e..7f05cff3 100644 --- a/packages/powersync_core/lib/src/web/sync_controller.dart +++ b/packages/powersync_core/lib/src/web/sync_controller.dart @@ -113,6 +113,9 @@ class SyncWorkerHandle implements StreamingSync { @override Future streamingSync() async { await _channel.startSynchronization( - database.database.openFactory.path, ResolvedSyncOptions(options)); + database.database.openFactory.path, + ResolvedSyncOptions(options), + database.schema, + ); } } diff --git a/packages/powersync_core/lib/src/web/sync_worker.dart b/packages/powersync_core/lib/src/web/sync_worker.dart index 5ee3c4ca..ddc4eaf0 100644 --- a/packages/powersync_core/lib/src/web/sync_worker.dart +++ b/packages/powersync_core/lib/src/web/sync_worker.dart @@ -45,12 +45,16 @@ class _SyncWorker { }); } - _SyncRunner referenceSyncTask( - String databaseIdentifier, SyncOptions options, _ConnectedClient client) { + _SyncRunner referenceSyncTask(String databaseIdentifier, SyncOptions options, + String schemaJson, _ConnectedClient client) { return _requestedSyncTasks.putIfAbsent(databaseIdentifier, () { return _SyncRunner(databaseIdentifier); }) - ..registerClient(client, options); + ..registerClient( + client, + options, + schemaJson, + ); } } @@ -86,8 +90,8 @@ class _ConnectedClient { }, ); - _runner = _worker.referenceSyncTask( - request.databaseName, recoveredOptions, this); + _runner = _worker.referenceSyncTask(request.databaseName, + recoveredOptions, request.schemaJson, this); return (JSObject(), null); case SyncWorkerMessageType.abortSynchronization: _runner?.disconnectClient(this); @@ -128,6 +132,7 @@ class _ConnectedClient { class _SyncRunner { final String identifier; ResolvedSyncOptions options = ResolvedSyncOptions(SyncOptions()); + String schemaJson = '{}'; final StreamGroup<_RunnerEvent> _group = StreamGroup(); final StreamController<_RunnerEvent> _mainEvents = StreamController(); @@ -146,10 +151,12 @@ class _SyncRunner { case _AddConnection( :final client, :final options, + :final schemaJson, ): connections.add(client); final (newOptions, reconnect) = this.options.applyFrom(options); this.options = newOptions; + this.schemaJson = schemaJson; if (sync == null) { await _requestDatabase(client); @@ -264,7 +271,7 @@ class _SyncRunner { sync = StreamingSyncImplementation( adapter: WebBucketStorage(database), - schema: null, + schemaJson: client._runner!.schemaJson, connector: InternalConnector( getCredentialsCached: client.channel.credentialsCallback, prefetchCredentials: ({required bool invalidate}) async { @@ -287,8 +294,9 @@ class _SyncRunner { sync!.streamingSync(); } - void registerClient(_ConnectedClient client, SyncOptions options) { - _mainEvents.add(_AddConnection(client, options)); + void registerClient( + _ConnectedClient client, SyncOptions options, String schemaJson) { + _mainEvents.add(_AddConnection(client, options, schemaJson)); } /// Remove a client, disconnecting if no clients remain.. @@ -307,8 +315,9 @@ sealed class _RunnerEvent {} final class _AddConnection implements _RunnerEvent { final _ConnectedClient client; final SyncOptions options; + final String schemaJson; - _AddConnection(this.client, this.options); + _AddConnection(this.client, this.options, this.schemaJson); } final class _RemoveConnection implements _RunnerEvent { diff --git a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart index 2b859e53..3c64d90f 100644 --- a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart +++ b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:js_interop'; import 'package:logging/logging.dart'; +import 'package:powersync_core/src/schema.dart'; import 'package:powersync_core/src/sync/options.dart'; import 'package:web/web.dart'; @@ -71,6 +72,7 @@ extension type StartSynchronization._(JSObject _) implements JSObject { required int requestId, required int retryDelayMs, required String implementationName, + required String schemaJson, String? syncParamsEncoded, }); @@ -79,6 +81,7 @@ extension type StartSynchronization._(JSObject _) implements JSObject { external int get crudThrottleTimeMs; external int? get retryDelayMs; external String? get implementationName; + external String get schemaJson; external String? get syncParamsEncoded; } @@ -410,7 +413,7 @@ final class WorkerCommunicationChannel { } Future startSynchronization( - String databaseName, ResolvedSyncOptions options) async { + String databaseName, ResolvedSyncOptions options, Schema schema) async { final (id, completion) = _newRequest(); port.postMessage(SyncWorkerMessage( type: SyncWorkerMessageType.startSynchronization.name, @@ -420,6 +423,7 @@ final class WorkerCommunicationChannel { retryDelayMs: options.retryDelay.inMilliseconds, requestId: id, implementationName: options.source.syncImplementation.name, + schemaJson: jsonEncode(schema), syncParamsEncoded: switch (options.source.params) { null => null, final params => jsonEncode(params), diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index 4529fd51..f2f54d7d 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:powersync_core/powersync_core.dart'; @@ -154,7 +156,7 @@ extension MockSync on PowerSyncDatabase { }) { final impl = StreamingSyncImplementation( adapter: BucketStorage(this), - schema: schema, + schemaJson: jsonEncode(schema), client: client, options: ResolvedSyncOptions(options), connector: InternalConnector.wrap(connector, this), From 5bce2a7c4a81248fae7b7d41e1538804279915e9 Mon Sep 17 00:00:00 2001 From: Jorge Sardina Date: Wed, 11 Jun 2025 12:37:25 +0200 Subject: [PATCH 20/62] review comments --- .../lib/src/database/powersync_db_mixin.dart | 14 +++++++------- packages/powersync_core/lib/src/schema.dart | 18 +++++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) 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 ae63823e..64744797 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -134,12 +134,12 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { await _afterSchemaReady(); } - void _assertSchemaIsReady() { + void _checkSchemaIsReady() { if (!manualSchemaManagement || _manualSchemaManagementCompleted) { return; } - throw AssertionError( + throw StateError( 'In manual schema management mode, you need to mark the powersync database as ready'); } @@ -319,7 +319,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { // the lock for the connection. await initialize(); - _assertSchemaIsReady(); + _checkSchemaIsReady(); final resolvedOptions = ResolvedSyncOptions.resolve( options, @@ -484,7 +484,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get an unique id for this client. /// This id is only reset when the database is deleted. Future getClientId() async { - _assertSchemaIsReady(); // TODO(skilldevs): Needed? + _checkSchemaIsReady(); // TODO(skilldevs): Needed? final row = await get('SELECT powersync_client_id() as client_id'); return row['client_id'] as String; } @@ -492,7 +492,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get upload queue size estimate and count. Future getUploadQueueStats( {bool includeSize = false}) async { - _assertSchemaIsReady(); + _checkSchemaIsReady(); if (includeSize) { final row = await getOptional( 'SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ps_crud'); @@ -520,7 +520,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// data by transaction. One batch may contain data from multiple transactions, /// and a single transaction may be split over multiple batches. Future getCrudBatch({int limit = 100}) async { - _assertSchemaIsReady(); + _checkSchemaIsReady(); final rows = await getAll( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?', [limit + 1]); @@ -567,7 +567,7 @@ 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 { - _assertSchemaIsReady(); + _checkSchemaIsReady(); return await readTransaction((tx) async { final first = await tx.getOptional( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1'); diff --git a/packages/powersync_core/lib/src/schema.dart b/packages/powersync_core/lib/src/schema.dart index 575aacc6..d5273c38 100644 --- a/packages/powersync_core/lib/src/schema.dart +++ b/packages/powersync_core/lib/src/schema.dart @@ -316,9 +316,8 @@ class Column { Map toJson() => {'name': name, 'type': type.sqlite}; } -class RawTable { - final String - name; // TODO: it does not need to be the same name as the raw table +final class RawTable { + final String name; final PendingStatement put; final PendingStatement delete; @@ -335,7 +334,7 @@ class RawTable { }; } -class PendingStatement { +final class PendingStatement { final String sql; final List params; @@ -348,12 +347,15 @@ class PendingStatement { } sealed class PendingStatementValue { + factory PendingStatementValue.id() = _PendingStmtValueId; + factory PendingStatementValue.column(String column) = _PendingStmtValueColumn; + dynamic toJson(); } -class PendingStmtValueColumn extends PendingStatementValue { +class _PendingStmtValueColumn implements PendingStatementValue { final String column; - PendingStmtValueColumn(this.column); + const _PendingStmtValueColumn(this.column); @override dynamic toJson() { @@ -363,7 +365,9 @@ class PendingStmtValueColumn extends PendingStatementValue { } } -class PendingStmtValueId extends PendingStatementValue { +class _PendingStmtValueId implements PendingStatementValue { + const _PendingStmtValueId(); + @override dynamic toJson() { return 'Id'; From f5dcaae4c2265fb14138e05591bffc74d591ebdb Mon Sep 17 00:00:00 2001 From: David Martos Date: Mon, 7 Jul 2025 11:02:52 +0200 Subject: [PATCH 21/62] remove todo --- .../powersync_core/lib/src/database/powersync_db_mixin.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 64744797..ad14dd89 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -484,7 +484,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get an unique id for this client. /// This id is only reset when the database is deleted. Future getClientId() async { - _checkSchemaIsReady(); // TODO(skilldevs): Needed? + _checkSchemaIsReady(); final row = await get('SELECT powersync_client_id() as client_id'); return row['client_id'] as String; } From b44177b76b7362e54a56dc65a2c1b3d3a766cd1e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 7 Jul 2025 17:32:24 +0200 Subject: [PATCH 22/62] Remove manual schema management APIs --- .../native/native_powersync_database.dart | 8 ---- .../lib/src/database/powersync_database.dart | 4 -- .../powersync_database_impl_stub.dart | 6 --- .../lib/src/database/powersync_db_mixin.dart | 38 +------------------ .../database/web/web_powersync_database.dart | 8 ---- .../test/utils/abstract_test_utils.dart | 3 +- 6 files changed, 3 insertions(+), 64 deletions(-) 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 94e3669b..d55b69db 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 @@ -45,9 +45,6 @@ class PowerSyncDatabaseImpl @override SqliteDatabase database; - @override - bool manualSchemaManagement; - @override @protected late Future isInitialized; @@ -80,7 +77,6 @@ class PowerSyncDatabaseImpl required String path, int maxReaders = SqliteDatabase.defaultMaxReaders, Logger? logger, - bool manualSchemaManagement = false, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package SqliteConnectionSetup? sqliteSetup}) { @@ -93,7 +89,6 @@ class PowerSyncDatabaseImpl schema: schema, maxReaders: maxReaders, logger: logger, - manualSchemaManagement: manualSchemaManagement, ); } @@ -110,14 +105,12 @@ class PowerSyncDatabaseImpl required Schema schema, int maxReaders = SqliteDatabase.defaultMaxReaders, Logger? logger, - bool manualSchemaManagement = false, }) { final db = SqliteDatabase.withFactory(openFactory, maxReaders: maxReaders); return PowerSyncDatabaseImpl.withDatabase( schema: schema, database: db, logger: logger, - manualSchemaManagement: manualSchemaManagement, ); } @@ -130,7 +123,6 @@ class PowerSyncDatabaseImpl required this.schema, required this.database, Logger? logger, - this.manualSchemaManagement = false, }) { this.logger = logger ?? autoLogger; isInitialized = baseInit(); diff --git a/packages/powersync_core/lib/src/database/powersync_database.dart b/packages/powersync_core/lib/src/database/powersync_database.dart index 916e355d..4de7ea92 100644 --- a/packages/powersync_core/lib/src/database/powersync_database.dart +++ b/packages/powersync_core/lib/src/database/powersync_database.dart @@ -36,7 +36,6 @@ abstract class PowerSyncDatabase required Schema schema, required String path, Logger? logger, - bool manualSchemaManagement = false, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package SqliteConnectionSetup? sqliteSetup, @@ -44,7 +43,6 @@ abstract class PowerSyncDatabase return PowerSyncDatabaseImpl( schema: schema, path: path, - manualSchemaManagement: manualSchemaManagement, logger: logger, // ignore: deprecated_member_use_from_same_package sqliteSetup: sqliteSetup, @@ -63,14 +61,12 @@ abstract class PowerSyncDatabase DefaultSqliteOpenFactory openFactory, { required Schema schema, int maxReaders = SqliteDatabase.defaultMaxReaders, - bool manualSchemaManagement = false, Logger? logger, }) { return PowerSyncDatabaseImpl.withFactory( openFactory, schema: schema, maxReaders: maxReaders, - manualSchemaManagement: manualSchemaManagement, logger: 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 ee3ab2af..a4f0b419 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 @@ -32,9 +32,6 @@ class PowerSyncDatabaseImpl @override SqliteDatabase get database => throw UnimplementedError(); - @override - bool get manualSchemaManagement => throw UnimplementedError(); - @override Future get isInitialized => throw UnimplementedError(); @@ -56,7 +53,6 @@ class PowerSyncDatabaseImpl {required Schema schema, required String path, int maxReaders = SqliteDatabase.defaultMaxReaders, - bool manualSchemaManagement = false, Logger? logger, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package @@ -76,7 +72,6 @@ class PowerSyncDatabaseImpl DefaultSqliteOpenFactory openFactory, { required Schema schema, int maxReaders = SqliteDatabase.defaultMaxReaders, - bool manualSchemaManagement = false, Logger? logger, }) { throw UnimplementedError(); @@ -90,7 +85,6 @@ class PowerSyncDatabaseImpl factory PowerSyncDatabaseImpl.withDatabase({ required Schema schema, required SqliteDatabase database, - bool manualSchemaManagement = false, Logger? logger, }) { throw UnimplementedError(); 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 ad14dd89..808efc71 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -38,10 +38,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Use [attachedLogger] to propagate logs to [Logger.root] for custom logging. Logger get logger; - bool get manualSchemaManagement; - - bool _manualSchemaManagementCompleted = false; - @Deprecated("This field is unused, pass params to connect() instead") Map? clientParams; @@ -114,36 +110,10 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { statusStream = statusStreamController.stream; updates = powerSyncUpdateNotifications(database.updates); - _manualSchemaManagementCompleted = false; - await database.initialize(); await _checkVersion(); await database.execute('SELECT powersync_init()'); - - if (!manualSchemaManagement) { - // Create the internal db schema - await updateSchema(schema); - await _afterSchemaReady(); - } - } - - Future markSchemaAsReady() async { - await isInitialized; - _manualSchemaManagementCompleted = true; - - await _afterSchemaReady(); - } - - void _checkSchemaIsReady() { - if (!manualSchemaManagement || _manualSchemaManagementCompleted) { - return; - } - - throw StateError( - 'In manual schema management mode, you need to mark the powersync database as ready'); - } - - Future _afterSchemaReady() async { + await updateSchema(schema); await _updateHasSynced(); } @@ -319,8 +289,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { // the lock for the connection. await initialize(); - _checkSchemaIsReady(); - final resolvedOptions = ResolvedSyncOptions.resolve( options, crudThrottleTime: crudThrottleTime, @@ -484,7 +452,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get an unique id for this client. /// This id is only reset when the database is deleted. Future getClientId() async { - _checkSchemaIsReady(); final row = await get('SELECT powersync_client_id() as client_id'); return row['client_id'] as String; } @@ -492,7 +459,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Get upload queue size estimate and count. Future getUploadQueueStats( {bool includeSize = false}) async { - _checkSchemaIsReady(); if (includeSize) { final row = await getOptional( 'SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ps_crud'); @@ -520,7 +486,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// data by transaction. One batch may contain data from multiple transactions, /// and a single transaction may be split over multiple batches. Future getCrudBatch({int limit = 100}) async { - _checkSchemaIsReady(); final rows = await getAll( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?', [limit + 1]); @@ -567,7 +532,6 @@ 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 { - _checkSchemaIsReady(); return await readTransaction((tx) async { final first = await tx.getOptional( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1'); 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 a879d9e0..4af2821e 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 @@ -39,9 +39,6 @@ class PowerSyncDatabaseImpl @override SqliteDatabase database; - @override - bool manualSchemaManagement; - @override @protected late Future isInitialized; @@ -73,7 +70,6 @@ class PowerSyncDatabaseImpl {required Schema schema, required String path, int maxReaders = SqliteDatabase.defaultMaxReaders, - bool manualSchemaManagement = false, Logger? logger, @Deprecated("Use [PowerSyncDatabase.withFactory] instead.") // ignore: deprecated_member_use_from_same_package @@ -85,7 +81,6 @@ class PowerSyncDatabaseImpl maxReaders: maxReaders, logger: logger, schema: schema, - manualSchemaManagement: manualSchemaManagement, ); } @@ -101,12 +96,10 @@ class PowerSyncDatabaseImpl DefaultSqliteOpenFactory openFactory, {required Schema schema, int maxReaders = SqliteDatabase.defaultMaxReaders, - bool manualSchemaManagement = false, Logger? logger}) { final db = SqliteDatabase.withFactory(openFactory, maxReaders: 1); return PowerSyncDatabaseImpl.withDatabase( schema: schema, - manualSchemaManagement: manualSchemaManagement, logger: logger, database: db, ); @@ -120,7 +113,6 @@ class PowerSyncDatabaseImpl PowerSyncDatabaseImpl.withDatabase({ required this.schema, required this.database, - this.manualSchemaManagement = false, Logger? logger, }) { if (logger != null) { diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index f2f54d7d..6a1a90aa 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -153,10 +153,11 @@ extension MockSync on PowerSyncDatabase { PowerSyncBackendConnector connector, { Logger? logger, SyncOptions options = const SyncOptions(retryDelay: Duration(seconds: 5)), + Schema? customSchema, }) { final impl = StreamingSyncImplementation( adapter: BucketStorage(this), - schemaJson: jsonEncode(schema), + schemaJson: jsonEncode(customSchema ?? schema), client: client, options: ResolvedSyncOptions(options), connector: InternalConnector.wrap(connector, this), From a74ec87718260fbf8e661681326f14582edcb432 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 7 Jul 2025 17:48:06 +0200 Subject: [PATCH 23/62] Add smoke test --- .../test/in_memory_sync_test.dart | 100 +++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/packages/powersync_core/test/in_memory_sync_test.dart b/packages/powersync_core/test/in_memory_sync_test.dart index b6432014..4b19b047 100644 --- a/packages/powersync_core/test/in_memory_sync_test.dart +++ b/packages/powersync_core/test/in_memory_sync_test.dart @@ -48,7 +48,7 @@ void _declareTests(String name, SyncOptions options) { var credentialsCallbackCount = 0; Future Function(PowerSyncDatabase) uploadData = (db) async {}; - void createSyncClient() { + void createSyncClient({Schema? schema}) { final (client, server) = inMemoryServer(); server.mount(syncService.router.call); @@ -63,6 +63,7 @@ void _declareTests(String name, SyncOptions options) { ); }, uploadData: (db) => uploadData(db)), options: options, + customSchema: schema, ); addTearDown(() async { @@ -207,6 +208,103 @@ void _declareTests(String name, SyncOptions options) { // because the messages were received in quick succession. expect(commits, 1); }); + } else { + // raw tables are only supported by the rust sync client + test('raw tabkes', () async { + final schema = Schema(const [], rawTables: [ + RawTable( + name: 'lists', + put: PendingStatement( + sql: 'INSERT OR REPLACE INTO lists (id, name) VALUES (?, ?)', + params: [ + PendingStatementValue.id(), + PendingStatementValue.column('name'), + ], + ), + delete: PendingStatement( + sql: 'DELETE FROM lists WHERE id = ?', + params: [ + PendingStatementValue.id(), + ], + ), + ), + ]); + + await database.execute( + 'CREATE TABLE lists (id TEXT NOT NULL PRIMARY KEY, name TEXT);'); + final query = StreamQueue( + database.watch('SELECT * FROM lists', throttle: Duration.zero)); + await expectLater(query, emits(isEmpty)); + + createSyncClient(schema: schema); + await waitForConnection(); + + syncService + ..addLine({ + 'checkpoint': Checkpoint( + lastOpId: '1', + writeCheckpoint: null, + checksums: [ + BucketChecksum(bucket: 'a', priority: 3, checksum: 0) + ], + ) + }) + ..addLine({ + 'data': { + 'bucket': 'a', + 'data': [ + { + 'checksum': 0, + 'data': json.encode({'name': 'custom list'}), + 'op': 'PUT', + 'op_id': '1', + 'object_id': 'my_list', + 'object_type': 'lists' + } + ] + } + }) + ..addLine({ + 'checkpoint_complete': {'last_op_id': '1'} + }); + + await expectLater( + query, + emits([ + {'id': 'my_list', 'name': 'custom list'} + ]), + ); + + syncService + ..addLine({ + 'checkpoint': Checkpoint( + lastOpId: '2', + writeCheckpoint: null, + checksums: [ + BucketChecksum(bucket: 'a', priority: 3, checksum: 0) + ], + ) + }) + ..addLine({ + 'data': { + 'bucket': 'a', + 'data': [ + { + 'checksum': 0, + 'op': 'REMOVE', + 'op_id': '2', + 'object_id': 'my_list', + 'object_type': 'lists' + } + ] + } + }) + ..addLine({ + 'checkpoint_complete': {'last_op_id': '2'} + }); + + await expectLater(query, emits(isEmpty)); + }); } group('partial sync', () { From be49f6a20e81594f66671eb6633bcafe2ed68b72 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 15 Jul 2025 10:46:31 +0200 Subject: [PATCH 24/62] Docs --- .../lib/src/database/powersync_db_mixin.dart | 7 ++ packages/powersync_core/lib/src/schema.dart | 67 +++++++++++++++++++ 2 files changed, 74 insertions(+) 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..dc4b2ddb 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -297,6 +297,13 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { params: params, ); + if (schema.rawTables.isNotEmpty && + resolvedOptions.source.syncImplementation != + SyncClientImplementation.rust) { + throw UnsupportedError( + 'Raw tables are only supported by the Rust client.'); + } + // ignore: deprecated_member_use_from_same_package clientParams = params; var thisConnectAborter = AbortController(); diff --git a/packages/powersync_core/lib/src/schema.dart b/packages/powersync_core/lib/src/schema.dart index d5273c38..1289cae0 100644 --- a/packages/powersync_core/lib/src/schema.dart +++ b/packages/powersync_core/lib/src/schema.dart @@ -7,7 +7,18 @@ 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; + + /// 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 []}); @@ -316,9 +327,45 @@ 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({ @@ -334,8 +381,22 @@ final class RawTable { }; } +/// 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}); @@ -346,8 +407,14 @@ final class PendingStatement { }; } +/// 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(); From 32412c037cb7c828c2af3caaf9237ed246b925e0 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 16 Jul 2025 09:12:04 +0200 Subject: [PATCH 25/62] Fix typo --- packages/powersync_core/test/in_memory_sync_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/powersync_core/test/in_memory_sync_test.dart b/packages/powersync_core/test/in_memory_sync_test.dart index 4b19b047..1819795d 100644 --- a/packages/powersync_core/test/in_memory_sync_test.dart +++ b/packages/powersync_core/test/in_memory_sync_test.dart @@ -210,7 +210,7 @@ void _declareTests(String name, SyncOptions options) { }); } else { // raw tables are only supported by the rust sync client - test('raw tabkes', () async { + test('raw tables', () async { final schema = Schema(const [], rawTables: [ RawTable( name: 'lists', From 82007dc120be8356dcba6fb8f5fbcbe927105746 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 16 Jul 2025 10:31:04 +0200 Subject: [PATCH 26/62] Attempt to make watch test less flaky --- packages/powersync_core/test/watch_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/powersync_core/test/watch_test.dart b/packages/powersync_core/test/watch_test.dart index 00e7ea49..6a026922 100644 --- a/packages/powersync_core/test/watch_test.dart +++ b/packages/powersync_core/test/watch_test.dart @@ -53,7 +53,7 @@ void main() { 'INSERT INTO customers(id, name) VALUES (?, ?)', [id, 'a customer']); var done = false; - inserts() async { + Future inserts() async { while (!done) { await powersync.execute( 'INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', @@ -65,7 +65,7 @@ void main() { const numberOfQueries = 10; - inserts(); + final insertsFuture = inserts(); try { List times = []; final results = await stream.take(numberOfQueries).map((e) { @@ -100,6 +100,8 @@ void main() { } finally { done = true; } + + await insertsFuture; }); test('onChange', () async { From ffd5bf23a71ab4177378523eacd36fe4838e21cd Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 16 Jul 2025 10:32:08 +0200 Subject: [PATCH 27/62] And another one --- packages/powersync_core/test/watch_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/powersync_core/test/watch_test.dart b/packages/powersync_core/test/watch_test.dart index 6a026922..28617e2e 100644 --- a/packages/powersync_core/test/watch_test.dart +++ b/packages/powersync_core/test/watch_test.dart @@ -113,7 +113,7 @@ void main() { const throttleDuration = Duration(milliseconds: baseTime); var done = false; - inserts() async { + Future inserts() async { while (!done) { await powersync.execute( 'INSERT INTO assets(id, make) VALUES (uuid(), ?)', ['test']); @@ -122,7 +122,7 @@ void main() { } } - inserts(); + final insertsFuture = inserts(); final stream = powersync.onChange({'assets', 'customers'}, throttle: throttleDuration).asyncMap((event) async { @@ -140,6 +140,7 @@ void main() { UpdateNotification.single('assets'), UpdateNotification.single('assets') ])); + await insertsFuture; }); test('emits update events with friendly names', () async { From 899245d92edec2e9655fb997e758ea6b8f851f57 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 17 Jul 2025 10:18:41 +0200 Subject: [PATCH 28/62] chore(release): publish packages - powersync_core@1.5.0 - powersync@1.15.0 - powersync_sqlcipher@0.1.10 - powersync_attachments_helper@0.6.18+11 - powersync_flutter_libs@0.4.10 --- CHANGELOG.md | 38 +++++++++++++++++++ demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/pubspec.yaml | 2 +- demos/firebase-nodejs-todolist/pubspec.yaml | 2 +- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/pubspec.yaml | 2 +- demos/supabase-todolist-drift/pubspec.yaml | 4 +- .../pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 4 +- demos/supabase-trello/pubspec.yaml | 2 +- packages/powersync/CHANGELOG.md | 6 +++ packages/powersync/pubspec.yaml | 6 +-- .../powersync_attachments_helper/CHANGELOG.md | 4 ++ .../powersync_attachments_helper/pubspec.yaml | 4 +- packages/powersync_core/CHANGELOG.md | 6 +++ packages/powersync_core/lib/src/version.dart | 2 +- packages/powersync_core/pubspec.yaml | 2 +- packages/powersync_flutter_libs/CHANGELOG.md | 4 ++ packages/powersync_flutter_libs/pubspec.yaml | 2 +- packages/powersync_sqlcipher/CHANGELOG.md | 4 ++ .../powersync_sqlcipher/example/pubspec.yaml | 2 +- packages/powersync_sqlcipher/pubspec.yaml | 6 +-- 23 files changed, 86 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f209e57f..e8ffa2cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,44 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 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 diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml index 930b5222..d2bb82f6 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.14.1 + powersync: ^1.15.0 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index 9afbba59..77d13739 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.14.1 + powersync: ^1.15.0 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml index 659d7002..e86b714a 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.1 + powersync: ^1.15.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index 2432eb04..7d4604d3 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.14.1 + powersync: ^1.15.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 1b3dd80a..4d681973 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.14.1 + powersync: ^1.15.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml index dfd00018..15afa2fd 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.1 + powersync: ^1.15.0 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 0f25ddf5..3773b612 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+10 - powersync: ^1.14.1 + powersync_attachments_helper: ^0.6.18+11 + powersync: ^1.15.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index 09c95e16..57ddebec 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.1 + powersync: ^1.15.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index 9230244e..725e4d64 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+10 - powersync: ^1.14.1 + powersync_attachments_helper: ^0.6.18+11 + powersync: ^1.15.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index 4e066ad6..afaae23f 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: random_name_generator: ^1.5.0 flutter_dotenv: ^5.2.1 logging: ^1.3.0 - powersync: ^1.14.1 + powersync: ^1.15.0 sqlite_async: ^0.11.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md index c2bcd63b..b901657d 100644 --- a/packages/powersync/CHANGELOG.md +++ b/packages/powersync/CHANGELOG.md @@ -1,3 +1,9 @@ +## 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. diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml index 4b6edf3b..268cf642 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync -version: 1.14.1 +version: 1.15.0 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 @@ -12,8 +12,8 @@ dependencies: sdk: flutter sqlite3_flutter_libs: ^0.5.23 - powersync_core: ^1.4.1 - powersync_flutter_libs: ^0.4.9 + powersync_core: ^1.5.0 + powersync_flutter_libs: ^0.4.10 collection: ^1.17.0 dev_dependencies: diff --git a/packages/powersync_attachments_helper/CHANGELOG.md b/packages/powersync_attachments_helper/CHANGELOG.md index 637e059d..ab5b00e6 100644 --- a/packages/powersync_attachments_helper/CHANGELOG.md +++ b/packages/powersync_attachments_helper/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.18+11 + + - Update a dependency to the latest release. + ## 0.6.18+10 - Update a dependency to the latest release. diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index 96989012..c920068a 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+10 +version: 0.6.18+11 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ environment: @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.4.1 + powersync_core: ^1.5.0 logging: ^1.2.0 sqlite_async: ^0.11.0 path_provider: ^2.0.13 diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index 22ae6ca2..ee22df4d 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,9 @@ +## 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. diff --git a/packages/powersync_core/lib/src/version.dart b/packages/powersync_core/lib/src/version.dart index b797baa0..e57575ab 100644 --- a/packages/powersync_core/lib/src/version.dart +++ b/packages/powersync_core/lib/src/version.dart @@ -1 +1 @@ -const String libraryVersion = '1.4.1'; +const String libraryVersion = '1.5.0'; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 0a01853c..f82d1c9a 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_core -version: 1.4.1 +version: 1.5.0 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Dart SDK - sync engine for building local-first apps. diff --git a/packages/powersync_flutter_libs/CHANGELOG.md b/packages/powersync_flutter_libs/CHANGELOG.md index f56c0102..0dc93482 100644 --- a/packages/powersync_flutter_libs/CHANGELOG.md +++ b/packages/powersync_flutter_libs/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.10 + + - Update PowerSync core extension to version 0.4.2. + ## 0.4.9 - Update PowerSync core extension to version 0.4.0. diff --git a/packages/powersync_flutter_libs/pubspec.yaml b/packages/powersync_flutter_libs/pubspec.yaml index cdc50d4d..24fa7e01 100644 --- a/packages/powersync_flutter_libs/pubspec.yaml +++ b/packages/powersync_flutter_libs/pubspec.yaml @@ -1,6 +1,6 @@ name: powersync_flutter_libs description: PowerSync core binaries for the PowerSync Flutter SDK. Needs to be included for Flutter apps. -version: 0.4.9 +version: 0.4.10 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ diff --git a/packages/powersync_sqlcipher/CHANGELOG.md b/packages/powersync_sqlcipher/CHANGELOG.md index aafa82a1..08d525b8 100644 --- a/packages/powersync_sqlcipher/CHANGELOG.md +++ b/packages/powersync_sqlcipher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.10 + + - raw tables + ## 0.1.9 - Rust client: Fix uploading local writes after reconnect. diff --git a/packages/powersync_sqlcipher/example/pubspec.yaml b/packages/powersync_sqlcipher/example/pubspec.yaml index ea7ad7bb..a86b0215 100644 --- a/packages/powersync_sqlcipher/example/pubspec.yaml +++ b/packages/powersync_sqlcipher/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - powersync_sqlcipher: ^0.1.9 + powersync_sqlcipher: ^0.1.10 dev_dependencies: flutter_test: diff --git a/packages/powersync_sqlcipher/pubspec.yaml b/packages/powersync_sqlcipher/pubspec.yaml index 5951158a..0cee52bf 100644 --- a/packages/powersync_sqlcipher/pubspec.yaml +++ b/packages/powersync_sqlcipher/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_sqlcipher -version: 0.1.9 +version: 0.1.10 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Flutter SDK - sync engine for building local-first apps. @@ -12,8 +12,8 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.4.1 - powersync_flutter_libs: ^0.4.9 + powersync_core: ^1.5.0 + powersync_flutter_libs: ^0.4.10 sqlcipher_flutter_libs: ^0.6.4 sqlite3_web: ^0.3.0 From 7f126f96073e0c296be9357790db3c7735e625d3 Mon Sep 17 00:00:00 2001 From: dean-journeyapps Date: Tue, 22 Jul 2025 10:29:01 +0200 Subject: [PATCH 29/62] Update android package name for supabase-todolist --- demos/benchmarks/pubspec.lock | 6 +- demos/django-todolist/pubspec.lock | 6 +- demos/firebase-nodejs-todolist/pubspec.lock | 6 +- demos/supabase-anonymous-auth/pubspec.lock | 6 +- .../supabase-edge-function-auth/pubspec.lock | 6 +- demos/supabase-simple-chat/pubspec.lock | 6 +- demos/supabase-todolist-drift/pubspec.lock | 8 +- .../pubspec.lock | 6 +- demos/supabase-todolist/.metadata | 45 ++ demos/supabase-todolist/android/.gitignore | 3 +- .../android/app/build.gradle | 70 -- .../android/app/build.gradle.kts | 44 ++ .../android/app/src/debug/AndroidManifest.xml | 7 +- .../android/app/src/main/AndroidManifest.xml | 19 +- .../co/powersync/demotodolist/MainActivity.kt | 6 - .../powersync_flutter_demo/MainActivity.kt | 5 + .../app/src/profile/AndroidManifest.xml | 7 +- demos/supabase-todolist/android/build.gradle | 31 - .../android/build.gradle.kts | 21 + .../android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../supabase-todolist/android/settings.gradle | 11 - .../android/settings.gradle.kts | 25 + .../xcshareddata/xcschemes/Runner.xcscheme | 2 + .../ios/RunnerTests/RunnerTests.swift | 12 + .../linux/runner/CMakeLists.txt | 26 + demos/supabase-todolist/linux/runner/main.cc | 6 + .../linux/runner/my_application.cc | 130 ++++ .../linux/runner/my_application.h | 18 + .../macos/RunnerTests/RunnerTests.swift | 12 + demos/supabase-todolist/pubspec.lock | 17 +- demos/supabase-trello/pubspec.lock | 6 +- .../powersync_attachments_stream/pubspec.lock | 669 ++++++++++++++++++ .../powersync_sqlcipher/example/pubspec.lock | 31 +- 34 files changed, 1095 insertions(+), 182 deletions(-) create mode 100644 demos/supabase-todolist/.metadata delete mode 100644 demos/supabase-todolist/android/app/build.gradle create mode 100644 demos/supabase-todolist/android/app/build.gradle.kts delete mode 100644 demos/supabase-todolist/android/app/src/main/kotlin/co/powersync/demotodolist/MainActivity.kt create mode 100644 demos/supabase-todolist/android/app/src/main/kotlin/com/powersync/demo/flutter/supabase_todolist/powersync_flutter_demo/MainActivity.kt delete mode 100644 demos/supabase-todolist/android/build.gradle create mode 100644 demos/supabase-todolist/android/build.gradle.kts delete mode 100644 demos/supabase-todolist/android/settings.gradle create mode 100644 demos/supabase-todolist/android/settings.gradle.kts create mode 100644 demos/supabase-todolist/ios/RunnerTests/RunnerTests.swift create mode 100644 demos/supabase-todolist/linux/runner/CMakeLists.txt create mode 100644 demos/supabase-todolist/linux/runner/main.cc create mode 100644 demos/supabase-todolist/linux/runner/my_application.cc create mode 100644 demos/supabase-todolist/linux/runner/my_application.h create mode 100644 demos/supabase-todolist/macos/RunnerTests/RunnerTests.swift create mode 100644 packages/powersync_attachments_stream/pubspec.lock diff --git a/demos/benchmarks/pubspec.lock b/demos/benchmarks/pubspec.lock index 78420348..026996f9 100644 --- a/demos/benchmarks/pubspec.lock +++ b/demos/benchmarks/pubspec.lock @@ -281,21 +281,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.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/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/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-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-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-todolist-drift/pubspec.lock b/demos/supabase-todolist-drift/pubspec.lock index 1a2be52e..8a463ae8 100644 --- a/demos/supabase-todolist-drift/pubspec.lock +++ b/demos/supabase-todolist-drift/pubspec.lock @@ -742,28 +742,28 @@ packages: path: "../../packages/powersync" relative: true source: path - version: "1.13.0" + version: "1.15.0" powersync_attachments_helper: dependency: "direct main" description: path: "../../packages/powersync_attachments_helper" relative: true source: path - version: "0.6.18+7" + version: "0.6.18+11" 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.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/.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/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"> +#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/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..cea2023c 100644 --- a/demos/supabase-todolist/pubspec.lock +++ b/demos/supabase-todolist/pubspec.lock @@ -462,28 +462,28 @@ packages: path: "../../packages/powersync" relative: true source: path - version: "1.13.0" + version: "1.15.0" powersync_attachments_helper: dependency: "direct main" description: path: "../../packages/powersync_attachments_helper" relative: true source: path - version: "0.6.18+7" + version: "0.6.18+11" 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: @@ -628,10 +628,11 @@ packages: 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: "9332aedd311a19dd215dcb55729bc68dc587dc7655b569ab8819b68ee0be0082" + url: "https://pub.dev" + source: hosted + version: "0.11.7" stack_trace: dependency: transitive description: 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/packages/powersync_attachments_stream/pubspec.lock b/packages/powersync_attachments_stream/pubspec.lock new file mode 100644 index 00000000..ae003e13 --- /dev/null +++ b/packages/powersync_attachments_stream/pubspec.lock @@ -0,0 +1,669 @@ +# 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: b1ade5707ab7a90dfd519eaac78a7184341d19adb6096c68d499b59c7c6cf880 + url: "https://pub.dev" + source: hosted + version: "7.7.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + 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: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + 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" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + 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" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.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: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + 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: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mutex: + dependency: transitive + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + 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: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + 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" + powersync_core: + dependency: "direct main" + description: + name: powersync_core + sha256: d8ae292bc77f0a96f44c6cc2911d1b781760a87a919e5045e75458cba83bb759 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + 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: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "608b56d594e4c8498c972c8f1507209f9fd74939971b948ddbbfbfd1c9cb3c15" + url: "https://pub.dev" + source: hosted + version: "2.7.7" + sqlite3_web: + dependency: transitive + description: + name: sqlite3_web + sha256: "967e076442f7e1233bd7241ca61f3efe4c7fc168dac0f38411bdb3bdf471eb3c" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + sqlite_async: + dependency: "direct main" + description: + name: sqlite_async + sha256: "9332aedd311a19dd215dcb55729bc68dc587dc7655b569ab8819b68ee0be0082" + url: "https://pub.dev" + source: hosted + version: "0.11.7" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + url: "https://pub.dev" + source: hosted + version: "1.25.15" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + 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: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.8.1 <4.0.0" + flutter: ">=3.27.0" diff --git a/packages/powersync_sqlcipher/example/pubspec.lock b/packages/powersync_sqlcipher/example/pubspec.lock index 068a9f39..e43cc1be 100644 --- a/packages/powersync_sqlcipher/example/pubspec.lock +++ b/packages/powersync_sqlcipher/example/pubspec.lock @@ -315,29 +315,26 @@ packages: source: hosted version: "2.1.8" powersync_core: - dependency: transitive + dependency: "direct overridden" description: - name: powersync_core - sha256: ad6ffccb5c1e89a9391124a63cd94698946c9bb440a1205725b574c5766b102d - url: "https://pub.dev" - source: hosted - version: "1.3.1" + path: "../../powersync_core" + relative: true + source: path + version: "1.5.0" powersync_flutter_libs: - dependency: transitive + dependency: "direct overridden" description: - name: powersync_flutter_libs - sha256: f1cd9f3a084a39007dd2fb414b03fcc29ab61f0af5d117263fe5f54e407d97d7 - url: "https://pub.dev" - source: hosted - version: "0.4.8" + path: "../../powersync_flutter_libs" + relative: true + source: path + version: "0.4.10" powersync_sqlcipher: dependency: "direct main" description: - name: powersync_sqlcipher - sha256: "33b3d67feab5d4dbf484e2c49e11d7b16f7bd406e70e974a2bbbe439ebc382c4" - url: "https://pub.dev" - source: hosted - version: "0.1.7" + path: ".." + relative: true + source: path + version: "0.1.10" process: dependency: transitive description: From 3a3253188f07c890a78166ef23a134599feedc3b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 22 Jul 2025 10:59:34 +0200 Subject: [PATCH 30/62] Use new error response format to parse errors (#309) * Fix parsing errors * Another test for recovering from header * Another one on parsing details --- .../powersync_core/lib/src/exceptions.dart | 50 ++++++++++++----- .../powersync_core/test/exceptions_test.dart | 56 +++++++++++++++++++ 2 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 packages/powersync_core/test/exceptions_test.dart diff --git a/packages/powersync_core/lib/src/exceptions.dart b/packages/powersync_core/lib/src/exceptions.dart index e4f7c864..bc35df4a 100644 --- a/packages/powersync_core/lib/src/exceptions.dart +++ b/packages/powersync_core/lib/src/exceptions.dart @@ -61,7 +61,12 @@ class SyncResponseException implements Exception { static SyncResponseException _fromResponseBody( http.BaseResponse response, String body) { final decoded = convert.jsonDecode(body); - final details = _stringOrFirst(decoded['error']?['details']) ?? body; + final details = switch (decoded['error']) { + final Map details => _errorDescription(details), + _ => null, + } ?? + body; + final message = '${response.reasonPhrase ?? "Request failed"}: $details'; return SyncResponseException(response.statusCode, message); } @@ -73,6 +78,37 @@ class SyncResponseException implements Exception { ); } + /// Extracts an error description from an error resonse looking like + /// `{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError"}`. + static String? _errorDescription(Map raw) { + final code = raw['code']; // Required, string + final description = raw['description']; // Required, string + + final name = raw['name']; // Optional, string + final details = raw['details']; // Optional, string + + if (code is! String || description is! String) { + return null; + } + + final fullDescription = StringBuffer(code); + if (name is String) { + fullDescription.write('($name)'); + } + + fullDescription + ..write(': ') + ..write(description); + + if (details is String) { + fullDescription + ..write(', ') + ..write(details); + } + + return fullDescription.toString(); + } + int statusCode; String description; @@ -84,18 +120,6 @@ class SyncResponseException implements Exception { } } -String? _stringOrFirst(Object? details) { - if (details == null) { - return null; - } else if (details is String) { - return details; - } else if (details case [final String first, ...]) { - return first; - } else { - return null; - } -} - class PowersyncNotReadyException implements Exception { /// @nodoc PowersyncNotReadyException(this.message); diff --git a/packages/powersync_core/test/exceptions_test.dart b/packages/powersync_core/test/exceptions_test.dart new file mode 100644 index 00000000..d63a152d --- /dev/null +++ b/packages/powersync_core/test/exceptions_test.dart @@ -0,0 +1,56 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:powersync_core/src/exceptions.dart'; +import 'package:test/test.dart'; + +void main() { + group('SyncResponseException', () { + const errorResponse = + '{"error":{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError"}}'; + + test('fromStreamedResponse', () async { + final exc = await SyncResponseException.fromStreamedResponse( + StreamedResponse(Stream.value(utf8.encode(errorResponse)), 401)); + + expect(exc.statusCode, 401); + expect(exc.description, + 'Request failed: PSYNC_S2106(AuthorizationError): Authentication required'); + }); + + test('fromResponse', () { + final exc = + SyncResponseException.fromResponse(Response(errorResponse, 401)); + expect(exc.statusCode, 401); + expect(exc.description, + 'Request failed: PSYNC_S2106(AuthorizationError): Authentication required'); + }); + + test('with description', () { + const errorResponse = + '{"error":{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError", "details": "Missing authorization header"}}'; + + final exc = + SyncResponseException.fromResponse(Response(errorResponse, 401)); + expect(exc.statusCode, 401); + expect(exc.description, + 'Request failed: PSYNC_S2106(AuthorizationError): Authentication required, Missing authorization header'); + }); + + test('malformed', () { + const malformed = + '{"message":"Route GET:/foo/bar not found","error":"Not Found","statusCode":404}'; + + final exc = SyncResponseException.fromResponse(Response(malformed, 401)); + expect(exc.statusCode, 401); + expect(exc.description, + 'Request failed: {"message":"Route GET:/foo/bar not found","error":"Not Found","statusCode":404}'); + + final exc2 = SyncResponseException.fromResponse(Response( + 'not even json', 500, + reasonPhrase: 'Internal server error')); + expect(exc2.statusCode, 500); + expect(exc2.description, 'Internal server error'); + }); + }); +} From 599189c747cc3ad5c5cc12d2b85f28fc7c9d167e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 30 Jul 2025 15:57:23 +0200 Subject: [PATCH 31/62] Update `package:sqlite3` for wasm build (#313) --- packages/sqlite3_wasm_build/build.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index fc414ec7..b2556e6c 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -1,7 +1,7 @@ #!/bin/sh set -e -SQLITE_VERSION="2.7.6" +SQLITE_VERSION="2.8.0" POWERSYNC_CORE_VERSION="0.4.2" SQLITE_PATH="sqlite3.dart" @@ -16,7 +16,6 @@ cd $SQLITE_PATH git apply ../patches/* cd "sqlite3/" -dart pub get # We need the analyzer dependency resolved to extract required symbols cmake -Dwasi_sysroot=/opt/homebrew/share/wasi-sysroot \ -Dclang=/opt/homebrew/opt/llvm/bin/clang\ From 25b9c550b4b51ec4ad2fda0263d197597fcdf81b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 31 Jul 2025 09:52:57 +0200 Subject: [PATCH 32/62] Rust client: Request sync lines as BSON (#312) --- .../lib/src/sync/stream_utils.dart | 99 +++++++++++++++++++ .../lib/src/sync/streaming_sync.dart | 37 ++++--- packages/powersync_core/pubspec.yaml | 2 + .../test/in_memory_sync_test.dart | 35 ++++--- .../sync_server/in_memory_sync_server.dart | 40 ++++++-- 5 files changed, 183 insertions(+), 30 deletions(-) diff --git a/packages/powersync_core/lib/src/sync/stream_utils.dart b/packages/powersync_core/lib/src/sync/stream_utils.dart index a4689cf9..0c28eebf 100644 --- a/packages/powersync_core/lib/src/sync/stream_utils.dart +++ b/packages/powersync_core/lib/src/sync/stream_utils.dart @@ -1,6 +1,12 @@ import 'dart:async'; import 'dart:convert' as convert; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:typed_data/typed_buffers.dart'; + +import '../exceptions.dart'; /// Inject a broadcast stream into a normal stream. Stream addBroadcast(Stream a, Stream broadcast) { @@ -75,6 +81,11 @@ extension ByteStreamToLines on Stream> { final textInput = transform(convert.utf8.decoder); return textInput.transform(const convert.LineSplitter()); } + + /// Splits this stream into BSON documents without parsing them. + Stream get bsonDocuments { + return Stream.eventTransformed(this, _BsonSplittingSink.new); + } } extension StreamToJson on Stream { @@ -99,3 +110,91 @@ Future cancelAll(List> subscriptions) async { final futures = subscriptions.map((sub) => sub.cancel()); await Future.wait(futures); } + +/// An [EventSink] that takes raw bytes as inputs, buffers them internally by +/// reading a 4-byte length prefix for each message and then emits them as +/// chunks. +final class _BsonSplittingSink implements EventSink> { + final EventSink _downstream; + + final length = ByteData(4); + int remainingBytes = 4; + + Uint8Buffer? pendingBuffer; + + _BsonSplittingSink(this._downstream); + + @override + void add(List data) { + var i = 0; + while (i < data.length) { + final availableInData = data.length - i; + + if (pendingBuffer case final pending?) { + // We're in the middle of reading a document + final bytesToRead = min(availableInData, remainingBytes); + pending.addAll(data, i, i + bytesToRead); + i += bytesToRead; + remainingBytes -= bytesToRead; + assert(remainingBytes >= 0); + + if (remainingBytes == 0) { + _downstream.add(pending.buffer + .asUint8List(pending.offsetInBytes, pending.lengthInBytes)); + + // Prepare reading another document, starting with its length + pendingBuffer = null; + remainingBytes = 4; + } + } else { + final bytesToRead = min(availableInData, remainingBytes); + final lengthAsUint8List = length.buffer.asUint8List(); + + lengthAsUint8List.setRange( + 4 - remainingBytes, + 4 - remainingBytes + bytesToRead, + data, + i, + ); + i += bytesToRead; + remainingBytes -= bytesToRead; + assert(remainingBytes >= 0); + + if (remainingBytes == 0) { + // Transition from reading length header to reading document. + // Subtracting 4 because the length of the header is included in the + // length. + remainingBytes = length.getInt32(0, Endian.little) - 4; + if (remainingBytes < 5) { + _downstream.addError( + PowerSyncProtocolException( + 'Invalid length for bson: $remainingBytes'), + StackTrace.current, + ); + } + + pendingBuffer = Uint8Buffer()..addAll(lengthAsUint8List); + } + } + } + + assert(i == data.length); + } + + @override + void addError(Object error, [StackTrace? stackTrace]) { + _downstream.addError(error, stackTrace); + } + + @override + void close() { + if (pendingBuffer != null || remainingBytes != 4) { + _downstream.addError( + PowerSyncProtocolException('Pending data when stream was closed'), + StackTrace.current, + ); + } + + _downstream.close(); + } +} diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index bbdcb85a..88125ce3 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -506,7 +506,11 @@ class StreamingSyncImplementation implements StreamingSync { } } - Future _postStreamRequest(Object? data) async { + Future _postStreamRequest( + Object? data, bool acceptBson) async { + const ndJson = 'application/x-ndjson'; + const bson = 'application/vnd.powersync.bson-stream'; + final credentials = await connector.getCredentialsCached(); if (credentials == null) { throw CredentialsException('Not logged in'); @@ -516,6 +520,8 @@ class StreamingSyncImplementation implements StreamingSync { final request = http.Request('POST', uri); request.headers['Content-Type'] = 'application/json'; request.headers['Authorization'] = "Token ${credentials.token}"; + request.headers['Accept'] = + acceptBson ? '$bson;q=0.9,$ndJson;q=0.8' : ndJson; request.headers.addAll(_userAgentHeaders); request.body = convert.jsonEncode(data); @@ -535,18 +541,13 @@ class StreamingSyncImplementation implements StreamingSync { return res; } - Stream _rawStreamingSyncRequest(Object? data) async* { - final response = await _postStreamRequest(data); - if (response != null) { - yield* response.stream.lines; - } - } - Stream _streamingSyncRequest(StreamingSyncRequest data) { - return _rawStreamingSyncRequest(data) - .parseJson - .cast>() - .transform(StreamingSyncLine.reader); + return Stream.fromFuture(_postStreamRequest(data, false)) + .asyncExpand((response) { + return response?.stream.lines.parseJson + .cast>() + .transform(StreamingSyncLine.reader); + }); } /// Delays the standard `retryDelay` Duration, but exits early if @@ -614,7 +615,17 @@ final class _ActiveRustStreamingIteration { } Stream _receiveLines(Object? data) { - return sync._rawStreamingSyncRequest(data).map(ReceivedLine.new); + return Stream.fromFuture(sync._postStreamRequest(data, true)) + .asyncExpand((response) { + if (response == null) { + return null; + } else { + final contentType = response.headers['content-type']; + final isBson = contentType == 'application/vnd.powersync.bson-stream'; + + return isBson ? response.stream.bsonDocuments : response.stream.lines; + } + }).map(ReceivedLine.new); } Future _handleLines(EstablishSyncStream request) async { diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index f82d1c9a..266f9f46 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: pub_semver: ^2.0.0 pubspec_parse: ^1.3.0 path: ^1.8.0 + typed_data: ^1.4.0 dev_dependencies: lints: ^5.1.1 @@ -38,6 +39,7 @@ dev_dependencies: shelf_static: ^1.1.2 stream_channel: ^2.1.2 fake_async: ^1.3.3 + bson: ^5.0.7 platforms: android: diff --git a/packages/powersync_core/test/in_memory_sync_test.dart b/packages/powersync_core/test/in_memory_sync_test.dart index 1819795d..57e36b0c 100644 --- a/packages/powersync_core/test/in_memory_sync_test.dart +++ b/packages/powersync_core/test/in_memory_sync_test.dart @@ -19,20 +19,33 @@ void main() { _declareTests( 'dart sync client', SyncOptions( - // ignore: deprecated_member_use_from_same_package - syncImplementation: SyncClientImplementation.dart, - retryDelay: Duration(milliseconds: 200)), + // ignore: deprecated_member_use_from_same_package + syncImplementation: SyncClientImplementation.dart, + retryDelay: Duration(milliseconds: 200), + ), + false, ); - _declareTests( - 'rust sync client', - SyncOptions( - syncImplementation: SyncClientImplementation.rust, - retryDelay: Duration(milliseconds: 200)), - ); + group('rust sync client', () { + _declareTests( + 'json', + SyncOptions( + syncImplementation: SyncClientImplementation.rust, + retryDelay: Duration(milliseconds: 200)), + false, + ); + + _declareTests( + 'bson', + SyncOptions( + syncImplementation: SyncClientImplementation.rust, + retryDelay: Duration(milliseconds: 200)), + true, + ); + }); } -void _declareTests(String name, SyncOptions options) { +void _declareTests(String name, SyncOptions options, bool bson) { final ignoredLogger = Logger.detached('powersync.test')..level = Level.OFF; group(name, () { @@ -74,7 +87,7 @@ void _declareTests(String name, SyncOptions options) { setUp(() async { logger = Logger.detached('powersync.active')..level = Level.ALL; credentialsCallbackCount = 0; - syncService = MockSyncService(); + syncService = MockSyncService(useBson: bson); factory = await testUtils.testFactory(); (raw, database) = await factory.openInMemoryDatabase(); diff --git a/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart b/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart index 6bea9454..19d7e137 100644 --- a/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart +++ b/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart @@ -1,12 +1,17 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; +import 'package:bson/bson.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; final class MockSyncService { + final bool useBson; + // Use a queued stream to make tests easier. - StreamController controller = StreamController(); + StreamController controller = + StreamController(); Completer _listener = Completer(); final router = Router(); @@ -16,13 +21,29 @@ final class MockSyncService { }; }; - MockSyncService() { + MockSyncService({this.useBson = false}) { router ..post('/sync/stream', (Request request) async { + if (useBson && + !request.headers['Accept']! + .contains('application/vnd.powersync.bson-stream')) { + throw "Want to serve bson, but client doesn't accept it"; + } + _listener.complete(request); // Respond immediately with a stream - return Response.ok(controller.stream.transform(utf8.encoder), headers: { - 'Content-Type': 'application/x-ndjson', + final bytes = controller.stream.map((line) { + return switch (line) { + final String line => utf8.encode(line), + final Uint8List line => line, + _ => throw ArgumentError.value(line, 'line', 'Unexpected type'), + }; + }); + + return Response.ok(bytes, headers: { + 'Content-Type': useBson + ? 'application/vnd.powersync.bson-stream' + : 'application/x-ndjson', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, context: { @@ -39,12 +60,19 @@ final class MockSyncService { Future get waitForListener => _listener.future; // Queue events which will be sent to connected clients. - void addRawEvent(String data) { + void addRawEvent(Object data) { controller.add(data); } void addLine(Object? message) { - addRawEvent('${json.encode(message)}\n'); + if (useBson) { + // Going through a JSON roundtrip ensures that the message can be + // serialized with the BSON package. + final cleanedMessage = json.decode(json.encode(message)); + addRawEvent(BsonCodec.serialize(cleanedMessage).byteList); + } else { + addRawEvent('${json.encode(message)}\n'); + } } void addKeepAlive([int tokenExpiresIn = 3600]) { From a06b9adb2598996eade8d1a346b08540d9093ee7 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 11 Aug 2025 09:20:38 +0200 Subject: [PATCH 33/62] Update sqlite packages (#314) --- demos/benchmarks/ios/Podfile.lock | 8 +-- demos/benchmarks/macos/Podfile.lock | 8 +-- demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/ios/Podfile.lock | 8 +-- demos/django-todolist/macos/Podfile.lock | 8 +-- demos/django-todolist/pubspec.yaml | 2 +- .../firebase-nodejs-todolist/ios/Podfile.lock | 8 +-- .../supabase-anonymous-auth/ios/Podfile.lock | 8 +-- .../macos/Podfile.lock | 8 +-- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../ios/Podfile.lock | 8 +-- .../macos/Podfile.lock | 8 +-- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/ios/Podfile.lock | 8 +-- demos/supabase-simple-chat/macos/Podfile.lock | 8 +-- .../supabase-todolist-drift/ios/Podfile.lock | 8 +-- .../macos/Podfile.lock | 8 +-- demos/supabase-todolist-drift/pubspec.yaml | 2 +- .../ios/Podfile.lock | 8 +-- .../macos/Podfile.lock | 8 +-- .../pubspec.yaml | 2 +- demos/supabase-todolist/ios/Podfile.lock | 8 +-- demos/supabase-todolist/macos/Podfile.lock | 8 +-- demos/supabase-todolist/pubspec.yaml | 2 +- demos/supabase-trello/ios/Podfile.lock | 8 +-- demos/supabase-trello/macos/Podfile.lock | 8 +-- demos/supabase-trello/pubspec.yaml | 2 +- .../powersync_attachments_helper/pubspec.yaml | 1 - .../lib/src/database/core_version.dart | 2 +- .../open_factory/web/web_open_factory.dart | 1 + .../lib/src/sync/stream_utils.dart | 52 +++++++++++++++++++ .../lib/src/sync/streaming_sync.dart | 5 +- packages/powersync_core/pubspec.yaml | 4 +- .../test/utils/abstract_test_utils.dart | 2 +- .../android/build.gradle | 2 +- .../ios/powersync_flutter_libs.podspec | 2 +- .../macos/powersync_flutter_libs.podspec | 2 +- packages/sqlite3_wasm_build/build.sh | 4 +- scripts/download_core_binary_demos.dart | 2 +- scripts/init_powersync_core_binary.dart | 2 +- 40 files changed, 151 insertions(+), 98 deletions(-) diff --git a/demos/benchmarks/ios/Podfile.lock b/demos/benchmarks/ios/Podfile.lock index 7a167d34..25c9d12d 100644 --- a/demos/benchmarks/ios/Podfile.lock +++ b/demos/benchmarks/ios/Podfile.lock @@ -3,10 +3,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - sqlite3 (3.49.2): - sqlite3/common (= 3.49.2) - sqlite3/common (3.49.2) @@ -54,8 +54,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/benchmarks/macos/Podfile.lock b/demos/benchmarks/macos/Podfile.lock index b1925cd1..44963244 100644 --- a/demos/benchmarks/macos/Podfile.lock +++ b/demos/benchmarks/macos/Podfile.lock @@ -3,10 +3,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - sqlite3 (3.49.2): - sqlite3/common (= 3.49.2) - sqlite3/common (3.49.2) @@ -54,8 +54,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml index d2bb82f6..2064eda9 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: 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.lock b/demos/django-todolist/ios/Podfile.lock index 92649cb7..ac41a2c0 100644 --- a/demos/django-todolist/ios/Podfile.lock +++ b/demos/django-todolist/ios/Podfile.lock @@ -3,10 +3,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -60,8 +60,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/django-todolist/macos/Podfile.lock b/demos/django-todolist/macos/Podfile.lock index 249a5b49..e7df9146 100644 --- a/demos/django-todolist/macos/Podfile.lock +++ b/demos/django-todolist/macos/Podfile.lock @@ -3,10 +3,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -60,8 +60,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index 77d13739..2f51af23 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 - sqlite_async: ^0.11.0 + sqlite_async: ^0.12.0 http: ^1.2.1 shared_preferences: ^2.2.3 diff --git a/demos/firebase-nodejs-todolist/ios/Podfile.lock b/demos/firebase-nodejs-todolist/ios/Podfile.lock index e3b3429e..1321b0cc 100644 --- a/demos/firebase-nodejs-todolist/ios/Podfile.lock +++ b/demos/firebase-nodejs-todolist/ios/Podfile.lock @@ -58,10 +58,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - RecaptchaInterop (101.0.0) - shared_preferences_foundation (0.0.1): - Flutter @@ -152,8 +152,8 @@ SPEC CHECKSUMS: GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 diff --git a/demos/supabase-anonymous-auth/ios/Podfile.lock b/demos/supabase-anonymous-auth/ios/Podfile.lock index 6553952e..ffeb80be 100644 --- a/demos/supabase-anonymous-auth/ios/Podfile.lock +++ b/demos/supabase-anonymous-auth/ios/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-anonymous-auth/macos/Podfile.lock b/demos/supabase-anonymous-auth/macos/Podfile.lock index f32a7411..ad0cf600 100644 --- a/demos/supabase-anonymous-auth/macos/Podfile.lock +++ b/demos/supabase-anonymous-auth/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index 7d4604d3..9c79ab9f 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: 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.lock b/demos/supabase-edge-function-auth/ios/Podfile.lock index 6553952e..ffeb80be 100644 --- a/demos/supabase-edge-function-auth/ios/Podfile.lock +++ b/demos/supabase-edge-function-auth/ios/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-edge-function-auth/macos/Podfile.lock b/demos/supabase-edge-function-auth/macos/Podfile.lock index f32a7411..ad0cf600 100644 --- a/demos/supabase-edge-function-auth/macos/Podfile.lock +++ b/demos/supabase-edge-function-auth/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 4d681973..47eb0c69 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: 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.lock b/demos/supabase-simple-chat/ios/Podfile.lock index 12271547..8717ec0b 100644 --- a/demos/supabase-simple-chat/ios/Podfile.lock +++ b/demos/supabase-simple-chat/ios/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-simple-chat/macos/Podfile.lock b/demos/supabase-simple-chat/macos/Podfile.lock index f32a7411..ad0cf600 100644 --- a/demos/supabase-simple-chat/macos/Podfile.lock +++ b/demos/supabase-simple-chat/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-drift/ios/Podfile.lock b/demos/supabase-todolist-drift/ios/Podfile.lock index 1af06a74..27028979 100644 --- a/demos/supabase-todolist-drift/ios/Podfile.lock +++ b/demos/supabase-todolist-drift/ios/Podfile.lock @@ -7,10 +7,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -77,8 +77,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-drift/macos/Podfile.lock b/demos/supabase-todolist-drift/macos/Podfile.lock index f32a7411..ad0cf600 100644 --- a/demos/supabase-todolist-drift/macos/Podfile.lock +++ b/demos/supabase-todolist-drift/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 3773b612..47f3fc31 100644 --- a/demos/supabase-todolist-drift/pubspec.yaml +++ b/demos/supabase-todolist-drift/pubspec.yaml @@ -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 diff --git a/demos/supabase-todolist-optional-sync/ios/Podfile.lock b/demos/supabase-todolist-optional-sync/ios/Podfile.lock index aac7e03f..50dd2421 100644 --- a/demos/supabase-todolist-optional-sync/ios/Podfile.lock +++ b/demos/supabase-todolist-optional-sync/ios/Podfile.lock @@ -7,10 +7,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -77,8 +77,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-optional-sync/macos/Podfile.lock b/demos/supabase-todolist-optional-sync/macos/Podfile.lock index f32a7411..ad0cf600 100644 --- a/demos/supabase-todolist-optional-sync/macos/Podfile.lock +++ b/demos/supabase-todolist-optional-sync/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index 57ddebec..aa70a961 100644 --- a/demos/supabase-todolist-optional-sync/pubspec.yaml +++ b/demos/supabase-todolist-optional-sync/pubspec.yaml @@ -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/ios/Podfile.lock b/demos/supabase-todolist/ios/Podfile.lock index aac7e03f..50dd2421 100644 --- a/demos/supabase-todolist/ios/Podfile.lock +++ b/demos/supabase-todolist/ios/Podfile.lock @@ -7,10 +7,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -77,8 +77,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist/macos/Podfile.lock b/demos/supabase-todolist/macos/Podfile.lock index f32a7411..ad0cf600 100644 --- a/demos/supabase-todolist/macos/Podfile.lock +++ b/demos/supabase-todolist/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -71,8 +71,8 @@ SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index 725e4d64..e23c3b4f 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -19,7 +19,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-trello/ios/Podfile.lock b/demos/supabase-trello/ios/Podfile.lock index 79306fa5..e0600993 100644 --- a/demos/supabase-trello/ios/Podfile.lock +++ b/demos/supabase-trello/ios/Podfile.lock @@ -41,10 +41,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - SDWebImage (5.21.1): - SDWebImage/Core (= 5.21.1) - SDWebImage/Core (5.21.1) @@ -125,8 +125,8 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: 881187a07f70ecabaf802fce45b186485464d618 + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 SDWebImage: f29024626962457f3470184232766516dee8dfea shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 diff --git a/demos/supabase-trello/macos/Podfile.lock b/demos/supabase-trello/macos/Podfile.lock index 0eed3cbb..ca81f490 100644 --- a/demos/supabase-trello/macos/Podfile.lock +++ b/demos/supabase-trello/macos/Podfile.lock @@ -9,10 +9,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.2) + - powersync-sqlite-core (0.4.4) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.2) + - powersync-sqlite-core (~> 0.4.4) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -83,8 +83,8 @@ SPEC CHECKSUMS: file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - powersync_flutter_libs: fa885a30ceb636655741eee2ff5282d0500fa96b + powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 + powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index afaae23f..a658396f 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: flutter_dotenv: ^5.2.1 logging: ^1.3.0 powersync: ^1.15.0 - sqlite_async: ^0.11.0 + sqlite_async: ^0.12.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 path: ^1.9.0 diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index c920068a..cc9eade5 100644 --- a/packages/powersync_attachments_helper/pubspec.yaml +++ b/packages/powersync_attachments_helper/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: powersync_core: ^1.5.0 logging: ^1.2.0 - sqlite_async: ^0.11.0 path_provider: ^2.0.13 dev_dependencies: diff --git a/packages/powersync_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart index f0156078..98020e33 100644 --- a/packages/powersync_core/lib/src/database/core_version.dart +++ b/packages/powersync_core/lib/src/database/core_version.dart @@ -60,7 +60,7 @@ extension type const PowerSyncCoreVersion((int, int, int) _tuple) { // Note: When updating this, also update the download URL in // scripts/init_powersync_core_binary.dart and the version ref in // packages/sqlite3_wasm_build/build.sh - static const minimum = PowerSyncCoreVersion((0, 4, 2)); + static const minimum = PowerSyncCoreVersion((0, 4, 4)); /// 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/open_factory/web/web_open_factory.dart b/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart index 6f5f156f..db099cb8 100644 --- a/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart +++ b/packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart @@ -23,6 +23,7 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory wasmModule: Uri.parse(sqliteOptions.webSqliteOptions.wasmUri), worker: Uri.parse(sqliteOptions.webSqliteOptions.workerUri), controller: PowerSyncAsyncSqliteController(), + handleCustomRequest: handleCustomRequest, ); } diff --git a/packages/powersync_core/lib/src/sync/stream_utils.dart b/packages/powersync_core/lib/src/sync/stream_utils.dart index 0c28eebf..d531e783 100644 --- a/packages/powersync_core/lib/src/sync/stream_utils.dart +++ b/packages/powersync_core/lib/src/sync/stream_utils.dart @@ -111,6 +111,58 @@ Future cancelAll(List> subscriptions) async { await Future.wait(futures); } +/// A variant of [Stream.fromFuture] that makes [StreamSubscription.cancel] +/// await the original future and report errors. +/// +/// When using the regular [Stream.fromFuture], cancelling the subscription +/// before the future completes with an error could cause an handled error to +/// be reported. +/// Further, it could cause concurrency issues in the stream client because it +/// was possible for us to: +/// +/// 1. Make an HTTP request, wrapped in [Stream.fromFuture] to later expand +/// sync lines from the response. +/// 2. Receive a cancellation request, posting an event on another stream that +/// is merged into the pending HTTP stream. +/// 3. Act on the cancellation request by cancelling the merged subscription. +/// 4. As a clean-up action, close the HTTP client (while the request is still +/// being resolved). +/// +/// Running step 4 and 1 concurrently is really bad, so we delay the +/// cancellation until step 1 has completed. +/// +/// As a further discussion, note that throwing in [StreamSubscription.cancel] +/// is also not exactly a good practice. However, it is the only way to properly +/// delay cancelling streams here, since the [future] itself is a critical +/// operation that must complete first here. +Stream streamFromFutureAwaitInCancellation(Future future) { + final controller = StreamController(sync: true); + var cancelled = false; + + final handledFuture = future.then((value) { + controller + ..add(value) + ..close(); + }, onError: (Object error, StackTrace trace) { + if (cancelled) { + // Make handledFuture complete with the error, so that controller.cancel + // throws (instead of the error being unhandled). + throw error; + } else { + controller + ..addError(error, trace) + ..close(); + } + }); + + controller.onCancel = () async { + cancelled = true; + await handledFuture; + }; + + return controller.stream; +} + /// An [EventSink] that takes raw bytes as inputs, buffers them internally by /// reading a 4-byte length prefix for each message and then emits them as /// chunks. diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 88125ce3..c460de30 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -542,7 +542,7 @@ class StreamingSyncImplementation implements StreamingSync { } Stream _streamingSyncRequest(StreamingSyncRequest data) { - return Stream.fromFuture(_postStreamRequest(data, false)) + return streamFromFutureAwaitInCancellation(_postStreamRequest(data, false)) .asyncExpand((response) { return response?.stream.lines.parseJson .cast>() @@ -615,7 +615,8 @@ final class _ActiveRustStreamingIteration { } Stream _receiveLines(Object? data) { - return Stream.fromFuture(sync._postStreamRequest(data, true)) + return streamFromFutureAwaitInCancellation( + sync._postStreamRequest(data, true)) .asyncExpand((response) { if (response == null) { return null; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 266f9f46..9ffae6ad 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -8,12 +8,12 @@ environment: sdk: ^3.4.3 dependencies: - sqlite_async: ^0.11.4 + sqlite_async: ^0.12.1 # We only use sqlite3 as a transitive dependency, # but right now we need a minimum of v2.4.6. sqlite3: ^2.4.6 # We implement a database controller, which is an interface of sqlite3_web. - sqlite3_web: ^0.3.1 + sqlite3_web: ^0.3.2 universal_io: ^2.0.0 meta: ^1.0.0 http: ^1.4.0 diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index 6a1a90aa..2b456429 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -51,7 +51,7 @@ Logger _makeTestLogger({Level level = Level.ALL, String? name}) { // Hack to fail the test if a SEVERE error is logged. // Not ideal, but works to catch "Sync Isolate error". uncaughtError() async { - throw record.error!; + throw 'Unexpected severe error on logger: ${record.error!}'; } uncaughtError(); diff --git a/packages/powersync_flutter_libs/android/build.gradle b/packages/powersync_flutter_libs/android/build.gradle index 91f9cb27..0f0e0679 100644 --- a/packages/powersync_flutter_libs/android/build.gradle +++ b/packages/powersync_flutter_libs/android/build.gradle @@ -50,5 +50,5 @@ android { } dependencies { - implementation 'com.powersync:powersync-sqlite-core:0.4.2' + implementation 'com.powersync:powersync-sqlite-core:0.4.4' } diff --git a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec index d83fc2c8..6d752c09 100644 --- a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec @@ -22,7 +22,7 @@ A new Flutter FFI plugin project. s.dependency 'Flutter' s.platform = :ios, '11.0' - s.dependency "powersync-sqlite-core", "~> 0.4.2" + s.dependency "powersync-sqlite-core", "~> 0.4.4" # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec index 4519dcb7..19afd88e 100644 --- a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec @@ -21,7 +21,7 @@ A new Flutter FFI plugin project. s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.dependency "powersync-sqlite-core", "~> 0.4.2" + s.dependency "powersync-sqlite-core", "~> 0.4.4" s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index b2556e6c..8b68f63d 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -1,8 +1,8 @@ #!/bin/sh set -e -SQLITE_VERSION="2.8.0" -POWERSYNC_CORE_VERSION="0.4.2" +SQLITE_VERSION="2.9.0" +POWERSYNC_CORE_VERSION="0.4.4" SQLITE_PATH="sqlite3.dart" if [ -d "$SQLITE_PATH" ]; then diff --git a/scripts/download_core_binary_demos.dart b/scripts/download_core_binary_demos.dart index e5cb52de..e4cae3d9 100644 --- a/scripts/download_core_binary_demos.dart +++ b/scripts/download_core_binary_demos.dart @@ -3,7 +3,7 @@ import 'dart:io'; final coreUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.2'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.4'; void main() async { final powersyncLibsLinuxPath = "packages/powersync_flutter_libs/linux"; diff --git a/scripts/init_powersync_core_binary.dart b/scripts/init_powersync_core_binary.dart index 1374f441..18c4b617 100644 --- a/scripts/init_powersync_core_binary.dart +++ b/scripts/init_powersync_core_binary.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:melos/melos.dart'; final sqliteUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.2'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.4'; void main() async { final sqliteCoreFilename = getLibraryForPlatform(); From 0e8d50b84d8674859fbefd5da6eaac8355f5a6cb Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 11 Aug 2025 16:20:58 +0200 Subject: [PATCH 34/62] Request cancellation (#315) --- .../lib/src/sync/streaming_sync.dart | 3 +- packages/powersync_core/pubspec.yaml | 2 +- .../test/in_memory_sync_test.dart | 44 ++++++++++++++++- .../sync_server/in_memory_sync_server.dart | 2 +- .../test/utils/in_memory_http.dart | 49 ++++++++++++++++++- 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index c460de30..ad0886a1 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -517,7 +517,8 @@ class StreamingSyncImplementation implements StreamingSync { } final uri = credentials.endpointUri('sync/stream'); - final request = http.Request('POST', uri); + final request = + http.AbortableRequest('POST', uri, abortTrigger: _abort!.onAbort); request.headers['Content-Type'] = 'application/json'; request.headers['Authorization'] = "Token ${credentials.token}"; request.headers['Accept'] = diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 9ffae6ad..7213180a 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: sqlite3_web: ^0.3.2 universal_io: ^2.0.0 meta: ^1.0.0 - http: ^1.4.0 + http: ^1.5.0 uuid: ^4.2.0 async: ^2.10.0 logging: ^1.1.1 diff --git a/packages/powersync_core/test/in_memory_sync_test.dart b/packages/powersync_core/test/in_memory_sync_test.dart index 57e36b0c..e4ab531b 100644 --- a/packages/powersync_core/test/in_memory_sync_test.dart +++ b/packages/powersync_core/test/in_memory_sync_test.dart @@ -7,6 +7,8 @@ import 'package:powersync_core/powersync_core.dart'; import 'package:powersync_core/sqlite3_common.dart'; import 'package:powersync_core/src/sync/streaming_sync.dart'; import 'package:powersync_core/src/sync/protocol.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; import 'package:test/test.dart'; import 'bucket_storage_test.dart'; @@ -63,7 +65,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { void createSyncClient({Schema? schema}) { final (client, server) = inMemoryServer(); - server.mount(syncService.router.call); + server.mount((req) => syncService.router(req)); final thisSyncClient = syncClient = database.connectWithMockService( client, @@ -937,6 +939,46 @@ void _declareTests(String name, SyncOptions options, bool bson) { expect(await query.next, 'from server'); }); + + group('abort', () { + test('during connect', () async { + final requestStarted = Completer(); + + syncService.router = Router() + ..post('/sync/stream', expectAsync1((Request request) async { + requestStarted.complete(); + + // emulate a network that never connects + await Completer().future; + })); + + syncClient.streamingSync(); + await requestStarted.future; + expect(database.currentStatus, isSyncStatus(connecting: true)); + + await syncClient.abort(); + expect(database.currentStatus.anyError, isNull); + }); + + test('during stream', () async { + final status = await waitForConnection(); + syncService.addLine({ + 'checkpoint': { + 'last_op_id': '0', + 'buckets': [ + { + 'bucket': 'bkt', + 'checksum': 0, + } + ], + }, + }); + await expectLater(status, emits(isSyncStatus(downloading: true))); + + await syncClient.abort(); + expect(database.currentStatus.anyError, isNull); + }); + }); }); } diff --git a/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart b/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart index 19d7e137..278f014e 100644 --- a/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart +++ b/packages/powersync_core/test/server/sync_server/in_memory_sync_server.dart @@ -14,7 +14,7 @@ final class MockSyncService { StreamController(); Completer _listener = Completer(); - final router = Router(); + var router = Router(); Object? Function() writeCheckpoint = () { return { 'data': {'write_checkpoint': '10'} diff --git a/packages/powersync_core/test/utils/in_memory_http.dart b/packages/powersync_core/test/utils/in_memory_http.dart index 61550e3c..a35d6a09 100644 --- a/packages/powersync_core/test/utils/in_memory_http.dart +++ b/packages/powersync_core/test/utils/in_memory_http.dart @@ -35,6 +35,11 @@ final class _MockServer implements shelf.Server { Future handleRequest( BaseRequest request, ByteStream body) async { + final cancellationFuture = switch (request) { + Abortable(:final abortTrigger) => abortTrigger, + _ => null, + }; + if (_handler case final endpoint?) { final shelfRequest = shelf.Request( request.method, @@ -42,10 +47,17 @@ final class _MockServer implements shelf.Server { headers: request.headers, body: body, ); - final shelfResponse = await endpoint(shelfRequest); + + final shelfResponse = await Future.any([ + Future.sync(() => endpoint(shelfRequest)), + if (cancellationFuture != null) + cancellationFuture.then((_) { + throw RequestAbortedException(); + }), + ]); return StreamedResponse( - shelfResponse.read(), + shelfResponse.read().injectCancellation(cancellationFuture), shelfResponse.statusCode, headers: shelfResponse.headers, ); @@ -54,3 +66,36 @@ final class _MockServer implements shelf.Server { } } } + +extension on Stream { + Stream injectCancellation(Future? token) { + if (token == null) { + return this; + } + + return Stream.multi( + (listener) { + final subscription = listen( + listener.addSync, + onError: listener.addErrorSync, + onDone: listener.closeSync, + ); + + listener + ..onPause = subscription.pause + ..onResume = subscription.resume + ..onCancel = subscription.cancel; + + token.whenComplete(() { + if (!listener.isClosed) { + listener + ..addErrorSync(RequestAbortedException()) + ..closeSync(); + subscription.cancel(); + } + }); + }, + isBroadcast: isBroadcast, + ); + } +} From 00fe51db0fd87a43428af1b76ac5ee4dab09304b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 11 Aug 2025 16:33:11 +0200 Subject: [PATCH 35/62] chore(release): publish packages - powersync_core@1.5.1 - powersync@1.15.1 - powersync_sqlcipher@0.1.11 - powersync_flutter_libs@0.4.11 --- CHANGELOG.md | 29 +++++++++++++++++++ demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/pubspec.yaml | 2 +- demos/firebase-nodejs-todolist/pubspec.yaml | 2 +- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/pubspec.yaml | 2 +- demos/supabase-todolist-drift/pubspec.yaml | 2 +- .../pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 2 +- demos/supabase-trello/pubspec.yaml | 2 +- packages/powersync/CHANGELOG.md | 7 +++++ packages/powersync/pubspec.yaml | 6 ++-- .../powersync_attachments_helper/pubspec.yaml | 2 +- packages/powersync_core/CHANGELOG.md | 7 +++++ packages/powersync_core/lib/src/version.dart | 2 +- packages/powersync_core/pubspec.yaml | 2 +- packages/powersync_flutter_libs/CHANGELOG.md | 4 +++ packages/powersync_flutter_libs/pubspec.yaml | 2 +- packages/powersync_sqlcipher/CHANGELOG.md | 7 +++++ .../powersync_sqlcipher/example/pubspec.yaml | 2 +- packages/powersync_sqlcipher/pubspec.yaml | 6 ++-- 22 files changed, 75 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ffa2cc..80ee6b95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,35 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2025-08-11 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`powersync_core` - `v1.5.1`](#powersync_core---v151) + - [`powersync` - `v1.15.1`](#powersync---v1151) + - [`powersync_sqlcipher` - `v0.1.11`](#powersync_sqlcipher---v0111) + - [`powersync_flutter_libs` - `v0.4.11`](#powersync_flutter_libs---v0411) + +--- + +#### `powersync_core` - `v1.5.1` +#### `powersync` - `v1.15.1` +#### `powersync_sqlcipher` - `v0.1.11` +#### `powersync_flutter_libs` - `v0.4.11` + + - Support latest versions of `package:sqlite3` and `package:sqlite_async`. + - Stream client: Improve `disconnect()` while a connection is being opened. + - Stream client: Support binary sync lines with Rust client and compatible PowerSync service versions. + - Sync client: Improve parsing error responses. + ## 2025-07-17 ### Changes diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml index 2064eda9..2b752fa6 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index 2f51af23..57b5a3b1 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml index e86b714a..2247602e 100644 --- a/demos/firebase-nodejs-todolist/pubspec.yaml +++ b/demos/firebase-nodejs-todolist/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index 9c79ab9f..5284da47 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 47eb0c69..217c0d8d 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml index 15afa2fd..4cf243be 100644 --- a/demos/supabase-simple-chat/pubspec.yaml +++ b/demos/supabase-simple-chat/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: supabase_flutter: ^2.0.2 timeago: ^3.6.0 - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 47f3fc31..038b8a3c 100644 --- a/demos/supabase-todolist-drift/pubspec.yaml +++ b/demos/supabase-todolist-drift/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter powersync_attachments_helper: ^0.6.18+11 - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index aa70a961..9036b626 100644 --- a/demos/supabase-todolist-optional-sync/pubspec.yaml +++ b/demos/supabase-todolist-optional-sync/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index e23c3b4f..2deaba1b 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter powersync_attachments_helper: ^0.6.18+11 - powersync: ^1.15.0 + powersync: ^1.15.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index a658396f..e50fcccb 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: random_name_generator: ^1.5.0 flutter_dotenv: ^5.2.1 logging: ^1.3.0 - powersync: ^1.15.0 + powersync: ^1.15.1 sqlite_async: ^0.12.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md index b901657d..78267063 100644 --- a/packages/powersync/CHANGELOG.md +++ b/packages/powersync/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.15.1 + + - Support latest versions of `package:sqlite3` and `package:sqlite_async`. + - Stream client: Improve `disconnect()` while a connection is being opened. + - Stream client: Support binary sync lines with Rust client and compatible PowerSync service versions. + - Sync client: Improve parsing error responses. + ## 1.15.0 - Update the PowerSync core extension to `0.4.2`. diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml index 268cf642..e237c3b8 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync -version: 1.15.0 +version: 1.15.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 @@ -12,8 +12,8 @@ dependencies: sdk: flutter sqlite3_flutter_libs: ^0.5.23 - powersync_core: ^1.5.0 - powersync_flutter_libs: ^0.4.10 + powersync_core: ^1.5.1 + powersync_flutter_libs: ^0.4.11 collection: ^1.17.0 dev_dependencies: diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index cc9eade5..a918f014 100644 --- a/packages/powersync_attachments_helper/pubspec.yaml +++ b/packages/powersync_attachments_helper/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.5.0 + powersync_core: ^1.5.1 logging: ^1.2.0 path_provider: ^2.0.13 diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index ee22df4d..8d74c70f 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.5.1 + + - Support latest versions of `package:sqlite3` and `package:sqlite_async`. + - Stream client: Improve `disconnect()` while a connection is being opened. + - Stream client: Support binary sync lines with Rust client and compatible PowerSync service versions. + - Sync client: Improve parsing error responses. + ## 1.5.0 - Update the PowerSync core extension to `0.4.2`. diff --git a/packages/powersync_core/lib/src/version.dart b/packages/powersync_core/lib/src/version.dart index e57575ab..2596f2ab 100644 --- a/packages/powersync_core/lib/src/version.dart +++ b/packages/powersync_core/lib/src/version.dart @@ -1 +1 @@ -const String libraryVersion = '1.5.0'; +const String libraryVersion = '1.5.1'; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 7213180a..e404cc9b 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_core -version: 1.5.0 +version: 1.5.1 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Dart SDK - sync engine for building local-first apps. diff --git a/packages/powersync_flutter_libs/CHANGELOG.md b/packages/powersync_flutter_libs/CHANGELOG.md index 0dc93482..e344e77b 100644 --- a/packages/powersync_flutter_libs/CHANGELOG.md +++ b/packages/powersync_flutter_libs/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.11 + + - Update PowerSync core extension to version 0.4.4. + ## 0.4.10 - Update PowerSync core extension to version 0.4.2. diff --git a/packages/powersync_flutter_libs/pubspec.yaml b/packages/powersync_flutter_libs/pubspec.yaml index 24fa7e01..9753173a 100644 --- a/packages/powersync_flutter_libs/pubspec.yaml +++ b/packages/powersync_flutter_libs/pubspec.yaml @@ -1,6 +1,6 @@ name: powersync_flutter_libs description: PowerSync core binaries for the PowerSync Flutter SDK. Needs to be included for Flutter apps. -version: 0.4.10 +version: 0.4.11 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ diff --git a/packages/powersync_sqlcipher/CHANGELOG.md b/packages/powersync_sqlcipher/CHANGELOG.md index 08d525b8..b5ee1665 100644 --- a/packages/powersync_sqlcipher/CHANGELOG.md +++ b/packages/powersync_sqlcipher/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.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. + ## 0.1.10 - raw tables diff --git a/packages/powersync_sqlcipher/example/pubspec.yaml b/packages/powersync_sqlcipher/example/pubspec.yaml index a86b0215..d056e4a8 100644 --- a/packages/powersync_sqlcipher/example/pubspec.yaml +++ b/packages/powersync_sqlcipher/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - powersync_sqlcipher: ^0.1.10 + powersync_sqlcipher: ^0.1.11 dev_dependencies: flutter_test: diff --git a/packages/powersync_sqlcipher/pubspec.yaml b/packages/powersync_sqlcipher/pubspec.yaml index 0cee52bf..e40aac2d 100644 --- a/packages/powersync_sqlcipher/pubspec.yaml +++ b/packages/powersync_sqlcipher/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_sqlcipher -version: 0.1.10 +version: 0.1.11 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Flutter SDK - sync engine for building local-first apps. @@ -12,8 +12,8 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.5.0 - powersync_flutter_libs: ^0.4.10 + powersync_core: ^1.5.1 + powersync_flutter_libs: ^0.4.11 sqlcipher_flutter_libs: ^0.6.4 sqlite3_web: ^0.3.0 From 801c23ca47a17a94fd4b2e67896bfce1c4f1dc7e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 14 Aug 2025 12:51:29 +0200 Subject: [PATCH 36/62] Sync isolate: Use updatesSync --- .../lib/src/database/native/native_powersync_database.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/powersync_core/lib/src/database/native/native_powersync_database.dart b/packages/powersync_core/lib/src/database/native/native_powersync_database.dart index d55b69db..3ebb95f9 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 @@ -438,7 +438,7 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async { } } - localUpdatesSubscription = db!.updates.listen((event) { + localUpdatesSubscription = db!.updatesSync.listen((event) { updatedTables.add(event.tableName); updateDebouncer ??= From 57c0c529d01da5c7cd723fe541a14ccf4cbb85c6 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 14 Aug 2025 17:11:49 +0200 Subject: [PATCH 37/62] chore(release): publish packages - powersync_core@1.5.2 - powersync@1.15.2 - powersync_sqlcipher@0.1.11+1 --- CHANGELOG.md | 31 +++++++++++++++++++ demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/pubspec.yaml | 2 +- demos/firebase-nodejs-todolist/pubspec.yaml | 2 +- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/pubspec.yaml | 2 +- demos/supabase-todolist-drift/pubspec.yaml | 2 +- .../pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 2 +- demos/supabase-trello/pubspec.yaml | 2 +- packages/powersync/CHANGELOG.md | 4 +++ packages/powersync/pubspec.yaml | 4 +-- .../powersync_attachments_helper/pubspec.yaml | 2 +- packages/powersync_core/CHANGELOG.md | 4 +++ packages/powersync_core/lib/src/version.dart | 2 +- packages/powersync_core/pubspec.yaml | 2 +- packages/powersync_sqlcipher/CHANGELOG.md | 4 +++ .../powersync_sqlcipher/example/pubspec.yaml | 2 +- packages/powersync_sqlcipher/pubspec.yaml | 4 +-- 20 files changed, 61 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80ee6b95..281e74d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 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 diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml index 2b752fa6..b2e1f55a 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index 57b5a3b1..9b187637 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml index 2247602e..46436e8f 100644 --- a/demos/firebase-nodejs-todolist/pubspec.yaml +++ b/demos/firebase-nodejs-todolist/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index 5284da47..db67696d 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 217c0d8d..2dd12558 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml index 4cf243be..a65519af 100644 --- a/demos/supabase-simple-chat/pubspec.yaml +++ b/demos/supabase-simple-chat/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: supabase_flutter: ^2.0.2 timeago: ^3.6.0 - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 038b8a3c..50089099 100644 --- a/demos/supabase-todolist-drift/pubspec.yaml +++ b/demos/supabase-todolist-drift/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter powersync_attachments_helper: ^0.6.18+11 - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index 9036b626..a914651c 100644 --- a/demos/supabase-todolist-optional-sync/pubspec.yaml +++ b/demos/supabase-todolist-optional-sync/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index 2deaba1b..9e04624f 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter powersync_attachments_helper: ^0.6.18+11 - powersync: ^1.15.1 + powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index e50fcccb..15a7dccf 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: random_name_generator: ^1.5.0 flutter_dotenv: ^5.2.1 logging: ^1.3.0 - powersync: ^1.15.1 + powersync: ^1.15.2 sqlite_async: ^0.12.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md index 78267063..9cd53047 100644 --- a/packages/powersync/CHANGELOG.md +++ b/packages/powersync/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.15.2 + + - Fix excessive memory consumption during large sync. + ## 1.15.1 - Support latest versions of `package:sqlite3` and `package:sqlite_async`. diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml index e237c3b8..fa7bdc11 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync -version: 1.15.1 +version: 1.15.2 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 @@ -12,7 +12,7 @@ dependencies: sdk: flutter sqlite3_flutter_libs: ^0.5.23 - powersync_core: ^1.5.1 + powersync_core: ^1.5.2 powersync_flutter_libs: ^0.4.11 collection: ^1.17.0 diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index a918f014..6d44ec30 100644 --- a/packages/powersync_attachments_helper/pubspec.yaml +++ b/packages/powersync_attachments_helper/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.5.1 + powersync_core: ^1.5.2 logging: ^1.2.0 path_provider: ^2.0.13 diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index 8d74c70f..ddd78a42 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.2 + + - Fix excessive memory consumption during large sync. + ## 1.5.1 - Support latest versions of `package:sqlite3` and `package:sqlite_async`. diff --git a/packages/powersync_core/lib/src/version.dart b/packages/powersync_core/lib/src/version.dart index 2596f2ab..26361d4c 100644 --- a/packages/powersync_core/lib/src/version.dart +++ b/packages/powersync_core/lib/src/version.dart @@ -1 +1 @@ -const String libraryVersion = '1.5.1'; +const String libraryVersion = '1.5.2'; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index e404cc9b..2b1bb090 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_core -version: 1.5.1 +version: 1.5.2 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Dart SDK - sync engine for building local-first apps. diff --git a/packages/powersync_sqlcipher/CHANGELOG.md b/packages/powersync_sqlcipher/CHANGELOG.md index b5ee1665..b037c1eb 100644 --- a/packages/powersync_sqlcipher/CHANGELOG.md +++ b/packages/powersync_sqlcipher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.11+1 + + - Fix excessive memory consumption during large sync. + ## 0.1.11 - Support latest versions of `package:sqlite3` and `package:sqlite_async`. diff --git a/packages/powersync_sqlcipher/example/pubspec.yaml b/packages/powersync_sqlcipher/example/pubspec.yaml index d056e4a8..d7746f71 100644 --- a/packages/powersync_sqlcipher/example/pubspec.yaml +++ b/packages/powersync_sqlcipher/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - powersync_sqlcipher: ^0.1.11 + powersync_sqlcipher: ^0.1.11+1 dev_dependencies: flutter_test: diff --git a/packages/powersync_sqlcipher/pubspec.yaml b/packages/powersync_sqlcipher/pubspec.yaml index e40aac2d..6aa109dc 100644 --- a/packages/powersync_sqlcipher/pubspec.yaml +++ b/packages/powersync_sqlcipher/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_sqlcipher -version: 0.1.11 +version: 0.1.11+1 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Flutter SDK - sync engine for building local-first apps. @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.5.1 + powersync_core: ^1.5.2 powersync_flutter_libs: ^0.4.11 sqlcipher_flutter_libs: ^0.6.4 sqlite3_web: ^0.3.0 From 760bbd93f335e2359cdd42cc4855add2ed41c1e0 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 Aug 2025 09:30:59 +0200 Subject: [PATCH 38/62] chore(release): publish packages - powersync_attachments_helper@0.6.19 --- CHANGELOG.md | 21 +++++++++++++++++++ demos/supabase-todolist-drift/pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 2 +- .../powersync_attachments_helper/CHANGELOG.md | 4 ++++ .../powersync_attachments_helper/pubspec.yaml | 2 +- 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 281e74d3..9937c109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 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 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 50089099..78a470c9 100644 --- a/demos/supabase-todolist-drift/pubspec.yaml +++ b/demos/supabase-todolist-drift/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - powersync_attachments_helper: ^0.6.18+11 + powersync_attachments_helper: ^0.6.19 powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index 9e04624f..cb94c2bb 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync_attachments_helper: ^0.6.18+11 + powersync_attachments_helper: ^0.6.19 powersync: ^1.15.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 diff --git a/packages/powersync_attachments_helper/CHANGELOG.md b/packages/powersync_attachments_helper/CHANGELOG.md index ab5b00e6..0aa268e3 100644 --- a/packages/powersync_attachments_helper/CHANGELOG.md +++ b/packages/powersync_attachments_helper/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.19 + + - Remove direct dependency on `sqlite_async`. + ## 0.6.18+11 - Update a dependency to the latest release. diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index 6d44ec30..b705cf09 100644 --- a/packages/powersync_attachments_helper/pubspec.yaml +++ b/packages/powersync_attachments_helper/pubspec.yaml @@ -1,6 +1,6 @@ name: powersync_attachments_helper description: A helper library for handling attachments when using PowerSync. -version: 0.6.18+11 +version: 0.6.19 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ environment: From 5712fefe803a51046e9fa6e538e84a80bbf2f204 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 Aug 2025 09:42:07 +0200 Subject: [PATCH 39/62] Avoid deprecation warning --- .../lib/features/offlineboards/presentation/index.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 6b6aaadfa8378160cbe8b2757848a0acb64be3e6 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 14 Aug 2025 17:55:48 +0200 Subject: [PATCH 40/62] Add nextCrudTransactions() stream --- .../lib/src/database/powersync_db_mixin.dart | 128 ++++++++++-------- packages/powersync_core/test/crud_test.dart | 33 +++++ 2 files changed, 106 insertions(+), 55 deletions(-) diff --git a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart index dc4b2ddb..a9ba96ff 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'; @@ -508,23 +509,10 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { } final last = all[all.length - 1]; return CrudBatch( - crud: all, - haveMore: haveMore, - complete: ({String? writeCheckpoint}) async { - await writeTransaction((db) async { - await db - .execute('DELETE FROM ps_crud WHERE id <= ?', [last.clientId]); - if (writeCheckpoint != null && - await db.getOptional('SELECT 1 FROM ps_crud LIMIT 1') == null) { - await db.execute( - 'UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name=\'\$local\'', - [writeCheckpoint]); - } else { - await db.execute( - 'UPDATE ps_buckets SET target_op = $maxOpId WHERE name=\'\$local\''); - } - }); - }); + crud: all, + haveMore: haveMore, + complete: _crudCompletionCallback(last.clientId), + ); } /// Get the next recorded transaction to upload. @@ -538,46 +526,76 @@ 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 nextCrudTransactions().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 awalys 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 _all_ transactions emitted by the + /// stream until that point as completed. + /// + /// This can be used to upload multiple transactions in a single batch, e.g. + /// with:AbortController + /// + /// If there is no local data to upload, the stream emits a single `onDone` + /// event. + Stream nextCrudTransactions() 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. diff --git a/packages/powersync_core/test/crud_test.dart b/packages/powersync_core/test/crud_test.dart index 4c18a4b2..f08ae022 100644 --- a/packages/powersync_core/test/crud_test.dart +++ b/packages/powersync_core/test/crud_test.dart @@ -271,6 +271,39 @@ void main() { expect(await powersync.getNextCrudTransaction(), equals(null)); }); + test('nextCrudTransactions', () async { + Future createTransaction(int size) { + return powersync.writeTransaction((tx) async { + for (var i = 0; i < size; i++) { + await tx.execute('INSERT INTO assets (id) VALUES (uuid())'); + } + }); + } + + await expectLater(powersync.nextCrudTransactions(), emitsDone); + + await createTransaction(5); + await createTransaction(10); + await createTransaction(15); + + CrudTransaction? lastTransaction; + final batch = []; + await for (final transaction in powersync.nextCrudTransactions()) { + batch.addAll(transaction.crud); + lastTransaction = transaction; + + if (batch.length > 10) { + break; + } + } + + expect(batch, hasLength(15)); + await lastTransaction!.complete(); + + final remainingTransaction = await powersync.getNextCrudTransaction(); + expect(remainingTransaction?.crud, hasLength(15)); + }); + test('include metadata', () async { await powersync.updateSchema(Schema([ Table( From 6f95791b4dad8581f3fa010bfa18459c0ce3ed35 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 14 Aug 2025 17:59:01 +0200 Subject: [PATCH 41/62] Add example to docs --- .../lib/src/database/powersync_db_mixin.dart | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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 a9ba96ff..9a595c65 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -544,7 +544,26 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// stream until that point as completed. /// /// This can be used to upload multiple transactions in a single batch, e.g. - /// with:AbortController + /// 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. From 9e46db980ec48b025848967dc2645fa25302095f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 14 Aug 2025 23:10:56 +0200 Subject: [PATCH 42/62] Rename to getCrudTransactions --- .../powersync_core/lib/src/database/powersync_db_mixin.dart | 4 ++-- packages/powersync_core/test/crud_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 9a595c65..78059962 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -527,7 +527,7 @@ 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() { - return nextCrudTransactions().firstOrNull; + return getCrudTransactions().firstOrNull; } /// Returns a stream of completed transactions with local writes against the @@ -567,7 +567,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// /// If there is no local data to upload, the stream emits a single `onDone` /// event. - Stream nextCrudTransactions() async* { + Stream getCrudTransactions() async* { var lastCrudItemId = -1; const sql = ''' WITH RECURSIVE crud_entries AS ( diff --git a/packages/powersync_core/test/crud_test.dart b/packages/powersync_core/test/crud_test.dart index f08ae022..0e188075 100644 --- a/packages/powersync_core/test/crud_test.dart +++ b/packages/powersync_core/test/crud_test.dart @@ -280,7 +280,7 @@ void main() { }); } - await expectLater(powersync.nextCrudTransactions(), emitsDone); + await expectLater(powersync.getCrudTransactions(), emitsDone); await createTransaction(5); await createTransaction(10); @@ -288,7 +288,7 @@ void main() { CrudTransaction? lastTransaction; final batch = []; - await for (final transaction in powersync.nextCrudTransactions()) { + await for (final transaction in powersync.getCrudTransactions()) { batch.addAll(transaction.crud); lastTransaction = transaction; From f631fda0a5cb11cf3a1819a320ddb4595c5f6a2c Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 Aug 2025 11:58:48 +0200 Subject: [PATCH 43/62] Clarify docs --- .../powersync_core/lib/src/database/powersync_db_mixin.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 78059962..f8a36858 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -540,8 +540,8 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Unlike [getNextCrudTransaction], which awalys 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 _all_ transactions emitted by the - /// stream until that point as completed. + /// [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: From 659bc77ed21d0e0faa7d3f26b0d1b7d42c774d08 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 18 Aug 2025 13:43:39 +0200 Subject: [PATCH 44/62] Update packages/powersync_core/lib/src/database/powersync_db_mixin.dart Co-authored-by: Ralf Kistner --- .../powersync_core/lib/src/database/powersync_db_mixin.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f8a36858..a53b5049 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -537,7 +537,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// method. Each entry emitted by the stream is a full transaction containing /// all local writes made while that transaction was active. /// - /// Unlike [getNextCrudTransaction], which awalys returns the oldest + /// 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 From 1f8f4a2f0691f34428c985e2c2d60392e32ccf43 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 1 Sep 2025 11:35:56 +0200 Subject: [PATCH 45/62] Update core extension to 0.4.5 (#324) --- .github/workflows/prepare_wasm.yml | 2 +- demos/benchmarks/ios/Podfile | 2 +- demos/benchmarks/ios/Podfile.lock | 12 +- demos/benchmarks/macos/Podfile | 2 +- demos/benchmarks/macos/Podfile.lock | 12 +- demos/django-todolist/ios/Podfile | 2 +- demos/django-todolist/ios/Podfile.lock | 12 +- demos/django-todolist/macos/Podfile | 2 +- demos/django-todolist/macos/Podfile.lock | 12 +- .../firebase-nodejs-todolist/ios/Podfile.lock | 10 +- demos/firebase-nodejs-todolist/macos/Podfile | 2 +- .../macos/Podfile.lock | 139 +++++++++++++++--- demos/supabase-anonymous-auth/ios/Podfile | 2 +- .../supabase-anonymous-auth/ios/Podfile.lock | 12 +- demos/supabase-anonymous-auth/macos/Podfile | 2 +- .../macos/Podfile.lock | 12 +- demos/supabase-edge-function-auth/ios/Podfile | 2 +- .../ios/Podfile.lock | 12 +- .../supabase-edge-function-auth/macos/Podfile | 2 +- .../macos/Podfile.lock | 12 +- demos/supabase-simple-chat/ios/Podfile | 2 +- demos/supabase-simple-chat/ios/Podfile.lock | 12 +- demos/supabase-simple-chat/macos/Podfile | 2 +- demos/supabase-simple-chat/macos/Podfile.lock | 12 +- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- demos/supabase-todolist-drift/ios/Podfile | 2 +- .../supabase-todolist-drift/ios/Podfile.lock | 12 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + .../ios/Runner/AppDelegate.swift | 2 +- demos/supabase-todolist-drift/macos/Podfile | 2 +- .../macos/Podfile.lock | 12 +- demos/supabase-todolist-drift/pubspec.lock | 62 ++++---- .../ios/Podfile | 2 +- .../ios/Podfile.lock | 12 +- .../macos/Podfile | 2 +- .../macos/Podfile.lock | 12 +- demos/supabase-todolist/ios/Podfile | 2 +- demos/supabase-todolist/ios/Podfile.lock | 41 +++--- demos/supabase-todolist/macos/Podfile | 2 +- demos/supabase-todolist/macos/Podfile.lock | 41 +++--- demos/supabase-trello/ios/Podfile | 2 +- demos/supabase-trello/ios/Podfile.lock | 20 +-- demos/supabase-trello/macos/Podfile | 2 +- demos/supabase-trello/macos/Podfile.lock | 12 +- .../lib/src/database/core_version.dart | 2 +- .../android/build.gradle | 2 +- .../ios/powersync_flutter_libs.podspec | 2 +- .../macos/powersync_flutter_libs.podspec | 2 +- .../powersync_sqlcipher/example/ios/Podfile | 2 +- .../powersync_sqlcipher/example/macos/Podfile | 2 +- packages/sqlite3_wasm_build/build.sh | 2 +- scripts/download_core_binary_demos.dart | 2 +- scripts/init_powersync_core_binary.dart | 2 +- 54 files changed, 332 insertions(+), 232 deletions(-) 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/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 25c9d12d..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + 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 44963244..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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/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 ac41a2c0..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + 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/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 e7df9146..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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/firebase-nodejs-todolist/ios/Podfile.lock b/demos/firebase-nodejs-todolist/ios/Podfile.lock index 1321b0cc..f5b33ded 100644 --- a/demos/firebase-nodejs-todolist/ios/Podfile.lock +++ b/demos/firebase-nodejs-todolist/ios/Podfile.lock @@ -58,10 +58,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - powersync-sqlite-core (~> 0.4.5) - RecaptchaInterop (101.0.0) - shared_preferences_foundation (0.0.1): - Flutter @@ -148,12 +148,12 @@ SPEC CHECKSUMS: FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + 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/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 ffeb80be..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + 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 ad0cf600..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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/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 ffeb80be..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + 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 ad0cf600..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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/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 8717ec0b..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + 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 ad0cf600..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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-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 index d97f17e2..3e44f9c6 100644 --- a/demos/supabase-todolist-drift/ios/Podfile +++ b/demos/supabase-todolist-drift/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-drift/ios/Podfile.lock b/demos/supabase-todolist-drift/ios/Podfile.lock index 27028979..498bff0c 100644 --- a/demos/supabase-todolist-drift/ios/Podfile.lock +++ b/demos/supabase-todolist-drift/ios/Podfile.lock @@ -7,10 +7,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 + powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 +PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 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..39ba8188 100644 --- a/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.pbxproj +++ b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.pbxproj @@ -455,7 +455,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; @@ -585,7 +585,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 +636,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-drift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demos/supabase-todolist-drift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5df..e3773d42 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 @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> 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/macos/Podfile b/demos/supabase-todolist-drift/macos/Podfile index c795730d..b52666a1 100644 --- a/demos/supabase-todolist-drift/macos/Podfile +++ b/demos/supabase-todolist-drift/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-drift/macos/Podfile.lock b/demos/supabase-todolist-drift/macos/Podfile.lock index ad0cf600..6983b2da 100644 --- a/demos/supabase-todolist-drift/macos/Podfile.lock +++ b/demos/supabase-todolist-drift/macos/Podfile.lock @@ -5,10 +5,10 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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-drift/pubspec.lock b/demos/supabase-todolist-drift/pubspec.lock index 8a463ae8..ba153676 100644 --- a/demos/supabase-todolist-drift/pubspec.lock +++ b/demos/supabase-todolist-drift/pubspec.lock @@ -317,26 +317,26 @@ packages: dependency: "direct main" description: name: drift - sha256: b584ddeb2b74436735dd2cf746d2d021e19a9a6770f409212fd5cbc2814ada85 + sha256: "6aaea757f53bb035e8a3baedf3d1d53a79d6549a6c13d84f7546509da9372c7c" url: "https://pub.dev" source: hosted - version: "2.26.1" + version: "2.28.1" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "54dc207c6e4662741f60e5752678df183957ab907754ffab0372a7082f6d2816" + sha256: "68c138e884527d2bd61df2ade276c3a144df84d1adeb0ab8f3196b5afe021bd4" url: "https://pub.dev" source: hosted - version: "2.26.1" + version: "2.28.0" drift_sqlite_async: dependency: "direct main" description: name: drift_sqlite_async - sha256: "5da283b7df605b639f979cc24c3add96a16ea08135ea5047da2f7b600bedc4fe" + sha256: "7080bb93f042fe71b5870db999b1ebf8ae61fc2faa3d8617d770055f75a85b17" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.3+1" fake_async: dependency: transitive description: @@ -484,10 +484,10 @@ 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: @@ -548,26 +548,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: @@ -742,28 +742,28 @@ packages: path: "../../packages/powersync" relative: true source: path - version: "1.15.0" + version: "1.15.2" powersync_attachments_helper: dependency: "direct main" description: path: "../../packages/powersync_attachments_helper" relative: true source: path - version: "0.6.18+11" + version: "0.6.19" powersync_core: dependency: "direct overridden" description: path: "../../packages/powersync_core" relative: true source: path - version: "1.5.0" + version: "1.5.2" powersync_flutter_libs: dependency: "direct overridden" description: path: "../../packages/powersync_flutter_libs" relative: true source: path - version: "0.4.10" + version: "0.4.11" pub_semver: dependency: transitive description: @@ -949,10 +949,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: @@ -965,26 +965,26 @@ 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" sqlparser: dependency: transitive description: name: sqlparser - sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee" + sha256: "7c859c803cf7e9a84d6db918bac824545045692bbe94a6386bd3a45132235d09" url: "https://pub.dev" source: hosted - version: "0.41.0" + version: "0.41.1" stack_trace: dependency: transitive description: @@ -1061,10 +1061,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" timing: dependency: transitive description: @@ -1165,10 +1165,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: @@ -1242,5 +1242,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.27.0" 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 50dd2421..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 + powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: f7b3cb7384a2d5da4b22b090e1f632de7f377987 +PODFILE CHECKSUM: 2c1730c97ea13f1ea48b32e9c79de785b4f2f02f COCOAPODS: 1.16.2 diff --git a/demos/supabase-todolist-optional-sync/macos/Podfile b/demos/supabase-todolist-optional-sync/macos/Podfile index c795730d..b52666a1 100644 --- a/demos/supabase-todolist-optional-sync/macos/Podfile +++ b/demos/supabase-todolist-optional-sync/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/demos/supabase-todolist-optional-sync/macos/Podfile.lock b/demos/supabase-todolist-optional-sync/macos/Podfile.lock index ad0cf600..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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/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 50dd2421..c180081d 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,38 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) + - powersync-sqlite-core (~> 0.4.5) - 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.49.2): + - sqlite3/fts5 (3.50.4): - sqlite3/common - - sqlite3/math (3.49.2): + - sqlite3/math (3.50.4): - sqlite3/common - - sqlite3/perf-threadsafe (3.49.2): + - sqlite3/perf-threadsafe (3.50.4): - sqlite3/common - - sqlite3/rtree (3.49.2): + - sqlite3/rtree (3.50.4): + - sqlite3/common + - 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 @@ -73,17 +76,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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 + powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 + powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e 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/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 ad0cf600..920b3fa3 100644 --- a/demos/supabase-todolist/macos/Podfile.lock +++ b/demos/supabase-todolist/macos/Podfile.lock @@ -1,39 +1,42 @@ 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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - powersync-sqlite-core (~> 0.4.5) - 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.49.2): + - sqlite3/fts5 (3.50.4): - sqlite3/common - - sqlite3/math (3.49.2): + - sqlite3/math (3.50.4): - sqlite3/common - - sqlite3/perf-threadsafe (3.49.2): + - sqlite3/perf-threadsafe (3.50.4): - sqlite3/common - - sqlite3/rtree (3.49.2): + - sqlite3/rtree (3.50.4): + - sqlite3/common + - 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 @@ -68,16 +71,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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 + powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171 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-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 e0600993..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter - - powersync-sqlite-core (~> 0.4.4) - - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: ecbd37268a3705351178a05c81434592f0dcc6e5 - 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/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 ca81f490..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.4) + - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - FlutterMacOS - - powersync-sqlite-core (~> 0.4.4) + - 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: 954b7c4f068e21e6e759a7f487f0d7da4062e858 - powersync_flutter_libs: e8debf4b25a998c233cf7992d17cd49b407c56d1 + 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/packages/powersync_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart index 98020e33..1148c77d 100644 --- a/packages/powersync_core/lib/src/database/core_version.dart +++ b/packages/powersync_core/lib/src/database/core_version.dart @@ -60,7 +60,7 @@ extension type const PowerSyncCoreVersion((int, int, int) _tuple) { // 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, 4)); + static const minimum = PowerSyncCoreVersion((0, 4, 5)); /// The first version of the core extensions that this version of the Dart /// SDK doesn't support. diff --git a/packages/powersync_flutter_libs/android/build.gradle b/packages/powersync_flutter_libs/android/build.gradle index 0f0e0679..7a9dbf83 100644 --- a/packages/powersync_flutter_libs/android/build.gradle +++ b/packages/powersync_flutter_libs/android/build.gradle @@ -50,5 +50,5 @@ android { } dependencies { - implementation 'com.powersync:powersync-sqlite-core:0.4.4' + implementation 'com.powersync:powersync-sqlite-core:0.4.5' } diff --git a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec index 6d752c09..36e71b15 100644 --- a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec @@ -22,7 +22,7 @@ A new Flutter FFI plugin project. s.dependency 'Flutter' s.platform = :ios, '11.0' - s.dependency "powersync-sqlite-core", "~> 0.4.4" + s.dependency "powersync-sqlite-core", "~> 0.4.5" # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec index 19afd88e..5f5e40a9 100644 --- a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec @@ -21,7 +21,7 @@ A new Flutter FFI plugin project. s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.dependency "powersync-sqlite-core", "~> 0.4.4" + s.dependency "powersync-sqlite-core", "~> 0.4.5" s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/powersync_sqlcipher/example/ios/Podfile b/packages/powersync_sqlcipher/example/ios/Podfile index e549ee22..620e46eb 100644 --- a/packages/powersync_sqlcipher/example/ios/Podfile +++ b/packages/powersync_sqlcipher/example/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/packages/powersync_sqlcipher/example/macos/Podfile b/packages/powersync_sqlcipher/example/macos/Podfile index 29c8eb32..ff5ddb3b 100644 --- a/packages/powersync_sqlcipher/example/macos/Podfile +++ b/packages/powersync_sqlcipher/example/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/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index 8b68f63d..2de0572e 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -2,7 +2,7 @@ set -e SQLITE_VERSION="2.9.0" -POWERSYNC_CORE_VERSION="0.4.4" +POWERSYNC_CORE_VERSION="0.4.5" SQLITE_PATH="sqlite3.dart" if [ -d "$SQLITE_PATH" ]; then diff --git a/scripts/download_core_binary_demos.dart b/scripts/download_core_binary_demos.dart index e4cae3d9..78793332 100644 --- a/scripts/download_core_binary_demos.dart +++ b/scripts/download_core_binary_demos.dart @@ -3,7 +3,7 @@ import 'dart:io'; final coreUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.4'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.5'; void main() async { final powersyncLibsLinuxPath = "packages/powersync_flutter_libs/linux"; diff --git a/scripts/init_powersync_core_binary.dart b/scripts/init_powersync_core_binary.dart index 18c4b617..1c8833dd 100644 --- a/scripts/init_powersync_core_binary.dart +++ b/scripts/init_powersync_core_binary.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:melos/melos.dart'; final sqliteUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.4'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.5'; void main() async { final sqliteCoreFilename = getLibraryForPlatform(); From 9b7e6648a29ffed5a41ada207e6d7b22251b0f92 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 1 Sep 2025 11:22:25 +0200 Subject: [PATCH 46/62] Add swiftpm definitions --- .gitignore | 1 + .../powersync_flutter_libs.podspec | 13 +++++---- .../powersync_flutter_libs/Package.resolved | 14 +++++++++ .../powersync_flutter_libs/Package.swift | 29 +++++++++++++++++++ .../PowerSyncFLutteLibsPlugin.swift | 13 +++++++++ .../ios/Classes/PowersyncFlutterLibsPlugin.h | 4 --- .../ios/Classes/PowersyncFlutterLibsPlugin.m | 7 ----- .../Classes/PowersyncFlutterLibsPlugin.h | 4 --- .../Classes/PowersyncFlutterLibsPlugin.m | 7 ----- .../macos/powersync_flutter_libs.podspec | 29 ------------------- packages/powersync_flutter_libs/pubspec.yaml | 6 ++-- 11 files changed, 69 insertions(+), 58 deletions(-) rename packages/powersync_flutter_libs/{ios => darwin}/powersync_flutter_libs.podspec (72%) create mode 100644 packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved create mode 100644 packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift create mode 100644 packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFLutteLibsPlugin.swift delete mode 100644 packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.h delete mode 100644 packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.m delete mode 100644 packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.h delete mode 100644 packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.m delete mode 100644 packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec diff --git a/.gitignore b/.gitignore index c47e4141..dfe71b44 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ pubspec_overrides.yaml .flutter-plugins-dependencies .flutter-plugins build +.build # Shared assets assets/* diff --git a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec similarity index 72% rename from packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec rename to packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec index 36e71b15..427e69bb 100644 --- a/packages/powersync_flutter_libs/ios/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec @@ -9,19 +9,22 @@ Pod::Spec.new do |s| s.description = <<-DESC A new Flutter FFI plugin project. DESC - s.homepage = 'http://example.com' + s.homepage = 'https://powersync.com' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Journey Mobile, Inc' => 'hello@powersync.com' } # This will ensure the source files in Classes/ are included in the native # builds of apps using this FFI plugin. Podspec does not support relative # paths, so Classes contains a forwarder C file that relatively imports # `../src/*` so that the C sources can be shared among all target platforms. s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.platform = :ios, '11.0' + s.source_files = 'powersync_flutter_libs/Sources/powersync_flutter_libs/**/*.swift' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.14' + # NOTE: Always update Package.swift as well when updating this! s.dependency "powersync-sqlite-core", "~> 0.4.5" # Flutter.framework does not contain a i386 slice. diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved new file mode 100644 index 00000000..89260492 --- /dev/null +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "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/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift new file mode 100644 index 00000000..8c9d857a --- /dev/null +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "powersync_flutter_libs", + platforms: [ + .iOS("12.0"), + .macOS("10.14") + ], + products: [ + .library(name: "powersync_flutter_libs", type: .static, targets: ["powersync_flutter_libs"]) + ], + dependencies: [ + .package( + url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", + // Note: Always update podspec as well when updating this. + exact: "0.4.5" + ) + ], + targets: [ + .target( + name: "powersync_flutter_libs", + dependencies: ["PowerSyncSQLiteCore"], + resources: [] + ) + ] +) diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFLutteLibsPlugin.swift b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFLutteLibsPlugin.swift new file mode 100644 index 00000000..f3a14c12 --- /dev/null +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFLutteLibsPlugin.swift @@ -0,0 +1,13 @@ +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +public class PowersyncFlutterLibsPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + // There's no native code, we just want to link the core extension + } +} diff --git a/packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.h b/packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.h deleted file mode 100644 index 0ddfb7f7..00000000 --- a/packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface PowersyncFlutterLibsPlugin : NSObject -@end diff --git a/packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.m b/packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.m deleted file mode 100644 index 74d2e35c..00000000 --- a/packages/powersync_flutter_libs/ios/Classes/PowersyncFlutterLibsPlugin.m +++ /dev/null @@ -1,7 +0,0 @@ -#import "PowersyncFlutterLibsPlugin.h" - -@implementation PowersyncFlutterLibsPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - -} -@end diff --git a/packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.h b/packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.h deleted file mode 100644 index 9725b1d6..00000000 --- a/packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface PowersyncFlutterLibsPlugin : NSObject -@end diff --git a/packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.m b/packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.m deleted file mode 100644 index 74d2e35c..00000000 --- a/packages/powersync_flutter_libs/macos/Classes/PowersyncFlutterLibsPlugin.m +++ /dev/null @@ -1,7 +0,0 @@ -#import "PowersyncFlutterLibsPlugin.h" - -@implementation PowersyncFlutterLibsPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - -} -@end diff --git a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec deleted file mode 100644 index 5f5e40a9..00000000 --- a/packages/powersync_flutter_libs/macos/powersync_flutter_libs.podspec +++ /dev/null @@ -1,29 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint powersync_flutter_libs.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'powersync_flutter_libs' - s.version = '0.0.1' - s.summary = 'A new Flutter FFI plugin project.' - s.description = <<-DESC -A new Flutter FFI plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - - # This will ensure the source files in Classes/ are included in the native - # builds of apps using this FFI plugin. Podspec does not support relative - # paths, so Classes contains a forwarder C file that relatively imports - # `../src/*` so that the C sources can be shared among all target platforms. - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - - s.dependency "powersync-sqlite-core", "~> 0.4.5" - - s.platform = :osx, '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.swift_version = '5.0' -end diff --git a/packages/powersync_flutter_libs/pubspec.yaml b/packages/powersync_flutter_libs/pubspec.yaml index 9753173a..2515531c 100644 --- a/packages/powersync_flutter_libs/pubspec.yaml +++ b/packages/powersync_flutter_libs/pubspec.yaml @@ -25,9 +25,11 @@ flutter: pluginClass: PowersyncFlutterLibsPlugin ios: pluginClass: PowersyncFlutterLibsPlugin - linux: - pluginClass: PowersyncFlutterLibsPlugin + sharedDarwinSource: true macos: pluginClass: PowersyncFlutterLibsPlugin + sharedDarwinSource: true + linux: + pluginClass: PowersyncFlutterLibsPlugin windows: pluginClass: PowersyncFlutterLibsPlugin From b5726f2fc169752a67f20d3ea97ac5989909998c Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 1 Sep 2025 11:40:17 +0200 Subject: [PATCH 47/62] Remove cocoapods from drift example --- demos/supabase-todolist-drift/ios/Podfile | 44 ------ .../supabase-todolist-drift/ios/Podfile.lock | 89 ------------ .../ios/Runner.xcodeproj/project.pbxproj | 128 ++++------------- .../xcshareddata/swiftpm/Package.resolved | 22 +++ .../xcshareddata/xcschemes/Runner.xcscheme | 18 +++ .../xcshareddata/swiftpm/Package.resolved | 22 +++ demos/supabase-todolist-drift/macos/Podfile | 43 ------ .../macos/Podfile.lock | 83 ----------- .../macos/Runner.xcodeproj/project.pbxproj | 134 ++++-------------- .../xcshareddata/swiftpm/Package.resolved | 22 +++ .../xcshareddata/xcschemes/Runner.xcscheme | 18 +++ .../xcshareddata/swiftpm/Package.resolved | 22 +++ .../powersync_flutter_libs/Package.swift | 10 +- 13 files changed, 181 insertions(+), 474 deletions(-) delete mode 100644 demos/supabase-todolist-drift/ios/Podfile delete mode 100644 demos/supabase-todolist-drift/ios/Podfile.lock create mode 100644 demos/supabase-todolist-drift/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 demos/supabase-todolist-drift/macos/Podfile delete mode 100644 demos/supabase-todolist-drift/macos/Podfile.lock create mode 100644 demos/supabase-todolist-drift/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 demos/supabase-todolist-drift/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/demos/supabase-todolist-drift/ios/Podfile b/demos/supabase-todolist-drift/ios/Podfile deleted file mode 100644 index 3e44f9c6..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, '13.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 498bff0c..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.5) - - powersync_flutter_libs (0.0.1): - - Flutter - - powersync-sqlite-core (~> 0.4.5) - - 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: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 - powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 - sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - -PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 - -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 39ba8188..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; @@ -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; @@ -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 e3773d42..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 @@ + + + + + + + + + + :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 6983b2da..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.5) - - powersync_flutter_libs (0.0.1): - - FlutterMacOS - - powersync-sqlite-core (~> 0.4.5) - - 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: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - 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: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 - -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..8e5eb05f --- /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" : "00776db5157c8648671b00e6673603144fafbfeb", + "version" : "0.4.5" + } + } + ], + "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 @@ + + + + + + + + + + Date: Mon, 1 Sep 2025 11:56:10 +0200 Subject: [PATCH 48/62] Update supabase-todolist --- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- demos/supabase-todolist/ios/Podfile.lock | 7 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- demos/supabase-todolist/macos/Podfile.lock | 7 +- .../macos/Runner.xcodeproj/project.pbxproj | 6 +- demos/supabase-todolist/pubspec.lock | 140 +++++++++--------- demos/supabase-todolist/pubspec.yaml | 4 + ...swift => PowerSyncFlutterLibsPlugin.swift} | 0 8 files changed, 89 insertions(+), 83 deletions(-) rename packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/{PowerSyncFLutteLibsPlugin.swift => PowerSyncFlutterLibsPlugin.swift} (100%) 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.lock b/demos/supabase-todolist/ios/Podfile.lock index c180081d..db1849fb 100644 --- a/demos/supabase-todolist/ios/Podfile.lock +++ b/demos/supabase-todolist/ios/Podfile.lock @@ -10,6 +10,7 @@ PODS: - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): - Flutter + - FlutterMacOS - powersync-sqlite-core (~> 0.4.5) - shared_preferences_foundation (0.0.1): - Flutter @@ -47,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`) @@ -67,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: @@ -81,7 +82,7 @@ SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 - powersync_flutter_libs: 7684a62208907328906eb932f1fc8b3d8879974e + powersync_flutter_libs: b2cd15651535031defb8f2799fa05e07d9514ecd shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 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/macos/Podfile.lock b/demos/supabase-todolist/macos/Podfile.lock index 920b3fa3..9a50fb9b 100644 --- a/demos/supabase-todolist/macos/Podfile.lock +++ b/demos/supabase-todolist/macos/Podfile.lock @@ -7,6 +7,7 @@ PODS: - FlutterMacOS - powersync-sqlite-core (0.4.5) - powersync_flutter_libs (0.0.1): + - Flutter - FlutterMacOS - powersync-sqlite-core (~> 0.4.5) - shared_preferences_foundation (0.0.1): @@ -44,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`) @@ -62,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: @@ -75,7 +76,7 @@ SPEC CHECKSUMS: FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 - powersync_flutter_libs: 41d8a7b193abf15e46f95f0ec1229d86b6893171 + powersync_flutter_libs: b2cd15651535031defb8f2799fa05e07d9514ecd shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 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/pubspec.lock b/demos/supabase-todolist/pubspec.lock index cea2023c..a16ca13c 100644 --- a/demos/supabase-todolist/pubspec.lock +++ b/demos/supabase-todolist/pubspec.lock @@ -5,10 +5,10 @@ packages: 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 +77,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 +117,10 @@ 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" clock: dependency: transitive description: @@ -202,10 +202,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 @@ -220,18 +220,18 @@ packages: dependency: transitive description: name: functions_client - sha256: b410e4d609522357396cd84bb9a8f6e3a4561b5f7d3ce82267f6f1c2af42f16b + sha256: "38e5049d4ca5b3482c606d8bfe82183aa24c9650ef1fa0582ab5957a947b937f" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" 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 +244,10 @@ 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_parser: dependency: transitive description: @@ -284,26 +284,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: @@ -380,18 +380,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 +420,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: @@ -444,10 +444,10 @@ packages: 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 +462,28 @@ packages: path: "../../packages/powersync" relative: true source: path - version: "1.15.0" + version: "1.15.2" powersync_attachments_helper: dependency: "direct main" description: path: "../../packages/powersync_attachments_helper" relative: true source: path - version: "0.6.18+11" + version: "0.6.19" powersync_core: dependency: "direct overridden" description: path: "../../packages/powersync_core" relative: true source: path - version: "1.5.0" + version: "1.5.2" powersync_flutter_libs: dependency: "direct overridden" description: path: "../../packages/powersync_flutter_libs" relative: true source: path - version: "0.4.10" + version: "0.4.11" pub_semver: dependency: transitive description: @@ -504,10 +504,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 +536,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: @@ -605,34 +605,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: name: sqlite_async - sha256: "9332aedd311a19dd215dcb55729bc68dc587dc7655b569ab8819b68ee0be0082" + sha256: "6116bfc6aef6ce77730b478385ba4a58873df45721f6a9bc6ffabf39b6576e36" url: "https://pub.dev" source: hosted - version: "0.11.7" + version: "0.12.1" stack_trace: dependency: transitive description: @@ -645,10 +645,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: @@ -677,18 +677,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: @@ -701,10 +701,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: @@ -725,26 +725,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: @@ -757,10 +757,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: @@ -797,18 +797,18 @@ 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" web: dependency: transitive description: @@ -845,10 +845,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: @@ -866,5 +866,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 cb94c2bb..ddb02717 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -29,3 +29,7 @@ dev_dependencies: 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/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFLutteLibsPlugin.swift b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFlutterLibsPlugin.swift similarity index 100% rename from packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFLutteLibsPlugin.swift rename to packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Sources/powersync_flutter_libs/PowerSyncFlutterLibsPlugin.swift From 95f5389f0e2579bdb117f84ff8c4543262e35480 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 1 Sep 2025 12:08:04 +0200 Subject: [PATCH 49/62] Explicitly enable swiftpm --- demos/supabase-todolist-drift/pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 78a470c9..cf0c3f4f 100644 --- a/demos/supabase-todolist-drift/pubspec.yaml +++ b/demos/supabase-todolist-drift/pubspec.yaml @@ -41,3 +41,5 @@ dev_dependencies: flutter: uses-material-design: true + config: + enable-swift-package-manager: true From 6a2ff65a5c8680ae5cac4fdc14b5f8e138cd7aaf Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 1 Sep 2025 12:37:47 +0200 Subject: [PATCH 50/62] Update deployment target for podspec --- .../darwin/powersync_flutter_libs.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec index 427e69bb..caa91fbb 100644 --- a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec @@ -21,8 +21,8 @@ A new Flutter FFI plugin project. s.source_files = 'powersync_flutter_libs/Sources/powersync_flutter_libs/**/*.swift' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' - s.ios.deployment_target = '12.0' - s.osx.deployment_target = '10.14' + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' # NOTE: Always update Package.swift as well when updating this! s.dependency "powersync-sqlite-core", "~> 0.4.5" From f5eb871770f77855fdb5132a01357a2deeb5246b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 18 Sep 2025 10:20:05 +0200 Subject: [PATCH 51/62] Update core extension to 0.4.6 --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- demos/supabase-todolist/ios/Podfile.lock | 8 ++++---- demos/supabase-todolist/macos/Podfile.lock | 8 ++++---- .../powersync_core/lib/src/database/core_version.dart | 11 +++++++---- packages/powersync_flutter_libs/android/build.gradle | 2 +- .../darwin/powersync_flutter_libs.podspec | 2 +- .../darwin/powersync_flutter_libs/Package.resolved | 4 ++-- .../darwin/powersync_flutter_libs/Package.swift | 2 +- packages/sqlite3_wasm_build/build.sh | 2 +- scripts/download_core_binary_demos.dart | 2 +- scripts/init_powersync_core_binary.dart | 2 +- 12 files changed, 27 insertions(+), 24 deletions(-) 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 index 8e5eb05f..759d5a05 100644 --- 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 @@ -13,8 +13,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "00776db5157c8648671b00e6673603144fafbfeb", - "version" : "0.4.5" + "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6", + "version" : "0.4.6" } } ], diff --git a/demos/supabase-todolist-drift/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/demos/supabase-todolist-drift/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8e5eb05f..759d5a05 100644 --- a/demos/supabase-todolist-drift/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/demos/supabase-todolist-drift/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -13,8 +13,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "00776db5157c8648671b00e6673603144fafbfeb", - "version" : "0.4.5" + "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6", + "version" : "0.4.6" } } ], diff --git a/demos/supabase-todolist/ios/Podfile.lock b/demos/supabase-todolist/ios/Podfile.lock index db1849fb..73dadf31 100644 --- a/demos/supabase-todolist/ios/Podfile.lock +++ b/demos/supabase-todolist/ios/Podfile.lock @@ -7,11 +7,11 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.5) + - powersync-sqlite-core (0.4.6) - powersync_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (~> 0.4.5) + - powersync-sqlite-core (~> 0.4.6) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -81,8 +81,8 @@ SPEC CHECKSUMS: camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 - powersync_flutter_libs: b2cd15651535031defb8f2799fa05e07d9514ecd + powersync-sqlite-core: 42c4a42a692b3b770a5488778789430d67a39b49 + powersync_flutter_libs: 19fc6b96ff8155ffea72a08990f6c9f2e712b8a6 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 diff --git a/demos/supabase-todolist/macos/Podfile.lock b/demos/supabase-todolist/macos/Podfile.lock index 9a50fb9b..51b6fdad 100644 --- a/demos/supabase-todolist/macos/Podfile.lock +++ b/demos/supabase-todolist/macos/Podfile.lock @@ -5,11 +5,11 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (0.4.5) + - powersync-sqlite-core (0.4.6) - powersync_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - powersync-sqlite-core (~> 0.4.5) + - powersync-sqlite-core (~> 0.4.6) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -75,8 +75,8 @@ SPEC CHECKSUMS: app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - powersync-sqlite-core: 6f32860379009d2a37cadc9e9427a431bdbd83c8 - powersync_flutter_libs: b2cd15651535031defb8f2799fa05e07d9514ecd + powersync-sqlite-core: 42c4a42a692b3b770a5488778789430d67a39b49 + powersync_flutter_libs: 19fc6b96ff8155ffea72a08990f6c9f2e712b8a6 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 diff --git a/packages/powersync_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart index 1148c77d..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, 5)); + // 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_flutter_libs/android/build.gradle b/packages/powersync_flutter_libs/android/build.gradle index 7a9dbf83..d6c0e3c1 100644 --- a/packages/powersync_flutter_libs/android/build.gradle +++ b/packages/powersync_flutter_libs/android/build.gradle @@ -50,5 +50,5 @@ android { } dependencies { - implementation 'com.powersync:powersync-sqlite-core:0.4.5' + implementation 'com.powersync:powersync-sqlite-core:0.4.6' } diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec index caa91fbb..b93848a8 100644 --- a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec @@ -25,7 +25,7 @@ A new Flutter FFI plugin project. s.osx.deployment_target = '10.15' # NOTE: Always update Package.swift as well when updating this! - s.dependency "powersync-sqlite-core", "~> 0.4.5" + s.dependency "powersync-sqlite-core", "~> 0.4.6" # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved index 89260492..a62a2d5c 100644 --- a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "00776db5157c8648671b00e6673603144fafbfeb", - "version" : "0.4.5" + "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6", + "version" : "0.4.6" } } ], diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift index 9af756e9..cfa880df 100644 --- a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs/Package.swift @@ -16,7 +16,7 @@ let package = Package( .package( url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", // Note: Always update podspec as well when updating this. - exact: "0.4.5" + exact: "0.4.6" ) ], targets: [ diff --git a/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index 2de0572e..eefbb4ae 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -2,7 +2,7 @@ set -e SQLITE_VERSION="2.9.0" -POWERSYNC_CORE_VERSION="0.4.5" +POWERSYNC_CORE_VERSION="0.4.6" SQLITE_PATH="sqlite3.dart" if [ -d "$SQLITE_PATH" ]; then diff --git a/scripts/download_core_binary_demos.dart b/scripts/download_core_binary_demos.dart index 78793332..f5453ce1 100644 --- a/scripts/download_core_binary_demos.dart +++ b/scripts/download_core_binary_demos.dart @@ -3,7 +3,7 @@ import 'dart:io'; final coreUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.5'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.6'; void main() async { final powersyncLibsLinuxPath = "packages/powersync_flutter_libs/linux"; diff --git a/scripts/init_powersync_core_binary.dart b/scripts/init_powersync_core_binary.dart index 1c8833dd..0c985895 100644 --- a/scripts/init_powersync_core_binary.dart +++ b/scripts/init_powersync_core_binary.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:melos/melos.dart'; final sqliteUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.5'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.6'; void main() async { final sqliteCoreFilename = getLibraryForPlatform(); From 052a7dd12a872dca295eff38fcc6691c54754408 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 21 Sep 2025 20:09:50 +0200 Subject: [PATCH 52/62] Support build_web_compilers --- .../src/open_factory/abstract_powersync_open_factory.dart | 7 ++++++- .../lib/src/open_factory/native/native_open_factory.dart | 8 +++++++- packages/powersync_core/pubspec.yaml | 1 - 3 files changed, 13 insertions(+), 3 deletions(-) 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/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 2b1bb090..7a4c9f7a 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: sqlite3: ^2.4.6 # We implement a database controller, which is an interface of sqlite3_web. sqlite3_web: ^0.3.2 - universal_io: ^2.0.0 meta: ^1.0.0 http: ^1.5.0 uuid: ^4.2.0 From a2b8d76d8419877cb822bf04826ec8175e189ec8 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 22 Sep 2025 16:05:53 +0200 Subject: [PATCH 53/62] Update `sqlite3_flutter_libs` dependency --- packages/powersync/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml index fa7bdc11..ad31d0d3 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - sqlite3_flutter_libs: ^0.5.23 + sqlite3_flutter_libs: ^0.5.39 powersync_core: ^1.5.2 powersync_flutter_libs: ^0.4.11 collection: ^1.17.0 From a34349ce860a85d62aa574f89d5cac502846b5b2 Mon Sep 17 00:00:00 2001 From: dean-journeyapps Date: Tue, 30 Sep 2025 10:04:22 +0200 Subject: [PATCH 54/62] Attachment package refactor (#311) --- .gitignore | 1 + .../lib/attachments/local_storage_native.dart | 8 + .../local_storage_unsupported.dart | 7 + .../lib/attachments/photo_capture_widget.dart | 35 +- .../lib/attachments/photo_widget.dart | 13 +- .../lib/attachments/queue.dart | 128 ++-- .../attachments/remote_storage_adapter.dart | 77 +- .../supabase-todolist/lib/models/schema.dart | 9 +- .../lib/widgets/todo_item_widget.dart | 8 +- demos/supabase-todolist/pubspec.lock | 188 ++++- demos/supabase-todolist/pubspec.yaml | 3 +- .../powersync_attachments_stream/pubspec.lock | 669 ------------------ packages/powersync_core/dartdoc_options.yaml | 5 + packages/powersync_core/doc/attachments.md | 122 ++++ .../lib/attachments/attachments.dart | 12 + .../powersync_core/lib/attachments/io.dart | 12 + .../lib/src/attachments/attachment.dart | 197 ++++++ .../attachments/attachment_queue_service.dart | 443 ++++++++++++ .../implementations/attachment_context.dart | 160 +++++ .../implementations/attachment_service.dart | 75 ++ .../lib/src/attachments/io_local_storage.dart | 93 +++ .../lib/src/attachments/local_storage.dart | 102 +++ .../lib/src/attachments/remote_storage.dart | 34 + .../src/attachments/sync/syncing_service.dart | 281 ++++++++ .../src/attachments/sync_error_handler.dart | 102 +++ packages/powersync_core/pubspec.yaml | 2 + .../test/attachments/attachment_test.dart | 372 ++++++++++ .../test/attachments/local_storage_test.dart | 365 ++++++++++ .../test/utils/abstract_test_utils.dart | 10 +- 29 files changed, 2737 insertions(+), 796 deletions(-) create mode 100644 demos/supabase-todolist/lib/attachments/local_storage_native.dart create mode 100644 demos/supabase-todolist/lib/attachments/local_storage_unsupported.dart delete mode 100644 packages/powersync_attachments_stream/pubspec.lock create mode 100644 packages/powersync_core/dartdoc_options.yaml create mode 100644 packages/powersync_core/doc/attachments.md create mode 100644 packages/powersync_core/lib/attachments/attachments.dart create mode 100644 packages/powersync_core/lib/attachments/io.dart create mode 100644 packages/powersync_core/lib/src/attachments/attachment.dart create mode 100644 packages/powersync_core/lib/src/attachments/attachment_queue_service.dart create mode 100644 packages/powersync_core/lib/src/attachments/implementations/attachment_context.dart create mode 100644 packages/powersync_core/lib/src/attachments/implementations/attachment_service.dart create mode 100644 packages/powersync_core/lib/src/attachments/io_local_storage.dart create mode 100644 packages/powersync_core/lib/src/attachments/local_storage.dart create mode 100644 packages/powersync_core/lib/src/attachments/remote_storage.dart create mode 100644 packages/powersync_core/lib/src/attachments/sync/syncing_service.dart create mode 100644 packages/powersync_core/lib/src/attachments/sync_error_handler.dart create mode 100644 packages/powersync_core/test/attachments/attachment_test.dart create mode 100644 packages/powersync_core/test/attachments/local_storage_test.dart diff --git a/.gitignore b/.gitignore index dfe71b44..95fba1e8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ pubspec_overrides.yaml .flutter-plugins-dependencies .flutter-plugins build +**/doc/api .build # Shared assets diff --git a/demos/supabase-todolist/lib/attachments/local_storage_native.dart b/demos/supabase-todolist/lib/attachments/local_storage_native.dart new file mode 100644 index 00000000..31c35b96 --- /dev/null +++ b/demos/supabase-todolist/lib/attachments/local_storage_native.dart @@ -0,0 +1,8 @@ +import 'package:path_provider/path_provider.dart'; +import 'package:powersync_core/attachments/attachments.dart'; +import 'package:powersync_core/attachments/io.dart'; + +Future 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..80460daf 100644 --- a/demos/supabase-todolist/lib/attachments/queue.dart +++ b/demos/supabase-todolist/lib/attachments/queue.dart @@ -1,90 +1,64 @@ 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(); +final logger = Logger('AttachmentQueue'); -/// 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'; +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', + ) + ], + ), + ); - 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); - } + await attachmentQueue.startSync(); +} - @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); - }); - } +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], + ); + }, + ); } -initializeAttachmentQueue(PowerSyncDatabase db) async { - attachmentQueue = PhotoAttachmentQueue(db, remoteStorage); - await attachmentQueue.init(); +Future deletePhotoAttachment(String fileId) async { + return await attachmentQueue.deleteFile( + attachmentId: fileId, + updateHook: (context, attachment) async { + // Optionally update relationships in the same transaction + }, + ); } 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/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/pubspec.lock b/demos/supabase-todolist/pubspec.lock index a16ca13c..24fad347 100644 --- a/demos/supabase-todolist/pubspec.lock +++ b/demos/supabase-todolist/pubspec.lock @@ -1,6 +1,22 @@ # 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: @@ -121,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -216,6 +256,14 @@ 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: @@ -224,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -248,6 +304,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -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: @@ -440,6 +536,14 @@ 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: @@ -464,14 +568,14 @@ packages: source: path 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.19" powersync_core: - dependency: "direct overridden" + dependency: "direct main" description: path: "../../packages/powersync_core" relative: true @@ -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: @@ -697,6 +849,14 @@ 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: @@ -705,6 +865,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -809,6 +977,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -833,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: diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index ddb02717..53d61b7a 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.19 powersync: ^1.15.2 + powersync_core: ^1.5.2 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 @@ -26,6 +26,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^3.0.1 + test: ^1.25.15 flutter: uses-material-design: true diff --git a/packages/powersync_attachments_stream/pubspec.lock b/packages/powersync_attachments_stream/pubspec.lock deleted file mode 100644 index ae003e13..00000000 --- a/packages/powersync_attachments_stream/pubspec.lock +++ /dev/null @@ -1,669 +0,0 @@ -# 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: b1ade5707ab7a90dfd519eaac78a7184341d19adb6096c68d499b59c7c6cf880 - url: "https://pub.dev" - source: hosted - version: "7.7.0" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - 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: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - 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" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_test: - dependency: "direct dev" - 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" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - http: - dependency: transitive - description: - name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" - url: "https://pub.dev" - source: hosted - version: "1.4.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: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - 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: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" - url: "https://pub.dev" - source: hosted - version: "10.0.9" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - logging: - dependency: "direct main" - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - mutex: - dependency: transitive - description: - name: mutex - sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" - 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: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 - url: "https://pub.dev" - source: hosted - version: "2.2.17" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - 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" - powersync_core: - dependency: "direct main" - description: - name: powersync_core - sha256: d8ae292bc77f0a96f44c6cc2911d1b781760a87a919e5045e75458cba83bb759 - url: "https://pub.dev" - source: hosted - version: "1.5.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - 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: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - sqlite3: - dependency: transitive - description: - name: sqlite3 - sha256: "608b56d594e4c8498c972c8f1507209f9fd74939971b948ddbbfbfd1c9cb3c15" - url: "https://pub.dev" - source: hosted - version: "2.7.7" - sqlite3_web: - dependency: transitive - description: - name: sqlite3_web - sha256: "967e076442f7e1233bd7241ca61f3efe4c7fc168dac0f38411bdb3bdf471eb3c" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - sqlite_async: - dependency: "direct main" - description: - name: sqlite_async - sha256: "9332aedd311a19dd215dcb55729bc68dc587dc7655b569ab8819b68ee0be0082" - url: "https://pub.dev" - source: hosted - version: "0.11.7" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test: - dependency: "direct dev" - description: - name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" - url: "https://pub.dev" - source: hosted - version: "1.25.15" - test_api: - dependency: transitive - description: - name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" - test_core: - dependency: transitive - description: - name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" - url: "https://pub.dev" - source: hosted - version: "0.6.8" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" - url: "https://pub.dev" - source: hosted - version: "2.2.2" - uuid: - dependency: transitive - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - 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: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" -sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.27.0" 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..aad9b5cb --- /dev/null +++ b/packages/powersync_core/doc/attachments.md @@ -0,0 +1,122 @@ +## 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 primary data model, and then download it from a secondary data store such as S3. +Because binary data is not directly stored in the source database in this model, we call these files _attachments_. + +## Alpha release + +The attachment helpers 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 libraries 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. + +### 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/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/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 7a4c9f7a..fa3dd02a 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -39,6 +39,8 @@ dev_dependencies: stream_channel: ^2.1.2 fake_async: ^1.3.3 bson: ^5.0.7 + test_descriptor: ^2.0.2 + mockito: ^5.5.0 platforms: android: diff --git a/packages/powersync_core/test/attachments/attachment_test.dart b/packages/powersync_core/test/attachments/attachment_test.dart new file mode 100644 index 00000000..90cd1f37 --- /dev/null +++ b/packages/powersync_core/test/attachments/attachment_test.dart @@ -0,0 +1,372 @@ +import 'dart:typed_data'; + +import 'package:async/async.dart'; +import 'package:logging/logging.dart'; +import 'package:mockito/mockito.dart'; +import 'package:powersync_core/attachments/attachments.dart'; +import 'package:powersync_core/powersync_core.dart'; +import 'package:test/test.dart'; + +import '../utils/abstract_test_utils.dart'; +import '../utils/test_utils_impl.dart'; + +void main() { + late TestPowerSyncFactory factory; + late PowerSyncDatabase db; + late MockRemoteStorage remoteStorage; + late LocalStorage localStorage; + late AttachmentQueue queue; + late StreamQueue> attachments; + + Stream> watchAttachments() { + return db + .watch('SELECT photo_id FROM users WHERE photo_id IS NOT NULL') + .map( + (rs) => [ + for (final row in rs) + WatchedAttachmentItem( + id: row['photo_id'] as String, fileExtension: 'jpg') + ], + ); + } + + setUpAll(() async { + factory = await TestUtils().testFactory(); + }); + + setUp(() async { + remoteStorage = MockRemoteStorage(); + localStorage = LocalStorage.inMemory(); + + final (raw, database) = await factory.openInMemoryDatabase( + schema: _schema, + // Uncomment to see test logs + logger: Logger.detached('PowerSyncTest'), + ); + await database.initialize(); + db = database; + + queue = AttachmentQueue( + db: db, + remoteStorage: remoteStorage, + watchAttachments: watchAttachments, + localStorage: localStorage, + archivedCacheLimit: 0, + ); + + attachments = StreamQueue(db.attachments); + await expectLater(attachments, emits(isEmpty)); + }); + + tearDown(() async { + await attachments.cancel(); + await queue.stopSyncing(); + await queue.close(); + + await db.close(); + }); + + test('downloads attachments', () async { + await queue.startSync(); + + // Create a user with a photo_id specified. Since we didn't save an + // attachment before assigning a photo_id, this is equivalent to reuqiring + // an attachment download. + await db.execute( + 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', + ['steven', 'steven@journeyapps.com'], + ); + + var [attachment] = await attachments.next; + if (attachment.state == AttachmentState.queuedDownload) { + // Depending on timing with the queue scanning for items asynchronously, + // we may see a queued download or a synced event initially. + [attachment] = await attachments.next; + } + + expect(attachment.state, AttachmentState.synced); + final localUri = attachment.localUri!; + + // A download should he been attempted for this file. + verify(remoteStorage.downloadFile(argThat(isAttachment(attachment.id)))); + + // A file should now exist. + expect(await localStorage.fileExists(localUri), isTrue); + + // Now clear the user's photo_id, which should archive the attachment. + await db.execute('UPDATE users SET photo_id = NULL'); + + var nextAttachment = (await attachments.next).firstOrNull; + if (nextAttachment != null) { + expect(nextAttachment.state, AttachmentState.archived); + nextAttachment = (await attachments.next).firstOrNull; + } + + expect(nextAttachment, isNull); + + // File should have been deleted too + expect(await localStorage.fileExists(localUri), isFalse); + }); + + test('stores relative paths', () async { + // Regression test we had in the Kotlin/Swift implementation: + // https://github.com/powersync-ja/powersync-swift/pull/74 + await queue.startSync(); + await db.execute( + 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, ?)', + ['steven', 'steven@journeyapps.com', 'picture_id'], + ); + + // Wait for attachment to sync. + await expectLater( + attachments, + emitsThrough([ + isA() + .having((e) => e.state, 'state', AttachmentState.synced) + ])); + + expect(await localStorage.fileExists('picture_id.jpg'), isTrue); + }); + + test('recovers from deleted local files', () async { + // Create an attachments record which has an invalid local_uri. + await db.execute( + 'INSERT OR REPLACE INTO attachments_queue ' + '(id, timestamp, filename, local_uri, media_type, size, state, has_synced, meta_data) ' + 'VALUES (uuid(), current_timestamp, ?, ?, ?, ?, ?, ?, ?)', + [ + 'attachment.jpg', + 'invalid/dir/attachment.jpg', + 'application/jpeg', + 1, + AttachmentState.synced.toInt(), + 1, + "" + ], + ); + await attachments.next; + + queue = AttachmentQueue( + db: db, + remoteStorage: remoteStorage, + watchAttachments: watchAttachments, + localStorage: localStorage, + archivedCacheLimit: 1, + ); + + // The attachment should be marked as archived, and the local URI should be + // removed. + await queue.startSync(); + + final [attachment] = await attachments.next; + expect(attachment.filename, 'attachment.jpg'); + expect(attachment.localUri, isNull); + expect(attachment.state, AttachmentState.archived); + }); + + test('uploads attachments', () async { + await queue.startSync(); + + final record = await queue.saveFile( + data: Stream.value(Uint8List(123)), + mediaType: 'image/jpg', + updateHook: (tx, attachment) async { + await tx.execute( + 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, ?);', + ['steven', 'steven@journeyapps.com', attachment.id], + ); + }, + ); + expect(record.size, 123); + + var [attachment] = await attachments.next; + if (attachment.state == AttachmentState.queuedUpload) { + // Wait for it to be synced + [attachment] = await attachments.next; + } + + expect(attachment.state, AttachmentState.synced); + + // An upload should have been attempted for this file. + verify(remoteStorage.uploadFile(any, argThat(isAttachment(record.id)))); + expect(await localStorage.fileExists(record.localUri!), isTrue); + + // Now clear the user's photo_id, which should archive the attachment. + await db.execute('UPDATE users SET photo_id = NULL'); + + // Should delete attachment from database + await expectLater(attachments, emitsThrough(isEmpty)); + + // File should have been deleted too + expect(await localStorage.fileExists(record.localUri!), isFalse); + }); + + test('delete attachments', () async { + await queue.startSync(); + + final id = await queue.generateAttachmentId(); + await db.execute( + 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, ?)', + ['steven', 'steven@journeyapps.com', id], + ); + + // Wait for the attachment to be synced. + await expectLater( + attachments, + emitsThrough([ + isA() + .having((e) => e.state, 'state', AttachmentState.synced) + ]), + ); + + await queue.deleteFile( + attachmentId: id, + updateHook: (tx, attachment) async { + await tx.execute( + 'UPDATE users SET photo_id = NULL WHERE photo_id = ?', + [attachment.id], + ); + }, + ); + + // Record should be deleted. + await expectLater(attachments, emitsThrough(isEmpty)); + verify(remoteStorage.deleteFile(argThat(isAttachment(id)))); + }); + + test('cached download', () async { + queue = AttachmentQueue( + db: db, + remoteStorage: remoteStorage, + watchAttachments: watchAttachments, + localStorage: localStorage, + archivedCacheLimit: 10, + ); + + await queue.startSync(); + + // Create attachment and wait for download. + await db.execute( + 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', + ['steven', 'steven@journeyapps.com'], + ); + await expectLater( + attachments, + emitsThrough([ + isA() + .having((e) => e.state, 'state', AttachmentState.synced) + ]), + ); + final [id as String, localUri as String] = + (await db.get('SELECT id, local_uri FROM attachments_queue')).values; + verify(remoteStorage.downloadFile(argThat(isAttachment(id)))); + expect(await localStorage.fileExists(localUri), isTrue); + + // Archive attachment by not referencing it anymore. + await db.execute('UPDATE users SET photo_id = NULL'); + await expectLater( + attachments, + emitsThrough([ + isA() + .having((e) => e.state, 'state', AttachmentState.archived) + ]), + ); + + // Restore from cache + await db.execute('UPDATE users SET photo_id = ?', [id]); + await expectLater( + attachments, + emitsThrough([ + isA() + .having((e) => e.state, 'state', AttachmentState.synced) + ]), + ); + expect(await localStorage.fileExists(localUri), isTrue); + + // Should not have downloaded attachment again because we have it locally. + verifyNoMoreInteractions(remoteStorage); + }); + + test('skip failed download', () async { + Future errorHandler( + Attachment attachment, Object exception, StackTrace trace) async { + return false; + } + + queue = AttachmentQueue( + db: db, + remoteStorage: remoteStorage, + watchAttachments: watchAttachments, + localStorage: localStorage, + errorHandler: AttachmentErrorHandler( + onDeleteError: expectAsync3(errorHandler, count: 0), + onDownloadError: expectAsync3(errorHandler, count: 1), + onUploadError: expectAsync3(errorHandler, count: 0), + ), + ); + + when(remoteStorage.downloadFile(any)).thenAnswer((_) async { + throw 'test error'; + }); + + await queue.startSync(); + await db.execute( + 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())', + ['steven', 'steven@journeyapps.com'], + ); + + expect(await attachments.next, [ + isA() + .having((e) => e.state, 'state', AttachmentState.queuedDownload) + ]); + expect(await attachments.next, [ + isA() + .having((e) => e.state, 'state', AttachmentState.archived) + ]); + }); +} + +extension on PowerSyncDatabase { + Stream> get attachments { + return watch('SELECT * FROM attachments_queue') + .map((rs) => rs.map(Attachment.fromRow).toList()); + } +} + +final class MockRemoteStorage extends Mock implements RemoteStorage { + MockRemoteStorage() { + when(uploadFile(any, any)).thenAnswer((_) async {}); + when(downloadFile(any)).thenAnswer((_) async { + return Stream.empty(); + }); + when(deleteFile(any)).thenAnswer((_) async {}); + } + + @override + Future uploadFile( + Stream? fileData, Attachment? attachment) async { + await noSuchMethod(Invocation.method(#uploadFile, [fileData, attachment])); + } + + @override + Future>> downloadFile(Attachment? attachment) { + return (noSuchMethod(Invocation.method(#downloadFile, [attachment])) ?? + Future.value(const Stream>.empty())) + as Future>>; + } + + @override + Future deleteFile(Attachment? attachment) async { + await noSuchMethod(Invocation.method(#deleteFile, [attachment])); + } +} + +final _schema = Schema([ + Table('users', + [Column.text('name'), Column.text('email'), Column.text('photo_id')]), + AttachmentsQueueTable(), +]); + +TypeMatcher isAttachment(String id) { + return isA().having((e) => e.id, 'id', id); +} diff --git a/packages/powersync_core/test/attachments/local_storage_test.dart b/packages/powersync_core/test/attachments/local_storage_test.dart new file mode 100644 index 00000000..9ceabac1 --- /dev/null +++ b/packages/powersync_core/test/attachments/local_storage_test.dart @@ -0,0 +1,365 @@ +@TestOn('vm') +library; + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:test/test.dart'; +import 'package:path/path.dart' as p; +import 'package:powersync_core/src/attachments/io_local_storage.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +void main() { + group('IOLocalStorage', () { + late IOLocalStorage storage; + + setUp(() async { + storage = IOLocalStorage(Directory(d.sandbox)); + }); + + tearDown(() async { + // Clean up is handled automatically by test_descriptor + // No manual cleanup needed + }); + + group('saveFile and readFile', () { + test('saves and reads binary data successfully', () async { + const filePath = 'test_file'; + final data = Uint8List.fromList([1, 2, 3, 4, 5]); + final size = await storage.saveFile(filePath, Stream.value(data)); + expect(size, equals(data.length)); + + final resultStream = storage.readFile(filePath); + final result = await resultStream.toList(); + expect(result, equals([data])); + + // Assert filesystem state using test_descriptor + await d.file(filePath, data).validate(); + }); + + test('throws when reading non-existent file', () async { + const filePath = 'non_existent'; + expect( + () => storage.readFile(filePath).toList(), + throwsA(isA()), + ); + + // Assert file does not exist using Dart's File API + expect(await File(p.join(d.sandbox, filePath)).exists(), isFalse); + }); + + test('creates parent directories if they do not exist', () async { + const filePath = 'subdir/nested/test'; + final nonExistentDir = Directory(p.join(d.sandbox, 'subdir', 'nested')); + final data = Uint8List.fromList([1, 2, 3]); + + expect(await nonExistentDir.exists(), isFalse); + + final size = await storage.saveFile(filePath, Stream.value(data)); + expect(size, equals(data.length)); + expect(await nonExistentDir.exists(), isTrue); + + final resultStream = storage.readFile(filePath); + final result = await resultStream.toList(); + expect(result, equals([data])); + + // Assert directory structure + await d.dir('subdir/nested', [d.file('test', data)]).validate(); + }); + + test('creates all parent directories for deeply nested file', () async { + const filePath = 'a/b/c/d/e/f/g/h/i/j/testfile'; + final nestedDir = Directory( + p.join(d.sandbox, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'), + ); + final data = Uint8List.fromList([42, 43, 44]); + + expect(await nestedDir.exists(), isFalse); + + final size = await storage.saveFile(filePath, Stream.value(data)); + expect(size, equals(data.length)); + expect(await nestedDir.exists(), isTrue); + + final resultStream = storage.readFile(filePath); + final result = await resultStream.toList(); + expect(result, equals([data])); + + // Assert deep directory structure + await d.dir('a/b/c/d/e/f/g/h/i/j', [ + d.file('testfile', data), + ]).validate(); + }); + + test('overwrites existing file', () async { + const filePath = 'overwrite_test'; + final originalData = Uint8List.fromList([1, 2, 3]); + final newData = Uint8List.fromList([4, 5, 6, 7]); + + await storage.saveFile(filePath, Stream.value(originalData)); + final size = await storage.saveFile(filePath, Stream.value(newData)); + expect(size, equals(newData.length)); + + final resultStream = storage.readFile(filePath); + final result = await resultStream.toList(); + expect(result, equals([newData])); + + // Assert file content + await d.file(filePath, newData).validate(); + }); + }); + + group('edge cases and robustness', () { + test('saveFile with empty data writes empty file and returns 0 size', + () async { + const filePath = 'empty_file'; + + final size = await storage.saveFile(filePath, Stream.empty()); + expect(size, 0); + + final resultStream = storage.readFile(filePath); + final chunks = await resultStream.toList(); + expect(chunks, isEmpty); + + final file = File(p.join(d.sandbox, filePath)); + expect(await file.exists(), isTrue); + expect(await file.length(), 0); + }); + + test('readFile preserves byte order (chunking may differ)', () async { + const filePath = 'ordered_chunks'; + final chunks = [ + Uint8List.fromList([0, 1, 2]), + Uint8List.fromList([3, 4]), + Uint8List.fromList([5, 6, 7, 8]), + ]; + final expectedBytes = + Uint8List.fromList(chunks.expand((c) => c).toList()); + await storage.saveFile(filePath, Stream.value(expectedBytes)); + + final outChunks = await storage.readFile(filePath).toList(); + final outBytes = Uint8List.fromList( + outChunks.expand((c) => c).toList(), + ); + expect(outBytes, equals(expectedBytes)); + }); + + test('fileExists becomes false after deleteFile', () async { + const filePath = 'exists_then_delete'; + await storage.saveFile(filePath, Stream.value(Uint8List.fromList([1]))); + expect(await storage.fileExists(filePath), isTrue); + await storage.deleteFile(filePath); + expect(await storage.fileExists(filePath), isFalse); + }); + + test('initialize is idempotent', () async { + await storage.initialize(); + await storage.initialize(); + + // Create a file, then re-initialize again + const filePath = 'idempotent_test'; + await storage.saveFile(filePath, Stream.value(Uint8List.fromList([9]))); + await storage.initialize(); + + // File should still exist (initialize should not clear data) + expect(await storage.fileExists(filePath), isTrue); + }); + + test('clear works even if base directory was removed externally', + () async { + await storage.initialize(); + + // Remove the base dir manually + final baseDir = Directory(d.sandbox); + if (await baseDir.exists()) { + await baseDir.delete(recursive: true); + } + + // Calling clear should recreate base dir + await storage.clear(); + expect(await baseDir.exists(), isTrue); + }); + + test('supports unicode and emoji filenames', () async { + const filePath = '測試_файл_📷.bin'; + final bytes = Uint8List.fromList([10, 20, 30, 40]); + await storage.saveFile(filePath, Stream.value(bytes)); + + final out = await storage.readFile(filePath).toList(); + expect(out, equals([bytes])); + + await d.file(filePath, bytes).validate(); + }); + + test('readFile accepts mediaType parameter (ignored by IO impl)', + () async { + const filePath = 'with_media_type'; + final data = Uint8List.fromList([1, 2, 3]); + await storage.saveFile(filePath, Stream.value(data)); + + final result = + await storage.readFile(filePath, mediaType: 'image/jpeg').toList(); + expect(result, equals([data])); + }); + }); + + group('deleteFile', () { + test('deletes existing file', () async { + const filePath = 'delete_test'; + final data = Uint8List.fromList([1, 2, 3]); + + await storage.saveFile(filePath, Stream.value(data)); + expect(await storage.fileExists(filePath), isTrue); + + await storage.deleteFile(filePath); + expect(await storage.fileExists(filePath), isFalse); + + // Assert file does not exist + expect(await File(p.join(d.sandbox, filePath)).exists(), isFalse); + }); + + test('does not throw when deleting non-existent file', () async { + const filePath = 'non_existent'; + await storage.deleteFile(filePath); + expect(await File(p.join(d.sandbox, filePath)).exists(), isFalse); + }); + }); + + group('initialize and clear', () { + test('initialize creates the base directory', () async { + final newStorage = + IOLocalStorage(Directory(p.join(d.sandbox, 'new_dir'))); + final baseDir = Directory(p.join(d.sandbox, 'new_dir')); + + expect(await baseDir.exists(), isFalse); + + await newStorage.initialize(); + + expect(await baseDir.exists(), isTrue); + }); + + test('clear removes and recreates the base directory', () async { + await storage.initialize(); + final testFile = p.join(d.sandbox, 'test_file'); + await File(testFile).writeAsString('test'); + + expect(await File(testFile).exists(), isTrue); + + await storage.clear(); + + expect(await Directory(d.sandbox).exists(), isTrue); + expect(await File(testFile).exists(), isFalse); + }); + }); + + group('fileExists', () { + test('returns true for existing file', () async { + const filePath = 'exists_test'; + final data = Uint8List.fromList([1, 2, 3]); + + await storage.saveFile(filePath, Stream.value(data)); + expect(await storage.fileExists(filePath), isTrue); + + await d.file(filePath, data).validate(); + }); + + test('returns false for non-existent file', () async { + const filePath = 'non_existent'; + expect(await storage.fileExists(filePath), isFalse); + expect(await File(p.join(d.sandbox, filePath)).exists(), isFalse); + }); + }); + + group('file system integration', () { + test('handles special characters in file path', () async { + const filePath = 'file with spaces & symbols!@#'; + final data = Uint8List.fromList([1, 2, 3]); + + final size = await storage.saveFile(filePath, Stream.value(data)); + expect(size, equals(data.length)); + + final resultStream = storage.readFile(filePath); + final result = await resultStream.toList(); + expect(result, equals([data])); + + await d.file(filePath, data).validate(); + }); + + test('handles large binary data stream', () async { + const filePath = 'large_file'; + final data = Uint8List.fromList(List.generate(10000, (i) => i % 256)); + final chunkSize = 1000; + final chunks = []; + for (var i = 0; i < data.length; i += chunkSize) { + chunks.add( + Uint8List.fromList( + data.sublist( + i, + i + chunkSize < data.length ? i + chunkSize : data.length, + ), + ), + ); + } + final size = await storage.saveFile(filePath, Stream.value(data)); + expect(size, equals(data.length)); + + final resultStream = storage.readFile(filePath); + final result = Uint8List.fromList( + (await resultStream.toList()).expand((chunk) => chunk).toList(), + ); + expect(result, equals(data)); + + await d.file(filePath, data).validate(); + }); + }); + + group('concurrent operations', () { + test('handles concurrent saves to different files', () async { + final futures = >[]; + final fileCount = 10; + + for (int i = 0; i < fileCount; i++) { + final data = Uint8List.fromList([i, i + 1, i + 2]); + futures.add(storage.saveFile('file_$i', Stream.value(data))); + } + + await Future.wait(futures); + + for (int i = 0; i < fileCount; i++) { + final resultStream = storage.readFile('file_$i'); + final result = await resultStream.toList(); + expect( + result, + equals([ + Uint8List.fromList([i, i + 1, i + 2]), + ]), + ); + await d + .file('file_$i', Uint8List.fromList([i, i + 1, i + 2])) + .validate(); + } + }); + + test('handles concurrent saves to the same file', () async { + const filePath = 'concurrent_test'; + final data1 = Uint8List.fromList([1, 2, 3]); + final data2 = Uint8List.fromList([4, 5, 6]); + final futures = [ + storage.saveFile(filePath, Stream.value(data1)), + storage.saveFile(filePath, Stream.value(data2)), + ]; + + await Future.wait(futures); + + final resultStream = storage.readFile(filePath); + final result = await resultStream.toList(); + expect(result, anyOf(equals([data1]), equals([data2]))); + + // Assert one of the possible outcomes + final file = File(p.join(d.sandbox, filePath)); + final fileData = await file.readAsBytes(); + expect(fileData, anyOf(equals(data1), equals(data2))); + }); + }); + }); +} diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index 2b456429..a95d2604 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -63,17 +63,21 @@ Logger _makeTestLogger({Level level = Level.ALL, String? name}) { abstract mixin class TestPowerSyncFactory implements PowerSyncOpenFactory { Future openRawInMemoryDatabase(); - Future<(CommonDatabase, PowerSyncDatabase)> openInMemoryDatabase() async { + Future<(CommonDatabase, PowerSyncDatabase)> openInMemoryDatabase({ + Schema? schema, + Logger? logger, + }) async { final raw = await openRawInMemoryDatabase(); - return (raw, wrapRaw(raw)); + return (raw, wrapRaw(raw, customSchema: schema, logger: logger)); } PowerSyncDatabase wrapRaw( CommonDatabase raw, { Logger? logger, + Schema? customSchema, }) { return PowerSyncDatabase.withDatabase( - schema: schema, + schema: customSchema ?? schema, database: SqliteDatabase.singleConnection( SqliteConnection.synchronousWrapper(raw)), logger: logger, From 4c0748469eff03d4051c9d35f5d85f4500df07ec Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 2 Oct 2025 15:27:53 +0200 Subject: [PATCH 55/62] Sync streams (#317) --- demos/benchmarks/pubspec.lock | 44 +- .../lib/widgets/guard_by_sync.dart | 4 +- .../xcshareddata/swiftpm/Package.resolved | 6 +- .../lib/powersync/powersync.dart | 7 +- .../lib/screens/lists.dart | 2 +- .../lib/app_config_template.dart | 3 + .../lib/widgets/guard_by_sync.dart | 4 +- .../lib/widgets/list_item_sync_stream.dart | 78 ++++ .../lib/widgets/lists_page.dart | 8 +- .../lib/widgets/todo_list_page.dart | 92 +++- .../powersync_core/lib/powersync_core.dart | 3 +- .../native/native_powersync_database.dart | 11 + .../powersync_database_impl_stub.dart | 3 + .../lib/src/database/powersync_db_mixin.dart | 214 ++-------- .../database/web/web_powersync_database.dart | 7 + .../lib/src/sync/connection_manager.dart | 396 ++++++++++++++++++ .../lib/src/sync/instruction.dart | 18 +- .../lib/src/sync/mutable_sync_status.dart | 14 +- .../powersync_core/lib/src/sync/options.dart | 17 +- .../powersync_core/lib/src/sync/stream.dart | 176 ++++++++ .../lib/src/sync/streaming_sync.dart | 158 +++++-- .../lib/src/sync/sync_status.dart | 135 ++++-- .../lib/src/web/sync_controller.dart | 11 + .../lib/src/web/sync_worker.dart | 80 +++- .../lib/src/web/sync_worker_protocol.dart | 71 +++- packages/powersync_core/pubspec.yaml | 2 +- .../test/{ => sync}/bucket_storage_test.dart | 10 +- .../test/{ => sync}/in_memory_sync_test.dart | 143 ++----- .../powersync_core/test/sync/stream_test.dart | 263 ++++++++++++ .../test/{ => sync}/streaming_sync_test.dart | 8 +- .../test/{ => sync}/sync_types_test.dart | 10 +- packages/powersync_core/test/sync/utils.dart | 136 ++++++ .../test/utils/abstract_test_utils.dart | 90 +++- 33 files changed, 1782 insertions(+), 442 deletions(-) create mode 100644 demos/supabase-todolist/lib/widgets/list_item_sync_stream.dart create mode 100644 packages/powersync_core/lib/src/sync/connection_manager.dart create mode 100644 packages/powersync_core/lib/src/sync/stream.dart rename packages/powersync_core/test/{ => sync}/bucket_storage_test.dart (99%) rename packages/powersync_core/test/{ => sync}/in_memory_sync_test.dart (88%) create mode 100644 packages/powersync_core/test/sync/stream_test.dart rename packages/powersync_core/test/{ => sync}/streaming_sync_test.dart (97%) rename packages/powersync_core/test/{ => sync}/sync_types_test.dart (95%) create mode 100644 packages/powersync_core/test/sync/utils.dart diff --git a/demos/benchmarks/pubspec.lock b/demos/benchmarks/pubspec.lock index 026996f9..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.15.0" + version: "1.15.2" powersync_core: dependency: "direct overridden" description: path: "../../packages/powersync_core" relative: true source: path - version: "1.5.0" + version: "1.5.2" powersync_flutter_libs: dependency: "direct overridden" description: path: "../../packages/powersync_flutter_libs" relative: true source: path - version: "0.4.10" + 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/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/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8e5eb05f..0c12c1e5 100644 --- a/demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/demos/supabase-todolist-drift/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,7 +5,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/simolus3/CSQLite.git", "state" : { - "revision" : "a8d28afef08ad8faa4ee9ef7845f61c2e8ac5810" + "revision" : "a268235ae86718e66d6a29feef3bd22c772eb82b" } }, { @@ -13,8 +13,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "00776db5157c8648671b00e6673603144fafbfeb", - "version" : "0.4.5" + "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6", + "version" : "0.4.6" } } ], 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/lib/app_config_template.dart b/demos/supabase-todolist/lib/app_config_template.dart index ccfdfa21..05ea5164 100644 --- a/demos/supabase-todolist/lib/app_config_template.dart +++ b/demos/supabase-todolist/lib/app_config_template.dart @@ -6,4 +6,7 @@ class AppConfig { static const String powersyncUrl = 'https://foo.powersync.journeyapps.com'; static const String supabaseStorageBucket = ''; // Optional. Only required when syncing attachments and using Supabase Storage. See packages/powersync_attachments_helper. + // Whether the PowerSync instance uses sync streams to make fetching todo + // items optional. + static const bool hasSyncStreams = false; } 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_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/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/database/native/native_powersync_database.dart b/packages/powersync_core/lib/src/database/native/native_powersync_database.dart index 3ebb95f9..40489365 100644 --- a/packages/powersync_core/lib/src/database/native/native_powersync_database.dart +++ b/packages/powersync_core/lib/src/database/native/native_powersync_database.dart @@ -133,6 +133,8 @@ class PowerSyncDatabaseImpl Future connectInternal({ required PowerSyncBackendConnector connector, required ResolvedSyncOptions options, + required List initiallyActiveStreams, + required Stream> activeStreams, required AbortController abort, required Zone asyncWorkZone, }) async { @@ -140,6 +142,7 @@ class PowerSyncDatabaseImpl bool triedSpawningIsolate = false; StreamSubscription? crudUpdateSubscription; + StreamSubscription? activeStreamsSubscription; final receiveMessages = ReceivePort(); final receiveUnhandledErrors = ReceivePort(); final receiveExit = ReceivePort(); @@ -157,6 +160,7 @@ class PowerSyncDatabaseImpl // Cleanup crudUpdateSubscription?.cancel(); + activeStreamsSubscription?.cancel(); receiveMessages.close(); receiveUnhandledErrors.close(); receiveExit.close(); @@ -198,6 +202,10 @@ class PowerSyncDatabaseImpl crudUpdateSubscription = crudStream.listen((event) { port.send(['update']); }); + + activeStreamsSubscription = activeStreams.listen((streams) { + port.send(['changed_subscriptions', streams]); + }); } else if (action == 'uploadCrud') { await (data[1] as PortCompleter).handle(() async { await connector.uploadData(this); @@ -366,6 +374,9 @@ Future _syncIsolate(_PowerSyncDatabaseIsolateArgs args) async { } } else if (action == 'close') { await shutdown(); + } else if (action == 'changed_subscriptions') { + openedStreamingSync + ?.updateSubscriptions(message[1] as List); } } }); diff --git a/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart b/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart index a4f0b419..ae891cb7 100644 --- a/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart +++ b/packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart @@ -7,6 +7,7 @@ import 'package:powersync_core/src/abort_controller.dart'; import 'package:powersync_core/src/database/powersync_db_mixin.dart'; import 'package:powersync_core/src/open_factory/abstract_powersync_open_factory.dart'; import '../sync/options.dart'; +import '../sync/streaming_sync.dart'; import 'powersync_database.dart'; import '../connector.dart'; @@ -115,6 +116,8 @@ class PowerSyncDatabaseImpl Future connectInternal({ required PowerSyncBackendConnector connector, required AbortController abort, + required List initiallyActiveStreams, + required Stream> activeStreams, required Zone asyncWorkZone, required ResolvedSyncOptions options, }) { diff --git a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart index a53b5049..fd722a2a 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -14,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; @@ -42,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; @@ -81,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'; @@ -107,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. @@ -141,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; @@ -198,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 @@ -262,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(); } } @@ -298,67 +216,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { params: params, ); - if (schema.rawTables.isNotEmpty && - resolvedOptions.source.syncImplementation != - SyncClientImplementation.rust) { - throw UnsupportedError( - 'Raw tables are only supported by the Rust client.'); - } - - // ignore: deprecated_member_use_from_same_package - clientParams = params; - var thisConnectAborter = AbortController(); - final zone = Zone.current; - - late void Function() retryHandler; - - Future connectWithSyncLock() async { - // Ensure there has not been a subsequent connect() call installing a new - // sync client. - assert(identical(_abortActiveSync, thisConnectAborter)); - assert(!thisConnectAborter.aborted); - - await connectInternal( - connector: connector, - options: resolvedOptions, - abort: thisConnectAborter, - // Run follow-up async tasks in the parent zone, a new one is introduced - // while we hold the lock (and async tasks won't hold the sync lock). - asyncWorkZone: zone, - ); - - thisConnectAborter.onCompletion.whenComplete(retryHandler); - } - - // If the sync encounters a failure without being aborted, retry - retryHandler = Zone.current.bindCallback(() async { - _activeGroup.syncConnectMutex.lock(() async { - // Is this still supposed to be active? (abort is only called within - // mutex) - if (!thisConnectAborter.aborted) { - // We only change _abortActiveSync after disconnecting, which resets - // the abort controller. - assert(identical(_abortActiveSync, thisConnectAborter)); - - // We need a new abort controller for this attempt - _abortActiveSync = thisConnectAborter = AbortController(); - - logger.warning('Sync client failed, retrying...'); - await connectWithSyncLock(); - } - }); - }); - - await _activeGroup.syncConnectMutex.lock(() async { - // Disconnect a previous sync client, if one is active. - await _abortCurrentSync(); - assert(_abortActiveSync == null); - - // Install the abort controller for this particular connect call, allowing - // it to be disconnected. - _abortActiveSync = thisConnectAborter; - await connectWithSyncLock(); - }); + await _connections.connect(connector: connector, options: resolvedOptions); } /// Internal method to establish a sync client connection. @@ -372,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, }); @@ -380,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. @@ -418,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.') @@ -441,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)); @@ -666,6 +503,13 @@ SELECT * FROM crud_entries; Future refreshSchema() async { await database.refreshSchema(); } + + /// Create a [SyncStream] instance for the given [name] and [parameters]. + /// + /// Use [SyncStream.subscribe] to subscribe to the returned stream. + SyncStream syncStream(String name, [Map? parameters]) { + return _connections.syncStream(name, parameters); + } } Stream powerSyncUpdateNotifications( diff --git a/packages/powersync_core/lib/src/database/web/web_powersync_database.dart b/packages/powersync_core/lib/src/database/web/web_powersync_database.dart index 4af2821e..15a83c7d 100644 --- a/packages/powersync_core/lib/src/database/web/web_powersync_database.dart +++ b/packages/powersync_core/lib/src/database/web/web_powersync_database.dart @@ -128,6 +128,8 @@ class PowerSyncDatabaseImpl Future connectInternal({ required PowerSyncBackendConnector connector, required AbortController abort, + required List initiallyActiveStreams, + required Stream> activeStreams, required Zone asyncWorkZone, required ResolvedSyncOptions options, }) async { @@ -141,6 +143,7 @@ class PowerSyncDatabaseImpl connector: connector, options: options.source, workerUri: Uri.base.resolve('/powersync_sync.worker.js'), + subscriptions: initiallyActiveStreams, ); } catch (e) { logger.warning( @@ -157,6 +160,7 @@ class PowerSyncDatabaseImpl crudUpdateTriggerStream: crudStream, options: options, client: BrowserClient(), + activeSubscriptions: initiallyActiveStreams, // Only allows 1 sync implementation to run at a time per database // This should be global (across tabs) when using Navigator locks. identifier: database.openFactory.path, @@ -168,7 +172,10 @@ class PowerSyncDatabaseImpl }); sync.streamingSync(); + final subscriptions = activeStreams.listen(sync.updateSubscriptions); + abort.onAbort.then((_) async { + subscriptions.cancel(); await sync.abort(); abort.completeAbort(); }).ignore(); diff --git a/packages/powersync_core/lib/src/sync/connection_manager.dart b/packages/powersync_core/lib/src/sync/connection_manager.dart new file mode 100644 index 00000000..8a326642 --- /dev/null +++ b/packages/powersync_core/lib/src/sync/connection_manager.dart @@ -0,0 +1,396 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:meta/meta.dart'; +import 'package:powersync_core/src/abort_controller.dart'; +import 'package:powersync_core/src/connector.dart'; +import 'package:powersync_core/src/database/active_instances.dart'; +import 'package:powersync_core/src/database/powersync_db_mixin.dart'; +import 'package:powersync_core/src/sync/options.dart'; +import 'package:powersync_core/src/sync/stream.dart'; +import 'package:powersync_core/src/sync/sync_status.dart'; + +import 'instruction.dart'; +import 'mutable_sync_status.dart'; +import 'streaming_sync.dart'; + +/// A (stream name, JSON parameters) pair that uniquely identifies a stream +/// instantiation to subscribe to. +typedef _RawStreamKey = (String, String); + +@internal +final class ConnectionManager { + final PowerSyncDatabaseMixin db; + final ActiveDatabaseGroup _activeGroup; + + /// All streams (with parameters) for which a subscription has been requested + /// explicitly. + final Map<_RawStreamKey, _ActiveSubscription> _locallyActiveSubscriptions = + {}; + + final StreamController _statusController = + StreamController.broadcast(); + + /// Fires when an entry is added or removed from [_locallyActiveSubscriptions] + /// while we're connected. + StreamController? _subscriptionsChanged; + + SyncStatus _currentStatus = + const SyncStatus(connected: false, lastSyncedAt: null); + + SyncStatus get currentStatus => _currentStatus; + Stream get statusStream => _statusController.stream; + + /// The abort controller for the current sync iteration. + /// + /// null when disconnected, present when connecting or connected. + /// + /// The controller must only be accessed from within a critical section of the + /// sync mutex. + AbortController? _abortActiveSync; + + ConnectionManager(this.db) : _activeGroup = db.group; + + void checkNotConnected() { + if (_abortActiveSync != null) { + throw StateError('Cannot update schema while connected'); + } + } + + Future _abortCurrentSync() async { + if (_abortActiveSync case final disconnector?) { + /// Checking `disconnecter.aborted` prevents race conditions + /// where multiple calls to `disconnect` can attempt to abort + /// the controller more than once before it has finished aborting. + if (disconnector.aborted == false) { + await disconnector.abort(); + _abortActiveSync = null; + } else { + /// Wait for the abort to complete. Continue updating the sync status after completed + await disconnector.onCompletion; + } + } + } + + Future disconnect() async { + // Also wrap this in the sync mutex to ensure there's no race between us + // connecting and disconnecting. + await _activeGroup.syncConnectMutex.lock(() async { + await _abortCurrentSync(); + _subscriptionsChanged?.close(); + _subscriptionsChanged = null; + }); + + manuallyChangeSyncStatus( + SyncStatus(connected: false, lastSyncedAt: currentStatus.lastSyncedAt)); + } + + Future firstStatusMatching(bool Function(SyncStatus) predicate) async { + if (predicate(currentStatus)) { + return; + } + await for (final result in statusStream) { + if (predicate(result)) { + break; + } + } + } + + List get _subscribedStreams => [ + for (final active in _locallyActiveSubscriptions.values) + (name: active.name, parameters: active.encodedParameters) + ]; + + Future connect({ + required PowerSyncBackendConnector connector, + required ResolvedSyncOptions options, + }) async { + if (db.schema.rawTables.isNotEmpty && + options.source.syncImplementation != SyncClientImplementation.rust) { + throw UnsupportedError( + 'Raw tables are only supported by the Rust client.'); + } + + var thisConnectAborter = AbortController(); + final zone = Zone.current; + + late void Function() retryHandler; + + final subscriptionsChanged = StreamController(); + + Future connectWithSyncLock() async { + // Ensure there has not been a subsequent connect() call installing a new + // sync client. + assert(identical(_abortActiveSync, thisConnectAborter)); + assert(!thisConnectAborter.aborted); + + // ignore: invalid_use_of_protected_member + await db.connectInternal( + connector: connector, + options: options, + abort: thisConnectAborter, + initiallyActiveStreams: _subscribedStreams, + activeStreams: subscriptionsChanged.stream.map((_) { + return _subscribedStreams; + }), + // Run follow-up async tasks in the parent zone, a new one is introduced + // while we hold the lock (and async tasks won't hold the sync lock). + asyncWorkZone: zone, + ); + + thisConnectAborter.onCompletion.whenComplete(retryHandler); + } + + // If the sync encounters a failure without being aborted, retry + retryHandler = Zone.current.bindCallback(() async { + _activeGroup.syncConnectMutex.lock(() async { + // Is this still supposed to be active? (abort is only called within + // mutex) + if (!thisConnectAborter.aborted) { + // We only change _abortActiveSync after disconnecting, which resets + // the abort controller. + assert(identical(_abortActiveSync, thisConnectAborter)); + + // We need a new abort controller for this attempt + _abortActiveSync = thisConnectAborter = AbortController(); + + db.logger.warning('Sync client failed, retrying...'); + await connectWithSyncLock(); + } + }); + }); + + await _activeGroup.syncConnectMutex.lock(() async { + // Disconnect a previous sync client, if one is active. + await _abortCurrentSync(); + assert(_abortActiveSync == null); + _subscriptionsChanged = subscriptionsChanged; + + // Install the abort controller for this particular connect call, allowing + // it to be disconnected. + _abortActiveSync = thisConnectAborter; + await connectWithSyncLock(); + }); + } + + void manuallyChangeSyncStatus(SyncStatus status) { + if (status != currentStatus) { + final newStatus = SyncStatus( + connected: status.connected, + downloading: status.downloading, + uploading: status.uploading, + connecting: status.connecting, + uploadError: status.uploadError, + downloadError: status.downloadError, + priorityStatusEntries: status.priorityStatusEntries, + downloadProgress: status.downloadProgress, + // Note that currently the streaming sync implementation will never set + // hasSynced. lastSyncedAt implies that syncing has completed at some + // point (hasSynced = true). + // The previous values of hasSynced should be preserved here. + lastSyncedAt: status.lastSyncedAt ?? currentStatus.lastSyncedAt, + hasSynced: status.lastSyncedAt != null + ? true + : status.hasSynced ?? currentStatus.hasSynced, + streamSubscriptions: status.internalSubscriptions, + ); + + // If the absence of hasSynced was the only difference, the new states + // would be equal and don't require an event. So, check again. + if (newStatus != currentStatus) { + _currentStatus = newStatus; + _statusController.add(_currentStatus); + } + } + } + + _SyncStreamSubscriptionHandle _referenceStreamSubscription( + String stream, Map? parameters) { + final key = (stream, json.encode(parameters)); + _ActiveSubscription active; + + if (_locallyActiveSubscriptions[key] case final current?) { + active = current; + } else { + active = _ActiveSubscription(this, + name: stream, parameters: parameters, encodedParameters: key.$2); + _locallyActiveSubscriptions[key] = active; + _subscriptionsChanged?.add(null); + } + + return _SyncStreamSubscriptionHandle(active); + } + + void _clearSubscription(_ActiveSubscription subscription) { + assert(subscription.refcount == 0); + _locallyActiveSubscriptions + .remove((subscription.name, subscription.encodedParameters)); + _subscriptionsChanged?.add(null); + } + + Future _subscriptionsCommand(Object? command) async { + await db.writeTransaction((tx) { + return tx.execute( + 'SELECT powersync_control(?, ?)', + ['subscriptions', json.encode(command)], + ); + }); + _subscriptionsChanged?.add(null); + } + + Future subscribe({ + required String stream, + required Map? parameters, + Duration? ttl, + StreamPriority? priority, + }) async { + await _subscriptionsCommand({ + 'subscribe': { + 'stream': { + 'name': stream, + 'params': parameters, + }, + 'ttl': ttl?.inSeconds, + 'priority': priority, + }, + }); + + await _activeGroup.syncConnectMutex.lock(() async { + if (_abortActiveSync == null) { + // Since we're not connected, update the offline sync status to reflect + // the new subscription. + // With a connection, the sync client would include it in its state. + await resolveOfflineSyncStatus(); + } + }); + } + + Future unsubscribeAll({ + required String stream, + required Object? parameters, + }) async { + await _subscriptionsCommand({ + 'unsubscribe': { + 'name': stream, + 'params': parameters, + }, + }); + } + + Future resolveOfflineSyncStatus() async { + final row = await db.database.get( + 'SELECT powersync_offline_sync_status() AS r;', + ); + + final status = CoreSyncStatus.fromJson( + json.decode(row['r'] as String) as Map); + + manuallyChangeSyncStatus((MutableSyncStatus()..applyFromCore(status)) + .immutableSnapshot(setLastSynced: true)); + } + + SyncStream syncStream(String name, Map? parameters) { + return _SyncStreamImplementation(this, name, parameters); + } + + void close() { + _statusController.close(); + } +} + +final class _SyncStreamImplementation implements SyncStream { + @override + final String name; + + @override + final Map? parameters; + + final ConnectionManager _connections; + + _SyncStreamImplementation(this._connections, this.name, this.parameters); + + @override + Future subscribe({ + Duration? ttl, + StreamPriority? priority, + }) async { + await _connections.subscribe( + stream: name, + parameters: parameters, + ttl: ttl, + priority: priority, + ); + + return _connections._referenceStreamSubscription(name, parameters); + } + + @override + Future unsubscribeAll() async { + await _connections.unsubscribeAll(stream: name, parameters: parameters); + } +} + +final class _ActiveSubscription { + final ConnectionManager connections; + var refcount = 0; + + final String name; + final String encodedParameters; + final Map? parameters; + + _ActiveSubscription( + this.connections, { + required this.name, + required this.encodedParameters, + required this.parameters, + }); + + void decrementRefCount() { + refcount--; + if (refcount == 0) { + connections._clearSubscription(this); + } + } +} + +final class _SyncStreamSubscriptionHandle implements SyncStreamSubscription { + final _ActiveSubscription _source; + var _active = true; + + _SyncStreamSubscriptionHandle(this._source) { + _source.refcount++; + _finalizer.attach(this, _source, detach: this); + } + + @override + String get name => _source.name; + + @override + Map? get parameters => _source.parameters; + + @override + void unsubscribe() { + if (_active) { + _active = false; + _finalizer.detach(this); + _source.decrementRefCount(); + } + } + + @override + Future waitForFirstSync() async { + return _source.connections.firstStatusMatching((status) { + final currentProgress = status.forStream(this); + return currentProgress?.subscription.hasSynced ?? false; + }); + } + + static final Finalizer<_ActiveSubscription> _finalizer = Finalizer((sub) { + sub.connections.db.logger.warning( + 'A subscription to ${sub.name} (with parameters ${sub.parameters}) ' + 'leaked! Please ensure calling SyncStreamSubscription.unsubscribe() ' + "when you don't need a subscription anymore. For global " + 'subscriptions, consider storing them in global fields to avoid this ' + 'warning.'); + }); +} diff --git a/packages/powersync_core/lib/src/sync/instruction.dart b/packages/powersync_core/lib/src/sync/instruction.dart index f0146e8e..3479e281 100644 --- a/packages/powersync_core/lib/src/sync/instruction.dart +++ b/packages/powersync_core/lib/src/sync/instruction.dart @@ -1,3 +1,4 @@ +import 'stream.dart'; import 'sync_status.dart'; /// An internal instruction emitted by the sync client in the core extension in @@ -13,7 +14,8 @@ sealed class Instruction { EstablishSyncStream.fromJson(establish as Map), {'FetchCredentials': final creds} => FetchCredentials.fromJson(creds as Map), - {'CloseSyncStream': _} => const CloseSyncStream(), + {'CloseSyncStream': final closeOptions as Map} => + CloseSyncStream(closeOptions['hide_disconnect'] as bool), {'FlushFileSystem': _} => const FlushFileSystem(), {'DidCompleteSync': _} => const DidCompleteSync(), _ => UnknownSyncInstruction(json) @@ -62,12 +64,14 @@ final class CoreSyncStatus { final bool connecting; final List priorityStatus; final DownloadProgress? downloading; + final List? streams; CoreSyncStatus({ required this.connected, required this.connecting, required this.priorityStatus, required this.downloading, + required this.streams, }); factory CoreSyncStatus.fromJson(Map json) { @@ -82,12 +86,16 @@ final class CoreSyncStatus { null => null, final raw as Map => DownloadProgress.fromJson(raw), }, + streams: (json['streams'] as List) + .map((e) => + CoreActiveStreamSubscription.fromJson(e as Map)) + .toList(), ); } static SyncPriorityStatus _priorityStatusFromJson(Map json) { return ( - priority: BucketPriority(json['priority'] as int), + priority: StreamPriority(json['priority'] as int), hasSynced: json['has_synced'] as bool?, lastSyncedAt: switch (json['last_synced_at']) { null => null, @@ -116,7 +124,7 @@ final class DownloadProgress { static BucketProgress _bucketProgressFromJson(Map json) { return ( - priority: BucketPriority(json['priority'] as int), + priority: StreamPriority(json['priority'] as int), atLast: json['at_last'] as int, sinceLast: json['since_last'] as int, targetCount: json['target_count'] as int, @@ -135,7 +143,9 @@ final class FetchCredentials implements Instruction { } final class CloseSyncStream implements Instruction { - const CloseSyncStream(); + final bool hideDisconnect; + + const CloseSyncStream(this.hideDisconnect); } final class FlushFileSystem implements Instruction { diff --git a/packages/powersync_core/lib/src/sync/mutable_sync_status.dart b/packages/powersync_core/lib/src/sync/mutable_sync_status.dart index 23e3becb..273cd597 100644 --- a/packages/powersync_core/lib/src/sync/mutable_sync_status.dart +++ b/packages/powersync_core/lib/src/sync/mutable_sync_status.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'instruction.dart'; +import 'stream.dart'; import 'sync_status.dart'; import 'bucket_storage.dart'; import 'protocol.dart'; @@ -15,6 +16,7 @@ final class MutableSyncStatus { InternalSyncDownloadProgress? downloadProgress; List priorityStatusEntries = const []; + List? streams; DateTime? lastSyncedAt; @@ -51,9 +53,9 @@ final class MutableSyncStatus { hasSynced: true, lastSyncedAt: now, priority: maxBy( - applied.checksums.map((cs) => BucketPriority(cs.priority)), + applied.checksums.map((cs) => StreamPriority(cs.priority)), (priority) => priority, - compare: BucketPriority.comparator, + compare: StreamPriority.comparator, )!, ) ]; @@ -90,11 +92,12 @@ final class MutableSyncStatus { final downloading => InternalSyncDownloadProgress(downloading.buckets), }; lastSyncedAt = status.priorityStatus - .firstWhereOrNull((s) => s.priority == BucketPriority.fullSyncPriority) + .firstWhereOrNull((s) => s.priority == StreamPriority.fullSyncPriority) ?.lastSyncedAt; + streams = status.streams; } - SyncStatus immutableSnapshot() { + SyncStatus immutableSnapshot({bool setLastSynced = false}) { return SyncStatus( connected: connected, connecting: connecting, @@ -103,9 +106,10 @@ final class MutableSyncStatus { downloadProgress: downloadProgress?.asSyncDownloadProgress, priorityStatusEntries: UnmodifiableListView(priorityStatusEntries), lastSyncedAt: lastSyncedAt, - hasSynced: null, // Stream client is not supposed to set this value. + hasSynced: setLastSynced ? lastSyncedAt != null : null, uploadError: uploadError, downloadError: downloadError, + streamSubscriptions: streams, ); } } diff --git a/packages/powersync_core/lib/src/sync/options.dart b/packages/powersync_core/lib/src/sync/options.dart index 6ae94b25..ee8b3c63 100644 --- a/packages/powersync_core/lib/src/sync/options.dart +++ b/packages/powersync_core/lib/src/sync/options.dart @@ -27,11 +27,18 @@ final class SyncOptions { /// The [SyncClientImplementation] to use. final SyncClientImplementation syncImplementation; + /// Whether streams that have been defined with `auto_subscribe: true` should + /// be synced when they don't have an explicit subscription. + /// + /// This is enabled by default. + final bool? includeDefaultStreams; + const SyncOptions({ this.crudThrottleTime, this.retryDelay, this.params, this.syncImplementation = SyncClientImplementation.defaultClient, + this.includeDefaultStreams, }); SyncOptions _copyWith({ @@ -44,6 +51,7 @@ final class SyncOptions { retryDelay: retryDelay, params: params ?? this.params, syncImplementation: syncImplementation, + includeDefaultStreams: includeDefaultStreams, ); } } @@ -96,16 +104,23 @@ extension type ResolvedSyncOptions(SyncOptions source) { Map get params => source.params ?? const {}; + bool get includeDefaultStreams => source.includeDefaultStreams ?? true; + (ResolvedSyncOptions, bool) applyFrom(SyncOptions other) { final newOptions = SyncOptions( crudThrottleTime: other.crudThrottleTime ?? crudThrottleTime, retryDelay: other.retryDelay ?? retryDelay, params: other.params ?? params, + syncImplementation: other.syncImplementation, + includeDefaultStreams: + other.includeDefaultStreams ?? includeDefaultStreams, ); final didChange = !_mapEquality.equals(newOptions.params, params) || newOptions.crudThrottleTime != crudThrottleTime || - newOptions.retryDelay != retryDelay; + newOptions.retryDelay != retryDelay || + newOptions.syncImplementation != source.syncImplementation || + newOptions.includeDefaultStreams != includeDefaultStreams; return (ResolvedSyncOptions(newOptions), didChange); } diff --git a/packages/powersync_core/lib/src/sync/stream.dart b/packages/powersync_core/lib/src/sync/stream.dart new file mode 100644 index 00000000..80c447be --- /dev/null +++ b/packages/powersync_core/lib/src/sync/stream.dart @@ -0,0 +1,176 @@ +import 'package:meta/meta.dart'; + +import 'sync_status.dart'; +import '../database/powersync_database.dart'; + +/// A description of a sync stream, consisting of its [name] and the +/// [parameters] used when subscribing. +abstract interface class SyncStreamDescription { + /// The name of the stream as it appears in the stream definition for the + /// PowerSync service. + String get name; + + /// The parameters used to subscribe to the stream, if any. + /// + /// The same stream can be subscribed to multiple times with different + /// parameters. + Map? get parameters; +} + +/// Information about a subscribed sync stream. +/// +/// This includes the [SyncStreamDescription] along with information about the +/// current sync status. +abstract interface class SyncSubscriptionDescription + extends SyncStreamDescription { + /// Whether this stream is active, meaning that the subscription has been + /// acknowledged by the sync serivce. + bool get active; + + /// Whether this stream subscription is included by default, regardless of + /// whether the stream has explicitly been subscribed to or not. + /// + /// It's possible for both [isDefault] and [hasExplicitSubscription] to be + /// true at the same time - this happens when a default stream was subscribed + /// explicitly. + bool get isDefault; + + /// Whether this stream has been subscribed to explicitly. + /// + /// It's possible for both [isDefault] and [hasExplicitSubscription] to be + /// true at the same time - this happens when a default stream was subscribed + /// explicitly. + bool get hasExplicitSubscription; + + /// For sync streams that have a time-to-live, the current time at which the + /// stream would expire if not subscribed to again. + DateTime? get expiresAt; + + /// Whether this stream subscription has been synced at least once. + bool get hasSynced; + + /// If [hasSynced] is true, the last time data from this stream has been + /// synced. + DateTime? get lastSyncedAt; +} + +/// A handle to a [SyncStreamDescription] that allows subscribing to the stream. +/// +/// To obtain an instance of [SyncStream], call [PowerSyncDatabase.syncStream]. +abstract interface class SyncStream extends SyncStreamDescription { + /// Adds a subscription to this stream, requesting it to be included when + /// connecting to the sync service. + /// + /// The [priority] can be used to override the priority of this stream. + Future subscribe({ + Duration? ttl, + StreamPriority? priority, + }); + + Future unsubscribeAll(); +} + +/// A [SyncStream] that has been subscribed to. +abstract interface class SyncStreamSubscription + implements SyncStreamDescription { + /// A variant of [PowerSyncDatabase.waitForFirstSync] that is specific to + /// this stream subscription. + Future waitForFirstSync(); + + /// Removes this subscription. + /// + /// Once all [SyncStreamSubscription]s for a [SyncStream] have been + /// unsubscribed, the `ttl` for that stream starts running. When it expires + /// without subscribing again, the stream will be evicted. + void unsubscribe(); +} + +/// An `ActiveStreamSubscription` as part of the sync status in Rust. +@internal +final class CoreActiveStreamSubscription + implements SyncSubscriptionDescription { + @override + final String name; + @override + final Map? parameters; + final StreamPriority priority; + final ({int total, int downloaded}) progress; + @override + final bool active; + @override + final bool isDefault; + @override + final bool hasExplicitSubscription; + @override + final DateTime? expiresAt; + @override + final DateTime? lastSyncedAt; + + @override + bool get hasSynced => lastSyncedAt != null; + + CoreActiveStreamSubscription._({ + required this.name, + required this.parameters, + required this.priority, + required this.progress, + required this.active, + required this.isDefault, + required this.hasExplicitSubscription, + required this.expiresAt, + required this.lastSyncedAt, + }); + + factory CoreActiveStreamSubscription.fromJson(Map json) { + return CoreActiveStreamSubscription._( + name: json['name'] as String, + parameters: json['parameters'] as Map?, + priority: switch (json['priority'] as int?) { + final prio? => StreamPriority(prio), + null => StreamPriority.fullSyncPriority, + }, + progress: _progressFromJson(json['progress'] as Map), + active: json['active'] as bool, + isDefault: json['is_default'] as bool, + hasExplicitSubscription: json['has_explicit_subscription'] as bool, + expiresAt: switch (json['expires_at']) { + null => null, + final timestamp as int => + DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), + }, + lastSyncedAt: switch (json['last_synced_at']) { + null => null, + final timestamp as int => + DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), + }, + ); + } + + Map toJson() { + return { + 'name': name, + 'parameters': parameters, + 'priority': priority.priorityNumber, + 'progress': { + 'total': progress.total, + 'downloaded': progress.downloaded, + }, + 'active': active, + 'is_default': isDefault, + 'has_explicit_subscription': hasExplicitSubscription, + 'expires_at': switch (expiresAt) { + null => null, + final expiresAt => expiresAt.millisecondsSinceEpoch / 1000, + }, + 'last_synced_at': switch (lastSyncedAt) { + null => null, + final lastSyncedAt => lastSyncedAt.millisecondsSinceEpoch / 1000, + } + }; + } + + static ({int total, int downloaded}) _progressFromJson( + Map json) { + return (total: json['total'] as int, downloaded: json['downloaded'] as int); + } +} diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index ad0886a1..60deef12 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -21,6 +21,8 @@ import 'stream_utils.dart'; import 'sync_status.dart'; import 'protocol.dart'; +typedef SubscribedStream = ({String name, String parameters}); + abstract interface class StreamingSync { Stream get statusStream; @@ -28,6 +30,8 @@ abstract interface class StreamingSync { /// Close any active streams. Future abort(); + + void updateSubscriptions(List streams); } @internal @@ -36,6 +40,7 @@ class StreamingSyncImplementation implements StreamingSync { final BucketStorage adapter; final InternalConnector connector; final ResolvedSyncOptions options; + List _activeSubscriptions; final Logger logger; @@ -69,6 +74,7 @@ class StreamingSyncImplementation implements StreamingSync { required this.crudUpdateTriggerStream, required this.options, required http.Client client, + List activeSubscriptions = const [], Mutex? syncMutex, Mutex? crudMutex, Logger? logger, @@ -80,7 +86,8 @@ class StreamingSyncImplementation implements StreamingSync { syncMutex = syncMutex ?? Mutex(identifier: "sync-$identifier"), crudMutex = crudMutex ?? Mutex(identifier: "crud-$identifier"), _userAgentHeaders = userAgentHeaders(), - logger = logger ?? isolateLogger; + logger = logger ?? isolateLogger, + _activeSubscriptions = activeSubscriptions; Duration get _retryDelay => options.retryDelay; @@ -124,6 +131,14 @@ class StreamingSyncImplementation implements StreamingSync { return _abort?.aborted ?? false; } + @override + void updateSubscriptions(List streams) { + _activeSubscriptions = streams; + if (_nonLineSyncEvents.hasListener) { + _nonLineSyncEvents.add(HandleChangedSubscriptions(streams)); + } + } + @override Future streamingSync() async { try { @@ -294,7 +309,12 @@ class StreamingSyncImplementation implements StreamingSync { } Future _rustStreamingSyncIteration() async { - await _ActiveRustStreamingIteration(this).syncIteration(); + logger.info('Starting Rust sync iteration'); + final response = await _ActiveRustStreamingIteration(this).syncIteration(); + logger.info( + 'Ending Rust sync iteration. Immediate restart: ${response.immediateRestart}'); + // Note: With the current loop in streamingSync(), any return value that + // isn't an exception triggers an immediate restart. } Future<(List, Map)> @@ -370,7 +390,7 @@ class StreamingSyncImplementation implements StreamingSync { // checkpoint later. } else { _updateStatusForPriority(( - priority: BucketPriority(bucketPriority), + priority: StreamPriority(bucketPriority), lastSyncedAt: DateTime.now(), hasSynced: true, )); @@ -449,6 +469,7 @@ class StreamingSyncImplementation implements StreamingSync { _state.updateStatus((s) => s.setConnected()); await handleLine(line as StreamingSyncLine); case UploadCompleted(): + case HandleChangedSubscriptions(): // Only relevant for the Rust sync implementation. break; case AbortCurrentIteration(): @@ -507,7 +528,8 @@ class StreamingSyncImplementation implements StreamingSync { } Future _postStreamRequest( - Object? data, bool acceptBson) async { + Object? data, bool acceptBson, + {Future? onAbort}) async { const ndJson = 'application/x-ndjson'; const bson = 'application/vnd.powersync.bson-stream'; @@ -517,8 +539,8 @@ class StreamingSyncImplementation implements StreamingSync { } final uri = credentials.endpointUri('sync/stream'); - final request = - http.AbortableRequest('POST', uri, abortTrigger: _abort!.onAbort); + final request = http.AbortableRequest('POST', uri, + abortTrigger: onAbort ?? _abort!.onAbort); request.headers['Content-Type'] = 'application/json'; request.headers['Authorization'] = "Token ${credentials.token}"; request.headers['Accept'] = @@ -589,25 +611,35 @@ typedef BucketDescription = ({ final class _ActiveRustStreamingIteration { final StreamingSyncImplementation sync; + var _isActive = true; var _hadSyncLine = false; StreamSubscription? _completedUploads; - final Completer _completedStream = Completer(); + final Completer _completedStream = Completer(); _ActiveRustStreamingIteration(this.sync); - Future syncIteration() async { + List _encodeSubscriptions(List subscriptions) { + return sync._activeSubscriptions + .map((s) => + {'name': s.name, 'params': convert.json.decode(s.parameters)}) + .toList(); + } + + Future syncIteration() async { try { await _control( 'start', convert.json.encode({ 'parameters': sync.options.params, 'schema': convert.json.decode(sync.schemaJson), + 'include_defaults': sync.options.includeDefaultStreams, + 'active_streams': _encodeSubscriptions(sync._activeSubscriptions), }), ); assert(_completedStream.isCompleted, 'Should have started streaming'); - await _completedStream.future; + return await _completedStream.future; } finally { _isActive = false; _completedUploads?.cancel(); @@ -615,9 +647,10 @@ final class _ActiveRustStreamingIteration { } } - Stream _receiveLines(Object? data) { + Stream _receiveLines(Object? data, + {required Future onAbort}) { return streamFromFutureAwaitInCancellation( - sync._postStreamRequest(data, true)) + sync._postStreamRequest(data, true, onAbort: onAbort)) .asyncExpand((response) { if (response == null) { return null; @@ -630,31 +663,72 @@ final class _ActiveRustStreamingIteration { }).map(ReceivedLine.new); } - Future _handleLines(EstablishSyncStream request) async { + Future _handleLines( + EstablishSyncStream request) async { + // This is a workaround for https://github.com/dart-lang/http/issues/1820: + // When cancelling the stream subscription of an HTTP response with the + // fetch-based client implementation, cancelling the subscription is delayed + // until the next chunk (typically a token_expires_in message in our case). + // So, before cancelling, we complete an abort controller for the request to + // speed things up. This is not an issue in most cases because the abort + // controller on this stream would be completed when disconnecting. But + // when switching sync streams, that's not the case and we need a second + // abort controller for the inner iteration. + final innerAbort = Completer.sync(); final events = addBroadcast( - _receiveLines(request.request), sync._nonLineSyncEvents.stream); - + _receiveLines( + request.request, + onAbort: Future.any([ + sync._abort!.onAbort, + innerAbort.future, + ]), + ), + sync._nonLineSyncEvents.stream, + ); + + var needsImmediateRestart = false; loop: - await for (final event in events) { - if (!_isActive || sync.aborted) { - break; - } + try { + await for (final event in events) { + if (!_isActive || sync.aborted) { + innerAbort.complete(); + break; + } - switch (event) { - case ReceivedLine(line: final Uint8List line): - _triggerCrudUploadOnFirstLine(); - await _control('line_binary', line); - case ReceivedLine(line: final line as String): - _triggerCrudUploadOnFirstLine(); - await _control('line_text', line); - case UploadCompleted(): - await _control('completed_upload'); - case AbortCurrentIteration(): - break loop; - case TokenRefreshComplete(): - await _control('refreshed_token'); + switch (event) { + case ReceivedLine(line: final Uint8List line): + _triggerCrudUploadOnFirstLine(); + await _control('line_binary', line); + case ReceivedLine(line: final line as String): + _triggerCrudUploadOnFirstLine(); + await _control('line_text', line); + case UploadCompleted(): + await _control('completed_upload'); + case AbortCurrentIteration(:final hideDisconnectState): + innerAbort.complete(); + needsImmediateRestart = hideDisconnectState; + break loop; + case TokenRefreshComplete(): + await _control('refreshed_token'); + case HandleChangedSubscriptions(:final currentSubscriptions): + await _control( + 'update_subscriptions', + convert.json + .encode(_encodeSubscriptions(currentSubscriptions))); + } + } + } on http.RequestAbortedException { + // Unlike a regular cancellation, cancelling via the abort controller + // emits an error. We did mean to just cancel the stream, so we can + // safely ignore that. + if (innerAbort.isCompleted) { + // ignore + } else { + rethrow; } } + + return (immediateRestart: needsImmediateRestart); } /// Triggers a local CRUD upload when the first sync line has been received. @@ -708,10 +782,11 @@ final class _ActiveRustStreamingIteration { sync.logger.warning('Could not prefetch credentials', e, s); }); } - case CloseSyncStream(): + case CloseSyncStream(:final hideDisconnect): if (!sync.aborted) { _isActive = false; - sync._nonLineSyncEvents.add(const AbortCurrentIteration()); + sync._nonLineSyncEvents + .add(AbortCurrentIteration(hideDisconnectState: hideDisconnect)); } case FlushFileSystem(): await sync.adapter.flushFileSystem(); @@ -723,6 +798,8 @@ final class _ActiveRustStreamingIteration { } } +typedef RustSyncIterationResult = ({bool immediateRestart}); + sealed class SyncEvent {} final class ReceivedLine implements SyncEvent { @@ -740,5 +817,18 @@ final class TokenRefreshComplete implements SyncEvent { } final class AbortCurrentIteration implements SyncEvent { - const AbortCurrentIteration(); + /// Whether we should immediately disconnect and hide the `disconnected` + /// state. + /// + /// This is used when we're changing subscription, to hide the brief downtime + /// we have while reconnecting. + final bool hideDisconnectState; + + const AbortCurrentIteration({this.hideDisconnectState = false}); +} + +final class HandleChangedSubscriptions implements SyncEvent { + final List currentSubscriptions; + + HandleChangedSubscriptions(this.currentSubscriptions); } diff --git a/packages/powersync_core/lib/src/sync/sync_status.dart b/packages/powersync_core/lib/src/sync/sync_status.dart index 62c48df1..61ae7c5f 100644 --- a/packages/powersync_core/lib/src/sync/sync_status.dart +++ b/packages/powersync_core/lib/src/sync/sync_status.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'bucket_storage.dart'; import 'protocol.dart'; +import 'stream.dart'; final class SyncStatus { /// true if currently connected. @@ -54,6 +55,9 @@ final class SyncStatus { final List priorityStatusEntries; + final List? _internalSubscriptions; + + @internal const SyncStatus({ this.connected = false, this.connecting = false, @@ -65,7 +69,8 @@ final class SyncStatus { this.downloadError, this.uploadError, this.priorityStatusEntries = const [], - }); + List? streamSubscriptions, + }) : _internalSubscriptions = streamSubscriptions; @override bool operator ==(Object other) { @@ -78,8 +83,10 @@ final class SyncStatus { other.uploadError == uploadError && other.lastSyncedAt == lastSyncedAt && other.hasSynced == hasSynced && - _statusEquality.equals( + _listEquality.equals( other.priorityStatusEntries, priorityStatusEntries) && + _listEquality.equals( + other._internalSubscriptions, _internalSubscriptions) && other.downloadProgress == downloadProgress); } @@ -110,6 +117,16 @@ final class SyncStatus { ); } + /// All sync streams currently being tracked in the database. + /// + /// This returns null when the database is currently being opened and we + /// don't have reliable information about all included streams yet. + Iterable? get syncStreams { + return _internalSubscriptions?.map((subscription) { + return SyncStreamStatus._(subscription, downloadProgress); + }); + } + /// Get the current [downloadError] or [uploadError]. Object? get anyError { return downloadError ?? uploadError; @@ -128,9 +145,9 @@ final class SyncStatus { /// information extracted from the lower priority `2` since each partial sync /// in priority `2` necessarily includes a consistent view over data in /// priority `1`. - SyncPriorityStatus statusForPriority(BucketPriority priority) { + SyncPriorityStatus statusForPriority(StreamPriority priority) { assert(priorityStatusEntries.isSortedByCompare( - (e) => e.priority, BucketPriority.comparator)); + (e) => e.priority, StreamPriority.comparator)); for (final known in priorityStatusEntries) { // Lower-priority buckets are synchronized after higher-priority buckets, @@ -149,6 +166,21 @@ final class SyncStatus { ); } + /// If the [stream] appears in [syncStreams], returns the current status for + /// that stream. + SyncStreamStatus? forStream(SyncStreamDescription stream) { + final raw = _internalSubscriptions?.firstWhereOrNull( + (e) => + e.name == stream.name && + _mapEquality.equals(e.parameters, stream.parameters), + ); + + if (raw == null) { + return null; + } + return SyncStreamStatus._(raw, downloadProgress); + } + @override int get hashCode { return Object.hash( @@ -159,8 +191,9 @@ final class SyncStatus { uploadError, downloadError, lastSyncedAt, - _statusEquality.hash(priorityStatusEntries), + _listEquality.hash(priorityStatusEntries), downloadProgress, + _listEquality.hash(_internalSubscriptions), ); } @@ -169,37 +202,66 @@ final class SyncStatus { return "SyncStatus"; } - // This should be a ListEquality, but that appears to - // cause weird type errors with DDC (but only after hot reloads?!) - static const _statusEquality = ListEquality(); + static const _listEquality = ListEquality(); + static const _mapEquality = MapEquality(); +} + +@internal +extension InternalSyncStatusAccess on SyncStatus { + List? get internalSubscriptions => + _internalSubscriptions; +} + +/// Current information about a [SyncStream] that the sync client is subscribed +/// to. +final class SyncStreamStatus { + /// If the [SyncStatus] is currently [SyncStatus.downloading], download + /// progress for this stream. + final ProgressWithOperations? progress; + final CoreActiveStreamSubscription _internal; + + /// The [SyncSubscriptionDescription] providing information about the current + /// stream state. + SyncSubscriptionDescription get subscription => _internal; + + /// The [StreamPriority] of the current stream. + /// + /// New data on higher-priority streams can interrupt lower-priority streams. + StreamPriority get priority => _internal.priority; + + SyncStreamStatus._(this._internal, SyncDownloadProgress? progress) + : progress = progress?._internal._forStream(_internal); } -/// The priority of a PowerSync bucket. -extension type const BucketPriority._(int priorityNumber) { +@Deprecated('Use StreamPriority instead') +typedef BucketPriority = StreamPriority; + +/// The priority of a PowerSync stream. +extension type const StreamPriority._(int priorityNumber) { static const _highest = 0; - factory BucketPriority(int i) { + factory StreamPriority(int i) { assert(i >= _highest); - return BucketPriority._(i); + return StreamPriority._(i); } - bool operator >(BucketPriority other) => comparator(this, other) > 0; - bool operator >=(BucketPriority other) => comparator(this, other) >= 0; - bool operator <(BucketPriority other) => comparator(this, other) < 0; - bool operator <=(BucketPriority other) => comparator(this, other) <= 0; + bool operator >(StreamPriority other) => comparator(this, other) > 0; + bool operator >=(StreamPriority other) => comparator(this, other) >= 0; + bool operator <(StreamPriority other) => comparator(this, other) < 0; + bool operator <=(StreamPriority other) => comparator(this, other) <= 0; - /// A [Comparator] instance suitable for comparing [BucketPriority] values. - static int comparator(BucketPriority a, BucketPriority b) => + /// A [Comparator] instance suitable for comparing [StreamPriority] values. + static int comparator(StreamPriority a, StreamPriority b) => -a.priorityNumber.compareTo(b.priorityNumber); /// The priority used by PowerSync to indicate that a full sync was completed. - static const fullSyncPriority = BucketPriority._(2147483647); + static const fullSyncPriority = StreamPriority._(2147483647); } /// Partial information about the synchronization status for buckets within a /// priority. typedef SyncPriorityStatus = ({ - BucketPriority priority, + StreamPriority priority, DateTime? lastSyncedAt, bool? hasSynced, }); @@ -227,7 +289,7 @@ class UploadQueueStats { /// Per-bucket download progress information. @internal typedef BucketProgress = ({ - BucketPriority priority, + StreamPriority priority, int atLast, int sinceLast, int targetCount, @@ -253,7 +315,7 @@ final class InternalSyncDownloadProgress extends ProgressWithOperations { final sinceLast = savedProgress?.sinceLast ?? 0; buckets[bucket.bucket] = ( - priority: BucketPriority._(bucket.priority), + priority: StreamPriority._(bucket.priority), atLast: atLast, sinceLast: sinceLast, targetCount: bucket.count ?? 0, @@ -268,7 +330,7 @@ final class InternalSyncDownloadProgress extends ProgressWithOperations { return InternalSyncDownloadProgress({ for (final bucket in target.checksums) bucket.bucket: ( - priority: BucketPriority(bucket.priority), + priority: StreamPriority(bucket.priority), atLast: 0, sinceLast: 0, targetCount: knownCount, @@ -287,17 +349,16 @@ final class InternalSyncDownloadProgress extends ProgressWithOperations { /// Sums the total target and completed operations for all buckets up until /// the given [priority] (inclusive). - ProgressWithOperations untilPriority(BucketPriority priority) { - final (total, downloaded) = - buckets.values.where((e) => e.priority >= priority).fold( - (0, 0), - (prev, entry) { - final downloaded = entry.sinceLast; - final total = entry.targetCount - entry.atLast; - return (prev.$1 + total, prev.$2 + downloaded); - }, - ); + ProgressWithOperations untilPriority(StreamPriority priority) { + final (total, downloaded) = buckets.values + .where((e) => e.priority >= priority) + .fold((0, 0), _addProgress); + + return ProgressWithOperations._(total, downloaded); + } + ProgressWithOperations _forStream(CoreActiveStreamSubscription subscription) { + final (:total, :downloaded) = subscription.progress; return ProgressWithOperations._(total, downloaded); } @@ -340,6 +401,12 @@ final class InternalSyncDownloadProgress extends ProgressWithOperations { } static const _mapEquality = MapEquality(); + + (int, int) _addProgress((int, int) prev, BucketProgress entry) { + final downloaded = entry.sinceLast; + final total = entry.targetCount - entry.atLast; + return (prev.$1 + total, prev.$2 + downloaded); + } } /// Information about a progressing download. @@ -403,7 +470,7 @@ extension type SyncDownloadProgress._(InternalSyncDownloadProgress _internal) /// The returned [ProgressWithOperations] tracks the target amount of /// operations that need to be downloaded in total and how many of them have /// already been received. - ProgressWithOperations untilPriority(BucketPriority priority) { + ProgressWithOperations untilPriority(StreamPriority priority) { return _internal.untilPriority(priority); } } diff --git a/packages/powersync_core/lib/src/web/sync_controller.dart b/packages/powersync_core/lib/src/web/sync_controller.dart index 7f05cff3..b3f0ef18 100644 --- a/packages/powersync_core/lib/src/web/sync_controller.dart +++ b/packages/powersync_core/lib/src/web/sync_controller.dart @@ -15,6 +15,7 @@ class SyncWorkerHandle implements StreamingSync { final PowerSyncBackendConnector connector; final SyncOptions options; late final WorkerCommunicationChannel _channel; + List subscriptions; final StreamController _status = StreamController.broadcast(); @@ -24,6 +25,7 @@ class SyncWorkerHandle implements StreamingSync { required this.options, required MessagePort sendToWorker, required SharedWorker worker, + required this.subscriptions, }) { _channel = WorkerCommunicationChannel( port: sendToWorker, @@ -81,6 +83,7 @@ class SyncWorkerHandle implements StreamingSync { required PowerSyncBackendConnector connector, required Uri workerUri, required SyncOptions options, + required List subscriptions, }) async { final worker = SharedWorker(workerUri.toString().toJS); final handle = SyncWorkerHandle._( @@ -89,6 +92,7 @@ class SyncWorkerHandle implements StreamingSync { connector: connector, sendToWorker: worker.port, worker: worker, + subscriptions: subscriptions, ); // Make sure that the worker is working, or throw immediately. @@ -116,6 +120,13 @@ class SyncWorkerHandle implements StreamingSync { database.database.openFactory.path, ResolvedSyncOptions(options), database.schema, + subscriptions, ); } + + @override + void updateSubscriptions(List streams) { + subscriptions = streams; + _channel.updateSubscriptions(streams); + } } diff --git a/packages/powersync_core/lib/src/web/sync_worker.dart b/packages/powersync_core/lib/src/web/sync_worker.dart index ddc4eaf0..1c92808f 100644 --- a/packages/powersync_core/lib/src/web/sync_worker.dart +++ b/packages/powersync_core/lib/src/web/sync_worker.dart @@ -8,6 +8,7 @@ import 'dart:convert'; import 'dart:js_interop'; import 'package:async/async.dart'; +import 'package:collection/collection.dart'; import 'package:http/browser_client.dart'; import 'package:logging/logging.dart'; import 'package:powersync_core/powersync_core.dart'; @@ -45,8 +46,12 @@ class _SyncWorker { }); } - _SyncRunner referenceSyncTask(String databaseIdentifier, SyncOptions options, - String schemaJson, _ConnectedClient client) { + _SyncRunner referenceSyncTask( + String databaseIdentifier, + SyncOptions options, + String schemaJson, + List subscriptions, + _ConnectedClient client) { return _requestedSyncTasks.putIfAbsent(databaseIdentifier, () { return _SyncRunner(databaseIdentifier); }) @@ -54,6 +59,7 @@ class _SyncWorker { client, options, schemaJson, + subscriptions, ); } } @@ -90,13 +96,22 @@ class _ConnectedClient { }, ); - _runner = _worker.referenceSyncTask(request.databaseName, - recoveredOptions, request.schemaJson, this); + _runner = _worker.referenceSyncTask( + request.databaseName, + recoveredOptions, + request.schemaJson, + request.subscriptions?.toDart ?? const [], + this, + ); return (JSObject(), null); case SyncWorkerMessageType.abortSynchronization: _runner?.disconnectClient(this); _runner = null; return (JSObject(), null); + case SyncWorkerMessageType.updateSubscriptions: + _runner?.updateClientSubscriptions( + this, (payload as UpdateSubscriptions).toDart); + return (JSObject(), null); default: throw StateError('Unexpected message type $type'); } @@ -137,9 +152,10 @@ class _SyncRunner { final StreamGroup<_RunnerEvent> _group = StreamGroup(); final StreamController<_RunnerEvent> _mainEvents = StreamController(); - StreamingSync? sync; + StreamingSyncImplementation? sync; _ConnectedClient? databaseHost; - final connections = <_ConnectedClient>[]; + final connections = <_ConnectedClient, List>{}; + List currentStreams = []; _SyncRunner(this.identifier) { _group.add(_mainEvents.stream); @@ -152,8 +168,9 @@ class _SyncRunner { :final client, :final options, :final schemaJson, + :final subscriptions, ): - connections.add(client); + connections[client] = subscriptions; final (newOptions, reconnect) = this.options.applyFrom(options); this.options = newOptions; this.schemaJson = schemaJson; @@ -165,6 +182,8 @@ class _SyncRunner { sync?.abort(); sync = null; await _requestDatabase(client); + } else { + reindexSubscriptions(); } case _RemoveConnection(:final client): connections.remove(client); @@ -191,6 +210,12 @@ class _SyncRunner { } else { await _requestDatabase(newHost); } + case _ClientSubscriptionsChanged( + :final client, + :final subscriptions + ): + connections[client] = subscriptions; + reindexSubscriptions(); } } catch (e, s) { _logger.warning('Error handling $event', e, s); @@ -199,12 +224,24 @@ class _SyncRunner { }); } + /// Updates [currentStreams] to the union of values in [connections]. + void reindexSubscriptions() { + final before = currentStreams.toSet(); + final after = connections.values.flattenedToSet; + if (!const SetEquality().equals(before, after)) { + _logger.info( + 'Subscriptions across tabs have changed, checking whether a reconnect is necessary'); + currentStreams = after.toList(); + sync?.updateSubscriptions(currentStreams); + } + } + /// Pings all current [connections], removing those that don't answer in 5s /// (as they are likely closed tabs as well). /// /// Returns the first client that responds (without waiting for others). Future<_ConnectedClient?> _collectActiveClients() async { - final candidates = connections.toList(); + final candidates = connections.keys.toList(); if (candidates.isEmpty) { return null; } @@ -269,6 +306,7 @@ class _SyncRunner { ); } + currentStreams = connections.values.flattenedToSet.toList(); sync = StreamingSyncImplementation( adapter: WebBucketStorage(database), schemaJson: client._runner!.schemaJson, @@ -283,10 +321,12 @@ class _SyncRunner { options: options, client: BrowserClient(), identifier: identifier, + activeSubscriptions: currentStreams, + logger: _logger, ); sync!.statusStream.listen((event) { _logger.fine('Broadcasting sync event: $event'); - for (final client in connections) { + for (final client in connections.keys) { client.channel.notify(SyncWorkerMessageType.notifySyncStatus, SerializedSyncStatus.from(event)); } @@ -294,9 +334,9 @@ class _SyncRunner { sync!.streamingSync(); } - void registerClient( - _ConnectedClient client, SyncOptions options, String schemaJson) { - _mainEvents.add(_AddConnection(client, options, schemaJson)); + void registerClient(_ConnectedClient client, SyncOptions options, + String schemaJson, List subscriptions) { + _mainEvents.add(_AddConnection(client, options, schemaJson, subscriptions)); } /// Remove a client, disconnecting if no clients remain.. @@ -308,6 +348,11 @@ class _SyncRunner { void disconnectClient(_ConnectedClient client) { _mainEvents.add(_DisconnectClient(client)); } + + void updateClientSubscriptions( + _ConnectedClient client, List subscriptions) { + _mainEvents.add(_ClientSubscriptionsChanged(client, subscriptions)); + } } sealed class _RunnerEvent {} @@ -316,8 +361,10 @@ final class _AddConnection implements _RunnerEvent { final _ConnectedClient client; final SyncOptions options; final String schemaJson; + final List subscriptions; - _AddConnection(this.client, this.options, this.schemaJson); + _AddConnection( + this.client, this.options, this.schemaJson, this.subscriptions); } final class _RemoveConnection implements _RunnerEvent { @@ -332,6 +379,13 @@ final class _DisconnectClient implements _RunnerEvent { _DisconnectClient(this.client); } +final class _ClientSubscriptionsChanged implements _RunnerEvent { + final _ConnectedClient client; + final List subscriptions; + + _ClientSubscriptionsChanged(this.client, this.subscriptions); +} + final class _ActiveDatabaseClosed implements _RunnerEvent { const _ActiveDatabaseClosed(); } diff --git a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart index 3c64d90f..950cd1d6 100644 --- a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart +++ b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart @@ -5,10 +5,12 @@ import 'dart:js_interop'; import 'package:logging/logging.dart'; import 'package:powersync_core/src/schema.dart'; import 'package:powersync_core/src/sync/options.dart'; +import 'package:powersync_core/src/sync/stream.dart'; import 'package:web/web.dart'; import '../connector.dart'; import '../log.dart'; +import '../sync/streaming_sync.dart'; import '../sync/sync_status.dart'; /// Names used in [SyncWorkerMessage] @@ -20,6 +22,9 @@ enum SyncWorkerMessageType { /// If parameters change, the sync worker reconnects. startSynchronization, + /// Update the active subscriptions that this client is interested in. + updateSubscriptions, + /// The [SyncWorkerMessage.payload] for the request is a numeric id, the /// response can be anything (void). /// This disconnects immediately, even if other clients are still open. @@ -74,6 +79,7 @@ extension type StartSynchronization._(JSObject _) implements JSObject { required String implementationName, required String schemaJson, String? syncParamsEncoded, + UpdateSubscriptions? subscriptions, }); external String get databaseName; @@ -83,6 +89,36 @@ extension type StartSynchronization._(JSObject _) implements JSObject { external String? get implementationName; external String get schemaJson; external String? get syncParamsEncoded; + external UpdateSubscriptions? get subscriptions; +} + +@anonymous +extension type UpdateSubscriptions._raw(JSObject _inner) implements JSObject { + external factory UpdateSubscriptions._({ + required int requestId, + required JSArray content, + }); + + factory UpdateSubscriptions(int requestId, List streams) { + return UpdateSubscriptions._( + requestId: requestId, + content: streams + .map((e) => [e.name.toJS, e.parameters.toJS].toJS) + .toList() + .toJS, + ); + } + + external int get requestId; + external JSArray get content; + + List get toDart { + return content.toDart.map((e) { + final [name, parameters] = (e as JSArray).toDart; + + return (name: name.toDart, parameters: parameters.toDart); + }).toList(); + } } @anonymous @@ -190,7 +226,7 @@ extension type SerializedBucketProgress._(JSObject _) implements JSObject { return { for (final entry in array.toDart) entry.name: ( - priority: BucketPriority(entry.priority), + priority: StreamPriority(entry.priority), atLast: entry.atLast, sinceLast: entry.sinceLast, targetCount: entry.targetCount, @@ -212,6 +248,7 @@ extension type SerializedSyncStatus._(JSObject _) implements JSObject { required String? downloadError, required JSArray? priorityStatusEntries, required JSArray? syncProgress, + required JSString streamSubscriptions, }); factory SerializedSyncStatus.from(SyncStatus status) { @@ -237,6 +274,7 @@ extension type SerializedSyncStatus._(JSObject _) implements JSObject { var other => SerializedBucketProgress.serialize( InternalSyncDownloadProgress.ofPublic(other).buckets), }, + streamSubscriptions: json.encode(status.internalSubscriptions).toJS, ); } @@ -250,8 +288,11 @@ extension type SerializedSyncStatus._(JSObject _) implements JSObject { external String? downloadError; external JSArray? priorityStatusEntries; external JSArray? syncProgress; + external JSString? streamSubscriptions; SyncStatus asSyncStatus() { + final streamSubscriptions = this.streamSubscriptions?.toDart; + return SyncStatus( connected: connected, connecting: connecting, @@ -271,7 +312,7 @@ extension type SerializedSyncStatus._(JSObject _) implements JSObject { final syncedMillis = (rawSynced as JSNumber?)?.toDartInt; return ( - priority: BucketPriority((rawPriority as JSNumber).toDartInt), + priority: StreamPriority((rawPriority as JSNumber).toDartInt), lastSyncedAt: syncedMillis != null ? DateTime.fromMicrosecondsSinceEpoch(syncedMillis) : null, @@ -285,6 +326,13 @@ extension type SerializedSyncStatus._(JSObject _) implements JSObject { SerializedBucketProgress.deserialize(serializedProgress)) .asSyncDownloadProgress, }, + streamSubscriptions: switch (streamSubscriptions) { + null => null, + final serialized => (json.decode(serialized) as List) + .map((e) => CoreActiveStreamSubscription.fromJson( + e as Map)) + .toList(), + }, ); } } @@ -339,6 +387,8 @@ final class WorkerCommunicationChannel { return; case SyncWorkerMessageType.startSynchronization: requestId = (message.payload as StartSynchronization).requestId; + case SyncWorkerMessageType.updateSubscriptions: + requestId = (message.payload as UpdateSubscriptions).requestId; case SyncWorkerMessageType.requestEndpoint: case SyncWorkerMessageType.abortSynchronization: case SyncWorkerMessageType.credentialsCallback: @@ -413,7 +463,11 @@ final class WorkerCommunicationChannel { } Future startSynchronization( - String databaseName, ResolvedSyncOptions options, Schema schema) async { + String databaseName, + ResolvedSyncOptions options, + Schema schema, + List streams, + ) async { final (id, completion) = _newRequest(); port.postMessage(SyncWorkerMessage( type: SyncWorkerMessageType.startSynchronization.name, @@ -428,11 +482,22 @@ final class WorkerCommunicationChannel { null => null, final params => jsonEncode(params), }, + subscriptions: UpdateSubscriptions(-1, streams), ), )); await completion; } + Future updateSubscriptions(List streams) async { + final (id, completion) = _newRequest(); + port.postMessage(SyncWorkerMessage( + type: SyncWorkerMessageType.updateSubscriptions.name, + payload: UpdateSubscriptions(id, streams), + )); + + await completion; + } + Future abortSynchronization() async { await _numericRequest(SyncWorkerMessageType.abortSynchronization); } diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index fa3dd02a..929b2d5e 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: uuid: ^4.2.0 async: ^2.10.0 logging: ^1.1.1 - collection: ^1.17.0 + collection: ^1.19.0 web: ^1.0.0 # Only used internally to download WASM / worker files. diff --git a/packages/powersync_core/test/bucket_storage_test.dart b/packages/powersync_core/test/sync/bucket_storage_test.dart similarity index 99% rename from packages/powersync_core/test/bucket_storage_test.dart rename to packages/powersync_core/test/sync/bucket_storage_test.dart index 94338791..496e5a49 100644 --- a/packages/powersync_core/test/bucket_storage_test.dart +++ b/packages/powersync_core/test/sync/bucket_storage_test.dart @@ -4,8 +4,9 @@ import 'package:powersync_core/src/sync/protocol.dart'; import 'package:sqlite_async/sqlite3_common.dart'; import 'package:test/test.dart'; -import 'utils/abstract_test_utils.dart'; -import 'utils/test_utils_impl.dart'; +import '../utils/abstract_test_utils.dart'; +import '../utils/test_utils_impl.dart'; +import 'utils.dart'; final testUtils = TestUtils(); @@ -39,11 +40,6 @@ const removeAsset1_4 = OplogEntry( const removeAsset1_5 = OplogEntry( opId: '5', op: OpType.remove, rowType: 'assets', rowId: 'O1', checksum: 5); -BucketChecksum checksum( - {required String bucket, required int checksum, int priority = 1}) { - return BucketChecksum(bucket: bucket, priority: priority, checksum: checksum); -} - SyncDataBatch syncDataBatch(List data) { return SyncDataBatch(data); } diff --git a/packages/powersync_core/test/in_memory_sync_test.dart b/packages/powersync_core/test/sync/in_memory_sync_test.dart similarity index 88% rename from packages/powersync_core/test/in_memory_sync_test.dart rename to packages/powersync_core/test/sync/in_memory_sync_test.dart index e4ab531b..87c18d82 100644 --- a/packages/powersync_core/test/in_memory_sync_test.dart +++ b/packages/powersync_core/test/sync/in_memory_sync_test.dart @@ -5,17 +5,16 @@ import 'package:async/async.dart'; import 'package:logging/logging.dart'; import 'package:powersync_core/powersync_core.dart'; import 'package:powersync_core/sqlite3_common.dart'; -import 'package:powersync_core/src/sync/streaming_sync.dart'; import 'package:powersync_core/src/sync/protocol.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; import 'package:test/test.dart'; -import 'bucket_storage_test.dart'; -import 'server/sync_server/in_memory_sync_server.dart'; -import 'utils/abstract_test_utils.dart'; -import 'utils/in_memory_http.dart'; -import 'utils/test_utils_impl.dart'; +import '../server/sync_server/in_memory_sync_server.dart'; +import '../utils/abstract_test_utils.dart'; +import '../utils/in_memory_http.dart'; +import '../utils/test_utils_impl.dart'; +import 'utils.dart'; void main() { _declareTests( @@ -55,35 +54,32 @@ void _declareTests(String name, SyncOptions options, bool bson) { late TestPowerSyncFactory factory; late CommonDatabase raw; - late PowerSyncDatabase database; + late TestDatabase database; late MockSyncService syncService; late Logger logger; - late StreamingSync syncClient; var credentialsCallbackCount = 0; Future Function(PowerSyncDatabase) uploadData = (db) async {}; - void createSyncClient({Schema? schema}) { + Future connect() async { final (client, server) = inMemoryServer(); server.mount((req) => syncService.router(req)); - final thisSyncClient = syncClient = database.connectWithMockService( - client, - TestConnector(() async { - credentialsCallbackCount++; - return PowerSyncCredentials( - endpoint: server.url.toString(), - token: 'token$credentialsCallbackCount', - expiresAt: DateTime.now(), - ); - }, uploadData: (db) => uploadData(db)), + database.httpClient = client; + await database.connect( + connector: TestConnector( + () async { + credentialsCallbackCount++; + return PowerSyncCredentials( + endpoint: server.url.toString(), + token: 'token$credentialsCallbackCount', + expiresAt: DateTime.now(), + ); + }, + uploadData: (db) => uploadData(db), + ), options: options, - customSchema: schema, ); - - addTearDown(() async { - await thisSyncClient.abort(); - }); } setUp(() async { @@ -94,7 +90,6 @@ void _declareTests(String name, SyncOptions options, bool bson) { factory = await testUtils.testFactory(); (raw, database) = await factory.openInMemoryDatabase(); await database.initialize(); - createSyncClient(); }); tearDown(() async { @@ -111,7 +106,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { } }); } - syncClient.streamingSync(); + await connect(); await syncService.waitForListener; expect(database.currentStatus.lastSyncedAt, isNull); @@ -146,7 +141,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { }); await expectLater( status, emits(isSyncStatus(downloading: false, hasSynced: true))); - await syncClient.abort(); + await database.disconnect(); final independentDb = factory.wrapRaw(raw, logger: ignoredLogger); addTearDown(independentDb.close); @@ -157,7 +152,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { // A complete sync also means that all partial syncs have completed expect( independentDb.currentStatus - .statusForPriority(BucketPriority(3)) + .statusForPriority(StreamPriority(3)) .hasSynced, isTrue); }); @@ -251,7 +246,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { database.watch('SELECT * FROM lists', throttle: Duration.zero)); await expectLater(query, emits(isEmpty)); - createSyncClient(schema: schema); + await database.updateSchema(schema); await waitForConnection(); syncService @@ -376,13 +371,13 @@ void _declareTests(String name, SyncOptions options, bool bson) { status, emitsThrough( isSyncStatus(downloading: true, hasSynced: false).having( - (e) => e.statusForPriority(BucketPriority(0)).hasSynced, + (e) => e.statusForPriority(StreamPriority(0)).hasSynced, 'status for $prio', isTrue, )), ); - await database.waitForFirstSync(priority: BucketPriority(prio)); + await database.waitForFirstSync(priority: StreamPriority(prio)); expect(await database.getAll('SELECT * FROM customers'), hasLength(prio + 1)); } @@ -419,9 +414,9 @@ void _declareTests(String name, SyncOptions options, bool bson) { 'priority': 1, } }); - await database.waitForFirstSync(priority: BucketPriority(1)); + await database.waitForFirstSync(priority: StreamPriority(1)); expect(database.currentStatus.hasSynced, isFalse); - await syncClient.abort(); + await database.disconnect(); final independentDb = factory.wrapRaw(raw, logger: ignoredLogger); addTearDown(independentDb.close); @@ -430,12 +425,12 @@ void _declareTests(String name, SyncOptions options, bool bson) { // Completing a sync for prio 1 implies a completed sync for prio 0 expect( independentDb.currentStatus - .statusForPriority(BucketPriority(0)) + .statusForPriority(StreamPriority(0)) .hasSynced, isTrue); expect( independentDb.currentStatus - .statusForPriority(BucketPriority(3)) + .statusForPriority(StreamPriority(3)) .hasSynced, isFalse); }); @@ -623,7 +618,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { Future expectProgress( StreamQueue status, { required Object total, - Map priorities = const {}, + Map priorities = const {}, }) async { await expectLater( status, @@ -683,10 +678,9 @@ void _declareTests(String name, SyncOptions options, bool bson) { await expectProgress(status, total: progress(5, 10)); // Emulate the app closing - create a new independent sync client. - await syncClient.abort(); + await database.disconnect(); syncService.endCurrentListener(); - createSyncClient(); status = await waitForConnection(); // Send same checkpoint again @@ -717,10 +711,9 @@ void _declareTests(String name, SyncOptions options, bool bson) { await expectProgress(status, total: progress(5, 10)); // Emulate the app closing - create a new independent sync client. - await syncClient.abort(); + await database.disconnect(); syncService.endCurrentListener(); - createSyncClient(); status = await waitForConnection(); // Send checkpoint with additional data @@ -751,9 +744,9 @@ void _declareTests(String name, SyncOptions options, bool bson) { // A sync rule deploy could reset buckets, making the new bucket smaller // than the existing one. - await syncClient.abort(); + await database.disconnect(); syncService.endCurrentListener(); - createSyncClient(); + status = await waitForConnection(); syncService.addLine({ 'checkpoint': Checkpoint( @@ -772,8 +765,8 @@ void _declareTests(String name, SyncOptions options, bool bson) { await expectProgress( status, priorities: { - BucketPriority(0): prio0, - BucketPriority(2): prio2, + StreamPriority(0): prio0, + StreamPriority(2): prio2, }, total: prio2, ); @@ -837,7 +830,7 @@ void _declareTests(String name, SyncOptions options, bool bson) { }); await expectLater(status, emits(isSyncStatus(downloading: true))); - await syncClient.abort(); + await database.disconnect(); expect(syncService.controller.hasListener, isFalse); }); @@ -856,9 +849,6 @@ void _declareTests(String name, SyncOptions options, bool bson) { syncService.addLine({ 'checkpoint_complete': {'last_op_id': '10'} }); - - await pumpEventQueue(); - expect(syncService.controller.hasListener, isFalse); syncService.endCurrentListener(); // Should reconnect after delay. @@ -878,9 +868,6 @@ void _declareTests(String name, SyncOptions options, bool bson) { await expectLater(status, emits(isSyncStatus(downloading: true))); syncService.addKeepAlive(0); - - await pumpEventQueue(); - expect(syncService.controller.hasListener, isFalse); syncService.endCurrentListener(); // Should reconnect after delay. @@ -952,11 +939,11 @@ void _declareTests(String name, SyncOptions options, bool bson) { await Completer().future; })); - syncClient.streamingSync(); + await connect(); await requestStarted.future; expect(database.currentStatus, isSyncStatus(connecting: true)); - await syncClient.abort(); + await database.disconnect(); expect(database.currentStatus.anyError, isNull); }); @@ -975,57 +962,9 @@ void _declareTests(String name, SyncOptions options, bool bson) { }); await expectLater(status, emits(isSyncStatus(downloading: true))); - await syncClient.abort(); + await database.disconnect(); expect(database.currentStatus.anyError, isNull); }); }); }); } - -TypeMatcher isSyncStatus({ - Object? downloading, - Object? connected, - Object? connecting, - Object? hasSynced, - Object? downloadProgress, -}) { - var matcher = isA(); - if (downloading != null) { - matcher = matcher.having((e) => e.downloading, 'downloading', downloading); - } - if (connected != null) { - matcher = matcher.having((e) => e.connected, 'connected', connected); - } - if (connecting != null) { - matcher = matcher.having((e) => e.connecting, 'connecting', connecting); - } - if (hasSynced != null) { - matcher = matcher.having((e) => e.hasSynced, 'hasSynced', hasSynced); - } - if (downloadProgress != null) { - matcher = matcher.having( - (e) => e.downloadProgress, 'downloadProgress', downloadProgress); - } - - return matcher; -} - -TypeMatcher isSyncDownloadProgress({ - required Object progress, - Map priorities = const {}, -}) { - var matcher = - isA().having((e) => e, 'untilCompletion', progress); - priorities.forEach((priority, expected) { - matcher = matcher.having( - (e) => e.untilPriority(priority), 'untilPriority($priority)', expected); - }); - - return matcher; -} - -TypeMatcher progress(int completed, int total) { - return isA() - .having((e) => e.downloadedOperations, 'completed', completed) - .having((e) => e.totalOperations, 'total', total); -} diff --git a/packages/powersync_core/test/sync/stream_test.dart b/packages/powersync_core/test/sync/stream_test.dart new file mode 100644 index 00000000..1625656c --- /dev/null +++ b/packages/powersync_core/test/sync/stream_test.dart @@ -0,0 +1,263 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:async/async.dart'; +import 'package:logging/logging.dart'; +import 'package:powersync_core/powersync_core.dart'; + +import 'package:test/test.dart'; + +import '../server/sync_server/in_memory_sync_server.dart'; +import '../utils/abstract_test_utils.dart'; +import '../utils/in_memory_http.dart'; +import '../utils/test_utils_impl.dart'; +import 'utils.dart'; + +void main() { + late final testUtils = TestUtils(); + + late TestPowerSyncFactory factory; + + late TestDatabase database; + late MockSyncService syncService; + late Logger logger; + late SyncOptions options; + + var credentialsCallbackCount = 0; + + Future connect() async { + final (client, server) = inMemoryServer(); + server.mount(syncService.router.call); + + database.httpClient = client; + await database.connect( + connector: TestConnector( + () async { + credentialsCallbackCount++; + return PowerSyncCredentials( + endpoint: server.url.toString(), + token: 'token$credentialsCallbackCount', + expiresAt: DateTime.now(), + ); + }, + uploadData: (db) async {}, + ), + options: options, + ); + } + + setUp(() async { + options = SyncOptions(syncImplementation: SyncClientImplementation.rust); + logger = Logger.detached('powersync.active')..level = Level.ALL; + credentialsCallbackCount = 0; + syncService = MockSyncService(); + + factory = await testUtils.testFactory(); + (_, database) = await factory.openInMemoryDatabase(); + await database.initialize(); + }); + + tearDown(() async { + await database.close(); + await syncService.stop(); + }); + + Future> waitForConnection( + {bool expectNoWarnings = true}) async { + if (expectNoWarnings) { + logger.onRecord.listen((e) { + if (e.level >= Level.WARNING) { + fail('Unexpected log: $e, ${e.stackTrace}'); + } + }); + } + await connect(); + await syncService.waitForListener; + + expect(database.currentStatus.lastSyncedAt, isNull); + expect(database.currentStatus.downloading, isFalse); + final status = StreamQueue(database.statusStream); + addTearDown(status.cancel); + + syncService.addKeepAlive(); + await expectLater( + status, emitsThrough(isSyncStatus(connected: true, hasSynced: false))); + return status; + } + + test('can disable default streams', () async { + options = SyncOptions( + syncImplementation: SyncClientImplementation.rust, + includeDefaultStreams: false, + ); + + await waitForConnection(); + final request = await syncService.waitForListener; + expect(json.decode(await request.readAsString()), + containsPair('streams', containsPair('include_defaults', false))); + }); + + test('subscribes with streams', () async { + final a = await database.syncStream('stream', {'foo': 'a'}).subscribe(); + final b = await database.syncStream('stream', {'foo': 'b'}).subscribe( + priority: StreamPriority(1)); + + final statusStream = await waitForConnection(); + final request = await syncService.waitForListener; + expect( + json.decode(await request.readAsString()), + containsPair( + 'streams', + containsPair('subscriptions', [ + { + 'stream': 'stream', + 'parameters': {'foo': 'a'}, + 'override_priority': null, + }, + { + 'stream': 'stream', + 'parameters': {'foo': 'b'}, + 'override_priority': 1, + }, + ]), + ), + ); + + syncService.addLine( + checkpoint( + lastOpId: 0, + buckets: [ + bucketDescription('a', subscriptions: [ + {'sub': 0} + ]), + bucketDescription('b', priority: 1, subscriptions: [ + {'sub': 1} + ]) + ], + streams: [ + stream('stream', false), + ], + ), + ); + + var status = await statusStream.next; + for (final subscription in [a, b]) { + expect(status.forStream(subscription)!.subscription.active, true); + expect(status.forStream(subscription)!.subscription.lastSyncedAt, isNull); + expect( + status.forStream(subscription)!.subscription.hasExplicitSubscription, + true, + ); + } + + syncService.addLine(checkpointComplete(priority: 1)); + status = await statusStream.next; + expect(status.forStream(a)!.subscription.lastSyncedAt, isNull); + expect(status.forStream(b)!.subscription.lastSyncedAt, isNotNull); + await b.waitForFirstSync(); + + syncService.addLine(checkpointComplete()); + await a.waitForFirstSync(); + }); + + test('reports default streams', () async { + final status = await waitForConnection(); + syncService.addLine( + checkpoint(lastOpId: 0, streams: [stream('default_stream', true)]), + ); + + await expectLater( + status, + emits( + isSyncStatus( + syncStreams: [ + isStreamStatus( + subscription: isSyncSubscription( + name: 'default_stream', + parameters: null, + isDefault: true, + ), + ), + ], + ), + ), + ); + }); + + test('changes subscriptions dynamically', () async { + await waitForConnection(); + syncService.addKeepAlive(); + + final subscription = await database.syncStream('a').subscribe(); + syncService.endCurrentListener(); + final request = await syncService.waitForListener; + expect( + json.decode(await request.readAsString()), + containsPair( + 'streams', + containsPair('subscriptions', [ + { + 'stream': 'a', + 'parameters': null, + 'override_priority': null, + }, + ]), + ), + ); + + // Given that the subscription has a TTL, dropping the handle should not + // re-subscribe. + subscription.unsubscribe(); + await pumpEventQueue(); + expect(syncService.controller.hasListener, isTrue); + }); + + test('subscriptions update while offline', () async { + final stream = StreamQueue(database.statusStream); + + final subscription = await database.syncStream('foo').subscribe(); + var status = await stream.next; + expect(status.forStream(subscription), isNotNull); + }); + + test('unsubscribing multiple times has no effect', () async { + final a = await database.syncStream('a').subscribe(); + final aAgain = await database.syncStream('a').subscribe(); + a.unsubscribe(); + a.unsubscribe(); // Should not decrement the refcount again + + // Pretend the streams are expired - they should still be requested because + // the core extension extends the lifetime of streams currently referenced + // before connecting. + await database.execute( + 'UPDATE ps_stream_subscriptions SET expires_at = unixepoch() - 1000'); + + await waitForConnection(); + final request = await syncService.waitForListener; + expect( + json.decode(await request.readAsString()), + containsPair( + 'streams', + containsPair('subscriptions', isNotEmpty), + ), + ); + aAgain.unsubscribe(); + }); + + test('unsubscribeAll', () async { + final a = await database.syncStream('a').subscribe(); + await database.syncStream('a').unsubscribeAll(); + + // Despite a being active, it should not be requested. + await waitForConnection(); + final request = await syncService.waitForListener; + expect( + json.decode(await request.readAsString()), + containsPair( + 'streams', + containsPair('subscriptions', isEmpty), + ), + ); + a.unsubscribe(); + }); +} diff --git a/packages/powersync_core/test/streaming_sync_test.dart b/packages/powersync_core/test/sync/streaming_sync_test.dart similarity index 97% rename from packages/powersync_core/test/streaming_sync_test.dart rename to packages/powersync_core/test/sync/streaming_sync_test.dart index 40becd16..5017993f 100644 --- a/packages/powersync_core/test/streaming_sync_test.dart +++ b/packages/powersync_core/test/sync/streaming_sync_test.dart @@ -9,10 +9,10 @@ import 'package:logging/logging.dart'; import 'package:powersync_core/powersync_core.dart'; import 'package:test/test.dart'; -import 'server/sync_server/in_memory_sync_server.dart'; -import 'test_server.dart'; -import 'utils/abstract_test_utils.dart'; -import 'utils/test_utils_impl.dart'; +import '../server/sync_server/in_memory_sync_server.dart'; +import '../test_server.dart'; +import '../utils/abstract_test_utils.dart'; +import '../utils/test_utils_impl.dart'; final testUtils = TestUtils(); diff --git a/packages/powersync_core/test/sync_types_test.dart b/packages/powersync_core/test/sync/sync_types_test.dart similarity index 95% rename from packages/powersync_core/test/sync_types_test.dart rename to packages/powersync_core/test/sync/sync_types_test.dart index 261152b2..5cd24c9d 100644 --- a/packages/powersync_core/test/sync_types_test.dart +++ b/packages/powersync_core/test/sync/sync_types_test.dart @@ -216,11 +216,11 @@ void main() { } }); - test('bucket priority comparisons', () { - expect(BucketPriority(0) < BucketPriority(3), isFalse); - expect(BucketPriority(0) > BucketPriority(3), isTrue); - expect(BucketPriority(0) >= BucketPriority(3), isTrue); - expect(BucketPriority(0) >= BucketPriority(0), isTrue); + test('stream priority comparisons', () { + expect(StreamPriority(0) < StreamPriority(3), isFalse); + expect(StreamPriority(0) > StreamPriority(3), isTrue); + expect(StreamPriority(0) >= StreamPriority(3), isTrue); + expect(StreamPriority(0) >= StreamPriority(0), isTrue); }); }); } diff --git a/packages/powersync_core/test/sync/utils.dart b/packages/powersync_core/test/sync/utils.dart new file mode 100644 index 00000000..53654f12 --- /dev/null +++ b/packages/powersync_core/test/sync/utils.dart @@ -0,0 +1,136 @@ +import 'package:powersync_core/powersync_core.dart'; +import 'package:powersync_core/src/sync/protocol.dart'; +import 'package:test/test.dart'; + +TypeMatcher isSyncStatus({ + Object? downloading, + Object? connected, + Object? connecting, + Object? hasSynced, + Object? downloadProgress, + Object? syncStreams, +}) { + var matcher = isA(); + if (downloading != null) { + matcher = matcher.having((e) => e.downloading, 'downloading', downloading); + } + if (connected != null) { + matcher = matcher.having((e) => e.connected, 'connected', connected); + } + if (connecting != null) { + matcher = matcher.having((e) => e.connecting, 'connecting', connecting); + } + if (hasSynced != null) { + matcher = matcher.having((e) => e.hasSynced, 'hasSynced', hasSynced); + } + if (downloadProgress != null) { + matcher = matcher.having( + (e) => e.downloadProgress, 'downloadProgress', downloadProgress); + } + if (syncStreams != null) { + matcher = matcher.having((e) => e.syncStreams, 'syncStreams', syncStreams); + } + + return matcher; +} + +TypeMatcher isSyncDownloadProgress({ + required Object progress, + Map priorities = const {}, +}) { + var matcher = + isA().having((e) => e, 'untilCompletion', progress); + priorities.forEach((priority, expected) { + matcher = matcher.having( + (e) => e.untilPriority(priority), 'untilPriority($priority)', expected); + }); + + return matcher; +} + +TypeMatcher progress(int completed, int total) { + return isA() + .having((e) => e.downloadedOperations, 'completed', completed) + .having((e) => e.totalOperations, 'total', total); +} + +TypeMatcher isStreamStatus({ + required Object? subscription, + Object? progress, +}) { + var matcher = isA() + .having((e) => e.subscription, 'subscription', subscription); + if (progress case final progress?) { + matcher = matcher.having((e) => e.progress, 'progress', progress); + } + + return matcher; +} + +TypeMatcher isSyncSubscription({ + required Object name, + required Object? parameters, + bool? isDefault, +}) { + var matcher = isA() + .having((e) => e.name, 'name', name) + .having((e) => e.parameters, 'parameters', parameters); + + if (isDefault != null) { + matcher = matcher.having((e) => e.isDefault, 'isDefault', isDefault); + } + + return matcher; +} + +BucketChecksum checksum( + {required String bucket, required int checksum, int priority = 1}) { + return BucketChecksum(bucket: bucket, priority: priority, checksum: checksum); +} + +/// Creates a `checkpoint` line. +Object checkpoint({ + required int lastOpId, + List buckets = const [], + String? writeCheckpoint, + List streams = const [], +}) { + return { + 'checkpoint': { + 'last_op_id': '$lastOpId', + 'write_checkpoint': null, + 'buckets': buckets, + 'streams': streams, + } + }; +} + +Object stream(String name, bool isDefault, {List errors = const []}) { + return {'name': name, 'is_default': isDefault, 'errors': errors}; +} + +/// Creates a `checkpoint_complete` or `partial_checkpoint_complete` line. +Object checkpointComplete({int? priority, String lastOpId = '1'}) { + return { + priority == null ? 'checkpoint_complete' : 'partial_checkpoint_complete': { + 'last_op_id': lastOpId, + if (priority != null) 'priority': priority, + }, + }; +} + +Object bucketDescription( + String name, { + int checksum = 0, + int priority = 3, + int count = 1, + Object? subscriptions, +}) { + return { + 'bucket': name, + 'checksum': checksum, + 'priority': priority, + 'count': count, + if (subscriptions != null) 'subscriptions': subscriptions, + }; +} diff --git a/packages/powersync_core/test/utils/abstract_test_utils.dart b/packages/powersync_core/test/utils/abstract_test_utils.dart index a95d2604..96469c5a 100644 --- a/packages/powersync_core/test/utils/abstract_test_utils.dart +++ b/packages/powersync_core/test/utils/abstract_test_utils.dart @@ -1,8 +1,11 @@ +import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:powersync_core/powersync_core.dart'; +import 'package:powersync_core/src/abort_controller.dart'; +import 'package:powersync_core/src/database/powersync_db_mixin.dart'; import 'package:powersync_core/src/sync/bucket_storage.dart'; import 'package:powersync_core/src/sync/internal_connector.dart'; import 'package:powersync_core/src/sync/options.dart'; @@ -63,7 +66,7 @@ Logger _makeTestLogger({Level level = Level.ALL, String? name}) { abstract mixin class TestPowerSyncFactory implements PowerSyncOpenFactory { Future openRawInMemoryDatabase(); - Future<(CommonDatabase, PowerSyncDatabase)> openInMemoryDatabase({ + Future<(CommonDatabase, TestDatabase)> openInMemoryDatabase({ Schema? schema, Logger? logger, }) async { @@ -71,16 +74,16 @@ abstract mixin class TestPowerSyncFactory implements PowerSyncOpenFactory { return (raw, wrapRaw(raw, customSchema: schema, logger: logger)); } - PowerSyncDatabase wrapRaw( + TestDatabase wrapRaw( CommonDatabase raw, { Logger? logger, Schema? customSchema, }) { - return PowerSyncDatabase.withDatabase( - schema: customSchema ?? schema, + return TestDatabase( database: SqliteDatabase.singleConnection( SqliteConnection.synchronousWrapper(raw)), - logger: logger, + logger: logger ?? Logger.detached('PowerSync.test'), + schema: customSchema ?? schema, ); } } @@ -151,6 +154,83 @@ class TestConnector extends PowerSyncBackendConnector { } } +/// A [PowerSyncDatabase] implemented by a single in-memory database connection +/// and a mock-HTTP sync client. +/// +/// This ensures tests for sync cover the `ConnectionManager` and other methods +/// exposed by the mixin. +final class TestDatabase + with SqliteQueries, PowerSyncDatabaseMixin + implements PowerSyncDatabase { + @override + final SqliteDatabase database; + @override + final Logger logger; + @override + Schema schema; + + @override + late final Future isInitialized; + + Client? httpClient; + + TestDatabase({ + required this.database, + required this.logger, + required this.schema, + }) { + isInitialized = baseInit(); + } + + @override + Future connectInternal({ + required PowerSyncBackendConnector connector, + required ResolvedSyncOptions options, + required List initiallyActiveStreams, + required Stream> activeStreams, + required AbortController abort, + required Zone asyncWorkZone, + }) async { + final impl = StreamingSyncImplementation( + adapter: BucketStorage(this), + schemaJson: jsonEncode(schema), + client: httpClient!, + options: options, + connector: InternalConnector.wrap(connector, this), + logger: logger, + crudUpdateTriggerStream: database + .onChange(['ps_crud'], throttle: const Duration(milliseconds: 10)), + activeSubscriptions: initiallyActiveStreams, + ); + impl.statusStream.listen(setStatus); + + asyncWorkZone.run(impl.streamingSync); + final subscriptions = activeStreams.listen(impl.updateSubscriptions); + + abort.onAbort.then((_) async { + subscriptions.cancel(); + await impl.abort(); + abort.completeAbort(); + }).ignore(); + } + + @override + Future readLock(Future Function(SqliteReadContext tx) callback, + {String? debugContext, Duration? lockTimeout}) async { + await isInitialized; + return database.readLock(callback, + debugContext: debugContext, lockTimeout: lockTimeout); + } + + @override + Future writeLock(Future Function(SqliteWriteContext tx) callback, + {String? debugContext, Duration? lockTimeout}) async { + await isInitialized; + return database.writeLock(callback, + debugContext: debugContext, lockTimeout: lockTimeout); + } +} + extension MockSync on PowerSyncDatabase { StreamingSyncImplementation connectWithMockService( Client client, From c4d28dc8eb64dd94f35f6aba2a62f421ae1141a9 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 2 Oct 2025 15:39:48 +0200 Subject: [PATCH 56/62] Warning on old attachments package --- packages/powersync_attachments_helper/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/powersync_attachments_helper/README.md b/packages/powersync_attachments_helper/README.md index dbd65bef..9ae81747 100644 --- a/packages/powersync_attachments_helper/README.md +++ b/packages/powersync_attachments_helper/README.md @@ -2,6 +2,12 @@ [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. +> [!WARNING] +> There is a new attachments helper library in the core PowerSync package, available under +> `package:powersync_core/attachments/attachments.dart`. While this package will continue to receive +> bugfixes, new feature development will only happen in the other package. + + ## Features - Handles syncing uploads, downloads and deletes between local and remote storage. From c0423b1962d1415d41fc9e9bc37e50ce8c56604a Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 2 Oct 2025 15:42:01 +0200 Subject: [PATCH 57/62] chore(release): publish packages - powersync_attachments_helper@0.6.20 - powersync@1.16.0 - powersync_core@1.6.0 - powersync_flutter_libs@0.4.12 - powersync_sqlcipher@0.1.12 --- CHANGELOG.md | 39 +++++++++++++++++++ demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/pubspec.yaml | 2 +- demos/firebase-nodejs-todolist/pubspec.yaml | 2 +- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/pubspec.yaml | 2 +- demos/supabase-todolist-drift/pubspec.yaml | 4 +- .../pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 4 +- demos/supabase-trello/pubspec.yaml | 2 +- packages/powersync/CHANGELOG.md | 7 ++++ packages/powersync/pubspec.yaml | 6 +-- .../powersync_attachments_helper/CHANGELOG.md | 4 ++ .../powersync_attachments_helper/pubspec.yaml | 4 +- packages/powersync_core/CHANGELOG.md | 8 ++++ packages/powersync_core/lib/src/version.dart | 2 +- packages/powersync_core/pubspec.yaml | 2 +- packages/powersync_flutter_libs/CHANGELOG.md | 4 ++ packages/powersync_flutter_libs/pubspec.yaml | 2 +- packages/powersync_sqlcipher/CHANGELOG.md | 7 ++++ .../powersync_sqlcipher/example/pubspec.yaml | 2 +- packages/powersync_sqlcipher/pubspec.yaml | 6 +-- 23 files changed, 93 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9937c109..a9d0ccfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,45 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 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 diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml index b2e1f55a..104a1c23 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index 9b187637..e5fa09ca 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml index 46436e8f..43537393 100644 --- a/demos/firebase-nodejs-todolist/pubspec.yaml +++ b/demos/firebase-nodejs-todolist/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-anonymous-auth/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index db67696d..7eaae283 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 2dd12558..5dfb7fd2 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml index a65519af..3d3626f2 100644 --- a/demos/supabase-simple-chat/pubspec.yaml +++ b/demos/supabase-simple-chat/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: supabase_flutter: ^2.0.2 timeago: ^3.6.0 - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/supabase-todolist-drift/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index cf0c3f4f..8ccc7e7e 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.19 - powersync: ^1.15.2 + powersync_attachments_helper: ^0.6.20 + powersync: ^1.16.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index a914651c..76b69edf 100644 --- a/demos/supabase-todolist-optional-sync/pubspec.yaml +++ b/demos/supabase-todolist-optional-sync/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.2 + powersync: ^1.16.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index 53d61b7a..4d5c201a 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -10,8 +10,8 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.15.2 - powersync_core: ^1.5.2 + powersync: ^1.16.0 + powersync_core: ^1.6.0 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index 15a7dccf..3a1327e0 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: random_name_generator: ^1.5.0 flutter_dotenv: ^5.2.1 logging: ^1.3.0 - powersync: ^1.15.2 + powersync: ^1.16.0 sqlite_async: ^0.12.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md index 9cd53047..43179ad3 100644 --- a/packages/powersync/CHANGELOG.md +++ b/packages/powersync/CHANGELOG.md @@ -1,3 +1,10 @@ +## 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. diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml index ad31d0d3..bd39106e 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync -version: 1.15.2 +version: 1.16.0 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 @@ -12,8 +12,8 @@ dependencies: sdk: flutter sqlite3_flutter_libs: ^0.5.39 - powersync_core: ^1.5.2 - powersync_flutter_libs: ^0.4.11 + powersync_core: ^1.6.0 + 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 0aa268e3..f80b8798 100644 --- a/packages/powersync_attachments_helper/CHANGELOG.md +++ b/packages/powersync_attachments_helper/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.20 + + - Add note about new attachment queue system in core package. + ## 0.6.19 - Remove direct dependency on `sqlite_async`. diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index b705cf09..63854d43 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.19 +version: 0.6.20 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ environment: @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.5.2 + powersync_core: ^1.6.0 logging: ^1.2.0 path_provider: ^2.0.13 diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index ddd78a42..1aabefb6 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/powersync_core/lib/src/version.dart b/packages/powersync_core/lib/src/version.dart index 26361d4c..05cb9eb9 100644 --- a/packages/powersync_core/lib/src/version.dart +++ b/packages/powersync_core/lib/src/version.dart @@ -1 +1 @@ -const String libraryVersion = '1.5.2'; +const String libraryVersion = '1.6.0'; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index 929b2d5e..f85058c3 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_core -version: 1.5.2 +version: 1.6.0 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Dart SDK - sync engine for building local-first apps. diff --git a/packages/powersync_flutter_libs/CHANGELOG.md b/packages/powersync_flutter_libs/CHANGELOG.md index e344e77b..79e23a87 100644 --- a/packages/powersync_flutter_libs/CHANGELOG.md +++ b/packages/powersync_flutter_libs/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.12 + + - Update core extension, add support for SwiftPM. + ## 0.4.11 - Update PowerSync core extension to version 0.4.4. diff --git a/packages/powersync_flutter_libs/pubspec.yaml b/packages/powersync_flutter_libs/pubspec.yaml index 2515531c..a6afea23 100644 --- a/packages/powersync_flutter_libs/pubspec.yaml +++ b/packages/powersync_flutter_libs/pubspec.yaml @@ -1,6 +1,6 @@ name: powersync_flutter_libs description: PowerSync core binaries for the PowerSync Flutter SDK. Needs to be included for Flutter apps. -version: 0.4.11 +version: 0.4.12 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ diff --git a/packages/powersync_sqlcipher/CHANGELOG.md b/packages/powersync_sqlcipher/CHANGELOG.md index b037c1eb..c0bcd85f 100644 --- a/packages/powersync_sqlcipher/CHANGELOG.md +++ b/packages/powersync_sqlcipher/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.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. + ## 0.1.11+1 - Fix excessive memory consumption during large sync. diff --git a/packages/powersync_sqlcipher/example/pubspec.yaml b/packages/powersync_sqlcipher/example/pubspec.yaml index d7746f71..7dc9c0c9 100644 --- a/packages/powersync_sqlcipher/example/pubspec.yaml +++ b/packages/powersync_sqlcipher/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - powersync_sqlcipher: ^0.1.11+1 + powersync_sqlcipher: ^0.1.12 dev_dependencies: flutter_test: diff --git a/packages/powersync_sqlcipher/pubspec.yaml b/packages/powersync_sqlcipher/pubspec.yaml index 6aa109dc..f2bc78eb 100644 --- a/packages/powersync_sqlcipher/pubspec.yaml +++ b/packages/powersync_sqlcipher/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_sqlcipher -version: 0.1.11+1 +version: 0.1.12 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Flutter SDK - sync engine for building local-first apps. @@ -12,8 +12,8 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.5.2 - powersync_flutter_libs: ^0.4.11 + powersync_core: ^1.6.0 + powersync_flutter_libs: ^0.4.12 sqlcipher_flutter_libs: ^0.6.4 sqlite3_web: ^0.3.0 From a355528fa9430196aea815ddbbaa2b91580560fd Mon Sep 17 00:00:00 2001 From: benitav Date: Fri, 3 Oct 2025 15:20:26 +0200 Subject: [PATCH 58/62] Improve discoverability, wording polish and fix inaccuracies --- .../powersync_attachments_helper/README.md | 18 ++++++++++++------ packages/powersync_core/doc/attachments.md | 6 ++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/powersync_attachments_helper/README.md b/packages/powersync_attachments_helper/README.md index 9ae81747..fdd46803 100644 --- a/packages/powersync_attachments_helper/README.md +++ b/packages/powersync_attachments_helper/README.md @@ -1,11 +1,19 @@ # 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] -> There is a new attachments helper library in the core PowerSync package, available under -> `package:powersync_core/attachments/attachments.dart`. While this package will continue to receive -> bugfixes, new feature development will only happen in the other package. +> 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](/packages/powersync_core/doc/attachments.md) 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 @@ -89,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_core/doc/attachments.md b/packages/powersync_core/doc/attachments.md index aad9b5cb..67383942 100644 --- a/packages/powersync_core/doc/attachments.md +++ b/packages/powersync_core/doc/attachments.md @@ -5,7 +5,7 @@ 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 primary data model, and then download it from a secondary data store such as S3. +in your local database, 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_. ## Alpha release @@ -31,8 +31,7 @@ The attachments' state is stored in a local-only attachments table. ### 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. +See the [supabase-todolist](https://github.com/powersync-ja/powersync.dart/tree/main/demos/supabase-todolist) demo for a basic example of attachment syncing. ### Setup @@ -77,7 +76,6 @@ final attachmentQueue = AttachmentQueue( ``` 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 From ae910b0d58f7bb6c366e652cceb1d9dee8e5c4cc Mon Sep 17 00:00:00 2001 From: benitav Date: Mon, 6 Oct 2025 10:56:45 +0200 Subject: [PATCH 59/62] Add benefits and better link --- packages/powersync_attachments_helper/README.md | 2 +- packages/powersync_core/doc/attachments.md | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/powersync_attachments_helper/README.md b/packages/powersync_attachments_helper/README.md index fdd46803..c9b5f532 100644 --- a/packages/powersync_attachments_helper/README.md +++ b/packages/powersync_attachments_helper/README.md @@ -10,7 +10,7 @@ > > 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](/packages/powersync_core/doc/attachments.md) to get started. +> 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`. diff --git a/packages/powersync_core/doc/attachments.md b/packages/powersync_core/doc/attachments.md index 67383942..555cda7b 100644 --- a/packages/powersync_core/doc/attachments.md +++ b/packages/powersync_core/doc/attachments.md @@ -5,16 +5,26 @@ 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 local database, and then download it from a secondary data store such as Supabase Storage or S3. +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 helpers described in this document are currently in an alpha state, intended for testing. +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 libraries for production use. +Do not rely on these utilities for production use. ## Usage From 227f416c0576fe04cbfc6768060ff96fc08bc420 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 6 Oct 2025 10:58:59 +0200 Subject: [PATCH 60/62] Fix decoding subscriptions from status --- packages/powersync_core/lib/src/web/sync_worker_protocol.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart index 950cd1d6..0448fe5a 100644 --- a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart +++ b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart @@ -328,8 +328,8 @@ extension type SerializedSyncStatus._(JSObject _) implements JSObject { }, streamSubscriptions: switch (streamSubscriptions) { null => null, - final serialized => (json.decode(serialized) as List) - .map((e) => CoreActiveStreamSubscription.fromJson( + final serialized => (json.decode(serialized) as List?) + ?.map((e) => CoreActiveStreamSubscription.fromJson( e as Map)) .toList(), }, From 4f4da24e580dec6b1d29a5e0907b83ba7c55e3d8 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 6 Oct 2025 10:51:52 +0200 Subject: [PATCH 61/62] Attachments docs: Point to uses in example --- demos/supabase-todolist/lib/attachments/queue.dart | 9 --------- packages/powersync_core/doc/attachments.md | 5 +++++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/demos/supabase-todolist/lib/attachments/queue.dart b/demos/supabase-todolist/lib/attachments/queue.dart index 80460daf..8f036c85 100644 --- a/demos/supabase-todolist/lib/attachments/queue.dart +++ b/demos/supabase-todolist/lib/attachments/queue.dart @@ -53,12 +53,3 @@ Future savePhotoAttachment( }, ); } - -Future deletePhotoAttachment(String fileId) async { - return await attachmentQueue.deleteFile( - attachmentId: fileId, - updateHook: (context, attachment) async { - // Optionally update relationships in the same transaction - }, - ); -} diff --git a/packages/powersync_core/doc/attachments.md b/packages/powersync_core/doc/attachments.md index 555cda7b..6919430f 100644 --- a/packages/powersync_core/doc/attachments.md +++ b/packages/powersync_core/doc/attachments.md @@ -43,6 +43,11 @@ The attachments' state is stored in a local-only attachments table. 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. From 1c904a89947c6e17164a02b6243628c29acfd017 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 6 Oct 2025 15:08:46 +0200 Subject: [PATCH 62/62] chore(release): publish packages - powersync@1.16.1 - powersync_core@1.6.1 - powersync_sqlcipher@0.1.13 --- CHANGELOG.md | 33 +++++++++++++++++++ demos/benchmarks/pubspec.yaml | 2 +- demos/django-todolist/pubspec.yaml | 2 +- demos/firebase-nodejs-todolist/pubspec.yaml | 2 +- demos/supabase-anonymous-auth/pubspec.yaml | 2 +- .../supabase-edge-function-auth/pubspec.yaml | 2 +- demos/supabase-simple-chat/pubspec.yaml | 2 +- demos/supabase-todolist-drift/pubspec.yaml | 2 +- .../pubspec.yaml | 2 +- demos/supabase-todolist/pubspec.yaml | 4 +-- demos/supabase-trello/pubspec.yaml | 2 +- packages/powersync/CHANGELOG.md | 4 +++ packages/powersync/pubspec.yaml | 4 +-- .../powersync_attachments_helper/pubspec.yaml | 2 +- packages/powersync_core/CHANGELOG.md | 6 ++++ packages/powersync_core/lib/src/version.dart | 2 +- packages/powersync_core/pubspec.yaml | 2 +- packages/powersync_sqlcipher/CHANGELOG.md | 4 +++ .../powersync_sqlcipher/example/pubspec.yaml | 2 +- packages/powersync_sqlcipher/pubspec.yaml | 4 +-- 20 files changed, 66 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9d0ccfc..e76e3a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ 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 diff --git a/demos/benchmarks/pubspec.yaml b/demos/benchmarks/pubspec.yaml index 104a1c23..aea63c20 100644 --- a/demos/benchmarks/pubspec.yaml +++ b/demos/benchmarks/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.16.0 + powersync: ^1.16.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/django-todolist/pubspec.yaml b/demos/django-todolist/pubspec.yaml index e5fa09ca..4c1cacff 100644 --- a/demos/django-todolist/pubspec.yaml +++ b/demos/django-todolist/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.16.0 + powersync: ^1.16.1 path_provider: ^2.1.1 path: ^1.8.3 logging: ^1.2.0 diff --git a/demos/firebase-nodejs-todolist/pubspec.yaml b/demos/firebase-nodejs-todolist/pubspec.yaml index 43537393..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.16.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/pubspec.yaml b/demos/supabase-anonymous-auth/pubspec.yaml index 7eaae283..b4f44bcb 100644 --- a/demos/supabase-anonymous-auth/pubspec.yaml +++ b/demos/supabase-anonymous-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.16.0 + powersync: ^1.16.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-edge-function-auth/pubspec.yaml b/demos/supabase-edge-function-auth/pubspec.yaml index 5dfb7fd2..f1f5ddcb 100644 --- a/demos/supabase-edge-function-auth/pubspec.yaml +++ b/demos/supabase-edge-function-auth/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - powersync: ^1.16.0 + powersync: ^1.16.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.2 path: ^1.8.3 diff --git a/demos/supabase-simple-chat/pubspec.yaml b/demos/supabase-simple-chat/pubspec.yaml index 3d3626f2..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.16.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/pubspec.yaml b/demos/supabase-todolist-drift/pubspec.yaml index 8ccc7e7e..051931b2 100644 --- a/demos/supabase-todolist-drift/pubspec.yaml +++ b/demos/supabase-todolist-drift/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter powersync_attachments_helper: ^0.6.20 - powersync: ^1.16.0 + powersync: ^1.16.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist-optional-sync/pubspec.yaml b/demos/supabase-todolist-optional-sync/pubspec.yaml index 76b69edf..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.16.0 + powersync: ^1.16.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index 4d5c201a..2175e2b4 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -10,8 +10,8 @@ environment: dependencies: flutter: sdk: flutter - powersync: ^1.16.0 - powersync_core: ^1.6.0 + powersync: ^1.16.1 + powersync_core: ^1.6.1 path_provider: ^2.1.1 supabase_flutter: ^2.0.1 path: ^1.8.3 diff --git a/demos/supabase-trello/pubspec.yaml b/demos/supabase-trello/pubspec.yaml index 3a1327e0..a609124e 100644 --- a/demos/supabase-trello/pubspec.yaml +++ b/demos/supabase-trello/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: random_name_generator: ^1.5.0 flutter_dotenv: ^5.2.1 logging: ^1.3.0 - powersync: ^1.16.0 + powersync: ^1.16.1 sqlite_async: ^0.12.0 path_provider: ^2.1.5 supabase_flutter: ^2.8.3 diff --git a/packages/powersync/CHANGELOG.md b/packages/powersync/CHANGELOG.md index 43179ad3..4f30cb92 100644 --- a/packages/powersync/CHANGELOG.md +++ b/packages/powersync/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.16.1 + + - Web: Fix decoding sync streams on status. + ## 1.16.0 - Add `getCrudTransactions()` returning a stream of completed transactions for uploads. diff --git a/packages/powersync/pubspec.yaml b/packages/powersync/pubspec.yaml index bd39106e..26fffc25 100644 --- a/packages/powersync/pubspec.yaml +++ b/packages/powersync/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync -version: 1.16.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 @@ -12,7 +12,7 @@ dependencies: sdk: flutter sqlite3_flutter_libs: ^0.5.39 - powersync_core: ^1.6.0 + powersync_core: ^1.6.1 powersync_flutter_libs: ^0.4.12 collection: ^1.17.0 diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index 63854d43..971228f9 100644 --- a/packages/powersync_attachments_helper/pubspec.yaml +++ b/packages/powersync_attachments_helper/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.6.0 + powersync_core: ^1.6.1 logging: ^1.2.0 path_provider: ^2.0.13 diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index 1aabefb6..4ba23772 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,9 @@ +## 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. diff --git a/packages/powersync_core/lib/src/version.dart b/packages/powersync_core/lib/src/version.dart index 05cb9eb9..211139eb 100644 --- a/packages/powersync_core/lib/src/version.dart +++ b/packages/powersync_core/lib/src/version.dart @@ -1 +1 @@ -const String libraryVersion = '1.6.0'; +const String libraryVersion = '1.6.1'; diff --git a/packages/powersync_core/pubspec.yaml b/packages/powersync_core/pubspec.yaml index f85058c3..723cd4e9 100644 --- a/packages/powersync_core/pubspec.yaml +++ b/packages/powersync_core/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_core -version: 1.6.0 +version: 1.6.1 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Dart SDK - sync engine for building local-first apps. diff --git a/packages/powersync_sqlcipher/CHANGELOG.md b/packages/powersync_sqlcipher/CHANGELOG.md index c0bcd85f..33b9cb2f 100644 --- a/packages/powersync_sqlcipher/CHANGELOG.md +++ b/packages/powersync_sqlcipher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.13 + + - Web: Fix decoding sync streams on status. + ## 0.1.12 - Add `getCrudTransactions()` returning a stream of completed transactions for uploads. diff --git a/packages/powersync_sqlcipher/example/pubspec.yaml b/packages/powersync_sqlcipher/example/pubspec.yaml index 7dc9c0c9..753aa73a 100644 --- a/packages/powersync_sqlcipher/example/pubspec.yaml +++ b/packages/powersync_sqlcipher/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - powersync_sqlcipher: ^0.1.12 + powersync_sqlcipher: ^0.1.13 dev_dependencies: flutter_test: diff --git a/packages/powersync_sqlcipher/pubspec.yaml b/packages/powersync_sqlcipher/pubspec.yaml index f2bc78eb..b73913c2 100644 --- a/packages/powersync_sqlcipher/pubspec.yaml +++ b/packages/powersync_sqlcipher/pubspec.yaml @@ -1,5 +1,5 @@ name: powersync_sqlcipher -version: 0.1.12 +version: 0.1.13 homepage: https://powersync.com repository: https://github.com/powersync-ja/powersync.dart description: PowerSync Flutter SDK - sync engine for building local-first apps. @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - powersync_core: ^1.6.0 + powersync_core: ^1.6.1 powersync_flutter_libs: ^0.4.12 sqlcipher_flutter_libs: ^0.6.4 sqlite3_web: ^0.3.0