LocalHtmlDataTest/sql-js/src/collection.ts
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

65 lines
2.3 KiB
TypeScript

import type { CollectionApi, IndexDefinition, IndexRange, StoreOptions } from './types.js';
import type { SqlJsStore } from './sqljs-store.js';
import type { SyncEngine } from './sync-engine.js';
export class Collection<T extends { id: string }> implements CollectionApi<T> {
private readonly storeName: string;
private readonly indexes: IndexDefinition<T>[];
private indexesBuilt = false;
constructor(options: StoreOptions<T>, private store: SqlJsStore, private sync: SyncEngine) {
this.storeName = options.name;
this.indexes = options.indexes ?? [];
this.sync.registerIndexes(this.storeName, this.indexes as IndexDefinition[]);
}
private async ensureIndexes(): Promise<void> {
if (this.indexesBuilt || this.indexes.length === 0) return;
await this.store.rebuildIndexes(this.storeName, this.indexes as IndexDefinition[]);
this.indexesBuilt = true;
}
async get(id: string): Promise<T | undefined> {
const rec = await this.store.getDoc(this.storeName, id);
return rec ? (rec.data as T) : undefined;
}
async put(doc: T): Promise<void> {
await this.ensureIndexes();
const existing = await this.store.getRawDoc(this.storeName, doc.id);
await this.sync.writeDoc(this.storeName, doc.id, doc, Date.now(), (existing?.rev ?? 0) + 1);
}
async delete(id: string): Promise<void> {
await this.ensureIndexes();
const existing = await this.store.getRawDoc(this.storeName, id);
if (!existing || existing.deleted) return;
await this.sync.deleteDocEvent(this.storeName, id, Date.now(), existing.rev + 1);
}
async all(): Promise<T[]> {
return (await this.store.getAllDocs(this.storeName)).map((d) => d.data as T);
}
async findByIndex(indexName: string, value: unknown): Promise<T[]> {
await this.ensureIndexes();
const ids = await this.store.findByIndex(this.storeName, indexName, value);
return this.fetchByIds(ids);
}
async queryByIndex(indexName: string, range: IndexRange): Promise<T[]> {
await this.ensureIndexes();
const ids = await this.store.queryByIndex(this.storeName, indexName, range);
return this.fetchByIds(ids);
}
private async fetchByIds(ids: string[]): Promise<T[]> {
const results: T[] = [];
for (const id of ids) {
const doc = await this.store.getDoc(this.storeName, id);
if (doc) results.push(doc.data as T);
}
return results;
}
}