@jovian/type-tools
Version:
TypeTools is a Typescript library for providing extensible tooling runtime validations and type helpers.
146 lines (141 loc) • 7.1 kB
text/typescript
/* Jovian (c) 2020, License: MIT */
import { Observable, Subscription } from 'rxjs';
import { ConfigSource } from './ix.config.source';
import { Entity } from './ix.entity';
import { HalfLifed } from './ix.halflifed';
import { timerInterval, Timer } from './ix.timer';
export class ReconnEntityBehavior {
static defaultProfile: ReconnEntityBehavior = null;
resetThreshold?: number = 5;
defunctThreshold?: number = 3;
errorHalfLife?: number = 300;
defunctHalfLife?: number = 3600;
restoreCheckInterval?: number = 60;
constructor(init?: Partial<ReconnEntityBehavior>) { if (init) { Object.assign(this, init); } }
}
export class ReconnEntity extends Entity {
ixReconn = {
config: { resetThreshold: 5, defunctThreshold: 3, } as ReconnEntityBehavior,
configSource: null as ConfigSource<ReconnEntityBehavior>,
configSubs: null as Subscription,
resetInProgress: null as Promise<void>,
error: null as Error,
errorLast: 0,
errorHeat: new HalfLifed({ hl: 300 }),
defunctHeat: new HalfLifed({ hl: 3600 }),
defunctState: false,
defunctRestoreCheckedLast: 0,
defunctRestoreCheckInterval: 60,
defunctRestoreChecker: null as Timer,
setRestoreFunction: (attempt: (self: ReconnEntity) => (boolean | Promise<boolean>)) => {
this.ixReconn.actions.attemptToRestore = attempt;
},
setConfigSource: (src: ConfigSource<ReconnEntityBehavior>) => {
if (this.ixReconn.configSource === src) { return; }
if (this.ixReconn.configSubs) { this.ixReconn.configSubs.unsubscribe(); }
this.ixReconn.configSource = src;
// tslint:disable-next-line: deprecation
this.ixReconn.configSubs = src.change$.subscribe(confData => {
const conf = new ReconnEntityBehavior(confData);
this.ixReconn.config.resetThreshold = conf.resetThreshold;
this.ixReconn.config.defunctThreshold = conf.defunctThreshold;
this.ixReconn.errorHeat.hl = conf.errorHalfLife;
this.ixReconn.defunctHeat.hl = conf.defunctHalfLife;
this.ixReconn.defunctRestoreCheckInterval = conf.restoreCheckInterval;
this.ixRx<ReconnEntity>('reconn:config_change').next(this);
});
},
actions: {
reset: null as () => any,
attemptToRestore: null as (self: ReconnEntity) => (boolean | Promise<boolean>),
},
on: {
reset: null as () => any,
defunct: null as () => any,
restore: null as () => any,
},
event: {
self: this,
get errorHeatUp$() { return (this.self as ReconnEntity).ixRx<number>('reconn:error_heat_up').obs(); },
get defunctHeatUp$() { return (this.self as ReconnEntity).ixRx<number>('reconn:defunct_heat_up').obs(); },
get errorDuringDefunct$() { return (this.self as ReconnEntity).ixRx<Error>('reconn:error_during_defunct').obs(); },
get beforeReset$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:before_reset').obs(); },
get reset$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:reset').obs(); },
get beforeDefunct$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:before_defunct').obs(); },
get defunct$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:defunct').obs(); },
get beforeRestore$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:before_restore').obs(); },
get restore$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:restore').obs(); },
get configChange$() { return (this.self as ReconnEntity).ixRx<ReconnEntity>('reconn:config_change').obs(); },
}
};
constructor(entityType?: string, ixIdOverride?: string) {
super(entityType, ixIdOverride);
if (!ReconnEntityBehavior.defaultProfile) { ReconnEntityBehavior.defaultProfile = new ReconnEntityBehavior(); }
const dfProfile = ReconnEntityBehavior.defaultProfile;
this.ixReconn.config.resetThreshold = dfProfile.resetThreshold;
this.ixReconn.config.defunctThreshold = dfProfile.defunctThreshold;
this.ixReconn.errorHeat.hl = dfProfile.errorHalfLife;
this.ixReconn.defunctHeat.hl = dfProfile.defunctHalfLife;
this.ixReconn.defunctRestoreCheckInterval = dfProfile.restoreCheckInterval;
this.ixReconn.errorHeat.afterUpdate.push(async (heat, v) => {
if (this.ixReconn.defunctState) {
this.ixRx<Error>('reconn:error_during_defunct').next(this.ixReconn.error);
return;
}
if (this.ixReconn.errorHeat.value > this.ixReconn.config.resetThreshold) {
this.ixReconn.errorHeat.reset();
this.ixReconn.defunctHeat.add(1);
this.ixRx<number>('reconn:defunct_heat_up').next(this.ixReconn.defunctHeat.value);
if (this.ixReconn.defunctHeat.value < this.ixReconn.config.defunctThreshold) {
this.ixRx<ReconnEntity>('reconn:before_reset').next(this);
let resolver = null;
this.ixReconn.resetInProgress = new Promise<void>(resolve => resolver = resolve );
try { await Promise.resolve(this.ixReconn.actions.reset?.()); } catch (e) { this.ixError(e); }
this.ixReconn.resetInProgress = null;
resolver?.();
this.ixReconn.on.reset?.();
this.ixRx<ReconnEntity>('reconn:reset').next(this);
} else {
this.ixReconn.defunctHeat.reset();
this.ixReconn.defunctState = true;
this.ixReconn.defunctRestoreCheckedLast = Date.now();
this.ixRx<ReconnEntity>('reconn:before_defunct').next(this);
this.ixReconn.on.defunct?.();
this.ixRx<ReconnEntity>('reconn:defunct').next(this);
}
}
});
this.ixReconn.defunctRestoreChecker = timerInterval(1000, async () => {
if (!this.ixReconn.defunctState) { return; }
if (Date.now() - this.ixReconn.defunctRestoreCheckedLast > this.ixReconn.defunctRestoreCheckInterval * 1000) {
this.ixReconn.defunctRestoreCheckedLast = Date.now();
if (this.ixReconn.actions.attemptToRestore) {
const res = await Promise.resolve(this.ixReconn.actions.attemptToRestore(this));
if (res) {
this.ixReconn.defunctState = false;
this.ixRx<ReconnEntity>('reconn:before_restore').next(this);
this.ixReconn.errorHeat.reset();
this.ixReconn.defunctHeat.reset();
this.ixReconn.on.restore?.();
this.ixRx<ReconnEntity>('reconn:restore').next(this);
}
}
}
});
// tslint:disable-next-line: deprecation
const eSub = this.error$.subscribe(e => {
const severity = e.severity ? e.severity : 1;
this.ixReconn.errorLast = Date.now();
this.ixReconn.error = e;
this.ixReconn.errorHeat.add(severity);
this.ixRx<number>('reconn:error_heat_up').next(this.ixReconn.errorHeat.value);
});
this.ixReconn.errorHeat.lcManagedBy(this);
this.ixReconn.defunctHeat.lcManagedBy(this);
this.ixReconn.defunctRestoreChecker.lcManagedBy(this);
this.addOnDestroy(() => {
eSub.unsubscribe();
if (this.ixReconn.configSubs) { this.ixReconn.configSubs.unsubscribe(); }
});
}
}