4.1 KiB
4.1 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
src/gen/ contains gRPC stubs generated from proto/yoresee_doc/v1/yoresee_doc.proto (parent repo). This directory is gitignored.
- Dev: generated automatically by
deploy/script/gen_proto.shfrom the parent repo. - Prod:
Dockerfile.prodgenerates stubs in a builder stage usinggrpc-toolsandprotoc-gen-js.
The file src/grpc/client.js imports from ../gen/yoresee_doc/v1/yoresee_doc_grpc_pb — code will fail at startup if stubs are missing.
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.envreads insrc/config.js. No dotenv — env vars must be set externally. - Doc loading cascade: Redis list buffers → gRPC
GetDocumentYjsSnapshot→ gRPCGetDocumentContent→ empty doc. - Persistence: updates appended to Redis list (
collab:yjs:doc:updates:{docId}). Compacted into single state update everyDOC_UPDATE_COMPACT_THRESHOLD(default 1000) updates. - Dirty-doc notification: published after
DIRTY_DOC_NOTIFY_THRESHOLD(default 200) updates per doc. MQ backend controlled byDIRTY_DOC_MQenv var (redis|rabbitmq|both). - Room naming: WebSocket path is the room name (e.g.
/doc-{docId}). Room name withdoc-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 |