Skip to main content
Glama
CachedWordPressClient.ts14.6 kB
/** * Cached WordPress API Client * Extends the base WordPress client with intelligent caching capabilities and performance monitoring */ import { WordPressClient } from "./api.js"; import { CacheManager, type CacheStats } from "@/cache/CacheManager.js"; import { HttpCacheWrapper } from "@/cache/HttpCacheWrapper.js"; import { CacheInvalidation } from "@/cache/CacheInvalidation.js"; import { SecurityConfig } from "@/security/SecurityConfig.js"; import type { WordPressClientConfig, HTTPMethod, RequestOptions } from "@/types/client.js"; import type { WordPressPost, WordPressUser, WordPressCategory, WordPressTag, WordPressSiteSettings, PostQueryParams, CreatePostRequest, UpdatePostRequest, } from "@/types/wordpress.js"; /** * WordPress client with intelligent caching */ export class CachedWordPressClient extends WordPressClient { private cacheManager: CacheManager; private httpCache: HttpCacheWrapper; private cacheInvalidation: CacheInvalidation; private siteId: string; constructor(config: WordPressClientConfig, siteId: string = "default") { super(config); this.siteId = siteId; // Initialize caching system this.cacheManager = new CacheManager({ maxSize: SecurityConfig.cache.maxSize, defaultTTL: SecurityConfig.cache.defaultTTL, enableLRU: SecurityConfig.cache.enableLRU, enableStats: SecurityConfig.cache.enableStats, sitePrefix: siteId, }); this.httpCache = new HttpCacheWrapper(this.cacheManager, siteId); this.cacheInvalidation = new CacheInvalidation(this.httpCache); } /** * Override request method to add caching */ async request<T = unknown>( method: HTTPMethod, endpoint: string, data: unknown = null, options: RequestOptions = {}, ): Promise<T> { const upperMethod = method.toUpperCase(); // Only cache GET requests if (upperMethod !== "GET" || !SecurityConfig.cache.enabled) { const response = await super.request<T>(method, endpoint, data, options); // Trigger cache invalidation for write operations if (["POST", "PUT", "PATCH", "DELETE"].includes(upperMethod)) { await this.handleCacheInvalidation(method, endpoint, data); } return response; } // Normalize endpoint/query params so both the HTTP request and cache key stay in sync const { normalizedEndpoint, normalizedParams } = this.normalizeEndpoint(endpoint, options.params); // Ensure we don't leak params to the super call (the parent client expects query strings) const baseOptions: RequestOptions = { ...options }; delete baseOptions.params; const cacheRequestOptions = { method, url: this.buildRequestUrl(normalizedEndpoint), headers: { ...(baseOptions.headers || {}) }, params: normalizedParams, data, }; const cacheOptions = this.getCacheOptions(normalizedEndpoint); const response = await this.httpCache.request( async (extraHeaders) => { const mergedOptions: RequestOptions = { ...baseOptions, headers: { ...(baseOptions.headers || {}), ...(extraHeaders || {}), }, }; const result = await super.request<T>(method, normalizedEndpoint, data, mergedOptions); return { data: result, status: 200, headers: mergedOptions.headers || {}, }; }, cacheRequestOptions, cacheOptions, ); return response.data; } /** * Enhanced methods with caching optimization */ /** * Get posts with intelligent caching */ async getPosts(params: PostQueryParams = {}): Promise<WordPressPost[]> { return await this.request<WordPressPost[]>("GET", "posts", null, { params, }); } /** * Get single post with caching */ async getPost(id: number): Promise<WordPressPost> { return await this.request<WordPressPost>("GET", `posts/${id}`); } /** * Create post with cache invalidation */ async createPost(data: CreatePostRequest): Promise<WordPressPost> { const post = await super.createPost(data); // Invalidate related caches await this.cacheInvalidation.invalidateResource("posts", post.id, "create"); return post; } /** * Update post with cache invalidation */ async updatePost(data: UpdatePostRequest): Promise<WordPressPost> { const post = await super.updatePost(data); // Invalidate related caches await this.cacheInvalidation.invalidateResource("posts", post.id, "update"); return post; } /** * Delete post with cache invalidation */ async deletePost(id: number, force?: boolean): Promise<{ deleted: boolean; previous?: WordPressPost }> { const result = await super.deletePost(id, force); // Invalidate related caches await this.cacheInvalidation.invalidateResource("posts", id, "delete"); return result; } /** * Get current user with session caching * Uses context=edit to ensure roles and capabilities are included */ async getCurrentUser(): Promise<WordPressUser> { return await this.request<WordPressUser>("GET", "users/me?context=edit"); } /** * Get categories with semi-static caching */ async getCategories(params: Record<string, unknown> = {}): Promise<WordPressCategory[]> { return await this.request<WordPressCategory[]>("GET", "categories", null, { params, }); } /** * Get tags with semi-static caching */ async getTags(params: Record<string, unknown> = {}): Promise<WordPressTag[]> { return await this.request<WordPressTag[]>("GET", "tags", null, { params }); } /** * Get site settings with static caching */ async getSiteSettings(): Promise<WordPressSiteSettings> { return await this.request<WordPressSiteSettings>("GET", "settings"); } /** * Cache management methods */ /** * Private helper methods */ private extractEndpoint(url: string): string { // Simple approach - use the endpoint part return url.replace(/^.*\/wp-json\/wp\/v2\//, "").split("?")[0]; } /** * Get cache options based on endpoint */ private getCacheOptions(endpoint: string) { // Determine cache type based on endpoint if (this.isStaticEndpoint(endpoint)) { return { ttl: SecurityConfig.cache.ttlPresets.static, cacheControl: SecurityConfig.cache.cacheHeaders.static, }; } if (this.isSemiStaticEndpoint(endpoint)) { return { ttl: SecurityConfig.cache.ttlPresets.semiStatic, cacheControl: SecurityConfig.cache.cacheHeaders.semiStatic, }; } if (this.isSessionEndpoint(endpoint)) { return { ttl: SecurityConfig.cache.ttlPresets.session, cacheControl: SecurityConfig.cache.cacheHeaders.session, private: true, }; } // Default to dynamic caching return { ttl: SecurityConfig.cache.ttlPresets.dynamic, cacheControl: SecurityConfig.cache.cacheHeaders.dynamic, }; } private normalizeEndpoint( endpoint: string, params: Record<string, unknown> | undefined, ): { normalizedEndpoint: string; normalizedParams: Record<string, unknown> } { const [pathPart, queryPart] = endpoint.split("?"); const collectedParams: Record<string, string[]> = {}; if (queryPart) { const searchParams = new URLSearchParams(queryPart); for (const [key, value] of searchParams.entries()) { if (!collectedParams[key]) { collectedParams[key] = []; } collectedParams[key].push(value); } } if (params) { for (const [key, value] of Object.entries(params)) { if (value === undefined || value === null) continue; const values = Array.isArray(value) ? value : [value]; collectedParams[key] = values.map((item) => String(item)); } } const searchParams = new URLSearchParams(); Object.keys(collectedParams) .sort() .forEach((key) => { collectedParams[key].forEach((value) => searchParams.append(key, value)); }); const normalizedParams: Record<string, unknown> = {}; for (const [key, value] of Object.entries(collectedParams)) { normalizedParams[key] = value.length === 1 ? value[0] : [...value]; } const normalizedEndpoint = searchParams.toString() ? `${pathPart}?${searchParams.toString()}` : pathPart; return { normalizedEndpoint, normalizedParams }; } private buildRequestUrl(endpoint: string): string { if (endpoint.startsWith("http")) { return endpoint; } const cleanEndpoint = endpoint.replace(/^\/+/, ""); const siteUrl = this.getSiteUrl().replace(/\/$/, ""); if (cleanEndpoint.startsWith("wp-json/")) { return `${siteUrl}/${cleanEndpoint}`; } return `${siteUrl}/wp-json/wp/v2/${cleanEndpoint}`; } /** * Handle cache invalidation for write operations */ private async handleCacheInvalidation(method: string, endpoint: string, data: unknown): Promise<void> { const resource = this.extractResourceFromEndpoint(endpoint); const id = this.extractIdFromEndpoint(endpoint); let operationType: "create" | "update" | "delete"; switch (method.toUpperCase()) { case "POST": operationType = "create"; break; case "PUT": case "PATCH": operationType = "update"; break; case "DELETE": operationType = "delete"; break; default: return; } await this.cacheInvalidation.invalidateResource(resource, id, operationType); } /** * Extract resource type from endpoint */ private extractResourceFromEndpoint(endpoint: string): string { const parts = endpoint.split("/"); return parts[0] || "unknown"; } /** * Extract ID from endpoint */ private extractIdFromEndpoint(endpoint: string): number | undefined { const match = endpoint.match(/\/(\d+)(?:\/|$)/); return match ? parseInt(match[1], 10) : undefined; } /** * Check if endpoint contains static data */ private isStaticEndpoint(endpoint: string): boolean { const staticEndpoints = ["settings", "types", "statuses"]; return staticEndpoints.some((pattern) => endpoint.includes(pattern)); } /** * Check if endpoint contains semi-static data */ private isSemiStaticEndpoint(endpoint: string): boolean { const semiStaticEndpoints = ["categories", "tags", "users", "taxonomies"]; return semiStaticEndpoints.some((pattern) => endpoint.includes(pattern)); } /** * Check if endpoint is session-related */ private isSessionEndpoint(endpoint: string): boolean { const sessionEndpoints = ["users/me", "application-passwords"]; return sessionEndpoints.some((pattern) => endpoint.includes(pattern)); } /** * Performance monitoring and cache management methods */ /** * Get cache statistics for performance monitoring */ getCacheStats(): { cache: CacheStats; invalidation: { queueSize: number; rulesCount: number; processing: boolean; }; } { return { cache: this.cacheManager.getStats(), invalidation: this.cacheInvalidation.getStats(), }; } /** * Get cache manager instance (for performance monitoring integration) */ getCacheManager(): CacheManager { return this.cacheManager; } /** * Clear cache entries (for cache management tools) */ clearCache(): number { const stats = this.cacheManager.getStats(); this.cacheManager.clear(); return stats.totalSize; } /** * Clear cache entries matching pattern */ clearCachePattern(pattern: string): number { const regex = new RegExp(pattern, "i"); return this.cacheManager.clearPattern(regex); } /** * Warm cache with essential data */ async warmCache(): Promise<void> { try { // Pre-load frequently accessed data const warmupOperations = [ () => this.getCurrentUser().catch(() => null), () => this.getCategories().catch(() => null), () => this.getTags().catch(() => null), () => this.getSiteSettings().catch(() => null), ]; // Execute warmup operations in parallel await Promise.allSettled(warmupOperations.map((op) => op())); } catch (_error) { // Ignore warmup errors - they shouldn't fail the cache warming } } /** * Get cache efficiency metrics */ getCacheEfficiency(): { hitRate: number; missRate: number; efficiency: string; memoryUsage: number; totalEntries: number; } { const stats = this.cacheManager.getStats(); const total = stats.hits + stats.misses; const hitRate = total > 0 ? stats.hits / total : 0; const missRate = 1 - hitRate; let efficiency = "Poor"; if (hitRate >= 0.9) efficiency = "Excellent"; else if (hitRate >= 0.8) efficiency = "Good"; else if (hitRate >= 0.6) efficiency = "Fair"; return { hitRate, missRate, efficiency, memoryUsage: this.estimateMemoryUsage(), totalEntries: stats.totalSize, }; } /** * Get cache configuration info */ getCacheInfo(): { enabled: boolean; siteId: string; maxSize: number; defaultTTL: number; currentSize: number; ttlPresets: Record<string, unknown>; } { const stats = this.cacheManager.getStats(); return { enabled: SecurityConfig.cache.enabled, siteId: this.siteId, maxSize: SecurityConfig.cache.maxSize, defaultTTL: SecurityConfig.cache.defaultTTL, currentSize: stats.totalSize, ttlPresets: SecurityConfig.cache.ttlPresets, }; } /** * Estimate memory usage of cache (in MB) */ private estimateMemoryUsage(): number { const stats = this.cacheManager.getStats(); // Rough estimate: ~1KB per cache entry return (stats.totalSize * 1024) / (1024 * 1024); } /** * Get detailed cache performance metrics */ getDetailedCacheMetrics(): { statistics: { cache: CacheStats; invalidation: { queueSize: number; rulesCount: number; processing: boolean; }; }; efficiency: Record<string, unknown>; configuration: Record<string, unknown>; siteInfo: { siteId: string; baseUrl: string; }; } { return { statistics: this.getCacheStats(), efficiency: this.getCacheEfficiency(), configuration: this.getCacheInfo(), siteInfo: { siteId: this.siteId, baseUrl: this.config.baseUrl, }, }; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server