Skip to content

Commit

Permalink
Merge fc1853d into 7b55e16
Browse files Browse the repository at this point in the history
  • Loading branch information
elle-j committed Sep 1, 2023
2 parents 7b55e16 + fc1853d commit 19eb7c1
Show file tree
Hide file tree
Showing 15 changed files with 937 additions and 123 deletions.
8 changes: 8 additions & 0 deletions examples/example-node-connection-and-error/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# TS-generated output files.
dist/

# Dependencies.
node_modules/

# Local MongoDB Realm database.
mongodb-realm/
97 changes: 97 additions & 0 deletions examples/example-node-connection-and-error/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Reference App Using RealmJS in Node.js

A skeleton app to be used as a reference for how to use the [Realm Node.js SDK](https://www.mongodb.com/docs/realm/sdk/node/) specifically around detecting various changes in e.g. connection state, user state, and sync errors, in order to better guide developers.

## Relevant Files

```
├── src
│ ├── atlas-app-services (Configure Atlas App)
│ │ ├── config.ts
│ │ └── getAtlasApp.ts
│ ├── models (Simplified data model)
│ │ ├── Kiosk.ts
│ │ ├── Product.ts
│ │ └── Store.ts
│ ├── index.ts (Entry point)
│ ├── logger.ts (Replaceable logger)
│ ├── realm-auth.ts (Main Realm auth usage examples)
│ └── realm-query.ts (Data access/manipulation helper)
└── other..
```

Main file for showcasing Realm usage pertaining to connection and error handling:
* [src/realm-auth.ts](./src/realm-auth.ts)

## Scope

The app addresses the following points:

* Listening when a user is logged out or removed.
* Listening when a user's tokens are refreshed.
* Listening when the underlying sync session is connecting, gets connected, gets disconnected, and fails to reconnect.
* Listening for sync errors.
* Listening for pre and post client resets.
* Explains that the refresh of access tokens is handled automatically by the SDK.
* [Refresh token expiration time](https://www.mongodb.com/docs/atlas/app-services/users/sessions/#configure-refresh-token-expiration) can be altered in the Atlas UI, whereafter you can observe the relevant client listeners being fired.
* Login is shown using email/password. With the above bullet point, testing refresh token expiration is still possible in this case despite not using JWT as the login method.
* Generally providing best practices for the surrounding Realm usage such as opening and closing of realms, configurations, adding subscriptions, etc.
* Includes useful comments around the use of Realm.
* Note that an over-simplified data model is used. This app also writes data to confirm the functionality.

### Summary

This reference app thus focuses on showing where and when you can (a) perform logging and (b) handle specific scenarios based on observed changes.

### Realm Details

* RealmJS version: ^12.0.0
* Device Sync type: Flexible

## Getting Started

### Prerequisites

* [Node.js](https://nodejs.org/)

### Set up an Atlas App Services App

To sync Realm data you must first:

1. [Create an App Services App](https://www.mongodb.com/docs/atlas/app-services/manage-apps/create/create-with-ui/)
2. Enable [Email/Password Authentication](https://www.mongodb.com/docs/atlas/app-services/authentication/email-password/#std-label-email-password-authentication)
3. [Enable Flexible Sync](https://www.mongodb.com/docs/atlas/app-services/sync/configure/enable-sync/) with **Development Mode** on.
* When Development Mode is enabled, queryable fields will be added automatically.
* Queryable fields used in this app: `_id`, `storeId`

After running the client and seeing the available collections in Atlas, [set read/write permissions](https://www.mongodb.com/docs/atlas/app-services/rules/roles/#with-device-sync) for all collections.

### Install Node.js dependencies

```sh
npm install
```

### Run the app

1. Copy your [Atlas App ID](https://www.mongodb.com/docs/atlas/app-services/reference/find-your-project-or-app-id/#std-label-find-your-app-id) from the App Services UI.
2. Paste the copied ID as the value of the existing variable `ATLAS_APP_ID` in [src/atlas-app-services/config.ts](./src/atlas-app-services/config.ts):
```js
export const ATLAS_APP_ID = "YOUR_APP_ID";
```

3. Start the script.

```sh
npm start
```


### Troubleshooting

* If permission is denied:
* Whitelist your IP address via the Atlas UI.
* Make sure you have [read/write permissions](https://www.mongodb.com/docs/atlas/app-services/rules/roles/#with-device-sync) for all collections.
* Removing the local database can be useful for certain errors.
* When running the app, the local database will exist in the directory `mongodb-realm/`.
* To remove it, run: `npm run rm-local-db`
20 changes: 20 additions & 0 deletions examples/example-node-connection-and-error/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@realm/example-node-connection-and-error",
"version": "1.0.0",
"description": "A skeleton app to be used as a reference for how to use the Realm Node.js SDK specifically around detecting various changes in e.g. connection state, user state, and sync errors",
"main": "src/index.ts",
"scripts": {
"build": "tsc",
"start": "npm run build && node dist/src/index.js",
"rm-local-db": "rm -rf mongodb-realm/",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"dependencies": {
"realm": "^12.0.0"
},
"devDependencies": {
"typescript": "^5.1.6"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import { BSON } from "realm";

// For this example app, the constant below is type annotated as `string` rather
// than inferred due to the equality check in `src/atlas-app-services/getAtlasApp.ts`
// verifying that this constant has been set.
export const ATLAS_APP_ID: string = "YOUR_APP_ID";
export const SYNC_STORE_ID = new BSON.ObjectId("6426106cb0ad9713140883ed");
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import Realm from "realm";

import { ATLAS_APP_ID } from "./config";
import { logger } from "../logger";

let app: Realm.App | null = null;

export const getAtlasApp = function getAtlasApp() {
if (!app) {
if (ATLAS_APP_ID === "YOUR_APP_ID") {
throw new Error(
"Please add your Atlas App ID to `src/atlas-app-services/config.ts`. Refer to `README.md` on how to find your ID.",
);
}

app = new Realm.App({ id: ATLAS_APP_ID });

// Using log level "all", "trace", or "debug" is good for debugging during developing.
// Lower log levels are recommended in production for performance improvement.
// logLevels = ["all", "trace", "debug", "detail", "info", "warn", "error", "fatal", "off"];
// You may import `NumericLogLevel` to get them as numbers starting from 0 (`all`).
Realm.setLogLevel("error");
Realm.setLogger((logLevel, message) => {
logger.info(`Log level: ${logLevel} - Log message: ${message}`);
});
}

return app;
};
54 changes: 54 additions & 0 deletions examples/example-node-connection-and-error/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import { register, logIn, logOut, openRealm } from "./realm-auth";
import { addDummyData, updateDummyData, deleteDummyData, getStore } from "./realm-query";

const exampleEmail = "john@doe.com";
const examplePassword = "123456";

/**
* Illustrates the flow of using a synced Realm.
*/
async function main(): Promise<void> {
let success = await register(exampleEmail, examplePassword);
if (!success) {
return;
}

success = await logIn(exampleEmail, examplePassword);
if (!success) {
return;
}

await openRealm();

// Cleaning the DB for this example before continuing.
deleteDummyData();
addDummyData();
updateDummyData();

// Print a kiosk and its products.
const store = getStore();
const firstKiosk = store?.kiosks[0];
if (firstKiosk) {
console.log(JSON.stringify(firstKiosk, null, 2));
}
}

main();
29 changes: 29 additions & 0 deletions examples/example-node-connection-and-error/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

/**
* Logger - This is meant to be replaced with a preferred logging implementation.
*/
export const logger = {
info(message: string) {
console.info(new Date().toLocaleString(), '|', message);
},
error(message: string) {
console.error(new Date().toLocaleString(), '|', message);
},
};
37 changes: 37 additions & 0 deletions examples/example-node-connection-and-error/src/models/Kiosk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import Realm, { BSON, ObjectSchema } from "realm";

import { Product } from "./Product";

export class Kiosk extends Realm.Object {
_id!: BSON.ObjectId;
storeId!: BSON.ObjectId;
products!: Realm.List<Product>;

static schema: ObjectSchema = {
name: "Kiosk",
primaryKey: "_id",
properties: {
_id: "objectId",
storeId: { type: "objectId", indexed: true },
products: "Product[]",
},
};
}
44 changes: 44 additions & 0 deletions examples/example-node-connection-and-error/src/models/Product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import Realm, { BSON, ObjectSchema } from "realm";

/**
* Current information and inventory about a type of product in a particular store.
* (This is simplified to refer to a complete product (e.g. a sandwich, rather than
* e.g. bread, cheese, lettuce, etc.)
*/
export class Product extends Realm.Object {
_id!: BSON.ObjectId;
storeId!: BSON.ObjectId;
name!: string;
price!: number;
numInStock!: number;

static schema: ObjectSchema = {
name: "Product",
primaryKey: "_id",
properties: {
_id: "objectId",
storeId: { type: "objectId", indexed: true },
name: "string",
price: "double",
numInStock: "int",
},
};
}

0 comments on commit 19eb7c1

Please sign in to comment.