Four variants of the same sync library (IndexedDB, NeDB, SQLite WASM, sql.js) plus a paste-bin demo app for testing multi-browser sync via shared folders. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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