Revise CLI menu

Collaboration

Seed, edit, and export live Yjs rooms that browsers are editing.

Every Revise document is a Yjs document. A sync server that relays a room's updates can also seed the room from a file, edit it, and export it, while people edit the same room in @reviseio/editor. Use the same version of both; revise 3 also works with rooms that @reviseio/editor 2.3 clients edit.

Seeding a room#

seedYDoc(ydoc, source) writes a document into an empty room and returns whether it wrote anything. A room that already holds a document is left alone, so call it on every connection. Two peers that both seeded an empty room before syncing are repaired to one copy.

With Hocuspocus:

import { Server } from "@hocuspocus/server";
import { seedYDoc } from "revise";

const server = new Server({
  port: 1234,
  async onLoadDocument({ document, documentName }) {
    // Load stored state first if you persist rooms, then:
    await seedYDoc(document, `documents/${documentName}.docx`);
  },
});

await server.listen();

Clients join with seed: "never" and no source file, so only the server ever seeds. A Word file is stored in the room along with its content, so any session over the room, on the server or in the editor, exports untouched content byte for byte.

Editing a live room#

import { ReviseDocument } from "revise";
import type * as Y from "yjs";

async function reviewRoom(room: Y.Doc): Promise<string[]> {
  const doc = await ReviseDocument.fromYDoc(room, { author: "Review bot" });
  try {
    const [hit] = doc.grep("Delaware");
    if (!hit) return [];
    const edit = await doc.tools.call("replace", {
      id: hit.id,
      replacements: [{ find: "Delaware", replace: "New York" }],
    });
    return edit.ok ? (edit.value.suggestionIds ?? []) : [];
  } finally {
    doc.dispose(); // the room stays alive
  }
}

Edits are tracked changes by default, so they appear in every open editor as suggestions for a person to accept or reject. Peers receive them as ordinary Yjs updates, and their carets stay in place.

Exporting a live room#

import { ReviseDocument } from "revise";
import type * as Y from "yjs";

async function downloadRoom(room: Y.Doc): Promise<Uint8Array> {
  const doc = await ReviseDocument.fromYDoc(room);
  try {
    const { bytes } = await doc.export({ format: "docx" });
    return bytes;
  } finally {
    doc.dispose();
  }
}

fromYDoc never replaces or destroys your Y.Doc: dispose() releases only the tool session, and reload() is not available on a room.

Without a live transport#

A request handler can take a client's document as one encoded update, edit it, and return only the delta, which merges into the live document like any peer's edit:

import * as Y from "yjs";
import { ReviseDocument } from "revise";

async function review(update: Uint8Array): Promise<Uint8Array> {
  const doc = await ReviseDocument.restore(update);
  try {
    const baseline = Y.encodeStateVector(doc.ydoc);
    await doc.replaceAll("Delaware", "New York");
    return Y.encodeStateAsUpdate(doc.ydoc, baseline);
  } finally {
    doc.dispose();
  }
}