Files

4.0 KiB

AGENTS.md — collab

Node.js (CommonJS) Yjs collaboration server. Maintains Yjs documents in memory, persists updates to Redis, and notifies downstream consumers of dirty documents via Redis Pub/Sub or RabbitMQ.

Commands

npm start          # node server.js — listens on PORT (default 1234)

No test, lint, typecheck, or build scripts exist. Verify by reading the code.

Proto Codegen

gRPC stubs come from the npm package @yoresee-doc/sdk-node (CommonJS/gRPC build), pinned to tag v0.1.0 in package.json (git+https://git.yoresee.cc/YoreseeDoc/yoresee_doc_sdk_node.git#v0.1.0). No local codegen required.

src/grpc/client.js requires stubs from @yoresee-doc/sdk-node/cjs/... (note: .js extension required — the SDK root package is ESM).

Architecture

Entry: server.js → wires HTTP server, WebSocket gateway, Redis, gRPC, and graceful shutdown.

server.js
├── src/http/server.js      — plain Node HTTP server (no Express)
│   └── src/http/router.js  — routes: /health, /livez, /readyz, /api/active-rooms, /internal/yjs/*
├── src/ws-gateway.js       — WebSocket upgrade handler, extracts room name from URL path
├── src/ws-utils.js         — Yjs sync/awareness protocol, WSSharedDoc, connection lifecycle
├── src/doc-loader.js       — loads doc: Redis list → gRPC snapshot → gRPC content (cascade)
├── src/persistence.js      — binds Y.Doc 'update' event → Redis list append + dirty tracking
├── src/redis/              — Redis client, room tracking, document buffer lists
├── src/grpc/               — gRPC client to backend (DocumentService, SystemService)
├── src/mq.js               — dirty-doc publisher (Redis Pub/Sub and/or RabbitMQ)
├── src/config.js            — all config from process.env (no .env loader)
├── src/key.js               — Redis key patterns
├── src/bootstrap.js         — listen + init Redis/gRPC
└── src/lifecycle.js         — graceful shutdown (SIGTERM/SIGINT)

Key Patterns

  • Module style: CommonJS (require/module.exports). No ESM.
  • Config: plain process.env reads in src/config.js. No dotenv — env vars must be set externally.
  • Doc loading cascade: Redis list buffers → gRPC GetDocumentYjsSnapshot → gRPC GetDocumentContent → empty doc.
  • Persistence: updates appended to Redis list (collab:yjs:doc:updates:{docId}). Compacted into single state update every DOC_UPDATE_COMPACT_THRESHOLD (default 1000) updates.
  • Dirty-doc notification: published after DIRTY_DOC_NOTIFY_THRESHOLD (default 200) updates per doc. MQ backend controlled by DIRTY_DOC_MQ env var (redis | rabbitmq | both).
  • Room naming: WebSocket path is the room name (e.g. /doc-{docId}). Room name with doc- prefix is stripped to get the doc ID.
  • Redis keys: defined in src/key.js — collab:yjs:doc:updates:{docId}, collab:room:doc-{docId}, collab:yjs:dirty:doc.
  • Health probes: /health, /livez, /readyz — readiness depends on Redis connection and drain state.

Environment Variables

Variable Default Description
PORT 1234 HTTP listen port
REDIS_HOST redis Redis hostname
REDIS_PORT 6379 Redis port
REDIS_PASSWORD '' Redis password
REDIS_DB 0 Redis DB index
BACKEND_ADDR backend:9090 Backend gRPC address
DIRTY_DOC_MQ redis MQ type: redis, rabbitmq, or both
DIRTY_DOC_TOPIC collab.dirty_docs Dirty-doc pub/sub topic
RABBITMQ_URL '' RabbitMQ connection URL (empty = disabled)
INTERNAL_RPC_KEY '' gRPC metadata auth key (x-internal-key)
DIRTY_DOC_NOTIFY_THRESHOLD 200 Updates before publishing dirty-doc event
DOC_UPDATES_TTL 259200 TTL for doc update counters (seconds)
DOC_UPDATE_COMPACT_THRESHOLD 1000 Updates before compacting Redis list
GC true Yjs garbage collection (false/0 to disable)
BACKEND_SNAPSHOT_TIMEOUT_MS 3000 Timeout for backend snapshot gRPC call