UNPKG

mcard-js

Version:

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

162 lines 7.99 kB
import { openDB } from 'idb'; export class MCardStore { dbPromise; constructor(dbName = 'mcard-ptr-store') { this.dbPromise = openDB(dbName, 8, { upgrade(db, oldVersion, newVersion, transaction) { // Legacy cleanup if (db.objectStoreNames.contains('mcards')) db.deleteObjectStore('mcards'); if (db.objectStoreNames.contains('handles')) db.deleteObjectStore('handles'); // 1. Card Store let cardStore; if (!db.objectStoreNames.contains('card')) { cardStore = db.createObjectStore('card', { keyPath: 'hash' }); } else { cardStore = transaction.objectStore('card'); } if (!cardStore.indexNames.contains('by_g_time')) { cardStore.createIndex('by_g_time', 'g_time'); } // 2. Handle Registry let registryStore; if (!db.objectStoreNames.contains('handle_registry')) { registryStore = db.createObjectStore('handle_registry', { keyPath: 'handle' }); } else { registryStore = transaction.objectStore('handle_registry'); } if (!registryStore.indexNames.contains('by_current_hash')) { registryStore.createIndex('by_current_hash', 'current_hash'); } if (!registryStore.indexNames.contains('by_updated_at')) { registryStore.createIndex('by_updated_at', 'updated_at'); } // 3. Handle History let historyStore; if (!db.objectStoreNames.contains('handle_history')) { historyStore = db.createObjectStore('handle_history', { keyPath: 'id', autoIncrement: true }); } else { historyStore = transaction.objectStore('handle_history'); } if (!historyStore.indexNames.contains('by_handle')) { historyStore.createIndex('by_handle', 'handle'); } if (!historyStore.indexNames.contains('by_previous_hash')) { historyStore.createIndex('by_previous_hash', 'previous_hash'); } if (!historyStore.indexNames.contains('by_changed_at')) { historyStore.createIndex('by_changed_at', 'changed_at'); } // 4. Schema Version if (!db.objectStoreNames.contains('schema_version')) { const versionStore = db.createObjectStore('schema_version', { keyPath: 'version' }); versionStore.add({ version: '3.0.2', applied_at: new Date().toISOString(), description: 'Monadic Core Schema (Strict Compliance)' }); } }, }); } // ═══════════════════════════════════════════════════════════════ // CARD OPERATIONS // ═══════════════════════════════════════════════════════════════ async putCard(hash, content) { const db = await this.dbPromise; const g_time = new Date().toISOString(); // Ensure we store the full object adhering to schema await db.put('card', { hash, content, g_time }); } async getCard(hash) { const db = await this.dbPromise; return db.get('card', hash); } // ═══════════════════════════════════════════════════════════════ // HANDLE OPERATIONS (Transaction-safe) // ═══════════════════════════════════════════════════════════════ async setHandle(handle, newHash) { const db = await this.dbPromise; const tx = db.transaction(['handle_registry', 'handle_history'], 'readwrite'); const registry = tx.objectStore('handle_registry'); const history = tx.objectStore('handle_history'); const now = new Date().toISOString(); const existing = await registry.get(handle); if (existing) { // Record history before update await history.add({ handle, previous_hash: existing.current_hash, changed_at: now }); // Update handle await registry.put({ handle, current_hash: newHash, created_at: existing.created_at, updated_at: now }); } else { // Create new handle await registry.put({ handle, current_hash: newHash, created_at: now, updated_at: now }); // Should we record initial creation in history? // The report logic implies history is "previous state". // SQL Schema: `changed_at` implies a change event. // First creation is an event. Let's strictly follow the provided JS snippet from report. // Report logic: "if (existing) { ...history.add... } else { ...registry.put... }" // So report does NOT add history on creation. } await tx.done; } async resolveHandle(handle) { const db = await this.dbPromise; const tx = db.transaction(['handle_registry', 'card'], 'readonly'); const registry = tx.objectStore('handle_registry'); const entry = await registry.get(handle); if (!entry) return undefined; const cardStore = tx.objectStore('card'); return cardStore.get(entry.current_hash); } // ═══════════════════════════════════════════════════════════════ // QUERY HELPERS (using Indices) // ═══════════════════════════════════════════════════════════════ async getHandleHistory(handle) { const db = await this.dbPromise; return db.getAllFromIndex('handle_history', 'by_handle', handle); } async getHandlesByHash(hash) { const db = await this.dbPromise; return db.getAllFromIndex('handle_registry', 'by_current_hash', hash); } // ═══════════════════════════════════════════════════════════════ // UI HELPERS (For Browser Demo - keeping these for compatibility but implementing via new methods where possible) // ═══════════════════════════════════════════════════════════════ async getAllCards() { const db = await this.dbPromise; return db.getAll('card'); } async getAllHandles() { const db = await this.dbPromise; return db.getAll('handle_registry'); } async getAllHistory() { const db = await this.dbPromise; return db.getAll('handle_history'); } } //# sourceMappingURL=MCardStore.js.map