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
- 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
181 lines
4.9 KiB
JavaScript
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
|
|
};
|
|
}
|
|
}
|
|
|