| |
| |
| |
| |
| |
| |
| |
| const KeyvRedis = require('@keyv/redis').default as typeof import('@keyv/redis').default; |
| import { Keyv } from 'keyv'; |
| import createMemoryStore from 'memorystore'; |
| import { RedisStore } from 'rate-limit-redis'; |
| import { Time } from 'librechat-data-provider'; |
| import { logger } from '@librechat/data-schemas'; |
| import session, { MemoryStore } from 'express-session'; |
| import { RedisStore as ConnectRedis } from 'connect-redis'; |
| import type { SendCommandFn } from 'rate-limit-redis'; |
| import { keyvRedisClient, ioredisClient } from './redisClients'; |
| import { cacheConfig } from './cacheConfig'; |
| import { violationFile } from './keyvFiles'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| export const standardCache = (namespace: string, ttl?: number, fallbackStore?: object): Keyv => { |
| if (keyvRedisClient && !cacheConfig.FORCED_IN_MEMORY_CACHE_NAMESPACES?.includes(namespace)) { |
| try { |
| const keyvRedis = new KeyvRedis(keyvRedisClient); |
| const cache = new Keyv(keyvRedis, { namespace, ttl }); |
| keyvRedis.namespace = cacheConfig.REDIS_KEY_PREFIX; |
| keyvRedis.keyPrefixSeparator = cacheConfig.GLOBAL_PREFIX_SEPARATOR; |
|
|
| cache.on('error', (err) => { |
| logger.error(`Cache error in namespace ${namespace}:`, err); |
| }); |
|
|
| return cache; |
| } catch (err) { |
| logger.error(`Failed to create Redis cache for namespace ${namespace}:`, err); |
| throw err; |
| } |
| } |
| if (fallbackStore) { |
| return new Keyv({ store: fallbackStore, namespace, ttl }); |
| } |
| return new Keyv({ namespace, ttl }); |
| }; |
|
|
| /** |
| * Creates a cache instance for storing violation data. |
| * Uses a file-based fallback store if Redis is not enabled. |
| * @param namespace - The cache namespace for violations. |
| * @param ttl - Time to live for cache entries. |
| * @returns Cache instance for violations. |
| */ |
| export const violationCache = (namespace: string, ttl?: number): Keyv => { |
| return standardCache(`violations:${namespace}`, ttl, violationFile); |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| export const sessionCache = (namespace: string, ttl?: number): MemoryStore | ConnectRedis => { |
| namespace = namespace.endsWith(':') ? namespace : `${namespace}:`; |
| if (!cacheConfig.USE_REDIS) { |
| const MemoryStore = createMemoryStore(session); |
| return new MemoryStore({ ttl, checkPeriod: Time.ONE_DAY }); |
| } |
| const store = new ConnectRedis({ client: ioredisClient, ttl, prefix: namespace }); |
| if (ioredisClient) { |
| ioredisClient.on('error', (err) => { |
| logger.error(`Session store Redis error for namespace ${namespace}:`, err); |
| }); |
| } |
| return store; |
| }; |
|
|
| |
| |
| |
| |
| |
| export const limiterCache = (prefix: string): RedisStore | undefined => { |
| if (!prefix) { |
| throw new Error('prefix is required'); |
| } |
| if (!cacheConfig.USE_REDIS) { |
| return undefined; |
| } |
| |
| prefix = prefix.endsWith(':') ? prefix : `${prefix}:`; |
|
|
| try { |
| const sendCommand: SendCommandFn = (async (...args: string[]) => { |
| if (ioredisClient == null) { |
| throw new Error('Redis client not available'); |
| } |
| try { |
| return await ioredisClient.call(args[0], ...args.slice(1)); |
| } catch (err) { |
| logger.error('Redis command execution failed:', err); |
| throw err; |
| } |
| }) as SendCommandFn; |
| return new RedisStore({ sendCommand, prefix }); |
| } catch (err) { |
| logger.error(`Failed to create Redis rate limiter for prefix ${prefix}:`, err); |
| return undefined; |
| } |
| }; |
|
|