| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | } |
| | }; |
| |
|