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

96 lines
2.9 KiB
TypeScript

// ── Event types ──────────────────────────────────────────────
export type EventType = 'put' | 'delete';
export interface SyncEvent {
type: EventType;
store: string; // "kv" | collection name
key: string;
id?: string; // present for collection documents
ts: number; // millisecond Unix epoch
clientId: string;
data?: unknown; // absent on deletes
rev?: number;
}
// ── Index / collection options ───────────────────────────────
export interface IndexDefinition<T = unknown> {
name: string;
fields: (keyof T & string)[] | string[];
}
export interface StoreOptions<T = unknown> {
name: string;
indexes?: IndexDefinition<T>[];
}
export interface OpenOptions {
dbName?: string;
autoSyncIntervalMs?: number;
clientId?: string;
conflictResolver?: (current: unknown, incoming: unknown) => unknown;
/** Passed to sqlite3InitModule(). Use locateFile to override .wasm loading. */
sqlite3Config?: Record<string, unknown>;
}
// ── Store record shapes ──────────────────────────────────────
export interface KVRecord {
key: string;
value: unknown;
ts: number;
rev: number;
}
export interface DocRecord {
store: string;
id: string;
data: unknown;
ts: number;
rev: number;
deleted?: boolean;
}
// ── Index range (replaces IDBKeyRange for SQLite) ────────────
export interface IndexRange {
gt?: unknown;
gte?: unknown;
lt?: unknown;
lte?: unknown;
}
// ── Event emitter ────────────────────────────────────────────
export type SyncDBEventName =
| 'sync:start'
| 'sync:end'
| 'change'
| 'conflict'
| 'folder:lost-permission';
export type SyncDBEventHandler = (...args: unknown[]) => void;
// ── KV API surface ───────────────────────────────────────────
export interface KVApi {
get<T = unknown>(key: string): Promise<T | undefined>;
set<T = unknown>(key: string, value: T): Promise<void>;
delete(key: string): Promise<void>;
has(key: string): Promise<boolean>;
keys(): Promise<string[]>;
entries<T = unknown>(): Promise<Array<[string, T]>>;
}
// ── Collection API surface ───────────────────────────────────
export interface CollectionApi<T extends { id: string }> {
get(id: string): Promise<T | undefined>;
put(doc: T): Promise<void>;
delete(id: string): Promise<void>;
all(): Promise<T[]>;
findByIndex(indexName: string, value: unknown): Promise<T[]>;
queryByIndex(indexName: string, range: IndexRange): Promise<T[]>;
}