Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | 5x 5x 5x 31x 31x 31x 38x 38x 97x 49x 3x 46x 94x 2x 92x 38x 38x 38x 2x 36x 23x 23x 10x 13x 13x 3x 3x 10x 22x 22x 1x 21x 21x 21x 2x 3x 3x 7x 4x 34x | import { Injectable, Logger } from '@nestjs/common';
import { createHash } from 'crypto';
interface CacheEntry {
data: any;
etag: string;
timestamp: number;
}
@Injectable()
export class ETagCacheService {
private readonly logger = new Logger(ETagCacheService.name);
private cache = new Map<string, CacheEntry>();
// Default TTL: 30 seconds for most endpoints
private defaultTtl = 30 * 1000;
// Safe stringify that handles circular references
private safeStringify(data: any): string {
const seen = new WeakSet();
return JSON.stringify(data, (key, value) => {
// Skip objects that we've seen before (circular refs)
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
// Skip non-serializable types
if (typeof value === 'function' || typeof value === 'symbol') {
return undefined;
}
return value;
});
}
computeETag(data: any): string | null {
try {
const jsonStr = this.safeStringify(data);
if (!jsonStr) {
return null;
}
return createHash('md5').update(jsonStr).digest('hex');
} catch (err) {
this.logger.warn(`Failed to compute ETag: ${err.message}`);
return null;
}
}
get(key: string, ttl = this.defaultTtl): CacheEntry | null {
const entry = this.cache.get(key);
if (!entry) {
return null;
}
// Check if expired
const now = Date.now();
if (now - entry.timestamp > ttl) {
this.cache.delete(key);
return null;
}
return entry;
}
set(key: string, data: any): string | null {
const etag = this.computeETag(data);
if (!etag) {
// Skip caching if ETag can't be computed (e.g., circular refs)
return null;
}
const entry: CacheEntry = {
data,
etag,
timestamp: Date.now(),
};
this.cache.set(key, entry);
return etag;
}
invalidate(key: string): void {
this.cache.delete(key);
}
invalidatePattern(pattern: string): void {
// Invalidate all keys matching a pattern (e.g., 'sessions:*')
const regex = new RegExp(pattern);
for (const key of this.cache.keys()) {
if (regex.test(key)) {
this.cache.delete(key);
}
}
}
clear(): void {
this.cache.clear();
}
}
|