electric-yjs

This skill builds on electric-shapes. Read it first for ShapeStream configuration.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "electric-yjs" with this command: npx skills add electric-sql/electric/electric-sql-electric-electric-yjs

This skill builds on electric-shapes. Read it first for ShapeStream configuration.

Electric — Yjs Collaboration

Setup

  1. Create Postgres tables

CREATE TABLE ydoc_update ( id SERIAL PRIMARY KEY, room TEXT NOT NULL, update BYTEA NOT NULL );

CREATE TABLE ydoc_awareness ( client_id TEXT, room TEXT, update BYTEA NOT NULL, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (client_id, room) );

-- Garbage collect stale awareness entries CREATE OR REPLACE FUNCTION gc_awareness_timeouts() RETURNS TRIGGER AS $$ BEGIN DELETE FROM ydoc_awareness WHERE updated_at < (CURRENT_TIMESTAMP - INTERVAL '30 seconds') AND room = NEW.room; RETURN NEW; END; $$ LANGUAGE plpgsql;

CREATE TRIGGER gc_awareness AFTER INSERT OR UPDATE ON ydoc_awareness FOR EACH ROW EXECUTE FUNCTION gc_awareness_timeouts();

  1. Create server endpoint for receiving updates

// PUT /api/yjs/update — receives binary Yjs update app.put('/api/yjs/update', async (req, res) => { const body = Buffer.from(await req.arrayBuffer()) await db.query('INSERT INTO ydoc_update (room, update) VALUES ($1, $2)', [ req.headers['x-room-id'], body, ]) res.status(200).end() })

  1. Configure ElectricProvider

import * as Y from 'yjs' import { ElectricProvider, LocalStorageResumeStateProvider, parseToDecoder, } from '@electric-sql/y-electric'

const ydoc = new Y.Doc() const roomId = 'my-document'

const resumeProvider = new LocalStorageResumeStateProvider(roomId)

const provider = new ElectricProvider({ doc: ydoc, documentUpdates: { shape: { url: /api/yjs/doc-shape?room=${roomId}, parser: parseToDecoder, }, sendUrl: '/api/yjs/update', getUpdateFromRow: (row) => row.update, }, awarenessUpdates: { shape: { url: /api/yjs/awareness-shape?room=${roomId}, parser: parseToDecoder, offset: 'now', // Only live awareness, no historical backfill }, sendUrl: '/api/yjs/awareness', protocol: provider.awareness, getUpdateFromRow: (row) => row.update, }, resumeState: resumeProvider.load(), debounceMs: 100, // Batch rapid edits })

// Persist resume state for efficient reconnection resumeProvider.subscribeToResumeState(provider)

Core Patterns

CORS headers for Yjs proxy

// Proxy must expose Electric headers const corsHeaders = { 'Access-Control-Expose-Headers': 'electric-offset, electric-handle, electric-schema, electric-cursor', }

Resume state for reconnection

// On construction, pass stored resume state const provider = new ElectricProvider({ doc: ydoc, documentUpdates: { shape: shapeOpts, sendUrl: '/api/yjs/update' }, resumeState: resumeProvider.load(), })

// Subscribe to persist updates const unsub = resumeProvider.subscribeToResumeState(provider)

// Clean up provider.destroy() unsub()

When stableStateVector is provided in resume state, the provider sends only the diff between the stored vector and current doc state on reconnect.

Connection lifecycle

provider.on('status', ({ status }) => { // 'connecting' | 'connected' | 'disconnected' console.log('Yjs sync status:', status) })

provider.on('sync', (synced: boolean) => { console.log('Document synced:', synced) })

// Manual disconnect/reconnect provider.disconnect() provider.connect()

Common Mistakes

HIGH Not persisting resume state for reconnection

Wrong:

const provider = new ElectricProvider({ doc: ydoc, documentUpdates: { shape: { url: '/api/yjs/doc-shape', parser: parseToDecoder }, sendUrl: '/api/yjs/update', getUpdateFromRow: (row) => row.update, }, })

Correct:

const resumeProvider = new LocalStorageResumeStateProvider('my-doc') const provider = new ElectricProvider({ doc: ydoc, documentUpdates: { shape: { url: '/api/yjs/doc-shape', parser: parseToDecoder }, sendUrl: '/api/yjs/update', getUpdateFromRow: (row) => row.update, }, resumeState: resumeProvider.load(), }) resumeProvider.subscribeToResumeState(provider)

Without resumeState , the provider fetches the ENTIRE document shape on every reconnect. With stableStateVector , only a diff is sent.

Source: packages/y-electric/src/types.ts:102-112

HIGH Missing BYTEA parser for shape streams

Wrong:

documentUpdates: { shape: { url: '/api/yjs/doc-shape' }, sendUrl: '/api/yjs/update', getUpdateFromRow: (row) => row.update, }

Correct:

import { parseToDecoder } from '@electric-sql/y-electric'

documentUpdates: { shape: { url: '/api/yjs/doc-shape', parser: parseToDecoder, }, sendUrl: '/api/yjs/update', getUpdateFromRow: (row) => row.update, }

Yjs updates are stored as BYTEA in Postgres. Without parseToDecoder , the shape returns raw hex strings instead of lib0 Decoders, and Y.applyUpdate fails silently or corrupts the document.

Source: packages/y-electric/src/utils.ts

MEDIUM Not setting debounceMs for collaborative editing

Wrong:

const provider = new ElectricProvider({ doc: ydoc, documentUpdates: { shape: shapeOpts, sendUrl: '/api/yjs/update' }, // Default debounceMs = 0: every keystroke sends a PUT })

Correct:

const provider = new ElectricProvider({ doc: ydoc, documentUpdates: { shape: shapeOpts, sendUrl: '/api/yjs/update' }, debounceMs: 100, })

Default debounceMs is 0, sending a PUT request for every keystroke. Set to 100+ to batch rapid edits and reduce server load.

Source: packages/y-electric/src/y-electric.ts

See also: electric-shapes/SKILL.md — Shape configuration and parser setup.

Version

Targets @electric-sql/y-electric v0.1.x.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

blog-planner

No summary provided by upstream source.

Repository SourceNeeds Review
General

electric-proxy-auth

No summary provided by upstream source.

Repository SourceNeeds Review
General

electric-shapes

No summary provided by upstream source.

Repository SourceNeeds Review
General

electric-debugging

No summary provided by upstream source.

Repository SourceNeeds Review