UNPKG

mcard-js

Version:

MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers

205 lines (202 loc) 6.73 kB
import { CORE_SCHEMAS, validateHandle } from "./chunk-U3FWUPD3.js"; import { MCard } from "./chunk-QPZ2HNSU.js"; // src/storage/SqliteWasmEngine.ts var SqliteWasmEngine = class { db = null; SQL = null; /** * Initialize the database * @param wasmUrl URL to sql-wasm.wasm file (optional, defaults to CDN) * @param existingData Optional existing database as Uint8Array */ async init(wasmUrl, existingData) { const initSqlJs = (await import("sql.js")).default; const SQL = await initSqlJs({ locateFile: (file) => wasmUrl || `https://sql.js.org/dist/${file}` }); this.SQL = SQL; this.db = existingData ? new SQL.Database(existingData) : new SQL.Database(); this.db.run(CORE_SCHEMAS.card); this.db.run(CORE_SCHEMAS.handleRegistry); this.db.run(CORE_SCHEMAS.handleHistory); this.db.run(CORE_SCHEMAS.handleIndex); } ensureDb() { if (!this.db) throw new Error("Database not initialized. Call init() first."); return this.db; } /** * Export database as Uint8Array (for persistence) */ export() { return this.ensureDb().export(); } /** * Get raw sql.js Database for use with VectorStore adapter. * * Example: * const vecStore = createWasmVectorStore(engine.getRawDb()); */ getRawDb() { return this.ensureDb(); } // =========== Card Operations =========== async add(card) { const db = this.ensureDb(); db.run( "INSERT OR REPLACE INTO card (hash, content, g_time) VALUES (?, ?, ?)", [card.hash, card.content, card.g_time] ); return card.hash; } async get(hash) { const db = this.ensureDb(); const result = db.exec(`SELECT hash, content, g_time FROM card WHERE hash = '${hash}'`); if (result.length === 0 || result[0].values.length === 0) return null; const [h, content, g_time] = result[0].values[0]; return MCard.fromData(new Uint8Array(content), h, g_time); } async delete(hash) { this.ensureDb().run("DELETE FROM card WHERE hash = ?", [hash]); } async getPage(pageNumber, pageSize) { const db = this.ensureDb(); const totalItems = await this.count(); const totalPages = Math.ceil(totalItems / pageSize); const offset = (pageNumber - 1) * pageSize; const result = db.exec(`SELECT hash, content, g_time FROM card LIMIT ${pageSize} OFFSET ${offset}`); const items = (result[0]?.values || []).map( ([h, content, g_time]) => MCard.fromData(new Uint8Array(content), h, g_time) ); return { items, totalItems, pageNumber, pageSize, totalPages, hasNext: pageNumber < totalPages, hasPrevious: pageNumber > 1 }; } async count() { const result = this.ensureDb().exec("SELECT COUNT(*) FROM card"); return result[0]?.values[0]?.[0] || 0; } async searchByHash(hashPrefix) { const db = this.ensureDb(); const stmt = db.prepare("SELECT hash, content, g_time FROM card WHERE hash LIKE ?"); stmt.bind([`${hashPrefix}%`]); const items = []; while (stmt.step()) { const row = stmt.get(); items.push(MCard.fromData(new Uint8Array(row[1]), row[0], row[2])); } stmt.free(); return items; } async search(query, pageNumber, pageSize) { const db = this.ensureDb(); const offset = (pageNumber - 1) * pageSize; const pattern = `%${query}%`; let stmt = db.prepare("SELECT COUNT(*) FROM card WHERE CAST(content AS TEXT) LIKE ?"); stmt.bind([pattern]); stmt.step(); const totalItems = stmt.get()[0]; stmt.free(); const totalPages = Math.ceil(totalItems / pageSize); stmt = db.prepare("SELECT hash, content, g_time FROM card WHERE CAST(content AS TEXT) LIKE ? LIMIT ? OFFSET ?"); stmt.bind([pattern, pageSize, offset]); const items = []; while (stmt.step()) { const row = stmt.get(); items.push(MCard.fromData(new Uint8Array(row[1]), row[0], row[2])); } stmt.free(); return { items, totalItems, pageNumber, pageSize, totalPages, hasNext: pageNumber < totalPages, hasPrevious: pageNumber > 1 }; } async getAll() { const result = this.ensureDb().exec("SELECT hash, content, g_time FROM card"); return (result[0]?.values || []).map( ([h, content, g_time]) => MCard.fromData(new Uint8Array(content), h, g_time) ); } async clear() { const db = this.ensureDb(); db.run("DELETE FROM card"); db.run("DELETE FROM handle_registry"); db.run("DELETE FROM handle_history"); } // =========== Handle Operations =========== async registerHandle(handle, hash) { const db = this.ensureDb(); const normalized = validateHandle(handle); const existing = db.exec(`SELECT handle FROM handle_registry WHERE handle = '${normalized}'`); if (existing.length > 0 && existing[0].values.length > 0) { throw new Error(`Handle '${handle}' already exists.`); } const now = (/* @__PURE__ */ new Date()).toISOString(); db.run( "INSERT INTO handle_registry (handle, current_hash, created_at, updated_at) VALUES (?, ?, ?, ?)", [normalized, hash, now, now] ); } async resolveHandle(handle) { const normalized = validateHandle(handle); const result = this.ensureDb().exec( `SELECT current_hash FROM handle_registry WHERE handle = '${normalized}'` ); return result[0]?.values[0]?.[0] || null; } async getByHandle(handle) { const hash = await this.resolveHandle(handle); if (!hash) return null; return this.get(hash); } async updateHandle(handle, newHash) { const db = this.ensureDb(); const normalized = validateHandle(handle); const existing = db.exec( `SELECT current_hash FROM handle_registry WHERE handle = '${normalized}'` ); if (existing.length === 0 || existing[0].values.length === 0) { throw new Error(`Handle '${handle}' not found.`); } const previousHash = existing[0].values[0][0]; const now = (/* @__PURE__ */ new Date()).toISOString(); db.run( "INSERT INTO handle_history (handle, previous_hash, changed_at) VALUES (?, ?, ?)", [normalized, previousHash, now] ); db.run( "UPDATE handle_registry SET current_hash = ?, updated_at = ? WHERE handle = ?", [newHash, now, normalized] ); return previousHash; } async getHandleHistory(handle) { const normalized = validateHandle(handle); const result = this.ensureDb().exec( `SELECT previous_hash, changed_at FROM handle_history WHERE handle = '${normalized}' ORDER BY id DESC` ); return (result[0]?.values || []).map(([previousHash, changedAt]) => ({ previousHash, changedAt })); } }; export { SqliteWasmEngine };