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
JavaScript
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