@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
259 lines • 8.21 kB
JavaScript
/**
* @module timescape/lifecycle
* @description Version lifecycle management with auto-deactivation
*/
export class VersionLifecycleManager {
registry;
config;
intervalId;
deactivationHistory = [];
manualOverrides = new Map();
constructor(registry, config) {
this.registry = registry;
this.config = {
enabled: true,
checkIntervalMs: 60 * 60 * 1000, // 1 hour
coldThresholdMs: 7 * 24 * 60 * 60 * 1000, // 7 days
minRequestCount: 10,
protectedTags: ['stable', 'production', 'latest'],
excludedHandlers: [],
dryRun: false,
...config,
};
}
/**
* Start lifecycle monitoring
*/
start() {
if (!this.config.enabled) {
return;
}
if (this.intervalId) {
throw new Error('Lifecycle manager already started');
}
// Initial check
this.checkVersions();
// Periodic checks
this.intervalId = setInterval(() => {
this.checkVersions();
}, this.config.checkIntervalMs);
}
/**
* Stop lifecycle monitoring
*/
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
}
/**
* Check all versions and deactivate cold ones
*/
checkVersions() {
const now = Date.now();
const state = this.registry.getAll();
for (const [handlerPath, timeline] of Object.entries(state.handlers)) {
// Skip excluded handlers
if (this.config.excludedHandlers.includes(handlerPath)) {
continue;
}
for (const version of timeline.versions) {
// Check manual override
const override = this.manualOverrides.get(version.tsv);
if (override === 'keep') {
continue;
}
if (override === 'deactivate') {
this.deactivateVersion(version.tsv, handlerPath, 'manual');
continue;
}
// Check if version is protected by tag
if (this.isProtected(version.tsv)) {
continue;
}
// Check if version should be deactivated
const timeSinceLastAccess = now - version.lastAccessed;
if (timeSinceLastAccess > this.config.coldThresholdMs) {
// Cold due to inactivity
this.deactivateVersion(version.tsv, handlerPath, 'cold');
}
else if (version.requestCount < this.config.minRequestCount) {
// Low usage
this.deactivateVersion(version.tsv, handlerPath, 'low_usage');
}
}
}
}
/**
* Deactivate a version
*/
deactivateVersion(tsv, handlerPath, reason) {
const info = this.registry.getVersionInfo(tsv);
if (!info) {
return;
}
// Already cold
if (info.status === 'cold') {
return;
}
const deactivationReason = {
tsv,
handlerPath,
reason,
lastAccessed: info.lastAccessed,
requestCount: info.requestCount,
deactivatedAt: Date.now(),
};
if (this.config.dryRun) {
console.log('[Lifecycle] [DRY RUN] Would deactivate version:', deactivationReason);
}
else {
this.registry.markCold(tsv);
this.deactivationHistory.push(deactivationReason);
if (this.config.onDeactivate) {
this.config.onDeactivate(tsv, handlerPath, reason);
}
console.log('[Lifecycle] Deactivated version:', deactivationReason);
}
}
/**
* Check if version is protected from deactivation
*/
isProtected(tsv) {
const tags = this.registry.getTagsForVersion(tsv);
return tags.some(tag => this.config.protectedTags.includes(tag));
}
/**
* Manually override version lifecycle
*/
setManualOverride(tsv, action) {
this.manualOverrides.set(tsv, action);
}
/**
* Remove manual override
*/
removeManualOverride(tsv) {
this.manualOverrides.delete(tsv);
}
/**
* Get manual overrides
*/
getManualOverrides() {
return new Map(this.manualOverrides);
}
/**
* Get deactivation history
*/
getDeactivationHistory(limit) {
const history = [...this.deactivationHistory].reverse();
return limit ? history.slice(0, limit) : history;
}
/**
* Clear deactivation history
*/
clearDeactivationHistory() {
this.deactivationHistory = [];
}
/**
* Update configuration
*/
updateConfig(config) {
const wasEnabled = this.config.enabled;
this.config = { ...this.config, ...config };
// Restart if enabled state changed
if (wasEnabled !== this.config.enabled) {
if (this.config.enabled) {
this.start();
}
else {
this.stop();
}
}
}
/**
* Get current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Get lifecycle statistics
*/
getStatistics() {
return {
totalDeactivations: this.deactivationHistory.length,
deactivationsByCold: this.deactivationHistory.filter(d => d.reason === 'cold').length,
deactivationsByLowUsage: this.deactivationHistory.filter(d => d.reason === 'low_usage').length,
deactivationsByManual: this.deactivationHistory.filter(d => d.reason === 'manual').length,
activeOverrides: this.manualOverrides.size,
isRunning: this.intervalId !== undefined,
};
}
/**
* Force check now (useful for testing)
*/
checkNow() {
this.checkVersions();
}
/**
* Reactivate a cold version
*/
reactivateVersion(tsv) {
const info = this.registry.getVersionInfo(tsv);
if (!info) {
return false;
}
if (info.status !== 'cold') {
return false;
}
this.registry.updateVersionStatus(tsv, 'warm');
this.removeManualOverride(tsv);
return true;
}
/**
* Get versions eligible for deactivation
*/
getEligibleForDeactivation() {
const now = Date.now();
const state = this.registry.getAll();
const eligible = [];
for (const [handlerPath, timeline] of Object.entries(state.handlers)) {
if (this.config.excludedHandlers.includes(handlerPath)) {
continue;
}
for (const version of timeline.versions) {
if (version.status === 'cold') {
continue;
}
if (this.manualOverrides.get(version.tsv) === 'keep') {
continue;
}
if (this.isProtected(version.tsv)) {
continue;
}
const timeSinceLastAccess = now - version.lastAccessed;
if (timeSinceLastAccess > this.config.coldThresholdMs) {
eligible.push({
tsv: version.tsv,
handlerPath,
reason: 'cold',
timeSinceLastAccess,
requestCount: version.requestCount,
});
}
else if (version.requestCount < this.config.minRequestCount) {
eligible.push({
tsv: version.tsv,
handlerPath,
reason: 'low_usage',
timeSinceLastAccess,
requestCount: version.requestCount,
});
}
}
}
return eligible;
}
}
//# sourceMappingURL=lifecycle.js.map