🌀 SwirlDB

A modular-first, CRDT-based embedded database built from composable adapters

Browser and server are equivalent nodes. No primary platform, no privileged environment. Just pure, swappable components.

Design Philosophy

Everything is an Adapter

SwirlDB is not a monolith with configuration options. It's a composition of swappable adapters:

  • Storage Adapters: localStorage, IndexedDB, redb, SQLite, sharded files, or your own
  • Sync Adapters: WebSocket, HTTP, WebRTC, custom protocols
  • Auth Adapters: JWT, OAuth, ABAC, custom policies
  • Encryption Adapters: AES-GCM, field-level, custom crypto

No feature flags. No conditional compilation. Each adapter is an independent implementation of a shared trait.

Core Principles

🔌 Adapter-First Architecture

Every subsystem is a pluggable adapter. Compose your database from the exact components you need. Runtime swappable with zero recompilation.

⚖️ Equivalent Nodes

Browser and server are peers implementing identical traits. Same CRDT engine, same storage interface, just different adapter implementations.

🎯 Path-Level Policies

Per-path control over storage, sync, and auth. Different data, different rules. Configure at runtime without rebuilding.

🧩 Shared Implementations

Pure Rust core with unified traits. Browser WASM and native servers share the same CRDT logic, differ only in platform adapters.

🔄 CRDT-Based Sync

Built on Automerge for conflict-free replicated data. Multi-user by default, with incremental delta sync and merge semantics.

⚡ Natural APIs

Native property access in JavaScript via Proxies. Idiomatic Rust APIs. No learning curve, just write code naturally.

Quick Example

import { SwirlDB } from '@swirldb/js';

// Create database with LocalStorage persistence
const db = await SwirlDB.withLocalStorage('my-app');

// Native property access via Proxies
db.data.user.name = 'Alice';
db.data.user.email = 'alice@example.com';

// Get values with .$value
console.log(db.data.user.name.$value); // 'Alice'

// Observe changes
db.data.user.name.$observe((newValue) => {
  console.log('Name changed:', newValue);
});

// Enable auto-persistence
db.enableAutoPersist(500); // Debounced 500ms
db.data.settings.theme = 'dark'; // Auto-saves

Architecture: The 3-Crate System

SwirlDB uses complete separation of concerns with no feature flags:

swirldb-core

Platform-agnostic Rust library

  • CRDT engine (Automerge-based)
  • Storage trait definitions (DocumentStorage + ChangeLog)
  • Policy engine (path-based authorization)
  • Auth providers (extensible)
  • Zero platform dependencies

swirldb-browser

WASM bindings for browsers

  • JavaScript bindings via wasm-bindgen
  • localStorage adapter (5-10MB)
  • IndexedDB adapter (50MB-1GB)
  • Browser-specific storage implementations
  • ~489KB total (gzipped)

swirldb-server

Pure Rust binary for servers

  • HTTP/WebSocket sync server (axum + tokio)
  • redb adapter (embedded, persistent)
  • Memory adapter (volatile, fast)
  • No Node.js dependency
  • Native I/O performance

Unified Traits, Different Implementations

Both browser and server implement the same storage traits:

trait DocumentStorage {
    async fn save(&self, key: &str, data: &[u8]) -> Result<()>;
    async fn load(&self, key: &str) -> Result<Option<Vec<u8>>>;
    // ...
}

trait ChangeLog {
    async fn append_change(&self, namespace_id: &str, change: Change) -> Result<()>;
    async fn get_changes_since(&self, namespace_id: &str, since: i64) -> Result<Vec<Change>>;
    // ...
}

Browser: localStorage + IndexedDB implementations

Server: redb + memory implementations

Same workflows. Same APIs. Just different backends.