orcommit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -0
  3. package/dist/cli.d.ts +70 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +391 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.d.ts +18 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +21 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/modules/api.d.ts +48 -0
  12. package/dist/modules/api.d.ts.map +1 -0
  13. package/dist/modules/api.js +286 -0
  14. package/dist/modules/api.js.map +1 -0
  15. package/dist/modules/cache.d.ts +74 -0
  16. package/dist/modules/cache.d.ts.map +1 -0
  17. package/dist/modules/cache.js +284 -0
  18. package/dist/modules/cache.js.map +1 -0
  19. package/dist/modules/config.d.ts +53 -0
  20. package/dist/modules/config.d.ts.map +1 -0
  21. package/dist/modules/config.js +180 -0
  22. package/dist/modules/config.js.map +1 -0
  23. package/dist/modules/core.d.ts +54 -0
  24. package/dist/modules/core.d.ts.map +1 -0
  25. package/dist/modules/core.js +474 -0
  26. package/dist/modules/core.js.map +1 -0
  27. package/dist/modules/diff-filter.d.ts +71 -0
  28. package/dist/modules/diff-filter.d.ts.map +1 -0
  29. package/dist/modules/diff-filter.js +332 -0
  30. package/dist/modules/diff-filter.js.map +1 -0
  31. package/dist/modules/git.d.ts +61 -0
  32. package/dist/modules/git.d.ts.map +1 -0
  33. package/dist/modules/git.js +362 -0
  34. package/dist/modules/git.js.map +1 -0
  35. package/dist/modules/logger.d.ts +67 -0
  36. package/dist/modules/logger.d.ts.map +1 -0
  37. package/dist/modules/logger.js +212 -0
  38. package/dist/modules/logger.js.map +1 -0
  39. package/dist/modules/tokenizer.d.ts +43 -0
  40. package/dist/modules/tokenizer.d.ts.map +1 -0
  41. package/dist/modules/tokenizer.js +200 -0
  42. package/dist/modules/tokenizer.js.map +1 -0
  43. package/dist/types/index.d.ts +141 -0
  44. package/dist/types/index.d.ts.map +1 -0
  45. package/dist/types/index.js +70 -0
  46. package/dist/types/index.js.map +1 -0
  47. package/dist/utils/index.d.ts +64 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +154 -0
  50. package/dist/utils/index.js.map +1 -0
  51. package/package.json +75 -0
@@ -0,0 +1,286 @@
1
+ import axios from 'axios';
2
+ import axiosRetry from 'axios-retry';
3
+ import PQueue from 'p-queue';
4
+ import { ApiError, NetworkError, RETRY_CONFIG, CHUNK_LIMITS } from '../types/index.js';
5
+ import { logger } from './logger.js';
6
+ export class ApiManager {
7
+ queue;
8
+ clients = new Map();
9
+ constructor(concurrency = CHUNK_LIMITS.MAX_CONCURRENT_REQUESTS) {
10
+ this.queue = new PQueue({
11
+ concurrency,
12
+ interval: 1000, // 1 second
13
+ intervalCap: concurrency * 2, // Allow burst capacity
14
+ });
15
+ // Handle graceful shutdown
16
+ this.setupGracefulShutdown();
17
+ }
18
+ /**
19
+ * Initialize API client for a provider
20
+ */
21
+ initializeProvider(provider, config) {
22
+ const providerConfig = config.providers[provider];
23
+ if (!providerConfig.apiKey) {
24
+ throw new ApiError(`API key not configured for ${provider}`);
25
+ }
26
+ const client = axios.create({
27
+ baseURL: providerConfig.baseUrl,
28
+ timeout: providerConfig.timeout || 60000,
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ 'Authorization': `Bearer ${providerConfig.apiKey}`,
32
+ 'User-Agent': 'orcommit/1.0.0',
33
+ ...(provider === 'openrouter' && {
34
+ 'HTTP-Referer': 'https://github.com/markolofsen/openrouter-commit',
35
+ 'X-Title': 'OpenRouter Commit CLI',
36
+ }),
37
+ },
38
+ });
39
+ // Configure retry logic
40
+ axiosRetry(client, {
41
+ retries: RETRY_CONFIG.MAX_RETRIES,
42
+ retryDelay: axiosRetry.exponentialDelay,
43
+ retryCondition: (error) => {
44
+ return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
45
+ error.response?.status === 429 ||
46
+ (error.response?.status !== undefined && error.response.status >= 500);
47
+ },
48
+ shouldResetTimeout: true,
49
+ onRetry: (retryCount, error) => {
50
+ logger.warn(`Retry attempt ${retryCount} for ${provider}: ${error.message}`);
51
+ },
52
+ });
53
+ // Add response interceptor for better error handling
54
+ client.interceptors.response.use(response => response, error => this.handleApiError(error, provider));
55
+ this.clients.set(provider, client);
56
+ logger.debug(`Initialized ${provider} client`, { baseUrl: providerConfig.baseUrl });
57
+ }
58
+ /**
59
+ * Generate commit message using the specified provider
60
+ */
61
+ async generateCommitMessage(request, provider) {
62
+ return this.queue.add(async () => {
63
+ try {
64
+ const client = this.clients.get(provider);
65
+ if (!client) {
66
+ throw new ApiError(`Client not initialized for ${provider}`);
67
+ }
68
+ logger.debug(`Sending request to ${provider}`, {
69
+ model: request.model,
70
+ messageCount: request.messages.length,
71
+ maxTokens: request.maxTokens
72
+ });
73
+ const response = await this.makeRequest(client, request, provider);
74
+ const commitMessage = this.extractCommitMessage(response, provider);
75
+ logger.debug(`Received response from ${provider}`, {
76
+ messageLength: commitMessage.length,
77
+ usage: response.usage
78
+ });
79
+ return {
80
+ success: true,
81
+ data: commitMessage,
82
+ };
83
+ }
84
+ catch (error) {
85
+ logger.error(`API request failed for ${provider}`, error);
86
+ if (error instanceof ApiError && error.isRetryable) {
87
+ return {
88
+ success: false,
89
+ error: error,
90
+ retryAfter: this.calculateRetryDelay(error.statusCode),
91
+ };
92
+ }
93
+ return {
94
+ success: false,
95
+ error: error instanceof Error ? new ApiError(error.message, undefined, error) :
96
+ new ApiError('Unknown API error'),
97
+ };
98
+ }
99
+ });
100
+ }
101
+ /**
102
+ * Process multiple chunks in parallel
103
+ */
104
+ async processChunks(chunks, baseRequest, provider) {
105
+ try {
106
+ const promises = chunks.map(chunk => this.generateCommitMessage({
107
+ provider: baseRequest.provider,
108
+ model: baseRequest.model,
109
+ maxTokens: baseRequest.maxTokens,
110
+ temperature: baseRequest.temperature,
111
+ messages: [
112
+ { role: 'system', content: baseRequest.systemPrompt },
113
+ { role: 'user', content: chunk }
114
+ ],
115
+ }, provider));
116
+ const results = await Promise.allSettled(promises);
117
+ const successfulResults = [];
118
+ const errors = [];
119
+ for (const result of results) {
120
+ if (result.status === 'fulfilled' && result.value.success && result.value.data) {
121
+ successfulResults.push(result.value.data);
122
+ }
123
+ else if (result.status === 'fulfilled' && result.value.error) {
124
+ errors.push(result.value.error instanceof ApiError ? result.value.error : new ApiError('Unknown error'));
125
+ }
126
+ else if (result.status === 'rejected') {
127
+ errors.push(new ApiError('Promise rejected', undefined, result.reason));
128
+ }
129
+ }
130
+ if (successfulResults.length === 0) {
131
+ return {
132
+ success: false,
133
+ error: new ApiError(`All chunk processing failed. ${errors.length} errors occurred.`),
134
+ };
135
+ }
136
+ return {
137
+ success: true,
138
+ data: successfulResults,
139
+ };
140
+ }
141
+ catch (error) {
142
+ return {
143
+ success: false,
144
+ error: error instanceof Error ? new ApiError(error.message, undefined, error) :
145
+ new ApiError('Unknown chunk processing error'),
146
+ };
147
+ }
148
+ }
149
+ /**
150
+ * Test API connection
151
+ */
152
+ async testConnection(provider) {
153
+ try {
154
+ const client = this.clients.get(provider);
155
+ if (!client) {
156
+ throw new ApiError(`Client not initialized for ${provider}`);
157
+ }
158
+ const testRequest = {
159
+ provider,
160
+ model: provider === 'openrouter' ? 'openai/gpt-3.5-turbo' : 'gpt-3.5-turbo',
161
+ messages: [{ role: 'user', content: 'test' }],
162
+ maxTokens: 10,
163
+ temperature: 0.1,
164
+ };
165
+ const result = await this.generateCommitMessage(testRequest, provider);
166
+ return result.success;
167
+ }
168
+ catch (error) {
169
+ logger.error(`Connection test failed for ${provider}`, error);
170
+ return false;
171
+ }
172
+ }
173
+ /**
174
+ * Get queue status
175
+ */
176
+ getQueueStatus() {
177
+ return {
178
+ pending: this.queue.pending,
179
+ size: this.queue.size,
180
+ isPaused: this.queue.isPaused,
181
+ };
182
+ }
183
+ /**
184
+ * Clear the queue and wait for ongoing requests
185
+ */
186
+ async shutdown() {
187
+ logger.info('Shutting down API manager...');
188
+ this.queue.clear();
189
+ await this.queue.onIdle();
190
+ logger.info('API manager shutdown complete');
191
+ }
192
+ // Private methods
193
+ async makeRequest(client, request, provider) {
194
+ const endpoint = '/chat/completions';
195
+ const payload = {
196
+ model: request.model,
197
+ messages: request.messages,
198
+ max_tokens: request.maxTokens,
199
+ temperature: request.temperature,
200
+ stream: request.stream ?? false,
201
+ };
202
+ const config = {
203
+ timeout: 60000,
204
+ };
205
+ const response = await client.post(endpoint, payload, config);
206
+ return this.parseResponse(response.data, provider);
207
+ }
208
+ parseResponse(data, provider) {
209
+ if (!data.choices || !Array.isArray(data.choices) || data.choices.length === 0) {
210
+ throw new ApiError(`Invalid response format from ${provider}: no choices found`);
211
+ }
212
+ const choice = data.choices[0];
213
+ const message = choice.message?.content;
214
+ if (!message) {
215
+ throw new ApiError(`Invalid response format from ${provider}: no message content`);
216
+ }
217
+ return {
218
+ message,
219
+ usage: data.usage ? {
220
+ promptTokens: data.usage.prompt_tokens,
221
+ completionTokens: data.usage.completion_tokens,
222
+ totalTokens: data.usage.total_tokens,
223
+ } : undefined,
224
+ model: data.model || 'unknown',
225
+ finishReason: choice.finish_reason || 'unknown',
226
+ };
227
+ }
228
+ extractCommitMessage(response, provider) {
229
+ let message = response.message.trim();
230
+ // Remove common prefixes and formatting
231
+ message = message.replace(/^(commit message:|commit:|message:)\s*/i, '');
232
+ message = message.replace(/^["']|["']$/g, ''); // Remove surrounding quotes
233
+ message = message.replace(/^\*\s*/, ''); // Remove leading asterisk
234
+ // Ensure it's not empty
235
+ if (!message || message.length < 3) {
236
+ throw new ApiError(`Generated commit message too short from ${provider}: "${message}"`);
237
+ }
238
+ // Limit length (conventional commits should be concise)
239
+ if (message.length > 200) {
240
+ logger.warn(`Commit message truncated (was ${message.length} characters)`);
241
+ message = message.substring(0, 197) + '...';
242
+ }
243
+ return message;
244
+ }
245
+ handleApiError(error, provider) {
246
+ if (error.response) {
247
+ // HTTP error response
248
+ const status = error.response.status;
249
+ const data = error.response.data;
250
+ let message = `${provider} API error (${status})`;
251
+ if (data?.error?.message) {
252
+ message += `: ${data.error.message}`;
253
+ }
254
+ else if (data?.message) {
255
+ message += `: ${data.message}`;
256
+ }
257
+ throw new ApiError(message, status, error);
258
+ }
259
+ else if (error.request) {
260
+ // Network error
261
+ throw new NetworkError(`Network error communicating with ${provider}: ${error.message}`, error);
262
+ }
263
+ else {
264
+ // Other error
265
+ throw new ApiError(`Request setup error for ${provider}: ${error.message}`, undefined, error);
266
+ }
267
+ }
268
+ calculateRetryDelay(statusCode) {
269
+ if (statusCode === 429) {
270
+ return RETRY_CONFIG.BASE_DELAY * 2; // Rate limited, wait longer
271
+ }
272
+ return RETRY_CONFIG.BASE_DELAY;
273
+ }
274
+ setupGracefulShutdown() {
275
+ const gracefulShutdown = async (signal) => {
276
+ logger.info(`Received ${signal}, shutting down gracefully...`);
277
+ await this.shutdown();
278
+ process.exit(0);
279
+ };
280
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
281
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
282
+ }
283
+ }
284
+ // Singleton instance
285
+ export const apiManager = new ApiManager();
286
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/modules/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ export interface CacheEntry<T> {
2
+ data: T;
3
+ timestamp: number;
4
+ hash: string;
5
+ model: string;
6
+ provider: string;
7
+ }
8
+ export interface CacheOptions {
9
+ ttl?: number;
10
+ maxSize?: number;
11
+ enabled?: boolean;
12
+ }
13
+ export declare class CacheManager {
14
+ private readonly cacheDir;
15
+ private readonly options;
16
+ private memoryCache;
17
+ constructor(options?: CacheOptions);
18
+ /**
19
+ * Generate cache key from diff content and request parameters
20
+ */
21
+ private generateCacheKey;
22
+ /**
23
+ * Get cached commit message if available and valid
24
+ */
25
+ get(content: string, model: string, provider: string, temperature: number): Promise<string | null>;
26
+ /**
27
+ * Store commit message in cache
28
+ */
29
+ set(content: string, model: string, provider: string, temperature: number, commitMessage: string): Promise<void>;
30
+ /**
31
+ * Check if cache entry is still valid
32
+ */
33
+ private isEntryValid;
34
+ /**
35
+ * Get entry from disk cache
36
+ */
37
+ private getFromDisk;
38
+ /**
39
+ * Save entry to disk cache
40
+ */
41
+ private saveToDisk;
42
+ /**
43
+ * Ensure cache directory exists
44
+ */
45
+ private ensureCacheDir;
46
+ /**
47
+ * Evict old entries from memory cache to keep it under size limit
48
+ */
49
+ private evictMemoryCache;
50
+ /**
51
+ * Clean up expired entries from disk cache
52
+ */
53
+ cleanup(): Promise<void>;
54
+ /**
55
+ * Get cache statistics
56
+ */
57
+ getStats(): Promise<{
58
+ memoryEntries: number;
59
+ diskEntries: number;
60
+ totalSize: string;
61
+ oldestEntry?: Date;
62
+ newestEntry?: Date;
63
+ }>;
64
+ /**
65
+ * Clear all cache (memory and disk)
66
+ */
67
+ clear(): Promise<void>;
68
+ /**
69
+ * Format bytes as human readable
70
+ */
71
+ private formatBytes;
72
+ }
73
+ export declare const cacheManager: CacheManager;
74
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/modules/cache.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC;IACR,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,WAAW,CAA8C;gBAErD,OAAO,GAAE,YAAiB;IAStC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;OAEG;IACG,GAAG,CACP,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiCzB;;OAEG;IACG,GAAG,CACP,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IAiChB;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;YACW,WAAW;IAiBzB;;OAEG;YACW,UAAU;IAcxB;;OAEG;YACW,cAAc;IAQ5B;;OAEG;YACW,gBAAgB;IAqB9B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C9B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,IAAI,CAAC;QACnB,WAAW,CAAC,EAAE,IAAI,CAAC;KACpB,CAAC;IAgDF;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B;;OAEG;IACH,OAAO,CAAC,WAAW;CAYpB;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -0,0 +1,284 @@
1
+ import { promises as fs } from 'fs';
2
+ import { createHash } from 'crypto';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { logger } from './logger.js';
6
+ export class CacheManager {
7
+ cacheDir;
8
+ options;
9
+ memoryCache = new Map();
10
+ constructor(options = {}) {
11
+ this.cacheDir = join(homedir(), '.cache', 'orcommit');
12
+ this.options = {
13
+ ttl: options.ttl ?? 24 * 60 * 60 * 1000, // 24 hours
14
+ maxSize: options.maxSize ?? 50, // 50 MB
15
+ enabled: options.enabled ?? true,
16
+ };
17
+ }
18
+ /**
19
+ * Generate cache key from diff content and request parameters
20
+ */
21
+ generateCacheKey(content, model, provider, temperature) {
22
+ const hash = createHash('sha256')
23
+ .update(content)
24
+ .update(model)
25
+ .update(provider)
26
+ .update(temperature.toString())
27
+ .digest('hex');
28
+ return hash.slice(0, 16); // Use first 16 chars for shorter filenames
29
+ }
30
+ /**
31
+ * Get cached commit message if available and valid
32
+ */
33
+ async get(content, model, provider, temperature) {
34
+ if (!this.options.enabled) {
35
+ return null;
36
+ }
37
+ const key = this.generateCacheKey(content, model, provider, temperature);
38
+ try {
39
+ // Check memory cache first
40
+ const memoryEntry = this.memoryCache.get(key);
41
+ if (memoryEntry && this.isEntryValid(memoryEntry)) {
42
+ logger.debug('Cache hit (memory)', { key });
43
+ return memoryEntry.data;
44
+ }
45
+ // Check disk cache
46
+ const diskEntry = await this.getFromDisk(key);
47
+ if (diskEntry && this.isEntryValid(diskEntry)) {
48
+ // Promote to memory cache
49
+ this.memoryCache.set(key, diskEntry);
50
+ logger.debug('Cache hit (disk)', { key });
51
+ return diskEntry.data;
52
+ }
53
+ logger.debug('Cache miss', { key });
54
+ return null;
55
+ }
56
+ catch (error) {
57
+ logger.warn(`Cache read error: ${error.message}`);
58
+ return null;
59
+ }
60
+ }
61
+ /**
62
+ * Store commit message in cache
63
+ */
64
+ async set(content, model, provider, temperature, commitMessage) {
65
+ if (!this.options.enabled) {
66
+ return;
67
+ }
68
+ const key = this.generateCacheKey(content, model, provider, temperature);
69
+ const contentHash = createHash('sha256').update(content).digest('hex');
70
+ const entry = {
71
+ data: commitMessage,
72
+ timestamp: Date.now(),
73
+ hash: contentHash,
74
+ model,
75
+ provider,
76
+ };
77
+ try {
78
+ // Store in memory cache
79
+ this.memoryCache.set(key, entry);
80
+ // Limit memory cache size
81
+ await this.evictMemoryCache();
82
+ // Store in disk cache
83
+ await this.saveToDisk(key, entry);
84
+ logger.debug('Cache stored', { key, provider, model });
85
+ }
86
+ catch (error) {
87
+ logger.warn(`Cache write error: ${error.message}`);
88
+ }
89
+ }
90
+ /**
91
+ * Check if cache entry is still valid
92
+ */
93
+ isEntryValid(entry) {
94
+ const now = Date.now();
95
+ const age = now - entry.timestamp;
96
+ return age < this.options.ttl;
97
+ }
98
+ /**
99
+ * Get entry from disk cache
100
+ */
101
+ async getFromDisk(key) {
102
+ try {
103
+ await this.ensureCacheDir();
104
+ const filePath = join(this.cacheDir, `${key}.json`);
105
+ const data = await fs.readFile(filePath, 'utf-8');
106
+ return JSON.parse(data);
107
+ }
108
+ catch (error) {
109
+ if (error.code !== 'ENOENT') {
110
+ logger.debug('Disk cache read error', error);
111
+ }
112
+ return null;
113
+ }
114
+ }
115
+ /**
116
+ * Save entry to disk cache
117
+ */
118
+ async saveToDisk(key, entry) {
119
+ try {
120
+ await this.ensureCacheDir();
121
+ const filePath = join(this.cacheDir, `${key}.json`);
122
+ const data = JSON.stringify(entry, null, 2);
123
+ await fs.writeFile(filePath, data, 'utf-8');
124
+ }
125
+ catch (error) {
126
+ logger.debug('Disk cache write error', error);
127
+ }
128
+ }
129
+ /**
130
+ * Ensure cache directory exists
131
+ */
132
+ async ensureCacheDir() {
133
+ try {
134
+ await fs.access(this.cacheDir);
135
+ }
136
+ catch {
137
+ await fs.mkdir(this.cacheDir, { recursive: true });
138
+ }
139
+ }
140
+ /**
141
+ * Evict old entries from memory cache to keep it under size limit
142
+ */
143
+ async evictMemoryCache() {
144
+ const maxEntries = 100; // Keep reasonable number in memory
145
+ if (this.memoryCache.size <= maxEntries) {
146
+ return;
147
+ }
148
+ // Convert to array and sort by timestamp (oldest first)
149
+ const entries = Array.from(this.memoryCache.entries())
150
+ .sort(([, a], [, b]) => a.timestamp - b.timestamp);
151
+ // Remove oldest entries
152
+ const toRemove = entries.slice(0, entries.length - maxEntries);
153
+ for (const [key] of toRemove) {
154
+ this.memoryCache.delete(key);
155
+ }
156
+ logger.debug(`Evicted ${toRemove.length} entries from memory cache`);
157
+ }
158
+ /**
159
+ * Clean up expired entries from disk cache
160
+ */
161
+ async cleanup() {
162
+ if (!this.options.enabled) {
163
+ return;
164
+ }
165
+ try {
166
+ await this.ensureCacheDir();
167
+ const files = await fs.readdir(this.cacheDir);
168
+ const now = Date.now();
169
+ let cleanedCount = 0;
170
+ for (const file of files) {
171
+ if (!file.endsWith('.json'))
172
+ continue;
173
+ try {
174
+ const filePath = join(this.cacheDir, file);
175
+ const data = await fs.readFile(filePath, 'utf-8');
176
+ const entry = JSON.parse(data);
177
+ const age = now - entry.timestamp;
178
+ if (age > this.options.ttl) {
179
+ await fs.unlink(filePath);
180
+ cleanedCount++;
181
+ }
182
+ }
183
+ catch (error) {
184
+ // If we can't parse the file, delete it
185
+ try {
186
+ await fs.unlink(join(this.cacheDir, file));
187
+ cleanedCount++;
188
+ }
189
+ catch {
190
+ // Ignore deletion errors
191
+ }
192
+ }
193
+ }
194
+ if (cleanedCount > 0) {
195
+ logger.debug(`Cleaned up ${cleanedCount} expired cache entries`);
196
+ }
197
+ }
198
+ catch (error) {
199
+ logger.warn(`Cache cleanup error: ${error.message}`);
200
+ }
201
+ }
202
+ /**
203
+ * Get cache statistics
204
+ */
205
+ async getStats() {
206
+ const memoryEntries = this.memoryCache.size;
207
+ let diskEntries = 0;
208
+ let totalSize = 0;
209
+ let oldestTimestamp = Number.MAX_SAFE_INTEGER;
210
+ let newestTimestamp = 0;
211
+ try {
212
+ await this.ensureCacheDir();
213
+ const files = await fs.readdir(this.cacheDir);
214
+ for (const file of files) {
215
+ if (!file.endsWith('.json'))
216
+ continue;
217
+ try {
218
+ const filePath = join(this.cacheDir, file);
219
+ const stats = await fs.stat(filePath);
220
+ const data = await fs.readFile(filePath, 'utf-8');
221
+ const entry = JSON.parse(data);
222
+ diskEntries++;
223
+ totalSize += stats.size;
224
+ if (entry.timestamp < oldestTimestamp) {
225
+ oldestTimestamp = entry.timestamp;
226
+ }
227
+ if (entry.timestamp > newestTimestamp) {
228
+ newestTimestamp = entry.timestamp;
229
+ }
230
+ }
231
+ catch {
232
+ // Skip invalid files
233
+ }
234
+ }
235
+ }
236
+ catch (error) {
237
+ logger.warn(`Error getting cache stats: ${error.message}`);
238
+ }
239
+ return {
240
+ memoryEntries,
241
+ diskEntries,
242
+ totalSize: this.formatBytes(totalSize),
243
+ oldestEntry: oldestTimestamp !== Number.MAX_SAFE_INTEGER ? new Date(oldestTimestamp) : undefined,
244
+ newestEntry: newestTimestamp > 0 ? new Date(newestTimestamp) : undefined,
245
+ };
246
+ }
247
+ /**
248
+ * Clear all cache (memory and disk)
249
+ */
250
+ async clear() {
251
+ // Clear memory cache
252
+ this.memoryCache.clear();
253
+ // Clear disk cache
254
+ try {
255
+ await this.ensureCacheDir();
256
+ const files = await fs.readdir(this.cacheDir);
257
+ for (const file of files) {
258
+ if (file.endsWith('.json')) {
259
+ await fs.unlink(join(this.cacheDir, file));
260
+ }
261
+ }
262
+ logger.debug('Cache cleared');
263
+ }
264
+ catch (error) {
265
+ logger.warn(`Error clearing cache: ${error.message}`);
266
+ }
267
+ }
268
+ /**
269
+ * Format bytes as human readable
270
+ */
271
+ formatBytes(bytes) {
272
+ const units = ['B', 'KB', 'MB', 'GB'];
273
+ let size = bytes;
274
+ let unitIndex = 0;
275
+ while (size >= 1024 && unitIndex < units.length - 1) {
276
+ size /= 1024;
277
+ unitIndex++;
278
+ }
279
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
280
+ }
281
+ }
282
+ // Singleton instance
283
+ export const cacheManager = new CacheManager();
284
+ //# sourceMappingURL=cache.js.map