From da6f41566a978e152a9b7447b6392480578f8190 Mon Sep 17 00:00:00 2001 From: dacharyc Date: Thu, 6 Jun 2024 11:09:59 -0400 Subject: [PATCH 1/4] Draft Write Data to a Synced Database page --- ...ng-write-error-information-description.rst | 38 ++ ...ng-write-error-information-description.rst | 13 + ...ng-write-error-information-description.rst | 14 + ...ng-write-error-information-description.rst | 14 + ...ng-write-error-information-description.rst | 38 ++ ...ng-write-error-information-description.rst | 44 +++ ...e-compensating-write-error-information.rst | 14 + ...ent-data-model-and-configuration-model.rst | 44 +++ ...a-model-and-configuration-subscription.rst | 45 +++ ...e-to-synced-database-successful-writes.rst | 44 +++ ...es-dont-match-permissions-code-example.rst | 86 +++++ ...s-dont-match-subscription-code-example.rst | 85 ++++ .../configure-and-open-synced-database.txt | 5 + source/sdk/sync/write-to-synced-database.txt | 364 +++++++++++++++++- 14 files changed, 847 insertions(+), 1 deletion(-) create mode 100644 source/includes/api-details/cpp/sync/write-to-synced-realm-compensating-write-error-information-description.rst create mode 100644 source/includes/api-details/csharp/sync/write-to-synced-database-compensating-write-error-information-description.rst create mode 100644 source/includes/api-details/dart/sync/write-to-synced-database-compensating-write-error-information-description.rst create mode 100644 source/includes/api-details/javascript/sync/write-to-synced-database-compensating-write-error-information-description.rst create mode 100644 source/includes/api-details/kotlin/sync/write-to-synced-database-compensating-write-error-information-description.rst create mode 100644 source/includes/api-details/swift/sync/write-to-synced-database-compensating-write-error-information-description.rst create mode 100644 source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information.rst create mode 100644 source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-model.rst create mode 100644 source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-subscription.rst create mode 100644 source/includes/sdk-examples/sync/write-to-synced-database-successful-writes.rst create mode 100644 source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst create mode 100644 source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst diff --git a/source/includes/api-details/cpp/sync/write-to-synced-realm-compensating-write-error-information-description.rst b/source/includes/api-details/cpp/sync/write-to-synced-realm-compensating-write-error-information-description.rst new file mode 100644 index 0000000000..13cba88e63 --- /dev/null +++ b/source/includes/api-details/cpp/sync/write-to-synced-realm-compensating-write-error-information-description.rst @@ -0,0 +1,38 @@ +The :cpp-sdk:`compensating_writes_info() +` +function provides an array of ``compensating_write_error_info`` +structs that contain: + +- The ``object_name`` of the object the client attempted to write +- The ``primary_key`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the +:ref:`App Services logs `. The C++ SDK exposes this object on the +client for convenience and debugging purposes. + +The following shows an example of how you might log information +about compensating write errors: + +.. io-code-block:: + + .. input:: /examples/generated/cpp/sync-errors.snippet.get-compensating-write-error-info.cpp + :language: cpp + + .. output:: + :language: console + + A write was rejected with a compensating write error. + An object of type Item + was rejected because write to ObjectID("6557ddb0bf050934870ca0f5") in + table "Item" not allowed; object is outside of the current query view. + +- The ``Item`` in this message is ``Item`` object used in the object model. +- The ``table "Item"`` refers to the Atlas collection where this object would + sync. +- The reason ``object is outside of the current query view`` in this message + is because the query subscription was set to require the object's ``complexity`` + property to be less than or equal to ``4``. The client attempted to write an + object outside of this boundary. +- The primary key is the ``objectId`` of the specific object the client + attempted to write. diff --git a/source/includes/api-details/csharp/sync/write-to-synced-database-compensating-write-error-information-description.rst b/source/includes/api-details/csharp/sync/write-to-synced-database-compensating-write-error-information-description.rst new file mode 100644 index 0000000000..1188cbfd8b --- /dev/null +++ b/source/includes/api-details/csharp/sync/write-to-synced-database-compensating-write-error-information-description.rst @@ -0,0 +1,13 @@ +The .NET SDK exposes a ``CompensatingWrites`` field on a +:dotnet-sdk:`CompensatingWriteException `. +You can access this information through the :ref:`Sync error handler +`. + +When a ``CompensatingWriteException`` is thrown, it includes an enumerable of +:dotnet-sdk:`CompensatingWriteInfo ` +objects. Each ``CompensatingWriteInfo`` object contains properties that describe +the object type, its primary key, and reason the server performed the compensating +write. + +This information is the same information you can find in the App Services logs. +It is exposed on the client for convenience and debugging purposes. diff --git a/source/includes/api-details/dart/sync/write-to-synced-database-compensating-write-error-information-description.rst b/source/includes/api-details/dart/sync/write-to-synced-database-compensating-write-error-information-description.rst new file mode 100644 index 0000000000..40be59629f --- /dev/null +++ b/source/includes/api-details/dart/sync/write-to-synced-database-compensating-write-error-information-description.rst @@ -0,0 +1,14 @@ +The Flutter SDK exposes a ``compensatingWrites`` field on a +:flutter-sdk:`CompensatingWriteError `. +You can access this information through the :ref:`Sync error handler +`. + +This field contains an array of :flutter-sdk:`CompensatingWriteInfo +` objects, which provide: + +- The ``objectType`` of the object the client attempted to write +- The ``primaryKey`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the App Services logs. +It is exposed on the client for convenience and debugging purposes. diff --git a/source/includes/api-details/javascript/sync/write-to-synced-database-compensating-write-error-information-description.rst b/source/includes/api-details/javascript/sync/write-to-synced-database-compensating-write-error-information-description.rst new file mode 100644 index 0000000000..44ff4b105b --- /dev/null +++ b/source/includes/api-details/javascript/sync/write-to-synced-database-compensating-write-error-information-description.rst @@ -0,0 +1,14 @@ +The Node.js SDK exposes a ``writes`` field on a +:js-sdk:`CompensatingWriteError `. +You can access this information through the :ref:`Sync error handler +`. + +This field contains an array of :js-sdk:`CompensatingWriteInfo +` objects, which provide: + +- The ``objectName`` of the object the client attempted to write +- The ``primaryKey`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the App Services logs. +It is exposed on the client for convenience and debugging purposes. diff --git a/source/includes/api-details/kotlin/sync/write-to-synced-database-compensating-write-error-information-description.rst b/source/includes/api-details/kotlin/sync/write-to-synced-database-compensating-write-error-information-description.rst new file mode 100644 index 0000000000..52fd806229 --- /dev/null +++ b/source/includes/api-details/kotlin/sync/write-to-synced-database-compensating-write-error-information-description.rst @@ -0,0 +1,38 @@ +The :kotlin-sync-sdk:`CompensatingWriteInfo +` +object provides: + +- The ``objectType`` of the object the client attempted to write +- The ``primaryKey`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the :ref:`App Services +logs `. The Kotlin SDK exposes this object on the client for +convenience and debugging purposes. + +The following shows an example of how you might log information +about compensating write errors: + +.. io-code-block:: + + .. input:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.access-compensating-write.kt + :language: kotlin + + .. output:: + :language: console + + A write was rejected with a compensating write error + The write to object type: Item + With primary key of: RealmAny{type=OBJECT_ID, value=BsonObjectId(649f2c38835cc0346b861b74)} + Was rejected because: write to "649f2c38835cc0346b861b74" in table "Item" not allowed; object is outside of the current query view + +- The ``Item`` in this message is ``Item`` object used in the object model on + this page. +- The primary key is the ``objectId`` of the specific object the client + attempted to write. +- The ``table "Item"`` refers to the Atlas collection where this object would + sync. +- The reason ``object is outside of the current query view`` in this example is + because the query subscription was set to require the object's ``complexity`` + property to be less than or equal to ``4``, and the client attempted to + write an object outside of this boundary. diff --git a/source/includes/api-details/swift/sync/write-to-synced-database-compensating-write-error-information-description.rst b/source/includes/api-details/swift/sync/write-to-synced-database-compensating-write-error-information-description.rst new file mode 100644 index 0000000000..17f548bcf2 --- /dev/null +++ b/source/includes/api-details/swift/sync/write-to-synced-database-compensating-write-error-information-description.rst @@ -0,0 +1,44 @@ +The Swift SDK exposes a :swift-sdk:`compensatingWriteInfo +` field on a +SyncError whose code is ``.writeRejected``. You can access this information +through the :ref:`Sync error handler `. + +This field contains an array of :objc-sdk:`RLMCompensatingWriteInfo +` objects, which provide: + +- The ``objectType`` of the object the client attempted to write +- The ``primaryKey`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the App Services logs. +It is exposed on the client for convenience and debugging purposes. + +.. example:: + + This error handler shows an example of how you might log information + about compensating write errors: + + .. literalinclude:: /examples/generated/code/start/Errors.snippet.access-compensating-write.swift + :language: swift + + The example error handler above produces this output when a compensating + write error occurs: + + .. code-block:: console + + A write was rejected with a compensating write error + The write to object type: Optional("Item") + With primary key of: objectId(641382026852d9220b2e2bbf) + Was rejected because: Optional("write to \"641382026852d9220b2e2bbf\" in table \"Item\" not + allowed; object is outside of the current query view") + + - The ``Optional("Item")`` in this message is an ``Item`` object used in the + :github:`Swift Template App `. + - The primary key is the ``objectId`` of the specific object the client + attempted to write. + - The ``table \"Item"\`` refers to the Atlas collection where this object + would sync. + - The reason ``object is outside of the current query view`` in this example + is because the query subscription was set to require the object's ``isComplete`` + property to be ``true``, and the client attempted to write an object where + this property was ``false``. diff --git a/source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information.rst b/source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information.rst new file mode 100644 index 0000000000..44ff4b105b --- /dev/null +++ b/source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information.rst @@ -0,0 +1,14 @@ +The Node.js SDK exposes a ``writes`` field on a +:js-sdk:`CompensatingWriteError `. +You can access this information through the :ref:`Sync error handler +`. + +This field contains an array of :js-sdk:`CompensatingWriteInfo +` objects, which provide: + +- The ``objectName`` of the object the client attempted to write +- The ``primaryKey`` of the specific object +- The ``reason`` for the compensating write error + +This information is the same information you can find in the App Services logs. +It is exposed on the client for convenience and debugging purposes. diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-model.rst b/source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-model.rst new file mode 100644 index 0000000000..ae9c9600fc --- /dev/null +++ b/source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-model.rst @@ -0,0 +1,44 @@ +.. tabs-drivers:: + + tabs: + - id: cpp-sdk + content: | + + .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.compensating-write-model.cpp + :language: cpp + + - id: csharp + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.cs + :language: csharp + + - id: dart + content: | + + .. literalinclude:: /examples/generated/flutter/write_to_synced_realm_test.snippet.realm-model.dart + :language: dart + + - id: javascript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.js + :language: javascript + + - id: kotlin + content: | + + .. literalinclude:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.flexible-sync-crud-model.kt + :language: kotlin + + - id: swift + content: | + + .. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.flexible-sync-crud-model.swift + :language: swift + + - id: typescript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.ts + :language: typescript diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-subscription.rst b/source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-subscription.rst new file mode 100644 index 0000000000..0670363c69 --- /dev/null +++ b/source/includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-subscription.rst @@ -0,0 +1,45 @@ +.. tabs-drivers:: + + tabs: + - id: cpp-sdk + content: | + + .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.compensating-write-setup.cpp + :language: cpp + + - id: csharp + content: | + + .. literalinclude:: /examples/generated/dotnet/FlexibleSyncExamples.snippet.example_sub.cs + :language: csharp + :copyable: false + + - id: dart + content: | + + .. literalinclude:: /examples/generated/flutter/write_to_synced_realm_test.snippet.subscription-setup.dart + :language: dart + + - id: javascript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.js + :language: javascript + + - id: kotlin + content: | + + .. literalinclude:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.flexible-sync-subscription-setup.kt + :language: kotlin + + - id: swift + content: | + + .. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.flexible-sync-subscription-setup.swift + :language: swift + + - id: typescript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.ts + :language: typescript diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-successful-writes.rst b/source/includes/sdk-examples/sync/write-to-synced-database-successful-writes.rst new file mode 100644 index 0000000000..0b9be914ce --- /dev/null +++ b/source/includes/sdk-examples/sync/write-to-synced-database-successful-writes.rst @@ -0,0 +1,44 @@ +.. tabs-drivers:: + + tabs: + - id: cpp-sdk + content: | + + .. literalinclude:: /examples/generated/cpp/sync-errors.snippet.successful-write-example.cpp + :language: cpp + + - id: csharp + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.cs + :language: csharp + + - id: dart + content: | + + .. literalinclude:: /examples/generated/flutter/write_to_synced_realm_test.snippet.write-synced-realm.dart + :language: dart + + - id: javascript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.js + :language: javascript + + - id: kotlin + content: | + + .. literalinclude:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.successful-write.kt + :language: kotlin + + - id: swift + content: | + + .. literalinclude:: /examples/generated/code/start/SyncedRealmCRUD.snippet.successful-write.swift + :language: swift + + - id: typescript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.ts + :language: typescript diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst new file mode 100644 index 0000000000..9999b61737 --- /dev/null +++ b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst @@ -0,0 +1,86 @@ +.. tabs-drivers:: + + tabs: + - id: cpp-sdk + content: | + + .. io-code-block:: + + .. input:: /examples/generated/cpp/sync-errors.snippet.write-not-matching-permissions.cpp + :language: cpp + + .. output:: + :language: console + + Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning) + + + - id: csharp + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.cs + :language: csharp + + - id: dart + content: | + + .. io-code-block:: + + .. input:: /examples/generated/flutter/write_to_synced_realm_test.snippet.not-match-permissions.dart + :language: dart + + .. output:: + :language: console + + [INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted + a write that is outside of permissions or query filters; it has been reverted" + (error_code=231, try_again=true, error_action=Warning) + [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write + for client version 25 in server version 2879: Client attempted a write that + is outside of permissions or query filters; it has been reverted + [ERROR] Realm: SyncSessionError message: Client attempted a write that + is outside of permissions or query filters; it has been reverted + Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c + category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false + + - id: javascript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.js + :language: javascript + + - id: kotlin + content: | + + .. io-code-block:: + + .. input:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.write-outside-permissions.kt + :language: kotlin + + .. output:: + :language: console + + [Session][CompensatingWrite(231)] Client attempted a write that + is disallowed by permissions, or modifies an object outside the + current query, and the server undid the change. + + - id: swift + content: | + + .. io-code-block:: + + .. input:: /examples/generated/code/start/SyncedRealmCRUD.snippet.write-outside-permissions.swift + :language: swift + + .. output:: + :language: console + + Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a + write that is outside of permissions or query filters; it has been + reverted" (error_code=231, try_again=true, error_action=Warning) + + - id: typescript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.ts + :language: typescript diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst new file mode 100644 index 0000000000..41d3cf2a62 --- /dev/null +++ b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst @@ -0,0 +1,85 @@ +.. tabs-drivers:: + + tabs: + - id: cpp-sdk + content: | + + .. io-code-block:: + + .. input:: /examples/generated/cpp/sync-errors.snippet.compensating-write-example.cpp + :language: cpp + + .. output:: + :language: console + + Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning) + + - id: csharp + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.cs + :language: csharp + + - id: dart + content: | + + .. io-code-block:: + + .. input:: /examples/generated/flutter/write_to_synced_realm_test.snippet.not-match-query-subscription.dart + :language: dart + + .. output:: + :language: console + + [INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted + a write that is outside of permissions or query filters; it has been reverted" + (error_code=231, try_again=true, error_action=Warning) + [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write + for client version 21 in server version 2877: Client attempted a write that + is outside of permissions or query filters; it has been reverted + [ERROR] Realm: SyncSessionError message: Client attempted a write that + is outside of permissions or query filters; it has been reverted + Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c + category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false + + - id: javascript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.js + :language: javascript + + - id: kotlin + content: | + + .. io-code-block:: + + .. input:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.write-outside-flexible-sync-query.kt + :language: kotlin + + .. output:: + :language: console + + [Session][CompensatingWrite(231)] Client attempted a write that + is disallowed by permissions, or modifies an object outside the + current query, and the server undid the change. + + - id: swift + content: | + + .. io-code-block:: + + .. input:: /examples/generated/code/start/SyncedRealmCRUD.snippet.write-outside-flexible-sync-query.swift + :language: swift + + .. output:: + :language: console + + Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a + write that is outside of permissions or query filters; it has been + reverted" (error_code=231, try_again=true, error_action=Warning) + + - id: typescript + content: | + + .. literalinclude:: /examples/MissingPlaceholders/example.ts + :language: typescript diff --git a/source/sdk/sync/configure-and-open-synced-database.txt b/source/sdk/sync/configure-and-open-synced-database.txt index d073d7040d..e67848c7dc 100644 --- a/source/sdk/sync/configure-and-open-synced-database.txt +++ b/source/sdk/sync/configure-and-open-synced-database.txt @@ -24,3 +24,8 @@ Placeholder page for configuring and opening a synced database. Download Changes Before Open ---------------------------- + +.. _sdks-synced-dbs-vs-non-synced-dbs: + +Synced Databases vs. Non-Synced Databases +----------------------------------------- diff --git a/source/sdk/sync/write-to-synced-database.txt b/source/sdk/sync/write-to-synced-database.txt index e351da49c4..0efa20637f 100644 --- a/source/sdk/sync/write-to-synced-database.txt +++ b/source/sdk/sync/write-to-synced-database.txt @@ -4,10 +4,372 @@ Write Data to a Synced Database =============================== +.. meta:: + :description: Atlas Device SDK uses a combination of server-side permissions and Sync subscription to validate what data users can write to a synced database. + :keywords: Realm, C++ SDK, Flutter SDK, Kotlin SDK, .NET SDK, Node.js SDK, Swift SDK, code example + +.. facet:: + :name: genre + :values: tutorial + +.. facet:: + :name: programming_language + :values: cpp, csharp, dart, javascript/typescript, kotlin, swift + .. contents:: On this page :local: :backlinks: none :depth: 3 :class: singlecol -Placeholder page for information about writing to a synced realm. +.. tabs-selector:: drivers + +When writing data to a synced database using Device Sync, you can use the +same APIs as writing to a non-synced database. However, there are some +differences in behavior to keep in mind as you develop your application. + +When you write to a synced database, your write operations must match *both* +of the following: + +- **The sync subscription query.** + - If your write operation doesn't match the query in the subscription, + the write reverts with a non-fatal compensating write error. +- **The permissions** in your App Services App. + - If your try to write data that doesn't match the permissions expression, + the write reverts with a non-fatal permission denied error. In the client, + this shows as a compensating write error. On the server, you can see more + details about how the write was denied was by a write filter in the role. + - To learn more about configuring permissions for your app, see + :ref:`sync-rules` and the :ref:`flexible-sync-permissions-guide` in the + App Services documentation. + +.. warning:: Multi-Process Sync is Not Supported + + Device Sync does not currently support opening or writing to a synced + database from more than one process. For more information, including + suggested alternatives, refer to: :ref:`multiprocess-sync-not-supported`. + +Determining What Data Syncs +--------------------------- + +The data that you can write to a synced database is the intersection of: + +- Your Device Sync configuration. +- Your Atlas server-side permissions. +- The Sync subscription query that you use when you open the database. + +The examples on this page use the following configurations and models: + +App Services Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device Sync is configured with the following queryable fields: + +- ``_id`` (always included) +- ``complexity`` +- ``ownerId`` + +The App Services App has permissions configured to let users read and write +only their own data: + +.. code-block:: json + + { + "name": "owner-read-write", + "apply_when": {}, + "document_filters": { + "read": { "ownerId": "%%user.id" }, + "write": { "ownerId": "%%user.id" } + }, + "read": true, + "write": true + } + +Client Data Model and Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The examples on this page use the following object model: + +.. include:: /includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-model.rst + +Using that object model, the synced database configuration syncs objects that +match the subscription query where the ``complexity`` property's value +is less than or equal to ``4``: + +.. include:: /includes/sdk-examples/sync/write-to-synced-database-client-data-model-and-configuration-subscription.rst + +What Data Syncs? +~~~~~~~~~~~~~~~~ + +The subscription query combined with the permissions mean that the synced +database only syncs objects where: + +- The ``ownerId`` matches the user ID of the logged-in user (from the + permissions) +- The ``complexity`` property's value is less than or equal to ``4`` (from the + subscription query) + +Any object in the Atlas collection where the ``ownerId`` does not match +the user ID of the logged-in user, or the ``complexity`` property's +value is greater than ``4``, cannot sync to this database. + +Write to a Synced Database +-------------------------- + +Writes to synced databases may broadly fall into one of two categories: + +- **Successful writes**: The written object matches both the query subscription + and the user's permissions. The object writes successfully to the database, + and syncs successfully to the App Services backend and other devices. + +- **Compensating writes**: When the written object does not match + the subscription query, or where the user does not have sufficient + permissions to perform the write, the SDK reverts the illegal write. + +.. _sdks-successful-writes: + +Successful Writes +~~~~~~~~~~~~~~~~~ + +When the write matches both the :ref:`permissions ` and the +:ref:`Sync subscription query ` in the client, +the SDK can successfully write the object to the synced database. This object +syncs with the App Services backend when the device has a network connection. + +.. include:: /includes/sdk-examples/sync/write-to-synced-database-successful-writes.rst + +.. _sdks-compensating-writes: + +Compensating Writes +~~~~~~~~~~~~~~~~~~~ + +In some cases, a write that initially appears to succeed is actually an +illegal write. In these cases, the object writes to the database, but when +the database syncs to the backend, the SDK reverts the write in a non-fatal +error operation called a compensating write. Compensating writes can occur +when: + +- **Writes don't match the query subscription**: The written object matches + the user's permissions, but does not match the query subscription. +- **Writes don't match permissions**: The written object matches the query + subscription, but does not match the user's permissions. + +In more detail, when you write data that is outside the bounds of a query +subscription or does not match the user's permissions, the following occurs: + +#. Because the client database has no concept of "illegal" writes, + the write initially succeeds until the SDK resolves the changeset + with the App Services backend. +#. Upon sync, the server applies the rules and permissions. + The server determines that the user does not have authorization to perform + the write. +#. The server sends a revert operation, called a "compensating write", back to + the client. +#. The client's database reverts the illegal write operation. + +Any client-side writes to a given object between an illegal write to that object +and the corresponding compensating write will be lost. + +In practice, this may look like an object being written to the database, and +then disappearing after the server sends the compensating write back to +the client. + +To learn more about permission denied errors, compensating write errors +and other Device Sync error types, refer to :ref:`sync-errors` in the App +Services documentation. + +The :ref:`App Services logs ` contain more information about +why a compensating write error occurs. + +Compensating Write Error Information +```````````````````````````````````` + +You can get additional information in the client about why a compensating +write occurs. + +.. tabs-drivers:: + + .. tab:: + :tabid: cpp-sdk + + .. include:: /includes/api-details/cpp/sync/write-to-synced-database-compensating-write-error-information-description.rst + + .. tab:: + :tabid: csharp + + .. include:: /includes/api-details/csharp/sync/write-to-synced-database-compensating-write-error-information-description.rst + + .. tab:: + :tabid: dart + + .. include:: /includes/api-details/dart/sync/write-to-synced-database-compensating-write-error-information-description.rst + + .. tab:: + :tabid: javascript + + .. include:: /includes/api-details/javascript/sync/write-to-synced-database-compensating-write-error-information-description.rst + + .. tab:: + :tabid: kotlin + + .. include:: /includes/api-details/kotlin/sync/write-to-synced-database-compensating-write-error-information-description.rst + + .. tab:: + :tabid: swift + + .. include:: /includes/api-details/swift/sync/write-to-synced-database-compensating-write-error-information-description.rst + + .. tab:: + :tabid: typescript + + .. include:: /includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information-description.rst + +.. _sdks-writes-outside-subscription: + +Writes that Don't Match the Query Subscription +`````````````````````````````````````````````` + +You can only write objects to a synced database if they match the +subscription query. If you perform a write that does not match the +subscription query, the SDK initially writes the object, but then performs +a compensating write. This is a non-fatal operation that +reverts an illegal write that does not match the subscription query. + +In practice, this may look like the write succeeding, but then the +object "disappears" when the SDK syncs with the App Services backend and +performs the compensating write. + +If you want to write an object that does not match the query subscription, +you must open a different database where the object matches the query +subscription. Alternately, you could write the object to a :ref:`non-synced +database ` that does not enforce +permissions or subscription queries. + +Code Example +++++++++++++ + +Given the configuration for the synced database above, attempting to +write this object does not match the query subscription: + +.. include:: /includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst + +App Services Error +++++++++++++++++++ + +The error message in the App Services logs in this scenario is: + +.. code-block:: console + + "Item": { + "63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\" + in table \"Item\" not allowed; object is outside of + the current query view" + } + +.. _sdks-writes-outside-permissions: + +Writes That Don't Match Permissions +``````````````````````````````````` + +Attempting to write to the client can also trigger a compensating write +error when the object does not match the user's server-side write permissions. + +On the client, this type of write behaves the same as a write that doesn't +match the query subscription. In practice, this may look like the write +succeeding, but then the object "disappears" when the database syncs with the +App Services backend and performs the compensating write. + +Code Example +++++++++++++ + +Given the permissions in the Device Sync Configuration detailed above, +attempting to write an object where the ``ownerId`` property does not match +the ``user.id`` of the logged-in user is not a legal write: + +.. include:: /includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst + +App Services Error +++++++++++++++++++ + +The error message in the App Services logs provides some additional +information to help you determine that it is a permissions issue, +and not a query subscription issue. In this example, the error message shows +that the the object does not match the user's role: + +.. code-block:: console + + "Item": { + "63bdfc40f16be7b1e8c7e4b8": "write to \"63bdfc40f16be7b1e8c7e4b8\" + in table \"Item\" was denied by write filter in role + \"owner-read-write\"" + } + +Group Writes for Improved Performance +------------------------------------- + +.. include:: /includes/sync-memory-performance.rst + +.. _multiprocess-sync-not-supported: + +Multi-Process Sync Not Supported +-------------------------------- + +If you are developing an app that may attempt to use a synced database in more +than one process, such as an Apple device application using a Share +Extension, avoid writing to a synced database in the additional process. +Device Sync supports opening a synced database in at most one process. In +practice, this means that if your app uses a synced database in an additional +process, it may crash intermittently. + +Crashes Related to Opening a Synced Database in Multiple Processes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you attempt to open a synced database in a Share Extension or other +multiple-process use case, and that database is not already open in the main +app, a write from a Share Extension may succeed. However, if the synced +database is already open in the main app, or is syncing data in the background, +you may see a crash related to ``Realm::MultiSyncAgents``. In this scenario, +you may need to restart the device. + +Alternatives to Writing to a Synced Database in an Multiple Processes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you need to read from or write to a synced database in more than one +process, there are a few recommended alternatives: + +- **Offline-first**: pass data on disk to or from the main process or main app +- **Always up-to-date**: communicate directly with the backing + Atlas collection across a network connection + +Pass Data On Disk +````````````````` + +If offline-first functionality is the most important consideration for your app, +you can pass data on disk to or from your main app. In an Apple ecosystem, for +example, you could copy objects to a non-synced database and read and share +it between apps in an App Group. Or you could use an on-disk queue to send the +data to or from the main app and only write to the synced database from there. +Then, regardless of the device's network connectivity, information can be +shared any time to or from the App Extension. + +Communicate Directly with the Backing Atlas Collection +`````````````````````````````````````````````````````` + +If having the information always up-to-date across all devices is the most +important consideration for your app, you can read or write data directly +to or from the backing Atlas collection across the network. Depending on your +needs, you may want to use one of these tools to communicate directly with +Atlas: + +- Query Atlas with the :ref:`the SDK MongoClient ` +- Pass data to an :ref:`App Services Function ` +- Make HTTPS calls with the :ref:`Data API ` + +Then, any device that has a network connection is always getting the most +up-to-date information, without waiting for the user to open your main app +as in the option above. + +This option does require your user's device to have a network connection +when using the secondary process. As a fallback, you could check for a network +connection. Then, use the on-disk option above in the event that the user's +device lacks network connectivity. From 9a2d5483d2fefa281c3165a603a86e6e94f5e7c3 Mon Sep 17 00:00:00 2001 From: dacharyc Date: Thu, 6 Jun 2024 11:22:04 -0400 Subject: [PATCH 2/4] Fix snooty build errors --- ...mpensating-write-error-information-description.rst} | 0 ...mpensating-write-error-information-description.rst} | 0 ...ase-writes-dont-match-subscription-code-example.rst | 10 +++++----- source/sdk/sync.txt | 2 +- source/sdk/sync/write-to-synced-database.txt | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename source/includes/api-details/cpp/sync/{write-to-synced-realm-compensating-write-error-information-description.rst => write-to-synced-database-compensating-write-error-information-description.rst} (100%) rename source/includes/api-details/typescript/sync/{write-to-synced-database-compensating-write-error-information.rst => write-to-synced-database-compensating-write-error-information-description.rst} (100%) diff --git a/source/includes/api-details/cpp/sync/write-to-synced-realm-compensating-write-error-information-description.rst b/source/includes/api-details/cpp/sync/write-to-synced-database-compensating-write-error-information-description.rst similarity index 100% rename from source/includes/api-details/cpp/sync/write-to-synced-realm-compensating-write-error-information-description.rst rename to source/includes/api-details/cpp/sync/write-to-synced-database-compensating-write-error-information-description.rst diff --git a/source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information.rst b/source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information-description.rst similarity index 100% rename from source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information.rst rename to source/includes/api-details/typescript/sync/write-to-synced-database-compensating-write-error-information-description.rst diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst index 41d3cf2a62..e95ac2f6ce 100644 --- a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst +++ b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst @@ -56,12 +56,12 @@ .. input:: /examples/generated/kotlin/SyncedRealmCRUD.snippet.write-outside-flexible-sync-query.kt :language: kotlin - .. output:: - :language: console + .. output:: + :language: console - [Session][CompensatingWrite(231)] Client attempted a write that - is disallowed by permissions, or modifies an object outside the - current query, and the server undid the change. + [Session][CompensatingWrite(231)] Client attempted a write that + is disallowed by permissions, or modifies an object outside the + current query, and the server undid the change. - id: swift content: | diff --git a/source/sdk/sync.txt b/source/sdk/sync.txt index 997bbf8942..fd9777ff90 100644 --- a/source/sdk/sync.txt +++ b/source/sdk/sync.txt @@ -16,7 +16,7 @@ Sync Data Add Sync to an App Configure & Open a Synced Database Manage Sync Subscriptions - Write to a Synced Realm + Write to a Synced Database Handle Sync Errors Manage Sync Sessions Stream Data to Atlas diff --git a/source/sdk/sync/write-to-synced-database.txt b/source/sdk/sync/write-to-synced-database.txt index 0efa20637f..4072a52837 100644 --- a/source/sdk/sync/write-to-synced-database.txt +++ b/source/sdk/sync/write-to-synced-database.txt @@ -362,7 +362,7 @@ needs, you may want to use one of these tools to communicate directly with Atlas: - Query Atlas with the :ref:`the SDK MongoClient ` -- Pass data to an :ref:`App Services Function ` +- Pass data to an :ref:`App Services Function ` - Make HTTPS calls with the :ref:`Data API ` Then, any device that has a network connection is always getting the most From d4cf1e43a7144dcd4737dc431a4087e9b53d9651 Mon Sep 17 00:00:00 2001 From: dacharyc Date: Thu, 6 Jun 2024 11:30:04 -0400 Subject: [PATCH 3/4] Improve formatting on C++ output code blocks --- ...ed-database-writes-dont-match-permissions-code-example.rst | 4 +++- ...d-database-writes-dont-match-subscription-code-example.rst | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst index 9999b61737..d063b5c80c 100644 --- a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst +++ b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-permissions-code-example.rst @@ -12,7 +12,9 @@ .. output:: :language: console - Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning) + Connection[2]: Session[11]: Received: ERROR "Client attempted a + write that is not allowed; it has been reverted" (error_code=231, + is_fatal=false, error_action=Warning) - id: csharp diff --git a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst index e95ac2f6ce..7f369795ba 100644 --- a/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst +++ b/source/includes/sdk-examples/sync/write-to-synced-database-writes-dont-match-subscription-code-example.rst @@ -12,7 +12,9 @@ .. output:: :language: console - Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning) + Connection[2]: Session[10]: Received: ERROR "Client attempted a + write that is not allowed; it has been reverted" (error_code=231, + is_fatal=false, error_action=Warning) - id: csharp content: | From 6a7698c3685bd75ec311e9fcbcdfd554d40a71d0 Mon Sep 17 00:00:00 2001 From: dacharyc Date: Thu, 11 Jul 2024 10:04:59 -0400 Subject: [PATCH 4/4] Incorporate review feedback --- source/includes/sync-memory-performance.rst | 6 +++--- source/sdk/sync/write-to-synced-database.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/includes/sync-memory-performance.rst b/source/includes/sync-memory-performance.rst index 46b66c19be..0fed7d7bbb 100644 --- a/source/includes/sync-memory-performance.rst +++ b/source/includes/sync-memory-performance.rst @@ -1,5 +1,5 @@ Every write transaction for a subscription set has a performance cost. If you -need to make multiple updates to a Realm object during a session, consider +need to make multiple updates to an SDK object during a session, consider keeping edited objects in memory until all changes are complete. This -improves sync performance by only writing the complete and updated object to your -realm instead of every change. \ No newline at end of file +improves sync performance by only writing the complete and updated object to +your database instead of every change. diff --git a/source/sdk/sync/write-to-synced-database.txt b/source/sdk/sync/write-to-synced-database.txt index 4072a52837..d9efc5bdca 100644 --- a/source/sdk/sync/write-to-synced-database.txt +++ b/source/sdk/sync/write-to-synced-database.txt @@ -226,7 +226,7 @@ write occurs. .. _sdks-writes-outside-subscription: -Writes that Don't Match the Query Subscription +Writes That Don't Match the Query Subscription `````````````````````````````````````````````` You can only write objects to a synced database if they match the @@ -331,8 +331,8 @@ database is already open in the main app, or is syncing data in the background, you may see a crash related to ``Realm::MultiSyncAgents``. In this scenario, you may need to restart the device. -Alternatives to Writing to a Synced Database in an Multiple Processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Alternatives to Writing to a Synced Database in Multiple Processes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you need to read from or write to a synced database in more than one process, there are a few recommended alternatives: