Files
michaelschiemer/resources/js/modules/livecomponent/RequestDeduplicator.js
Michael Schiemer 36ef2a1e2c
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
fix: Gitea Traefik routing and connection pool optimization
- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
2025-11-09 14:46:15 +01:00

181 lines
4.9 KiB
JavaScript

/**
* Request Deduplication for LiveComponents
*
* Prevents duplicate requests for the same action with the same parameters.
* Useful for preventing race conditions and reducing server load.
*/
export class RequestDeduplicator {
constructor() {
this.pendingRequests = new Map();
this.requestCache = new Map();
this.cacheTimeout = 1000; // 1 second cache
}
/**
* Generate request key for deduplication
*
* @param {string} componentId - Component ID
* @param {string} method - Action method name
* @param {Object} params - Action parameters
* @returns {string} Request key
*/
generateKey(componentId, method, params) {
// Sort params for consistent key generation
const sortedParams = Object.keys(params)
.sort()
.reduce((acc, key) => {
acc[key] = params[key];
return acc;
}, {});
const paramsString = JSON.stringify(sortedParams);
return `${componentId}:${method}:${paramsString}`;
}
/**
* Check if request is already pending
*
* @param {string} componentId - Component ID
* @param {string} method - Action method name
* @param {Object} params - Action parameters
* @returns {Promise|null} Pending request promise or null
*/
getPendingRequest(componentId, method, params) {
const key = this.generateKey(componentId, method, params);
return this.pendingRequests.get(key) || null;
}
/**
* Register pending request
*
* @param {string} componentId - Component ID
* @param {string} method - Action method name
* @param {Object} params - Action parameters
* @param {Promise} promise - Request promise
* @returns {Promise} Request promise
*/
registerPendingRequest(componentId, method, params, promise) {
const key = this.generateKey(componentId, method, params);
// Store pending request
this.pendingRequests.set(key, promise);
// Clean up when request completes
promise
.then(() => {
this.pendingRequests.delete(key);
})
.catch(() => {
this.pendingRequests.delete(key);
});
return promise;
}
/**
* Check if request result is cached
*
* @param {string} componentId - Component ID
* @param {string} method - Action method name
* @param {Object} params - Action parameters
* @returns {Object|null} Cached result or null
*/
getCachedResult(componentId, method, params) {
const key = this.generateKey(componentId, method, params);
const cached = this.requestCache.get(key);
if (!cached) {
return null;
}
// Check if cache is still valid
const now = Date.now();
if (now - cached.timestamp > this.cacheTimeout) {
this.requestCache.delete(key);
return null;
}
return cached.result;
}
/**
* Cache request result
*
* @param {string} componentId - Component ID
* @param {string} method - Action method name
* @param {Object} params - Action parameters
* @param {Object} result - Request result
*/
cacheResult(componentId, method, params, result) {
const key = this.generateKey(componentId, method, params);
this.requestCache.set(key, {
result,
timestamp: Date.now()
});
// Clean up old cache entries
this.cleanupCache();
}
/**
* Clean up expired cache entries
*/
cleanupCache() {
const now = Date.now();
for (const [key, cached] of this.requestCache.entries()) {
if (now - cached.timestamp > this.cacheTimeout) {
this.requestCache.delete(key);
}
}
}
/**
* Clear all pending requests
*/
clearPendingRequests() {
this.pendingRequests.clear();
}
/**
* Clear all cached results
*/
clearCache() {
this.requestCache.clear();
}
/**
* Clear requests and cache for specific component
*
* @param {string} componentId - Component ID
*/
clearComponent(componentId) {
// Clear pending requests
for (const [key] of this.pendingRequests.entries()) {
if (key.startsWith(`${componentId}:`)) {
this.pendingRequests.delete(key);
}
}
// Clear cache
for (const [key] of this.requestCache.entries()) {
if (key.startsWith(`${componentId}:`)) {
this.requestCache.delete(key);
}
}
}
/**
* Get statistics
*
* @returns {Object} Statistics
*/
getStats() {
return {
pendingRequests: this.pendingRequests.size,
cachedResults: this.requestCache.size
};
}
}