From b09c95cb5ce7df55d0b4eb8ae9204796dfc3929c Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Tue, 4 Feb 2025 11:27:19 +0200 Subject: [PATCH 1/2] chore: change examples to use cursor get by column name: --- .../kotlin-multiplatform.mdx | 6 +- .../kotlin-multiplatform/usage-examples.mdx | 6 +- client-sdk-references/swift.mdx | 24 +++---- migration-guides/mongodb-atlas.mdx | 64 +++++++++---------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/client-sdk-references/kotlin-multiplatform.mdx b/client-sdk-references/kotlin-multiplatform.mdx index 59795a3..888eb4b 100644 --- a/client-sdk-references/kotlin-multiplatform.mdx +++ b/client-sdk-references/kotlin-multiplatform.mdx @@ -242,9 +242,9 @@ fun watchCustomers(): Flow> { // TODO: implement your UI based on the result set return database.watch("SELECT * FROM customers", mapper = { cursor -> User( - id = cursor.getString(0)!!, - name = cursor.getString(1)!!, - email = cursor.getString(2)!! + id = cursor.getString("id")!!, + name = cursor.getString("name")!!, + email = cursor.getString("email")!! ) }) } diff --git a/client-sdk-references/kotlin-multiplatform/usage-examples.mdx b/client-sdk-references/kotlin-multiplatform/usage-examples.mdx index 599b80b..bfe81ab 100644 --- a/client-sdk-references/kotlin-multiplatform/usage-examples.mdx +++ b/client-sdk-references/kotlin-multiplatform/usage-examples.mdx @@ -30,9 +30,9 @@ fun watchCustomers(): Flow> { // TODO: implement your UI based on the result set return database.watch("SELECT * FROM customers", mapper = { cursor -> User( - id = cursor.getString(0)!!, - name = cursor.getString(1)!!, - email = cursor.getString(2)!! + id = cursor.getString("id")!!, + name = cursor.getString("name")!!, + email = cursor.getString("email")!! ) }) } diff --git a/client-sdk-references/swift.mdx b/client-sdk-references/swift.mdx index c27d9a7..8951f99 100644 --- a/client-sdk-references/swift.mdx +++ b/client-sdk-references/swift.mdx @@ -249,10 +249,10 @@ func getList(_ id: String) async throws { parameters: [id], mapper: { cursor in ListContent( - id: cursor.getString(index: 0)!, - name: cursor.getString(index: 1)!, - createdAt: cursor.getString(index: 2)!, - ownerId: cursor.getString(index: 3)! + id: cursor.getString(name: "id")!, + name: cursor.getString(name: "name")!, + createdAt: cursor.getString(name: "created_at")!, + ownerId: cursor.getString(name: "owner_id")! ) } ) @@ -271,10 +271,10 @@ func getLists() async throws { parameters: [], mapper: { cursor in ListContent( - id: cursor.getString(index: 0)!, - name: cursor.getString(index: 1)!, - createdAt: cursor.getString(index: 2)!, - ownerId: cursor.getString(index: 3)! + id: cursor.getString(name: "id")!, + name: cursor.getString(name: "name")!, + createdAt: cursor.getString(name: "created_at")!, + ownerId: cursor.getString(name: "owner_id")! ) } ) @@ -293,10 +293,10 @@ func watchLists(_ callback: @escaping (_ lists: [ListContent]) -> Void ) async { parameters: [], mapper: { cursor in ListContent( - id: cursor.getString(index: 0)!, - name: cursor.getString(index: 1)!, - createdAt: cursor.getString(index: 2)!, - ownerId: cursor.getString(index: 3)! + id: cursor.getString(name: "id")!, + name: cursor.getString(name: "name")!, + createdAt: cursor.getString(name: "created_at")!, + ownerId: cursor.getString(name: "owner_id")! ) } ) { diff --git a/migration-guides/mongodb-atlas.mdx b/migration-guides/mongodb-atlas.mdx index ab63b1b..500cb00 100644 --- a/migration-guides/mongodb-atlas.mdx +++ b/migration-guides/mongodb-atlas.mdx @@ -48,7 +48,7 @@ Here is a quick overview of the resulting PowerSync architecture: * **PowerSync Client SDKs** use **SQLite** under the hood. Even though MongoDB is a “NoSQL” document database, PowerSync’s use of SQLite works well with MongoDB, since the [PowerSync protocol](/architecture/powersync-protocol) is schemaless (it syncs schemaless JSON data) and we dynamically apply a [client-side schema](/installation/client-side-setup/define-your-schema) to the data in SQLite using SQLite views. Client-side queries can be written in SQL or you can make use of an ORM (we provide a few [ORM integrations](https://www.powersync.com/blog/using-orms-with-powersync)) * **Reads vs Writes**: PowerSync handles syncing of reads differently from writes. * **Reads**: The PowerSync Service connects to your MongoDB database and replicates data in real-time to PowerSync clients. Reads are configured using PowerSync’s [“Sync Rules”](/usage/sync-rules/). Sync Rules are more flexible than MongoDB Realm Flexible Sync, but are defined on the server-side, not on the client-side. - * **Writes**: The client-side application can perform writes directly on the local SQLite database. The writes are also automatically placed into an upload queue by the PowerSync Client SDK. The SDK then uses a developer-defined `uploadData()` function to manage the uploading of those writes sequentially to the backend. + * **Writes**: The client-side application can perform writes directly on the local SQLite database. The writes are also automatically placed into an upload queue by the PowerSync Client SDK. The SDK then uses a developer-defined `uploadData()` function to manage the uploading of those writes sequentially to the backend. * **Authorization**: Authorization is controlled separately for reads vs. writes. * **Reads**: The [Sync Rules](/usage/sync-rules/) control which users can access which data. * **Writes**: The backend controls authorization for how users can modify data. @@ -59,7 +59,7 @@ Here is a quick overview of the resulting PowerSync architecture: ## Migration Steps -Follow the steps below to migrate a MongoDB Atlas Device Sync app to PowerSync. +Follow the steps below to migrate a MongoDB Atlas Device Sync app to PowerSync. ### 1. Remove Realm @@ -67,13 +67,13 @@ Before adding PowerSync, remove MongoDB Realm from your project. This includes u ### 2. Create PowerSync account and instance -To get started quickly with PowerSync, sign up for a free PowerSync Cloud account [here](https://accounts.journeyapps.com/portal/powersync-signup?s=mongodb-migration-guide). +To get started quickly with PowerSync, sign up for a free PowerSync Cloud account [here](https://accounts.journeyapps.com/portal/powersync-signup?s=mongodb-migration-guide). It is also possible to self-host PowerSync. An end-to-end demo app using Docker Compose is available [here](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs-mongodb). ### 3. Connect PowerSync to MongoDB -Once your account is set up, [create a new PowerSync instance](/installation/database-connection#create-a-powersync-cloud-instance) and configure the instance to connect to your source [MongoDB database](/installation/database-connection#mongodb-beta-specifics). +Once your account is set up, [create a new PowerSync instance](/installation/database-connection#create-a-powersync-cloud-instance) and configure the instance to connect to your source [MongoDB database](/installation/database-connection#mongodb-beta-specifics). ### 4. Define Sync Rules @@ -81,19 +81,19 @@ Sync Rules allow you to control which data gets synced to which users/devices. E To get a good understanding of how Sync Rules operate, have a look at our blog post: [Sync Rules from First Principles: Partial Replication to SQLite](https://www.powersync.com/blog/sync-rules-from-first-principles-partial-replication-to-sqlite). -If you have a PowerSync Service instance set up and connected, open the `sync-rules.yaml` file associated with your PowerSync project and edit the SQL-like queries based on your database schema. Below is a simple Sync Rules example using a simple database schema. Sync Rules involve organizing data into ["buckets"](/usage/sync-rules/organize-data-into-buckets) (a bucket is a grouping of data). The example below uses a ["global bucket"](/usage/sync-rules/example-global-data) as a simple starting point — data in a "global bucket" will be synced to all users. +If you have a PowerSync Service instance set up and connected, open the `sync-rules.yaml` file associated with your PowerSync project and edit the SQL-like queries based on your database schema. Below is a simple Sync Rules example using a simple database schema. Sync Rules involve organizing data into ["buckets"](/usage/sync-rules/organize-data-into-buckets) (a bucket is a grouping of data). The example below uses a ["global bucket"](/usage/sync-rules/example-global-data) as a simple starting point — data in a "global bucket" will be synced to all users. Note that MongoDB uses “_id” as the name of the ID field in collections whereas PowerSync uses “id” in its client-side database. This is why `SELECT _id as id` should always be used in the data queries when pairing PowerSync with MongoDB. ```yaml bucket_definitions: # This is the name of the bucket, in this case the global bucket synced to all users. - global: + global: # This is the query used to determine the data in each bucket - data: + data: # Note that we select the MongoDB _id field as id - - SELECT _id as id, * FROM lists - - SELECT _id as id, * FROM todos + - SELECT _id as id, * FROM lists + - SELECT _id as id, * FROM todos ``` To filter data based on the user and other more advanced use cases, refer to the [Sync Rules documentation](/usage/sync-rules). @@ -109,13 +109,13 @@ Add PowerSync to your app project by following the instructions for the relevant The PowerSync client-side schema represents a “view” of the data synced from the PowerSync Service to the client app. No migrations are required — the schema is applied directly when the local PowerSync SQLite database is constructed. -To make this step easy for you, the [PowerSync Dashboard](/usage/tools/powersync-dashboard) allows automatically generating the client-side schema based on the Sync Rules defined for a PowerSync instance. To generate the schema, go to the [dashboard](https://powersync.journeyapps.com/), right-click on the instance, and select “Generate Client Schema”. Alternatively you can use the PowerSync [CLI](/usage/tools/cli) to generate the schema. +To make this step easy for you, the [PowerSync Dashboard](/usage/tools/powersync-dashboard) allows automatically generating the client-side schema based on the Sync Rules defined for a PowerSync instance. To generate the schema, go to the [dashboard](https://powersync.journeyapps.com/), right-click on the instance, and select “Generate Client Schema”. Alternatively you can use the PowerSync [CLI](/usage/tools/cli) to generate the schema. Here is an example of a client-side schema for PowerSync using a simple `todos` table: - ```typescript TypeScript - React Native + ```typescript TypeScript - React Native import { column, Schema, Table } from '@powersync/react-native'; const todos = new Table( @@ -127,14 +127,14 @@ Here is an example of a client-side schema for PowerSync using a simple `todos` created_by: column.text, completed_by: column.text, completed: column.integer - }, + }, { indexes: { list: ['list_id'] } } ); export const AppSchema = new Schema({ todos }); - ``` + ``` ```typescript TypeScript - Web import { column, Schema, Table } from '@powersync/web'; @@ -212,7 +212,7 @@ Here is an example of a client-side schema for PowerSync using a simple `todos` let AppSchema = Schema(todos) ``` - ```dart Flutter + ```dart Flutter import 'package:powersync/powersync.dart'; const schema = Schema(([ @@ -235,7 +235,7 @@ Here is an example of a client-side schema for PowerSync using a simple `todos` A few things to note regarding the PowerSync client-side schema: -* The schema does not explicitly specify an `id` column, since the PowerSync automatically creates an `id` column of type `text`. +* The schema does not explicitly specify an `id` column, since the PowerSync automatically creates an `id` column of type `text`. * SQLite has very simple data types which are [used by](/usage/sync-rules/types#types) PowerSync. * For MongoDB specific data types, refer to [MongoDB Type Mapping](/usage/sync-rules/types#mongodb-beta-type-mapping). * PowerSync also supports [syncing attachments or files](/usage/use-case-examples/attachments-files#attachments-files) using helper packages. @@ -247,7 +247,7 @@ Now that we have our Sync Rules and client-side schema defined, we can instantia - ```typescript TypeScript - React Native + ```typescript TypeScript - React Native import { PowerSyncDatabase } from '@powersync/react-native'; import { Connector } from './Connector'; import { AppSchema } from './Schema'; @@ -263,7 +263,7 @@ Now that we have our Sync Rules and client-side schema defined, we can instantia const connector = new Connector(); db.connect(connector); }; - ``` + ``` ```typescript TypeScript - Web import { PowerSyncDatabase } from '@powersync/web'; @@ -322,7 +322,7 @@ Now that we have our Sync Rules and client-side schema defined, we can instantia await db.connect(connector: connector); ``` - ```dart Flutter + ```dart Flutter import 'package:powersync/powersync.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart'; @@ -340,7 +340,7 @@ Now that we have our Sync Rules and client-side schema defined, we can instantia ### 8. Reading and writing data -Reading data in the application which uses PowerSync is very simple: we use SQLite syntax to query data in our local database. +Reading data in the application which uses PowerSync is very simple: we use SQLite syntax to query data in our local database. @@ -350,7 +350,7 @@ Reading data in the application which uses PowerSync is very simple: we use SQLi const results = await db.getAll('SELECT * FROM todos'); return results; } - ``` + ``` ```java Kotlin // Reading Data @@ -367,19 +367,19 @@ Reading data in the application which uses PowerSync is very simple: we use SQLi sql: "SELECT * FROM todos", mapper: { cursor in TodoContent( - list_id: cursor.getString(index: 0)!, - description: cursor.getString(index: 1)!, - completed: cursor.getString(index: 2)!, - created_by: cursor.getString(index: 3)!, - completed_by: cursor.getString(index: 4)!, - completed_at: cursor.getString(index: 5)! + list_id: cursor.getString(name: "list_id")!, + description: cursor.getString(name: "description")!, + completed: cursor.getBoolean(name: "completed")!, + created_by: cursor.getString(name: "created_by")!, + completed_by: cursor.getString(name: "completed_by")!, + completed_at: cursor.getString(name: "completed_at")! ) } ) } ``` - ```dart Flutter + ```dart Flutter /// Reading Data Future getTodos() async { final result = await db.get('SELECT * FROM todos'); @@ -398,7 +398,7 @@ The same applies to writing data: `INSERT`, `UPDATE` and `DELETE` statements are export const insertTodo = async (listId: string, description: string) => { await db.execute('INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)', [listId, description]); } - ``` + ``` ```java Kotlin // Writing Data @@ -422,7 +422,7 @@ The same applies to writing data: `INSERT`, `UPDATE` and `DELETE` statements are } ``` - ```dart Flutter + ```dart Flutter /// Writing Data await db.execute( 'INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)', @@ -452,7 +452,7 @@ There are two options: #### Using PowerSync’s serverless cloud functions -PowerSync provides serverless cloud functions for backend functionality, with a template available for MongoDB. See the [step-by-step instructions](/usage/tools/cloudcode) on how to use the template. The template can be customized, or it can be used as-is. +PowerSync provides serverless cloud functions for backend functionality, with a template available for MongoDB. See the [step-by-step instructions](/usage/tools/cloudcode) on how to use the template. The template can be customized, or it can be used as-is. The template provides [turnkey conflict resolution](https://www.powersync.com/blog/turnkey-backend-functionality-conflict-resolution-for-powersync#turnkey-conflict-resolution) which roughly matches the built-in conflict resolution behavior provided by MongoDB Atlas Device Sync. @@ -465,7 +465,7 @@ For more information, see our blog post: [Turnkey Backend Functionality & Confli This option gives you complete control over the backend. The simplest backend implementation is to simply apply writes to MongoDB as they are received, which results in a last-write-wins conflict resolution strategy (same as the “turnkey backend functionality” option above). See [Writing Client Changes](/installation/app-backend-setup/writing-client-changes) for more details. -On the client-side, you need to wire up the `uploadData()` function in the “backend connector” to use your own backend API. The [App Backend Setup](/installation/app-backend-setup) section of our docs provides step-by-step instructions for this. +On the client-side, you need to wire up the `uploadData()` function in the “backend connector” to use your own backend API. The [App Backend Setup](/installation/app-backend-setup) section of our docs provides step-by-step instructions for this. Also see the section on [how to set up a simple backend API](https://www.powersync.com/blog/migrating-a-mongodb-atlas-device-sync-app-to-powersync#backend-api-setup) in our practical MongoDB migration [example](https://www.powersync.com/blog/migrating-a-mongodb-atlas-device-sync-app-to-powersync) on our blog. @@ -474,4 +474,4 @@ We also have [example backend implementations](/resources/demo-apps-example-proj ## Questions? Need help? -[Get in touch](https://www.powersync.com/contact) with us. \ No newline at end of file +[Get in touch](https://www.powersync.com/contact) with us. From ac643edabe454632f95bf929ca14711bb6db0789 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Thu, 6 Feb 2025 14:11:06 +0200 Subject: [PATCH 2/2] docs: update implementations --- .../kotlin-multiplatform.mdx | 6 ++--- .../kotlin-multiplatform/usage-examples.mdx | 6 ++--- client-sdk-references/swift.mdx | 26 +++++++++---------- migration-guides/mongodb-atlas.mdx | 12 ++++----- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/client-sdk-references/kotlin-multiplatform.mdx b/client-sdk-references/kotlin-multiplatform.mdx index 888eb4b..331246e 100644 --- a/client-sdk-references/kotlin-multiplatform.mdx +++ b/client-sdk-references/kotlin-multiplatform.mdx @@ -242,9 +242,9 @@ fun watchCustomers(): Flow> { // TODO: implement your UI based on the result set return database.watch("SELECT * FROM customers", mapper = { cursor -> User( - id = cursor.getString("id")!!, - name = cursor.getString("name")!!, - email = cursor.getString("email")!! + id = cursor.getString("id"), + name = cursor.getString("name"), + email = cursor.getString("email") ) }) } diff --git a/client-sdk-references/kotlin-multiplatform/usage-examples.mdx b/client-sdk-references/kotlin-multiplatform/usage-examples.mdx index bfe81ab..b1fbffd 100644 --- a/client-sdk-references/kotlin-multiplatform/usage-examples.mdx +++ b/client-sdk-references/kotlin-multiplatform/usage-examples.mdx @@ -30,9 +30,9 @@ fun watchCustomers(): Flow> { // TODO: implement your UI based on the result set return database.watch("SELECT * FROM customers", mapper = { cursor -> User( - id = cursor.getString("id")!!, - name = cursor.getString("name")!!, - email = cursor.getString("email")!! + id = cursor.getString("id"), + name = cursor.getString("name"), + email = cursor.getString("email") ) }) } diff --git a/client-sdk-references/swift.mdx b/client-sdk-references/swift.mdx index 8951f99..98dda6c 100644 --- a/client-sdk-references/swift.mdx +++ b/client-sdk-references/swift.mdx @@ -6,7 +6,7 @@ sidebarTitle: "Overview" Refer to the powersync-swift repo on GitHub. - + A full API Reference for this SDK is not yet available. This is planned for the V1 release. @@ -249,10 +249,10 @@ func getList(_ id: String) async throws { parameters: [id], mapper: { cursor in ListContent( - id: cursor.getString(name: "id")!, - name: cursor.getString(name: "name")!, - createdAt: cursor.getString(name: "created_at")!, - ownerId: cursor.getString(name: "owner_id")! + id: try cursor.getString(name: "id")!, + name: try cursor.getString(name: "name")!, + createdAt: try cursor.getString(name: "created_at")!, + ownerId: try cursor.getString(name: "owner_id")! ) } ) @@ -271,10 +271,10 @@ func getLists() async throws { parameters: [], mapper: { cursor in ListContent( - id: cursor.getString(name: "id")!, - name: cursor.getString(name: "name")!, - createdAt: cursor.getString(name: "created_at")!, - ownerId: cursor.getString(name: "owner_id")! + id: try cursor.getString(name: "id")!, + name: try cursor.getString(name: "name")!, + createdAt: try cursor.getString(name: "created_at")!, + ownerId: try cursor.getString(name: "owner_id")! ) } ) @@ -293,10 +293,10 @@ func watchLists(_ callback: @escaping (_ lists: [ListContent]) -> Void ) async { parameters: [], mapper: { cursor in ListContent( - id: cursor.getString(name: "id")!, - name: cursor.getString(name: "name")!, - createdAt: cursor.getString(name: "created_at")!, - ownerId: cursor.getString(name: "owner_id")! + id: try cursor.getString(name: "id")!, + name: try cursor.getString(name: "name")!, + createdAt: try cursor.getString(name: "created_at")!, + ownerId: try cursor.getString(name: "owner_id")! ) } ) { diff --git a/migration-guides/mongodb-atlas.mdx b/migration-guides/mongodb-atlas.mdx index 500cb00..7b9d9bd 100644 --- a/migration-guides/mongodb-atlas.mdx +++ b/migration-guides/mongodb-atlas.mdx @@ -367,12 +367,12 @@ Reading data in the application which uses PowerSync is very simple: we use SQLi sql: "SELECT * FROM todos", mapper: { cursor in TodoContent( - list_id: cursor.getString(name: "list_id")!, - description: cursor.getString(name: "description")!, - completed: cursor.getBoolean(name: "completed")!, - created_by: cursor.getString(name: "created_by")!, - completed_by: cursor.getString(name: "completed_by")!, - completed_at: cursor.getString(name: "completed_at")! + list_id: try cursor.getString(name: "list_id"), + description: try cursor.getString(name: "description"), + completed: try cursor.getBooleanOptional(name: "completed"), + created_by: try cursor.getString(name: "created_by"), + completed_by: try cursor.getStringOptional(name: "completed_by"), + completed_at: try cursor.getStringOptional(name: "completed_at") ) } )