kanban-persistence-json 0.5.1

JSON file storage backend for the kanban project management tool
Documentation

kanban-persistence-json

JSON file storage backend for the kanban workspace. Implements StoreFactory and PersistenceStore from kanban-persistence.

JsonFileStore

pub struct JsonFileStore {
    path: String,
    instance_id: String,
}

impl JsonFileStore {
    pub fn new(path: &str) -> Self;
    pub fn with_instance_id(path: &str, instance_id: &str) -> Self;
}

instance_id is a random UUID generated per process; used for conflict detection.


V2 Envelope Format

{
  "version": 2,
  "metadata": {
    "saved_at": "2024-06-15T10:30:00Z",
    "instance_id": "550e8400-e29b-41d4-a716-446655440000",
    "save_count": 42
  },
  "data": { /* kanban_domain::Snapshot */ }
}

V1 format was a bare Snapshot JSON object (flat, no wrapper).


Save Flow

  1. Check for conflict: compare file metadata (size + mtime) against the last-seen value
  2. Update PersistenceMetadata (increment save_count, set saved_at)
  3. Wrap snapshot in the V2 envelope
  4. Write to a temporary file (.tmp suffix) in the same directory
  5. Atomic rename: temp → final path

The atomic rename means a crash at any point leaves either the old file or the new file intact — always a complete, consistent file on disk.

Debounced saving: a 500ms minimum interval is enforced between saves to avoid thrashing on rapid successive mutations.


Load Flow

  1. Read the file
  2. Detect format version: V2 envelopes begin with {"version":2; anything else is treated as V1
  3. V1 migration: rename existing file to <path>.v1.backup, parse as bare Snapshot, re-save as V2
  4. Parse the V2 envelope, extract and return data as StoreSnapshot

Conflict Detection

FileMetadata captures the file's size and modification time at last load/save. On the next save, the current file metadata is compared:

  • If they match → file unchanged since last save — safe to write
  • If they differ → another instance wrote the file; return PersistenceError::Conflict

JsonStoreFactory

pub struct JsonStoreFactory;

impl StoreFactory for JsonStoreFactory {
    fn name(&self) -> &str { "json" }
    fn supported_patterns(&self) -> &[&str] { &["*.json"] }
    fn matches(&self, locator: &str) -> bool { /* see below */ }
    fn create(&self, locator: &str) -> Result<...>;
}

matches logic:

  1. If the locator ends with .json → true
  2. If the locator starts with sqlite:// or ends with .sqlite/.sqlite3/.db → false
  3. Otherwise → true (catch-all fallback for plain file paths)

Dependencies

Crate Purpose
kanban-persistence PersistenceStore, StoreFactory traits
kanban-domain Snapshot type
serde_json JSON parsing
tokio Async I/O
tempfile Temp file for atomic writes