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
CodeMirror 6 and
operational transformation, so all
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.
- ```bash
- # client
- npm install rushlight
- # server
- npm install rushlight-server
- ```
On the frontend, create a CollabClient object and attach it to your CodeMirror
instance via extensions.
- ```ts
- import { EditorView } from "codemirror";
- import { CollabClient } from "rushlight";
- const rushlight = await CollabClient.of({
- async connection(message) {
- // You can use any method to send messages to the server. This example
- // executes a simple POST request.
- const resp = await fetch(`/doc/${id}`, {
- method: "POST",
- body: JSON.stringify(message),
- headers: { "Content-Type": "application/json" },
- });
- if (resp.status !== 200) {
- throw new Error(`Request failed with status ${resp.status}`);
- }
- return await resp.json();
- },
- });
- const view = new EditorView({
- extensions: [
- // ...
- rushlight,
- rushlight.presence, // Optional, if you want to show remote cursors.
- ],
- // ...
- });
- ```
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.
- ```ts
- import express from "express";
- import { Checkpoint, CollabServer } from "rushlight-server";
- const rushlight = await CollabServer.of({
- redisUrl: process.env.REDIS_URL || "redis://localhost:6473",
- async loadCheckpoint(id: string): Promise<Checkpoint> {
- // ... Load the document from your database.
- return { version, doc };
- },
- async saveCheckpoint(id: string, { version, doc }: Checkpoint) {
- // ... Save the new version of the document to your database.
- },
- });
- rushlight.compactionTask(); // Run this in the background.
- const app = express();
- app.post("/doc/:id", express.json(), async (req, res) => {
- const id = req.params.id;
- try {
- res.json(await rushlight.handle(id, req.body));
- } catch (e: any) {
- console.log("Failed to handle user message:", e.toString());
- res.status(400).send(e.toString());
- }
- });
- app.listen(8080);
- ```
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.