Docs Menu
Docs Home
/ /
Atlas Device SDKs
/ /

Handle Sync Errors - Node.js SDK

On this page

  • Sync Error Handler
  • Add a Generic Sync Error Handler
  • Handle Compensating Write Errors
  • Handle Client Reset Errors
  • Client Reset Modes
  • Automatic vs. Manual Client Reset
  • Client Reset with Recovery
  • Recover Unsynced Changes Mode
  • Recover or Discard Unsynced Changes Mode
  • Manual Client Reset Fallback
  • Discard Unsynced Changes Mode
  • Discard Unsynced Changes after Breaking Schema Changes
  • Manual Mode
  • Manual Data Recovery
  • Test Client Reset Handling

When you use Atlas Device Sync in your Realm app, you can encounter a new class of errors: sync errors.

The Realm Node.js SDK can help you detect and handle sync errors. For example, you can write your own sync error handler to respond to specific errors. You can also define how your client app handles client resets.

You should set an error handler for apps that use Atlas Device Sync. A generic error handler will detect and respond to failed sync-related API calls to your Atlas backend.

Set an error handler by registering an error callback function as part of the SyncConfiguration.

const handleSyncError = async (session, error) => {
// ... handle the error using session and error information.
console.log(session);
console.log(error);
};
const config = {
schema: [DogSchema],
sync: {
flexible: true,
user: app.currentUser,
onError: handleSyncError,
},
};
// Open realm with config that contains error handler.
const realm = await Realm.open(config);
const handleSyncError = (
session: Realm.App.Sync.Session,
error: Realm.SyncError | Realm.ClientResetError
) => {
// ... handle the error using session and error information.
console.log(session);
console.log(error);
};
const config: Realm.Configuration = {
schema: [DogSchema],
sync: {
flexible: true,
user: app.currentUser!,
onError: handleSyncError,
},
};
// Open realm with config that contains error handler.
const realm = await Realm.open(config);

Tip

For a list of common Device Sync errors and how to handle them, refer to Sync Errors in the App Services Device Sync documentation.

You may want your sync error handler to specifically address compensating write errors in a way that makes sense for your app. The CompensatingWriteError class can help you identify and react to compensating write errors in your custom error handler.

const errorCallback = (session, error) => {
// Check if error type matches CompensatingWriteError.
if (error instanceof CompensatingWriteError) {
// Handle the compensating write error as needed.
console.debug({
name: error.name,
code: error.code,
message: error.message,
atlasLogUrl: error.logUrl,
});
const compensatingWrites = error.writes.sort((a, b) =>
a.primaryKey.toString().localeCompare(b.primaryKey.toString())
);
console.debug(compensatingWrites);
}
};
const app = new Realm.App({
id: APP_ID,
});
const credentials = Credentials.anonymous();
await app.logIn(credentials);
const realm = await Realm.open({
schema: [Person, Turtle],
sync: {
flexible: true,
user: app.currentUser,
onError: errorCallback,
},
});
const errorCallback: ErrorCallback = (session, error) => {
// Check if error type matches CompensatingWriteError.
if (error instanceof CompensatingWriteError) {
// Handle the compensating write error as needed.
console.debug({
name: error.name,
code: error.code,
message: error.message,
atlasLogUrl: error.logUrl,
});
const compensatingWrites = error.writes.sort((a, b) =>
(a.primaryKey as BSON.ObjectId)
.toString()
.localeCompare((b.primaryKey as BSON.ObjectId).toString())
);
console.debug(compensatingWrites);
}
};
const app = new Realm.App({
id: APP_ID,
});
const credentials = Credentials.anonymous();
await app.logIn(credentials);
const realm = await Realm.open({
schema: [Person, Turtle],
sync: {
flexible: true,
user: app.currentUser!,
onError: errorCallback,
},
});

A client reset error is a scenario where a client realm cannot sync data with the Atlas App Services backend. Clients in this state may continue to run and save data locally but cannot send or receive sync changesets until they perform a client reset.

To learn about the causes of and modes for handling client resets, check out Device Sync Client Resets in the App Services documentation.

You can specify which client reset mode your app should use to restore the realm to a syncable state:

  • Recover unsynced changes mode: When you choose this mode, the client attempts to recover unsynced changes. Choose this mode when you do not want to fall through to discard unsynced changes.

  • Recover or discard unsynced changes mode: The client first attempts to recover changes that have not yet synced. If the client cannot recover unsynced data, it falls through to discard unsynced changes but continues to automatically perform the client reset. Choose this mode when you want to enable automatic client recovery to fall back to discard unsynced changes.

  • Discard unsynced changes mode: Restores the realm to a syncable state by discarding changes made since the last sync.

  • Manual recovery mode: Downloads a new copy of the realm, and moves the unsyncable realm to a backup. Migrate unsynced data from the backup copy of the realm to the new syncable copy.

The Realm SDKs provide client reset modes that automatically handle most client reset errors.

Automatic client reset modes restore your local realm file to a syncable state without closing the realm or missing notifications. The following client reset modes support automatic client resets:

  • Recover unsynced changes mode

  • Recover or discard unsynced changes mode

  • Discard unsynced changes mode

The differences between these modes are based on how they handle changes on the device that have not yet synced to the backend. Only manual recovery mode does not perform an automatic client reset.

Choose recover unsynced changes mode to handle most client reset scenarios automatically. This attempts to recover unsynced changes when a client reset occurs.

If your app requires specific client reset logic that can't be handled automatically, you may want or need to add a manual client reset handler to the automatic client reset mode.

New in version 10.23.0.

Client Recovery is a feature that is enabled by default when you configure Device Sync. When Client Recovery is enabled, Realm automatically manages the client reset process in most cases. The client can recover unsynced changes when there are no schema changes, or non-breaking schema changes.

To use Client Recovery, configure your realm with one of the following client reset modes:

  • Recover unsynced changes mode

  • Recover or discard unsynced changes

When Client Recovery is enabled, these rules determine how objects are integrated, including how conflicts are resolved when both the backend and the client make changes to the same object:

  • Objects created locally that were not synced before client reset are synced.

  • If an object is deleted on the server, but is modified on the recovering client, the delete takes precedence and the client discards the update.

  • If an object is deleted on the recovering client, but not the server, then the client applies the server's delete instruction.

  • In the case of conflicting updates to the same field, the client update is applied.

For more information about configuring Client Recovery, refer to Client Recovery in the App Services documentation.

Client Recovery cannot succeed when your app makes breaking schema changes. A breaking change is a change that you can make in your server-side schema that requires additional action to handle. In this scenario, client reset falls back to a manual error client reset fallback.

For information on breaking vs. non-breaking schema changes, refer to Breaking vs. Non-Breaking Change Quick Reference in the App Services documentation.

When you choose recover unsynced changes mode, the client attempts to recover unsynced changes with Client Recovery. Choose this mode when you do not want to fall through to discard unsynced changes.

To handle client resets with the recover unsynced changes mode, pass a ClientResetConfig to the clientReset field of your SyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "recoverUnsyncedChanges".

  • onBefore: Optional. Callback function invoked before the SDK executes this mode, when the SDK receives a client reset error from the backend. Provides a copy of the realm.

  • onAfter: Optional. Callback function invoked after the SDK successfully executes this mode. Provides instances of the realm before and after the client reset.

  • onFallback: Optional. Callback function which the SDK invokes only if the automatic recovery fails. For more information, refer to the Manual Client Reset Fallback section.

The following example implements recover unsynced changes mode:

const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
flexible: true,
clientReset: {
mode: "recoverUnsyncedChanges",
onBefore: (realm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
},
};

In recover or discard unsynced changes mode, the client first attempts to recover changes that have not yet synced. If the client cannot recover unsynced data, it falls through to discard unsynced changes but continues to automatically perform the client reset. Choose this mode when you want to enable automatic client recovery to fall back to discard unsynced changes.

Do not use recover or discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To handle client resets with the recover or discard unsynced changes mode, pass a ClientResetConfig to the clientReset field of your SyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "recoverOrDiscardUnsyncedChanges".

  • onBefore: Optional. Callback function invoked before the SDK executes this mode, when the SDK receives a client reset error from the backend. Provides a copy of the realm.

  • onAfter: Optional. Callback function invoked after the SDK successfully executes this mode. Provides instances of the realm before and after the client reset.

  • onFallback: Optional. Callback function which the SDK invokes only if both the automatic recovery and and discarding changes fails. For more information, refer to the Manual Client Reset Fallback section.

The following example implements recover unsynced changes mode:

const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
flexible: true,
clientReset: {
mode: "recoverOrDiscardUnsyncedChanges",
onBefore: (realm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
},
};

If the client reset with recovery cannot complete automatically, like when there are breaking schema changes, the client reset process falls through to a manual error handler. This may occur in either of the client reset with recovery modes, recover unsynced changes and recover or discard unsynced changes.

You must provide a manual client reset implementation in the SyncConfiguration.onFallback() callback. onFallback() takes two arguments:

  • session: Session object representing the state of the Device Sync session.

  • path: String with the path to the current realm file.

The following example demonstrates how you can manually handle this error case by discarding all unsynced changes:

// Must define `realm` at higher scope than `config` so it's accessible
// from the `onFallback` callback
let realm;
const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
flexible: true,
clientReset: {
mode: "recoverOrDiscardUnsyncedChanges", // or "recoverUnsyncedChanges"
// can also include `onBefore` and `onAfter` callbacks
onFallback: (_session, path) => {
try {
// Prompt user to perform a client reset immediately. If they don't,
// they won't receive any data from the server until they restart the app
// and all changes they make will be discarded when the app restarts.
const didUserConfirmReset = showUserAConfirmationDialog();
if (didUserConfirmReset) {
// Close and delete old realm from device
realm.close();
Realm.deleteFile(path);
// Perform client reset
Realm.App.Sync.initiateClientReset(app, path);
// Navigate the user back to the main page or reopen the
// the Realm and reinitialize the current page
}
} catch (err) {
// Reset failed. Notify user that they'll need to
// update the app
}
},
},
},
};
realm = await Realm.open(config);

New in version 10.11.0.

Changed in version 10.23.0: Mode renamed from "discardLocal" to "discardUnsyncedChanges". Both currently work, but in a future version, "discardLocal" will be removed. "clientResetBefore" and "clientResetAfter" callbacks renamed to "onBefore" and "onAfter", respectively.

Discard Unsynced Changes mode permanently deletes all local unsynced changes made since the last successful sync. You might use this mode when your app requires client recovery logic that is not consistent with automatic Client Recovery, or when you don't want to recover unsynced data.

Do not use discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To handle client resets with the discard unsynced changes mode, pass a ClientResetConfig to the clientReset field of your SyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "discardUnsyncedChanges".

  • onBefore: Optional. Callback function invoked before the SDK executes this mode, when the SDK receives a client reset error from the backend. Provides a copy of the realm.

  • onAfter: Optional. Callback function invoked after the SDK successfully executes this mode. Provides instances of the realm before and after the client reset.

The following example implements discard unsynced changes mode:

const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
flexible: true,
clientReset: {
mode: "discardUnsyncedChanges",
onBefore: (realm) => {
console.log("Beginning client reset for ", realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
console.log("Finished client reset for", beforeRealm.path);
console.log("New realm path", afterRealm.path);
},
},
},
};

If your application experiences a breaking schema change, discard unsynced changes mode cannot handle the resulting client reset automatically. Instead, you must provide a manual client reset implementation in the SyncConfiguration error() callback. The following example demonstrates how you can manually handle this error case by discarding all unsynced changes:

// Once you have opened your Realm, you will have to keep a reference to it.
// In the error handler, this reference is called `realm`
async function handleSyncError(session, syncError) {
if (syncError.name == "ClientReset") {
console.log(syncError);
try {
console.log("error type is ClientReset....");
const path = realm.path; // realm.path will not be accessible after realm.close()
realm.close();
Realm.App.Sync.initiateClientReset(app, path);
// Download Realm from the server.
// Ensure that the backend state is fully downloaded before proceeding,
// which is the default behavior.
realm = await Realm.open(config);
realm.close();
} catch (err) {
console.error(err);
}
} else {
// ...handle other error types
}
}
const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
flexible: true,
clientReset: {
mode: "discardUnsyncedChanges",
onBefore: (realm) => {
// NOT used with destructive schema changes
console.log("Beginning client reset for ", realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
// Destructive schema changes do not hit this function.
// Instead, they go through the error handler.
console.log("Finished client reset for", beforeRealm.path);
console.log("New realm path", afterRealm.path);
},
},
onError: handleSyncError, // invoked with destructive schema changes
},
};

Note

Discard with Recovery

If you'd like to attempt to recover unsynced changes, but but discard any changes that cannot be recovered, refer to the recover or discard unsynced changes mode section.

Changed in version 10.23.0: onManual callback added

In manual mode, you define your own client reset handler. You might want to use a manual client reset handler if the Automatic Recovery logic does not work for your app and you can't discard unsynced local data.

To handle client resets with manual mode, pass a ClientResetConfig to the clientReset field of your SyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "manual".

  • onManual: Optional. Callback function invoked when the client reset occurs. Provides information about the sync session and the path to the current realm. If you don't set the onManual error handler, the client reset error falls back to the general sync error handler.

const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
flexible: true,
clientReset: {
mode: "manual",
onManual: (session, path) => {
// handle manual client reset here
},
},
},
};

To recover data from a manual client reset requires significant amounts of code, schema concessions, and custom conflict resolution logic. If you need to implement your own custom client reset logic, see the Advanced Guide to Manual Client Reset Data Recovery.

You can manually test your application's client reset handling by terminating and re-enabling Device Sync.

When you terminate and re-enable Sync, clients that have previously connected with Sync are unable to connect until after they perform a client reset. Terminating Sync deletes the metadata from the server that allows the client to synchronize. The client must download a new copy of the realm from the server. The server sends a client reset error to these clients. So, when you terminate Sync, you trigger the client reset condition.

To test client reset handling:

  1. Write data from a client application and wait for it to synchronize.

  2. Terminate and re-enable Device Sync.

  3. Run the client app again. The app should get a client reset error when it tries to connect to the server.

Warning

While you iterate on client reset handling in your client application, you may need to terminate and re-enable Sync repeatedly. Terminating and re-enabling Sync renders all existing clients unable to sync until after completing a client reset. To avoid this in production, test client reset handling in a development environment.

Back

Manage a Sync Session