Files
smartupdate/ts/smartupdate.classes.cachemanager.ts
T

152 lines
4.1 KiB
TypeScript

import * as plugins from './smartupdate.plugins.js';
import type { ICacheStatus, ICacheOptions, TCacheStrategy, TCachedUpdateStatus } from './smartupdate.interfaces.js';
type TCacheStoreData = Record<string, ICacheStatus>;
/**
* Manages caching of update check results
*/
export class UpdateCacheManager {
public readonly kvStore: plugins.npmextra.KeyValueStore<TCacheStoreData>;
private cacheDurationMs: number;
constructor(options: ICacheOptions) {
this.cacheDurationMs = options.durationMs;
this.kvStore = new plugins.npmextra.KeyValueStore<TCacheStoreData>({
typeArg: options.storeType || 'userHomeDir',
identityArg: options.storeIdentifier || 'global_smartupdate',
customPath: options.customPath,
});
}
/**
* Get the cache duration in milliseconds
*/
public getCacheDuration(): number {
return this.cacheDurationMs;
}
/**
* Get cached status for a package
*/
public async getCached(packageName: string): Promise<ICacheStatus | null> {
return (await this.kvStore.readKey(packageName)) || null;
}
/**
* Set cache status for a package
*/
public async setCached(packageName: string, status: ICacheStatus): Promise<void> {
await this.kvStore.writeKey(packageName, status);
}
/**
* Clear cache for a specific package or all packages
*/
public async clearCache(packageName?: string): Promise<void> {
if (packageName) {
const cacheData = await this.kvStore.readAll();
delete cacheData[packageName];
await this.kvStore.wipe();
await this.kvStore.writeAll(cacheData);
} else {
await this.kvStore.wipe();
}
}
/**
* Check if we should check the registry or use cache
* @returns Object with shouldCheck flag and optional nextCheckTime
*/
public async shouldCheckRegistry(
packageName: string,
strategy: TCacheStrategy = 'time-based',
cacheContext: {
currentVersion?: string;
registryUrl?: string;
} = {}
): Promise<{
shouldCheck: boolean;
cacheStatus?: ICacheStatus;
nextCheckTime?: Date;
minutesUntilNextCheck?: number;
}> {
// Never use cache
if (strategy === 'never') {
return { shouldCheck: true };
}
// Get cached data
const cacheStatus = await this.getCached(packageName);
// No cache exists
if (!cacheStatus || !this.cacheMatchesContext(cacheStatus, cacheContext)) {
return { shouldCheck: true };
}
// Always use cache if available
if (strategy === 'always') {
return { shouldCheck: false, cacheStatus };
}
// Time-based strategy: check if cache is still valid
const now = Date.now();
const lastCheckTime = cacheStatus.lastCheck;
const timeSinceLastCheck = now - lastCheckTime;
// Cache is still valid
if (timeSinceLastCheck < this.cacheDurationMs) {
const nextCheckTime = new Date(lastCheckTime + this.cacheDurationMs);
const minutesUntilNextCheck = (this.cacheDurationMs - timeSinceLastCheck) / 60000;
return {
shouldCheck: false,
cacheStatus,
nextCheckTime,
minutesUntilNextCheck,
};
}
// Cache is expired
return { shouldCheck: true, cacheStatus };
}
/**
* Create a new cache status object
*/
public createCacheStatus(
latestVersion: string,
performedUpgrade: boolean = false,
metadata: {
currentVersion?: string;
registryUrl?: string;
status?: TCachedUpdateStatus;
} = {}
): ICacheStatus {
return {
lastCheck: Date.now(),
latestVersion,
currentVersion: metadata.currentVersion,
registryUrl: metadata.registryUrl,
status: metadata.status,
performedUpgrade,
};
}
private cacheMatchesContext(
cacheStatus: ICacheStatus,
cacheContext: {
currentVersion?: string;
registryUrl?: string;
}
): boolean {
if (cacheContext.currentVersion && cacheStatus.currentVersion !== cacheContext.currentVersion) {
return false;
}
if (cacheContext.registryUrl && cacheStatus.registryUrl !== cacheContext.registryUrl) {
return false;
}
return true;
}
}