Rushlight

Real-time collaborative code editing on your own infrastructure

README

🕯️ Rushlight


_Make collaborative code editors that run on your own infrastructure: just Redis
and a database._


Supports multiple real-time documents, with live cursors. Based on
changes are resolved by server code. It's designed to be as easy to integrate as
possible (read: boring). The backend is stateless, and _you can bring your own
transport_; even a single HTTP handler is enough.

Unlike most toy examples, Rushlight supports persistence in any durable database
you choose. Real-time updates are replicated in-memory by Redis, with automatic
log compaction.

An experiment by Eric Zhang, author of

Motivation


Let's say you're writing a web application. You already have a database, and you
want to add real-time collaborative editing. However, most libraries are
unsuitable because they:

- Require proprietary gadgets
- Are not flexible enough, e.g., to customize appearance
- Make you subscribe to a cloud service where you can't control the data
- Use decentralized algorithms like CRDTs that are hard to reason about
- Make it difficult to authenticate users or apply rate limits
- Rely on a single stateful server, which breaks with replication / horizontal
  autoscaling
- Need WebSockets or other protocols that aren't supported by some providers
- Are just generally too opinionated

This library tries to take a more practical approach.

Usage


Install the client and server packages.

  1. ```bash
  2. # client
  3. npm install rushlight

  4. # server
  5. npm install rushlight-server
  6. ```

On the frontend, create a CollabClient object and attach it to your CodeMirror
instance via extensions.

  1. ```ts
  2. import { EditorView } from "codemirror";
  3. import { CollabClient } from "rushlight";

  4. const rushlight = await CollabClient.of({
  5.   async connection(message) {
  6.     // You can use any method to send messages to the server. This example
  7.     // executes a simple POST request.
  8.     const resp = await fetch(`/doc/${id}`, {
  9.       method: "POST",
  10.       body: JSON.stringify(message),
  11.       headers: { "Content-Type": "application/json" },
  12.     });
  13.     if (resp.status !== 200) {
  14.       throw new Error(`Request failed with status ${resp.status}`);
  15.     }
  16.     return await resp.json();
  17.   },
  18. });

  19. const view = new EditorView({
  20.   extensions: [
  21.     // ...
  22.     rushlight,
  23.     rushlight.presence, // Optional, if you want to show remote cursors.
  24.   ],
  25.   // ...
  26. });
  27. ```

Then, on the server, we need to write a corresponding handler for the POST
request. Create a CollabServer object, which requires a Redis connection
string and a persistent database for document storage.

The example below is with express, but you can use any framework.

  1. ```ts
  2. import express from "express";
  3. import { Checkpoint, CollabServer } from "rushlight-server";

  4. const rushlight = await CollabServer.of({
  5.   redisUrl: process.env.REDIS_URL || "redis://localhost:6473",
  6.   async loadCheckpoint(id: string): Promise<Checkpoint> {
  7.     // ... Load the document from your database.
  8.     return { version, doc };
  9.   },
  10.   async saveCheckpoint(id: string, { version, doc }: Checkpoint) {
  11.     // ... Save the new version of the document to your database.
  12.   },
  13. });

  14. rushlight.compactionTask(); // Run this in the background.

  15. const app = express();

  16. app.post("/doc/:id", express.json(), async (req, res) => {
  17.   const id = req.params.id;
  18.   try {
  19.     res.json(await rushlight.handle(id, req.body));
  20.   } catch (e: any) {
  21.     console.log("Failed to handle user message:", e.toString());
  22.     res.status(400).send(e.toString());
  23.   }
  24. });

  25. app.listen(8080);
  26. ```

That's it! See the ClientOptions and ServerOptions types for more
configuration options.

To view a full demo application, a collaborative Markdown editor using Postgres
to store documents, see the [app/](app/) folder in this repository.