Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m11s
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m4s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 31s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 29s
Verify Deployment / Verify Deployment (push) Failing after 57s
Relay router, reserve system, oracle publisher, token-aggregation compliance middleware, and Monad deployment scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
2.7 KiB
TypeScript
89 lines
2.7 KiB
TypeScript
import { Request, Response, NextFunction } from 'express';
|
|
|
|
interface CacheEntry {
|
|
data: unknown;
|
|
expiresAt: number;
|
|
}
|
|
|
|
const cache = new Map<string, CacheEntry>();
|
|
const DEFAULT_TTL = 60 * 1000; // 1 minute
|
|
|
|
/** Never cache generic API error envelopes (avoids poisoning cache if status/body ever disagree). */
|
|
function looksLikeGenericErrorPayload(body: unknown): boolean {
|
|
if (body == null || typeof body !== 'object' || Array.isArray(body)) return false;
|
|
const o = body as Record<string, unknown>;
|
|
if (typeof o.error !== 'string') return false;
|
|
// Success shapes we must not treat as errors
|
|
if ('pools' in o || 'tokens' in o || 'data' in o || 'chains' in o || 'tree' in o || 'quote' in o) return false;
|
|
if (o.status === 'healthy') return false;
|
|
return true;
|
|
}
|
|
|
|
function buildCacheKey(req: Request): string {
|
|
const base = `${req.method}:${req.originalUrl}`;
|
|
if (req.method !== 'POST' && req.method !== 'PUT' && req.method !== 'PATCH') {
|
|
return base;
|
|
}
|
|
try {
|
|
const body = req.body;
|
|
if (body == null) {
|
|
return base;
|
|
}
|
|
const serialized = typeof body === 'string' ? body : JSON.stringify(body);
|
|
if (!serialized || serialized === '{}' || serialized === '[]') {
|
|
return base;
|
|
}
|
|
return `${base}:${serialized}`;
|
|
} catch {
|
|
return base;
|
|
}
|
|
}
|
|
|
|
export function cacheMiddleware(ttl: number = DEFAULT_TTL) {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
const bypassCache =
|
|
req.query.refresh === '1' ||
|
|
req.query.noCache === '1' ||
|
|
/\bno-cache\b|\bno-store\b/i.test(req.header('cache-control') || '');
|
|
const key = buildCacheKey(req);
|
|
const cached = bypassCache ? undefined : cache.get(key);
|
|
|
|
if (cached && cached.expiresAt > Date.now()) {
|
|
res.setHeader('X-Token-Aggregation-Cache', 'hit');
|
|
return res.json(cached.data);
|
|
}
|
|
|
|
res.setHeader('X-Token-Aggregation-Cache', bypassCache ? 'bypass' : 'miss');
|
|
|
|
// Store original json method
|
|
const originalJson = res.json.bind(res);
|
|
|
|
// Override json method to cache response
|
|
res.json = function (body: unknown) {
|
|
// Only cache successful payloads. Otherwise a 500 body gets replayed on cache hit with HTTP 200.
|
|
const okStatus = res.statusCode >= 200 && res.statusCode < 300;
|
|
if (!bypassCache && okStatus && !looksLikeGenericErrorPayload(body)) {
|
|
cache.set(key, {
|
|
data: body,
|
|
expiresAt: Date.now() + ttl,
|
|
});
|
|
}
|
|
return originalJson(body);
|
|
};
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
export function clearCache(): void {
|
|
cache.clear();
|
|
}
|
|
|
|
export function clearCacheForPattern(pattern: string): void {
|
|
for (const key of cache.keys()) {
|
|
if (key.includes(pattern)) {
|
|
cache.delete(key);
|
|
}
|
|
}
|
|
}
|