{"id":16668474,"url":"https://github.com/ekzhang/rushlight","last_synced_at":"2025-03-17T00:31:38.034Z","repository":{"id":176356120,"uuid":"656845220","full_name":"ekzhang/rushlight","owner":"ekzhang","description":"Real-time collaborative code editing on your own infrastructure","archived":false,"fork":false,"pushed_at":"2023-06-26T06:12:50.000Z","size":229,"stargazers_count":100,"open_issues_count":0,"forks_count":8,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-05-02T00:54:12.614Z","etag":null,"topics":["codemirror","collaborative-editing","markdown","postgres","redis"],"latest_commit_sha":null,"homepage":"https://rushlight.up.railway.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ekzhang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-06-21T19:01:48.000Z","updated_at":"2024-04-22T06:05:27.000Z","dependencies_parsed_at":"2023-07-24T15:16:43.895Z","dependency_job_id":null,"html_url":"https://github.com/ekzhang/rushlight","commit_stats":null,"previous_names":["ekzhang/cm-collab","ekzhang/rushlight"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekzhang%2Frushlight","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekzhang%2Frushlight/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekzhang%2Frushlight/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekzhang%2Frushlight/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ekzhang","download_url":"https://codeload.github.com/ekzhang/rushlight/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243835951,"owners_count":20355611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codemirror","collaborative-editing","markdown","postgres","redis"],"created_at":"2024-10-12T11:25:29.646Z","updated_at":"2025-03-17T00:31:38.027Z","avatar_url":"https://github.com/ekzhang.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# 🕯️ Rushlight\n\n_Make collaborative code editors that run on your own infrastructure: just Redis\nand a database._\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://i.imgur.com/FaJUXrI.gif\" width=\"720\"\u003e\n\u003c/p\u003e\n\nSupports multiple real-time documents, with live cursors. Based on\n[CodeMirror 6](https://codemirror.net/) and\n[operational transformation](https://codemirror.net/examples/collab/), so all\nchanges are resolved by server code. It's designed to be as easy to integrate as\npossible (read: boring). The backend is stateless, and _you can bring your own\ntransport_; even a single HTTP handler is enough.\n\nUnlike most toy examples, Rushlight supports persistence in any durable database\nyou choose. Real-time updates are replicated in-memory by Redis, with automatic\nlog compaction.\n\nAn experiment by [Eric Zhang](https://www.ekzhang.com/), author of\n[Rustpad](https://github.com/ekzhang/rustpad).\n\n## Motivation\n\nLet's say you're writing a web application. You already have a database, and you\nwant to add real-time collaborative editing. However, most libraries are\nunsuitable because they:\n\n- Require proprietary gadgets\n- Are not flexible enough, e.g., to customize appearance\n- Make you subscribe to a cloud service where you can't control the data\n- Use decentralized algorithms like CRDTs that are hard to reason about\n- Make it difficult to authenticate users or apply rate limits\n- Rely on a single stateful server, which breaks with replication / horizontal\n  autoscaling\n- Need WebSockets or other protocols that aren't supported by some providers\n- Are just generally too opinionated\n\nThis library tries to take a more practical approach.\n\n## Usage\n\nInstall the client and server packages.\n\n```bash\n# client\nnpm install rushlight\n\n# server\nnpm install rushlight-server\n```\n\nOn the frontend, create a `CollabClient` object and attach it to your CodeMirror\ninstance via extensions.\n\n```ts\nimport { EditorView } from \"codemirror\";\nimport { CollabClient } from \"rushlight\";\n\nconst rushlight = await CollabClient.of({\n  async connection(message) {\n    // You can use any method to send messages to the server. This example\n    // executes a simple POST request.\n    const resp = await fetch(`/doc/${id}`, {\n      method: \"POST\",\n      body: JSON.stringify(message),\n      headers: { \"Content-Type\": \"application/json\" },\n    });\n    if (resp.status !== 200) {\n      throw new Error(`Request failed with status ${resp.status}`);\n    }\n    return await resp.json();\n  },\n});\n\nconst view = new EditorView({\n  doc: rushlight.initialDoc,\n  extensions: [\n    // ...\n    rushlight,\n    rushlight.presence, // Optional, if you want to show remote cursors.\n  ],\n  // ...\n});\n```\n\nThen, on the server, we need to write a corresponding handler for the POST\nrequest. Create a `CollabServer` object, which requires a Redis connection\nstring and a persistent database for document storage.\n\nThe example below is with `express`, but you can use any framework.\n\n```ts\nimport express from \"express\";\nimport { Checkpoint, CollabServer } from \"rushlight-server\";\n\nconst rushlight = await CollabServer.of({\n  redisUrl: process.env.REDIS_URL || \"redis://localhost:6473\",\n  async loadCheckpoint(id: string): Promise\u003cCheckpoint\u003e {\n    // ... Load the document from your database.\n    return { version, doc };\n  },\n  async saveCheckpoint(id: string, { version, doc }: Checkpoint) {\n    // ... Save the new version of the document to your database.\n  },\n});\n\nrushlight.compactionTask(); // Run this in the background.\n\nconst app = express();\n\napp.post(\"/doc/:id\", express.json(), async (req, res) =\u003e {\n  const id = req.params.id;\n  try {\n    res.json(await rushlight.handle(id, req.body));\n  } catch (e: any) {\n    console.log(\"Failed to handle user message:\", e.toString());\n    res.status(400).send(e.toString());\n  }\n});\n\napp.listen(8080);\n```\n\nThat's it! See the `ClientOptions` and `ServerOptions` types for more\nconfiguration options.\n\nTo view a full demo application, a collaborative Markdown editor using Postgres\nto store documents, see the [`app/`](app/) folder in this repository.\n\n## Development\n\nThese are instructions for developing the library itself and running the demo\napplication. Clone the repository, which is an NPM workspace. To build the\nTypeScript files, just run:\n\n```bash\nnpm install\nnpm run lint\nnpm run build\n```\n\nThe demo application requires Node.js version 18 or higher and Docker Compose.\n\n```bash\ndocker compose up\nnpm run dev\n```\n\nVisit `http://localhost:6480` in your browser.\n\n## Deployment\n\nFor the demo application:\n\n```bash\nnpm ci\nnpm run build\n\nexport REDIS_URL=redis://...\nexport DATABASE_URL=postgres://...\nnpm start -w=app\n```\n\nListens on port 6471 by default, or the `PORT` environment variable if set.\n\n## Why the name?\n\nIt comes from this quote, and the fact that rushlights are a type of makeshift\ncandle; you make do with what you have.\n\n\u003e “Early Sunday morning, Natasha and I lit a candle, looked in the mirror … They\n\u003e say you can see your future in the long row of candles, stretching back and\n\u003e back and back, into the depths of the mirror.”\n\u003e\n\u003e ”I see nothing but the candle in the mirror. No visions of the future. So lost\n\u003e and alone.”\n\u003e\n\u003e ―Dave Malloy, _Natasha, Pierre \u0026 The Great Comet of 1812_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekzhang%2Frushlight","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fekzhang%2Frushlight","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekzhang%2Frushlight/lists"}