@rcap/realtime
v0.1.0
Published
Real-time multiplayer rooms powered by CRDTs. Connect users, sync state, track presence.
Maintainers
Readme
@rcap/realtime
Real-time multiplayer rooms powered by CRDTs. Add collaborative features to any application with minimal code.
- Shared State - Conflict-free data structures that sync automatically across all clients
- Presence - Track who's connected and their live state (cursors, selections, etc.)
- Ephemeral Events - Fire-and-forget broadcasts for transient actions
- Yjs Compatible - Works with the entire Yjs ecosystem (y-codemirror, y-prosemirror, y-quill, y-monaco)
- Auto Reconnect - Exponential backoff, pending queue, seamless recovery
- Persistence - Optional IndexedDB for offline support
Quick Start
npm install @rcap/realtime yjsimport { createRoom } from '@rcap/realtime';
const room = createRoom({
apiKey: 'your-api-key',
roomId: 'my-room',
user: { id: 'user-1', name: 'Alice' },
});
await room.connect();Shared State
State is stored in CRDT data structures that automatically merge across clients - no conflict resolution needed.
// Key-value map
const settings = room.map('settings');
settings.set('theme', 'dark');
settings.get('theme'); // 'dark'
settings.observe((changes) => {
console.log('changed keys:', Object.keys(changes.changed));
});
// Ordered list
const items = room.array('items');
items.push({ id: 1, text: 'First item' });
items.observe((changes) => {
console.log('array updated:', items.toArray());
});
// Text (for editor bindings)
const text = room.doc.getText('content');Presence
Track connected users and their live state.
// Set your state (visible to all other users)
room.presence.set({ cursor: { x: 100, y: 200 }, status: 'editing' });
// Update specific fields
room.presence.update({ cursor: { x: 150, y: 250 } });
// Watch others
room.presence.subscribe((others) => {
for (const user of others) {
console.log(user.user.name, user.state.cursor);
}
});Ephemeral Events
Broadcast events that are delivered to all connected clients but not persisted.
// Send
room.broadcast('reaction', { emoji: 'thumbsup', from: 'Alice' });
// Receive
room.on('reaction', (data, sender) => {
console.log(`${sender.name} reacted with ${data.emoji}`);
});Editor Integrations
The underlying Y.Doc is exposed directly, so any Yjs editor binding works out of the box.
// CodeMirror 6
import { yCollab } from 'y-codemirror.next';
const ytext = room.doc.getText('editor');
// Use yCollab(ytext, awareness) in your extensions
// ProseMirror
import { ySyncPlugin } from 'y-prosemirror';
const ydoc = room.doc;
// Use ySyncPlugin(ydoc.getXmlFragment('prosemirror'))API
createRoom(options)
| Option | Type | Description |
|--------|------|-------------|
| apiKey | string | API key for authentication |
| roomId | string | Unique room identifier |
| user | { id, name, color?, avatar? } | Your user info |
| baseUrl | string | Server URL (default: wss://sync.elvenvtt.com) |
| persistence | boolean | Enable IndexedDB persistence (default: true in browser) |
Room
| Method | Description |
|--------|-------------|
| room.connect() | Connect to the room (returns promise) |
| room.disconnect() | Disconnect from the room |
| room.map(name) | Get a SharedMap |
| room.array(name) | Get a SharedArray |
| room.doc | Raw Y.Doc for editor bindings |
| room.transact(fn) | Batch mutations in a single update |
| room.broadcast(event, data) | Send ephemeral event |
| room.on(event, callback) | Listen for ephemeral events |
| room.presence | Presence instance |
| room.onReconnect(fn) | Reconnection callback |
| room.onDisconnect(fn) | Disconnection callback |
| room.getHealth() | Connection health info |
SharedMap
| Method | Description |
|--------|-------------|
| map.get(key) | Get value |
| map.set(key, value) | Set value |
| map.delete(key) | Delete key |
| map.has(key) | Check if key exists |
| map.toJSON() | Get all entries as object |
| map.observe(callback) | Watch for changes |
SharedArray
| Method | Description |
|--------|-------------|
| array.push(...items) | Append items |
| array.insert(index, ...items) | Insert at index |
| array.delete(index, count?) | Remove items |
| array.get(index) | Get item at index |
| array.toArray() | Get all items |
| array.observe(callback) | Watch for changes |
License
MIT
