mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
97 lines
4.1 KiB
Markdown
97 lines
4.1 KiB
Markdown
# EventsCacheStore Overview
|
|
|
|
The `EventsCacheStore` is a specialized persistence layer designed to handle the application's critical state: thousands of video events and their deletion status (tombstones). It uses **IndexedDB** as the primary storage backend, falling back to `localStorage` (via `PersistenceManager`) only if IDB is unavailable.
|
|
|
|
## 1. Problem Statement
|
|
|
|
Nostr clients must handle high-volume event streams. Storing every event naively to disk on every change causes:
|
|
- **Write Trashing**: Frequent, redundant writes degrade performance and battery life.
|
|
- **I/O Blocking**: Large JSON serializations on the main thread cause UI jank.
|
|
- **Startup Latency**: Reading thousands of small files or one giant blob is slow.
|
|
|
|
## 2. Architecture: Incremental Persistence
|
|
|
|
`EventsCacheStore` solves this with a **Differential State Snapshot** approach.
|
|
|
|
### Key Concepts
|
|
|
|
1. **Memory Fingerprints**:
|
|
- The store maintains a `Map<ID, Fingerprint>` in memory, mirroring what is currently on disk.
|
|
- `Event Fingerprint`: `JSON.stringify(videoObject)` (fast enough for 1000s of items).
|
|
- `Tombstone Fingerprint`: `ts:<timestamp>`.
|
|
|
|
2. **Debounced Snapshots**:
|
|
- `PersistenceManager` debounces writes (e.g., 75ms). When a write triggers, it passes the *entire* current state (`allEvents`) plus a set of `dirtyKeys` (IDs that changed since last save).
|
|
|
|
3. **The Diff Algorithm (`_diffAndStore`)**:
|
|
- Instead of clearing and rewriting the DB, it iterates over the `dirtyKeys`.
|
|
- It computes the new fingerprint.
|
|
- **Optimization**: If `newFingerprint === persistedFingerprint`, the write is skipped.
|
|
- If different, it performs a `put()` operation and updates the memory fingerprint map.
|
|
|
|
4. **Deletion Detection**:
|
|
- After processing updates, it compares the size of the memory state (`items.size`) vs the persisted state (`fingerprintsMap.size`).
|
|
- Since `fingerprintsMap` is updated during the loop to include new items, if `items.size < fingerprintsMap.size`, it implies some items in the persisted map are no longer in memory.
|
|
- In this case, it iterates the *entire* fingerprint map to find and `delete()` the missing keys.
|
|
|
|
### Flow Diagram
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant App as NostrClient
|
|
participant PM as PersistenceManager
|
|
participant ECS as EventsCacheStore
|
|
participant IDB as IndexedDB
|
|
|
|
App->>App: Event arrives (updates allEvents)
|
|
App->>PM: saveLocalData(reason, dirtyKeys)
|
|
PM->>PM: Debounce (75ms)
|
|
PM->>ECS: persistSnapshot(payload, dirtyKeys)
|
|
|
|
activate ECS
|
|
ECS->>ECS: Ensure fingerprints loaded (once)
|
|
|
|
loop For each Dirty Key
|
|
ECS->>ECS: Compute Fingerprint
|
|
alt Fingerprint Changed
|
|
ECS->>IDB: put(record)
|
|
ECS->>ECS: Update Fingerprint Map
|
|
else Same Fingerprint
|
|
ECS->>ECS: Skip
|
|
end
|
|
end
|
|
|
|
alt items.size < fingerprintsMap.size
|
|
loop For each Persisted Key
|
|
alt Not in Memory
|
|
ECS->>IDB: delete(key)
|
|
ECS->>ECS: Remove from Fingerprint Map
|
|
end
|
|
end
|
|
end
|
|
|
|
ECS-->>PM: { writes, deletes, skipped }
|
|
deactivate ECS
|
|
```
|
|
|
|
## 3. Data Schema
|
|
|
|
The IndexedDB `bitvid-events-cache` (v1) contains three object stores:
|
|
|
|
| Store | Key Path | Value Description |
|
|
| :--- | :--- | :--- |
|
|
| `events` | `id` | `{ id, video, fingerprint }` |
|
|
| `tombstones` | `key` | `{ key, timestamp, fingerprint }` |
|
|
| `meta` | `key` | `{ key: "meta", savedAt: <ts>, version: 1 }` |
|
|
|
|
## 4. Invariants & Assumptions
|
|
|
|
- **Single Threaded**: The store assumes it is the only writer. It does not handle cross-tab locking (though IDB transactions provide atomicity).
|
|
- **Memory-First**: The `allEvents` Map in memory is the source of truth. IDB is a cache.
|
|
- **TTL**: On `restoreSnapshot()`, if the data is older than the TTL (e.g. 10 minutes), the entire cache is wiped to ensure freshness.
|
|
|
|
## 5. Maintenance
|
|
|
|
- **Adding Fields**: If the `Video` object shape changes, the fingerprint (JSON string) automatically changes, forcing a rewrite. No migration needed for content changes.
|
|
- **Schema Changes**: To add a new store, increment `EVENTS_CACHE_DB_VERSION` and update `onupgradeneeded` in `getDb()`.
|