UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

611 lines (537 loc) 17.5 kB
/** * Advanced TypeScript-aware serializer for session persistence * Handles complex data structures, functions, and type preservation * * @module advanced-serializer */ /** * Enhanced serializer that handles TypeScript-specific data types * and complex JavaScript objects that standard JSON can't handle */ export class AdvancedSerializer { constructor(options = {}) { this.options = { enableCompression: options.enableCompression || false, maxDepth: options.maxDepth || 100, preserveUndefined: options.preserveUndefined !== false, preserveFunctions: options.preserveFunctions || false, preserveSymbols: options.preserveSymbols || false, customSerializers: new Map(options.customSerializers || []), ...options }; // Built-in type handlers this.typeHandlers = new Map([ ['Date', this._serializeDate.bind(this)], ['Map', this._serializeMap.bind(this)], ['Set', this._serializeSet.bind(this)], ['RegExp', this._serializeRegExp.bind(this)], ['Error', this._serializeError.bind(this)], ['BigInt', this._serializeBigInt.bind(this)], ['ArrayBuffer', this._serializeArrayBuffer.bind(this)], ['TypedArray', this._serializeTypedArray.bind(this)], ['Function', this._serializeFunction.bind(this)], ['Symbol', this._serializeSymbol.bind(this)] ]); // Deserialization handlers this.deserializers = new Map([ ['__Date__', this._deserializeDate.bind(this)], ['__Map__', this._deserializeMap.bind(this)], ['__Set__', this._deserializeSet.bind(this)], ['__RegExp__', this._deserializeRegExp.bind(this)], ['__Error__', this._deserializeError.bind(this)], ['__BigInt__', this._deserializeBigInt.bind(this)], ['__ArrayBuffer__', this._deserializeArrayBuffer.bind(this)], ['__TypedArray__', this._deserializeTypedArray.bind(this)], ['__Function__', this._deserializeFunction.bind(this)], ['__Symbol__', this._deserializeSymbol.bind(this)], ['__undefined__', () => undefined], ['__NaN__', () => NaN], ['__Infinity__', () => Infinity], ['__-Infinity__', () => -Infinity] ]); } /** * Serialize any JavaScript/TypeScript value to a JSON-compatible string */ serialize(value, context = { depth: 0, seen: new WeakSet() }) { try { const serialized = this._serializeValue(value, context); const result = JSON.stringify(serialized); if (this.options.enableCompression && result.length > 1024) { // In production, use actual compression library return this._compress(result); } return result; } catch (error) { throw new SerializationError(`Serialization failed: ${error.message}`, { originalError: error, value: this._safeStringify(value) }); } } /** * Deserialize a string back to the original JavaScript/TypeScript value */ deserialize(serialized) { try { let data = serialized; // Handle compression if (this.options.enableCompression && this._isCompressed(serialized)) { data = this._decompress(serialized); } const parsed = JSON.parse(data); return this._deserializeValue(parsed); } catch (error) { throw new DeserializationError(`Deserialization failed: ${error.message}`, { originalError: error, serialized: serialized?.substring?.(0, 200) + '...' }); } } /** * Serialize session-specific data with enhanced TypeScript support */ serializeSessionData(sessionData) { const startTime = Date.now(); try { // Enhanced session data with metadata const enhancedData = { ...sessionData, __serializer_meta__: { version: '1.0.0', timestamp: new Date().toISOString(), nodeVersion: process.version, platform: process.platform, serializer: 'AdvancedSerializer' } }; const result = this.serialize(enhancedData); // Log performance metrics const duration = Date.now() - startTime; if (duration > 100) { console.warn(`[AdvancedSerializer] Slow serialization: ${duration}ms for ${result.length} bytes`); } return result; } catch (error) { throw new SessionSerializationError(`Session serialization failed: ${error.message}`, { originalError: error, sessionId: sessionData?.id, dataKeys: Object.keys(sessionData || {}) }); } } /** * Deserialize session data with validation and recovery */ deserializeSessionData(serialized) { try { const data = this.deserialize(serialized); // Validate serializer metadata if (data.__serializer_meta__) { const meta = data.__serializer_meta__; if (meta.serializer !== 'AdvancedSerializer') { console.warn(`[AdvancedSerializer] Data serialized with different serializer: ${meta.serializer}`); } // Remove metadata before returning delete data.__serializer_meta__; } return data; } catch (error) { // Attempt fallback deserialization try { console.warn(`[AdvancedSerializer] Attempting fallback deserialization`); const fallbackData = JSON.parse(serialized); // Basic cleanup of common serialization issues return this._cleanupFallbackData(fallbackData); } catch (fallbackError) { throw new SessionDeserializationError(`Session deserialization failed: ${error.message}`, { originalError: error, fallbackError: fallbackError.message }); } } } /** * Internal value serialization with type preservation */ _serializeValue(value, context) { // Handle circular references if (context.depth > this.options.maxDepth) { return { __circular__: true }; } // Handle null and primitives if (value === null) return null; if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { return value; } // Handle special values if (value === undefined && this.options.preserveUndefined) { return { __type__: '__undefined__' }; } if (typeof value === 'number' && isNaN(value)) { return { __type__: '__NaN__' }; } if (value === Infinity) { return { __type__: '__Infinity__' }; } if (value === -Infinity) { return { __type__: '__-Infinity__' }; } // Handle objects with circular reference detection if (typeof value === 'object') { if (context.seen.has(value)) { return { __circular__: true }; } context.seen.add(value); } // Handle typed objects const typeName = this._getObjectType(value); if (this.typeHandlers.has(typeName)) { return this.typeHandlers.get(typeName)(value, context); } // Handle custom serializers for (const [matcher, serializer] of this.options.customSerializers) { if (typeof matcher === 'function' && matcher(value)) { return serializer(value, context); } } // Handle arrays if (Array.isArray(value)) { return value.map(item => this._serializeValue(item, { ...context, depth: context.depth + 1 })); } // Handle plain objects if (value && typeof value === 'object') { const result = {}; for (const [key, val] of Object.entries(value)) { // Handle symbol keys if enabled if (typeof key === 'symbol' && this.options.preserveSymbols) { result[`__symbol_${key.toString()}__`] = this._serializeValue(val, { ...context, depth: context.depth + 1 }); } else if (typeof key === 'string') { result[key] = this._serializeValue(val, { ...context, depth: context.depth + 1 }); } } return result; } // Handle functions if enabled if (typeof value === 'function' && this.options.preserveFunctions) { return this._serializeFunction(value, context); } // Fallback for unknown types return { __unknown__: this._safeStringify(value) }; } /** * Internal value deserialization */ _deserializeValue(value) { if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { return value; } if (Array.isArray(value)) { return value.map(item => this._deserializeValue(item)); } if (value && typeof value === 'object') { // Handle special markers if (value.__circular__) { return '[Circular Reference]'; } if (value.__unknown__) { return `[Unknown Type: ${value.__unknown__}]`; } // Handle typed values if (value.__type__ && this.deserializers.has(value.__type__)) { return this.deserializers.get(value.__type__)(value); } // Handle regular objects const result = {}; for (const [key, val] of Object.entries(value)) { // Handle symbol keys if (key.startsWith('__symbol_') && key.endsWith('__')) { const symbolKey = Symbol.for(key.slice(9, -2)); result[symbolKey] = this._deserializeValue(val); } else { result[key] = this._deserializeValue(val); } } return result; } return value; } /** * Type-specific serializers */ _serializeDate(date) { return { __type__: '__Date__', value: date.toISOString() }; } _serializeMap(map) { return { __type__: '__Map__', entries: Array.from(map.entries()).map(([k, v]) => [ this._serializeValue(k, { depth: 0, seen: new WeakSet() }), this._serializeValue(v, { depth: 0, seen: new WeakSet() }) ]) }; } _serializeSet(set) { return { __type__: '__Set__', values: Array.from(set).map(v => this._serializeValue(v, { depth: 0, seen: new WeakSet() })) }; } _serializeRegExp(regexp) { return { __type__: '__RegExp__', source: regexp.source, flags: regexp.flags }; } _serializeError(error) { return { __type__: '__Error__', name: error.name, message: error.message, stack: error.stack, cause: error.cause ? this._serializeValue(error.cause, { depth: 0, seen: new WeakSet() }) : undefined }; } _serializeBigInt(bigint) { return { __type__: '__BigInt__', value: bigint.toString() }; } _serializeArrayBuffer(buffer) { return { __type__: '__ArrayBuffer__', data: Array.from(new Uint8Array(buffer)) }; } _serializeTypedArray(array) { return { __type__: '__TypedArray__', constructor: array.constructor.name, data: Array.from(array) }; } _serializeFunction(func) { if (!this.options.preserveFunctions) { return { __type__: '__Function__', name: func.name || '[Anonymous]' }; } return { __type__: '__Function__', name: func.name, source: func.toString(), isAsync: func.constructor.name === 'AsyncFunction', isGenerator: func.constructor.name === 'GeneratorFunction' }; } _serializeSymbol(symbol) { return { __type__: '__Symbol__', description: symbol.description, key: Symbol.keyFor(symbol) }; } /** * Type-specific deserializers */ _deserializeDate(data) { return new Date(data.value); } _deserializeMap(data) { const map = new Map(); for (const [k, v] of data.entries) { map.set(this._deserializeValue(k), this._deserializeValue(v)); } return map; } _deserializeSet(data) { return new Set(data.values.map(v => this._deserializeValue(v))); } _deserializeRegExp(data) { return new RegExp(data.source, data.flags); } _deserializeError(data) { const error = new Error(data.message); error.name = data.name; error.stack = data.stack; if (data.cause !== undefined) { error.cause = this._deserializeValue(data.cause); } return error; } _deserializeBigInt(data) { return BigInt(data.value); } _deserializeArrayBuffer(data) { return new Uint8Array(data.data).buffer; } _deserializeTypedArray(data) { const Constructor = globalThis[data.constructor]; if (!Constructor) { throw new Error(`Unknown TypedArray constructor: ${data.constructor}`); } return new Constructor(data.data); } _deserializeFunction(data) { if (!this.options.preserveFunctions || !data.source) { return function restoredFunction() { throw new Error(`Function ${data.name} was not preserved during serialization`); }; } try { // This is potentially unsafe - use with caution in production if (data.isAsync) { return new Function(`return (${data.source})`)(); } else if (data.isGenerator) { return new Function(`return (${data.source})`)(); } else { return new Function(`return (${data.source})`)(); } } catch (error) { console.warn(`Failed to restore function ${data.name}:`, error.message); return function restoredFunction() { throw new Error(`Function ${data.name} could not be restored: ${error.message}`); }; } } _deserializeSymbol(data) { if (data.key) { return Symbol.for(data.key); } return Symbol(data.description); } /** * Utility methods */ _getObjectType(obj) { if (obj instanceof Date) return 'Date'; if (obj instanceof Map) return 'Map'; if (obj instanceof Set) return 'Set'; if (obj instanceof RegExp) return 'RegExp'; if (obj instanceof Error) return 'Error'; if (typeof obj === 'bigint') return 'BigInt'; if (obj instanceof ArrayBuffer) return 'ArrayBuffer'; if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) return 'TypedArray'; if (typeof obj === 'function') return 'Function'; if (typeof obj === 'symbol') return 'Symbol'; return 'Object'; } _safeStringify(value) { try { return JSON.stringify(value); } catch { return '[Non-serializable]'; } } _cleanupFallbackData(data) { // Basic cleanup for common serialization issues if (typeof data === 'object' && data !== null) { for (const [key, value] of Object.entries(data)) { if (typeof value === 'string') { // Try to parse common serialized values if (value === 'undefined') { data[key] = undefined; } else if (value === 'NaN') { data[key] = NaN; } else if (value === 'Infinity') { data[key] = Infinity; } else if (value === '-Infinity') { data[key] = -Infinity; } } else if (typeof value === 'object') { data[key] = this._cleanupFallbackData(value); } } } return data; } _compress(data) { // Placeholder for compression - implement actual compression in production return `__compressed__${Buffer.from(data).toString('base64')}`; } _decompress(data) { // Placeholder for decompression if (data.startsWith('__compressed__')) { return Buffer.from(data.substring(14), 'base64').toString(); } return data; } _isCompressed(data) { return typeof data === 'string' && data.startsWith('__compressed__'); } } /** * Custom error classes for better error handling */ export class SerializationError extends Error { constructor(message, details = {}) { super(message); this.name = 'SerializationError'; this.details = details; } } export class DeserializationError extends Error { constructor(message, details = {}) { super(message); this.name = 'DeserializationError'; this.details = details; } } export class SessionSerializationError extends SerializationError { constructor(message, details = {}) { super(message, details); this.name = 'SessionSerializationError'; } } export class SessionDeserializationError extends DeserializationError { constructor(message, details = {}) { super(message, details); this.name = 'SessionDeserializationError'; } } /** * Factory function for creating pre-configured serializers */ export function createSessionSerializer(options = {}) { return new AdvancedSerializer({ preserveUndefined: true, preserveFunctions: false, // Disabled for security preserveSymbols: false, enableCompression: true, maxDepth: 50, ...options }); } /** * Utility functions for common serialization tasks */ export function serializeSessionCheckpoint(checkpointData) { const serializer = createSessionSerializer(); return serializer.serializeSessionData(checkpointData); } export function deserializeSessionCheckpoint(serializedData) { const serializer = createSessionSerializer(); return serializer.deserializeSessionData(serializedData); } export function serializeSessionMetadata(metadata) { const serializer = createSessionSerializer({ maxDepth: 20 }); return serializer.serialize(metadata); } export function deserializeSessionMetadata(serializedMetadata) { const serializer = createSessionSerializer({ maxDepth: 20 }); return serializer.deserialize(serializedMetadata); } // Export default instance for convenience export default new AdvancedSerializer();