import * as CF from '@efforting.tech/schema/field-configuration-factories'; export const Object_Serialization_Strategy = new CF.Schema({ identity: CF.boolean(true, 'Serialize based on identity'), type: CF.boolean(true, 'Serialize based on constructor'), symbols: CF.boolean(true, 'Include symbol-keyed properties'), keys: CF.boolean(true, 'Include property keys'), values: CF.boolean(true, 'Include property values'), }, 'Object serialization strategy'); export const State_Serialization_Strategy = new CF.Schema({ //BUG - there is currently no way (I think) to put defaults into a sub schema - this should be fixed array: CF.schema(Object_Serialization_Strategy, { identity: false, type: true, symbols: false, keys: false, values: true, }, 'State serialization strategy for Array'), object: CF.schema(Object_Serialization_Strategy, { identity: true, type: true, symbols: true, keys: true, values: true, }, 'State serialization strategy for Object'), }, 'State serialization strategy'); export class State_Serializer { static settings_schema = State_Serialization_Strategy; constructor(symbols=new Map(), strategy) { strategy = this.constructor.settings_schema.load(strategy); Object.assign(this, { symbols, strategy }); } serialize_symbols(...symbols) { const result = []; for (const symbol of symbols) { const existing = this.symbols.get(symbol); if (existing !== undefined) { result.push(existing); } else { const new_symbol_index = this.symbols.size; this.symbols.set(symbol, new_symbol_index); result.push(new_symbol_index); } } return result; } serialize_object(object) { const state_strategy = this.strategy; const result = []; switch (typeof(object)) { case 'object': if (object === null) { return ['N']; } const is_array = object.constructor === Array; const strategy = is_array ? state_strategy.array : state_strategy.object; result.push(is_array ? 'a' : 'o'); if (strategy.identity) { const existing_instance = this.symbols.get(object); if (existing_instance !== undefined) { return existing_instance; } const [instance] = this.serialize_symbols(object); result.push(instance); } if (strategy.type) { const [type] = this.serialize_symbols(object.constructor); result.push(type); } if (strategy.symbols) { const symbols = this.serialize_symbols(...Object.getOwnPropertySymbols(object)); result.push(symbols); } if (strategy.keys) { const keys = Object.keys(object).map(item => this.serialize_object(item)); result.push(keys); } if (strategy.values) { const values = Object.values(object).map(item => this.serialize_object(item)); result.push(values); } return result; case 'string': return ['s', ...this.serialize_symbols(typeof(object)), object]; case 'number': return ['n', ...this.serialize_symbols(typeof(object)), object]; case 'boolean': return [object ? 'T' : 'F']; case 'function': const [reference_type, reference_id] = this.serialize_symbols(typeof(object), object); return ['f', reference_type, reference_id]; case 'symbol': return ['S', ...this.serialize_symbols(object)]; case 'undefined': return ['u']; default: throw new Error(typeof(object)); //TODO - proper error } } };