From eb1ca40e3e9ed342be65c253fdad781ff1061c40 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 21 Nov 2025 09:10:29 +0200 Subject: [PATCH 1/7] wip --- .../src/components/providers/SystemProvider.tsx | 6 +++++- .../sync/stream/AbstractStreamingSyncImplementation.ts | 10 +++++++++- .../src/client/sync/stream/streaming-sync-types.ts | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx index 8a3f3c209..8014e05ae 100644 --- a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx +++ b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx @@ -68,7 +68,11 @@ export const SystemProvider = ({ children }: { children: React.ReactNode }) => { const l = connector.registerListener({ initialized: () => {}, sessionStarted: () => { - powerSync.connect(connector); + powerSync.connect(connector, { + appMetadata: { + app_version: '1.0.0' + } + }); } }); diff --git a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index ab3e37c7c..1b2bc3467 100644 --- a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -16,7 +16,7 @@ import { import { CrudEntry } from '../bucket/CrudEntry.js'; import { SyncDataBucket } from '../bucket/SyncDataBucket.js'; import { AbstractRemote, FetchStrategy, SyncStreamOptions } from './AbstractRemote.js'; -import { coreStatusToJs, EstablishSyncStream, Instruction, SyncPriorityStatus } from './core-instruction.js'; +import { EstablishSyncStream, Instruction, coreStatusToJs } from './core-instruction.js'; import { BucketRequest, CrudUploadNotification, @@ -129,6 +129,11 @@ export interface InternalConnectionOptions extends BaseConnectionOptions, Additi /** @internal */ export interface BaseConnectionOptions { + /** + * A set of metadata to be included in service logs. + */ + appMetadata?: Record; + /** * Whether to use a JavaScript implementation to handle received sync lines from the sync * service, or whether this work should be offloaded to the PowerSync core extension. @@ -223,6 +228,7 @@ export const DEFAULT_STREAMING_SYNC_OPTIONS = { export type RequiredPowerSyncConnectionOptions = Required; export const DEFAULT_STREAM_CONNECTION_OPTIONS: RequiredPowerSyncConnectionOptions = { + appMetadata: {}, connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET, clientImplementation: DEFAULT_SYNC_CLIENT_IMPLEMENTATION, fetchStrategy: FetchStrategy.Buffered, @@ -699,6 +705,7 @@ The next upload iteration will be delayed.`); include_checksum: true, raw_data: true, parameters: resolvedOptions.params, + app_metadata: resolvedOptions.appMetadata, client_id: clientId } }; @@ -1088,6 +1095,7 @@ The next upload iteration will be delayed.`); try { const options: any = { parameters: resolvedOptions.params, + app_metadata: resolvedOptions.appMetadata, active_streams: this.activeStreams, include_defaults: resolvedOptions.includeDefaultStreams }; diff --git a/packages/common/src/client/sync/stream/streaming-sync-types.ts b/packages/common/src/client/sync/stream/streaming-sync-types.ts index fdb73f360..8fa10520c 100644 --- a/packages/common/src/client/sync/stream/streaming-sync-types.ts +++ b/packages/common/src/client/sync/stream/streaming-sync-types.ts @@ -90,6 +90,11 @@ export interface StreamingSyncRequest { */ parameters?: Record; + /** + * Application metadata to be included in service logs. + */ + app_metadata?: Record; + client_id?: string; } From 478ef3583953a181d948ee120e9e5c4eebc11573 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 24 Nov 2025 16:22:08 +0200 Subject: [PATCH 2/7] add app version metadata --- .../src/components/providers/SystemProvider.tsx | 4 +++- demos/react-supabase-todolist/vite.config.mts | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx index 8014e05ae..6bcf627de 100644 --- a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx +++ b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx @@ -7,6 +7,8 @@ import { createBaseLogger, DifferentialWatchedQuery, LogLevel, PowerSyncDatabase import React, { Suspense } from 'react'; import { NavigationPanelContextProvider } from '../navigation/NavigationPanelContext'; +declare const APP_VERSION: string; + const SupabaseContext = React.createContext(null); export const useSupabase = () => React.useContext(SupabaseContext); @@ -70,7 +72,7 @@ export const SystemProvider = ({ children }: { children: React.ReactNode }) => { sessionStarted: () => { powerSync.connect(connector, { appMetadata: { - app_version: '1.0.0' + app_version: APP_VERSION } }); } diff --git a/demos/react-supabase-todolist/vite.config.mts b/demos/react-supabase-todolist/vite.config.mts index ab0b7d202..ea84c4153 100644 --- a/demos/react-supabase-todolist/vite.config.mts +++ b/demos/react-supabase-todolist/vite.config.mts @@ -1,9 +1,9 @@ -import wasm from 'vite-plugin-wasm'; -import topLevelAwait from 'vite-plugin-top-level-await'; import { fileURLToPath, URL } from 'url'; +import topLevelAwait from 'vite-plugin-top-level-await'; +import wasm from 'vite-plugin-wasm'; -import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; // https://vitejs.dev/config/ @@ -19,6 +19,9 @@ export default defineConfig({ resolve: { alias: [{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) }] }, + define: { + APP_VERSION: JSON.stringify(process.env.npm_package_version) + }, publicDir: '../public', envDir: '..', // Use this dir for env vars, not 'src'. optimizeDeps: { From 2cd3c43234409d019c899087f86278ab11bf5c92 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 24 Nov 2025 16:36:54 +0200 Subject: [PATCH 3/7] Add app metadata for Rust client. --- .../sync/stream/AbstractStreamingSyncImplementation.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index 1b2bc3467..b52e8cc00 100644 --- a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -954,7 +954,14 @@ The next upload iteration will be delayed.`); const syncOptions: SyncStreamOptions = { path: '/sync/stream', abortSignal: abortController.signal, - data: instr.request + data: { + ...instr.request, + // FIXME the Rust core does not currently pass metadata through + app_metadata: { + ...resolvedOptions.appMetadata, + ...instr.request.app_metadata + } + } }; if (resolvedOptions.connectionMethod == SyncStreamConnectionMethod.HTTP) { From 54c30c4ae6d6bc18d210698aa53be2c2901f2323 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 24 Nov 2025 16:37:09 +0200 Subject: [PATCH 4/7] Add metadata to example node.js demo --- demos/example-node/src/main.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/demos/example-node/src/main.ts b/demos/example-node/src/main.ts index f64361f0a..18dc27af7 100644 --- a/demos/example-node/src/main.ts +++ b/demos/example-node/src/main.ts @@ -1,5 +1,8 @@ import { once } from 'node:events'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; import repl_factory from 'node:repl'; +import { fileURLToPath } from 'node:url'; import { Worker } from 'node:worker_threads'; import { @@ -10,10 +13,9 @@ import { SyncStreamConnectionMethod } from '@powersync/node'; import { exit } from 'node:process'; +import { WorkerOpener } from 'node_modules/@powersync/node/src/db/options.js'; import { AppSchema, DemoConnector } from './powersync.js'; import { enableUncidiDiagnostics } from './UndiciDiagnostics.js'; -import { WorkerOpener } from 'node_modules/@powersync/node/src/db/options.js'; -import { LockContext } from 'node_modules/@powersync/node/dist/bundle.cjs'; const main = async () => { const baseLogger = createBaseLogger(); @@ -60,9 +62,19 @@ const main = async () => { }); console.log(await db.get('SELECT powersync_rs_version();')); + // Read package version for metadata + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + const packageJsonPath = join(__dirname, '..', 'package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const packageVersion = packageJson.version; + await db.connect(new DemoConnector(), { connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET, - clientImplementation: SyncClientImplementation.RUST + clientImplementation: SyncClientImplementation.RUST, + appMetadata: { + package_version: packageVersion + } }); // Example using a proxy agent for more control over the connection: // const proxyAgent = new (await import('undici')).ProxyAgent({ From 1063e09b8c93887999cb823ac424e4cf6e877acd Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 24 Nov 2025 16:47:43 +0200 Subject: [PATCH 5/7] Add changeset. Simplify node.js demo --- .changeset/quiet-owls-flash.md | 5 +++++ demos/example-node/src/main.ts | 12 +----------- 2 files changed, 6 insertions(+), 11 deletions(-) create mode 100644 .changeset/quiet-owls-flash.md diff --git a/.changeset/quiet-owls-flash.md b/.changeset/quiet-owls-flash.md new file mode 100644 index 000000000..779739c1f --- /dev/null +++ b/.changeset/quiet-owls-flash.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': minor +--- + +Added ability to specify appMetadata for sync/stream requests diff --git a/demos/example-node/src/main.ts b/demos/example-node/src/main.ts index 18dc27af7..0c5d5419f 100644 --- a/demos/example-node/src/main.ts +++ b/demos/example-node/src/main.ts @@ -1,8 +1,5 @@ import { once } from 'node:events'; -import { readFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; import repl_factory from 'node:repl'; -import { fileURLToPath } from 'node:url'; import { Worker } from 'node:worker_threads'; import { @@ -62,18 +59,11 @@ const main = async () => { }); console.log(await db.get('SELECT powersync_rs_version();')); - // Read package version for metadata - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const packageJsonPath = join(__dirname, '..', 'package.json'); - const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); - const packageVersion = packageJson.version; - await db.connect(new DemoConnector(), { connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET, clientImplementation: SyncClientImplementation.RUST, appMetadata: { - package_version: packageVersion + package_version: process.env.npm_package_version || 'unknown' } }); // Example using a proxy agent for more control over the connection: From 7b68aba23a68e28d11dd93bc73afc60b1fc80af9 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 25 Nov 2025 11:24:45 +0200 Subject: [PATCH 6/7] add runtime validation of appMetadata --- demos/example-node/src/main.ts | 3 +-- .../sync/stream/AbstractStreamingSyncImplementation.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/demos/example-node/src/main.ts b/demos/example-node/src/main.ts index 0c5d5419f..bfef05d51 100644 --- a/demos/example-node/src/main.ts +++ b/demos/example-node/src/main.ts @@ -58,12 +58,11 @@ const main = async () => { logger }); console.log(await db.get('SELECT powersync_rs_version();')); - await db.connect(new DemoConnector(), { connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET, clientImplementation: SyncClientImplementation.RUST, appMetadata: { - package_version: process.env.npm_package_version || 'unknown' + app_version: process.env.npm_package_version || 'unknown' } }); // Example using a proxy agent for more control over the connection: diff --git a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index b52e8cc00..6a2b73da7 100644 --- a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -664,6 +664,16 @@ The next upload iteration will be delayed.`); ...DEFAULT_STREAM_CONNECTION_OPTIONS, ...(options ?? {}) }; + + // Validate app metadata + const invalidMetadata = Object.entries(resolvedOptions.appMetadata).filter( + ([_, value]) => typeof value != 'string' + ); + if (invalidMetadata.length > 0) { + throw new Error( + `Invalid appMetadata provided. Only string values are allowed. Invalid values: ${invalidMetadata.map(([key, value]) => `${key}: ${value}`).join(', ')}` + ); + } const clientImplementation = resolvedOptions.clientImplementation; this.updateSyncStatus({ clientImplementation }); From 8bf0b1477555b0fecc1e08e7229063653b48deca Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 25 Nov 2025 11:26:54 +0200 Subject: [PATCH 7/7] update changeset --- .changeset/quiet-owls-flash.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.changeset/quiet-owls-flash.md b/.changeset/quiet-owls-flash.md index 779739c1f..51c3f8511 100644 --- a/.changeset/quiet-owls-flash.md +++ b/.changeset/quiet-owls-flash.md @@ -1,5 +1,18 @@ --- +'@powersync/react-native': minor '@powersync/common': minor +'@powersync/node': minor +'@powersync/web': minor +'@powersync/capacitor': minor --- -Added ability to specify appMetadata for sync/stream requests +Added ability to specify `appMetadata` for sync/stream requests + +```javascript +powerSync.connect(connector, { + // This will be included in PowerSync service logs + appMetadata: { + app_version: MY_APP_VERSION + } +}); +```