okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
131 lines • 3.67 kB
JavaScript
/**
* In-memory cache implementation
*/
export class MemoryCache {
cache = new Map();
tagIndex = new Map();
maxSize;
eventBus;
constructor(options = {}) {
this.maxSize = options.maxSize || 1000;
if (options.eventBus) {
this.eventBus = options.eventBus;
}
}
async get(key) {
const entry = this.cache.get(key);
if (!entry) {
this.eventBus?.emit('cache:miss', { key });
return undefined;
}
if (entry.expires < Date.now()) {
await this.delete(key);
this.eventBus?.emit('cache:miss', { key });
return undefined;
}
this.eventBus?.emit('cache:hit', { key });
return entry.value;
}
async set(key, value, options) {
// Evict old entries if at capacity
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
const oldestKey = this.cache.keys().next().value;
if (oldestKey) {
await this.delete(oldestKey);
}
}
const ttl = options?.ttl || 300; // Default 5 minutes
const expires = Date.now() + ttl * 1000;
const entry = {
value,
expires,
...(options?.tags && { tags: options.tags }),
};
this.cache.set(key, entry);
// Update tag index
if (options?.tags) {
for (const tag of options.tags) {
if (!this.tagIndex.has(tag)) {
this.tagIndex.set(tag, new Set());
}
this.tagIndex.get(tag).add(key);
}
}
this.eventBus?.emit('cache:set', { key, ttl });
}
async delete(key) {
const entry = this.cache.get(key);
if (!entry) {
return false;
}
// Remove from tag index
if (entry.tags) {
for (const tag of entry.tags) {
const keys = this.tagIndex.get(tag);
if (keys) {
keys.delete(key);
if (keys.size === 0) {
this.tagIndex.delete(tag);
}
}
}
}
return this.cache.delete(key);
}
async has(key) {
const entry = this.cache.get(key);
if (!entry) {
return false;
}
if (entry.expires < Date.now()) {
await this.delete(key);
return false;
}
return true;
}
async clear() {
this.cache.clear();
this.tagIndex.clear();
this.eventBus?.emit('cache:clear', {});
}
async clearByTag(tag) {
const keys = this.tagIndex.get(tag);
if (!keys) {
return;
}
for (const key of keys) {
await this.delete(key);
}
this.eventBus?.emit('cache:clear', { pattern: `tag:${tag}` });
}
async size() {
// Clean up expired entries first
const now = Date.now();
const expiredKeys = [];
for (const [key, entry] of this.cache) {
if (entry.expires < now) {
expiredKeys.push(key);
}
}
for (const key of expiredKeys) {
await this.delete(key);
}
return this.cache.size;
}
/**
* Get all keys (for debugging)
*/
async keys() {
return Array.from(this.cache.keys());
}
/**
* Get cache statistics
*/
getStats() {
return {
size: this.cache.size,
tags: this.tagIndex.size,
};
}
}
//# sourceMappingURL=memory-cache.js.map