Skip to main content
platxa-yjs-server Yjs WebSocket server implementation guide for real-time collaboration. Configure y-websocket, awareness cursors, and persistence with production-ready patterns.
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/platxa/platxa-skill-generator --skill platxa-yjs-server명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... SOC
name platxa-yjs-server description Yjs WebSocket server implementation guide for real-time collaboration. Configure y-websocket, awareness cursors, and persistence with production-ready patterns. allowed-tools ["Read","Glob","Grep"] suggests ["platxa-sidecar-builder"] metadata {"version":"1.0.0","tags":["guide","yjs","websocket","collaboration","crdt"]} user-invocable true
Platxa Yjs Server
Guide for implementing Yjs WebSocket servers for real-time collaborative editing in the Platxa platform.
Overview
This skill covers the server-side implementation of Yjs CRDT synchronization:
Component What You Can Configure y-websocket Server WebSocket setup, room management, connection handling Awareness Protocol User presence, cursor positions, selection highlighting
Persistence LevelDB, IndexedDB, file system, git integration
Authentication JWT validation, session management, single-user enforcement
Error Handling Reconnection, conflict resolution, graceful degradation
Workflow When implementing a Yjs server, follow this workflow:
Step 1: Choose Architecture Determine your requirements:
Single-document : One Y.Doc shared by all clients (simple chat, whiteboard)
Multi-document : Separate Y.Doc per file/room (code editor, multi-file IDE)
Auth model : Anonymous, JWT, session-based
Step 2: Setup Server Choose implementation approach:
y-websocket utils : Use built-in setupWSConnection for quick start
Custom server : Build on raw WebSocket for full control
Step 3: Configure Awareness Add user presence features:
Set local state (user id, name, color)
Handle awareness updates from other clients
Render cursor decorations in editor
Step 4: Add Persistence Select storage backend based on needs:
Development : In-memory (default, no persistence)
Production : y-leveldb for Node.js server persistence
Client-side : y-indexeddb for offline support
Audit trail : Git commits on file save
Quick Start
Basic Server (Node.js) import { WebSocketServer } from 'ws' ;
import { setupWSConnection } from 'y-websocket/bin/utils' ;
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , (ws, req ) => {
setupWSConnection (ws, req);
});
console .log ('Yjs server running on ws://localhost:1234' );
Basic Client import * as Y from 'yjs' ;
import { WebsocketProvider } from 'y-websocket' ;
const doc = new Y.Doc ();
const provider = new WebsocketProvider (
'ws://localhost:1234' ,
'my-room' ,
doc
);
const yText = doc.getText ('content' );
provider.on ('sync' , (synced : boolean ) => {
console .log ('Synced:' , synced);
});
Server Configuration Presets
Basic (Development) Minimal setup for local development:
import { WebSocketServer } from 'ws' ;
import { setupWSConnection } from 'y-websocket/bin/utils' ;
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , setupWSConnection);
Authenticated JWT validation before allowing connection:
import { WebSocketServer } from 'ws' ;
import { setupWSConnection } from 'y-websocket/bin/utils' ;
import jwt from 'jsonwebtoken' ;
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , (ws, req ) => {
const token = req.headers ['sec-websocket-protocol' ];
try {
const payload = jwt.verify (token, process.env .JWT_SECRET );
(ws as any ).user = payload;
setupWSConnection (ws, req);
} catch (err) {
ws.close (4001 , 'Unauthorized' );
}
});
Persistent (LevelDB) Documents survive server restarts:
import { WebSocketServer } from 'ws' ;
import { setupWSConnection, setPersistence } from 'y-websocket/bin/utils' ;
import { LeveldbPersistence } from 'y-leveldb' ;
const ldb = new LeveldbPersistence ('./yjs-data' );
setPersistence ({
bindState : async (docName, ydoc) => {
const persistedYdoc = await ldb.getYDoc (docName);
const state = Y.encodeStateAsUpdate (persistedYdoc);
Y.applyUpdate (ydoc, state);
ydoc.on ('update' , (update ) => {
ldb.storeUpdate (docName, update);
});
},
writeState : async (docName, ydoc) => {
}
});
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , setupWSConnection);
Platxa Production Full setup with authentication, single-user, and file persistence:
import { WebSocketServer } from 'ws' ;
import * as Y from 'yjs' ;
import { encoding, decoding, syncProtocol, awarenessProtocol } from 'y-protocols' ;
import fs from 'fs/promises' ;
const docs = new Map <string , Y.Doc >();
const sessions = new Map <string , { userId : string ; expiry : number }>();
async function handleConnection (ws, req, user ) {
const docPath = req.url .replace ('/ws/doc/' , '' );
if (user.client_type !== 'ai' ) {
const existing = sessions.get (docPath);
if (existing && existing.userId !== user.id && existing.expiry > Date .now ()) {
ws.close (4003 , 'Document locked by another user' );
return ;
}
sessions.set (docPath, { userId : user.id , expiry : Date .now () + 30000 });
}
let doc = docs.get (docPath);
if (!doc) {
doc = new Y.Doc ();
try {
const content = await fs.readFile (`/mnt/addons/${docPath} ` , 'utf-8' );
doc.getText ('content' ).insert (0 , content);
} catch (e) {
}
docs.set (docPath, doc);
}
}
Awareness Protocol
Setting User Presence
provider.awareness .setLocalState ({
id : user.id ,
name : user.name ,
color : generateColor (user.id ),
colorLight : generateColor (user.id , 0.9 ),
cursor : null
});
function generateColor (id : string , lightness = 0.45 ) {
const hue = hashString (id) % 360 ;
return `hsl(${hue} , 70%, ${lightness * 100 } %)` ;
}
Reading Connected Users function getConnectedUsers (provider : WebsocketProvider ) {
const users : UserPresence [] = [];
provider.awareness .getStates ().forEach ((state, clientId ) => {
if (state && clientId !== provider.awareness .clientID ) {
users.push (state as UserPresence );
}
});
return users;
}
provider.awareness .on ('change' , () => {
const users = getConnectedUsers (provider);
updateUserList (users);
});
Cursor Decorations (Monaco) import { MonacoBinding } from 'y-monaco' ;
const binding = new MonacoBinding (
yText,
editor.getModel (),
new Set ([editor]),
provider.awareness
);
Examples
Example 1: Collaborative Text Editor User : "Set up a simple Yjs server for collaborative text editing"
import { WebSocketServer } from 'ws' ;
import { setupWSConnection } from 'y-websocket/bin/utils' ;
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , (ws, req ) => {
console .log ('Client connected' );
setupWSConnection (ws, req);
});
import * as Y from 'yjs' ;
import { WebsocketProvider } from 'y-websocket' ;
const doc = new Y.Doc ();
const provider = new WebsocketProvider ('ws://localhost:1234' , 'document' , doc);
provider.awareness .setLocalState ({
name : 'User ' + Math .floor (Math .random () * 100 ),
color : `hsl(${Math .random() * 360 } , 70%, 45%)`
});
const yText = doc.getText ('content' );
yText.observe (() => {
console .log ('Content:' , yText.toString ());
});
Example 2: Multi-Room Document Server User : "Handle multiple documents with JWT auth"
import { WebSocketServer } from 'ws' ;
import * as Y from 'yjs' ;
import jwt from 'jsonwebtoken' ;
const docs = new Map <string , Y.Doc >();
function getDoc (roomName : string ): Y.Doc {
if (!docs.has (roomName)) {
docs.set (roomName, new Y.Doc ());
}
return docs.get (roomName)!;
}
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , (ws, req ) => {
const token = req.headers ['sec-websocket-protocol' ];
try {
const user = jwt.verify (token, process.env .JWT_SECRET ) as any ;
const roomName = new URL (req.url , 'http://localhost' ).searchParams .get ('room' );
if (!roomName) {
ws.close (4000 , 'Room name required' );
return ;
}
const doc = getDoc (roomName);
} catch (err) {
ws.close (4001 , 'Invalid token' );
}
});
Example 3: Persistent Server with Git Commits User : "Documents should persist and have version history"
import { WebSocketServer } from 'ws' ;
import { LeveldbPersistence } from 'y-leveldb' ;
import { setPersistence, setupWSConnection } from 'y-websocket/bin/utils' ;
import simpleGit from 'simple-git' ;
import fs from 'fs/promises' ;
const ldb = new LeveldbPersistence ('./yjs-data' );
const git = simpleGit ('./workspace' );
setPersistence ({
bindState : async (docName, ydoc) => {
const persisted = await ldb.getYDoc (docName);
Y.applyUpdate (ydoc, Y.encodeStateAsUpdate (persisted));
ydoc.on ('update' , async (update) => {
await ldb.storeUpdate (docName, update);
});
},
writeState : async (docName, ydoc) => {
const content = ydoc.getText ('content' ).toString ();
const filePath = `./workspace/${docName} ` ;
await fs.writeFile (filePath, content);
await git.add (filePath);
await git.commit (`Update ${docName} ` , { '--author' : 'Yjs Server <yjs@local>' });
}
});
const wss = new WebSocketServer ({ port : 1234 });
wss.on ('connection' , setupWSConnection);
Best Practices
Security Practice Implementation Auth via header Use Sec-WebSocket-Protocol, not query params Validate JWT Check signature, expiry, and claims Rate limiting Limit connections per IP/user Path validation Prevent directory traversal in doc names
Performance Practice Implementation One Y.Doc per file Don't share docs across unrelated content Debounce persistence Write to disk every 300ms, not every update Cleanup on disconnect Destroy Y.Doc when last client leaves Monitor memory Track doc count and memory usage
Reliability Practice Implementation Exponential backoff Client reconnection: 1s, 2s, 4s, 8s... max 30s Client persistence Use y-indexeddb for offline support Server persistence Use y-leveldb for crash recovery Audit trail Git commits for version history
Troubleshooting
Connection Not Established Symptom : WebSocket connection fails immediately
CORS : Ensure server allows origin
Auth format : Use Sec-WebSocket-Protocol header, not Bearer
Protocol : y-websocket v2 vs v3 incompatibility
Awareness Not Syncing Symptom : Can't see other users' cursors
Pass awareness to MonacoBinding: new MonacoBinding(yText, model, editors, awareness)
Verify provider connected: provider.wsconnected
Check local state set: provider.awareness.getLocalState()
Documents Empty After Restart Symptom : All content lost on server restart
Add persistence layer (y-leveldb)
Check LevelDB path exists and is writable
Verify bindState loads persisted content
Memory Growing Over Time Symptom : Node.js process memory increasing
Track active docs, destroy on last client leave
Set maximum doc count, evict LRU
Check for event listener leaks (remove on cleanup)
Output Checklist After implementing Yjs server, verify:
Related Resources
Monaco Integration : Use platxa-monaco-config skill for editor setup
Frontend Hooks : See YjsProvider, useYjsDocument, useMonacoBinding
Awareness Details : See references/awareness-protocol.md
Persistence Options : See references/persistence-options.md
Protocol Reference : See references/websocket-api.md
이 저장소의 다른 Skills Autonomous skill creator for Claude Code CLI. Use when the user asks to "create a skill", "generate a skill", or "build a new slash command". Uses multi-phase orchestrated workflow with Task tool subagents to research domains, design architecture, generate content, validate quality, and iteratively improve via eval infrastructure. Creates production-ready skills following the Agent Skills open standard and Claude Code extensions.
Automatically generates and improves code documentation across Python, JavaScript/TypeScript, Java, Go, and Rust. Detects language conventions, applies appropriate docstring standards, and validates documentation quality.
Generate conventional commit messages by analyzing staged git changes. Detects change type, suggests scope, and formats messages following the Conventional Commits specification.