Skip to content

Commit

Permalink
Docs overhaul (hathora#109)
Browse files Browse the repository at this point in the history
* overhaul readme and add chat example

* chat implementation

* readme edits

* remove quickstart

* begin reference docs

* use relative links

* link reference from main readme

* replace postimg links

* more docs edits

* client section

* add placeholder concepts readme

* more improvements to reference

* add some rough concepts docs
  • Loading branch information
hpx7 authored Sep 23, 2021
1 parent da4a81d commit 2f5c649
Show file tree
Hide file tree
Showing 19 changed files with 533 additions and 110 deletions.
189 changes: 85 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# rtag - real time app generator
# rtag - realtime app generator

## Overview

Rtag is a framework that aims to simplify application development by allowing the developer to focus only on application-specific logic, without having to think about adjacent concerns such as infrastructure, tooling, networking, storage, etc.
Rtag is a framework for building realtime applications with a focus on development experience.

### Features

Rtag comes out of the box with the following features so that developers don't have to think about them:

- Networking (state synchronization and RPC, efficient binary serialization)
- Authentication
- Automatic persistence
- Declarative API format with client generation
- Development server with hot reloading and built in debug UI

### Application spec

The foundation of an rtag application is the `rtag.yml` file, which defines various aspects of the application's behavior. One of the primary components the developer includes in this file is a fully typed API, which lists the server methods as well as the client state tree.

From this specification, rtag automatically generates the following:

Rtag uses a YAML file (`rtag.yml`) to define the application specification. The developer declares the client data model, mutation functions, and other relevant configuration in this file. Rtag then uses this specification to automatically generate several key components:
- server side method stubs that set up the entire server code structure and just need to be filled in with the application's business logic
- clients for frontends to use to communicate with the rtag server in a typesafe manner
- clients for frontends to communicate with the rtag server in a typesafe manner
- a web-based debug application that allows for testing backend logic right away without writing any frontend code

Rtag also comes with a powerful development server with features like hot code reloading and live code bundling, which aim to get out of the way of the developer and create an enjoyable development experience.

## Installation

#### Requirements
Expand All @@ -23,120 +36,88 @@ Install rtag from the npm registry:
npm install -g rtag
```

## Quickstart

1. Inside a new directory, create a `rtag.yml` file and fill it out as per your project specifications (see below)
2. Run `rtag init` to generate your initial rtag project. For subsequent changes made to `rtag.yml`, run `rtag` instead
3. Run `rtag install` to install dependencies
4. Start the server using `rtag start`
5. View debug app at http://localhost:3000/

## App configuration (rtag.yml)

Rtag apps contain a yaml configuration file in the root directory. This file sets up the contract between client and server and contains other relevant configuration for the application.

#### Client state object
## Example

This is the state object each connected client will have access to. Rtag automatically keeps the client state up to date as it is updated in the server.

The client state definition lives inside the `types` section, and is referenced using `userState`:
The spec for a simple chat app:

```yml
types:
MyIdAlias: string
MyEnum:
- VAL_1
- VAL_2
MyState:
id: MyIdAlias
enum: MyEnum
bool: boolean
num: number
str: string
arr: string[]
opt?: string
userState: MyState
```
# rtag.yml

A custom error type can also be defined, although in many cases using a primitive `string` type may suffice:

```yml
error: string
```

#### Mutation methods

Methods are the way through which the state can be modified in the server. Methods take 0 or more user-defined arguments and have to be implemented in the server, possibly mutating the state and returning either a success or error response.

The method signatures are defined under the `methods` section. The argument types can be primitives or can reference types defined in the `types` section. One of the methods must be designated as the `initialize` function, which is responsible for creating the initial version of the state.
types:
Username: string
Message:
text: string
sentAt: number
sentBy: Username
sentTo?: Username
RoomState:
name: string
createdBy: Username
messages: Message[]

```yml
methods:
doSomeAction:
arg1: string
arg2: MyEnum[]
emptyMethod:
createState:
conf: MyConfiguration
initialize: createState
```

#### Authentication
createRoom:
name: string
sendPublicMessage:
text: string
sendPrivateMessage:
to: Username
text: string

The `auth` section is used to configure the authentication modes that the application can use. The two currently supported modes are `anonymous` and `google`. At least one authentication method must be configured.

```yml
auth:
anonymous:
separator: "-"
google:
clientId: <PUBLIC_GOOGLE_APP_ID>.apps.googleusercontent.com
```

## Backend

The entry point for the application's backend logic lives in the root of the `server` directory, in a file called `impl.ts`.

#### Internal state

The server side representation of a single state instance.

#### Initialize method

Returns the initial internal state based on the user context and arguments.

#### Mutation methods

Modify internal state based on the user context and arguments. Perform input/state validation by returning an error result if invalid request or a success result otherwise.

#### getUserState method

Maps from the internal state to the user state based on the user context. This mapping allows privacy rules to be enforced so that the user only gets the data they should have access to.

#### onTick method

Server ticks can be enabled by setting `tick: true` in `rtag.yml`.

This method is called at a regular interval in the server and has access to the internal state. It is used for background tasks that can update the state, e.g. physics simulations etc.
userState: RoomState
initialize: createRoom
error: string
```
## Frontend
After running `rtag init`, `rtag generate`, `rtag install`, and `rtag start`, the following debug view is automatically generated:

![image](https://user-images.githubusercontent.com/5400947/134371999-eca307b9-4e28-4313-96c1-1f8cbcbddec3.png)

We then fill in the methods in `server/impl.ts` with our desired implementation:

```ts
import { Methods, Context } from "./.rtag/methods";
import { UserData, Response } from "./.rtag/base";
import { RoomState, ICreateRoomRequest, ISendPublicMessageRequest, ISendPrivateMessageRequest } from "./.rtag/types";
export class Impl implements Methods<RoomState> {
createRoom(user: UserData, ctx: Context, request: ICreateRoomRequest): RoomState {
return { name: request.name, createdBy: user.name, messages: [] };
}
sendPublicMessage(state: RoomState, user: UserData, ctx: Context, request: ISendPublicMessageRequest): Response {
state.messages.push({ text: request.text, sentAt: ctx.time(), sentBy: user.name });
return Response.ok();
}
sendPrivateMessage(state: RoomState, user: UserData, ctx: Context, request: ISendPrivateMessageRequest): Response {
state.messages.push({ text: request.text, sentAt: ctx.time(), sentBy: user.name, sentTo: request.to });
return Response.ok();
}
getUserState(state: RoomState, user: UserData): RoomState {
return {
name: state.name,
createdBy: state.createdBy,
messages: state.messages.filter(
(msg) => msg.sentBy === user.name || msg.sentTo === user.name || msg.sentTo === undefined
),
};
}
}
```

One of rtag's most powerful prototyping features is the generated debug app, which lets you interact with your application and test your backend logic without writing any frontend code. Furthermore, rtag provides ways to incrementally add custom presentation logic as you become ready for it.
> Note that currently, the only backend language supported is typescript. More language support is planned for the future.

#### Plugins
Finally, we can see our working application in action:

Plugins go inside the `client/plugins` directory. To create a plugin for type `Foo`, create a file named `Foo.ts` and rerun the `rtag` command. This will cause the debug app to render your plugin's component anywhere `Foo` shows up in the state tree (instead of the rendering the default json view).
![image](https://user-images.githubusercontent.com/5400947/134372344-6b4ed46c-feed-4776-95f8-9d0499570b76.png)

Your plugin must export a webcomponent (a class that extends `HTMLElement`). While you are free to write a native webcomponent without any dependencies, most popular frontend libraries have ways to create webcomponents. Some examples include:
- React (via https://github.com/bitovi/react-to-webcomponent)
- Vue (via https://github.com/vuejs/vue-web-component-wrapper)
- Lit (no wrapper required)
For more examples, check out the [examples directory](https://github.com/hpx7/rtag/tree/develop/examples).

Plugins receive the following props as input:
- val -- this is the value you are rendering, it has the type of your filename
- state -- this is the entire state tree, it has the type of `userState`
- client -- this is the rtag client instance (so you can make method calls from within your plugin), with type `RtagClient`
## Additional resources

#### Fully custom frontend
For a high level overview of rtag concepts and goals, see [concepts](docs/concepts.md). For more details on how to implement an rtag application, check out the [reference docs](docs/reference.md).

When you're ready to move away from the debug app, create an `index.html` file at the root of the `client` directory and rerun the `rtag` command. This file now serves as the entry point to your frontend. You are free to use any technologies you wish to build your frontend, just make sure to import the generated client to communicate with the rtag server.
If you have any questions/suggestions or want to report a bug, please feel free to file an issue or start a discussion on Github!
24 changes: 24 additions & 0 deletions docs/concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Concepts

> This section is under construction!
The primary goal of rtag is to offer the simplest development experience possible. This document explores some of the features which aim to accomplish this.

## Type driven development

By encoding the API in the `rtag.yml` upfront, rtag is given a lot of information about how the application functions. It then tries to use this information to help the developer as much as possible, by generating (1) typesafe clients with the method calls and types built in, (2) the server interface which the implmentation needs to implement, and (3) a debug application which allows extremely fast prototyping and testing.

## Core loop

1. clients call methods, server runs `onTick`
2. state is mutated inside methods/`onTick`
3. changes are picked up by framework via change detection
4. updates are broadcasted via `getUserState`

## Replay log

rtag models the backend as a deterministic state machine. Internal state is modified determinsitically as a function of its inputs. If we write down the method + its inputs every time we detect a modification, we can later reconstruct the state by replaying all the method calls sequentially.

There are a few common sources of non-determinism that often live in server side logic: (pseudo-)random numbers, current time, and api calls. To still be able to determinsitically reconstruct the state while still allowing for these sources, rtag passes in a `context` object as an argument to methods/`onTick`. By utilizing the functions on this object (e.g. `context.time()`), rtag is able to write down the appropriate information in the replay log so that it can feed in the same values during replay time.

Ultimately, this system frees developers from having to think about persistence in their server side logic and operate soley on in-memory objects, while still providing durability automatically.
Loading

0 comments on commit 2f5c649

Please sign in to comment.