Skip to main content
Glama
redis.ts7.1 kB
import Redis from "ioredis"; // ============================================ // Configuration // ============================================ const redis = new Redis({ host: process.env.REDIS_HOST || "localhost", port: parseInt(process.env.REDIS_PORT || "6379"), password: process.env.REDIS_PASSWORD, db: parseInt(process.env.REDIS_DB || "0"), retryStrategy: (times) => Math.min(times * 50, 2000), }); redis.on("connect", () => console.log("Redis connected")); redis.on("error", (err) => console.error("Redis error:", err)); // ============================================ // Basic Operations // ============================================ export async function set( key: string, value: unknown, ttlSeconds?: number, ): Promise<void> { const serialized = JSON.stringify(value); if (ttlSeconds) { await redis.setex(key, ttlSeconds, serialized); } else { await redis.set(key, serialized); } } export async function get<T>(key: string): Promise<T | null> { const value = await redis.get(key); return value ? JSON.parse(value) : null; } export async function del(key: string): Promise<boolean> { const result = await redis.del(key); return result > 0; } export async function exists(key: string): Promise<boolean> { const result = await redis.exists(key); return result === 1; } // ============================================ // Cache Helper // ============================================ export async function cached<T>( key: string, ttlSeconds: number, fetchFn: () => Promise<T>, ): Promise<T> { const cached = await get<T>(key); if (cached !== null) { return cached; } const fresh = await fetchFn(); await set(key, fresh, ttlSeconds); return fresh; } // ============================================ // Rate Limiting // ============================================ export interface RateLimitResult { allowed: boolean; remaining: number; resetIn: number; } export async function rateLimit( key: string, maxRequests: number, windowSeconds: number, ): Promise<RateLimitResult> { const current = await redis.incr(key); if (current === 1) { await redis.expire(key, windowSeconds); } const ttl = await redis.ttl(key); return { allowed: current <= maxRequests, remaining: Math.max(0, maxRequests - current), resetIn: ttl > 0 ? ttl : windowSeconds, }; } // ============================================ // Session Store // ============================================ export interface Session { userId: string; data: Record<string, unknown>; createdAt: number; } const SESSION_PREFIX = "session:"; const SESSION_TTL = 86400; // 24 hours export const sessionStore = { async create( sessionId: string, userId: string, data: Record<string, unknown> = {}, ): Promise<void> { const session: Session = { userId, data, createdAt: Date.now(), }; await set(`${SESSION_PREFIX}${sessionId}`, session, SESSION_TTL); }, async get(sessionId: string): Promise<Session | null> { return get<Session>(`${SESSION_PREFIX}${sessionId}`); }, async update( sessionId: string, data: Record<string, unknown>, ): Promise<void> { const session = await this.get(sessionId); if (session) { session.data = { ...session.data, ...data }; await set(`${SESSION_PREFIX}${sessionId}`, session, SESSION_TTL); } }, async destroy(sessionId: string): Promise<boolean> { return del(`${SESSION_PREFIX}${sessionId}`); }, async refresh(sessionId: string): Promise<void> { await redis.expire(`${SESSION_PREFIX}${sessionId}`, SESSION_TTL); }, }; // ============================================ // Pub/Sub // ============================================ const subscriber = redis.duplicate(); type MessageHandler = (message: string, channel: string) => void; const handlers = new Map<string, MessageHandler[]>(); subscriber.on("message", (channel, message) => { const channelHandlers = handlers.get(channel) || []; channelHandlers.forEach((handler) => handler(message, channel)); }); export async function subscribe( channel: string, handler: MessageHandler, ): Promise<void> { const existing = handlers.get(channel) || []; handlers.set(channel, [...existing, handler]); if (existing.length === 0) { await subscriber.subscribe(channel); } } export async function publish( channel: string, message: unknown, ): Promise<number> { const serialized = typeof message === "string" ? message : JSON.stringify(message); return redis.publish(channel, serialized); } // ============================================ // Queue (Simple Job Queue) // ============================================ export interface Job<T = unknown> { id: string; type: string; data: T; createdAt: number; attempts: number; } const QUEUE_PREFIX = "queue:"; export const queue = { async push<T>(name: string, type: string, data: T): Promise<string> { const job: Job<T> = { id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, type, data, createdAt: Date.now(), attempts: 0, }; await redis.lpush(`${QUEUE_PREFIX}${name}`, JSON.stringify(job)); return job.id; }, async pop<T>(name: string, timeout = 0): Promise<Job<T> | null> { const result = await redis.brpop(`${QUEUE_PREFIX}${name}`, timeout); if (!result) return null; const job = JSON.parse(result[1]) as Job<T>; job.attempts++; return job; }, async size(name: string): Promise<number> { return redis.llen(`${QUEUE_PREFIX}${name}`); }, async clear(name: string): Promise<void> { await redis.del(`${QUEUE_PREFIX}${name}`); }, }; // ============================================ // Leaderboard // ============================================ export const leaderboard = { async addScore(name: string, memberId: string, score: number): Promise<void> { await redis.zadd(name, score, memberId); }, async incrementScore( name: string, memberId: string, increment: number, ): Promise<number> { return redis.zincrby(name, increment, memberId); }, async getTop( name: string, count = 10, ): Promise<Array<{ memberId: string; score: number }>> { const results = await redis.zrevrange(name, 0, count - 1, "WITHSCORES"); const entries: Array<{ memberId: string; score: number }> = []; for (let i = 0; i < results.length; i += 2) { entries.push({ memberId: results[i], score: parseFloat(results[i + 1]), }); } return entries; }, async getRank(name: string, memberId: string): Promise<number | null> { const rank = await redis.zrevrank(name, memberId); return rank !== null ? rank + 1 : null; }, async getScore(name: string, memberId: string): Promise<number | null> { const score = await redis.zscore(name, memberId); return score !== null ? parseFloat(score) : null; }, }; // ============================================ // Exports // ============================================ export { redis }; export default redis;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/millsydotdev/Code-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server