Collaborative editing

Apply anchored text, formatting, image, and table operations through Yjs.

Install the optional adapter

Terminal
bun add @tumblerjs/word yjs

Import collaboration APIs from @tumblerjs/word/collaboration. Yjs is an optional peer dependency; the native document engine and package APIs work without it. The host owns authenticated transport, permissions, persistence, and presence.

Apply an anchored transaction

TypeScript
import * as Y from 'yjs';
import { createWordCommands } from '@tumblerjs/word/collaboration';

const shared = new Y.Doc();
const body = shared.getText('body');
body.insert(0, 'Hello world\n');
const commands = createWordCommands({ defaultFontSize: 12 });
const target = commands.anchorWordRange(body, { start: 0, end: 5 });
const delta = commands.wordTransaction(body, [{
  kind: 'replace', target, expected: 'Hello', value: 'Welcome',
}]);
shared.transact(() => body.applyDelta(delta), 'local-edit');

Anchors use Yjs character identities rather than XML element IDs. The adapter preflights the whole batch and rejects stale expected text, invalid ranges, split graphemes, and unsupported structure. Apply the returned delta synchronously to the same document state.

Commands accept any named top-level Y.Text, so body and story streams can have independent histories.

Change kindPurpose
replace / formatReplace anchored text or patch text and paragraph attributes.
image / image-update / image-moveInsert an image, change its metadata, or move it to an anchored destination.
table / table-editInsert a table or apply a supported structural table action.
table-formatChange cell, row, column, or table formatting without replacing cell text.

Table changes identify tableId and cellId as well as the anchored target. A table-format patch selects its scope and can carry cell, row, and columnWidths changes. Images use the adapter DocumentImage metadata; hosts own asset bytes and validate their references.

Retain imported document structure

TypeScript
import {
  openWordArtifact, importWordContent,
  createWordSourceManifest, NativeWordDocument,
} from '@tumblerjs/word';

const source = openWordArtifact(bytes);
const content = importWordContent(source);
const manifest = createWordSourceManifest(source);
const model = new NativeWordDocument({ source });
model.update(content);
const exported = model.artifact().bytes();

Keep the original source artifact and its manifest immutable. Source IDs refer to that original package, including across later exports. The host maps shared text and attributes to WordContentBlock trees, then calls model.update with the current tree.

The adapter does not automatically convert arbitrary Y.Text attributes into a complete imported document.

Source references retain styles, sections, links, numbering, table properties, drawings, and opaque XML during supported edits. Reusing source on a split paragraph represents a continuation. An independent copied paragraph or run fragment needs a new sourceCopy identity.

Repeated source tables and rows are detected as copies.

Validate host-owned data

createWordCommands accepts validateAttributes and validateUpdate hooks for host schemas and immutable asset/source references. validateDocument(staged, original) inspects the preflight replica without decoding another update. It runs synchronously, must not mutate either document, and can throw to reject the batch.

If both document and encoded-update hooks exist, both run.

defaultAttributes(text, range) supplies formatting defaults for the selected document range, including paragraph-style inheritance. A replace change can include a format patch so replacement text and its formatting commit together.

Built-in operation validation is not an authorization system: enforce permissions and validate incoming remote updates at the host boundary.

Next Find and replace