Jason Tudisco 7077191c0c NIP-78 encrypted transport, instant UI, re-sync recovery
Nostr transport:
- Switch from kind 1 to NIP-78 kind 30078 (application-specific data)
- Relays store events persistently — no inventory protocol needed
- AES-256-GCM encryption via Web Crypto API (room key = shared secret)
- PBKDF2 key derivation (100k iterations) from room key
- Chunking for large events (images >48KB) with per-chunk encryption
- Auto re-publish missing local events on room join
- Manual "Re-sync" button for recovery of failed publishes

Performance (all variants):
- Emit 'change' immediately after local store write (IDB/NeDB/SQLite)
- Folder and Nostr writes run fire-and-forget in background
- Fast event hashing: fingerprint metadata only, skip full payload SHA-256
- Saving spinner in paste UI while write completes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 01:11:29 -06:00
..

IndexSyncFile — IndexedDB Variant

Local-first key-value and document store using IndexedDB as the local cache, syncing with a user-selected folder via the File System Access API.

Why IndexedDB

  • Zero dependencies — uses the browser's built-in IndexedDB API directly
  • Persistent by default — IndexedDB data survives page reloads and browser restarts without any extra work
  • No WASM loading — instant startup, no async module initialization
  • Battle-tested — IndexedDB is the most widely supported browser storage API

Architecture

Write: app ---> IndexedDB (immediate) ---> /events/timestamp_hash.json
Sync:  /events/*.json ---> sort by timestamp ---> skip applied ---> apply to IndexedDB

IndexedDB serves as both the fast query layer and the local persistence layer. The folder's event log is the sync mechanism that keeps multiple browsers converged.

IDB schema (5 object stores)

Store Key Purpose
kv key Key-value records
docs [store, id] Document collection records
idx [store, indexName, id] Secondary index entries
applied filename Tracks which event files have been processed
meta key Client ID, directory handle persistence

The FileSystemDirectoryHandle is stored directly in the meta IDB store via structured clone, so the user doesn't need to re-select the folder on every page load.

Install and build

npm install
npm run build

Usage

import { FolderSyncDB } from './dist/index.js';

const db = await FolderSyncDB.open({
  dbName: 'MyApp',
  autoSyncIntervalMs: 5000,
});

await db.selectFolder();

// KV
await db.kv.set('user', { name: 'Alice' });

// Collections
const notes = db.collection({
  name: 'notes',
  indexes: [{ name: 'byTag', fields: ['tag'] }],
});
await notes.put({ id: 'n1', title: 'Hello', tag: 'work' });

// Exact match
const workNotes = await notes.findByIndex('byTag', 'work');

// Range query (IDB compound key ranges)
const recent = await notes.queryByIndex('byDate', { gte: '2024-01-01' });

Index implementation

Secondary indexes are maintained in a dedicated idx IDB object store using compound keys [store, indexName, id]. An IDB index on [store, indexName, value] enables efficient exact-match and range queries via IDBKeyRange.

Index entries are updated atomically with document writes inside a single IDB transaction. On first use of a collection, indexes are rebuilt from existing documents.

Variant-specific notes

  • Directory handle is persisted in IDB (no separate storage needed)
  • IDB transactions are used for atomic document + index updates
  • No external dependencies