From 83c7350fbe0665d64c66d65360b7e02fa9c00c7d Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 27 Oct 2026 10:19:45 +0200 Subject: [PATCH 1/8] Add checkpoint requests usage examples for all SDKs --- client-sdks/checkpoint-requests.mdx | 2 +- client-sdks/usage-examples.mdx | 191 +++++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 4 deletions(-) diff --git a/client-sdks/checkpoint-requests.mdx b/client-sdks/checkpoint-requests.mdx index 34986ff5..96a03b59 100644 --- a/client-sdks/checkpoint-requests.mdx +++ b/client-sdks/checkpoint-requests.mdx @@ -192,7 +192,7 @@ Creating the request has no timeout of its own, so it can stay suspended while t Aborting a `waitForSync` call or configuring a timeout does not remove the checkpoint. It only limits how long you wait for the checkpoint to sync and apply locally. -The [React Native](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/app/views/todos/lists.tsx), [React](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/app/views/layout.tsx), [Flutter](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist-drift/lib/stores/refresh.dart), [Kotlin](https://github.com/powersync-ja/powersync-kotlin/blob/main/demos/supabase-todolist/shared/src/commonMain/kotlin/com/powersync/demos/components/RefreshIcon.kt), and [Swift](https://github.com/powersync-ja/powersync-swift/blob/main/Demos/PowerSyncExample/PowerSyncExample/PowerSync/SystemManager.swift) demo apps use this pattern for a refresh action. +The [React Native](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/app/views/todos/lists.tsx), [React](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/app/views/layout.tsx), [Flutter](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist-drift/lib/stores/refresh.dart), [Kotlin](https://github.com/powersync-ja/powersync-kotlin/blob/main/demos/supabase-todolist/shared/src/commonMain/kotlin/com/powersync/demos/components/RefreshIcon.kt), [Swift](https://github.com/powersync-ja/powersync-swift/blob/main/Demos/PowerSyncExample/PowerSyncExample/PowerSync/SystemManager.swift), [.NET](https://github.com/powersync-ja/powersync-dotnet/blob/main/demos/MAUITodo/Data/PowerSyncData.cs), and [Rust](https://github.com/powersync-ja/powersync-native/blob/main/examples/egui_todolist/src/ui.rs) demo apps use this pattern for a refresh action. ## Handling Wait Failures diff --git a/client-sdks/usage-examples.mdx b/client-sdks/usage-examples.mdx index badded8f..302e5ec4 100644 --- a/client-sdks/usage-examples.mdx +++ b/client-sdks/usage-examples.mdx @@ -1171,9 +1171,194 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call - - `hasSynced` and `waitForFirstSync()` report whether a complete sync has finished at least once, possibly in an earlier app session. To wait until local data is up to date at a later point, for example after the app returns to the foreground, use [checkpoint requests](/client-sdks/checkpoint-requests). - +## Wait for Local Data to Catch Up + +`hasSynced` and `waitForFirstSync()` report whether a complete sync has finished at least once, possibly in an earlier app session. To wait until the local database has caught up with the server as of now, use [Sync Catch-Up](/client-sdks/checkpoint-requests): request a checkpoint, then wait for it to sync. These examples use it for a refresh action, so the refresh indicator stops only once the device has caught up. + +Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or later, and you must enable them with the `checkpointMode` option when calling `connect()`. See [Prerequisites](/client-sdks/checkpoint-requests#prerequisites). + + + + ```dart + RefreshIndicator( + // The indicator stays visible until this future completes + onRefresh: () async { + try { + final checkpoint = await db.requestCheckpoint(); + await checkpoint.waitForSync(); + } catch (e) { + // Show the error to the user + } + }, + child: ListView(children: listItems), + ) + ``` + + See the [Flutter demo app](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist-drift/lib/stores/refresh.dart) for a complete example. + + + ```tsx + import { useState } from 'react'; + import { RefreshControl, ScrollView } from 'react-native'; + import { usePowerSync } from '@powersync/react-native'; + + export function ListsScreen() { + const powerSync = usePowerSync(); + const [refreshing, setRefreshing] = useState(false); + + const onRefresh = async () => { + setRefreshing(true); + try { + const checkpoint = await powerSync.requestCheckpoint(); + await checkpoint.waitForSync(); + } catch (e) { + // Show the error to the user + } finally { + setRefreshing(false); + } + }; + + return ( + }> + {/* List items */} + + ); + } + ``` + + See the [React Native demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/app/views/todos/lists.tsx) for a complete example. + + + ```tsx + import { useState } from 'react'; + import { usePowerSync } from '@powersync/react'; + + export function RefreshButton() { + const powerSync = usePowerSync(); + const [refreshing, setRefreshing] = useState(false); + + async function refresh() { + setRefreshing(true); + try { + const checkpoint = await powerSync.requestCheckpoint(); + await checkpoint.waitForSync(); + } catch (e) { + // Show the error to the user + } finally { + setRefreshing(false); + } + } + + return ( + + ); + } + ``` + + See the [React demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/app/views/layout.tsx) for a complete example. + + + The Capacitor SDK uses the same API as the JavaScript Web SDK, so the JavaScript Web example also applies. + + + ```js + const checkpoint = await db.requestCheckpoint(); + await checkpoint.waitForSync(); + + // This query now reflects the server state from when you created the request + const lists = await db.getAll('SELECT * FROM lists'); + ``` + + + ```kotlin + @OptIn(ExperimentalCheckpointRequestsApi::class) + @Composable + fun RefreshButton(database: PowerSyncDatabase) { + val scope = rememberCoroutineScope() + var isRefreshing by remember { mutableStateOf(false) } + + IconButton( + onClick = { + scope.launch { + isRefreshing = true + try { + val checkpoint = database.requestCheckpoint() + checkpoint.waitForSync() + } catch (e: Exception) { + if (e is CancellationException) throw e + // Show the error to the user + } finally { + isRefreshing = false + } + } + }, + enabled = !isRefreshing, + ) { + Icon(Icons.Default.Refresh, contentDescription = "Refresh") + } + } + ``` + + See the [Kotlin demo app](https://github.com/powersync-ja/powersync-kotlin/blob/main/demos/supabase-todolist/shared/src/commonMain/kotlin/com/powersync/demos/components/RefreshIcon.kt) for a complete example. + + + ```swift + List(lists) { list in + Text(list.name) + } + // The indicator stays visible until this closure returns + .refreshable { + do { + let checkpoint = try await db.requestCheckpoint() + try await checkpoint.waitForSync() + } catch { + // Show the error to the user + } + } + ``` + + See the [Swift demo app](https://github.com/powersync-ja/powersync-swift/blob/main/Demos/PowerSyncExample/PowerSyncExample/Components/ListView.swift) for a complete example. + + + ```cs + // In XAML: + private async void OnRefreshing(object sender, EventArgs e) + { + try + { + var checkpoint = await db.RequestCheckpoint(); + await checkpoint.WaitForSync(); + } + catch (Exception ex) + { + await DisplayAlert("Refresh failed", ex.Message, "OK"); + } + finally + { + ListsRefreshView.IsRefreshing = false; + } + } + ``` + + See the [.NET MAUI demo app](https://github.com/powersync-ja/powersync-dotnet/blob/main/demos/MAUITodo/Views/ListsPage.xaml.cs) for a complete example. + + + ```rust + use powersync::{CheckpointError, PowerSyncDatabase}; + + async fn refresh(db: &PowerSyncDatabase) -> Result<(), CheckpointError> { + let checkpoint = db.request_checkpoint().await?; + checkpoint.wait_for_sync().await + } + ``` + + See the [egui demo app](https://github.com/powersync-ja/powersync-native/blob/main/examples/egui_todolist/src/ui.rs) for a complete example. + + + +If the device is offline, `requestCheckpoint()` waits until it can reach the PowerSync Service again, so the refresh indicator stays visible until then. Consider offering the refresh action only while the database is connected. For timeouts and error types, see [Handling Wait Failures](/client-sdks/checkpoint-requests#handling-wait-failures). ## Report Sync Download Progress From 2fd8eb7261bb0ef5dd527ac59bea7366dc25fd13 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 27 Oct 2026 10:23:35 +0200 Subject: [PATCH 2/8] Reorder tabs consistently --- .claude/CLAUDE.md | 2 +- client-sdks/usage-examples.mdx | 728 ++++++++++++++++----------------- 2 files changed, 365 insertions(+), 365 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 81d892c4..64114b3c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -118,7 +118,7 @@ Use the Mintlify skill for syntax. These project conventions take precedence ove | `` | Neutral background context | | `` | Success confirmations | -In platform lists, tabs, code groups, accordions, and cards, order SDKs as follows: JavaScript variants (React Native, Web, Node.js), Dart, Kotlin, Swift, .NET, Rust. Put platform-specific extras such as Capacitor and Tauri after the JavaScript variants they belong to. +In platform lists, tabs, code groups, accordions, and cards, order SDKs as follows: JavaScript variants (Web, React Native, Node.js), Dart, Kotlin, Swift, .NET, Rust. Put platform-specific extras such as Capacitor and Tauri after the JavaScript variants they belong to. Wrap images in a `Frame` with a caption and descriptive alt text: diff --git a/client-sdks/usage-examples.mdx b/client-sdks/usage-examples.mdx index 302e5ec4..cb86b64c 100644 --- a/client-sdks/usage-examples.mdx +++ b/client-sdks/usage-examples.mdx @@ -14,47 +14,27 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ## Using Transactions to Group Changes - - Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). - - The [writeTransaction(callback)](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/writeTransaction.html) method combines all writes into a single transaction, only committing to persistent storage once. - - ```dart - deleteList(SqliteDatabase db, String id) async { - await db.writeTransaction((tx) async { - // Delete the main list - await tx.execute('DELETE FROM lists WHERE id = ?', [id]); - // Delete any children of the list - await tx.execute('DELETE FROM todos WHERE list_id = ?', [id]); - }); - } - ``` - - Also see [readTransaction(callback)](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/readTransaction.html) - - + Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). - [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if [`tx.rollback()`](https://github.com/powersync-ja/powersync-js/blob/ed5bb49b5a1dc579050304fab847feb8d09b45c7/packages/common/src/db/DBAdapter.ts#L53) has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. + [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if `tx.rollback()` has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. ```js // ListsWidget.jsx - import {Alert, Button, FlatList, Text, View} from 'react-native'; + import React, { useState } from 'react'; export const ListsWidget = () => { - // Populate lists with one of methods listed above - const [lists, setLists] = React.useState([]); + const [lists, setLists] = useState([]); return ( - - ({key: list.id, ...list}))} - renderItem={({item}) => ( - {item.name} - + + ))} + + + + ); + }; ``` Also see [PowerSyncDatabase.readTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction). - + Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). - [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if `tx.rollback()` has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. + [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if [`tx.rollback()`](https://github.com/powersync-ja/powersync-js/blob/ed5bb49b5a1dc579050304fab847feb8d09b45c7/packages/common/src/db/DBAdapter.ts#L53) has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. ```js // ListsWidget.jsx - import React, { useState } from 'react'; + import {Alert, Button, FlatList, Text, View} from 'react-native'; export const ListsWidget = () => { - const [lists, setLists] = useState([]); + // Populate lists with one of methods listed above + const [lists, setLists] = React.useState([]); return ( -
-
    - {lists.map((list) => ( -
  • - {list.name} - -
  • - ))} -
- -
- ); - }; + }} + /> + + ) + } ``` Also see [PowerSyncDatabase.readTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction). @@ -163,6 +145,24 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call Example not yet available. + + Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). + + The [writeTransaction(callback)](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/writeTransaction.html) method combines all writes into a single transaction, only committing to persistent storage once. + + ```dart + deleteList(SqliteDatabase db, String id) async { + await db.writeTransaction((tx) async { + // Delete the main list + await tx.execute('DELETE FROM lists WHERE id = ?', [id]); + // Delete any children of the list + await tx.execute('DELETE FROM todos WHERE list_id = ?', [id]); + }); + } + ``` + + Also see [readTransaction(callback)](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/readTransaction.html) + Use `writeTransaction` to group statements that can write to the database. @@ -240,12 +240,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ## Listen for Changes in Data - - Use [watch](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/watch.html) to watch for changes to the dependent tables of any SQL query. - - - - + Use [PowerSyncDatabase.watch](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) to watch for changes in source tables. @@ -259,7 +254,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call For advanced watch query features like incremental updates and differential results, see [Live Queries / Watch Queries](/client-sdks/watch-queries). - + Use [PowerSyncDatabase.watch](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) to watch for changes in source tables. @@ -301,6 +296,11 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call For advanced watch query features like incremental updates and differential results, see [Live Queries / Watch Queries](/client-sdks/watch-queries). + + Use [watch](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/watch.html) to watch for changes to the dependent tables of any SQL query. + + + Use the `watch` method to watch for changes to the dependent tables of any SQL query. @@ -326,23 +326,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ## Insert, Update, and Delete Data in the Local Database - - Use [execute](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/execute.html) to run INSERT, UPDATE or DELETE queries. - - ```dart - FloatingActionButton( - onPressed: () async { - await db.execute( - 'INSERT INTO customers(id, name, email) VALUES(uuid(), ?, ?)', - ['Fred', 'fred@example.org'], - ); - }, - tooltip: '+', - child: const Icon(Icons.add), - ); - ``` - - + Use [PowerSyncDatabase.execute](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#execute) to run INSERT, UPDATE or DELETE queries. ```js @@ -361,7 +345,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ); ``` - + Use [PowerSyncDatabase.execute](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#execute) to run INSERT, UPDATE or DELETE queries. ```js @@ -386,6 +370,22 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call Example not yet available. + + Use [execute](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/execute.html) to run INSERT, UPDATE or DELETE queries. + + ```dart + FloatingActionButton( + onPressed: () async { + await db.execute( + 'INSERT INTO customers(id, name, email) VALUES(uuid(), ?, ?)', + ['Fred', 'fred@example.org'], + ); + }, + tooltip: '+', + child: const Icon(Icons.add), + ); + ``` + Use `execute` to run `INSERT`, `UPDATE` or `DELETE` queries. @@ -447,31 +447,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ## Send Changes in Local Data to Your Backend Service - - Override [uploadData](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncBackendConnector/uploadData.html) to send local updates to your backend service. - - ```dart - @override - Future uploadData(PowerSyncDatabase database) async { - final batch = await database.getCrudBatch(); - if (batch == null) return; - for (var op in batch.crud) { - switch (op.op) { - case UpdateType.put: - // Send the data to your backend service - // Replace `_myApi` with your own API client or service - await _myApi.put(op.table, op.opData!); - break; - default: - // TODO: implement the other operations (patch, delete) - break; - } - } - await batch.complete(); - } - ``` - - + Override [uploadData](https://github.com/powersync-ja/powersync-js/blob/ed5bb49b5a1dc579050304fab847feb8d09b45c7/packages/common/src/client/connection/PowerSyncBackendConnector.ts#L24) to send local updates to your backend service. ```js @@ -497,7 +473,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call } ``` - + Override [uploadData](https://github.com/powersync-ja/powersync-js/blob/ed5bb49b5a1dc579050304fab847feb8d09b45c7/packages/common/src/client/connection/PowerSyncBackendConnector.ts#L24) to send local updates to your backend service. ```js @@ -575,6 +551,30 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call } ``` + + Override [uploadData](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncBackendConnector/uploadData.html) to send local updates to your backend service. + + ```dart + @override + Future uploadData(PowerSyncDatabase database) async { + final batch = await database.getCrudBatch(); + if (batch == null) return; + for (var op in batch.crud) { + switch (op.op) { + case UpdateType.put: + // Send the data to your backend service + // Replace `_myApi` with your own API client or service + await _myApi.put(op.table, op.opData!); + break; + default: + // TODO: implement the other operations (patch, delete) + break; + } + } + await batch.complete(); + } + ``` + Override `uploadData` to send local updates to your backend service. If you are using Supabase, see [SupabaseConnector.kt](https://github.com/powersync-ja/powersync-kotlin/blob/main/integrations/supabase/src/commonMain/kotlin/com/powersync/connector/supabase/SupabaseConnector.kt) for a complete implementation. @@ -723,74 +723,8 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ## Accessing PowerSync Connection Status Information - - Use [SyncStatus](https://pub.dev/documentation/powersync/latest/powersync/SyncStatus-class.html) and register an event listener with [statusStream](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/statusStream.html) to listen for status changes to your PowerSync instance. - - ```dart - class _StatusAppBarState extends State { - late SyncStatus _connectionState; - StreamSubscription? _syncStatusSubscription; - - @override - void initState() { - super.initState(); - _connectionState = db.currentStatus; - _syncStatusSubscription = db.statusStream.listen((event) { - setState(() { - _connectionState = db.currentStatus; - }); - }); - } - - @override - void dispose() { - super.dispose(); - _syncStatusSubscription?.cancel(); - } - - @override - Widget build(BuildContext context) { - final statusIcon = _getStatusIcon(_connectionState); - - return AppBar( - title: Text(widget.title), - actions: [ - ... - statusIcon - ], - ); - } - } - - Widget _getStatusIcon(SyncStatus status) { - if (status.anyError != null) { - // The error message is verbose, could be replaced with something - // more user-friendly - if (!status.connected) { - return _makeIcon(status.anyError!.toString(), Icons.cloud_off); - } else { - return _makeIcon(status.anyError!.toString(), Icons.sync_problem); - } - } else if (status.connecting) { - return _makeIcon('Connecting', Icons.cloud_sync_outlined); - } else if (!status.connected) { - return _makeIcon('Not connected', Icons.cloud_off); - } else if (status.uploading && status.downloading) { - // The status changes often between downloading, uploading and both, - // so we use the same icon for all three - return _makeIcon('Uploading and downloading', Icons.cloud_sync_outlined); - } else if (status.uploading) { - return _makeIcon('Uploading', Icons.cloud_sync_outlined); - } else if (status.downloading) { - return _makeIcon('Downloading', Icons.cloud_sync_outlined); - } else { - return _makeIcon('Connected', Icons.cloud_queue); - } - } - ``` - - - Use [PowerSyncDatabase.connected](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#connected) and register an event listener with [PowerSyncDatabase.registerListener](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#registerlistener) to listen for status changes to your PowerSync instance. + + Use [PowerSyncDatabase.connected](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#connected) and register an event listener with [PowerSyncDatabase.registerListener](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#registerlistener) to listen for status changes to your PowerSync instance. ```js // Example of using connected status to show online or offline @@ -825,7 +759,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call />; ``` - + Use [PowerSyncDatabase.connected](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#connected) and register an event listener with [PowerSyncDatabase.registerListener](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#registerlistener) to listen for status changes to your PowerSync instance. ```js @@ -867,6 +801,72 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call Example not yet available. + + Use [SyncStatus](https://pub.dev/documentation/powersync/latest/powersync/SyncStatus-class.html) and register an event listener with [statusStream](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/statusStream.html) to listen for status changes to your PowerSync instance. + + ```dart + class _StatusAppBarState extends State { + late SyncStatus _connectionState; + StreamSubscription? _syncStatusSubscription; + + @override + void initState() { + super.initState(); + _connectionState = db.currentStatus; + _syncStatusSubscription = db.statusStream.listen((event) { + setState(() { + _connectionState = db.currentStatus; + }); + }); + } + + @override + void dispose() { + super.dispose(); + _syncStatusSubscription?.cancel(); + } + + @override + Widget build(BuildContext context) { + final statusIcon = _getStatusIcon(_connectionState); + + return AppBar( + title: Text(widget.title), + actions: [ + ... + statusIcon + ], + ); + } + } + + Widget _getStatusIcon(SyncStatus status) { + if (status.anyError != null) { + // The error message is verbose, could be replaced with something + // more user-friendly + if (!status.connected) { + return _makeIcon(status.anyError!.toString(), Icons.cloud_off); + } else { + return _makeIcon(status.anyError!.toString(), Icons.sync_problem); + } + } else if (status.connecting) { + return _makeIcon('Connecting', Icons.cloud_sync_outlined); + } else if (!status.connected) { + return _makeIcon('Not connected', Icons.cloud_off); + } else if (status.uploading && status.downloading) { + // The status changes often between downloading, uploading and both, + // so we use the same icon for all three + return _makeIcon('Uploading and downloading', Icons.cloud_sync_outlined); + } else if (status.uploading) { + return _makeIcon('Uploading', Icons.cloud_sync_outlined); + } else if (status.downloading) { + return _makeIcon('Downloading', Icons.cloud_sync_outlined); + } else { + return _makeIcon('Connected', Icons.cloud_queue); + } + } + ``` + ```kotlin // Intialize the DB @@ -985,48 +985,7 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call ## Wait for the Initial Sync to Complete - - Use the [hasSynced](https://pub.dev/documentation/powersync/latest/powersync/SyncStatus/hasSynced.html) property and register a listener to indicate to the user whether the initial sync is in progress. - - ```dart - // Example of using hasSynced to show whether the first sync has completed - - /// Global reference to the database - final PowerSyncDatabase db; - - bool hasSynced = false; - StreamSubscription? _syncStatusSubscription; - - // Use the exposed statusStream - Stream watchSyncStatus() { - return db.statusStream; - } - - @override - void initState() { - super.initState(); - _syncStatusSubscription = watchSyncStatus.listen((status) { - setState(() { - hasSynced = status.hasSynced ?? false; - }); - }); - } - - @override - Widget build(BuildContext context) { - return Text(hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'); - } - - // Don't forget to dispose of stream subscriptions when the view is disposed - void dispose() { - super.dispose(); - _syncStatusSubscription?.cancel(); - } - ``` - - For async use cases, see the [waitForFirstSync](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/waitForFirstSync.html) method which returns a promise that resolves once the first full sync has completed. - - + Use the [hasSynced](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus#hassynced) property and register an event listener with [PowerSyncDatabase.registerListener](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#registerlistener) to indicate to the user whether the initial sync is in progress. ```js @@ -1044,12 +1003,12 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call }); }, [powerSync]); - return {hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'}; + return
{hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'}
; ``` - For async use cases, see [PowerSyncDatabase.waitForFirstSync](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#waitforfirstsync), which returns a promise that resolves once the first full sync has completed (it queries the internal SQL [ps_buckets](/architecture/client-architecture) table to determine if data has been synced). + For async use cases, see [PowerSyncDatabase.waitForFirstSync()](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#waitforfirstsync), which returns a promise that resolves once the first full sync has completed (it queries the internal SQL [ps_buckets](/architecture/client-architecture) table to determine if data has been synced).
- + Use the [hasSynced](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus#hassynced) property and register an event listener with [PowerSyncDatabase.registerListener](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#registerlistener) to indicate to the user whether the initial sync is in progress. ```js @@ -1067,10 +1026,10 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call }); }, [powerSync]); - return
{hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'}
; + return {hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'}; ``` - For async use cases, see [PowerSyncDatabase.waitForFirstSync()](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#waitforfirstsync), which returns a promise that resolves once the first full sync has completed (it queries the internal SQL [ps_buckets](/architecture/client-architecture) table to determine if data has been synced). + For async use cases, see [PowerSyncDatabase.waitForFirstSync](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#waitforfirstsync), which returns a promise that resolves once the first full sync has completed (it queries the internal SQL [ps_buckets](/architecture/client-architecture) table to determine if data has been synced).
Example not yet available. @@ -1078,6 +1037,47 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call Example not yet available. + + Use the [hasSynced](https://pub.dev/documentation/powersync/latest/powersync/SyncStatus/hasSynced.html) property and register a listener to indicate to the user whether the initial sync is in progress. + + ```dart + // Example of using hasSynced to show whether the first sync has completed + + /// Global reference to the database + final PowerSyncDatabase db; + + bool hasSynced = false; + StreamSubscription? _syncStatusSubscription; + + // Use the exposed statusStream + Stream watchSyncStatus() { + return db.statusStream; + } + + @override + void initState() { + super.initState(); + _syncStatusSubscription = watchSyncStatus.listen((status) { + setState(() { + hasSynced = status.hasSynced ?? false; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Text(hasSynced ? 'Initial sync completed!' : 'Busy with initial sync...'); + } + + // Don't forget to dispose of stream subscriptions when the view is disposed + void dispose() { + super.dispose(); + _syncStatusSubscription?.cancel(); + } + ``` + + For async use cases, see the [waitForFirstSync](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/waitForFirstSync.html) method which returns a promise that resolves once the first full sync has completed. + Use the `hasSynced` property and register a listener to indicate to the user whether the initial sync is in progress. @@ -1178,35 +1178,16 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or later, and you must enable them with the `checkpointMode` option when calling `connect()`. See [Prerequisites](/client-sdks/checkpoint-requests#prerequisites). - - ```dart - RefreshIndicator( - // The indicator stays visible until this future completes - onRefresh: () async { - try { - final checkpoint = await db.requestCheckpoint(); - await checkpoint.waitForSync(); - } catch (e) { - // Show the error to the user - } - }, - child: ListView(children: listItems), - ) - ``` - - See the [Flutter demo app](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist-drift/lib/stores/refresh.dart) for a complete example. - - + ```tsx import { useState } from 'react'; - import { RefreshControl, ScrollView } from 'react-native'; - import { usePowerSync } from '@powersync/react-native'; + import { usePowerSync } from '@powersync/react'; - export function ListsScreen() { + export function RefreshButton() { const powerSync = usePowerSync(); const [refreshing, setRefreshing] = useState(false); - const onRefresh = async () => { + async function refresh() { setRefreshing(true); try { const checkpoint = await powerSync.requestCheckpoint(); @@ -1216,28 +1197,29 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late } finally { setRefreshing(false); } - }; + } return ( - }> - {/* List items */} - + ); } ``` - See the [React Native demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/app/views/todos/lists.tsx) for a complete example. + See the [React demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/app/views/layout.tsx) for a complete example. - + ```tsx import { useState } from 'react'; - import { usePowerSync } from '@powersync/react'; + import { RefreshControl, ScrollView } from 'react-native'; + import { usePowerSync } from '@powersync/react-native'; - export function RefreshButton() { + export function ListsScreen() { const powerSync = usePowerSync(); const [refreshing, setRefreshing] = useState(false); - async function refresh() { + const onRefresh = async () => { setRefreshing(true); try { const checkpoint = await powerSync.requestCheckpoint(); @@ -1247,17 +1229,17 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late } finally { setRefreshing(false); } - } + }; return ( - + }> + {/* List items */} + ); } ``` - See the [React demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/app/views/layout.tsx) for a complete example. + See the [React Native demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/app/views/todos/lists.tsx) for a complete example. The Capacitor SDK uses the same API as the JavaScript Web SDK, so the JavaScript Web example also applies. @@ -1271,6 +1253,24 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late const lists = await db.getAll('SELECT * FROM lists'); ``` + + ```dart + RefreshIndicator( + // The indicator stays visible until this future completes + onRefresh: () async { + try { + final checkpoint = await db.requestCheckpoint(); + await checkpoint.waitForSync(); + } catch (e) { + // Show the error to the user + } + }, + child: ListView(children: listItems), + ) + ``` + + See the [Flutter demo app](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist-drift/lib/stores/refresh.dart) for a complete example. + ```kotlin @OptIn(ExperimentalCheckpointRequestsApi::class) @@ -1363,65 +1363,46 @@ If the device is offline, `requestCheckpoint()` waits until it can reach the Pow ## Report Sync Download Progress - - You can show users a progress bar when data downloads using the `downloadProgress` property from the [SyncStatus](https://pub.dev/documentation/powersync/latest/powersync/SyncStatus/downloadProgress.html) class. `downloadProgress.downloadedFraction` gives you a value from 0.0 to 1.0 representing the total sync progress. This is especially useful for long-running initial syncs. + + You can show users a progress bar when data downloads using the `downloadProgress` property from the [SyncStatus](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) class. This is especially useful for long-running initial syncs. `downloadProgress.downloadedFraction` gives you a value from 0.0 to 1.0 representing the total sync progress. - As an example, this widget renders a progress bar when a download is active: + Example (React, using [MUI](https://mui.com) components): - ```dart - import 'package:flutter/material.dart'; - import 'package:powersync/powersync.dart' hide Column; - - class SyncProgressBar extends StatelessWidget { - final PowerSyncDatabase db; - - /// When set, show progress towards the [BucketPriority] instead of towards - /// the full sync. - final BucketPriority? priority; - - const SyncProgressBar({ - super.key, - required this.db, - this.priority, - }); + ```jsx + import { Box, LinearProgress, Stack, Typography } from '@mui/material'; + import { useStatus } from '@powersync/react'; + import { FC, ReactNode } from 'react'; - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: db.statusStream, - initialData: db.currentStatus, - builder: (context, snapshot) { - final status = snapshot.requireData; - final progress = switch (priority) { - null => status.downloadProgress, - var priority? => status.downloadProgress?.untilPriority(priority), - }; + export const SyncProgressBar: FC<{ priority?: number }> = ({ priority }) => { + const status = useStatus(); + const progressUntilNextSync = status.downloadProgress; + const progress = priority == null ? progressUntilNextSync : progressUntilNextSync?.untilPriority(priority); - if (progress != null) { - return Center( - child: Column( - children: [ - const Text('Busy with sync...'), - LinearProgressIndicator(value: progress?.downloadedFraction), - Text( - '${progress.downloadedOperations} out of ${progress.totalOperations}') - ], - ), - ); - } else { - return const SizedBox.shrink(); - } - }, - ); + if (progress == null) { + return <>; } - } + return ( + + + + {progress.downloadedOperations == progress.totalOperations ? ( + Applying server-side changes + ) : ( + + Downloaded {progress.downloadedOperations} out of {progress.totalOperations}. + + )} + + + ); + }; ``` Also see: - - [SyncDownloadProgress API](https://pub.dev/documentation/powersync/latest/powersync/SyncDownloadProgress-extension-type.html) - - [Demo component](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist/lib/widgets/guard_by_sync.dart) + - [SyncStatus API](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) + - [Demo component](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/components/widgets/GuardBySync.tsx) You can show users a progress bar when data downloads using the `downloadProgress` property from the [SyncStatus](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) class. This is especially useful for long-running initial syncs. `downloadProgress.downloadedFraction` gives you a value from 0.0 to 1.0 representing the total sync progress. @@ -1463,52 +1444,71 @@ If the device is offline, `requestCheckpoint()` waits until it can reach the Pow - [SyncStatus API](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) - [Demo component](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/library/widgets/GuardBySync.tsx) - - You can show users a progress bar when data downloads using the `downloadProgress` property from the [SyncStatus](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) class. This is especially useful for long-running initial syncs. `downloadProgress.downloadedFraction` gives you a value from 0.0 to 1.0 representing the total sync progress. + + Example not yet available. + + + Example not yet available. + + + You can show users a progress bar when data downloads using the `downloadProgress` property from the [SyncStatus](https://pub.dev/documentation/powersync/latest/powersync/SyncStatus/downloadProgress.html) class. `downloadProgress.downloadedFraction` gives you a value from 0.0 to 1.0 representing the total sync progress. This is especially useful for long-running initial syncs. - Example (React, using [MUI](https://mui.com) components): + As an example, this widget renders a progress bar when a download is active: - ```jsx - import { Box, LinearProgress, Stack, Typography } from '@mui/material'; - import { useStatus } from '@powersync/react'; - import { FC, ReactNode } from 'react'; + ```dart + import 'package:flutter/material.dart'; + import 'package:powersync/powersync.dart' hide Column; - export const SyncProgressBar: FC<{ priority?: number }> = ({ priority }) => { - const status = useStatus(); - const progressUntilNextSync = status.downloadProgress; - const progress = priority == null ? progressUntilNextSync : progressUntilNextSync?.untilPriority(priority); + class SyncProgressBar extends StatelessWidget { + final PowerSyncDatabase db; - if (progress == null) { - return <>; + /// When set, show progress towards the [BucketPriority] instead of towards + /// the full sync. + final BucketPriority? priority; + + const SyncProgressBar({ + super.key, + required this.db, + this.priority, + }); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: db.statusStream, + initialData: db.currentStatus, + builder: (context, snapshot) { + final status = snapshot.requireData; + final progress = switch (priority) { + null => status.downloadProgress, + var priority? => status.downloadProgress?.untilPriority(priority), + }; + + if (progress != null) { + return Center( + child: Column( + children: [ + const Text('Busy with sync...'), + LinearProgressIndicator(value: progress?.downloadedFraction), + Text( + '${progress.downloadedOperations} out of ${progress.totalOperations}') + ], + ), + ); + } else { + return const SizedBox.shrink(); + } + }, + ); } + } - return ( - - - - {progress.downloadedOperations == progress.totalOperations ? ( - Applying server-side changes - ) : ( - - Downloaded {progress.downloadedOperations} out of {progress.totalOperations}. - - )} - - - ); - }; ``` Also see: - - [SyncStatus API](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) - - [Demo component](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-supabase-todolist/src/components/widgets/GuardBySync.tsx) - - - Example not yet available. - - - Example not yet available. + - [SyncDownloadProgress API](https://pub.dev/documentation/powersync/latest/powersync/SyncDownloadProgress-extension-type.html) + - [Demo component](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist/lib/widgets/guard_by_sync.dart) You can show users a progress bar when data downloads using the `syncStatus.downloadProgress` property. This is especially useful for long-running initial syncs. `downloadProgress.downloadedFraction` gives a value from 0.0 to 1.0 representing the total sync progress. From 3e672292db70004c6f75dcbad2d8dd35899425c8 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 27 Oct 2026 10:44:25 +0200 Subject: [PATCH 3/8] Codex fixes --- .claude/CLAUDE.md | 2 +- client-sdks/usage-examples.mdx | 521 ++++-------------- .../basic-watch-query-javascript-async.mdx | 2 +- .../basic-watch-query-javascript-callback.mdx | 4 +- 4 files changed, 123 insertions(+), 406 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 64114b3c..0c91990a 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -130,7 +130,7 @@ Wrap images in a `Frame` with a caption and descriptive alt text: ### Code Examples -- Provide complete, runnable, tested examples with realistic data and expected output where useful. Never include real credentials or secrets. +- Keep usage examples focused on the fundamentals. They may omit surrounding app setup and production error handling, but the demonstrated APIs and behavior must be correct. Use realistic data and expected output where useful. Test examples where practical. Never include real credentials or secrets. - Give fenced code blocks a language tag. Use filenames only for self-hosted examples. - Do not use placeholder names such as `foo`, `bar`, or `example.com` in runnable examples. - In SQL, use table names directly unless an alias is necessary for a self-join or ambiguous column. Prefer `SELECT tasks.* FROM tasks JOIN projects ON ...` to aliases such as `t`. diff --git a/client-sdks/usage-examples.mdx b/client-sdks/usage-examples.mdx index cb86b64c..a16395d9 100644 --- a/client-sdks/usage-examples.mdx +++ b/client-sdks/usage-examples.mdx @@ -20,59 +20,12 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if `tx.rollback()` has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. ```js - // ListsWidget.jsx - import React, { useState } from 'react'; - - export const ListsWidget = () => { - const [lists, setLists] = useState([]); - - return ( -
-
    - {lists.map((list) => ( -
  • - {list.name} - -
  • - ))} -
- -
- ); - }; + async function deleteList(db, listId) { + await db.writeTransaction(async (tx) => { + await tx.execute('DELETE FROM lists WHERE id = ?', [listId]); + await tx.execute('DELETE FROM todos WHERE list_id = ?', [listId]); + }); + } ``` Also see [PowerSyncDatabase.readTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction). @@ -83,57 +36,11 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if [`tx.rollback()`](https://github.com/powersync-ja/powersync-js/blob/ed5bb49b5a1dc579050304fab847feb8d09b45c7/packages/common/src/db/DBAdapter.ts#L53) has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. ```js - // ListsWidget.jsx - import {Alert, Button, FlatList, Text, View} from 'react-native'; - - export const ListsWidget = () => { - // Populate lists with one of methods listed above - const [lists, setLists] = React.useState([]); - - return ( - - ({key: list.id, ...list}))} - renderItem={({item}) => ( - {item.name} - - ); + export function AddCustomerButton({ db }) { + const handleButtonClick = async () => { + await db.execute( + 'INSERT INTO customers(id, name, email) VALUES(uuid(), ?, ?)', + ['Fred', 'fred@example.org'] + ); + }; + + return - ); + return ; + } ```
@@ -270,10 +267,10 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call } ``` - + Example not yet available. - + Example not yet available. @@ -381,7 +378,7 @@ The backend API calls represent your own API client, which must send every entry } ``` - + ```js async function uploadData(database) { const transaction = await database.getNextCrudTransaction(); @@ -392,7 +389,7 @@ The backend API calls represent your own API client, which must send every entry } ``` - + ```js async function uploadData(database) { const transaction = await database.getNextCrudTransaction(); @@ -510,10 +507,10 @@ The backend API calls represent your own API client, which must send every entry } ``` - + Example not yet available. - + Example not yet available. @@ -746,10 +743,10 @@ The backend API calls represent your own API client, which must send every entry For async use cases, see [PowerSyncDatabase.waitForFirstSync](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#waitforfirstsync), which returns a promise that resolves once the first full sync has completed (it queries the internal SQL [ps_buckets](/architecture/client-architecture) table to determine if data has been synced). - + Example not yet available. - + Example not yet available. @@ -889,7 +886,7 @@ The backend API calls represent your own API client, which must send every entry ## Wait for Local Data to Catch Up -`hasSynced` and `waitForFirstSync()` report whether a complete sync has finished at least once, possibly in an earlier app session. To wait until the local database has caught up with the server as of now, use [Sync Catch-Up](/client-sdks/checkpoint-requests): request a checkpoint, then wait for it to sync. These examples use it for a refresh action, so the refresh indicator stops only once the device has caught up. +`hasSynced` and `waitForFirstSync()` report whether a complete sync has finished at least once, possibly in an earlier app session. To wait until the local database has caught up with the server as of now, use [Sync Catch-Up](/client-sdks/checkpoint-requests): request a checkpoint, then wait for it to sync. Most of these examples use it for a refresh action, so the refresh indicator stops only once the device has caught up. Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or later, and you must enable them with the `checkpointMode` option when calling `connect()`. See [Prerequisites](/client-sdks/checkpoint-requests#prerequisites). @@ -957,18 +954,18 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late See the [React Native demo app](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/app/views/todos/lists.tsx) for a complete example. - - The Capacitor SDK uses the same API as the JavaScript Web SDK, so the JavaScript Web example also applies. - ```js const checkpoint = await db.requestCheckpoint(); await checkpoint.waitForSync(); - // This query now reflects the server state from when you created the request + // This query now includes all server changes up to when you created the request const lists = await db.getAll('SELECT * FROM lists'); ``` + + The Capacitor SDK uses the same API as the JavaScript Web SDK, so the JavaScript Web example also applies. + ```dart RefreshIndicator( @@ -1161,10 +1158,10 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late - [SyncStatus API](https://powersync-ja.github.io/powersync-js/common/interfaces/SyncStatus) - [Demo component](https://github.com/powersync-ja/powersync-js/blob/main/demos/react-native-supabase-todolist/library/widgets/GuardBySync.tsx) - + Example not yet available. - + Example not yet available. @@ -1228,7 +1225,7 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late - [Demo component](https://github.com/powersync-ja/powersync.dart/blob/main/demos/supabase-todolist/lib/widgets/guard_by_sync.dart) - You can show users a progress bar when data downloads using the `syncStatus.downloadProgress` property. This is especially useful for long-running initial syncs. `downloadProgress.downloadedFraction` gives a value from 0.0 to 1.0 representing the total sync progress. + You can show users a progress bar when data downloads using the `syncStatus.downloadProgress` property. This is especially useful for long-running initial syncs. `downloadProgress.fraction` gives a value from 0.0 to 1.0 representing the total sync progress. Example (Compose): @@ -1300,7 +1297,7 @@ Checkpoint requests are in alpha. They require PowerSync Service v1.24.0 or late - [Demo component](https://github.com/powersync-ja/powersync-kotlin/blob/main/demos/supabase-todolist/shared/src/commonMain/kotlin/com/powersync/demos/components/GuardBySync.kt) - You can show users a progress bar when data downloads using the `downloadProgress` property from the [`SyncStatusData`](https://powersync-ja.github.io/powersync-swift/documentation/powersync/syncstatusdata/) object. `downloadProgress.downloadedFraction` gives you a value from 0.0 to 1.0 representing the total sync progress. This is especially useful for long-running initial syncs. + You can show users a progress bar when data downloads using the `downloadProgress` property from the [`SyncStatusData`](https://powersync-ja.github.io/powersync-swift/documentation/powersync/syncstatusdata/) object. `downloadProgress.fraction` gives you a value from 0.0 to 1.0 representing the total sync progress. This is especially useful for long-running initial syncs. Example: diff --git a/configuration/app-backend/client-side-integration.mdx b/configuration/app-backend/client-side-integration.mdx index 21aa1983..5c8e7639 100644 --- a/configuration/app-backend/client-side-integration.mdx +++ b/configuration/app-backend/client-side-integration.mdx @@ -168,22 +168,22 @@ The PowerSync sync token is used exclusively for the connection between the Clie For an example implementation of a PowerSync 'backend connector', see the SDK guide for your platform: - + - + - + - + diff --git a/integrations/supabase/guide.mdx b/integrations/supabase/guide.mdx index 76e68a41..f3d66f3f 100644 --- a/integrations/supabase/guide.mdx +++ b/integrations/supabase/guide.mdx @@ -259,9 +259,10 @@ Clone the to-do list demo app for your framework: -```bash Flutter -git clone https://github.com/powersync-ja/powersync.dart.git -cd powersync.dart/demos/supabase-todolist/ +```bash JavaScript Web +git clone https://github.com/powersync-ja/powersync-js.git +cd powersync-js/demos/react-supabase-todolist +pnpm install ``` ```bash React Native @@ -270,10 +271,9 @@ cd powersync-js/demos/react-native-supabase-todolist pnpm install ``` -```bash JavaScript Web -git clone https://github.com/powersync-ja/powersync-js.git -cd powersync-js/demos/react-supabase-todolist -pnpm install +```bash Flutter +git clone https://github.com/powersync-ja/powersync.dart.git +cd powersync.dart/demos/supabase-todolist/ ``` ```bash Kotlin @@ -296,10 +296,10 @@ Create or open the config file for your framework: -```bash Flutter -cp lib/app_config_template.dart lib/app_config.dart +```bash JavaScript Web +cp .env.local.template .env.local -# Edit `lib/app_config.dart` and insert the necessary credentials as detailed below. +# Edit `.env.local` and insert the necessary credentials as detailed below. ``` ```bash React Native @@ -308,10 +308,10 @@ cp .env .env.local # Edit `.env.local` and insert the necessary credentials as detailed below. ``` -```bash JavaScript Web -cp .env.local.template .env.local +```bash Flutter +cp lib/app_config_template.dart lib/app_config.dart -# Edit `.env.local` and insert the necessary credentials as detailed below. +# Edit `lib/app_config.dart` and insert the necessary credentials as detailed below. ``` ```bash Kotlin @@ -343,11 +343,9 @@ Set these values in the config file: -```bash Flutter -# Ensure you have [melos](https://melos.invertase.dev/~melos-latest/getting-started) installed. - -melos bootstrap -flutter run +```bash JavaScript Web +# In `demos/react-supabase-todolist`: +pnpm dev ``` ```bash React Native @@ -358,9 +356,11 @@ pnpm ios pnpm android ``` -```bash JavaScript Web -# In `demos/react-supabase-todolist`: -pnpm dev +```bash Flutter +# Ensure you have [melos](https://melos.invertase.dev/~melos-latest/getting-started) installed. + +melos bootstrap +flutter run ``` ```bash Kotlin diff --git a/intro/examples.mdx b/intro/examples.mdx index fc0b3742..a12f5fc8 100644 --- a/intro/examples.mdx +++ b/intro/examples.mdx @@ -15,33 +15,6 @@ All examples are organized by platform and backend technology. You can adapt any ## Official Demos/Example Projects - - #### Supabase Backend - - - [To-Do List App](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-supabase-todolist#readme) - - Demonstrates [File/Attachment Handling](/client-sdks/advanced/attachments) - - [PowerChat - Group Chat App](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-supabase-group-chat#readme) - - [To-Do List App: React Native Web & Electron](https://github.com/powersync-community/powersync-react-native-web-expo-electron#readme) - - [Background Sync Demo](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-supabase-background-sync) - - Demonstrates [Background Syncing](/client-sdks/advanced/background-syncing) using Expo's BackgroundTask API - - #### Django Custom Backend - - - [To-Do List App](https://github.com/powersync-ja/powersync-js/tree/main/demos/django-react-native-todolist#readme) - - Corresponding backend: [Django To-Do List Backend](https://github.com/powersync-ja/powersync-django-backend-todolist-demo) - - #### Node.js Backend - - - ['MBnB' Listing Search App with Node.js MongoDB Backend](https://github.com/powersync-ja/powersync-react-native-mongodb-mbnb#readme) - - This repo contains both the React Native app and Node.js backend - - #### Other - - - [Point of Sale (POS) App](https://github.com/powersync-community/powersync-pos-demo#readme) - - [React Native Barebones Demo](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-barebones) - - This is a barebones example of using PowerSync in a plain React Native app without Expo or any other framework. See [here](/client-sdks/reference/react-native-and-expo#installation) for more background. - - #### Supabase Backend @@ -73,6 +46,33 @@ All examples are organized by platform and backend technology. You can adapt any - [Vite with Encryption](https://github.com/powersync-ja/powersync-js/blob/main/demos/example-vite-encryption#readme) - Web database encryption demo + + #### Supabase Backend + + - [To-Do List App](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-supabase-todolist#readme) + - Demonstrates [File/Attachment Handling](/client-sdks/advanced/attachments) + - [PowerChat - Group Chat App](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-supabase-group-chat#readme) + - [To-Do List App: React Native Web & Electron](https://github.com/powersync-community/powersync-react-native-web-expo-electron#readme) + - [Background Sync Demo](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-supabase-background-sync) + - Demonstrates [Background Syncing](/client-sdks/advanced/background-syncing) using Expo's BackgroundTask API + + #### Django Custom Backend + + - [To-Do List App](https://github.com/powersync-ja/powersync-js/tree/main/demos/django-react-native-todolist#readme) + - Corresponding backend: [Django To-Do List Backend](https://github.com/powersync-ja/powersync-django-backend-todolist-demo) + + #### Node.js Backend + + - ['MBnB' Listing Search App with Node.js MongoDB Backend](https://github.com/powersync-ja/powersync-react-native-mongodb-mbnb#readme) + - This repo contains both the React Native app and Node.js backend + + #### Other + + - [Point of Sale (POS) App](https://github.com/powersync-community/powersync-pos-demo#readme) + - [React Native Barebones Demo](https://github.com/powersync-ja/powersync-js/tree/main/demos/react-native-barebones) + - This is a barebones example of using PowerSync in a plain React Native app without Expo or any other framework. See [here](/client-sdks/reference/react-native-and-expo#installation) for more background. + + #### Examples diff --git a/intro/setup-guide.mdx b/intro/setup-guide.mdx index b37db58c..64c74fce 100644 --- a/intro/setup-guide.mdx +++ b/intro/setup-guide.mdx @@ -706,12 +706,12 @@ Now it's time to integrate PowerSync into your app. This involves installing the Add the PowerSync Client SDK to your app project. PowerSync provides SDKs for various platforms and frameworks. - - - + + + @@ -784,8 +784,8 @@ For development, you can use the development token you generated in the [Generat -```typescript React Native (TS) -import type { CommonPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/react-native'; +```typescript Web & Capacitor (TS) +import type { CommonPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/web'; import { db } from './Database'; class Connector implements PowerSyncBackendConnector { @@ -815,8 +815,8 @@ const connector = new Connector(); await db.connect(connector); ``` -```typescript Web & Capacitor (TS) -import type { CommonPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/web'; +```typescript React Native (TS) +import type { CommonPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/react-native'; import { db } from './Database'; class Connector implements PowerSyncBackendConnector { @@ -868,6 +868,92 @@ const connector = new Connector(); await db.connect(connector); ``` +```rust Tauri (Rust) +// For Tauri, connecting to PowerSync must be done in Rust so that sync state +// is shared across all windows. Calling connect() from JavaScript will throw. +// +// 1. Define your backend connector in Rust: + +use async_trait::async_trait; +use tauri_plugin_powersync::PowerSyncExt; +use powersync::{BackendConnector, PowerSyncCredentials, PowerSyncDatabase, SyncOptions}; +use powersync::error::PowerSyncError; + +struct MyBackendConnector { + db: PowerSyncDatabase, +} + +#[async_trait] +impl BackendConnector for MyBackendConnector { + async fn fetch_credentials(&self) -> Result { + // for development: use development token + Ok(PowerSyncCredentials { + endpoint: "https://your-instance.powersync.com".to_string(), + token: "your-development-token-here".to_string(), + }) + } + + async fn upload_data(&self) -> Result<(), PowerSyncError> { + let mut local_writes = self.db.crud_transactions(); + while let Some(tx) = local_writes.try_next().await? { + // upload to your backend API + tx.complete().await?; + } + Ok(()) + } +} + +// 2. Add a Tauri command that receives the database handle from JavaScript: +#[tauri::command] +async fn connect( + app: tauri::AppHandle, + handle: usize, +) -> tauri_plugin_powersync::Result<()> { + let database = app.powersync().database_from_javascript_handle(handle)?; + let options = SyncOptions::new(MyBackendConnector { db: database.clone() }); + database.connect(options).await; + Ok(()) +} + +// 3. Register the command in your Tauri builder: +// .invoke_handler(tauri::generate_handler![connect]) + +// 4. Then call it from JavaScript after initializing the database: +// await db.init(); +// await invoke('connect', { handle: db.rustHandle }); +``` + +```dart Dart/Flutter +import 'package:powersync/powersync.dart'; + +class Connector extends PowerSyncBackendConnector { + @override + Future fetchCredentials() async { + return PowerSyncCredentials( + endpoint: 'https://your-instance.powersync.com', + token: 'your-development-token-here' + ); + } + + @override + Future uploadData(PowerSyncDatabase database) async { + final transaction = await database.getNextCrudTransaction(); + if (transaction == null) return; + + for (final op in transaction.crud) { + final record = {...op.opData, 'id': op.id}; + // upload to your backend API + } + + await transaction.complete(); + } +} + +// connect the database to PowerSync Service +final connector = Connector(); +await db.connect(connector); +``` + ```kotlin Kotlin import com.powersync.PowerSyncCredentials import com.powersync.PowerSyncDatabase @@ -929,37 +1015,6 @@ let connector = Connector() await db.connect(connector: connector) ``` -```dart Dart/Flutter -import 'package:powersync/powersync.dart'; - -class Connector extends PowerSyncBackendConnector { - @override - Future fetchCredentials() async { - return PowerSyncCredentials( - endpoint: 'https://your-instance.powersync.com', - token: 'your-development-token-here' - ); - } - - @override - Future uploadData(PowerSyncDatabase database) async { - final transaction = await database.getNextCrudTransaction(); - if (transaction == null) return; - - for (final op in transaction.crud) { - final record = {...op.opData, 'id': op.id}; - // upload to your backend API - } - - await transaction.complete(); - } -} - -// connect the database to PowerSync Service -final connector = Connector(); -await db.connect(connector); -``` - ```csharp .NET using System; using System.Collections.Generic; @@ -1035,61 +1090,6 @@ db.connect(SyncOptions::new(MyBackendConnector { .await; ``` -```rust Tauri (Rust) -// For Tauri, connecting to PowerSync must be done in Rust so that sync state -// is shared across all windows. Calling connect() from JavaScript will throw. -// -// 1. Define your backend connector in Rust: - -use async_trait::async_trait; -use tauri_plugin_powersync::PowerSyncExt; -use powersync::{BackendConnector, PowerSyncCredentials, PowerSyncDatabase, SyncOptions}; -use powersync::error::PowerSyncError; - -struct MyBackendConnector { - db: PowerSyncDatabase, -} - -#[async_trait] -impl BackendConnector for MyBackendConnector { - async fn fetch_credentials(&self) -> Result { - // for development: use development token - Ok(PowerSyncCredentials { - endpoint: "https://your-instance.powersync.com".to_string(), - token: "your-development-token-here".to_string(), - }) - } - - async fn upload_data(&self) -> Result<(), PowerSyncError> { - let mut local_writes = self.db.crud_transactions(); - while let Some(tx) = local_writes.try_next().await? { - // upload to your backend API - tx.complete().await?; - } - Ok(()) - } -} - -// 2. Add a Tauri command that receives the database handle from JavaScript: -#[tauri::command] -async fn connect( - app: tauri::AppHandle, - handle: usize, -) -> tauri_plugin_powersync::Result<()> { - let database = app.powersync().database_from_javascript_handle(handle)?; - let options = SyncOptions::new(MyBackendConnector { db: database.clone() }); - database.connect(options).await; - Ok(()) -} - -// 3. Register the command in your Tauri builder: -// .invoke_handler(tauri::generate_handler![connect]) - -// 4. Then call it from JavaScript after initializing the database: -// await db.init(); -// await invoke('connect', { handle: db.rustHandle }); -``` - Once connected, you can read from and write to the client-side SQLite database. PowerSync automatically syncs changes from your source database down into the SQLite database. For client-side mutations to be uploaded back to your source database, you need to complete the backend integration as explained below. @@ -1146,6 +1146,20 @@ for await (const todos of stream) { // }); ``` +```dart Dart/Flutter +// Get all todos +final todos = await db.getAll('SELECT * FROM todos'); + +// Get a single todo +final todo = await db.get('SELECT * FROM todos WHERE id = ?', [todoId]); + +// Watch for changes +db.watch('SELECT * FROM todos WHERE list_id = ?', [listId]) + .listen((todos) { + // Update UI when data changes + }); +``` + ```kotlin Kotlin // Get all todos val todos = database.getAll("SELECT * FROM todos") { cursor -> @@ -1185,20 +1199,6 @@ for try await todos in db.watch( } ``` -```dart Dart/Flutter -// Get all todos -final todos = await db.getAll('SELECT * FROM todos'); - -// Get a single todo -final todo = await db.get('SELECT * FROM todos WHERE id = ?', [todoId]); - -// Watch for changes -db.watch('SELECT * FROM todos WHERE list_id = ?', [listId]) - .listen((todos) { - // Update UI when data changes - }); -``` - ```csharp .NET // Define a result type with properties matching schema columns (some columns omitted for brevity) // public class ListResult { public string id; public string name; public string owner_id; public string created_at; ... } @@ -1319,6 +1319,23 @@ await db.execute( await db.execute('DELETE FROM todos WHERE id = ?', [todoId]); ``` +```dart Dart/Flutter +// Insert a new todo +await db.execute( + 'INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)', + [listId, 'Buy groceries'] +); + +// Update a todo +await db.execute( + 'UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?', + [todoId] +); + +// Delete a todo +await db.execute('DELETE FROM todos WHERE id = ?', [todoId]); +``` + ```kotlin Kotlin // Insert a new todo database.writeTransaction { ctx -> @@ -1361,23 +1378,6 @@ try await db.execute( ) ``` -```dart Dart/Flutter -// Insert a new todo -await db.execute( - 'INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)', - [listId, 'Buy groceries'] -); - -// Update a todo -await db.execute( - 'UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?', - [todoId] -); - -// Delete a todo -await db.execute('DELETE FROM todos WHERE id = ?', [todoId]); -``` - ```csharp .NET // Insert a new todo await db.Execute( From ab7bcd0cc3a45cb2c0a06f2851150179ad154f7d Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 27 Oct 2026 11:16:02 +0200 Subject: [PATCH 5/8] Polish the agent instructions re: code example quality --- .claude/CLAUDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 0c91990a..b53043e1 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -130,7 +130,7 @@ Wrap images in a `Frame` with a caption and descriptive alt text: ### Code Examples -- Keep usage examples focused on the fundamentals. They may omit surrounding app setup and production error handling, but the demonstrated APIs and behavior must be correct. Use realistic data and expected output where useful. Test examples where practical. Never include real credentials or secrets. +- Make examples complete and runnable where possible. If an example needs a lot of boilerplate just to run, such as app setup or connector wiring, you may leave that out. Make the omission clear with a comment or a sentence before the code block, such as `// Inside your backend connector`. The code you show must still be correct. Use realistic data and expected output where useful. Never include real credentials or secrets. - Give fenced code blocks a language tag. Use filenames only for self-hosted examples. - Do not use placeholder names such as `foo`, `bar`, or `example.com` in runnable examples. - In SQL, use table names directly unless an alias is necessary for a self-join or ambiguous column. Prefer `SELECT tasks.* FROM tasks JOIN projects ON ...` to aliases such as `t`. @@ -152,7 +152,7 @@ Sync Rules are deprecated, but many customers still run them. New documentation ## Verification -- Verify technical claims and run code examples before publication. Select other checks appropriate to the change. +- Verify technical claims and run code examples before publication. Run an example that leaves out boilerplate with the boilerplate added. If that is not practical, check every API and behavior it shows against the SDK source. Select other checks appropriate to the change. - Run `vale ` for changed MDX pages. Add new technical terms to `.github/vale/config/vocabularies/PowerSync/accept.txt`; do not add ordinary misspellings. - After link or navigation changes, run `pnpm check:links`. It runs the Mintlify path, anchor, and snippet checks through `scripts/check-links.mjs`, resolves anchors defined in imported snippets, and is the same check CI runs. It tells you if your Node version is unsupported. Validate repository instruction links as file paths, since the site checker does not cover them. - Use [the lint command](commands/lint-docs.md) for the check workflow and [the reviewer](agents/document-reviewer.md) for editorial review. Passing linters does not establish technical accuracy or style compliance. From 797262d65be4c897201d4f2fe8dfa8a9a2c1814b Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 27 Oct 2026 11:18:52 +0200 Subject: [PATCH 6/8] Update 1.14 migration note --- client-sdks/reference/swift.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-sdks/reference/swift.mdx b/client-sdks/reference/swift.mdx index 8694ee5f..ab05fe6b 100644 --- a/client-sdks/reference/swift.mdx +++ b/client-sdks/reference/swift.mdx @@ -361,5 +361,5 @@ See [Supported Platforms -> Swift SDK](/resources/supported-platforms#swift). Update the version number in `Package.swift` or via Xcode Package Dependencies as documented in the installation instructions: [Installation](/client-sdks/reference/swift#installation). - **Upgrading to v1.14**: This release removes the internal Kotlin dependency and re-implements the SQLite connection pool logic natively in Swift. The public API is unchanged, but the internals are a full rewrite. We recommend testing queries in your app after upgrading to this version. + **Upgrading to v1.14**: This release removes the internal Kotlin dependency and re-implements the SQLite connection pool logic natively in Swift. The public API is unchanged, except that `CrudBatch`, `CrudEntry`, and `CrudTransaction` are now structs that you cannot create in your own code, for example in tests or mocks. The internals are a full rewrite, so we recommend testing queries in your app after upgrading to this version. From df2b6f15f498e9dd0e67b3705b2947ae7eda7a36 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 27 Oct 2026 11:42:51 +0200 Subject: [PATCH 7/8] Further polish --- .../config/vocabularies/PowerSync/accept.txt | 2 + client-sdks/usage-examples.mdx | 863 ++++++++++++------ client-sdks/writing-data.mdx | 10 +- 3 files changed, 597 insertions(+), 278 deletions(-) diff --git a/.github/vale/config/vocabularies/PowerSync/accept.txt b/.github/vale/config/vocabularies/PowerSync/accept.txt index 2a534ce0..a94a22e7 100644 --- a/.github/vale/config/vocabularies/PowerSync/accept.txt +++ b/.github/vale/config/vocabularies/PowerSync/accept.txt @@ -23,6 +23,8 @@ composables config configs cooldown +coroutine +coroutines cybersecurity dealbreaker deduplicate diff --git a/client-sdks/usage-examples.mdx b/client-sdks/usage-examples.mdx index 26312721..6e99690f 100644 --- a/client-sdks/usage-examples.mdx +++ b/client-sdks/usage-examples.mdx @@ -11,13 +11,17 @@ import RustWatch from '/snippets/rust/basic-watch-query.mdx'; import JavaScriptAsyncWatch from '/snippets/basic-watch-query-javascript-async.mdx'; import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-callback.mdx'; +These examples assume that your app already has a PowerSync database instance, as described in the Getting Started steps for your [Client SDK](/client-sdks/overview). Each example focuses on one task and leaves out other app setup. + ## Using Transactions to Group Changes +A transaction groups several writes into one unit. Either all of the writes are saved, or none of them are. PowerSync also queues the changes from one transaction for upload as a group. + +These examples delete a list and its todos together, so the app never keeps todos for a deleted list. + - Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). - - [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if `tx.rollback()` has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. + `writeTransaction()` runs your callback inside a transaction. PowerSync saves the changes when the callback finishes, and rolls them back if the callback throws an error. ```js async function deleteList(db, listId) { @@ -28,12 +32,14 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call } ``` - Also see [PowerSyncDatabase.readTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction). + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`writeTransaction()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) + - [`readTransaction()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction) - Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). - - [PowerSyncDatabase.writeTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) automatically commits changes after the transaction callback is completed if [`tx.rollback()`](https://github.com/powersync-ja/powersync-js/blob/ed5bb49b5a1dc579050304fab847feb8d09b45c7/packages/common/src/db/DBAdapter.ts#L53) has not explicitly been called. If an exception is thrown in the callback then changes are automatically rolled back. + `writeTransaction()` runs your callback inside a transaction. PowerSync saves the changes when the callback finishes, and rolls them back if the callback throws an error. ```js async function deleteList(db, listId) { @@ -44,53 +50,78 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call } ``` - Also see [PowerSyncDatabase.readTransaction(callback)](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction). + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`writeTransaction()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) + - [`readTransaction()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction) - Example not yet available. + `writeTransaction()` runs your callback inside a transaction. PowerSync saves the changes when the callback finishes, and rolls them back if the callback throws an error. + + ```js + async function deleteList(db, listId) { + await db.writeTransaction(async (tx) => { + await tx.execute('DELETE FROM lists WHERE id = ?', [listId]); + await tx.execute('DELETE FROM todos WHERE list_id = ?', [listId]); + }); + } + ``` + + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`writeTransaction()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#writetransaction) + - [`readTransaction()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#readtransaction) - Example not yet available. + The Capacitor SDK uses the same API as the JavaScript Web SDK, so the JavaScript Web example also applies. - Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). - - The [writeTransaction(callback)](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/writeTransaction.html) method combines all writes into a single transaction, only committing to persistent storage once. + `writeTransaction()` runs your callback inside a transaction. PowerSync saves the changes when the callback finishes, and rolls them back if the callback throws. ```dart Future deleteList(PowerSyncDatabase db, String id) async { await db.writeTransaction((tx) async { - // Delete the main list - await tx.execute('DELETE FROM lists WHERE id = ?', [id]); - // Delete any children of the list - await tx.execute('DELETE FROM todos WHERE list_id = ?', [id]); + await tx.execute('DELETE FROM lists WHERE id = ?', [id]); + await tx.execute('DELETE FROM todos WHERE list_id = ?', [id]); }); } ``` - Also see [readTransaction(callback)](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/readTransaction.html) + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`writeTransaction()` API reference](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/writeTransaction.html) + - [`readTransaction()` API reference](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteConnection/readTransaction.html) - Use `writeTransaction` to group statements that can write to the database. + `writeTransaction` runs the block inside a transaction. Run each statement on the `transaction` that the block receives. PowerSync saves the changes when the block finishes, and rolls them back if the block throws an exception. ```kotlin - database.writeTransaction { transaction -> - transaction.execute( - sql = "DELETE FROM lists WHERE id = ?", - parameters = listOf(listId) - ) - transaction.execute( - sql = "DELETE FROM todos WHERE list_id = ?", - parameters = listOf(listId) - ) + suspend fun deleteList(database: PowerSyncDatabase, listId: String) { + database.writeTransaction { transaction -> + transaction.execute( + sql = "DELETE FROM lists WHERE id = ?", + parameters = listOf(listId) + ) + transaction.execute( + sql = "DELETE FROM todos WHERE list_id = ?", + parameters = listOf(listId) + ) + } } ``` + + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`PowerSyncDatabase` API reference](https://powersync-ja.github.io/powersync-kotlin/common/com.powersync/-power-sync-database/index.html) - Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception). + `writeTransaction` runs the closure inside a transaction. Statements inside the closure run synchronously, so they use `try` without `await`. PowerSync saves the changes when the closure finishes, and rolls them back if the closure throws. ```swift - // Delete a list and its todos in a transaction func deleteList(db: any PowerSyncDatabaseProtocol, listId: String) async throws { try await db.writeTransaction { tx in try tx.execute(sql: "DELETE FROM lists WHERE id = ?", parameters: [listId]) @@ -99,9 +130,15 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call } ``` - Also see [`readTransaction`](https://powersync-ja.github.io/powersync-swift/documentation/powersync/queries/readtransaction\(callback:\)). + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`writeTransaction` API reference](https://powersync-ja.github.io/powersync-swift/documentation/powersync/queries/writetransaction\(callback:\)) + - [`readTransaction` API reference](https://powersync-ja.github.io/powersync-swift/documentation/powersync/queries/readtransaction\(callback:\)) + `WriteTransaction` runs the callback inside a transaction. PowerSync saves the changes when the callback finishes, and rolls them back if the callback throws an exception. + ```cs using PowerSync.Common.Client; @@ -111,22 +148,23 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call { await db.WriteTransaction(async (tx) => { - // Delete the main list await tx.Execute("DELETE FROM lists WHERE id = ?", new object[] { listId }); - // Delete any children of the list await tx.Execute("DELETE FROM todos WHERE list_id = ?", new object[] { listId }); - - // Transactions are automatically committed at the end of execution - // Transactions are automatically rolled back if an exception occurred }); } } ``` - Also see [`ReadTransaction`](https://powersync-ja.github.io/powersync-dotnet/api/PowerSync.Common.Client.PowerSyncDatabase.ReadTransaction.html). + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`WriteTransaction` API reference](https://powersync-ja.github.io/powersync-dotnet/api/PowerSync.Common.Client.PowerSyncDatabase.WriteTransaction.html) + - [`ReadTransaction` API reference](https://powersync-ja.github.io/powersync-dotnet/api/PowerSync.Common.Client.PowerSyncDatabase.ReadTransaction.html) - ```Rust + `db.writer()` returns the write connection, and `transaction()` starts a transaction on it. Call `commit()` to save the changes. If the function returns early with an error, the transaction is dropped and its changes are rolled back. + + ```rust use powersync::{PowerSyncDatabase, error::PowerSyncError}; use rusqlite::params; @@ -141,14 +179,21 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call Ok(()) } ``` + + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [Rust SDK: Mutations](/client-sdks/reference/rust#mutations) ## Listen for Changes in Data +A watch query runs a SQL query again whenever the tables it reads from change, and gives you the new results. Use it to keep your UI up to date with local data, including changes that arrive through sync. + - Use [PowerSyncDatabase.watch](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) to watch for changes in source tables. + `watch()` gives you new results each time the query's tables change. You can read the results with an async iterator (`for await`), or receive them in an `onResult` callback. @@ -159,10 +204,13 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call - For advanced watch query features like incremental updates and differential results, see [Live Queries / Watch Queries](/client-sdks/watch-queries). + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries), including incremental and differential results + - [`watch()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) - Use [PowerSyncDatabase.watch](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) to watch for changes in source tables. + `watch()` gives you new results each time the query's tables change. You can read the results with an async iterator (`for await`), or receive them in an `onResult` callback. @@ -173,10 +221,13 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call - For advanced watch query features like incremental updates and differential results, see [Live Queries / Watch Queries](/client-sdks/watch-queries). + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries), including incremental and differential results + - [`watch()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) - Use [PowerSyncDatabase.watch](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) to watch for changes in source tables. + `watch()` gives you new results each time the query's tables change. You can read the results with an async iterator (`for await`), or receive them in an `onResult` callback. @@ -187,54 +238,75 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call - For advanced watch query features like incremental updates and differential results, see [Live Queries / Watch Queries](/client-sdks/watch-queries). + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries), including incremental and differential results + - [`watch()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#watch) - The Capacitor SDK uses the same API as the [JavaScript Web SDK](/client-sdks/reference/javascript-web#watching-queries-powersync-watch). Use `db.watch()` to watch for changes in source tables. - - - - - - - - - - - For advanced watch query features like incremental updates and differential results, see [Live Queries / Watch Queries](/client-sdks/watch-queries). + The Capacitor SDK uses the same API as the JavaScript Web SDK, so the JavaScript Web example also applies. - Use [watch](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/watch.html) to watch for changes to the dependent tables of any SQL query. + `watch()` returns a `Stream` that emits new results each time the query's tables change. This example uses a `StreamBuilder` to rebuild the widget with each result. + + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries) + - [`watch()` API reference](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/watch.html) - Use the `watch` method to watch for changes to the dependent tables of any SQL query. + `watch()` returns a `Flow` that emits new results each time the query's tables change. The mapper function converts each row into a `ListItem`. + + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries) + - [Kotlin SDK: Watching Queries](/client-sdks/reference/kotlin#watching-queries-powersync-watch) - Use `watch` to watch for changes to the dependent tables of any SQL query. + `watch()` returns an `AsyncThrowingStream` that emits new results each time the query's tables change. The mapper closure converts each row into a `ListContent`. + + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries) + - [Swift SDK: Watching Queries](/client-sdks/reference/swift#watching-queries-powersync-watch) - Use `db.Watch()` to watch queries for changes. `Watch` returns an `IAsyncEnumerable` (since v0.0.11-alpha.1). + `Watch()` returns an `IAsyncEnumerable` (since v0.0.11-alpha.1) that yields new results each time the query's tables change. Cancel the token to stop watching. + + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries) + - [.NET SDK: Watching Queries](/client-sdks/reference/dotnet#watching-queries-powersync-watch) - Use `watch_statement` to watch for changes to the dependent tables of any SQL query. + `watch_statement()` returns a stream that emits new results each time the query's tables change. The closure reads the rows of each result. + + **Learn more:** + + - [Live Queries / Watch Queries](/client-sdks/watch-queries) + - [Rust SDK: Watching Queries](/client-sdks/reference/rust#watching-queries) ## Insert, Update, and Delete Data in the Local Database +Write to the local SQLite database with standard SQL `INSERT`, `UPDATE`, and `DELETE` statements. PowerSync applies each write locally right away and queues it for upload to your backend. + +The insert examples use `uuid()`, a SQL function that PowerSync provides, to generate an ID for each new row. + - Use [PowerSyncDatabase.execute](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#execute) to run INSERT, UPDATE or DELETE queries. + `execute()` runs a SQL statement. The `?` placeholders take their values from the parameter array. This React button inserts a new customer when clicked. ```jsx export function AddCustomerButton({ db }) { @@ -248,9 +320,14 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call return ; } ``` + + **Learn more:** + + - [Writing Data](/client-sdks/writing-data) + - [`execute()` API reference](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#execute) - Use [PowerSyncDatabase.execute](https://powersync-ja.github.io/powersync-js/common/interfaces/CommonPowerSyncDatabase#execute) to run INSERT, UPDATE or DELETE queries. + `execute()` runs a SQL statement. The `?` placeholders take their values from the parameter array. This React Native button inserts a new customer when pressed. ```jsx import { Button } from 'react-native'; @@ -266,15 +343,32 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call return