Skip to content

Commit

Permalink
docs: doc edits
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Sklar <[email protected]>
Co-authored-by: Erik Margetis <[email protected]>
  • Loading branch information
3 people committed Apr 24, 2024
1 parent 990b21d commit 3b574b0
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 66 deletions.
9 changes: 5 additions & 4 deletions docs/core-concepts/mutators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
7 changes: 5 additions & 2 deletions docs/core-concepts/rooms.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ 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:

```js title="/src/components/App.jsx"
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');
```
6 changes: 4 additions & 2 deletions docs/core-concepts/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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`.
26 changes: 21 additions & 5 deletions docs/core-concepts/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
28 changes: 13 additions & 15 deletions docs/getting-started/add-to-existing-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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!"
Expand All @@ -53,22 +51,23 @@ 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:

```shell
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
Expand All @@ -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.
Loading

0 comments on commit 3b574b0

Please sign in to comment.