yjs
Expert guidance for Yjs, the high-performance CRDT (Conflict-free Replicated Data Type) framework for building collaborative applications. Helps developers implement real-time document editing, offline-first sync, and peer-to-peer collaboration with automatic conflict resolution.
What this skill does
# Yjs — CRDT Framework for Collaborative Editing
## Overview
Yjs, the high-performance CRDT (Conflict-free Replicated Data Type) framework for building collaborative applications. Helps developers implement real-time document editing, offline-first sync, and peer-to-peer collaboration with automatic conflict resolution.
## Instructions
### Document and Shared Types
Create collaborative data structures that merge automatically:
```typescript
// src/collaboration/document.ts — Set up a collaborative document with shared types
import * as Y from "yjs";
// A Y.Doc is the top-level container for all shared data
// Every connected client gets a copy that stays in sync
const doc = new Y.Doc();
// Y.Text — collaborative rich text (used with editors like Tiptap, ProseMirror)
const yText = doc.getText("document-content");
yText.insert(0, "Hello, ");
yText.insert(7, "world!");
// Result: "Hello, world!" — inserts merge correctly even if concurrent
// Y.Map — collaborative key-value store (like a shared object)
const yMap = doc.getMap("settings");
yMap.set("theme", "dark");
yMap.set("fontSize", 14);
// Two users setting different keys: both applied
// Two users setting the same key: last-write-wins (deterministic)
// Y.Array — collaborative ordered list
const yArray = doc.getArray("tasks");
yArray.push([{ id: "1", title: "Design mockup", done: false }]);
yArray.push([{ id: "2", title: "Implement API", done: false }]);
// Concurrent inserts at different positions: both preserved in correct order
// Y.XmlFragment — collaborative XML tree (for rich text editors)
const yXml = doc.getXmlFragment("rich-content");
// Used internally by editor bindings (Tiptap, ProseMirror, Slate)
// Nested structures — Y types can be nested arbitrarily
const yNestedMap = new Y.Map();
yNestedMap.set("status", "active");
yMap.set("project", yNestedMap); // Map inside a map
```
### WebSocket Provider
Connect clients through a WebSocket server for real-time sync:
```typescript
// src/collaboration/provider.ts — WebSocket-based real-time sync
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
const doc = new Y.Doc();
// Connect to a y-websocket server
// All clients in the same room sync automatically
const provider = new WebsocketProvider(
"wss://your-yjs-server.example.com", // WebSocket server URL
"document-room-123", // Room name — clients in same room sync
doc,
{
connect: true, // Auto-connect on creation
params: { token: "auth-token-here" }, // Auth params sent on connect
}
);
// Awareness — lightweight presence data (cursors, selections, user info)
// Unlike document state, awareness is ephemeral (not persisted)
const awareness = provider.awareness;
awareness.setLocalStateField("user", {
name: "Alice",
color: "#ff5733",
cursor: null,
});
// Listen to other users' awareness changes
awareness.on("change", () => {
const states = awareness.getStates(); // Map<clientId, state>
states.forEach((state, clientId) => {
if (clientId !== doc.clientID) {
console.log(`User ${state.user?.name} is connected`);
}
});
});
// Connection status
provider.on("status", ({ status }: { status: string }) => {
console.log(`Connection: ${status}`); // "connecting" | "connected" | "disconnected"
});
// Sync status — fires when initial sync with server is complete
provider.on("sync", (isSynced: boolean) => {
if (isSynced) console.log("Document fully synced with server");
});
```
### Server-Side Setup
Run a y-websocket server for document persistence:
```typescript
// server/yjs-server.ts — WebSocket server with persistence
import { WebSocketServer } from "ws";
import { setupWSConnection, setPersistence } from "y-websocket/bin/utils";
import * as Y from "yjs";
import { MongodbPersistence } from "y-mongodb-provider";
const wss = new WebSocketServer({ port: 1234 });
// Persist documents to MongoDB (survives server restarts)
const mdb = new MongodbPersistence(process.env.MONGODB_URL!, {
collectionName: "yjs-documents",
flushSize: 100, // Batch 100 updates before flushing to DB
multipleCollections: true, // Separate collection per document for performance
});
setPersistence({
bindState: async (docName: string, ydoc: Y.Doc) => {
// Load existing document state from MongoDB
const persistedDoc = await mdb.getYDoc(docName);
const persistedState = Y.encodeStateAsUpdate(persistedDoc);
Y.applyUpdate(ydoc, persistedState);
// Save updates as they happen
ydoc.on("update", (update: Uint8Array) => {
mdb.storeUpdate(docName, update);
});
},
writeState: async (docName: string, ydoc: Y.Doc) => {
// Called when all clients disconnect — final persistence
await mdb.flushDocument(docName);
},
});
wss.on("connection", (ws, req) => {
// Authenticate the connection
const token = new URL(req.url!, "http://localhost").searchParams.get("token");
if (!verifyToken(token)) {
ws.close(4001, "Unauthorized");
return;
}
setupWSConnection(ws, req);
});
console.log("Yjs WebSocket server running on port 1234");
```
### Editor Integration (Tiptap)
Add collaborative editing to a Tiptap rich text editor:
```tsx
// src/components/CollaborativeEditor.tsx — Tiptap with Yjs collaboration
import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import Collaboration from "@tiptap/extension-collaboration";
import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
interface Props {
documentId: string;
userName: string;
userColor: string;
}
export function CollaborativeEditor({ documentId, userName, userColor }: Props) {
const doc = useMemo(() => new Y.Doc(), []);
const provider = useMemo(
() => new WebsocketProvider("wss://yjs.example.com", documentId, doc),
[doc, documentId]
);
const editor = useEditor({
extensions: [
StarterKit.configure({
history: false, // Disable default history — Yjs handles undo/redo
}),
Collaboration.configure({
document: doc, // Bind editor content to Yjs document
}),
CollaborationCursor.configure({
provider, // Share cursor positions via awareness
user: { name: userName, color: userColor },
}),
],
});
// Clean up on unmount
useEffect(() => {
return () => {
provider.destroy();
doc.destroy();
};
}, [doc, provider]);
return (
<div className="editor-container">
<EditorContent editor={editor} />
<ConnectionStatus provider={provider} />
</div>
);
}
function ConnectionStatus({ provider }: { provider: WebsocketProvider }) {
const [status, setStatus] = useState("connecting");
useEffect(() => {
const handler = ({ status }: { status: string }) => setStatus(status);
provider.on("status", handler);
return () => provider.off("status", handler);
}, [provider]);
return (
<div className={`status-badge ${status}`}>
{status === "connected" ? "🟢 Connected" : "🔴 Reconnecting..."}
</div>
);
}
```
### Offline Support and Sync
Handle offline editing with automatic merge on reconnect:
```typescript
// src/collaboration/offline.ts — IndexedDB persistence for offline support
import * as Y from "yjs";
import { IndexeddbPersistence } from "y-indexeddb";
import { WebsocketProvider } from "y-websocket";
const doc = new Y.Doc();
// IndexedDB provider — saves document locally in the browser
// Changes made offline are preserved and synced when reconnected
const indexedDb = new IndexeddbPersistence("my-app-docs", doc);
indexedDb.on("synced", () => {
console.log("Local data loaded from IndexedDB");
});
// WebSocket provider — syncs with other clients when online
const wsProvider = new WebsocketProvider("wss://yjs.example.com", "doc-123", doc);
// The two providers work together:
/Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.