mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
121 lines • 3.97 kB
JavaScript
import { MCard } from '../../../model/MCard.js';
// ============ Rate Limiting ============
export class RateLimiter {
limits;
defaultLimit;
constructor(tokensPerSecond = 10, maxBurst = 20) {
this.limits = new Map();
this.defaultLimit = { tokensPerSecond, maxBurst };
}
/**
* Check if request allowed. Consumes a token if allowed.
*/
check(domain) {
const now = Date.now();
const bucket = this.limits.get(domain) || {
tokens: this.defaultLimit.maxBurst,
lastRefill: now
};
// Refill tokens based on time elapsed
const elapsed = (now - bucket.lastRefill) / 1000;
const refill = elapsed * this.defaultLimit.tokensPerSecond;
bucket.tokens = Math.min(this.defaultLimit.maxBurst, bucket.tokens + refill);
bucket.lastRefill = now;
if (bucket.tokens >= 1) {
bucket.tokens -= 1;
this.limits.set(domain, bucket);
return true;
}
this.limits.set(domain, bucket);
return false;
}
/**
* Wait until rate limit allows request
*/
async waitFor(domain) {
while (!this.check(domain)) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
// ============ Response Caching ============
export class NetworkCache {
memoryCache;
collection;
constructor(collection) {
this.memoryCache = new Map();
this.collection = collection;
}
/**
* Generate cache key from request config
*/
static generateKey(method, url, body) {
const keyData = `${method}:${url}:${body || ''}`;
// Simple hash for cache key
let hash = 0;
for (let i = 0; i < keyData.length; i++) {
const char = keyData.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return `cache_${Math.abs(hash).toString(36)}`;
}
/**
* Get cached response if valid
*/
get(cacheKey) {
const cached = this.memoryCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) {
return { ...cached.response, timing: { ...cached.response.timing, total: 0 } };
}
if (cached) {
this.memoryCache.delete(cacheKey);
}
return null;
}
/**
* Cache a response with TTL
*/
async set(cacheKey, response, ttlSeconds, persist = false) {
this.memoryCache.set(cacheKey, {
response,
expiresAt: Date.now() + (ttlSeconds * 1000)
});
if (persist && this.collection) {
const cacheEntry = {
key: cacheKey,
response,
expiresAt: Date.now() + (ttlSeconds * 1000),
cachedAt: new Date().toISOString()
};
const card = await MCard.create(JSON.stringify(cacheEntry));
await this.collection.add(card);
}
}
}
// ============ Retry Logic Helper ============
export class RetryUtils {
static calculateBackoffDelay(attempt, strategy, baseDelay, maxDelay) {
let delay;
switch (strategy) {
case 'exponential':
delay = baseDelay * Math.pow(2, attempt - 1);
break;
case 'linear':
delay = baseDelay * attempt;
break;
case 'constant':
default:
delay = baseDelay;
}
const jitter = delay * 0.1 * (Math.random() * 2 - 1);
delay = Math.round(delay + jitter);
return maxDelay ? Math.min(delay, maxDelay) : delay;
}
static shouldRetryStatus(status, retryOn) {
const defaultRetryStatuses = [408, 429, 500, 502, 503, 504];
const retryStatuses = retryOn || defaultRetryStatuses;
return retryStatuses.includes(status);
}
}
//# sourceMappingURL=NetworkInfrastructure.js.map