From 12dd749ae1319c5c0cc3dc1fe58cb0e92c334542 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 3 Aug 2026 12:23:22 +0200 Subject: [PATCH 01/12] Checkpoint Requests --- architecture/consistency.mdx | 2 + client-sdks/advanced/checkpoint-requests.mdx | 129 +++++++++++++++++++ docs.json | 1 + handling-writes/custom-write-checkpoints.mdx | 6 +- 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 client-sdks/advanced/checkpoint-requests.mdx diff --git a/architecture/consistency.mdx b/architecture/consistency.mdx index d39419e4..c9ac905a 100644 --- a/architecture/consistency.mdx +++ b/architecture/consistency.mdx @@ -13,6 +13,8 @@ A checkpoint is a single point-in-time on the server (similar to an [LSN in Post The client only updates its local state when it has all the data matching a checkpoint, and then it updates the state to exactly match that of the checkpoint. There is no intermediate state while downloading large sets of changes such as large server-side transactions. Different tables and [buckets](/architecture/powersync-service#bucket-system) are all included in the same consistent checkpoint, to ensure that the state is consistent over all data in the client. +Alpha [Checkpoint Requests](/client-sdks/advanced/checkpoint-requests) let you wait until the local database has applied a new point-in-time checkpoint after the initial sync. + ## Client-Side Mutations Client-side mutations are applied on top of the last checkpoint received from the server, as well as being persisted into an [upload queue](/architecture/client-architecture#writing-data-via-sqlite-database-and-upload-queue). diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx new file mode 100644 index 00000000..9b8f6da9 --- /dev/null +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -0,0 +1,129 @@ +--- +title: "Checkpoint Requests (Alpha)" +sidebarTitle: "Checkpoint Requests" +description: "Wait for the local database to reach a point-in-time sync boundary with checkpoint requests." +--- + +Use a checkpoint request when your app needs to confirm that the local database has applied all relevant source changes through a specific point. For example, you can use one to implement pull-to-refresh after the initial sync has already completed. + + +Checkpoint requests are an alpha API and may change. Client support is currently available only for Swift and requires PowerSync Service version 1.24.0 or later. Support for other SDKs is planned. + + +## How Checkpoint Requests Work + +When you create a checkpoint request, the PowerSync Service records the source database's current replication position. The PowerSync Client SDK then waits until a checkpoint that covers that position has been completely synced and applied to the local database. + +A checkpoint request is a point-in-time barrier. You cannot supply an arbitrary source position. The PowerSync Service captures the position when it handles the request. + +Checkpoint requests differ from `waitForFirstSync()`: + +- `waitForFirstSync()` waits for the first complete sync after the database starts syncing. +- `requestCheckpoint()` creates a new boundary and lets you wait for the local database to reach it, even after the initial sync has completed. + +See [Consistency](/architecture/consistency) for more information about how PowerSync applies complete checkpoints. + +## Prerequisites + +Before creating a checkpoint request: + +1. Use a PowerSync Swift SDK release that supports the alpha checkpoint request API. +2. Run PowerSync Service version 1.24.0 or later. +3. Connect with `checkpointMode` set to `.requests()`. + +```swift +try await database.connect( + connector: connector, + options: ConnectOptions(checkpointMode: .requests()) +) +``` + +The default checkpoint mode is `.legacy`. Calling `requestCheckpoint()` in legacy mode throws `CheckpointRequestError.checkpointRequestsNotEnabled`. + +## Waiting for a New Sync Boundary + +Create a request and wait for it to sync before reading the refreshed local state: + +```swift +func refreshLocalData() async throws { + let checkpoint = try await database.requestCheckpoint() + try await checkpoint.waitForSync(timeout: 30) + // Local queries now include changes covered by the requested checkpoint. +} +``` + +`requestCheckpoint()` requires an active or connecting sync client. You can call it while the client is connecting, but the device must be online for the checkpoint request to reach the PowerSync Service. If the device is offline or the sync client is reconnecting, the call waits for a connection and continues once the service is reachable. + +Creating the request does not have its own timeout, so it may remain suspended while the sync client retries its connection. Cancel the calling task if you need to stop waiting for request creation. + +The timeout passed to `waitForSync(timeout:)` only limits how long you wait for the created checkpoint to be applied locally. + +## Handling Wait Failures + +Handle request creation and waiting errors separately when your app needs different recovery behavior: + +```swift +do { + let checkpoint = try await database.requestCheckpoint() + try await checkpoint.waitForSync(timeout: 30) +} catch CheckpointWaitError.timeout { + showRefreshMessage("The refresh timed out. Try again.") +} catch CheckpointWaitError.disconnected { + showRefreshMessage("Reconnect before refreshing again.") +} catch let error as any CheckpointError { + showRefreshMessage(error.localizedDescription) +} +``` + +A request remains valid across a disconnect. After reconnecting with `.requests()`, you can call `waitForSync()` again on the same request. Discard existing request values after clearing the local PowerSync database because clearing it resets the persisted request state. + +`waitForSync()` also fails if the sync client reports an upload or download error. Wait for sync to recover before retrying. + +## Relationship to Local Writes + +When `.requests()` mode is enabled, the PowerSync Client SDK also uses checkpoint request IDs internally after it finishes uploading the local write queue. This preserves PowerSync's normal consistency guarantee without requiring you to call `requestCheckpoint()` for each local write. + +You can create an explicit checkpoint request while local writes are waiting to upload. PowerSync does not apply the requested checkpoint while those writes are pending. After the upload queue is empty, the PowerSync Client SDK creates another checkpoint request as its upload target. If the explicit request was created first, this upload target has a newer request ID, supersedes the earlier request, and captures a source position after the upload has completed. + +`waitForSync()` considers the explicit request complete when the same or a newer checkpoint request has been applied locally. If the explicit request is created after the upload target instead, it captures an even later source position. You can therefore write locally and wait for the uploaded result to return through sync: + +```swift +try await database.execute( + sql: "INSERT INTO tasks (id, description) VALUES (uuid(), ?)", + parameters: ["Review the project plan"] +) + +let checkpoint = try await database.requestCheckpoint() +try await checkpoint.waitForSync(timeout: 30) +// The pending write has uploaded and its source state has synced locally. +``` + +This behavior relies on `uploadData()` returning only after your backend has committed the uploaded changes to the source database. See [Writing Client Changes](/handling-writes/writing-client-changes#why-must-my-write-endpoint-be-synchronous) for this requirement. + +The upload response remains the authority on whether your backend accepted, changed, or rejected a mutation. A synced checkpoint request only confirms that PowerSync and the local database have progressed through the captured source boundary. + +## Asynchronous Upload Backends + +The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, make your existing connector conform to `CustomCheckpointRequestConnector`: + +```swift +extension BackendConnector: CustomCheckpointRequestConnector { + func postCheckpointRequest( + _ checkpointRequestId: Int64, + clientId: String + ) async throws -> Int64 { + let response = try await backendAPI.createCheckpointRequest( + checkpointRequestId: checkpointRequestId, + clientId: clientId + ) + + return response.checkpointRequestId + } +} +``` + +Your backend must process the checkpoint request after all earlier uploads for that client. It must then write the increasing request ID into the source-side checkpoint mechanism so PowerSync can observe it through replication. + +Treat requests as idempotent because the SDK may post the same ID again after reconnecting. Return the newest request ID already recorded for that user and client when it is greater than the submitted ID. The connector must use your application's own authentication for this call because `postCheckpointRequest()` does not receive the PowerSync sync token. + +See [Custom Write Checkpoints](/handling-writes/custom-write-checkpoints) for the source-side setup used by asynchronous upload pipelines. That page uses the previous name for checkpoint acknowledgements. diff --git a/docs.json b/docs.json index afa5071e..889dcd9b 100644 --- a/docs.json +++ b/docs.json @@ -359,6 +359,7 @@ "client-sdks/advanced/unit-testing", "client-sdks/advanced/local-only-usage", "client-sdks/advanced/background-syncing", + "client-sdks/advanced/checkpoint-requests", "client-sdks/advanced/data-encryption", "client-sdks/advanced/sqlite-extensions" ] diff --git a/handling-writes/custom-write-checkpoints.mdx b/handling-writes/custom-write-checkpoints.mdx index 1fcb775d..1fd66423 100644 --- a/handling-writes/custom-write-checkpoints.mdx +++ b/handling-writes/custom-write-checkpoints.mdx @@ -9,6 +9,10 @@ description: "Use Custom Write Checkpoints to track asynchronous data uploads th Custom Write Checkpoints are available for customers on our [Team and Enterprise](https://www.powersync.com/pricing) plans. + +The alpha [Checkpoint Requests](/client-sdks/advanced/checkpoint-requests) API uses the term "custom checkpoint requests" for asynchronous upload backends. Client support is currently available for Swift. This page retains the previous "Custom Write Checkpoints" name for the source-side configuration. + + To ensure [consistency](/architecture/consistency), PowerSync relies on Write Checkpoints. These checkpoints ensure that clients have uploaded their own local changes/mutations to the server before applying downloaded data from the server to the local database. The essential requirement is that the client must get a Write Checkpoint after uploading its last write/mutation. Then, when downloading data from the server, the client checks whether the Write Checkpoint is part of the largest [sync checkpoint](https://github.com/powersync-ja/powersync-service/blob/main/docs/specs/sync-protocol.md) received from the server (i.e. from the PowerSync Service). If it is, the client applies the server-side state to the local database. @@ -199,4 +203,4 @@ router.put('/checkpoint', async (req, res) => { ``` -An example implementation can be seen in the [Node.js backend demo](https://github.com/powersync-ja/powersync-nodejs-backend-todolist-demo/blob/main/src/api/data.js), including examples for [MongoDB](https://github.com/powersync-ja/powersync-nodejs-backend-todolist-demo/blob/main/src/persistance/mongo/mongo-persistance.js) and [MySQL](https://github.com/powersync-ja/powersync-nodejs-backend-todolist-demo/blob/main/src/persistance/mysql/mysql-persistance.js). \ No newline at end of file +An example implementation can be seen in the [Node.js backend demo](https://github.com/powersync-ja/powersync-nodejs-backend-todolist-demo/blob/main/src/api/data.js), including examples for [MongoDB](https://github.com/powersync-ja/powersync-nodejs-backend-todolist-demo/blob/main/src/persistance/mongo/mongo-persistance.js) and [MySQL](https://github.com/powersync-ja/powersync-nodejs-backend-todolist-demo/blob/main/src/persistance/mysql/mysql-persistance.js). From 25b1e8586d9e8aed0d801b096a2b939174aa1f9a Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 3 Aug 2026 13:39:24 +0200 Subject: [PATCH 02/12] add note about custom checkpoint requests logic --- client-sdks/advanced/checkpoint-requests.mdx | 28 +++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index 9b8f6da9..7bfc0f14 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -104,7 +104,27 @@ The upload response remains the authority on whether your backend accepted, chan ## Asynchronous Upload Backends -The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, make your existing connector conform to `CustomCheckpointRequestConnector`: +The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, use the same source-side behavior described in [Custom Write Checkpoints](/handling-writes/custom-write-checkpoints). That page uses the previous name for checkpoint acknowledgements. + +The difference on the client is that the PowerSync Client SDK generates the checkpoint request ID and sends it to your backend through `CustomCheckpointRequestConnector`. + +### Checkpoint Request IDs + +The SDK persists an increasing checkpoint request ID in the local database. When connecting, it sends its current ID to the backend to reconcile the local counter with any state the backend still holds. The SDK uses the ID returned by the backend as the starting point before allocating later requests. + +Store the greatest request ID received for each authenticated user and PowerSync client ID. When handling a request: + +1. If the submitted ID is greater than the stored ID, record and process the submitted ID. +2. If the submitted ID is equal to or less than the stored ID, do not move the stored value backward. +3. Return the greater of the submitted and stored IDs. + +This comparison makes repeated and stale requests idempotent. It also handles seeding after the local request counter has been reset. For example, if the client submits ID `1` while the backend still holds ID `42`, return `42`. The SDK then continues allocating IDs after `42`. + +You can delete stored request records after an appropriate retention period. While a record exists, return its value during reconciliation so the SDK can resume from that value. + +### Swift Connector + +Make your existing connector conform to `CustomCheckpointRequestConnector` and forward the request to your application backend: ```swift extension BackendConnector: CustomCheckpointRequestConnector { @@ -122,8 +142,4 @@ extension BackendConnector: CustomCheckpointRequestConnector { } ``` -Your backend must process the checkpoint request after all earlier uploads for that client. It must then write the increasing request ID into the source-side checkpoint mechanism so PowerSync can observe it through replication. - -Treat requests as idempotent because the SDK may post the same ID again after reconnecting. Return the newest request ID already recorded for that user and client when it is greater than the submitted ID. The connector must use your application's own authentication for this call because `postCheckpointRequest()` does not receive the PowerSync sync token. - -See [Custom Write Checkpoints](/handling-writes/custom-write-checkpoints) for the source-side setup used by asynchronous upload pipelines. That page uses the previous name for checkpoint acknowledgements. +The connector must use your application's own authentication for this call because `postCheckpointRequest()` does not receive the PowerSync sync token. From 0f718ce1f0c4dab15a156fd6773784f2fc74be81 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 4 Aug 2026 12:08:40 +0200 Subject: [PATCH 03/12] Polish the intro --- client-sdks/advanced/checkpoint-requests.mdx | 38 +++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index 7bfc0f14..5221d22b 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -1,25 +1,31 @@ --- title: "Checkpoint Requests (Alpha)" sidebarTitle: "Checkpoint Requests" -description: "Wait for the local database to reach a point-in-time sync boundary with checkpoint requests." +description: "Use checkpoint requests to wait until the local database has caught up with the current server state." --- -Use a checkpoint request when your app needs to confirm that the local database has applied all relevant source changes through a specific point. For example, you can use one to implement pull-to-refresh after the initial sync has already completed. +PowerSync syncs continuously in the background, and [sync status](/client-sdks/usage-examples#accessing-powersync-connection-status-information) fields like `connected`, `downloading`, and `hasSynced` tell you that this is happening. What they cannot tell you is whether the data on the device is up to date with the server right now. Checkpoint requests let you confirm this: you request a marker of the current server state, then wait until the local database has caught up to it. Checkpoint requests are an alpha API and may change. Client support is currently available only for Swift and requires PowerSync Service version 1.24.0 or later. Support for other SDKs is planned. -## How Checkpoint Requests Work +Use a checkpoint request whenever your app needs to confirm that it is up to date with the server, after the initial sync has already completed. Example use cases include: + +- Pull-to-refresh: resolve the refresh indicator only once the device has caught up with the server, instead of hiding it after an arbitrary delay. +- Push notifications: when a user opens a notification, wait until the data it refers to has synced before rendering the screen. +- Backend processing: after your backend finishes a job and writes the result to the source database, know when that result is available locally. +- App startup or foregrounding: show a "syncing latest changes" state that ends exactly when the device is caught up. -When you create a checkpoint request, the PowerSync Service records the source database's current replication position. The PowerSync Client SDK then waits until a checkpoint that covers that position has been completely synced and applied to the local database. +Checkpoint requests are related to, but different from, [waitForFirstSync()](/client-sdks/usage-examples#wait-for-the-initial-sync-to-complete). `waitForFirstSync()` resolves once, when the first complete sync finishes. A checkpoint request can be created at any time after that, and confirms that the local database has caught up with the server as of the moment you created it. + +## How Checkpoint Requests Work -A checkpoint request is a point-in-time barrier. You cannot supply an arbitrary source position. The PowerSync Service captures the position when it handles the request. +During normal sync, the PowerSync Service groups changes from the source database into checkpoints, and the PowerSync Client SDK applies each checkpoint to the local database as a single consistent unit. This happens continuously in the background. Checkpoint requests add a way to point at a specific checkpoint: one that reflects server state at or after the moment you asked. -Checkpoint requests differ from `waitForFirstSync()`: +When you call `requestCheckpoint()`, the PowerSync Service records the source database's current replication position. `waitForSync()` then resolves once a checkpoint that covers that position has been completely synced and applied to the local database. At that point, everything the source database contained when the Service handled the request is present locally. -- `waitForFirstSync()` waits for the first complete sync after the database starts syncing. -- `requestCheckpoint()` creates a new boundary and lets you wait for the local database to reach it, even after the initial sync has completed. +You cannot supply an arbitrary point in time. The PowerSync Service always captures the source database's position at the moment it handles the request. See [Consistency](/architecture/consistency) for more information about how PowerSync applies complete checkpoints. @@ -38,25 +44,25 @@ try await database.connect( ) ``` -The default checkpoint mode is `.legacy`. Calling `requestCheckpoint()` in legacy mode throws `CheckpointRequestError.checkpointRequestsNotEnabled`. +Checkpoint requests are opt-in. Without this connect option, calling `requestCheckpoint()` throws an error. -## Waiting for a New Sync Boundary +## Waiting for the Latest Server Data -Create a request and wait for it to sync before reading the refreshed local state: +Create a checkpoint request, then wait for it to sync before reading the refreshed data: ```swift func refreshLocalData() async throws { let checkpoint = try await database.requestCheckpoint() try await checkpoint.waitForSync(timeout: 30) - // Local queries now include changes covered by the requested checkpoint. + // Local queries now reflect server state from when the request was made. } ``` -`requestCheckpoint()` requires an active or connecting sync client. You can call it while the client is connecting, but the device must be online for the checkpoint request to reach the PowerSync Service. If the device is offline or the sync client is reconnecting, the call waits for a connection and continues once the service is reachable. +`requestCheckpoint()` requires that the database is connected or connecting. The device must be online for the request to reach the PowerSync Service. If it is offline or the sync client is reconnecting, the call waits and continues once the Service is reachable. -Creating the request does not have its own timeout, so it may remain suspended while the sync client retries its connection. Cancel the calling task if you need to stop waiting for request creation. +Creating the request has no timeout of its own, so it can stay suspended while the sync client retries its connection. Cancel the calling task if you need to stop waiting. -The timeout passed to `waitForSync(timeout:)` only limits how long you wait for the created checkpoint to be applied locally. +The timeout passed to `waitForSync(timeout:)` only limits how long you wait for the checkpoint to sync and apply locally. ## Handling Wait Failures @@ -100,7 +106,7 @@ try await checkpoint.waitForSync(timeout: 30) This behavior relies on `uploadData()` returning only after your backend has committed the uploaded changes to the source database. See [Writing Client Changes](/handling-writes/writing-client-changes#why-must-my-write-endpoint-be-synchronous) for this requirement. -The upload response remains the authority on whether your backend accepted, changed, or rejected a mutation. A synced checkpoint request only confirms that PowerSync and the local database have progressed through the captured source boundary. +The upload response remains the authority on whether your backend accepted, changed, or rejected a mutation. A synced checkpoint request only confirms that PowerSync and the local database have progressed through the source database position the request captured. ## Asynchronous Upload Backends From 634d4f528bc9344d787d12111389236e6b13dd0b Mon Sep 17 00:00:00 2001 From: benitav Date: Tue, 4 Aug 2026 13:15:56 +0200 Subject: [PATCH 04/12] Apply suggestion from @benitav --- client-sdks/advanced/checkpoint-requests.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index 5221d22b..28e48644 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -33,7 +33,7 @@ See [Consistency](/architecture/consistency) for more information about how Powe Before creating a checkpoint request: -1. Use a PowerSync Swift SDK release that supports the alpha checkpoint request API. +1. Use PowerSync Swift SDK v1.16.0 or greater. 2. Run PowerSync Service version 1.24.0 or later. 3. Connect with `checkpointMode` set to `.requests()`. From d70b3bbcf124a02378aca1f3741d002bc7214d8d Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 4 Aug 2026 13:21:34 +0200 Subject: [PATCH 05/12] Polish --- client-sdks/advanced/checkpoint-requests.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index 28e48644..2bed1485 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -33,8 +33,8 @@ See [Consistency](/architecture/consistency) for more information about how Powe Before creating a checkpoint request: -1. Use PowerSync Swift SDK v1.16.0 or greater. -2. Run PowerSync Service version 1.24.0 or later. +1. Run PowerSync Swift SDK v1.16.0 or later. +2. Run PowerSync Service v1.24.0 or later. 3. Connect with `checkpointMode` set to `.requests()`. ```swift From 0c9a3315b9acb3b4444924f81cda7034ae69d472 Mon Sep 17 00:00:00 2001 From: benitav Date: Tue, 4 Aug 2026 13:54:23 +0200 Subject: [PATCH 06/12] Apply suggestion from @benitav --- architecture/consistency.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/architecture/consistency.mdx b/architecture/consistency.mdx index c9ac905a..97e6f87b 100644 --- a/architecture/consistency.mdx +++ b/architecture/consistency.mdx @@ -13,7 +13,7 @@ A checkpoint is a single point-in-time on the server (similar to an [LSN in Post The client only updates its local state when it has all the data matching a checkpoint, and then it updates the state to exactly match that of the checkpoint. There is no intermediate state while downloading large sets of changes such as large server-side transactions. Different tables and [buckets](/architecture/powersync-service#bucket-system) are all included in the same consistent checkpoint, to ensure that the state is consistent over all data in the client. -Alpha [Checkpoint Requests](/client-sdks/advanced/checkpoint-requests) let you wait until the local database has applied a new point-in-time checkpoint after the initial sync. +[Checkpoint Requests](/client-sdks/advanced/checkpoint-requests) (currently in an alpha release) let you wait until the local database has applied a new point-in-time checkpoint after the initial sync. ## Client-Side Mutations From f908ae2812b6c5fd6099a7a6903b6d89bf795541 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 4 Aug 2026 13:59:10 +0200 Subject: [PATCH 07/12] Add to Feature status page --- resources/feature-status.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/feature-status.mdx b/resources/feature-status.mdx index beb233f3..5c53dd0e 100644 --- a/resources/feature-status.mdx +++ b/resources/feature-status.mdx @@ -67,6 +67,7 @@ Below is a summary of the current main PowerSync features and their release stat | | | | **Client SDKs** | | | High Performance Diffs | Experimental | +| Checkpoint Requests | Alpha | | Tauri SDK | Alpha | | Rust SDK | Alpha | | Capacitor SDK | Beta | From 04c3a6468b55409d7fe44e095ab7f7015c7de26f Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Tue, 4 Aug 2026 17:45:24 +0200 Subject: [PATCH 08/12] Additional wording polish --- client-sdks/advanced/checkpoint-requests.mdx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index 2bed1485..85490156 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -1,21 +1,27 @@ --- title: "Checkpoint Requests (Alpha)" sidebarTitle: "Checkpoint Requests" -description: "Use checkpoint requests to wait until the local database has caught up with the current server state." +description: "Confirm that the local database is fully up to date with the server, covering pending uploads and the latest server changes." --- -PowerSync syncs continuously in the background, and [sync status](/client-sdks/usage-examples#accessing-powersync-connection-status-information) fields like `connected`, `downloading`, and `hasSynced` tell you that this is happening. What they cannot tell you is whether the data on the device is up to date with the server right now. Checkpoint requests let you confirm this: you request a marker of the current server state, then wait until the local database has caught up to it. +PowerSync syncs continuously in the background, and [sync status](/client-sdks/usage-examples#accessing-powersync-connection-status-information) fields like `connected`, `downloading`, and `hasSynced` tell you that this is happening. What they cannot tell you is whether the data on the device is up to date with the server right now. + +Checkpoint requests let you confirm this: you request a marker of the current server state, then wait until the local database has caught up to it. Waiting covers uploads as well as downloads: a request created after local writes confirms that those writes have been uploaded and their results have synced back (see [Relationship to Local Writes](#relationship-to-local-writes)). + +This is also the API to use when your app needs to "force a sync." While connected, PowerSync already syncs as quickly as it can, so a checkpoint request does not make sync run faster. But it tells you when the device has caught up, which is usually what a forced sync is meant to achieve. Checkpoint requests are an alpha API and may change. Client support is currently available only for Swift and requires PowerSync Service version 1.24.0 or later. Support for other SDKs is planned. -Use a checkpoint request whenever your app needs to confirm that it is up to date with the server, after the initial sync has already completed. Example use cases include: +Example use cases include: +- Critical operations: confirm that pending local writes have uploaded and the latest server data has downloaded before a user starts a work session, or before the app performs a sensitive operation. - Pull-to-refresh: resolve the refresh indicator only once the device has caught up with the server, instead of hiding it after an arbitrary delay. - Push notifications: when a user opens a notification, wait until the data it refers to has synced before rendering the screen. - Backend processing: after your backend finishes a job and writes the result to the source database, know when that result is available locally. - App startup or foregrounding: show a "syncing latest changes" state that ends exactly when the device is caught up. +- Background refresh: connect, wait for a checkpoint request to sync, then disconnect again, for example from a scheduled background task. Checkpoint requests are related to, but different from, [waitForFirstSync()](/client-sdks/usage-examples#wait-for-the-initial-sync-to-complete). `waitForFirstSync()` resolves once, when the first complete sync finishes. A checkpoint request can be created at any time after that, and confirms that the local database has caught up with the server as of the moment you created it. @@ -87,11 +93,11 @@ A request remains valid across a disconnect. After reconnecting with `.requests( ## Relationship to Local Writes -When `.requests()` mode is enabled, the PowerSync Client SDK also uses checkpoint request IDs internally after it finishes uploading the local write queue. This preserves PowerSync's normal consistency guarantee without requiring you to call `requestCheckpoint()` for each local write. +PowerSync never applies a checkpoint while local writes are waiting to upload, so sync cannot revert your own pending changes. When `.requests()` mode is enabled, the PowerSync Client SDK maintains this guarantee with checkpoint requests: each time it finishes uploading the local write queue, it internally creates a request that captures a source position from after the upload completed. You do not need to call `requestCheckpoint()` for your own writes. -You can create an explicit checkpoint request while local writes are waiting to upload. PowerSync does not apply the requested checkpoint while those writes are pending. After the upload queue is empty, the PowerSync Client SDK creates another checkpoint request as its upload target. If the explicit request was created first, this upload target has a newer request ID, supersedes the earlier request, and captures a source position after the upload has completed. +`waitForSync()` considers a request complete when the same or a newer checkpoint request has been applied locally. This makes explicit requests safe to combine with pending writes. If you create a request while local writes are waiting to upload, it is not applied while they are pending; once the upload queue empties, the SDK's newer internal request supersedes it and captures a source position from after the upload. If you create the request after the SDK's internal request instead, it captures an even later position. -`waitForSync()` considers the explicit request complete when the same or a newer checkpoint request has been applied locally. If the explicit request is created after the upload target instead, it captures an even later source position. You can therefore write locally and wait for the uploaded result to return through sync: +In both cases, waiting on the request also waits for the pending upload and for its result to sync back. You can therefore write locally and wait for the uploaded result to return through sync: ```swift try await database.execute( From 29b07443a5429d7ce9737bf5ef4c91e84cb3cbf2 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Wed, 5 Aug 2026 12:04:47 +0200 Subject: [PATCH 09/12] Additional wording polish --- client-sdks/advanced/checkpoint-requests.mdx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index 85490156..afd0f85e 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -1,15 +1,13 @@ --- title: "Checkpoint Requests (Alpha)" sidebarTitle: "Checkpoint Requests" -description: "Confirm that the local database is fully up to date with the server, covering pending uploads and the latest server changes." +description: "Confirm that sync has completed and data on the device is currently up to date." --- -PowerSync syncs continuously in the background, and [sync status](/client-sdks/usage-examples#accessing-powersync-connection-status-information) fields like `connected`, `downloading`, and `hasSynced` tell you that this is happening. What they cannot tell you is whether the data on the device is up to date with the server right now. +PowerSync syncs continuously in the background and [SyncStatus](/client-sdks/usage-examples#accessing-powersync-connection-status-information) fields like `downloading` and `hasSynced` tell you that this is happening. However, none of these fields tell you whether the data on the device is up to date with the server _right now_. Checkpoint requests let you confirm this: you request a marker of the current server state, then wait until the local database has caught up to it. Waiting covers uploads as well as downloads: a request created after local writes confirms that those writes have been uploaded and their results have synced back (see [Relationship to Local Writes](#relationship-to-local-writes)). -This is also the API to use when your app needs to "force a sync." While connected, PowerSync already syncs as quickly as it can, so a checkpoint request does not make sync run faster. But it tells you when the device has caught up, which is usually what a forced sync is meant to achieve. - Checkpoint requests are an alpha API and may change. Client support is currently available only for Swift and requires PowerSync Service version 1.24.0 or later. Support for other SDKs is planned. @@ -18,13 +16,12 @@ Example use cases include: - Critical operations: confirm that pending local writes have uploaded and the latest server data has downloaded before a user starts a work session, or before the app performs a sensitive operation. - Pull-to-refresh: resolve the refresh indicator only once the device has caught up with the server, instead of hiding it after an arbitrary delay. -- Push notifications: when a user opens a notification, wait until the data it refers to has synced before rendering the screen. +- Links and push notifications: when a user opens a link or notification, wait until the data it refers to has synced before rendering the screen. - Backend processing: after your backend finishes a job and writes the result to the source database, know when that result is available locally. - App startup or foregrounding: show a "syncing latest changes" state that ends exactly when the device is caught up. - Background refresh: connect, wait for a checkpoint request to sync, then disconnect again, for example from a scheduled background task. Checkpoint requests are related to, but different from, [waitForFirstSync()](/client-sdks/usage-examples#wait-for-the-initial-sync-to-complete). `waitForFirstSync()` resolves once, when the first complete sync finishes. A checkpoint request can be created at any time after that, and confirms that the local database has caught up with the server as of the moment you created it. - ## How Checkpoint Requests Work During normal sync, the PowerSync Service groups changes from the source database into checkpoints, and the PowerSync Client SDK applies each checkpoint to the local database as a single consistent unit. This happens continuously in the background. Checkpoint requests add a way to point at a specific checkpoint: one that reflects server state at or after the moment you asked. From d2126b5c0b0f69d673ad1c5dd1c0914ba83876f8 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Wed, 5 Aug 2026 12:19:22 +0200 Subject: [PATCH 10/12] Polish --- client-sdks/advanced/checkpoint-requests.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index afd0f85e..ac3ce899 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -1,6 +1,6 @@ --- title: "Checkpoint Requests (Alpha)" -sidebarTitle: "Checkpoint Requests" +sidebarTitle: "Confirm Sync Completion (Checkpoint Requests)" description: "Confirm that sync has completed and data on the device is currently up to date." --- @@ -22,6 +22,7 @@ Example use cases include: - Background refresh: connect, wait for a checkpoint request to sync, then disconnect again, for example from a scheduled background task. Checkpoint requests are related to, but different from, [waitForFirstSync()](/client-sdks/usage-examples#wait-for-the-initial-sync-to-complete). `waitForFirstSync()` resolves once, when the first complete sync finishes. A checkpoint request can be created at any time after that, and confirms that the local database has caught up with the server as of the moment you created it. + ## How Checkpoint Requests Work During normal sync, the PowerSync Service groups changes from the source database into checkpoints, and the PowerSync Client SDK applies each checkpoint to the local database as a single consistent unit. This happens continuously in the background. Checkpoint requests add a way to point at a specific checkpoint: one that reflects server state at or after the moment you asked. @@ -107,13 +108,13 @@ try await checkpoint.waitForSync(timeout: 30) // The pending write has uploaded and its source state has synced locally. ``` -This behavior relies on `uploadData()` returning only after your backend has committed the uploaded changes to the source database. See [Writing Client Changes](/handling-writes/writing-client-changes#why-must-my-write-endpoint-be-synchronous) for this requirement. +This behavior relies on `uploadData()` returning only after your backend has committed the uploaded changes to the source database. See [Writing Client Changes](/handling-writes/writing-client-changes#why-must-my-write-endpoint-be-synchronous) for the reason your write endpoint must be synchronous. The upload response remains the authority on whether your backend accepted, changed, or rejected a mutation. A synced checkpoint request only confirms that PowerSync and the local database have progressed through the source database position the request captured. ## Asynchronous Upload Backends -The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, use the same source-side behavior described in [Custom Write Checkpoints](/handling-writes/custom-write-checkpoints). That page uses the previous name for checkpoint acknowledgements. +The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, use the same source-side behavior described in [Custom Write Checkpoints](/handling-writes/custom-write-checkpoints). The difference on the client is that the PowerSync Client SDK generates the checkpoint request ID and sends it to your backend through `CustomCheckpointRequestConnector`. From 5f7d08d46ae975e3fa6f07427fdff3e323460d06 Mon Sep 17 00:00:00 2001 From: Benita Volkmann Date: Wed, 5 Aug 2026 12:20:47 +0200 Subject: [PATCH 11/12] Wording --- client-sdks/advanced/checkpoint-requests.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index ac3ce899..ede27d6a 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -16,7 +16,7 @@ Example use cases include: - Critical operations: confirm that pending local writes have uploaded and the latest server data has downloaded before a user starts a work session, or before the app performs a sensitive operation. - Pull-to-refresh: resolve the refresh indicator only once the device has caught up with the server, instead of hiding it after an arbitrary delay. -- Links and push notifications: when a user opens a link or notification, wait until the data it refers to has synced before rendering the screen. +- Data availability: when a user opens a link or notification, wait until the data it refers to has synced before rendering the screen. - Backend processing: after your backend finishes a job and writes the result to the source database, know when that result is available locally. - App startup or foregrounding: show a "syncing latest changes" state that ends exactly when the device is caught up. - Background refresh: connect, wait for a checkpoint request to sync, then disconnect again, for example from a scheduled background task. From 89e62b693231acef3c12e0f1ed7615975ae85c49 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Aug 2026 15:42:06 +0200 Subject: [PATCH 12/12] add note about custom checkpoint requests and event definitions. --- client-sdks/advanced/checkpoint-requests.mdx | 4 +++- handling-writes/custom-write-checkpoints.mdx | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client-sdks/advanced/checkpoint-requests.mdx b/client-sdks/advanced/checkpoint-requests.mdx index ede27d6a..9cc94c18 100644 --- a/client-sdks/advanced/checkpoint-requests.mdx +++ b/client-sdks/advanced/checkpoint-requests.mdx @@ -114,7 +114,9 @@ The upload response remains the authority on whether your backend accepted, chan ## Asynchronous Upload Backends -The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, use the same source-side behavior described in [Custom Write Checkpoints](/handling-writes/custom-write-checkpoints). +The managed flow assumes that `uploadData()` returns only after your backend commits the uploaded changes to the source database. If your backend queues uploads for later processing, use custom checkpoint requests. This feature is available for customers on [Team and Enterprise](https://www.powersync.com/pricing) plans. + +Follow the [Custom Write Checkpoints source-side setup](/handling-writes/custom-write-checkpoints#sync-rules-requirements), including its `checkpoint_requests` event definition. The `checkpoint` column stores the checkpoint request ID generated by the client. The difference on the client is that the PowerSync Client SDK generates the checkpoint request ID and sends it to your backend through `CustomCheckpointRequestConnector`. diff --git a/handling-writes/custom-write-checkpoints.mdx b/handling-writes/custom-write-checkpoints.mdx index 1fd66423..ded19ad3 100644 --- a/handling-writes/custom-write-checkpoints.mdx +++ b/handling-writes/custom-write-checkpoints.mdx @@ -93,9 +93,20 @@ create publication powersync for table public.lists, public.todos, public.checkp ### Sync Rules Requirements -You need to enable the `write_checkpoints` sync event in your Sync Rules. This event should map the rows from the `checkpoints` table to the `CheckpointPayload` payload. +For clients using the [Checkpoint Requests](/client-sdks/advanced/checkpoint-requests) API, enable the `checkpoint_requests` event in your sync configuration. This event maps rows from the `checkpoints` table to the `CheckpointPayload` payload. -```YAML +```yaml +event_definitions: + # Note this event is only supported for customers on [Team and Enterprise](https://www.powersync.com/pricing) plans. + checkpoint_requests: + payloads: + # This defines where the replicated custom Write Checkpoints should be extracted from + - SELECT user_id, checkpoint, client_id FROM checkpoints +``` + +Use the `write_checkpoints` event only for clients using the legacy Custom Write Checkpoints flow: + +```yaml # sync-rules.yaml # Register the custom write_checkpoints event