Aiden McClelland 003cb1dcf2 fix: address PR review comments
- patch.rs: convert Replace to Add when idx == onto_idx in Remove rebase
- store.rs: use ciborium::from_reader_with_buffer to reuse scratch buffer
- store.rs: compress takes &mut bool instead of returning bool
- store.rs: move OPEN_STORES cleanup to Drop impl (std::sync::Mutex)
- store.rs: PatchDb::close no longer errors on other references
- store.rs: revert exists() to treat null as non-existent
- remove @claude changelog comments from backend
2026-03-06 16:32:33 -07:00
2026-03-06 16:32:33 -07:00
2026-03-06 16:32:33 -07:00

patch-db

A database that tracks state updates as RFC 6902 JSON Patches. Enables observable, event-driven state management with a Rust backend and TypeScript client.

Overview

patch-db stores your application state as a single JSON document. Instead of opaque writes, every mutation is recorded as a JSON Patch — a sequence of add/remove/replace operations. Subscribers receive only the patches relevant to the subtree they're watching, making it efficient for UIs that need to react to fine-grained state changes.

Key properties

  • Event-sourced — patches are the source of truth, not snapshots
  • Observable — subscribers watch arbitrary subtrees via JSON Pointers and receive scoped patches in real time
  • Persistent — the Rust backend writes to disk with CBOR serialization, automatic compaction, and crash-safe backup files
  • Type-safe — derive macros on the Rust side; generic type parameters and deep watch$() overloads on the TypeScript side
  • Immutable values — the Rust side uses imbl_value::Value for efficient structural sharing

Quick start

Rust

Add to your Cargo.toml:

[dependencies]
patch-db = { git = "https://github.com/Start9Labs/patch-db" }
use patch_db::PatchDb;
use json_ptr::ROOT;

#[tokio::main]
async fn main() -> Result<(), patch_db::Error> {
    let db = PatchDb::open("my.db").await?;

    // Write a value
    db.put(&ROOT, &serde_json::json!({ "count": 0 })).await?;

    // Read it back
    let dump = db.dump(&ROOT).await;
    println!("revision {}: {}", dump.id, dump.value);

    // Subscribe to changes
    let mut watch = db.watch(ROOT.to_owned()).await;
    // watch implements Stream — use it with tokio, futures, etc.

    Ok(())
}

TypeScript

import { PatchDB, Dump, Update } from 'patch-db'
import { Observable } from 'rxjs'

interface AppState {
  users: { [id: string]: { name: string; online: boolean } }
  settings: { theme: string }
}

// source$ delivers updates from the server (WebSocket, SSE, etc.)
const source$: Observable<Update<AppState>[]> = getUpdatesFromServer()

const db = new PatchDB<AppState>(source$)
db.start()

// Watch a deeply nested path — fully type-safe
db.watch$('settings', 'theme').subscribe(theme => {
  console.log('Theme changed:', theme)
})

Further reading

  • ARCHITECTURE.md — project structure, crate/package details, data flow, storage format
  • CONTRIBUTING.md — environment setup, build commands, testing, code style

License

MIT

Description
No description provided
Readme 1.1 MiB
Languages
Rust 89.6%
TypeScript 10.4%