UNPKG

mcard-js

Version:

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

718 lines (668 loc) 36.6 kB
// src/model/Handle.ts var MAX_HANDLE_LENGTH = 255; function isValidStartChar(char) { return /^\p{L}$/u.test(char); } function isValidBodyChar(char) { return /^[\p{L}\p{N}_./ -]$/u.test(char); } var HandleValidationError = class extends Error { constructor(message) { super(message); this.name = "HandleValidationError"; } }; function validateHandle(handle) { if (!handle) { throw new HandleValidationError("Handle cannot be empty."); } const normalized = handle.trim().normalize("NFC").toLowerCase(); if (normalized.length === 0) { throw new HandleValidationError("Handle cannot be empty after normalization."); } if (normalized.length > MAX_HANDLE_LENGTH) { throw new HandleValidationError( `Handle '${handle}' is too long (${normalized.length} chars). Maximum is ${MAX_HANDLE_LENGTH}.` ); } if (!isValidStartChar(normalized[0])) { throw new HandleValidationError( `Invalid handle '${handle}'. Must start with a letter (any language).` ); } for (let i = 1; i < normalized.length; i++) { if (!isValidBodyChar(normalized[i])) { throw new HandleValidationError( `Invalid character '${normalized[i]}' at position ${i} in handle '${handle}'.` ); } } return normalized; } var ContentHandle = class { handle; currentHash; createdAt; updatedAt; constructor(handle, currentHash, createdAt, updatedAt) { this.handle = validateHandle(handle); this.currentHash = currentHash; this.createdAt = createdAt ?? /* @__PURE__ */ new Date(); this.updatedAt = updatedAt ?? this.createdAt; } /** * Update handle to point to new hash * @returns Previous hash for history tracking */ update(newHash) { const previousHash = this.currentHash; this.currentHash = newHash; this.updatedAt = /* @__PURE__ */ new Date(); return previousHash; } toObject() { return { handle: this.handle, currentHash: this.currentHash, createdAt: this.createdAt.toISOString(), updatedAt: this.updatedAt.toISOString() }; } }; // src/storage/schema_constants.ts var MCARD_SCHEMA_SQL = `-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- MCard Unified Database Schema (Monadic Core) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- -- STABILITY WARNING: This schema is the invariant "seeding meta-language" for all -- Domain Specific Languages (DSLs). It must remain as stable as possible. -- -- THE UNIFICATION THESIS: -- This design unites three distinct namespaces into a single relational network -- by leveraging the power of cryptographic hash functions: -- 1. CONTENT SPACE (Intrinsic Identity): Content-Addressable Storage (CAS). -- 2. HANDLE SPACE (Reserved Words): Mapping human logic to content hashes. -- 3. VERSION SPACE (Temporal Evolution): Tracking state transitions via hash history. -- -- TURING MACHINE ANALOGY (The Infinitely Long Tape): -- The \`card\` table stores content blobs with unique identities, emulating the -- "Infinitely Long Tape" of the Turing Machine formalism. By using relational -- queries, this tape can be dynamically constructed and traversed for different -- DSLs, providing a flexible substrate for practically all computable languages. -- -- By using hash values as the universal primitives across content, handles, and -- time, we create a "Wordless Book" \u2014 a minimal substrate capable of seeding any -- domain-specific knowledge system without requiring schema changes. -- -- Monadic Mapping (See: Monadic Justification for Schema Design.md): -- 1. Card = Monad (Perception/State/Exponent) - Intrinsic Identity -- 2. Handle = Registry (Appetition/Reader/Sum) - Mutable Reference -- 3. Version = History (Harmony/Writer/Product) - Coordinated Evolution -- -- Version: 3.0.2 (Turing Tape Refinement) -- Last Updated: 2025-12-20 -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- LAYER 1: CORE CONTENT-ADDRESSABLE STORAGE (The Monad / Exponent) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- The fundamental MCard storage: content-addressed by cryptographic hash. -- "Each Monad ... mirrors the universe" -- @table card -- @description Core content-addressable storage table (Monad/State) -- @column hash - SHA-256 hash of content (primary key) -- @column content - The actual content (BLOB for binary safety) -- @column g_time - Generation timestamp (ISO 8601) CREATE TABLE IF NOT EXISTS card ( hash TEXT PRIMARY KEY, content BLOB NOT NULL, g_time TEXT NOT NULL ); -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- LAYER 2: HANDLE SYSTEM (Appetition / Sum) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- The mutable pointer that "desires" to reference new content. -- "Appetition: the tendency to move to new states" -- @table handle_registry (Monadic Role: Handle) -- @description Maps human-readable handles to current content hashes -- @column handle - UTF-8 handle name (primary key) -- @column current_hash - FK to card.hash of current version -- @column created_at - When handle was first created -- @column updated_at - When handle was last updated CREATE TABLE IF NOT EXISTS handle_registry ( handle TEXT PRIMARY KEY, current_hash TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (current_hash) REFERENCES card(hash) ); -- @index idx_handle_current_hash -- @description Efficient reverse lookup from hash to handles CREATE INDEX IF NOT EXISTS idx_handle_current_hash ON handle_registry(current_hash); -- @table handle_history (Monadic Role: Version / Product) -- @description Audit trail for handle pointer changes (Pre-Established Harmony) -- @column id - Auto-increment primary key -- @column handle - The handle that was updated -- @column previous_hash - Hash it pointed to before update -- @column changed_at - When the change occurred CREATE TABLE IF NOT EXISTS handle_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, handle TEXT NOT NULL, previous_hash TEXT NOT NULL, changed_at TEXT NOT NULL, FOREIGN KEY (handle) REFERENCES handle_registry(handle), FOREIGN KEY (previous_hash) REFERENCES card(hash) ); -- @index idx_handle_history_handle -- @description Efficient lookup of history by handle CREATE INDEX IF NOT EXISTS idx_handle_history_handle ON handle_history(handle); -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- LEGACY SUPPORT: FTS5 Documents Table -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- KEPT FOR BACKWARD COMPATIBILITY with existing codebase. -- New implementations should use mcard_vector_schema.sql -> mcard_fts -- @virtual_table documents -- @description Legacy FTS table for backward compatibility -- @note Synced with card table via triggers CREATE VIRTUAL TABLE IF NOT EXISTS documents USING fts5(content); -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- SCHEMA METADATA -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- @table schema_version -- @description Tracks schema version for migrations CREATE TABLE IF NOT EXISTS schema_version ( version TEXT PRIMARY KEY, applied_at TEXT NOT NULL, description TEXT ); -- Insert current schema version INSERT OR IGNORE INTO schema_version (version, applied_at, description) VALUES ('3.0.0', datetime('now'), 'Monadic Core Schema (split vectors to mcard_vector_schema.sql)'); `; var MCARD_VECTOR_SCHEMA_SQL = `-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- MCard Vector Database Schema (mcard_vectors.db) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- -- This schema defines the structure for "Secondary Qualities" (Extrinsic Embeddings). -- It is separated from the core mcard.db (Monadic Intrinsic Properties) to ensure: -- 1. Separation of Concerns (Intrinsic vs Extrinsic) -- 2. Linearity (Vectors are observer-dependent and large) -- 3. Upgradability (Embedding models change frequently) -- -- See: Monadic Justification for Schema Design.md -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- LAYER 3: VECTOR STORAGE (Semantic Embeddings) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- Storage for vector embeddings enabling semantic search. -- Links to content via hash for content-addressing. -- @table mcard_vector_metadata -- @description Metadata for stored embeddings -- @column id - Auto-increment primary key -- @column hash - FK to card.hash -- @column model_name - Embedding model used (e.g., "nomic-embed-text") -- @column dimensions - Vector dimensions -- @column chunk_index - Index for chunked documents (0 = first/whole) -- @column chunk_total - Total chunks for this document -- @column chunk_text - Preview text for this chunk -- @column created_at - When embedding was created CREATE TABLE IF NOT EXISTS mcard_vector_metadata ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT NOT NULL, model_name TEXT NOT NULL, dimensions INTEGER NOT NULL, chunk_index INTEGER DEFAULT 0, chunk_total INTEGER DEFAULT 1, chunk_text TEXT, created_at TEXT NOT NULL, UNIQUE(hash, chunk_index) ); -- @index idx_vector_metadata_hash -- @description Efficient lookup of embeddings by content hash CREATE INDEX IF NOT EXISTS idx_vector_metadata_hash ON mcard_vector_metadata(hash); -- @table mcard_embeddings -- @description Fallback embedding storage when sqlite-vec is unavailable -- @column id - Auto-increment primary key -- @column metadata_id - FK to mcard_vector_metadata.id -- @column embedding - Serialized float32 vector as BLOB CREATE TABLE IF NOT EXISTS mcard_embeddings ( id INTEGER PRIMARY KEY AUTOINCREMENT, metadata_id INTEGER NOT NULL, embedding BLOB NOT NULL, UNIQUE(metadata_id), FOREIGN KEY (metadata_id) REFERENCES mcard_vector_metadata(id) ); -- @virtual_table mcard_fts -- @description Full-text search for hybrid retrieval -- @note Uses Porter stemming with Unicode support CREATE VIRTUAL TABLE IF NOT EXISTS mcard_fts USING fts5( hash, content, tokenize='porter unicode61' ); -- Note: sqlite-vec virtual table is created dynamically with dimensions: -- CREATE VIRTUAL TABLE IF NOT EXISTS mcard_vec USING vec0( -- metadata_id INTEGER PRIMARY KEY, -- embedding float[\${dimensions}] -- ); -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- LAYER 4: SEMANTIC VERSIONING (Handle-Vector Bridge) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- Bridges handles to vector embeddings for semantic version comparison. -- Enables measuring semantic drift between document versions. -- @table handle_version_vectors -- @description Links handle versions to their semantic embeddings -- @column id - Auto-increment primary key -- @column handle - FK to handle_registry.handle -- @column hash - FK to card.hash (this version) -- @column parent_hash - FK to card.hash (previous version) -- @column version_order - 0 = current, 1 = previous, etc. -- @column is_current - TRUE if this is the current version -- @column embedding_id - FK to mcard_vector_metadata.id -- @column semantic_delta_from_parent - Cosine similarity to parent [-1, 1] -- @column upgrade_type - Classification: 'trivial' | 'minor' | 'major' | 'breaking' -- @column created_at - When this version was linked CREATE TABLE IF NOT EXISTS handle_version_vectors ( id INTEGER PRIMARY KEY AUTOINCREMENT, handle TEXT NOT NULL, hash TEXT NOT NULL, parent_hash TEXT, version_order INTEGER NOT NULL, is_current BOOLEAN DEFAULT FALSE, embedding_id INTEGER, semantic_delta_from_parent REAL, upgrade_type TEXT, created_at TEXT NOT NULL, UNIQUE(handle, hash), FOREIGN KEY (embedding_id) REFERENCES mcard_vector_metadata(id) ); -- @index idx_hvv_handle -- @description Efficient lookup of versions by handle CREATE INDEX IF NOT EXISTS idx_hvv_handle ON handle_version_vectors(handle); -- @index idx_hvv_hash -- @description Efficient lookup of versions by hash CREATE INDEX IF NOT EXISTS idx_hvv_hash ON handle_version_vectors(hash); -- @index idx_hvv_current -- @description Efficient lookup of current versions CREATE INDEX IF NOT EXISTS idx_hvv_current ON handle_version_vectors(is_current); -- @index idx_hvv_parent -- @description Efficient lookup by parent hash CREATE INDEX IF NOT EXISTS idx_hvv_parent ON handle_version_vectors(parent_hash); -- @table version_similarity_cache -- @description Precomputed pairwise similarities for performance -- @column id - Auto-increment primary key -- @column handle - Handle these versions belong to -- @column hash_a - First version hash -- @column hash_b - Second version hash -- @column similarity_score - Cosine similarity [-1, 1] -- @column distance_euclidean - L2 distance [0, \u221E) -- @column computed_at - When similarity was computed CREATE TABLE IF NOT EXISTS version_similarity_cache ( id INTEGER PRIMARY KEY AUTOINCREMENT, handle TEXT NOT NULL, hash_a TEXT NOT NULL, hash_b TEXT NOT NULL, similarity_score REAL NOT NULL, distance_euclidean REAL, computed_at TEXT NOT NULL, UNIQUE(handle, hash_a, hash_b) ); -- @index idx_vsc_handle -- @description Efficient lookup of cached similarities by handle CREATE INDEX IF NOT EXISTS idx_vsc_handle ON version_similarity_cache(handle); -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- LAYER 5: KNOWLEDGE GRAPH (GraphRAG) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- Storage for extracted knowledge graph entities and relationships. -- @table graph_entities -- @description Nodes in the knowledge graph -- @column id - Auto-increment primary key -- @column name - Entity name -- @column type - Entity type (e.g., "Person", "Organization") -- @column description - Optional description -- @column source_hash - FK to card.hash where entity was extracted -- @column embedding - Optional entity embedding -- @column created_at - When entity was created CREATE TABLE IF NOT EXISTS graph_entities ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, type TEXT NOT NULL, description TEXT, source_hash TEXT NOT NULL, embedding BLOB, created_at TEXT NOT NULL, UNIQUE(name, type, source_hash) ); -- @index idx_entity_name -- @description Efficient entity lookup by name CREATE INDEX IF NOT EXISTS idx_entity_name ON graph_entities(name); -- @index idx_entity_type -- @description Efficient entity lookup by type CREATE INDEX IF NOT EXISTS idx_entity_type ON graph_entities(type); -- @index idx_entity_source -- @description Efficient entity lookup by source document CREATE INDEX IF NOT EXISTS idx_entity_source ON graph_entities(source_hash); -- @table graph_relationships -- @description Edges in the knowledge graph -- @column id - Auto-increment primary key -- @column source_entity_id - FK to graph_entities.id (from) -- @column target_entity_id - FK to graph_entities.id (to) -- @column relationship - Relationship type/label -- @column description - Optional description -- @column weight - Relationship strength -- @column source_hash - FK to card.hash where relationship was extracted -- @column created_at - When relationship was created CREATE TABLE IF NOT EXISTS graph_relationships ( id INTEGER PRIMARY KEY AUTOINCREMENT, source_entity_id INTEGER NOT NULL, target_entity_id INTEGER NOT NULL, relationship TEXT NOT NULL, description TEXT, weight REAL DEFAULT 1.0, source_hash TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE(source_entity_id, target_entity_id, relationship, source_hash), FOREIGN KEY (source_entity_id) REFERENCES graph_entities(id), FOREIGN KEY (target_entity_id) REFERENCES graph_entities(id) ); -- @index idx_rel_source -- @description Efficient lookup of relationships by source entity CREATE INDEX IF NOT EXISTS idx_rel_source ON graph_relationships(source_entity_id); -- @index idx_rel_target -- @description Efficient lookup of relationships by target entity CREATE INDEX IF NOT EXISTS idx_rel_target ON graph_relationships(target_entity_id); -- @table graph_communities -- @description Hierarchical community summaries (GraphRAG) -- @column id - Auto-increment primary key -- @column level - Hierarchy level (0 = leaf) -- @column title - Community title -- @column summary - AI-generated summary -- @column embedding - Community embedding -- @column member_entity_ids - JSON array of entity IDs -- @column parent_community_id - FK to parent community -- @column created_at - When community was created CREATE TABLE IF NOT EXISTS graph_communities ( id INTEGER PRIMARY KEY AUTOINCREMENT, level INTEGER NOT NULL DEFAULT 0, title TEXT, summary TEXT NOT NULL, embedding BLOB, member_entity_ids TEXT, parent_community_id INTEGER, created_at TEXT NOT NULL, FOREIGN KEY (parent_community_id) REFERENCES graph_communities(id) ); -- @index idx_community_level -- @description Efficient lookup of communities by level CREATE INDEX IF NOT EXISTS idx_community_level ON graph_communities(level); -- @table graph_extractions -- @description Tracks which documents have been processed for entity extraction -- @column hash - FK to card.hash (primary key) -- @column entity_count - Number of entities extracted -- @column relationship_count - Number of relationships extracted -- @column extracted_at - When extraction was performed CREATE TABLE IF NOT EXISTS graph_extractions ( hash TEXT PRIMARY KEY, entity_count INTEGER DEFAULT 0, relationship_count INTEGER DEFAULT 0, extracted_at TEXT NOT NULL ); -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- SCHEMA METADATA (Vector DB) -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -- @table schema_version -- @description Tracks schema version for migrations CREATE TABLE IF NOT EXISTS vector_schema_version ( version TEXT PRIMARY KEY, applied_at TEXT NOT NULL, description TEXT ); -- Insert current schema version INSERT OR IGNORE INTO vector_schema_version (version, applied_at, description) VALUES ('1.0.0', datetime('now'), 'Initial MCard Vector Schema'); `; // src/storage/schema.ts var TABLE_LAYERS = { // Layer 1: Core "card": "core", // Layer 2: Handle System "handle_registry": "handle", "handle_history": "handle", // Layer 3: Vector Storage "mcard_vector_metadata": "vector", "mcard_embeddings": "vector", "mcard_fts": "vector", // Layer 4: Semantic Versioning "handle_version_vectors": "semantic", "version_similarity_cache": "semantic", // Layer 5: Knowledge Graph "graph_entities": "graph", "graph_relationships": "graph", "graph_communities": "graph", "graph_extractions": "graph", // Metadata "schema_version": "metadata" }; var MCardSchema = class _MCardSchema { static instance = null; schemaPath = ""; rawSql = ""; statements = []; tables = /* @__PURE__ */ new Map(); indexes = /* @__PURE__ */ new Map(); loaded = false; constructor() { } /** * Get the singleton instance. */ static getInstance() { if (!_MCardSchema.instance) { _MCardSchema.instance = new _MCardSchema(); _MCardSchema.instance.load(); } return _MCardSchema.instance; } /** * Reset the singleton (for testing). */ static resetInstance() { _MCardSchema.instance = null; } load() { if (this.loaded) return; this.schemaPath = "IN_MEMORY_CONSTANTS"; this.rawSql = MCARD_SCHEMA_SQL; if (MCARD_VECTOR_SCHEMA_SQL) { this.rawSql += "\n\n" + MCARD_VECTOR_SCHEMA_SQL; } this.statements = this.parseStatements(this.rawSql); for (const stmt of this.statements) { const name = this.extractName(stmt); if (name) { const upper = stmt.toUpperCase(); if (upper.includes("CREATE TABLE") || upper.includes("CREATE VIRTUAL TABLE")) { this.tables.set(name.toLowerCase(), stmt); } else if (upper.includes("CREATE INDEX")) { this.indexes.set(name.toLowerCase(), stmt); } } } this.loaded = true; } parseStatements(sql) { sql = sql.replace(/\/\*[\s\S]*?\*\//g, ""); const statements = []; let current = []; for (const line of sql.split("\n")) { const stripped = line.split("--")[0].trim(); if (!stripped) continue; current.push(stripped); if (stripped.endsWith(";")) { const statement = current.join(" "); if (!statement.trim().toUpperCase().startsWith("INSERT")) { statements.push(statement); } current = []; } } return statements.filter((s) => s.trim()); } extractName(statement) { let match = statement.match(/CREATE\s+(?:VIRTUAL\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/i); if (match) return match[1]; match = statement.match(/CREATE\s+INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/i); if (match) return match[1]; return null; } // ───────────────────────────────────────────────────────────────────────── // Schema Access // ───────────────────────────────────────────────────────────────────────── getSchemaPath() { return this.schemaPath; } getTable(tableName) { return this.tables.get(tableName.toLowerCase()); } getIndex(indexName) { return this.indexes.get(indexName.toLowerCase()); } getAllTables() { return new Map(this.tables); } getAllIndexes() { return new Map(this.indexes); } getAllStatements() { return [...this.statements]; } getTablesByLayer(layer) { return Object.entries(TABLE_LAYERS).filter(([_, l]) => l === layer).map(([name, _]) => name); } getLayerStatements(layer) { const statements = []; const tables = this.getTablesByLayer(layer); for (const table of tables) { const stmt = this.getTable(table); if (stmt) statements.push(stmt); } for (const [_, stmt] of this.indexes) { const match = stmt.match(/ON\s+(\w+)/i); if (match && tables.includes(match[1].toLowerCase())) { statements.push(stmt); } } return statements; } // ───────────────────────────────────────────────────────────────────────── // Database Initialization // ───────────────────────────────────────────────────────────────────────── execStatements(db, statements) { for (const stmt of statements) { db.exec(stmt); } return statements.length; } initLayer(db, layer) { return this.execStatements(db, this.getLayerStatements(layer)); } initCoreTables(db) { return this.initLayer(db, "core"); } initHandleTables(db) { return this.initLayer(db, "handle"); } initVectorTables(db, enableFts = true) { const statements = this.getLayerStatements("vector").filter((s) => enableFts || !s.toLowerCase().includes("fts")); return this.execStatements(db, statements); } initSemanticTables(db) { return this.initLayer(db, "semantic"); } initGraphTables(db) { return this.initLayer(db, "graph"); } initAllTables(db, options = {}) { const { enableFts = true, enableGraph = true, enableSemantic = true } = options; let count = 0; count += this.initCoreTables(db); count += this.initHandleTables(db); count += this.initVectorTables(db, enableFts); if (enableSemantic) { count += this.initSemanticTables(db); } if (enableGraph) { count += this.initGraphTables(db); } return count; } initVec0Table(db, dimensions) { db.exec(` CREATE VIRTUAL TABLE IF NOT EXISTS mcard_vec USING vec0( metadata_id INTEGER PRIMARY KEY, embedding float[${dimensions}] ) `); } }; function getSchemaInstance() { return MCardSchema.getInstance(); } var CARD_TABLE_SCHEMA = getSchemaInstance().getTable("card") || ""; var HANDLE_REGISTRY_SCHEMA = getSchemaInstance().getTable("handle_registry") || ""; var HANDLE_HISTORY_SCHEMA = getSchemaInstance().getTable("handle_history") || ""; var HANDLE_INDEX_SCHEMA = getSchemaInstance().getIndex("idx_handle_current_hash") || ""; var VECTOR_METADATA_SCHEMA = getSchemaInstance().getTable("mcard_vector_metadata") || ""; var VECTOR_METADATA_INDEX = getSchemaInstance().getIndex("idx_vector_metadata_hash") || ""; var VECTOR_EMBEDDINGS_SCHEMA = getSchemaInstance().getTable("mcard_embeddings") || ""; var VECTOR_FTS_SCHEMA = getSchemaInstance().getTable("mcard_fts") || ""; var HANDLE_VERSION_VECTORS_SCHEMA = getSchemaInstance().getTable("handle_version_vectors") || ""; var HANDLE_VERSION_VECTORS_INDEXES = [ getSchemaInstance().getIndex("idx_hvv_handle"), getSchemaInstance().getIndex("idx_hvv_hash"), getSchemaInstance().getIndex("idx_hvv_current"), getSchemaInstance().getIndex("idx_hvv_parent") ].filter(Boolean).join("; "); var VERSION_SIMILARITY_CACHE_SCHEMA = getSchemaInstance().getTable("version_similarity_cache") || ""; var VERSION_SIMILARITY_CACHE_INDEX = getSchemaInstance().getIndex("idx_vsc_handle") || ""; var GRAPH_ENTITY_SCHEMA = getSchemaInstance().getTable("graph_entities") || ""; var GRAPH_ENTITY_INDEX_NAME = getSchemaInstance().getIndex("idx_entity_name") || ""; var GRAPH_ENTITY_INDEX_TYPE = getSchemaInstance().getIndex("idx_entity_type") || ""; var GRAPH_ENTITY_INDEX_SOURCE = getSchemaInstance().getIndex("idx_entity_source") || ""; var GRAPH_RELATIONSHIP_SCHEMA = getSchemaInstance().getTable("graph_relationships") || ""; var GRAPH_RELATIONSHIP_INDEX_SOURCE = getSchemaInstance().getIndex("idx_rel_source") || ""; var GRAPH_RELATIONSHIP_INDEX_TARGET = getSchemaInstance().getIndex("idx_rel_target") || ""; var GRAPH_COMMUNITY_SCHEMA = getSchemaInstance().getTable("graph_communities") || ""; var GRAPH_COMMUNITY_INDEX_LEVEL = getSchemaInstance().getIndex("idx_community_level") || ""; var GRAPH_EXTRACTION_SCHEMA = getSchemaInstance().getTable("graph_extractions") || ""; var CORE_SCHEMAS = { card: CARD_TABLE_SCHEMA, handleRegistry: HANDLE_REGISTRY_SCHEMA, handleHistory: HANDLE_HISTORY_SCHEMA, handleIndex: HANDLE_INDEX_SCHEMA }; function initCoreSchemas(db) { const schema = MCardSchema.getInstance(); schema.initCoreTables(db); schema.initHandleTables(db); } export { HandleValidationError, validateHandle, ContentHandle, CORE_SCHEMAS, initCoreSchemas };