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
JavaScript
// 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
};