UNPKG

meta-log-db

Version:

Native database package for Meta-Log (ProLog, DataLog, R5RS)

597 lines (505 loc) 15.5 kB
/** * CanvasL Metaverse Browser - Unified Browser API for CanvasL Operations * * Provides a unified interface for CanvasL metaverse browser functionality, * consolidating implementations from template-projector and ui packages. * * Features: * - CanvasL/JSONL file loading and parsing * - ProLog, DataLog, SPARQL query execution * - R5RS function execution * - SHACL validation * - CanvasL object execution (rdf-triple, r5rs-call, sparql-construct, etc.) * - Browser-native with IndexedDB caching */ import type { MetaLogDbBrowser, BrowserConfig } from './database.js'; import type { Fact, Canvas, PrologQueryResult, DatalogQueryResult, SparqlQueryResult, ShaclValidationReport } from '../types/index.js'; /** * Configuration for CanvasL Metaverse Browser */ export interface CanvasLBrowserConfig extends BrowserConfig { enableEncryption?: boolean; mnemonic?: string; indexedDBName?: string; cacheStrategy?: 'memory' | 'indexeddb' | 'both'; r5rsEngineURL?: string; } /** * Options for CanvasL queries */ export interface CanvasLQueryOptions { canvasFile?: string; timeout?: number; facts?: Fact[]; } /** * Result of CanvasL object execution */ export interface CanvasLExecutionResult { type: string; result?: any; error?: string; object?: any; } /** * Unified CanvasL Metaverse Browser */ export class CanvasLMetaverseBrowser { private db: MetaLogDbBrowser | null = null; private initialized: boolean = false; private initPromise: Promise<void> | null = null; private config: BrowserConfig; constructor(config: CanvasLBrowserConfig = {}) { this.config = { enableProlog: true, enableDatalog: true, enableRdf: true, enableShacl: true, enableEncryption: false, cacheStrategy: 'both', indexedDBName: 'meta-log-db', ...config }; } /** * Initialize browser database * Uses lazy initialization with promise-based pattern to prevent race conditions */ async init(): Promise<void> { if (this.initialized) { return; } // If initialization is already in progress, wait for it if (this.initPromise) { return this.initPromise; } this.initPromise = this._doInit(); return this.initPromise; } private async _doInit(): Promise<void> { try { // Dynamic import to avoid bundling issues const { MetaLogDbBrowser } = await import('./database.js'); // Create browser-native database instance this.db = new MetaLogDbBrowser(this.config); // Initialize (sets up IndexedDB, file I/O, etc.) await this.db.init(); this.initialized = true; } catch (error) { console.error('Failed to initialize CanvasLMetaverseBrowser:', error); throw new Error(`CanvasL Metaverse Browser initialization failed: ${error instanceof Error ? error.message : String(error)}`); } finally { this.initPromise = null; } } /** * Load CanvasL/JSONL file from URL or path * Standardized parameter order: path (identifier), url (optional fetch location) */ async loadCanvas(path: string, url?: string): Promise<void> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } try { // Use path as identifier, url as the actual URL to fetch // If url is not provided, use path as both const fileUrl = url || path; await this.db.loadCanvas(path, fileUrl); } catch (error) { throw new Error(`Failed to load canvas from ${url || path}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Parse JSONL canvas from URL without loading into database */ async parseJsonlCanvas(path: string, url?: string): Promise<Canvas> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return await this.db.parseJsonlCanvas(path, url); } /** * Parse CanvasL file from URL without loading into database */ async parseCanvasL(path: string, url?: string): Promise<Canvas> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return await this.db.parseCanvasL(path, url); } /** * Extract facts from loaded canvas */ extractFacts(canvasFile?: string): Fact[] { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return this.db.extractFacts(); } /** * Convert facts to RDF triples */ jsonlToRdf(facts?: Fact[]): any[] { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return this.db.jsonlToRdf(facts); } /** * Execute ProLog query */ async prologQuery(query: string, options?: CanvasLQueryOptions): Promise<PrologQueryResult> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } // Add facts if provided if (options?.facts && options.facts.length > 0) { this.db.buildPrologDb(options.facts); } return await this.db.prologQuery(query); } /** * Execute DataLog query */ async datalogQuery(goal: string, program?: any, options?: CanvasLQueryOptions): Promise<DatalogQueryResult> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return await this.db.datalogQuery(goal, program); } /** * Execute SPARQL query */ async sparqlQuery(query: string, options?: CanvasLQueryOptions): Promise<SparqlQueryResult> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return await this.db.sparqlQuery(query); } /** * Validate with SHACL */ async validateShacl(shapes?: any, triples?: any[]): Promise<ShaclValidationReport> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return await this.db.validateShacl(shapes, triples); } /** * Execute R5RS function */ async executeR5RS(functionName: string, args: any[] = []): Promise<any> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } return await this.db.executeR5RS(functionName, args); } /** * Get R5RS function (if available) */ async getR5RSFunction(name: string): Promise<any> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } // Try to access R5RS registry through the database instance const db = this.db as any; if (db.r5rs) { const fn = db.r5rs.getFunction(name); if (fn) { return { name, function: fn, available: true }; } } return null; } /** * List R5RS functions */ async listR5RSFunctions(pattern?: string): Promise<string[]> { await this.init(); if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } // Try to access R5RS registry through the database instance const db = this.db as any; if (db.r5rs) { let functions = db.r5rs.getFunctionNames(); // Filter by pattern if provided if (pattern) { const regex = new RegExp(pattern, 'i'); functions = functions.filter((name: string) => regex.test(name)); } return functions; } return []; } /** * Invoke R5RS function (alias for executeR5RS) */ async invokeR5RSFunction(name: string, args: any[], context?: any): Promise<any> { // MetaLogDbBrowser doesn't support context parameter directly // Context would need to be handled at a higher level return await this.executeR5RS(name, args); } /** * Add ProLog rule */ addPrologRule(rule: string): void { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } this.db.addPrologRule(rule); } /** * Add DataLog rule */ addDatalogRule(rule: string): void { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } // MetaLogDbBrowser uses buildDatalogProgram instead of addDatalogRule const db = this.db as any; if (db.buildDatalogProgram) { db.buildDatalogProgram([rule]); } } /** * Store RDF triples */ storeTriples(triples: any[]): void { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } this.db.storeTriples(triples); } /** * Add ProLog facts */ addPrologFacts(facts: Fact[]): void { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } this.db.buildPrologDb(facts); } /** * Add DataLog facts */ addDatalogFacts(facts: Fact[]): void { if (!this.db) { throw new Error('MetaLogDbBrowser not initialized'); } // Facts are added when loading canvas files // For direct addition, we'd need to build a DataLog program const db = this.db as any; if (db.datalog) { db.datalog.addFacts(facts); } } /** * Clear all data */ async clear(): Promise<void> { if (this.db) { await this.db.clearCache(); } } /** * Check if browser is initialized */ isInitialized(): boolean { return this.initialized && this.db !== null; } /** * Get the underlying database instance (for advanced usage) */ getDb(): MetaLogDbBrowser | null { return this.db; } /** * Execute a CanvasL object * Supports: rdf-triple, r5rs-call, sparql-construct, prolog-query, datalog-query, shacl-validate, slide */ async executeCanvasLObject(obj: any): Promise<CanvasLExecutionResult> { await this.init(); try { switch (obj.type) { case 'rdf-triple': return await this.executeRdfTriple(obj); case 'r5rs-call': return await this.executeR5RSCall(obj); case 'sparql-construct': return await this.executeSparqlConstruct(obj); case 'prolog-query': return await this.executePrologQuery(obj); case 'datalog-query': return await this.executeDatalogQuery(obj); case 'shacl-validate': return await this.executeShaclValidate(obj); case 'slide': return { type: 'slide', result: obj }; default: console.warn(`Unknown CanvasL object type: ${obj.type}`); return { type: 'unknown', result: obj }; } } catch (error) { return { type: obj.type || 'error', error: error instanceof Error ? error.message : String(error), object: obj }; } } /** * Execute RDF triple */ private async executeRdfTriple(obj: any): Promise<CanvasLExecutionResult> { const triple = { subject: obj.subject, predicate: obj.predicate, object: obj.object }; this.storeTriples([triple]); return { type: 'rdf-triple', result: triple }; } /** * Execute R5RS function call */ private async executeR5RSCall(obj: any): Promise<CanvasLExecutionResult> { // Handle different R5RS call formats let functionName: string; let args: any[] = []; if (obj.function) { functionName = obj.function; args = obj.args || []; } else if (obj.expression) { // Parse expression like "(r5rs:church-add 2 3)" const match = obj.expression.match(/^\(([^\s]+)\s*(.*)\)$/); if (match) { functionName = match[1]; // Simple argument parsing (could be enhanced) const argStr = match[2].trim(); if (argStr) { args = argStr.split(/\s+/).map((a: string) => { // Try to parse as number const num = Number(a); return isNaN(num) ? a : num; }); } } else { throw new Error(`Invalid R5RS expression format: ${obj.expression}`); } } else { throw new Error('R5RS call object missing function or expression'); } const result = await this.executeR5RS(functionName, args); return { type: 'r5rs-result', result }; } /** * Execute SPARQL CONSTRUCT query */ private async executeSparqlConstruct(obj: any): Promise<CanvasLExecutionResult> { const query = typeof obj.query === 'string' ? obj.query : obj.query?.template || obj.query; const result = await this.sparqlQuery(query); // If CONSTRUCT query, triples are already added to store return { type: 'sparql-result', result }; } /** * Execute ProLog query */ private async executePrologQuery(obj: any): Promise<CanvasLExecutionResult> { const query = obj.query || obj.goal; const facts = obj.facts || []; const result = await this.prologQuery(query, { facts }); return { type: 'prolog-result', result }; } /** * Execute DataLog query */ private async executeDatalogQuery(obj: any): Promise<CanvasLExecutionResult> { const goal = obj.goal || obj.query; const program = obj.program || null; const result = await this.datalogQuery(goal, program); return { type: 'datalog-result', result }; } /** * Execute SHACL validation */ private async executeShaclValidate(obj: any): Promise<CanvasLExecutionResult> { const shapes = obj.shapes || obj.shape; const triples = obj.triples || obj.focus || null; const result = await this.validateShacl(shapes, triples); return { type: 'shacl-result', result }; } /** * Execute multiple CanvasL objects */ async executeCanvasLObjects(objects: any[]): Promise<{ triples: any[]; slides: any[]; objects: Map<string, any>; errors: Array<{ object: any; error: string }>; }> { const results: { triples: any[]; slides: any[]; objects: Map<string, any>; errors: Array<{ object: any; error: string }>; } = { triples: [] as any[], slides: [] as any[], objects: new Map<string, any>(), errors: [] as Array<{ object: any; error: string }> }; for (const obj of objects) { try { // Skip directives and macros if (obj['@include'] || obj.type === '@include' || obj['@version'] || obj.type === 'macro') { continue; } // Handle slide objects directly if (obj.type === 'slide') { if (!obj.id) { console.warn('Slide object missing id:', obj); } if (!obj.dimension) { obj.dimension = '0D'; } results.slides.push(obj); if (obj.id) { results.objects.set(obj.id, obj); } continue; } const result = await this.executeCanvasLObject(obj); // Store object by ID if it has one if (obj.id) { results.objects.set(obj.id, result); } if (result.type === 'rdf-triple') { results.triples.push(result.result); } else if (result.type === 'slide') { results.slides.push(result.result); } else if (result.error) { results.errors.push({ object: obj, error: result.error }); } } catch (error) { results.errors.push({ object: obj, error: error instanceof Error ? error.message : String(error) }); } } return results; } }