import { Rule } from './Rule' ; import { ISODateTimeString } from 'polar-shared/src/metadata/ISODateTimeStrings' ; import { ISODateTimeStrings } from 'polar-shared/src/metadata/ISODateTimeStrings' ; import { isPresent } from 'polar-shared/src/Preconditions' ; import { Reducers } from 'polar-shared/src/util/Reducers' ; export type EventHandler = ( ) => void ; export interface EventHandlers { readonly [ name ] : EventHandler ; } /**
 * An engine that should run with facts , against rules, which can emit events.
 *
 * The engine can be event based or poll based and is designed to be very
 * efficient to run just from a few variables.
 */ export class Engine < F , H extends EventHandlers > { private readonly engineState : MutableEngineState < F , H > ; private readonly eventMap : MutableEventMap < H > ; param facts param rules param eventHandlers param externalEngineState constructor ( private facts , private readonly rules , private readonly eventHandlers , private readonly externalEngineState ? ) { const defaultRuleStates = ( ) => { if ( externalEngineState ) { return externalEngineState . ruleStates ; } return { } ; } ; const defaultEventTimes = ( ) => { if ( externalEngineState ) { return externalEngineState . eventTimes ; } return { } ; } ; this . engineState = { ruleStates : defaultRuleStates ( ) , } ; this . eventMap = EventMaps . create ( this . eventHandlers , defaultEventTimes ( ) ) ; } public run ( ) { const { engineState , rules , eventMap } = this ; const ruleNames = Object . getOwnPropertyNames ( this . rules ) ; for ( const ruleName of ruleNames ) { const rule = rules [ ruleName ] ; const state = engineState . ruleStates [ ruleName ] ; const result = rule . run ( this . facts , eventMap , state ) ; engineState . ruleStates [ ruleName ] = result [ 1 ] ; this . facts = result [ 0 ] ; } } public toExternalEngineState ( ) { return { ... this . engineState , eventTimes : EventMaps . toEventTimes ( this . eventMap ) } ; } public getFacts ( ) { return this . facts ; } } export class RuleMap < F , H extends EventHandlers > { [ name ] : Rule < F , H , any > ; } export type RuleKeys < F , H extends EventHandlers > = keyof RuleMap < F , H > ; /**
 * Provide the order of the rules.  This is a bit verbose but Javascript object
 * keys order isn't defined reliably and I want to make sure we never have bugs
 * here.
 */ export type RuleOrder < F , H extends EventHandlers > = [ RuleKeys < F , H > ] ; interface MutableEngineState < F , H extends EventHandlers > { /**
     * The states of the individual rules.
     */ ruleStates : { [ id ] : any ; } ; } /**
 * The engine state between run which between facts F and handlers H.
 */ export interface ExternalEngineState < F , H extends EventHandlers > extends Readonly < MutableEngineState < F , H > > { /**
     *
     */ readonly eventTimes : Partial < EventTimes < H > > ; } export type MutableEventTimes < E extends EventHandlers > = { [ name in keyof E ] : ISODateTimeString | undefined ; } ; export type EventTimes < E extends EventHandlers > = Readonly < MutableEventTimes < E > > ; export interface MutableEvent { handler : EventHandler ; lastExecuted : ISODateTimeString | undefined ; } export interface Event extends Readonly < MutableEvent > { } export type MutableEventMap < E extends EventHandlers > = { [ name in keyof E ] : Event ; } ; export type EventMap < E extends EventHandlers > = Readonly < MutableEventMap < E > > ; export class EventMaps { public static create < E extends EventHandlers > ( handlers , eventTimes ) { const result = < any > { } ; for ( const handlerName of Object . keys ( handlers ) ) { const handler = handlers [ handlerName ] ; const lastExecuted = eventTimes [ handlerName ] ; const event = { handler : ( ) => { event . lastExecuted = ISODateTimeStrings . create ( ) ; handler ( ) ; } , lastExecuted } ; ( < any > result ) [ handlerName ] = event ; } return result ; } public static toEventTimes < E extends EventHandlers > ( eventMap ) { const result = < any > { } ; for ( const eventName of Object . keys ( eventMap ) ) { const event = eventMap [ eventName ] ; ( < any > result ) [ eventName ] = event . lastExecuted ; } return result ; } public static toLastExecutedTimes < E extends EventHandlers > ( eventMap ) { const eventTimes = EventMaps . toEventTimes ( eventMap ) ; return Object . values ( eventTimes ) . filter ( current => isPresent ( current ) ) . map ( current => current ! ) . sort ( ) ; } public static earliestExecution < E extends EventHandlers > ( eventMap ) { const times = [ ... this . toLastExecutedTimes ( eventMap ) ] ; return times . reduce ( Reducers . FIRST , undefined ) ; } public static latestExecution < E extends EventHandlers > ( eventMap ) { const times = [ ... this . toLastExecutedTimes ( eventMap ) ] ; const result = times . reduce ( Reducers . LAST , undefined ) ; return result ; } }