electric-yjs
Set up ElectricProvider for real-time collaborative editing with Yjs via Electric shapes. Covers ElectricProvider configuration, document updates shape with BYTEA parser (parseToDecoder), awareness shape at offset='now', LocalStorageResumeStateProvider for reconnection with stableStateVector diff, debounceMs for batching writes, sendUrl PUT endpoint, required Postgres schema (ydoc_update and ydoc_awareness tables), CORS header exposure, and sendErrorRetryHandler. Load when implementing collaborative editing with Yjs and Electric.
Best use case
electric-yjs is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Set up ElectricProvider for real-time collaborative editing with Yjs via Electric shapes. Covers ElectricProvider configuration, document updates shape with BYTEA parser (parseToDecoder), awareness shape at offset='now', LocalStorageResumeStateProvider for reconnection with stableStateVector diff, debounceMs for batching writes, sendUrl PUT endpoint, required Postgres schema (ydoc_update and ydoc_awareness tables), CORS header exposure, and sendErrorRetryHandler. Load when implementing collaborative editing with Yjs and Electric.
Teams using electric-yjs should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/electric-yjs/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How electric-yjs Compares
| Feature / Agent | electric-yjs | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Set up ElectricProvider for real-time collaborative editing with Yjs via Electric shapes. Covers ElectricProvider configuration, document updates shape with BYTEA parser (parseToDecoder), awareness shape at offset='now', LocalStorageResumeStateProvider for reconnection with stableStateVector diff, debounceMs for batching writes, sendUrl PUT endpoint, required Postgres schema (ydoc_update and ydoc_awareness tables), CORS header exposure, and sendErrorRetryHandler. Load when implementing collaborative editing with Yjs and Electric.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
This skill builds on electric-shapes. Read it first for ShapeStream configuration.
# Electric — Yjs Collaboration
## Setup
### 1. Create Postgres tables
```sql
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();
```
### 2. Create server endpoint for receiving updates
```ts
// 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()
})
```
### 3. Configure ElectricProvider
```ts
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
```ts
// Proxy must expose Electric headers
const corsHeaders = {
'Access-Control-Expose-Headers':
'electric-offset, electric-handle, electric-schema, electric-cursor',
}
```
### Resume state for reconnection
```ts
// 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
```ts
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:
```ts
const provider = new ElectricProvider({
doc: ydoc,
documentUpdates: {
shape: { url: '/api/yjs/doc-shape', parser: parseToDecoder },
sendUrl: '/api/yjs/update',
getUpdateFromRow: (row) => row.update,
},
})
```
Correct:
```ts
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:
```ts
documentUpdates: {
shape: { url: '/api/yjs/doc-shape' },
sendUrl: '/api/yjs/update',
getUpdateFromRow: (row) => row.update,
}
```
Correct:
```ts
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:
```ts
const provider = new ElectricProvider({
doc: ydoc,
documentUpdates: { shape: shapeOpts, sendUrl: '/api/yjs/update' },
// Default debounceMs = 0: every keystroke sends a PUT
})
```
Correct:
```ts
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.Related Skills
electric-shapes
Configure ShapeStream and Shape to sync a Postgres table to the client. Covers ShapeStreamOptions (url, table, where, columns, replica, offset, handle), custom type parsers (timestamptz, jsonb, int8), column mappers (snakeCamelMapper, createColumnMapper), onError retry semantics, backoff options, log modes (full, changes_only), requestSnapshot, fetchSnapshot, subscribe/unsubscribe, and Shape materialized view. Load when setting up sync, configuring shapes, parsing types, or handling sync errors.
electric-schema-shapes
Design Postgres schema and Electric shape definitions together for a new feature. Covers single-table shape constraint, cross-table joins using multiple shapes, WHERE clause design for tenant isolation, column selection for bandwidth optimization, replica mode choice (default vs full for old_value), enum casting in WHERE clauses, and txid handshake setup with pg_current_xact_id() for optimistic writes. Load when designing database tables for use with Electric shapes.
electric-proxy-auth
Set up a server-side proxy to forward Electric shape requests securely. Covers ELECTRIC_PROTOCOL_QUERY_PARAMS forwarding, server-side shape definition (table, where, params), content-encoding/content-length header cleanup, CORS configuration for electric-offset/electric-handle/ electric-schema/electric-cursor headers, auth token injection, ELECTRIC_SECRET/SOURCE_SECRET server-side only, tenant isolation via WHERE positional params, onError 401 token refresh, and subset security (AND semantics). Load when creating proxy routes, adding auth, or configuring CORS for Electric.
electric-postgres-security
Pre-deploy security checklist for Postgres with Electric. Checks REPLICATION role, SELECT grants, CREATE on database, table ownership, REPLICA IDENTITY FULL on all synced tables, publication management (auto vs manual with ELECTRIC_MANUAL_TABLE_PUBLISHING), connection pooler exclusion for DATABASE_URL (use direct connection), and ELECTRIC_POOLED_DATABASE_URL for pooled queries. Load before deploying Electric to production or when diagnosing Postgres permission errors.
electric-orm
Use Electric with Drizzle ORM or Prisma for the write path. Covers getting pg_current_xact_id() from ORM transactions using Drizzle tx.execute(sql) and Prisma $queryRaw, running migrations that preserve REPLICA IDENTITY FULL, and schema management patterns compatible with Electric shapes. Load when using Drizzle or Prisma alongside Electric for writes.
electric-new-feature
End-to-end guide for adding a new synced feature with Electric and TanStack DB. Covers the full journey: design Postgres schema, set REPLICA IDENTITY FULL, define shape, create proxy route, set up TanStack DB collection with electricCollectionOptions, implement optimistic mutations with txid handshake (pg_current_xact_id, awaitTxId), and build live queries with useLiveQuery. Also covers migration from old ElectricSQL (electrify/db pattern does not exist), current API patterns (table as query param not path, handle not shape_id). Load when building a new feature from scratch.
electric-deployment
Deploy Electric via Docker, Docker Compose, or Electric Cloud. Covers DATABASE_URL (direct connection, not pooler), ELECTRIC_SECRET (required since v1.x), ELECTRIC_INSECURE for dev, wal_level=logical, max_replication_slots, ELECTRIC_STORAGE_DIR persistence, ELECTRIC_POOLED_DATABASE_URL for pooled queries, IPv6 with ELECTRIC_DATABASE_USE_IPV6, Kubernetes readiness probes (200 vs 202), replication slot cleanup, and Postgres v14+ requirements. Load when deploying Electric or configuring Postgres for logical replication.
electric-debugging
Troubleshoot Electric sync issues. Covers fast-loop detection from CDN/proxy cache key misconfiguration, stale cache diagnosis (StaleCacheError), MissingHeadersError from CORS misconfiguration, 409 shape expired handling, SSE proxy buffering (nginx proxy_buffering off, Caddy flush_interval -1), HTTP/1.1 6-connection limit in local dev (Caddy HTTP/2 proxy), WAL growth from replication slots (max_slot_wal_keep_size), Vercel CDN cache issues, and onError/backoff behavior. Load when shapes are not receiving updates, sync is slow, or errors appear in the console.
ElectricSQL
## Overview
Electric SQL — Sync Engine for Postgres
You are an expert in Electric SQL, the sync engine that streams Postgres data to local apps in real-time. You help developers build local-first applications where data syncs from Postgres to client-side SQLite/PGlite automatically — enabling instant reads, offline support, and real-time multi-user collaboration using Postgres as the single source of truth with Shape-based partial replication.
Daily Logs
Record the user's daily activities, progress, decisions, and learnings in a structured, chronological format.
Socratic Method: The Dialectic Engine
This skill transforms Claude into a Socratic agent — a cognitive partner who guides