mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
335 lines • 14.4 kB
JavaScript
/**
* MCard Database Schema Loader for JavaScript/TypeScript
*
* Singleton implementation that loads all schemas from:
* schema/mcard_schema.sql (SINGLE SOURCE OF TRUTH)
*
* This ensures schema consistency between Python and JavaScript runtimes.
*
* Usage:
* import { MCardSchema } from './schema';
*
* const schema = MCardSchema.getInstance();
* const cardSchema = schema.getTable('card');
* schema.initAllTables(db);
*
* Architecture Layers:
* Layer 1: Core Content Storage (card)
* Layer 2: Handle System (handle_registry, handle_history)
* Layer 3: Vector Storage (mcard_vector_metadata, mcard_embeddings)
* Layer 4: Semantic Versioning (handle_version_vectors, similarity_cache)
* Layer 5: Knowledge Graph (entities, relationships, communities)
*
* See: docs/architecture/Handle_Vector_Similarity_Design.md
*/
import { MCARD_SCHEMA_SQL, MCARD_VECTOR_SCHEMA_SQL } from './schema_constants';
const 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',
};
// ─────────────────────────────────────────────────────────────────────────────
// Singleton Schema Manager
// ─────────────────────────────────────────────────────────────────────────────
/**
* Singleton class for MCard database schema management.
*
* Ensures all schema definitions come from the single source of truth:
* schema/mcard_schema.sql
*/
export class MCardSchema {
static instance = null;
schemaPath = '';
rawSql = '';
statements = [];
tables = new Map();
indexes = 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'; // path no longer relevant
this.rawSql = MCARD_SCHEMA_SQL;
// Load vector schema if available
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) {
// Remove block comments
sql = sql.replace(/\/\*[\s\S]*?\*\//g, '');
const statements = [];
let current = [];
for (const line of sql.split('\n')) {
// Skip line comments
const stripped = line.split('--')[0].trim();
if (!stripped)
continue;
current.push(stripped);
if (stripped.endsWith(';')) {
const statement = current.join(' ');
// Skip INSERT statements
if (!statement.trim().toUpperCase().startsWith('INSERT')) {
statements.push(statement);
}
current = [];
}
}
return statements.filter(s => s.trim());
}
extractName(statement) {
// Handle CREATE TABLE
let match = statement.match(/CREATE\s+(?:VIRTUAL\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/i);
if (match)
return match[1];
// Handle CREATE INDEX
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);
// Add tables
for (const table of tables) {
const stmt = this.getTable(table);
if (stmt)
statements.push(stmt);
}
// Add indexes for these tables
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}]
)
`);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Convenience Exports (Backward Compatibility)
// ─────────────────────────────────────────────────────────────────────────────
// These are lazy-loaded from the singleton
let _cachedSchemas = {};
function getSchemaInstance() {
return MCardSchema.getInstance();
}
// Core schemas
export const CARD_TABLE_SCHEMA = getSchemaInstance().getTable('card') || '';
export const HANDLE_REGISTRY_SCHEMA = getSchemaInstance().getTable('handle_registry') || '';
export const HANDLE_HISTORY_SCHEMA = getSchemaInstance().getTable('handle_history') || '';
export const HANDLE_INDEX_SCHEMA = getSchemaInstance().getIndex('idx_handle_current_hash') || '';
// Vector schemas
export const VECTOR_METADATA_SCHEMA = getSchemaInstance().getTable('mcard_vector_metadata') || '';
export const VECTOR_METADATA_INDEX = getSchemaInstance().getIndex('idx_vector_metadata_hash') || '';
export const VECTOR_EMBEDDINGS_SCHEMA = getSchemaInstance().getTable('mcard_embeddings') || '';
export const VECTOR_FTS_SCHEMA = getSchemaInstance().getTable('mcard_fts') || '';
// Semantic versioning schemas
export const HANDLE_VERSION_VECTORS_SCHEMA = getSchemaInstance().getTable('handle_version_vectors') || '';
export const 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('; ');
export const VERSION_SIMILARITY_CACHE_SCHEMA = getSchemaInstance().getTable('version_similarity_cache') || '';
export const VERSION_SIMILARITY_CACHE_INDEX = getSchemaInstance().getIndex('idx_vsc_handle') || '';
// Graph schemas
export const GRAPH_ENTITY_SCHEMA = getSchemaInstance().getTable('graph_entities') || '';
export const GRAPH_ENTITY_INDEX_NAME = getSchemaInstance().getIndex('idx_entity_name') || '';
export const GRAPH_ENTITY_INDEX_TYPE = getSchemaInstance().getIndex('idx_entity_type') || '';
export const GRAPH_ENTITY_INDEX_SOURCE = getSchemaInstance().getIndex('idx_entity_source') || '';
export const GRAPH_RELATIONSHIP_SCHEMA = getSchemaInstance().getTable('graph_relationships') || '';
export const GRAPH_RELATIONSHIP_INDEX_SOURCE = getSchemaInstance().getIndex('idx_rel_source') || '';
export const GRAPH_RELATIONSHIP_INDEX_TARGET = getSchemaInstance().getIndex('idx_rel_target') || '';
export const GRAPH_COMMUNITY_SCHEMA = getSchemaInstance().getTable('graph_communities') || '';
export const GRAPH_COMMUNITY_INDEX_LEVEL = getSchemaInstance().getIndex('idx_community_level') || '';
export const GRAPH_EXTRACTION_SCHEMA = getSchemaInstance().getTable('graph_extractions') || '';
// Schema collections
export const CORE_SCHEMAS = {
card: CARD_TABLE_SCHEMA,
handleRegistry: HANDLE_REGISTRY_SCHEMA,
handleHistory: HANDLE_HISTORY_SCHEMA,
handleIndex: HANDLE_INDEX_SCHEMA,
};
export const VECTOR_SCHEMAS = {
metadata: VECTOR_METADATA_SCHEMA,
metadataIndex: VECTOR_METADATA_INDEX,
embeddings: VECTOR_EMBEDDINGS_SCHEMA,
fts: VECTOR_FTS_SCHEMA,
};
export const SEMANTIC_SCHEMAS = {
handleVersionVectors: HANDLE_VERSION_VECTORS_SCHEMA,
handleVersionVectorsIndexes: HANDLE_VERSION_VECTORS_INDEXES,
similarityCache: VERSION_SIMILARITY_CACHE_SCHEMA,
similarityCacheIndex: VERSION_SIMILARITY_CACHE_INDEX,
};
export const GRAPH_SCHEMAS = {
entities: GRAPH_ENTITY_SCHEMA,
entityIndexName: GRAPH_ENTITY_INDEX_NAME,
entityIndexType: GRAPH_ENTITY_INDEX_TYPE,
entityIndexSource: GRAPH_ENTITY_INDEX_SOURCE,
relationships: GRAPH_RELATIONSHIP_SCHEMA,
relIndexSource: GRAPH_RELATIONSHIP_INDEX_SOURCE,
relIndexTarget: GRAPH_RELATIONSHIP_INDEX_TARGET,
communities: GRAPH_COMMUNITY_SCHEMA,
communityIndexLevel: GRAPH_COMMUNITY_INDEX_LEVEL,
extractions: GRAPH_EXTRACTION_SCHEMA,
};
// ─────────────────────────────────────────────────────────────────────────────
// Initialization Functions (use singleton internally)
// ─────────────────────────────────────────────────────────────────────────────
export function initCoreSchemas(db) {
const schema = MCardSchema.getInstance();
// Initialize both core (card) and handle tables (handle_registry, handle_history)
// because they are fundamental to MCard operation
schema.initCoreTables(db);
schema.initHandleTables(db);
}
export function initVectorSchemas(db, enableFts = true) {
MCardSchema.getInstance().initVectorTables(db, enableFts);
}
export function initSemanticSchemas(db) {
MCardSchema.getInstance().initSemanticTables(db);
}
export function initGraphSchemas(db) {
MCardSchema.getInstance().initGraphTables(db);
}
export function initAllSchemas(db, options = {}) {
MCardSchema.getInstance().initAllTables(db, options);
}
// Dynamic schema generator (dimensions can't come from SQL file)
export function getVec0Schema(dimensions) {
return `
CREATE VIRTUAL TABLE IF NOT EXISTS mcard_vec USING vec0(
metadata_id INTEGER PRIMARY KEY,
embedding float[${dimensions}]
)
`;
}
//# sourceMappingURL=schema.js.map