# Collaboration

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

Every Revise document is a [Yjs](https://yjs.dev) 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`](https://revise.io/developer/docs/editor-sdk/guides/collaboration). 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](https://tiptap.dev/docs/hocuspocus):

```ts
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

```ts
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

```ts
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:

```ts
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();
  }
}
```
