From 3b574b0dfecff939b2ee9df0e37e488fa4a30dd5 Mon Sep 17 00:00:00 2001 From: jgoteam Date: Wed, 24 Apr 2024 08:44:25 -0700 Subject: [PATCH] docs: doc edits Co-authored-by: Alex Sklar Co-authored-by: Erik Margetis --- docs/core-concepts/mutators.md | 9 ++--- docs/core-concepts/rooms.md | 7 ++-- docs/core-concepts/subscriptions.md | 6 ++-- docs/core-concepts/transactions.md | 26 +++++++++++--- .../add-to-existing-project.md | 28 +++++++-------- docs/getting-started/start-a-new-project.md | 34 +++++++++---------- docs/guides/analytics.md | 22 +++++++++--- docs/guides/authentication.md | 22 ++++++------ docs/guides/configuration.md | 2 +- docs/index.md | 6 ++-- 10 files changed, 96 insertions(+), 66 deletions(-) diff --git a/docs/core-concepts/mutators.md b/docs/core-concepts/mutators.md index ed0881e..c26d2a0 100644 --- a/docs/core-concepts/mutators.md +++ b/docs/core-concepts/mutators.md @@ -4,12 +4,13 @@ sidebar_position: 3 # Mutators -A **mutator** is a Javascript function, defined by the developer, that defines logic to transform the shared application state through a [transaction](https://syncosaurus.github.io/docs/core-concepts/transactions). A key feature of mutators is that *they run twice*: +A **mutator** is a Javascript function, defined by the developer, that transforms the shared application state through a [transaction](transactions.md). A key feature of mutators is that *they run twice*: -- Mutators are first run on the client against the client's local state, and the client's user interface updates immediately. The mutation does not wait for a server response. This is intentional, and is also vital in keeping the client's perceived response times low and the user experience smooth. Because these mutators have not yet been confirmed by the authoritative server, the mutations made by this first run of mutators are known as optimistic mutations.  -- Mutators are then run by the server against the server's state, which is the authoritative state. Although the defined mutators are the same, because the mutators are now run against the server's state instead of the client's state, the result of the mutation may be different. This occurrence is a normal part of the syncing process. When the server broadcasts its authoritative updates and if the client state with applied optimistic mutations is different than the authoritative state sent by the server, Syncosaurus will automatically roll back its applied optimistic mutations and apply updates to match the server's authoritative state. +- Mutators are first run on the client against the client's local state, and the client's user interface updates immediately. The mutation does not wait for a server response. This is intentional, and vital in keeping the client's perceived response times low and the user experience smooth. Because these mutators have not yet been confirmed by the authoritative server, the mutations made by this first run of mutators are known as *optimistic mutations*.  +- Mutators are then run by the server against the server's state, which is the authoritative state. Although the defined mutators are the same, because the mutators are now run against the server's state instead of the client's state, the result of the mutation may be different. This occurrence is a normal part of the syncing process. +- When the server broadcasts its authoritative updates and if the client state with applied optimistic mutations is different than the authoritative state sent by the server, Syncosaurus will automatically roll back its applied optimistic mutations and apply updates to match the server's authoritative state. -Because mutators are just high-level Javascript functions, the developer gains a high level of flexibility and control in how and when updates should be mutated. Mutators can be used to not only modify the shared state of a collaborative application. These mutators can also be used to selectively aggregate, merge, and/or delete different pieces of the shared state, as well as to implement custom application logic or fine-grained authorization. +Because mutators are Javascript functions, the developer gains a high level of flexibility and control in how and when updates should be mutated. Mutators can be used to modify the shared state of a collaborative application. They can also be used to selectively aggregate, merge, and/or delete different pieces of the shared state, as well as to implement custom application logic. The function `increment` below is an example of a mutator: diff --git a/docs/core-concepts/rooms.md b/docs/core-concepts/rooms.md index fe12a16..2582ea5 100644 --- a/docs/core-concepts/rooms.md +++ b/docs/core-concepts/rooms.md @@ -4,7 +4,9 @@ sidebar_position: 1 # Rooms -The primary unit of collaboration within a Syncosaurus application is the **room**, a central coordination point and data store location. Much like a physical meeting room may serve as a hub for multiple individuals to communicate and exchange information and ideas, a room in Syncosaurus serves as a hub for users of a collaborative application (known as *clients*) to share updates and access the same data. In both scenarios, any update sent by any individual within a room may be received by all individuals present within the room, and any individual in the room also has access to the room's shared data (assuming they have permission to do so.) As with an individual moving in the physical world, a client in a collaborative web application can also move between different rooms, which often has its own features and rules. The application developer is ultimately responsible for determining these room attributes and how they align with the attributes of clients entering, collaborating, and leaving those rooms. +The primary unit of collaboration within a Syncosaurus application is the **room**, a central coordination point and data store location. Much like a physical meeting room may serve as a hub for multiple individuals to communicate and exchange information and ideas, a room in Syncosaurus serves as a hub for users of a collaborative application (known as **clients**) to share updates and access the same data. In both scenarios, any update sent by any individual within a room may be received by all individuals present within the room, and any individual in the room also has access to the room's shared data (assuming they have permission to do so.) As with an individual moving in the physical world, a client in a collaborative web application can also move between different rooms, which often has its own features and rules. + +The application developer is ultimately responsible for defining these room attributes and how they align with the attributes of clients entering, collaborating, and leaving those rooms. Let's illustrate this idea with an example. Let's say we have an e-learning platform on which teachers can host lectures and discussions in different rooms. A music room could have a chord visualization feature, while a statistics course could come with a specialized charting widget. Room client attributes need to be considered in conjunction with these features: imagine if students were given the same permissions as instructors. A music class could easily become a cacophonous zoo, while the chaos level of a statistics class over time might start to resemble the trajectory of a jet taking off to the moon. In Syncosaurus, the room name is specified by passing it to the `launch` method of a `Syncosaurus` instance: @@ -12,11 +14,12 @@ In Syncosaurus, the room name is specified by passing it to the `launch` method import mutators from './mutators.js'; const synco = new Syncosaurus({ - mutators, userID: "my-user-id", server: "https://my-cool-app.workers.dev", auth: "E3B6CD6A-1EDC-49B8-B2CF-7372583145A6", + mutators, }); +//highlight-next-line synco.launch('my-room-name'); ``` diff --git a/docs/core-concepts/subscriptions.md b/docs/core-concepts/subscriptions.md index 39544a3..31c6f3f 100644 --- a/docs/core-concepts/subscriptions.md +++ b/docs/core-concepts/subscriptions.md @@ -6,9 +6,9 @@ sidebar_position: 4 A **subscription** allows clients to monitor if and when changes occur to the shared state of a room, regardless if the source of a change is local or from the authoritative server. -A **query** is a request for a specific type of subscription, and works hand-in-hand with subscriptions, allowing for customization of what state changes are being monitored. Queries are able to filter, aggregate and transform the shared state, or only portions of the shared state, to enable more granular application behavior and logic. These abilities are non-mutating and do not affect the shared application state, as it is only the server that can dictate final changes to shared state. +A **query** specifies *what* shared state changes a subcription monitors, and also *how*. Queries are able to filter, aggregate and transform the shared state, or only portions of the shared state, to enable more granular application behavior and logic. These abilities are non-mutating and do not affect the shared application state, as under the hood, they are utilizing [read transactions](transactions.md). -At the implementation level, a *subscription* is represented by the return value of `useSubscribe`, a custom React hook that uses the [useState](https://react.dev/reference/react/useState) React hook to monitor data from the local client state. What an update to this monitored data occurs, associated UI components re-renders with this refreshed data. At the implementation level, a *query* is a developer-defined function, whose return value dictates what is considered a subscription update. Whenever a query's return value changes, its subscription is activated, and UI components that are connected to that subscription are updated. +At the implementation level, a *subscription* is represented by the return value of `useSubscribe`, a custom React hook that uses the [useState](https://react.dev/reference/react/useState) React hook to monitor data from the local client state. When an update to this monitored data occurs, associated UI components re-render with this refreshed data. At the implementation level, a *query* is a developer-defined function, whose return value dictates what is considered a subscription update. Whenever a query's return value changes, its subscription is activated, and UI components that are connected to that subscription are updated. Here is a simple implementation of a subscription and its query: @@ -21,3 +21,5 @@ Here, we call the `useSubscribe` hook, and pass in three arguments: - The first argument is always a `Syncosaurus` instance, which has access to the local client state and the complete list of active subscriptions. - The second argument is our *query*. The *query* function must be defined with `tx` as its only parameter. This `tx` parameter is what allows a query and subscription to monitor the local client state for their specified changes. - The third argument is the initial starting value. If there are not yet changes to the local state that is relevant to the subscription, then the `useSubscribe` hook will return this initial starting value. + +The return value of `useSubscribe` is the part of the shared state we specified to monitor, in this case, the value at the key `count`. diff --git a/docs/core-concepts/transactions.md b/docs/core-concepts/transactions.md index c1b2923..67a4b6c 100644 --- a/docs/core-concepts/transactions.md +++ b/docs/core-concepts/transactions.md @@ -4,14 +4,30 @@ sidebar_position: 2 # Transactions -A **transaction** represents a single update, which can be either from a client to the server, or the server to a client. +A **transaction** represents a single [client](rooms.md) update. -In a Syncosaurus application, since the server is the authority, every transaction must be confirmed by the server before being broadcasted to other clients in a room. +In a Syncosaurus application, since the server is the authority, the server must confirm every transaction before being broadcast to other clients in a room. -A transaction, as an instance of the `transaction` class, consists of: +At the implementation level, a transaction consists of: - methods that allow mutators and queries to update and read from the local client state, key-value store - metadata on the transaction it represents, specifically *when* and *how* the application state should be updated -In Syncosaurus, transactions are categorized as either *read transactions* or *write transactions*. As their names imply, transactions are either observing or changing a specified part of the shared application state. +Transactions are categorized as either *read transactions* or *write transactions*. As their names imply, transactions are either observing or changing a specified part of the shared application state. -It is specifically through *write transactions* that [mutations](https://syncosaurus.github.io/docs/core-concepts/mutators) are applied to the shared state of a collaborative application. +## Read Transactions + +Read transactions are instances of the `ReadTransaction` class, and have access to the following methods: + - `get` - returns the value for the key from the local store based on a given key + - `has` - returns true if a given key exists in the local store + - `isEmpty` - returns true if the local store is empty + - `scan` - returns an object containing a subset of KV pairs from the local store where a developer-defined callback evaluates to true + +[Subscription queries](subscriptions.md) utilize read transactions to monitor for changes in the shared state of a collaborative applications. + +## Write Transactions + +Write transations are instances of the `WriteTransaction` class, and have access to the listed `ReadTransaction` instance methods above, in addition to the `set` and `delete` methods: + - `set` - updates the value of a given key in the local store + - `delete` - deletes a given key (and therefore value) from the local store + +[Mutations](mutators.md) are applied to the shared state of a collaborative application though write transactions. diff --git a/docs/getting-started/add-to-existing-project.md b/docs/getting-started/add-to-existing-project.md index d27d680..e9a5aff 100644 --- a/docs/getting-started/add-to-existing-project.md +++ b/docs/getting-started/add-to-existing-project.md @@ -13,8 +13,6 @@ npx syncosaurus setup That's it! Make sure to correctly configure your Syncosaurus application before deployment - see here for details. -Note that Syncosaurus currently only supports applications built with either React or a React-based framework. - ## Spin up a local development environment A local development environment makes it significantly easier to rapidly iterate, test, and debug your application in a consistent, controlled setting. Syncosaurus comes built-in with a local Syncosaurus server. @@ -40,10 +38,10 @@ Press 'x' to gracefully shut down the server Make sure to update your `server` value in your Syncosaurus constructor calls in your application code: ```js title="/src/components/App.jsx" -import mutators from './mutators.js'; +import mutators from '../mutators.js'; const synco = new Syncosaurus({ - // update this value to the local Syncosaurus server URL + // highlight-next-line server: "http://localhost:8787", userID: "my-user-id", auth: "4fJbGOWma=QEebX6H1X6AAe3/yok1R-fwKMqot5XOvxPU0YwI!sm8nQ!" @@ -53,14 +51,15 @@ const synco = new Syncosaurus({ ## Deploy your Syncosaurus project -When you are ready to deploy your Syncosaurus application, make sure the following requirements are first met: +Before you deploy your Syncosaurus application, make sure the following requirements are met: -1. Make sure that you are logged in to Cloudflare. - 1. You can check your login status with `syncosaurus whoami` - 2. If you are not logged in, run `syncosaurus login` and provide your Cloudflare credentials via OAuth or API token. -2. Navigate to your root directory of your project, which needs to have a `syncosaurus.json` configuration file. -3. Make sure your mutators are correctly defined in your `mutators.js` file, and your `mutators.js` file is located in the `src` sub-directory. -4. Make sure your authentication handler is correctly defined in your `authHandler.js` file, and your `authHandler.js` file is located in the `src` sub-directory. +- You are logged in to Cloudflare. + - You can check your login status with `syncosaurus whoami` + - If you are not logged in, run `syncosaurus login` and log in via OAuth or API token. +- You are in the root directory of your project, which must have `syncosaurus.json` configuration file. +- Your mutators are correctly defined in your `mutators.js` file, and your `mutators.js` file is located in the `src` sub-directory. +- Your `auth` value is defined correctly in the Syncosaurus constructor +- Your authentication handler is correctly defined in your `authHandler.js` file, and your `authHandler.js` file is located in the `src` sub-directory. Once the above requirements have been fulfilled, run the following command in your terminal: @@ -68,7 +67,7 @@ Once the above requirements have been fulfilled, run the following command in yo npx syncosaurus deploy ``` -If your application was configured correctly, the URL to your deployed application should be displayed in your terminal output, which will look akin to the output displayed below: +If your application was configured correctly, the URL to your deployed application should be displayed in your terminal output, which should look similar to the output displayed below: ```shell ❯ npx syncosaurus deploy @@ -77,8 +76,7 @@ If your application was configured correctly, the URL to your deployed applicati Evolving your Syncosaurus server... done! -✅ Success! Your Syncosaurus server is available at - https://my-cool-app.johnsmith.workers.dev +✅ Success! Your Syncosaurus server is available at https://dino-arcade.petrie.workers.dev ``` -To deploy your frontend, simply add this URL as an environment variable in your frontend application's directory. +To deploy your frontend, simply add the provided Syncosaurus server URL as an environment variable to your frontend deployment. diff --git a/docs/getting-started/start-a-new-project.md b/docs/getting-started/start-a-new-project.md index d2d12e0..debc02c 100644 --- a/docs/getting-started/start-a-new-project.md +++ b/docs/getting-started/start-a-new-project.md @@ -4,8 +4,6 @@ sidebar_position: 1 # Start a New Project -## Setup - To create a new Syncosaurus application, simply run the following command in your terminal: ```shell @@ -16,7 +14,7 @@ That's it! Follow the prompts to set up your new Syncosaurus project. ## Spin up a local development environment -A local development environment makes it significantly easier to rapidly iterate, test, and debug your application in a consistent, controlled setting. Syncosaurus comes built-in with both a local Syncosaurus server. If you are building a Syncosaurus project from scratch using `syncosaurus init`, Syncosaurus includes a local Vite UI server as well. +A local development environment makes it significantly easier to rapidly iterate, test, and debug your application in a consistent, controlled setting. Syncosaurus comes built-in with a local Syncosaurus server. If you are building a project from scratch using `syncosaurus init`, Syncosaurus includes a local Vite UI server as well. To start both the Syncosaurus server and the Vite UI server, run the following command in your terminal: @@ -30,10 +28,10 @@ If your file structure adheres to Syncosaurus rules, and your configuration sett - a localhost address for your local Vite UI server - The port used will be determined by Vite. Vite's default port is port 5173. If that port is being used, Vite will find and designate the next available port. -Your output will look something like the following: +Your output should something like the following: ```shell -my-projects/my-synco-app via ⬢ v21.7.1 +my-projects/dino-arcade via ⬢ v21.7.1 ❯ npx syncosaurus dev Checking for Syncosaurus installation...... found Initializing local dev environment...... done! @@ -48,10 +46,10 @@ Press 'x' to gracefully shut down both servers Make sure to update your `server` value in your Syncosaurus constructor calls in your application code: ```js title="/src/components/App.jsx" -import mutators from './mutators.js'; +import mutators from '../mutators.js'; const synco = new Syncosaurus({ - // update this value to the local Syncosaurus server URL + // highlight-next-line server: "http://localhost:8787", userID: "my-user-id" auth: "4fJbGOWma=QEebX6H1X6AAe3/yok1R-fwKMqot5XOvxPU0YwI!sm8nQ!" @@ -63,14 +61,15 @@ Now you're all set - freely iterate on your application, and see your code chang ## Deploy your Syncosaurus application -When you are ready to deploy your Syncosaurus application, make sure the following requirements are first met: +Before you deploy your Syncosaurus application, make sure the following requirements are met: -1. Make sure that you are logged in to Cloudflare. - 1. You can check your login status with `syncosaurus whoami` - 2. If you are not logged in, run `syncosaurus login` and provide your Cloudflare credentials via OAuth or API token. -2. Navigate to your root directory of your project, which needs to have a `syncosaurus.json` configuration file. -3. Make sure your mutators are correctly defined in your `mutators.js` file, and your `mutators.js` file is located in the `src` sub-directory. -4. Make sure your authentication handler is correctly defined in your `authHandler.js` file, and your `authHandler.js` file is located in the `src` sub-directory. +- You are logged in to Cloudflare. + - You can check your login status with `syncosaurus whoami` + - If you are not logged in, run `syncosaurus login` and log in via OAuth or API token. +- You are in the root directory of your project, which must have `syncosaurus.json` configuration file. +- Your mutators are correctly defined in your `mutators.js` file, and your `mutators.js` file is located in the `src` sub-directory. +- Your `auth` value is defined correctly in the Syncosaurus constructor +- Your authentication handler is correctly defined in your `authHandler.js` file, and your `authHandler.js` file is located in the `src` sub-directory. Once the above requirements have been fulfilled, run the following command in your terminal: @@ -78,7 +77,7 @@ Once the above requirements have been fulfilled, run the following command in yo npx syncosaurus deploy ``` -If your application was configured correctly, the URL to your deployed application should be displayed in your terminal output, which will look akin to the output displayed below: +If your application was configured correctly, the URL to your deployed application should be displayed in your terminal output, which should look similar to the output displayed below: ```shell ❯ npx syncosaurus deploy @@ -87,8 +86,7 @@ If your application was configured correctly, the URL to your deployed applicati Evolving your Syncosaurus server... done! -✅ Success! Your Syncosaurus server is available at - https://my-cool-app.johnsmith.workers.dev +✅ Success! Your Syncosaurus server is available at https://dino-arcade.ducky.workers.dev ``` -To deploy your frontend, simply add this URL as an environment variable for your frontend deployment. +To deploy your frontend, simply add the provided Syncosaurus server URL as an environment variable to your frontend deployment. diff --git a/docs/guides/analytics.md b/docs/guides/analytics.md index c0da0d7..5d4ba54 100644 --- a/docs/guides/analytics.md +++ b/docs/guides/analytics.md @@ -8,16 +8,28 @@ Syncosaurus includes an application analytics tool to easily view and analyze ag ## Setup -- Clone this repository to your local environment: git clone https://github.com/syncosaurus/syncosaurus-dashboard.git -- Create an `.env` file with the following environment variables and values in the root directory: +1. Clone the repository to your local environment, and install the required dependencies: + ```shell + git clone https://github.com/syncosaurus/syncosaurus-dashboard.git + ``` + +2. Create an `.env` file with the following environment variables and values in the root directory: - `CLOUDFLARE_ACCOUNT_ID`: This is the account ID associated with your Cloudflare account. For instructions on finding your account ID, click [here](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/). - `BEARER_AUTH`: This is an Cloudflare Analytics API token associated with your account. This is used to access the GraphQL Analytics API. For instructions on creating an API token, click [here](https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/). - `X_AUTH_EMAIL`: This is the e-mail address associated with your Cloudflare account. - `X_AUTH_KEY`: This is your Cloudflare global API key. This is used to access the Cloudflare API. For instructions on finding your global API key, click [here](https://developers.cloudflare.com/fundamentals/api/get-started/keys/). - `PORT`: This is the local port that you want the dashboard app to use. If this value does not exist, the application defaults to port 3001. -- Install dependencies: `npm install` -- Run the dashboard application: `npm start` -- An URL to the dashboard application will be displayed (ex: http://localhost:3001). Click the URL to launch the dashboard application. +3. Install dependencies: + ```shell + npm install + ``` + +4. Run the dashboard application: + ```shell + npm start + ``` + +5. An URL to the dashboard application will be displayed (ex: http://localhost:3001). Click the URL to launch the dashboard application. ## Metrics Info diff --git a/docs/guides/authentication.md b/docs/guides/authentication.md index e79e128..bd8ca29 100644 --- a/docs/guides/authentication.md +++ b/docs/guides/authentication.md @@ -13,13 +13,13 @@ The Syncosaurus framework supports token-based authentication. JSON web tokens a To use a token, you can directly pass a *token string* to the `Syncosaurus` constructor as the `auth` key's value: ```js title="/src/components/App.jsx" -import mutators from './mutators.js'; +import mutators from '../mutators.js'; const synco = new Syncosaurus({ mutators, - userID: "my--user-id", + userID: "my-user-id", server: "https://my-cool-app.workers.dev", - // Pass your authentication token here + // highlight-next-line auth: "E3B6CD6A-1EDC-49B8-B2CF-7372583145A6", }); ``` @@ -27,14 +27,14 @@ const synco = new Syncosaurus({ You can also pass an *asynchronous function* that returns a token string to the `Syncosaurus` constructor as the `auth` key's value: ```js title="/src/components/App.jsx" -import mutators from './mutators.js'; -import getToken from './auth-client.js'; +import mutators from '../mutators.js'; +import getToken from '../auth-client.js'; const synco = new Syncosaurus({ mutators, userID: "my--user-id", server: "https://my-cool-app.workers.dev", - // Pass your async fn that returns an auth token here + // highlight-next-line auth: async () => return await getToken(), }); ``` @@ -44,12 +44,10 @@ const synco = new Syncosaurus({ Next, an `authHandler` function needs to be defined: ```js title="/src/authHandler.js" -const authServerUrl = `https://www.myAuthServer.com`; - export default async function authHandler(token) { try { - const response = await fetch(`${authServerUrl}/verify?token=${token}`); - const { payload } = response.json(); + const response = await fetch(`${env.authServerUrl}/verify?token=${token}`); + const { payload } = await response.json(); const { userID } = payload; return userID; } catch (error) { @@ -60,12 +58,12 @@ export default async function authHandler(token) { ### Configuration -Note that in order for your Syncosaurus server to correctly read, access, and verify your authentication token(s), the authentication handler function `authHandler` must adhere to the following requirements: +Note that for your Syncosaurus server to correctly read, access, and verify your authentication token(s), the authentication handler function `authHandler` must adhere to the following requirements: - The function must be named `authHandler` - The function must be placed in the `src` subdirectory of your Syncosaurus project. Do not place `authHandler.js` in a subdirectory of the `src` directory. - `authHandler.js` must be a module that exports the `authHandler` function as its default export. -Both the `auth` token and the `authHandler` function need to be correctly defined and appropriately handle errors in order for Syncosaurus to correctly implement authentication in your application. +Both the `auth` token and the `authHandler` function need to be correctly defined and appropriately handle errors for Syncosaurus to correctly implement authentication in your application. - If the above requirements are not met, then authentication will not be configured, and will be skipped. - However, the Syncosaurus server will reject a request if: - `auth` is either a string or function and `authHandler` is a function, and the `authHandler` function throws any type of error. diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index ec04aa2..dd8ad76 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -21,7 +21,7 @@ The default value of `msgFrequency` is `16` (ms), which equates to approximately ### `useStorage` -This value determines whether your application persists data to the Syncosaurus server using [Cloudflare's Transactional Storage API](https://developers.cloudflare.com/durable-objects/api/transactional-storage-api/) +This value determines whether your application persists data to the Syncosaurus server using [Cloudflare's Transactional Storage API](https://developers.cloudflare.com/durable-objects/api/transactional-storage-api/). The default value of `useStorage` is `false`. diff --git a/docs/index.md b/docs/index.md index bd76b7f..0d43651 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,9 @@ Built with Cloudflare's [Durable Objects](https://developers.cloudflare.com/dura ## Features -- An synchronization engine based on server reconciliation +- A synchronization engine based on [client-side prediction and server reconciliation](https://gabrielgambetta.com/client-side-prediction-server-reconciliation.html) - The ability to automatically persist state on a per-room basis - A convenient CLI tool to seamlessly create, configure, manage, and deploy your collaborative application -- An analytics dashboard to monitor your collaborative application's usage and error metrics \ No newline at end of file +- An [analytics dashboard](guides/analytics.md) to monitor usage and error metrics +- [Authentication](guides/authentication.md) support to secure your [rooms](core-concepts/rooms.md) +- [Presence](core-concepts/presence.md) support to broadcast ephemeral data, including live cursors and live status updates \ No newline at end of file