This commit is contained in:
oliver
2026-08-28 10:11:11 -03:00
parent f533ff8e36
commit 585951c8ce
1380 changed files with 1540268 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
/**
* Upstash Client-Side Cache Integration
* Provides intelligent caching for API calls and dynamic content
*/
class UpstashClient {
constructor() {
this.cacheEndpoint = '/api/cache';
this.localCache = new Map();
this.pendingRequests = new Map();
}
/**
* Get data from cache or fetch if not available
*/
async get(key, fetcher, ttl = 3600) {
// Check local memory cache first
const localData = this.localCache.get(key);
if (localData && Date.now() - localData.timestamp < localData.ttl * 1000) {
console.log(`Local cache HIT: ${key}`);
return localData.data;
}
// Check if request is already pending
if (this.pendingRequests.has(key)) {
console.log(`Waiting for pending request: ${key}`);
return this.pendingRequests.get(key);
}
// Create promise for this request
const promise = this._fetchWithCache(key, fetcher, ttl);
this.pendingRequests.set(key, promise);
try {
const result = await promise;
return result;
} finally {
this.pendingRequests.delete(key);
}
}
async _fetchWithCache(key, fetcher, ttl) {
try {
// Try to get from Upstash cache
const cached = await this._getFromUpstash(key);
if (cached) {
console.log(`Upstash cache HIT: ${key}`);
// Store in local cache
this.localCache.set(key, {
data: cached,
timestamp: Date.now(),
ttl: ttl
});
return cached;
}
console.log(`Cache MISS: ${key} - fetching fresh data`);
// Fetch fresh data
const freshData = await fetcher();
// Store in both Upstash and local cache
await this._setInUpstash(key, freshData, ttl);
this.localCache.set(key, {
data: freshData,
timestamp: Date.now(),
ttl: ttl
});
return freshData;
} catch (error) {
console.error(`Cache error for ${key}:`, error);
// Fallback to fetcher on error
return fetcher();
}
}
async _getFromUpstash(key) {
try {
const response = await fetch(`${this.cacheEndpoint}?action=get&key=${encodeURIComponent(key)}`);
if (response.ok) {
const result = await response.json();
return result.data;
}
} catch (error) {
console.error('Upstash get error:', error);
}
return null;
}
async _setInUpstash(key, value, ttl) {
try {
await fetch(this.cacheEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'set',
key: key,
value: JSON.stringify(value),
ttl: ttl
})
});
} catch (error) {
console.error('Upstash set error:', error);
}
}
/**
* Invalidate cache for a specific key or pattern
*/
async invalidate(pattern) {
// Clear local cache
if (pattern === '*') {
this.localCache.clear();
} else {
for (const [key] of this.localCache) {
if (key.includes(pattern)) {
this.localCache.delete(key);
}
}
}
// Invalidate Upstash cache
try {
await fetch('/api/cache/invalidate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ pattern })
});
} catch (error) {
console.error('Cache invalidation error:', error);
}
}
/**
* Preload data into cache
*/
async preload(key, fetcher, ttl = 3600) {
// Don't wait for result
this.get(key, fetcher, ttl).catch(console.error);
}
/**
* Get cache statistics
*/
async getStats() {
try {
const response = await fetch('/api/cache/stats');
if (response.ok) {
const stats = await response.json();
stats.localCacheSize = this.localCache.size;
return stats;
}
} catch (error) {
console.error('Stats error:', error);
}
return { localCacheSize: this.localCache.size };
}
}
// Initialize global cache client
window.upstashCache = new UpstashClient();
// Intelligent prefetching based on user behavior
document.addEventListener('DOMContentLoaded', () => {
// Prefetch on link hover
let prefetchedUrls = new Set();
document.querySelectorAll('a[href^="/"]').forEach(link => {
link.addEventListener('mouseenter', () => {
const href = link.getAttribute('href');
if (!prefetchedUrls.has(href)) {
prefetchedUrls.add(href);
// Prefetch the page content
const cacheKey = `page:${href}`;
window.upstashCache.preload(cacheKey, async () => {
const response = await fetch(href);
return response.text();
}, 3600);
}
});
});
// Cache API responses
const originalFetch = window.fetch;
window.fetch = function(url, options = {}) {
// Only cache GET requests to our API
if (typeof url === 'string' &&
url.startsWith('/api/') &&
(!options.method || options.method === 'GET') &&
!url.includes('/cache')) {
const cacheKey = `api:${url}`;
return window.upstashCache.get(cacheKey, () => originalFetch(url, options), 1800);
}
return originalFetch(url, options);
};
// Performance monitoring
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'navigation') {
console.log('Page Load Performance:', {
domContentLoaded: entry.domContentLoadedEventEnd - entry.domContentLoadedEventStart,
loadComplete: entry.loadEventEnd - entry.loadEventStart,
totalTime: entry.loadEventEnd - entry.fetchStart
});
// Report to analytics if needed
if (window.gtag) {
window.gtag('event', 'page_load_time', {
value: Math.round(entry.loadEventEnd - entry.fetchStart),
metric_name: 'load_time'
});
}
}
}
});
observer.observe({ entryTypes: ['navigation'] });
}
// Cache warming for critical resources
const criticalResources = [
'/odoo/',
'/odoo-ia/',
'/odoo-hosting/'
];
// Warm cache in idle time
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
criticalResources.forEach(resource => {
const cacheKey = `page:${resource}`;
window.upstashCache.preload(cacheKey, async () => {
const response = await fetch(resource);
return response.text();
}, 7200); // 2 hours for directory pages
});
});
}
});
// Export for use in other scripts
window.UpstashClient = UpstashClient;
// Log cache status
console.log('🚀 Upstash client-side caching initialized');
window.upstashCache.getStats().then(stats => {
console.log('📊 Cache stats:', stats);
});