import { FactRecord , FactReference , PredecessorCollection } from '../storage' ; import { toJSON } from '../util/obj' ; import { computeHash } from './hash' ; export type HashMap = { [ key ] : any ; } ; type DehydrationEntry = { fact : HashMap ; record : FactRecord ; reference : FactReference ; } ; export class Dehydration { private entries : DehydrationEntry [ ] = [ ] ; factRecords ( ) { return this . entries . map ( entry => entry . record ) ; } dehydrate ( fact ) { const entry = this . entries . find ( entry => { return entry . fact === fact ; } ) ; if ( entry ) { return entry . reference ; } const record = this . createFactRecord ( fact ) ; const reference = { type : record . type , hash : record . hash } ; if ( ! this . entries . find ( entry => { return entry . reference . hash === reference . hash && entry . reference . type === reference . type ; } ) ) { this . entries . push ( { fact , record , reference } ) ; } return reference ; } private createFactRecord ( fact ) { let type = null ; let fields = { } ; let predecessors = { } ; for ( let field in fact ) { const value = toJSON ( fact [ field ] ) ; if ( value === null || value === undefined ) { } else if ( field === 'type' && typeof ( value ) === 'string' ) { type = value ; } else if ( typeof ( value ) === 'object' ) { if ( Array . isArray ( value ) ) { predecessors [ field ] = value . filter ( element => element ) . map ( element => this . dehydrate ( element ) ) ; } else { predecessors [ field ] = this . dehydrate ( value ) ; } } else { fields [ field ] = value ; } } const hash = computeHash ( fields , predecessors ) ; if ( ! type ) { throw new Error ( 'Specify the type of the fact and all of its predecessors.' ) ; } return { type , hash , predecessors , fields } ; } } type HydrationEntry = { record : FactRecord ; fact : HashMap ; } ; const hashSymbol = typeof ( Symbol ) === "undefined" ? null : Symbol ( "hash" ) ; export class Hydration { private entries : HydrationEntry [ ] ; constructor ( records ) { this . entries = records . map ( r => { return { record : r , fact : null } ; } ) ; } hydrate ( reference ) { const entry = this . entries . find ( r => r . record . hash === reference . hash && r . record . type === reference . type ) ; if ( ! entry ) { throw new Error ( 'Referenced fact not found in tree' ) ; } if ( entry . fact ) { return entry . fact ; } const fields = entry . record . fields ; let fact = { } ; for ( const field in fields ) { fact [ field ] = fields [ field ] ; } fact . type = entry . record . type ; for ( const role in entry . record . predecessors ) { const value = entry . record . predecessors [ role ] ; fact [ role ] = this . hydratePredecessors ( value ) ; } entry . fact = fact ; if ( hashSymbol ) { ( fact as any ) [ hashSymbol ] = reference . hash ; } return fact ; } private hydratePredecessors ( references ) { if ( Array . isArray ( references ) ) { return references . map ( p => this . hydrate ( p ) ) ; } else { return this . hydrate ( references ) ; } } } export function lookupHash < T extends Object > ( fact ) { return hashSymbol && ( fact as any ) [ hashSymbol ] as string ; } export function hydrate < T > ( record ) { const fact = record . fields ; fact . type = record . type ; return < T > fact ; } export function hydrateFromTree < T > ( references , records ) { const hydration = new Hydration ( records ) ; return references . map ( r => < T > hydration . hydrate ( r ) ) ; } export function dehydrateFact ( fact ) { const dehydration = new Dehydration ( ) ; dehydration . dehydrate ( fact ) ; return dehydration . factRecords ( ) ; } export function dehydrateReference ( fact ) { const dehydration = new Dehydration ( ) ; return dehydration . dehydrate ( fact ) ; }