Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🧪 QA (push) Failing after 1m31s
Build & Deploy / 🏗️ Build (push) Failing after 3m51s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
102 lines
2.7 KiB
TypeScript
102 lines
2.7 KiB
TypeScript
import type { CacheAdapter, CacheConfig } from "./interfaces";
|
|
import * as fs from "node:fs/promises";
|
|
import * as path from "node:path";
|
|
import { existsSync } from "node:fs";
|
|
import * as crypto from "node:crypto";
|
|
|
|
|
|
|
|
export class FileCacheAdapter implements CacheAdapter {
|
|
private cacheDir: string;
|
|
private prefix: string;
|
|
private defaultTTL: number;
|
|
|
|
constructor(config?: CacheConfig & { cacheDir?: string }) {
|
|
this.cacheDir = config?.cacheDir || path.resolve(process.cwd(), ".cache");
|
|
this.prefix = config?.prefix || "";
|
|
this.defaultTTL = config?.defaultTTL || 3600;
|
|
|
|
if (!existsSync(this.cacheDir)) {
|
|
fs.mkdir(this.cacheDir, { recursive: true }).catch((err) => {
|
|
console.error(
|
|
`Failed to create cache directory: ${this.cacheDir}`,
|
|
err,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
private sanitize(key: string): string {
|
|
const clean = key.replace(/[^a-z0-9]/gi, "_");
|
|
if (clean.length > 64) {
|
|
return crypto.createHash("md5").update(key).digest("hex");
|
|
}
|
|
return clean;
|
|
}
|
|
|
|
private getFilePath(key: string): string {
|
|
const safeKey = this.sanitize(`${this.prefix}${key}`).toLowerCase();
|
|
return path.join(this.cacheDir, `${safeKey}.json`);
|
|
}
|
|
|
|
async get<T>(key: string): Promise<T | null> {
|
|
const filePath = this.getFilePath(key);
|
|
try {
|
|
if (!existsSync(filePath)) return null;
|
|
const content = await fs.readFile(filePath, "utf8");
|
|
const data = JSON.parse(content);
|
|
|
|
if (data.expiry && Date.now() > data.expiry) {
|
|
await this.del(key);
|
|
return null;
|
|
}
|
|
|
|
return data.value;
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
|
const filePath = this.getFilePath(key);
|
|
const effectiveTTL = ttl !== undefined ? ttl : this.defaultTTL;
|
|
const data = {
|
|
value,
|
|
expiry: effectiveTTL > 0 ? Date.now() + effectiveTTL * 1000 : null,
|
|
updatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
try {
|
|
await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
} catch (error) {
|
|
console.error(`Failed to write cache file: ${filePath}`, error);
|
|
}
|
|
}
|
|
|
|
async del(key: string): Promise<void> {
|
|
const filePath = this.getFilePath(key);
|
|
try {
|
|
if (existsSync(filePath)) {
|
|
await fs.unlink(filePath);
|
|
}
|
|
} catch (_error) {
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
try {
|
|
if (existsSync(this.cacheDir)) {
|
|
const files = await fs.readdir(this.cacheDir);
|
|
for (const file of files) {
|
|
if (file.endsWith(".json")) {
|
|
await fs.unlink(path.join(this.cacheDir, file));
|
|
}
|
|
}
|
|
}
|
|
} catch (_error) {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|