diff --git a/client-sdks/reference/capacitor.mdx b/client-sdks/reference/capacitor.mdx index a1c2f69d..3bcfcb98 100644 --- a/client-sdks/reference/capacitor.mdx +++ b/client-sdks/reference/capacitor.mdx @@ -212,7 +212,7 @@ export class Connector { // Implement uploadData to send local changes to your backend service. // You can omit this method if you only want to sync data from the database to the client - // See example implementation here: https://docs.powersync.com/client-sdks/reference/javascript-web#3-integrate-with-your-backend + // See example implementation here: https://docs.powersync.com/client-sdks/usage-examples#send-changes-in-local-data-to-your-backend-service } } ``` diff --git a/client-sdks/reference/flutter.mdx b/client-sdks/reference/flutter.mdx index 588a2bca..7b7b97cd 100644 --- a/client-sdks/reference/flutter.mdx +++ b/client-sdks/reference/flutter.mdx @@ -215,7 +215,7 @@ class MyBackendConnector extends PowerSyncBackendConnector { // Implement uploadData to send local changes to your backend service // You can omit this method if you only want to sync data from the server to the client - // See example implementation here: https://docs.powersync.com/client-sdks/reference/flutter#3-integrate-with-your-backend + // See example implementation here: https://docs.powersync.com/client-sdks/usage-examples#send-changes-in-local-data-to-your-backend-service @override Future uploadData(PowerSyncDatabase database) async { // This function is called whenever there is data to upload, whether the diff --git a/client-sdks/reference/javascript-web.mdx b/client-sdks/reference/javascript-web.mdx index e7069c25..0fc72dd2 100644 --- a/client-sdks/reference/javascript-web.mdx +++ b/client-sdks/reference/javascript-web.mdx @@ -207,7 +207,7 @@ export class Connector { // Implement uploadData to send local changes to your backend service. // You can omit this method if you only want to sync data from the database to the client - // See example implementation here: https://docs.powersync.com/client-sdks/reference/javascript-web#3-integrate-with-your-backend + // See example implementation here: https://docs.powersync.com/client-sdks/usage-examples#send-changes-in-local-data-to-your-backend-service } } ``` diff --git a/client-sdks/reference/node.mdx b/client-sdks/reference/node.mdx index 9b1592e0..812e922d 100644 --- a/client-sdks/reference/node.mdx +++ b/client-sdks/reference/node.mdx @@ -128,7 +128,7 @@ export class Connector implements PowerSyncBackendConnector { // Implement uploadData to send local changes to your backend service. // You can omit this method if you only want to sync data from the database to the client - // See example implementation here: https://docs.powersync.com/client-sdks/reference/javascript-web#3-integrate-with-your-backend + // See example implementation here: https://docs.powersync.com/client-sdks/usage-examples#send-changes-in-local-data-to-your-backend-service } } ``` diff --git a/client-sdks/reference/react-native-and-expo.mdx b/client-sdks/reference/react-native-and-expo.mdx index 62afe990..8858d506 100644 --- a/client-sdks/reference/react-native-and-expo.mdx +++ b/client-sdks/reference/react-native-and-expo.mdx @@ -176,7 +176,7 @@ export class Connector implements PowerSyncBackendConnector { /** * Implement uploadData to send local changes to your backend service. * You can omit this method if you only want to sync data from the database to the client - * See example implementation here:https://docs.powersync.com/client-sdks/reference/react-native-and-expo#3-integrate-with-your-backend + * See example implementation here: https://docs.powersync.com/client-sdks/usage-examples#send-changes-in-local-data-to-your-backend-service */ async uploadData(database: AbstractPowerSyncDatabase) { diff --git a/client-sdks/usage-examples.mdx b/client-sdks/usage-examples.mdx index 3e028df9..330cbc9f 100644 --- a/client-sdks/usage-examples.mdx +++ b/client-sdks/usage-examples.mdx @@ -551,11 +551,57 @@ import JavaScriptCallbackWatch from '/snippets/basic-watch-query-javascript-call - Example not yet available. + 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 + // Implement the uploadData method in your backend connector + async function uploadData(database) { + const batch = await database.getCrudBatch(); + if (batch === null) return; + + for (const op of batch.crud) { + switch (op.op) { + case '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(); + } + ``` - Example not yet available. + 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 + // Implement the uploadData method in your backend connector + async function uploadData(database) { + const batch = await database.getCrudBatch(); + if (batch === null) return; + + for (const op of batch.crud) { + switch (op.op) { + case '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(); + } + ``` diff --git a/maintenance-ops/deploying-schema-changes.mdx b/maintenance-ops/deploying-schema-changes.mdx index 400db7c3..a8cdcec3 100644 --- a/maintenance-ops/deploying-schema-changes.mdx +++ b/maintenance-ops/deploying-schema-changes.mdx @@ -34,9 +34,9 @@ See the appropriate subsections below for details on the various scenarios. - Pass in a "`schema_version`" or similar parameter from the client, and use this in Sync Rules to use either the old or new table name in the data queries. + Define a versioned stream alongside the old one, or pass in a `schema_version` or similar parameter from the client and use it to choose either the old or new table name in the queries. - See this section for details: + See this section for details on both approaches: [Multiple Client Versions](/sync/advanced/multiple-client-versions) diff --git a/maintenance-ops/implementing-schema-changes.mdx b/maintenance-ops/implementing-schema-changes.mdx index 816b948a..d2f263c8 100644 --- a/maintenance-ops/implementing-schema-changes.mdx +++ b/maintenance-ops/implementing-schema-changes.mdx @@ -13,7 +13,7 @@ The [client-side schema](/intro/setup-guide#define-your-client-side-schema) is j The developer is responsible for keeping client-side schema changes backwards-compatible with older versions of client apps. PowerSync has some functionality to assist with this: -1. [Different stream queries](/sync/advanced/multiple-client-versions) can be applied based on [connection parameters](/sync/streams/parameters#connection-parameters) such as client version. (In Sync Rules, this uses [client parameters](/sync/rules/client-parameters).) +1. [Versioned streams](/sync/advanced/multiple-client-versions) can serve different data to different client versions, either by defining a separate stream per version or by filtering on [connection parameters](/sync/streams/parameters#connection-parameters) such as client version. (In Sync Rules, this uses [client parameters](/sync/rules/client-parameters).) 2. Stream queries can apply simple data transformations to keep data in a format compatible with older clients, for example by aliasing or casting columns. (In Sync Rules, this is done via [data query expressions](/sync/rules/data-queries).) diff --git a/sync/advanced/multiple-client-versions.mdx b/sync/advanced/multiple-client-versions.mdx index a34f357a..1c1cb9f3 100644 --- a/sync/advanced/multiple-client-versions.mdx +++ b/sync/advanced/multiple-client-versions.mdx @@ -3,23 +3,53 @@ title: "Multiple Client Versions" description: "Handle multiple client app versions that require different output schemas from Sync Streams." --- -When schema changes are additive, old clients would just ignore the new tables and columns, and no special handling is required. However, in some cases, the schema changes may be more drastic and may need separate Sync Streams (or Sync Rules) based on the client version. +When schema changes are additive, old clients ignore the new tables and columns, and no special handling is required. More drastic changes, such as renaming tables or changing a table's structure, can break older app versions that are still in use. In these cases, define separate versions of the affected [Sync Streams](/sync/streams/overview) so that each client version receives the tables and columns it expects. -To distinguish between client versions, clients can pass version information to the PowerSync Service. In [Sync Streams](/sync/streams/overview), these are called connection parameters (accessed via `connection.parameter()`). In legacy [Sync Rules](/sync/rules/overview), these are called [client parameters](/sync/rules/client-parameters). +## Versioning by Stream Name -Example to use different table names based on the client's `schema_version`: +With Sync Streams, the most convenient approach is usually to define a new stream alongside the old one. New app versions subscribe to the new stream, while older app versions continue subscribing to the old one. + +For example, suppose a new app version changes the structure of the `assets` table in its [client-side schema](/intro/setup-guide#define-your-client-side-schema), defining it as `assets_v2`, while older app versions still define `assets`. Define a new stream alongside the existing one, using an alias to map the source `assets` table to the new client-side name: + +```yaml +streams: + # Old stream, kept for backward compatibility with older app versions. + # Remove it once those versions are no longer in use. + user_assets: + query: SELECT * FROM assets WHERE user_id = auth.user_id() + + # New app versions subscribe to this stream. + # The alias maps the table to the new client-side name. + user_assets_v2: + query: SELECT * FROM assets AS assets_v2 WHERE user_id = auth.user_id() +``` + +```js +// New app versions subscribe to the new stream +const subscription = await db.syncStream('user_assets_v2').subscribe(); +``` + +Once the older app versions are no longer in use, remove the old stream from your configuration and deploy the change. + +## Versioning with Connection Parameters + +Alternatively, clients can pass their version to the PowerSync Service as a [connection parameter](/sync/streams/parameters#connection-parameters), and stream queries filter on it so each client only receives data for its version. This approach is useful when your streams are auto-subscribed: auto-subscribed streams sync to every client on connect, so clients cannot select a stream version by name. In legacy [Sync Rules](/sync/rules/overview), connection parameters are called [client parameters](/sync/rules/client-parameters). + +The example below implements the same `assets` use case, with both stream versions auto-subscribed and filtered by a `schema_version` connection parameter: ```yaml # Client passes connection params: {"schema_version": } streams: - assets_v1: - query: SELECT * FROM assets AS assets_v1 + user_assets: + auto_subscribe: true + query: SELECT * FROM assets WHERE user_id = auth.user_id() AND connection.parameter('schema_version') = '1' - assets_v2: + user_assets_v2: + auto_subscribe: true query: SELECT * FROM assets AS assets_v2 WHERE user_id = auth.user_id() AND connection.parameter('schema_version') = '2' @@ -28,13 +58,13 @@ Example to use different table names based on the client's `schema_version`: ```yaml # Client passes in: "params": {"schema_version": } - assets_v1: + user_assets: parameters: SELECT request.user_id() AS user_id WHERE request.parameters() ->> 'schema_version' = '1' data: - - SELECT * FROM assets AS assets_v1 WHERE user_id = bucket.user_id + - SELECT * FROM assets WHERE user_id = bucket.user_id - assets_v2: + user_assets_v2: parameters: SELECT request.user_id() AS user_id WHERE request.parameters() ->> 'schema_version' = '2' data: diff --git a/sync/streams/overview.mdx b/sync/streams/overview.mdx index a80b1b81..c2439495 100644 --- a/sync/streams/overview.mdx +++ b/sync/streams/overview.mdx @@ -6,9 +6,9 @@ sidebarTitle: "Quickstart" import StreamDefinitionReference from '/snippets/stream-definition-reference.mdx'; -Instead of syncing entire tables, you tell PowerSync exactly which data each user/client can sync. You write simple SQL-like queries to define streams of data, and your client app subscribes to the streams it needs. PowerSync handles the rest, keeping data in sync in real-time and making it available offline. +With Sync Streams, you write simple SQL-like queries to define streams of data, and your client app subscribes to the streams it needs. This enables _partial sync_: each client syncs only the relevant subset of data, instead of the entire database. PowerSync handles the rest, keeping subscribed data synced in real-time to a client-side SQLite database, where it stays available even when the device is offline. -For example, you might create a stream that syncs only the current user's to-do items, another for shared projects they have access to, and another for reference data that everyone needs. Your app subscribes to these streams on demand, and only that data syncs to the client-side SQLite database. Offline-first apps that need all relevant data available upfront can use `auto_subscribe: true` so streams sync automatically when clients connect. +For example, you might define a stream that syncs only the current user's to-do items, another for shared projects they have access to, and another for reference data that everyone needs. Your app subscribes to these streams on demand, and only that data syncs to the device. Offline-first apps that need all relevant data available upfront can use `auto_subscribe: true` so streams sync automatically when clients connect. **Are you still using Sync Rules?** Sync Streams support everything Sync Rules do, plus more expressive queries (including JOIN support), on-demand syncing, and a simpler developer experience (e.g. React hooks that manage subscriptions automatically).