-
-
Notifications
You must be signed in to change notification settings - Fork 143
Expand file tree
/
Copy pathdocumentation-cache.ts
More file actions
152 lines (130 loc) · 4.98 KB
/
documentation-cache.ts
File metadata and controls
152 lines (130 loc) · 4.98 KB
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import * as vscode from 'vscode';
import { createHash } from 'crypto';
// Cache entry interface
interface CacheEntry {
data: string;
timestamp: number;
extensionVersion: string;
}
/**
* DocumentationCache class handles persistent caching of ZModel documentation
* using VS Code's globalState for cross-session persistence
*/
export class DocumentationCache implements vscode.Disposable {
private static readonly CACHE_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days cache duration
private static readonly CACHE_PREFIX = 'doc-cache.';
private extensionContext: vscode.ExtensionContext;
private extensionVersion: string;
constructor(context: vscode.ExtensionContext) {
this.extensionContext = context;
this.extensionVersion = context.extension.packageJSON.version as string;
// clear expired cache entries on initialization
this.clearExpiredCache();
}
/**
* Dispose of the cache resources (implements vscode.Disposable)
*/
dispose(): void {}
/**
* Get the cache prefix used for keys
*/
getCachePrefix(): string {
return DocumentationCache.CACHE_PREFIX;
}
/**
* Enable cache synchronization across machines via VS Code Settings Sync
*/
private enableCacheSync(): void {
const cacheKeys = this.extensionContext.globalState
.keys()
.filter((key) => key.startsWith(DocumentationCache.CACHE_PREFIX));
if (cacheKeys.length > 0) {
this.extensionContext.globalState.setKeysForSync(cacheKeys);
}
}
/**
* Generate a cache key from request body with normalized content
*/
private generateCacheKey(models: string[]): string {
// Remove ALL whitespace characters from each model string for cache key generation
// This ensures identical content with different formatting uses the same cache
const normalizedModels = models.map((model) => model.replace(/\s/g, '')).sort();
const hash = createHash('sha512')
.update(JSON.stringify({ models: normalizedModels }))
.digest('hex');
return `${DocumentationCache.CACHE_PREFIX}${hash}`;
}
/**
* Check if cache entry is still valid (not expired)
*/
private isCacheValid(entry: CacheEntry): boolean {
return Date.now() - entry.timestamp < DocumentationCache.CACHE_DURATION_MS;
}
/**
* Get cached response if available and valid
*/
async getCachedResponse(models: string[]): Promise<string | null> {
const cacheKey = this.generateCacheKey(models);
const entry = this.extensionContext.globalState.get<CacheEntry>(cacheKey);
if (entry && this.isCacheValid(entry)) {
console.log('Using cached documentation response from persistent storage');
return entry.data;
}
// Clean up expired entry if it exists
if (entry) {
await this.extensionContext.globalState.update(cacheKey, undefined);
}
return null;
}
/**
* Cache a response for future use
*/
async setCachedResponse(models: string[], data: string): Promise<void> {
const cacheKey = this.generateCacheKey(models);
const cacheEntry: CacheEntry = {
data,
timestamp: Date.now(),
extensionVersion: this.extensionVersion,
};
await this.extensionContext.globalState.update(cacheKey, cacheEntry);
// Update sync keys to include new cache entry
this.enableCacheSync();
}
/**
* Clear expired cache entries from persistent storage
*/
async clearExpiredCache(): Promise<void> {
const now = Date.now();
let clearedCount = 0;
const allKeys = this.extensionContext.globalState.keys();
for (const key of allKeys) {
if (key.startsWith(DocumentationCache.CACHE_PREFIX)) {
const entry = this.extensionContext.globalState.get<CacheEntry>(key);
if (
entry?.extensionVersion !== this.extensionVersion ||
now - entry.timestamp >= DocumentationCache.CACHE_DURATION_MS
) {
await this.extensionContext.globalState.update(key, undefined);
clearedCount++;
}
}
}
if (clearedCount > 0) {
console.log(`Cleared ${clearedCount} expired cache entries from persistent storage`);
}
}
/**
* Clear all cache entries from persistent storage
*/
async clearAllCache(): Promise<void> {
const allKeys = this.extensionContext.globalState.keys();
let clearedCount = 0;
for (const key of allKeys) {
if (key.startsWith(DocumentationCache.CACHE_PREFIX)) {
await this.extensionContext.globalState.update(key, undefined);
clearedCount++;
}
}
console.log(`Cleared all cache entries from persistent storage (${clearedCount} items)`);
}
}