Storage
SwirlDB provides pluggable storage through the DocumentStorage trait for
current-state snapshots. CRDT change tracking is handled internally by Automerge.
Storage Architecture
Trait Definitions
All storage adapters implement these platform-agnostic traits:
trait DocumentStorage {
async fn save(&self, key: &str, data: &[u8]) -> Result<()>;
async fn load(&self, key: &str) -> Result<Option<Vec<u8>>>;
async fn delete(&self, key: &str) -> Result<()>;
async fn list_keys(&self) -> Result<Vec<String>>;
} Browser Adapters
In-Memory (Default)
Volatile storage. Data is lost on page reload.
const db = await SwirlDB.create();
// Data lost on page reload
db.data.user.name = 'Alice'; Capacity: Limited by browser memory (~1-2GB)
Use case: Development, temporary sessions, caching
LocalStorage
Key-value storage with synchronous API. Data persists across sessions.
const db = await SwirlDB.withLocalStorage('my-app');
// Auto-persists to localStorage
db.data.user.settings = { theme: 'dark' };
await db.persist(); Capacity: ~5-10MB (browser-dependent)
Use case: Small apps, user preferences, offline-first PWAs
IndexedDB
Transactional database with asynchronous API. Supports structured data and binary blobs.
const db = await SwirlDB.withIndexedDB('my-app');
// Handles large datasets
db.data.documents = largeCollection;
await db.persist(); Capacity: ~50MB-1GB+ (browser quota system)
Use case: Large datasets, offline-first apps, media storage
Server Adapters
In-Memory (Multi-threaded)
Concurrent in-memory storage with thread-safe access.
use swirldb_server::storage::MemoryAdapter;
let storage = Arc::new(MemoryAdapter::new());
let db = SwirlDB::with_storage(storage, "db").await;
// Thread-safe, concurrent access
tokio::spawn(async move {
db.set_path("counter", 1.into()).unwrap();
}); Capacity: Limited by server RAM
Use case: Caching, testing, ephemeral state
Redb (Embedded Database)
Embedded database with ACID guarantees and zero-copy reads.
use swirldb_server::storage::RedbAdapter;
let storage = RedbAdapter::new("./data/swirldb.redb")?;
let db = SwirlDB::with_storage(Arc::new(storage), "db").await;
// ACID transactions, crash-safe
db.set_path("user.profile", user_data)?;
db.persist().await?; // Durable commit Capacity: Limited by disk space
Use case: Production servers, durable state, embedded systems
SQLite (Planned)
SQL database with queryable schema. Supports relational queries alongside CRDT operations.
use swirldb_server::storage::SqliteAdapter;
let storage = SqliteAdapter::new("./data/swirldb.db")?;
let db = SwirlDB::with_storage(Arc::new(storage), "db").await;
// SQL queries + CRDT operations
let results = storage.query("SELECT * FROM documents WHERE created > ?", [timestamp])?; Status: 🔜 Planned
S3 / Cloud Storage (Planned)
Object storage adapter. Compatible with AWS S3, Google Cloud Storage, Azure Blob, or S3-compatible backends.
use swirldb_server::storage::S3Adapter;
let storage = S3Adapter::new("my-bucket", "us-east-1")?;
let db = SwirlDB::with_storage(Arc::new(storage), "db").await;
db.persist().await?; // Writes to S3 Status: 🔜 Planned
Integration with Encryption
Storage adapters integrate with encryption providers for at-rest protection.
Encrypted Storage Example
use swirldb_core::encryption::AesGcmProvider;
use swirldb_server::storage::RedbAdapter;
// Create encrypted storage
let encryption = AesGcmProvider::new_random();
let storage = RedbAdapter::new("./data")
.with_encryption(encryption);
let db = SwirlDB::with_storage(Arc::new(storage), "db").await;
// All data encrypted before hitting disk
db.set_path("sensitive.data", secret_value)?;
db.persist().await?; // Encrypted write to redb Custom Storage Adapters
Implement the DocumentStorage trait to create
custom backends for your specific needs.
Example: Redis Adapter
use swirldb_core::storage::{DocumentStorage, DocumentStorageMarker};
use async_trait::async_trait;
struct RedisStorage {
client: redis::Client,
}
impl DocumentStorageMarker for RedisStorage {}
#[async_trait]
impl DocumentStorage for RedisStorage {
async fn save(&self, key: &str, data: &[u8]) -> Result<()> {
let mut conn = self.client.get_async_connection().await?;
redis::cmd("SET")
.arg(key)
.arg(data)
.query_async(&mut conn)
.await?;
Ok(())
}
async fn load(&self, key: &str) -> Result<Option<Vec<u8>>> {
let mut conn = self.client.get_async_connection().await?;
let result: Option<Vec<u8>> = redis::cmd("GET")
.arg(key)
.query_async(&mut conn)
.await?;
Ok(result)
}
// Implement delete, list_keys...
} Performance Characteristics
Browser Storage Comparison
| Adapter | Capacity | Speed | API |
|---|---|---|---|
| In-Memory | ~1-2GB | Fastest | Sync |
| LocalStorage | ~5-10MB | Fast | Sync |
| IndexedDB | ~50MB-1GB+ | Moderate | Async |
Server Storage Comparison
| Adapter | Capacity | Durability | Use Case |
|---|---|---|---|
| In-Memory | Limited by RAM | ❌ Volatile | Caching, testing |
| Redb | Limited by disk | ✅ ACID | Production, embedded |
| SQLite | Limited by disk | ✅ ACID | Queryable, portable |