From d7a6881db85c7ed2ae50e0162bfff9bb1cebfe9c Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 26 Nov 2025 11:47:00 +0200 Subject: [PATCH 1/8] feat: app metadata --- demos/benchmarks/pubspec.lock | 8 ++-- demos/django-todolist/pubspec.lock | 8 ++-- demos/firebase-nodejs-todolist/pubspec.lock | 8 ++-- demos/supabase-anonymous-auth/pubspec.lock | 8 ++-- .../supabase-edge-function-auth/pubspec.lock | 8 ++-- demos/supabase-simple-chat/pubspec.lock | 8 ++-- demos/supabase-todolist-drift/pubspec.lock | 8 ++-- .../pubspec.lock | 8 ++-- demos/supabase-todolist/lib/powersync.dart | 4 +- demos/supabase-todolist/macos/Podfile.lock | 12 +++--- demos/supabase-todolist/pubspec.lock | 23 ++++------- demos/supabase-trello/pubspec.lock | 8 ++-- .../powersync_core/lib/src/sync/options.dart | 12 ++++++ .../powersync_core/lib/src/sync/protocol.dart | 7 +++- .../lib/src/sync/streaming_sync.dart | 33 ++++++++++++--- .../powersync_sqlcipher/example/pubspec.lock | 41 +++++++++---------- 16 files changed, 116 insertions(+), 88 deletions(-) diff --git a/demos/benchmarks/pubspec.lock b/demos/benchmarks/pubspec.lock index b2fb290b..bb759c1e 100644 --- a/demos/benchmarks/pubspec.lock +++ b/demos/benchmarks/pubspec.lock @@ -191,10 +191,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mutex: dependency: transitive description: @@ -401,10 +401,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/demos/django-todolist/pubspec.lock b/demos/django-todolist/pubspec.lock index 0cbb85ee..aae9c9f3 100644 --- a/demos/django-todolist/pubspec.lock +++ b/demos/django-todolist/pubspec.lock @@ -204,10 +204,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mutex: dependency: transitive description: @@ -470,10 +470,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/demos/firebase-nodejs-todolist/pubspec.lock b/demos/firebase-nodejs-todolist/pubspec.lock index 30b59fdd..3c01b04d 100644 --- a/demos/firebase-nodejs-todolist/pubspec.lock +++ b/demos/firebase-nodejs-todolist/pubspec.lock @@ -324,10 +324,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -654,10 +654,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/demos/supabase-anonymous-auth/pubspec.lock b/demos/supabase-anonymous-auth/pubspec.lock index a988c291..f36ba2ac 100644 --- a/demos/supabase-anonymous-auth/pubspec.lock +++ b/demos/supabase-anonymous-auth/pubspec.lock @@ -268,10 +268,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -598,10 +598,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/demos/supabase-edge-function-auth/pubspec.lock b/demos/supabase-edge-function-auth/pubspec.lock index a988c291..f36ba2ac 100644 --- a/demos/supabase-edge-function-auth/pubspec.lock +++ b/demos/supabase-edge-function-auth/pubspec.lock @@ -268,10 +268,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -598,10 +598,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/demos/supabase-simple-chat/pubspec.lock b/demos/supabase-simple-chat/pubspec.lock index 8a63472b..dba63e09 100644 --- a/demos/supabase-simple-chat/pubspec.lock +++ b/demos/supabase-simple-chat/pubspec.lock @@ -284,10 +284,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -614,10 +614,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" timeago: dependency: "direct main" description: diff --git a/demos/supabase-todolist-drift/pubspec.lock b/demos/supabase-todolist-drift/pubspec.lock index be3cba5f..940c8d19 100644 --- a/demos/supabase-todolist-drift/pubspec.lock +++ b/demos/supabase-todolist-drift/pubspec.lock @@ -604,10 +604,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1061,10 +1061,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" timing: dependency: transitive description: diff --git a/demos/supabase-todolist-optional-sync/pubspec.lock b/demos/supabase-todolist-optional-sync/pubspec.lock index 6aa2c4a3..c7f1562b 100644 --- a/demos/supabase-todolist-optional-sync/pubspec.lock +++ b/demos/supabase-todolist-optional-sync/pubspec.lock @@ -340,10 +340,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -694,10 +694,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/demos/supabase-todolist/lib/powersync.dart b/demos/supabase-todolist/lib/powersync.dart index 412e53c7..d6e5973d 100644 --- a/demos/supabase-todolist/lib/powersync.dart +++ b/demos/supabase-todolist/lib/powersync.dart @@ -153,7 +153,9 @@ Future getDatabasePath() async { return join(dir.path, dbFilename); } -const options = SyncOptions(syncImplementation: SyncClientImplementation.rust); +const options = SyncOptions( + syncImplementation: SyncClientImplementation.rust, + appMetadata: {'app_version': '1.0.1'}); Future openDatabase() async { // Open the local database diff --git a/demos/supabase-todolist/macos/Podfile.lock b/demos/supabase-todolist/macos/Podfile.lock index 51b6fdad..0bdf6b82 100644 --- a/demos/supabase-todolist/macos/Podfile.lock +++ b/demos/supabase-todolist/macos/Podfile.lock @@ -72,15 +72,15 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f + app_links: c3185399a5cabc2e610ee5ad52fb7269b84ff869 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 powersync-sqlite-core: 42c4a42a692b3b770a5488778789430d67a39b49 - powersync_flutter_libs: 19fc6b96ff8155ffea72a08990f6c9f2e712b8a6 - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + powersync_flutter_libs: 6d1e96884e20082a54b3ceec06939649d038f572 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 - url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + sqlite3_flutter_libs: 86f82662868ee26ff3451f73cac9c5fc2a1f57fa + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 diff --git a/demos/supabase-todolist/pubspec.lock b/demos/supabase-todolist/pubspec.lock index 2e88f33f..916c0aca 100644 --- a/demos/supabase-todolist/pubspec.lock +++ b/demos/supabase-todolist/pubspec.lock @@ -420,10 +420,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -567,13 +567,6 @@ packages: relative: true source: path version: "1.16.1" - powersync_attachments_helper: - dependency: "direct overridden" - description: - path: "../../packages/powersync_attachments_helper" - relative: true - source: path - version: "0.6.20" powersync_core: dependency: "direct main" description: @@ -853,26 +846,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.11" typed_data: dependency: transitive description: diff --git a/demos/supabase-trello/pubspec.lock b/demos/supabase-trello/pubspec.lock index c2e77eb7..b2c6c349 100644 --- a/demos/supabase-trello/pubspec.lock +++ b/demos/supabase-trello/pubspec.lock @@ -428,10 +428,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -782,10 +782,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: diff --git a/packages/powersync_core/lib/src/sync/options.dart b/packages/powersync_core/lib/src/sync/options.dart index ee8b3c63..94a70046 100644 --- a/packages/powersync_core/lib/src/sync/options.dart +++ b/packages/powersync_core/lib/src/sync/options.dart @@ -3,6 +3,11 @@ import 'package:meta/meta.dart'; /// Options that affect how the sync client connects to the sync service. final class SyncOptions { + /// A map of application metadata that is passed to the PowerSync service. + /// + /// Application metadata that will be displayed in PowerSync service logs. + final Map? appMetadata; + /// A JSON object that is passed to the sync service and forwarded to sync /// rules. /// @@ -39,12 +44,14 @@ final class SyncOptions { this.params, this.syncImplementation = SyncClientImplementation.defaultClient, this.includeDefaultStreams, + this.appMetadata, }); SyncOptions _copyWith({ Duration? crudThrottleTime, Duration? retryDelay, Map? params, + Map? appMetadata, }) { return SyncOptions( crudThrottleTime: crudThrottleTime ?? this.crudThrottleTime, @@ -52,6 +59,7 @@ final class SyncOptions { params: params ?? this.params, syncImplementation: syncImplementation, includeDefaultStreams: includeDefaultStreams, + appMetadata: appMetadata ?? this.appMetadata, ); } } @@ -89,14 +97,18 @@ extension type ResolvedSyncOptions(SyncOptions source) { Duration? crudThrottleTime, Duration? retryDelay, Map? params, + Map? appMetadata, }) { return ResolvedSyncOptions((source ?? SyncOptions())._copyWith( crudThrottleTime: crudThrottleTime, retryDelay: retryDelay, params: params, + appMetadata: appMetadata, )); } + Map get appMetadata => source.appMetadata ?? const {}; + Duration get crudThrottleTime => source.crudThrottleTime ?? const Duration(milliseconds: 10); diff --git a/packages/powersync_core/lib/src/sync/protocol.dart b/packages/powersync_core/lib/src/sync/protocol.dart index 4e07334b..8ca27312 100644 --- a/packages/powersync_core/lib/src/sync/protocol.dart +++ b/packages/powersync_core/lib/src/sync/protocol.dart @@ -247,15 +247,18 @@ class StreamingSyncRequest { bool includeChecksum = true; String clientId; Map? parameters; + Map? appMetadata; - StreamingSyncRequest(this.buckets, this.parameters, this.clientId); + StreamingSyncRequest( + this.buckets, this.parameters, this.clientId, this.appMetadata); Map toJson() { final Map json = { 'buckets': buckets, 'include_checksum': includeChecksum, 'raw_data': true, - 'client_id': clientId + 'client_id': clientId, + 'app_metadata': appMetadata, }; if (parameters != null) { diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 60deef12..fd974db9 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -12,14 +12,14 @@ import 'package:powersync_core/src/sync/options.dart'; import 'package:powersync_core/src/user_agent/user_agent.dart'; import 'package:sqlite_async/mutex.dart'; -import 'bucket_storage.dart'; import '../crud.dart'; +import 'bucket_storage.dart'; import 'instruction.dart'; import 'internal_connector.dart'; import 'mutable_sync_status.dart'; +import 'protocol.dart'; import 'stream_utils.dart'; import 'sync_status.dart'; -import 'protocol.dart'; typedef SubscribedStream = ({String name, String parameters}); @@ -339,8 +339,8 @@ class StreamingSyncImplementation implements StreamingSync { Checkpoint? targetCheckpoint; - var requestStream = _streamingSyncRequest( - StreamingSyncRequest(bucketRequests, options.params, clientId!)) + var requestStream = _streamingSyncRequest(StreamingSyncRequest( + bucketRequests, options.params, clientId!, options.appMetadata)) .map(ReceivedLine.new); var merged = addBroadcast(requestStream, _nonLineSyncEvents.stream); @@ -632,6 +632,7 @@ final class _ActiveRustStreamingIteration { await _control( 'start', convert.json.encode({ + 'app_metadata': sync.options.appMetadata, 'parameters': sync.options.params, 'schema': convert.json.decode(sync.schemaJson), 'include_defaults': sync.options.includeDefaultStreams, @@ -766,8 +767,28 @@ final class _ActiveRustStreamingIteration { _ => Level.WARNING, }, line); - case EstablishSyncStream(): - _completedStream.complete(_handleLines(instruction)); + case EstablishSyncStream(:final request): + // FIXME + // Merge app_metadata from options into the instruction request + // since the Rust instruction does not yet merge app_metadata from the supplied options. + // Rust client values will override options values if present. + final mergedRequest = Map.from(request); + final appMetadata = sync.options.appMetadata; + if (appMetadata.isNotEmpty) { + final existingMetadata = request['app_metadata']; + if (existingMetadata is Map) { + // Merge: start with options, then let Rust override + mergedRequest['app_metadata'] = { + ...appMetadata, + ...existingMetadata.cast(), + }; + } else { + // No existing metadata, just use options + mergedRequest['app_metadata'] = appMetadata; + } + } + _completedStream + .complete(_handleLines(EstablishSyncStream(mergedRequest))); case UpdateSyncStatus(:final status): sync._state.updateStatus((m) => m.applyFromCore(status)); case FetchCredentials(:final didExpire): diff --git a/packages/powersync_sqlcipher/example/pubspec.lock b/packages/powersync_sqlcipher/example/pubspec.lock index 29f874c6..30c2fbc5 100644 --- a/packages/powersync_sqlcipher/example/pubspec.lock +++ b/packages/powersync_sqlcipher/example/pubspec.lock @@ -222,10 +222,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -315,28 +315,25 @@ packages: source: hosted version: "2.1.8" powersync_core: - dependency: transitive + dependency: "direct overridden" description: - name: powersync_core - sha256: "878f335489bffa7f92167b42ce4ac2c0895015387a74285d4f2f2a11d47e08d2" - url: "https://pub.dev" - source: hosted + path: "../../powersync_core" + relative: true + source: path version: "1.6.1" powersync_flutter_libs: - dependency: transitive + dependency: "direct overridden" description: - name: powersync_flutter_libs - sha256: "6385c74c3537b72086f62becd038e1f4a50bff57783e260de95f89f1c2a16aa2" - url: "https://pub.dev" - source: hosted + path: "../../powersync_flutter_libs" + relative: true + source: path version: "0.4.12" powersync_sqlcipher: dependency: "direct main" description: - name: powersync_sqlcipher - sha256: d22a358480d60f4f4091b03100a58da6651389ca21e23427a90812148da9d196 - url: "https://pub.dev" - source: hosted + path: ".." + relative: true + source: path version: "0.1.13" process: dependency: transitive @@ -419,18 +416,18 @@ packages: dependency: transitive description: name: sqlite3_web - sha256: "0f6ebcb4992d1892ac5c8b5ecd22a458ab9c5eb6428b11ae5ecb5d63545844da" + sha256: "3973adf9ee74e485d5552319e1903129f4ed999fe675aa0b4b5d35d112b2aa1c" url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.4.1" sqlite_async: dependency: transitive description: name: sqlite_async - sha256: cf1871971324cccc03d26762ff10406fc6384af9a1ea538f12352aa5906164ec + sha256: ba3acc93e20a810ce6bde7ba1a3356ca5b162d34906f186374398106f1458f52 url: "https://pub.dev" source: hosted - version: "0.12.2" + version: "0.13.0" stack_trace: dependency: transitive description: @@ -475,10 +472,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" typed_data: dependency: transitive description: From 4c820738d07e17caf3119ae96b5045629a5c031f Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 9 Dec 2025 10:54:17 +0200 Subject: [PATCH 2/8] Add appmetadata to demo --- demos/supabase-todolist/lib/powersync.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/demos/supabase-todolist/lib/powersync.dart b/demos/supabase-todolist/lib/powersync.dart index d6e5973d..43013f06 100644 --- a/demos/supabase-todolist/lib/powersync.dart +++ b/demos/supabase-todolist/lib/powersync.dart @@ -171,7 +171,11 @@ Future openDatabase() async { // If the user is already logged in, connect immediately. // Otherwise, connect once logged in. currentConnector = SupabaseConnector(); - db.connect(connector: currentConnector, options: options); + db.connect( + connector: currentConnector, + options: SyncOptions( + syncImplementation: SyncClientImplementation.rust, + appMetadata: {'app_version': '1.0.1'})); } Supabase.instance.client.auth.onAuthStateChange.listen((data) async { From 8759d66a114dfbccb2cbed7b886bd4a4956e2968 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 10 Dec 2025 10:47:54 +0200 Subject: [PATCH 3/8] remove fixme. Add unit tests --- .../lib/src/sync/streaming_sync.dart | 22 +---------- .../powersync_core/test/sync/stream_test.dart | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/powersync_core/lib/src/sync/streaming_sync.dart b/packages/powersync_core/lib/src/sync/streaming_sync.dart index 6e1272d3..e3e6a922 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -775,27 +775,7 @@ final class _ActiveRustStreamingIteration { }, line); case EstablishSyncStream(:final request): - // FIXME - // Merge app_metadata from options into the instruction request - // since the Rust instruction does not yet merge app_metadata from the supplied options. - // Rust client values will override options values if present. - final mergedRequest = Map.from(request); - final appMetadata = sync.options.appMetadata; - if (appMetadata.isNotEmpty) { - final existingMetadata = request['app_metadata']; - if (existingMetadata is Map) { - // Merge: start with options, then let Rust override - mergedRequest['app_metadata'] = { - ...appMetadata, - ...existingMetadata.cast(), - }; - } else { - // No existing metadata, just use options - mergedRequest['app_metadata'] = appMetadata; - } - } - _completedStream - .complete(_handleLines(EstablishSyncStream(mergedRequest))); + _completedStream.complete(_handleLines(EstablishSyncStream(request))); case UpdateSyncStatus(:final status): sync._state.updateStatus((m) => m.applyFromCore(status)); case FetchCredentials(:final didExpire): diff --git a/packages/powersync_core/test/sync/stream_test.dart b/packages/powersync_core/test/sync/stream_test.dart index 1625656c..0f2978ec 100644 --- a/packages/powersync_core/test/sync/stream_test.dart +++ b/packages/powersync_core/test/sync/stream_test.dart @@ -4,7 +4,6 @@ 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'; @@ -260,4 +259,40 @@ void main() { ); a.unsubscribe(); }); + + test('passes app metadata to the server (rust)', () async { + options = SyncOptions( + syncImplementation: SyncClientImplementation.rust, + appMetadata: {'foo': 'bar'}, + ); + + await waitForConnection(); + + final request = await syncService.waitForListener; + expect( + json.decode(await request.readAsString()), + containsPair( + 'app_metadata', + containsPair('foo', 'bar'), + ), + ); + }); + + test('passes app metadata to the server (legacy sync client)', () async { + options = SyncOptions( + syncImplementation: SyncClientImplementation.dart, + appMetadata: {'foo': 'bar'}, + ); + + await waitForConnection(); + + final request = await syncService.waitForListener; + expect( + json.decode(await request.readAsString()), + containsPair( + 'app_metadata', + containsPair('foo', 'bar'), + ), + ); + }); } From 92eb0977bb24c2e348d607dd55cc6ebdae2f9997 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 10 Dec 2025 10:52:37 +0200 Subject: [PATCH 4/8] cleanup --- packages/powersync_core/lib/src/sync/streaming_sync.dart | 4 ++-- 1 file changed, 2 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 e3e6a922..3488d13f 100644 --- a/packages/powersync_core/lib/src/sync/streaming_sync.dart +++ b/packages/powersync_core/lib/src/sync/streaming_sync.dart @@ -774,8 +774,8 @@ final class _ActiveRustStreamingIteration { _ => Level.WARNING, }, line); - case EstablishSyncStream(:final request): - _completedStream.complete(_handleLines(EstablishSyncStream(request))); + case EstablishSyncStream(): + _completedStream.complete(_handleLines(instruction)); case UpdateSyncStatus(:final status): sync._state.updateStatus((m) => m.applyFromCore(status)); case FetchCredentials(:final didExpire): From ba8430edbd87057ce0c3c4004f51a46986ee05bc Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 10 Dec 2025 11:58:57 +0200 Subject: [PATCH 5/8] fix lint --- demos/supabase-todolist/lib/powersync.dart | 2 +- packages/powersync_core/test/sync/stream_test.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/demos/supabase-todolist/lib/powersync.dart b/demos/supabase-todolist/lib/powersync.dart index 43013f06..4e1c6751 100644 --- a/demos/supabase-todolist/lib/powersync.dart +++ b/demos/supabase-todolist/lib/powersync.dart @@ -173,7 +173,7 @@ Future openDatabase() async { currentConnector = SupabaseConnector(); db.connect( connector: currentConnector, - options: SyncOptions( + options: const SyncOptions( syncImplementation: SyncClientImplementation.rust, appMetadata: {'app_version': '1.0.1'})); } diff --git a/packages/powersync_core/test/sync/stream_test.dart b/packages/powersync_core/test/sync/stream_test.dart index 0f2978ec..31ef4619 100644 --- a/packages/powersync_core/test/sync/stream_test.dart +++ b/packages/powersync_core/test/sync/stream_test.dart @@ -280,6 +280,7 @@ void main() { test('passes app metadata to the server (legacy sync client)', () async { options = SyncOptions( + // ignore: deprecated_member_use syncImplementation: SyncClientImplementation.dart, appMetadata: {'foo': 'bar'}, ); From fa703e0a75ef884f9d737e5fd2b7b0eeb9f1f9e7 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 10 Dec 2025 12:06:48 +0200 Subject: [PATCH 6/8] actually fix lint --- packages/powersync_core/test/sync/stream_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/powersync_core/test/sync/stream_test.dart b/packages/powersync_core/test/sync/stream_test.dart index 31ef4619..afc81b45 100644 --- a/packages/powersync_core/test/sync/stream_test.dart +++ b/packages/powersync_core/test/sync/stream_test.dart @@ -280,7 +280,7 @@ void main() { test('passes app metadata to the server (legacy sync client)', () async { options = SyncOptions( - // ignore: deprecated_member_use + // ignore: deprecated_member_use_from_same_package syncImplementation: SyncClientImplementation.dart, appMetadata: {'foo': 'bar'}, ); From 12c15572a6c17498394529ecd0c9188ede911959 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 10 Dec 2025 12:12:00 +0200 Subject: [PATCH 7/8] cleanup demo --- demos/supabase-todolist/lib/powersync.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/demos/supabase-todolist/lib/powersync.dart b/demos/supabase-todolist/lib/powersync.dart index 4e1c6751..d6e5973d 100644 --- a/demos/supabase-todolist/lib/powersync.dart +++ b/demos/supabase-todolist/lib/powersync.dart @@ -171,11 +171,7 @@ Future openDatabase() async { // If the user is already logged in, connect immediately. // Otherwise, connect once logged in. currentConnector = SupabaseConnector(); - db.connect( - connector: currentConnector, - options: const SyncOptions( - syncImplementation: SyncClientImplementation.rust, - appMetadata: {'app_version': '1.0.1'})); + db.connect(connector: currentConnector, options: options); } Supabase.instance.client.auth.onAuthStateChange.listen((data) async { From a927bc6ec43b3dbe08c89ad9fc85a9ea260c4fbf Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 12 Dec 2025 12:37:19 +0200 Subject: [PATCH 8/8] add support for shared web workers --- packages/powersync_core/lib/src/sync/options.dart | 4 +++- packages/powersync_core/lib/src/web/sync_worker.dart | 5 +++++ .../powersync_core/lib/src/web/sync_worker_protocol.dart | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/powersync_core/lib/src/sync/options.dart b/packages/powersync_core/lib/src/sync/options.dart index 4d228ca4..ef677436 100644 --- a/packages/powersync_core/lib/src/sync/options.dart +++ b/packages/powersync_core/lib/src/sync/options.dart @@ -125,13 +125,15 @@ extension type ResolvedSyncOptions(SyncOptions source) { syncImplementation: other.syncImplementation, includeDefaultStreams: other.includeDefaultStreams ?? includeDefaultStreams, + appMetadata: other.appMetadata ?? appMetadata, ); final didChange = !_mapEquality.equals(newOptions.params, params) || newOptions.crudThrottleTime != crudThrottleTime || newOptions.retryDelay != retryDelay || newOptions.syncImplementation != source.syncImplementation || - newOptions.includeDefaultStreams != includeDefaultStreams; + newOptions.includeDefaultStreams != includeDefaultStreams || + !_mapEquality.equals(newOptions.appMetadata, appMetadata); return (ResolvedSyncOptions(newOptions), didChange); } diff --git a/packages/powersync_core/lib/src/web/sync_worker.dart b/packages/powersync_core/lib/src/web/sync_worker.dart index 1c92808f..02a5bdb9 100644 --- a/packages/powersync_core/lib/src/web/sync_worker.dart +++ b/packages/powersync_core/lib/src/web/sync_worker.dart @@ -94,6 +94,11 @@ class _ConnectedClient { null => SyncClientImplementation.defaultClient, final name => SyncClientImplementation.values.byName(name), }, + appMetadata: switch (request.appMetadataEncoded) { + null => null, + final encodedAppMetadata => Map.from( + jsonDecode(encodedAppMetadata) as Map), + }, ); _runner = _worker.referenceSyncTask( 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 0448fe5a..2bd3f18e 100644 --- a/packages/powersync_core/lib/src/web/sync_worker_protocol.dart +++ b/packages/powersync_core/lib/src/web/sync_worker_protocol.dart @@ -80,6 +80,7 @@ extension type StartSynchronization._(JSObject _) implements JSObject { required String schemaJson, String? syncParamsEncoded, UpdateSubscriptions? subscriptions, + String? appMetadataEncoded, }); external String get databaseName; @@ -90,6 +91,7 @@ extension type StartSynchronization._(JSObject _) implements JSObject { external String get schemaJson; external String? get syncParamsEncoded; external UpdateSubscriptions? get subscriptions; + external String? get appMetadataEncoded; } @anonymous @@ -483,6 +485,10 @@ final class WorkerCommunicationChannel { final params => jsonEncode(params), }, subscriptions: UpdateSubscriptions(-1, streams), + appMetadataEncoded: switch (options.source.appMetadata) { + null => null, + final appMetadata => jsonEncode(appMetadata), + }, ), )); await completion;