The Problem

Colanode is a local-first Slack/Notion alternative where data syncs from a local SQLite database to a self-hosted server. The architecture spans 1250 files across React/TypeScript frontends, Node/Express backends, and CRDT-powered real-time collaboration. Teams self-host to retain data control, but the codebase shows signs of growing pains: duplicated logic, deeply nested control flow, and oversized files that ripple changes widely.

What This Does

The repository is split into packages/ (898 files, shared libs) and apps/ (267 files, concrete deployments). The client (packages/client) handles UI, CRDT synchronization via Yjs, and database operations against local SQLite. The server (apps/server) provides the sync endpoint, manages authentication, workspace routing, and embeddings pipelines. Entry points are apps/server/src/index.ts (init), apps/mobile/src/app.tsx (App), and packages/crdt/src/index.ts (applyArrayChanges/applyObjectChanges). Real-time edits flow through synchronizer.ts and mappers.ts, with CRDT operations (applyArrayChanges, applyObjectChanges, applyRecordChanges) at the core. File effects originate in apps/desktop/src/main/file-system.ts (makeDirectory, exists, copy, readStream, writeStream) and packages/client/src/databases/workspace/schema.ts (UserTable, NodeTable definitions). Database writes go through Kysely-service layers in both desktop and mobile apps.

How It Is Wired

Execution starts at init in apps/server/src/index.ts:14, which reaches 203 functions and is itself called from 6 places. The most connected modules are instability hotspots: packages/client/src/handlers/mutations/index (Ca=1, Ce=40, instability=0.98) and packages/client/src/mutations/index (instability=1), meaning 40 downstream dependents shift if mutation logic changes. Traced paths from init reach filesystem effects via makeDirectory [fs.promises.mkdir] and database effects via delete [fs.unlink, redis.del]. The call graph shows publish called from 63 places, generateId from 49, and delete from 42 — these are the wide-blast-radius functions any change propagates through. The CRDT layer (packages/crdt/src/index.ts) defines applyArrayChanges (reaches 31 functions, called from 2) and applyObjectChanges (reaches 31, called from 3), which are the merge primitives for concurrent edits.

How To Use It

Setup: Clone with git clone https://github.com/moses-y/colanode. Install dependencies: npm install (root and apps/desktop, apps/mobile, apps/server). The project uses npm with lockfile present.

Configuration: Environment example at apps/server/.env.example. Required vars likely include database connection strings, Redis config, and API keys referenced in apps/server/src/lib/config/ (account, ai, cors, email, jobs, logging, postgres, redis, storage, workspace). Docker base image is node:22-alpine in apps/server/Dockerfile — pin by digest for reproducibility.

Running it: Start the server with npm run start from apps/server (script inferred from package.json). The desktop app runs via npm run dev in apps/desktop; mobile via npm run dev in apps/mobile. For production Docker builds, see the 8 workflow files under .github/workflows/.

Real-World Use

A team self-hosts the Colanode server for internal knowledge management. Editors open the desktop app, create a workspace, and begin taking notes rich-text pages. As they type, Yjs CRDT syncs changes locally and to the server in the background. If a teammate edits the same page, conflicts merge on save. The team also stores PDFs and images in workspace-file buckets, with uploads funneled through apps/server/src/api/client/routes/workspaces/files/file-upload-tus.ts. Nightly, the cleanup job removes stale node revisions. If the internet drops, the local SQLite continues recording edits; when connectivity returns, the sync pipeline pushes pending changes to the server.

Code Health & Issues

The static analysis surface reveals 38 findings across 5 categories:

  • HIGH/clarity: Duplicated code blocks — 1549 repeated 6-line patterns across 265 files, including apps/desktop/src/main/path-service.ts, apps/mobile/src/services/path-service.ts, apps/desktop/src/main.ts, apps/web/src/workers/dedicated.ts. Fix: extract shared helpers; DRY the repeated logic.
  • HIGH/cognitive_load: Deep nesting x24 — files like packages/ui/src/components/nodes/node-breadcrumb.tsx, packages/ui/src/editor/views/code-block.tsx, packages/ui/src/components/auth/logout-container.tsx have max indentation depth 8. Fix: flatten with early returns/guard clauses.
  • HIGH/cognitive_load: Oversized file x7 — packages/client/src/services/workspaces/node-service.ts (1242 lines), packages/ui/src/lib/databases.ts, scripts/src/seed/node-generator.ts. Fix: split into cohesive units by responsibility.
  • MEDIUM/cognitive_load: High branching density x6 — packages/core/src/lib/permissions.ts, packages/core/src/lib/utils.ts, apps/desktop/src/main/app-badge.ts (20 branch points over 56 lines). Fix: decompose decision-heavy logic; consider table/strategy dispatch.

Additional SDLC observations from the hygiene audit (not part of the measured block):

  • 3 HIGH: GitHub Actions pinned to version tags (not commit SHAs) in .github/workflows; CI does not invoke the test suite (26 test files, no test command in 4 workflows); committed database dumps assets/emojis/emojis.db and assets/icons/icons.db (11.0MB and 8.4MB).
  • 3 MEDIUM: GITHUB_TOKEN permissions not declared in 3 workflows; Dependabot/Renovate not configured; container base image node:22-alpine not pinned by digest.
  • 1 LOW: pre-commit secret gate absent.

The Bottom Line

Colanode functionalizes a local-first collaboration platform with real-time CRDT sync, file storage, and embeddings-driven search. The codebase is large and interconnected — mutation logic and CRDT operations are high-impact change points, and duplicated/oversized files increase merge risk. It’s suitable for teams that need self-hosted, offline-capable collaboration and are willing to maintain a moderately complex Node/TypeScript codebase. Teams wanting out-of-the-box polish or low maintenance overhead should look elsewhere.