@kimsungwhee/apple-docs-mcp
Version:
MCP server for Apple Developer Documentation - Search iOS/macOS/SwiftUI/UIKit docs, WWDC videos, Swift/Objective-C APIs & code examples in Claude, Cursor & AI assistants
223 lines • 6.89 kB
JavaScript
/**
* Simple in-memory cache with TTL (Time To Live) support
*/
export class MemoryCache {
cache = new Map();
maxSize;
defaultTTL;
hits = 0;
misses = 0;
constructor(maxSize = 1000, defaultTTL = 30 * 60 * 1000) {
this.maxSize = maxSize;
this.defaultTTL = defaultTTL;
// Clean up expired entries every 5 minutes
setInterval(() => this.cleanup(), 5 * 60 * 1000);
}
/**
* Get the current size of the cache
*/
size() {
return this.cache.size;
}
/**
* Get all entries as array of [key, value] pairs
*/
entries() {
const result = [];
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp <= entry.ttl) {
result.push([key, entry.data]);
}
}
return result;
}
/**
* Get value from cache
*/
get(key) {
const entry = this.cache.get(key);
if (!entry) {
this.misses++;
return undefined;
}
// Check if expired
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
this.misses++;
return undefined;
}
this.hits++;
return entry.data;
}
/**
* Set value in cache
*/
set(key, value, ttl) {
// If cache is full, remove oldest entry
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
if (firstKey) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, {
data: value,
timestamp: Date.now(),
ttl: ttl ?? this.defaultTTL,
});
}
/**
* Check if key exists and is not expired
*/
has(key) {
return this.get(key) !== undefined;
}
/**
* Delete entry from cache
*/
delete(key) {
return this.cache.delete(key);
}
/**
* Clear all cache entries
*/
clear() {
this.cache.clear();
}
/**
* Clean up expired entries
*/
cleanup() {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > entry.ttl) {
this.cache.delete(key);
}
}
}
/**
* Get cache statistics
*/
getStats() {
const total = this.hits + this.misses;
const hitRate = total > 0 ? (this.hits / total * 100).toFixed(2) + '%' : '0.00%';
return {
size: this.cache.size,
maxSize: this.maxSize,
hitRate,
hits: this.hits,
misses: this.misses,
};
}
/**
* Get or set with async function
*/
async getOrSet(key, fetchFn, ttl) {
// Try to get from cache first
const cached = this.get(key);
if (cached !== undefined) {
return cached;
}
// Fetch new data
const data = await fetchFn();
// Store in cache
this.set(key, data, ttl);
return data;
}
}
import { CACHE_SIZE, CACHE_TTL } from './constants.js';
// Create different cache instances for different types of data
export const apiCache = new MemoryCache(CACHE_SIZE.API_DOCS, CACHE_TTL.API_DOCS);
export const searchCache = new MemoryCache(CACHE_SIZE.SEARCH_RESULTS, CACHE_TTL.SEARCH_RESULTS);
export const indexCache = new MemoryCache(CACHE_SIZE.FRAMEWORK_INDEX, CACHE_TTL.FRAMEWORK_INDEX);
export const technologiesCache = new MemoryCache(CACHE_SIZE.TECHNOLOGIES, CACHE_TTL.TECHNOLOGIES);
export const updatesCache = new MemoryCache(CACHE_SIZE.UPDATES, CACHE_TTL.UPDATES);
export const sampleCodeCache = new MemoryCache(CACHE_SIZE.SAMPLE_CODE, CACHE_TTL.SAMPLE_CODE);
export const technologyOverviewsCache = new MemoryCache(CACHE_SIZE.TECHNOLOGY_OVERVIEWS, CACHE_TTL.TECHNOLOGY_OVERVIEWS);
export const wwdcDataCache = new MemoryCache(100, 30 * 60 * 1000); // 30 minutes TTL
/**
* Generate cache key for URL-based requests
*/
export function generateUrlCacheKey(url, params) {
let key = url;
if (params) {
const sortedParams = Object.keys(params)
.sort()
.map(k => `${k}=${JSON.stringify(params[k])}`)
.join('&');
key += `?${sortedParams}`;
}
return key;
}
/**
* Generate cache key for enhanced analysis
*/
export function generateEnhancedCacheKey(url, options) {
return generateUrlCacheKey(url, options);
}
/**
* Cache decorator for async functions
*/
export function cached(cache, keyGenerator, ttl) {
return function (_target, _propertyName, descriptor) {
const method = descriptor.value;
descriptor.value = async function (...args) {
const key = keyGenerator(...args);
return cache.getOrSet(key, () => method.apply(this, args), ttl);
};
};
}
/**
* Cache decorator (alias for compatibility)
*/
export function withCache(cache, keyGenerator, ttl) {
return function (_target, _propertyName, descriptor) {
// Handle the case where descriptor might be undefined (TypeScript decorators)
if (!descriptor) {
throw new Error('withCache decorator requires a method descriptor');
}
const method = descriptor.value;
if (!method) {
throw new Error('withCache decorator can only be applied to methods');
}
const isAsync = method.constructor.name === 'AsyncFunction';
descriptor.value = function (...args) {
const key = keyGenerator ? keyGenerator(...args) : JSON.stringify(args);
// Try to get from cache first
const cached = cache.get(key);
if (cached !== undefined) {
return cached;
}
// Execute the method
const result = method.apply(this, args);
// Handle both sync and async methods
if (isAsync || result instanceof Promise) {
return Promise.resolve(result).then((data) => {
cache.set(key, data, ttl);
return data;
}).catch((error) => {
// Don't cache errors
throw error;
});
}
else {
// Sync method
cache.set(key, result, ttl);
return result;
}
};
return descriptor;
};
}
/**
* Get cache instance by name (singleton pattern)
*/
const cacheInstances = new Map();
export function getCacheInstance(name, maxSize, defaultTTL) {
if (!cacheInstances.has(name)) {
cacheInstances.set(name, new MemoryCache(maxSize, defaultTTL));
}
return cacheInstances.get(name);
}
//# sourceMappingURL=cache.js.map