import { Authentication } from './authentication/authentication' ; import { dehydrateReference , Dehydration , HashMap , hydrate , hydrateFromTree , lookupHash } from './fact/hydrate' ; import { runService } from './feed/service' ; import { SyncStatus , SyncStatusNotifier } from './http/web-client' ; import { MemoryStore } from './memory/memory-store' ; import { Query } from './query/query' ; import { Condition , ensure , FactDescription , Preposition , Specification } from './query/query-parser' ; import { FactEnvelope , FactPath , uniqueFactReferences } from './storage' ; import { toJSON } from './util/obj' ; import { ServiceRunner } from './util/serviceRunner' ; import { Trace , Tracer } from './util/trace' ; import { Watch } from './watch/watch' ; import { WatchImpl } from './watch/watch-impl' ; import { WatchNoOp } from './watch/watch-noop' ; export interface Profile { displayName : string ; } export { Trace , Tracer , Preposition , FactDescription , ensure } ; export class Jinaga { private errorHandlers : ( ( message : string ) => void ) [ ] = [ ] ; private loadingHandlers : ( ( loading : boolean ) => void ) [ ] = [ ] ; private progressHandlers : ( ( count : number ) => void ) [ ] = [ ] ; private serviceRunner = new ServiceRunner ( exception => this . error ( exception ) ) ; constructor ( private authentication , private store , private syncStatusNotifier ) { } param handler onError ( handler ) { this . errorHandlers . push ( handler ) ; } param handler onLoading ( handler ) { this . loadingHandlers . push ( handler ) ; } param handler onProgress ( handler ) { this . progressHandlers . push ( handler ) ; } onSyncStatus ( handler ) { this . syncStatusNotifier . onSyncStatus ( handler ) ; } returns async login < U > ( ) { const { userFact , profile } = await this . authentication . login ( ) ; return { userFact : hydrate < U > ( userFact ) , profile } ; } returns async local < D > ( ) { const deviceFact = await this . authentication . local ( ) ; return hydrate < D > ( deviceFact ) ; } param prototype returns async fact < T > ( prototype ) { if ( ! prototype ) { return null ; } try { this . validateFact ( prototype ) ; const dehydration = new Dehydration ( ) ; const reference = dehydration . dehydrate ( prototype ) ; const factRecords = dehydration . factRecords ( ) ; const hydrated = hydrateFromTree ( [ reference ] , factRecords ) [ 0 ] ; const envelopes = factRecords . map ( fact => { return < FactEnvelope > { fact : fact , signatures : [ ] } ; } ) ; const saved = await this . authentication . save ( envelopes ) ; return hydrated as T ; } catch ( error ) { this . error ( error ) ; throw error ; } } param start param preposition returns async query < T , U > ( start , preposition ) { if ( ! start ) { return [ ] ; } const fact = JSON . parse ( JSON . stringify ( start ) ) ; this . validateFact ( fact ) ; const reference = dehydrateReference ( fact ) ; const query = new Query ( preposition . steps ) ; const results = await this . authentication . query ( reference , query ) ; if ( results . length === 0 ) { return [ ] ; } const references = results . map ( r => r [ r . length - 1 ] ) ; const uniqueReferences = uniqueFactReferences ( references ) ; const facts = await this . authentication . load ( uniqueReferences ) ; return hydrateFromTree ( uniqueReferences , facts ) ; } param start param preposition param resultAdded param resultRemoved returns watch < T , U , V > ( start , preposition , resultAdded , resultRemoved ) ; param start param preposition param resultAdded returns watch < T , U , V > ( start , preposition , resultAdded ) ; watch < T , U , V > ( start , preposition , resultAdded , resultRemoved ? ) { if ( ! start ) { return new WatchNoOp < U , V > ( ) ; } const fact = JSON . parse ( JSON . stringify ( start ) ) ; this . validateFact ( fact ) ; const reference = dehydrateReference ( fact ) ; const query = new Query ( preposition . steps ) ; const onResultAdded = ( path , fact , take ) => { const model = resultAdded ( fact ) ; take ( resultRemoved ? < V > model : null ) ; } ; const watch = new WatchImpl < U , V > ( reference , query , onResultAdded , resultRemoved , this . authentication ) ; watch . begin ( ) ; return watch ; } service < T , U > ( start , preposition , handler ) { if ( ! start ) { return ; } const fact = JSON . parse ( JSON . stringify ( start ) ) ; this . validateFact ( fact ) ; const reference = dehydrateReference ( fact ) ; const query = new Query ( preposition . steps ) ; const feed = this . authentication ; const serviceRunner = this . serviceRunner ; runService < U > ( feed , reference , query , serviceRunner , handler ) ; } async stop ( ) { await this . serviceRunner . all ( ) ; } param specification returns static for < T , U > ( specification ) { return Preposition . for ( specification ) ; } param specification returns for < T , U > ( specification ) { return Jinaga . for ( specification ) ; } param template returns static match < T > ( template ) { return new Specification < T > ( template , [ ] ) ; } param template returns match < T > ( template ) { return Jinaga . match ( template ) ; } param template returns static exists < T > ( template ) { return new Condition < T > ( template , [ ] , false ) ; } param template returns exists < T > ( template ) { return Jinaga . exists ( template ) ; } param template returns static notExists < T > ( template ) { return new Condition < T > ( template , [ ] , true ) ; } param template returns notExists < T > ( template ) { return Jinaga . notExists ( template ) ; } param condition returns static not < T , U > ( condition ) { return target => { const original = condition ( target ) ; return new Condition < U > ( original . template , original . conditions , ! original . negative ) ; } ; } param condition returns not < T , U > ( condition ) { return Jinaga . not ( condition ) ; } static hash < T > ( fact ) { const hash = lookupHash ( fact ) ; if ( hash ) { return hash ; } const reference = dehydrateReference ( fact ) ; return reference . hash ; } hash < T > ( fact ) { return Jinaga . hash ( fact ) ; } returns graphviz ( ) { return this . store . graphviz ( ) . join ( '\n' ) ; } returns inspect ( ) { return this . store . inspect ( ) ; } private validateFact ( prototype ) { if ( ! prototype ) { throw new Error ( 'A fact or any of its predecessors cannot be null.' ) ; } if ( ! ( 'type' in prototype ) ) { throw new Error ( 'Specify the type of the fact and all of its predecessors.' ) ; } for ( const field in prototype ) { const value = toJSON ( prototype [ field ] ) ; if ( typeof ( value ) === 'object' ) { if ( Array . isArray ( value ) ) { value . filter ( element => element ) . forEach ( element => this . validateFact ( element ) ) ; } else { this . validateFact ( value ) ; } } else if ( typeof ( value ) === 'function' ) { throw new Error ( `A fact may not have any methods: ${ field } in ${ prototype . type } is a function.` ) ; } } } private error ( error ) { Trace . error ( error ) ; this . errorHandlers . forEach ( ( errorHandler ) => { errorHandler ( error ) ; } ) ; } }