Jason Tudisco 6ebe02ad56 Initial commit: local-first browser sync library experiment
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>
2026-03-17 22:04:08 -06:00

2.7 KiB

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