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>
80 lines
2.7 KiB
Markdown
80 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
npm install
|
|
npm run build
|
|
```
|
|
|
|
## Usage
|
|
|
|
```ts
|
|
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
|