UNPKG

mcard-js

Version:

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

236 lines 9.04 kB
/** * IO Effects - Observable side-effects for Lambda Calculus computations * * Implements the IO Monad pattern for CLM: * IO a = World → (a, World') * * In our context: * Reduction = TermHash → (TermHash', IOEffects) * * IO effects are purely observational - they do not affect computation results. * The same input will always produce the same output regardless of IO configuration. * * @module mcard-js/ptr/lambda/IOEffects */ // ───────────────────────────────────────────────────────────────────────────── // IO Effects Handler // ───────────────────────────────────────────────────────────────────────────── export class IOEffectsHandler { config; events = []; constructor(config = {}) { this.config = { enabled: config.enabled ?? false, console: config.console ?? true, network: config.network, onStep: config.onStep ?? false, onComplete: config.onComplete ?? true, onError: config.onError ?? true, format: config.format ?? 'minimal' }; } /** * Check if IO effects are enabled */ isEnabled() { return this.config.enabled; } /** * Emit a step event */ async emitStep(stepNumber, termHash, prettyPrint) { if (!this.config.enabled || !this.config.onStep) return; const event = { type: 'step', stepNumber, termHash, prettyPrint, timestamp: new Date() }; this.events.push(event); await this.dispatch(event); } /** * Emit a completion event */ async emitComplete(normalForm, prettyPrint, totalSteps, reductionPath) { if (!this.config.enabled || !this.config.onComplete) return; const event = { type: 'complete', normalForm, prettyPrint, totalSteps, reductionPath, timestamp: new Date() }; this.events.push(event); await this.dispatch(event); } /** * Emit an error event */ async emitError(message, partialSteps, lastTermHash) { if (!this.config.enabled || !this.config.onError) return; const event = { type: 'error', message, partialSteps, lastTermHash, timestamp: new Date() }; this.events.push(event); await this.dispatch(event); } /** * Get all collected events */ getEvents() { return [...this.events]; } /** * Clear collected events */ clearEvents() { this.events = []; } /** * Dispatch event to configured outputs */ async dispatch(event) { const formatted = this.format(event); // Console output if (this.config.console) { this.logToConsole(event, formatted); } // Network output if (this.config.network?.enabled && this.config.network.endpoint) { await this.sendToNetwork(event, formatted); } } /** * Format event for output */ format(event) { switch (this.config.format) { case 'json': return JSON.stringify(event); case 'verbose': return this.formatVerbose(event); case 'minimal': default: return this.formatMinimal(event); } } formatMinimal(event) { switch (event.type) { case 'step': return `[IO:step ${event.stepNumber}] ${event.prettyPrint}`; case 'complete': return `[IO:complete] ${event.totalSteps} steps → ${event.prettyPrint}`; case 'error': return `[IO:error] ${event.message}`; } } formatVerbose(event) { const ts = event.timestamp.toISOString(); switch (event.type) { case 'step': return [ `┌─ IO Effect: Reduction Step ${event.stepNumber}`, `│ Time: ${ts}`, `│ Hash: ${event.termHash.substring(0, 16)}...`, `│ Term: ${event.prettyPrint}`, `└─────────────────────────────────────────` ].join('\n'); case 'complete': return [ `╔═══════════════════════════════════════════`, `║ IO Effect: Normalization Complete`, `╠═══════════════════════════════════════════`, `║ Time: ${ts}`, `║ Total Steps: ${event.totalSteps}`, `║ Normal Form: ${event.prettyPrint}`, `║ Hash: ${event.normalForm.substring(0, 16)}...`, `╚═══════════════════════════════════════════` ].join('\n'); case 'error': return [ `╔═══════════════════════════════════════════`, `║ IO Effect: ERROR`, `╠═══════════════════════════════════════════`, `║ Time: ${ts}`, `║ Message: ${event.message}`, `║ Partial Steps: ${event.partialSteps}`, `╚═══════════════════════════════════════════` ].join('\n'); } } logToConsole(event, formatted) { const prefix = '\x1b[36m'; // Cyan color const reset = '\x1b[0m'; switch (event.type) { case 'step': console.log(`${prefix}${formatted}${reset}`); break; case 'complete': console.log(`${prefix}${formatted}${reset}`); break; case 'error': console.error(`\x1b[31m${formatted}${reset}`); // Red for errors break; } } async sendToNetwork(event, formatted) { if (!this.config.network?.endpoint) return; try { const response = await fetch(this.config.network.endpoint, { method: this.config.network.method || 'POST', headers: { 'Content-Type': 'application/json', ...this.config.network.headers }, body: JSON.stringify({ event: event.type, data: event, formatted }) }); if (!response.ok) { console.warn(`IO network effect failed: ${response.status}`); } } catch (err) { console.warn(`IO network effect error: ${err}`); } } } // ───────────────────────────────────────────────────────────────────────────── // Factory and Utilities // ───────────────────────────────────────────────────────────────────────────── /** * Create an IO effects handler from CLM config */ export function createIOHandler(config) { if (!config || !config.io_effects) { return new IOEffectsHandler({ enabled: false }); } const ioConfig = config.io_effects; return new IOEffectsHandler({ enabled: ioConfig.enabled ?? false, console: ioConfig.console ?? true, network: ioConfig.network, onStep: ioConfig.on_step ?? ioConfig.onStep ?? false, onComplete: ioConfig.on_complete ?? ioConfig.onComplete ?? true, onError: ioConfig.on_error ?? ioConfig.onError ?? true, format: ioConfig.format ?? 'minimal' }); } /** * No-op handler for when IO effects are disabled */ export const noopIOHandler = new IOEffectsHandler({ enabled: false }); //# sourceMappingURL=IOEffects.js.map