UNPKG

@spfn/core

Version:

SPFN Framework Core - File-based routing, transactions, repository pattern

992 lines (982 loc) 29.6 kB
import pino from 'pino'; import { existsSync, mkdirSync, accessSync, constants, writeFileSync, unlinkSync, createWriteStream, statSync, readdirSync, renameSync } from 'fs'; import { join } from 'path'; // src/logger/adapters/pino.ts var PinoAdapter = class _PinoAdapter { logger; constructor(config) { this.logger = pino({ level: config.level, // 기본 필드 base: config.module ? { module: config.module } : void 0 }); } child(module) { const childLogger = new _PinoAdapter({ level: this.logger.level, module }); childLogger.logger = this.logger.child({ module }); return childLogger; } debug(message, context) { this.logger.debug(context || {}, message); } info(message, context) { this.logger.info(context || {}, message); } warn(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.logger.warn({ err: errorOrContext, ...context }, message); } else { this.logger.warn(errorOrContext || {}, message); } } error(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.logger.error({ err: errorOrContext, ...context }, message); } else { this.logger.error(errorOrContext || {}, message); } } fatal(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.logger.fatal({ err: errorOrContext, ...context }, message); } else { this.logger.fatal(errorOrContext || {}, message); } } async close() { } }; // src/logger/types.ts var LOG_LEVEL_PRIORITY = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }; // src/logger/formatters.ts var SENSITIVE_KEYS = [ "password", "passwd", "pwd", "secret", "token", "apikey", "api_key", "accesstoken", "access_token", "refreshtoken", "refresh_token", "authorization", "auth", "cookie", "session", "sessionid", "session_id", "privatekey", "private_key", "creditcard", "credit_card", "cardnumber", "card_number", "cvv", "ssn", "pin" ]; var MASKED_VALUE = "***MASKED***"; function isSensitiveKey(key) { const lowerKey = key.toLowerCase(); return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive)); } function maskSensitiveData(data) { if (data === null || data === void 0) { return data; } if (Array.isArray(data)) { return data.map((item) => maskSensitiveData(item)); } if (typeof data === "object") { const masked = {}; for (const [key, value] of Object.entries(data)) { if (isSensitiveKey(key)) { masked[key] = MASKED_VALUE; } else if (typeof value === "object" && value !== null) { masked[key] = maskSensitiveData(value); } else { masked[key] = value; } } return masked; } return data; } var COLORS = { reset: "\x1B[0m", bright: "\x1B[1m", dim: "\x1B[2m", // 로그 레벨 컬러 debug: "\x1B[36m", // cyan info: "\x1B[32m", // green warn: "\x1B[33m", // yellow error: "\x1B[31m", // red fatal: "\x1B[35m", // magenta // 추가 컬러 gray: "\x1B[90m" }; function formatTimestamp(date) { return date.toISOString(); } function formatTimestampHuman(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); const hours = String(date.getHours()).padStart(2, "0"); const minutes = String(date.getMinutes()).padStart(2, "0"); const seconds = String(date.getSeconds()).padStart(2, "0"); const ms = String(date.getMilliseconds()).padStart(3, "0"); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`; } function formatError(error) { const lines = []; lines.push(`${error.name}: ${error.message}`); if (error.stack) { const stackLines = error.stack.split("\n").slice(1); lines.push(...stackLines); } return lines.join("\n"); } function formatConsole(metadata, colorize = true) { const parts = []; const timestamp = formatTimestampHuman(metadata.timestamp); if (colorize) { parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`); } else { parts.push(`[${timestamp}]`); } if (metadata.module) { if (colorize) { parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`); } else { parts.push(`[module=${metadata.module}]`); } } if (metadata.context && Object.keys(metadata.context).length > 0) { Object.entries(metadata.context).forEach(([key, value]) => { const valueStr = typeof value === "string" ? value : String(value); if (colorize) { parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`); } else { parts.push(`[${key}=${valueStr}]`); } }); } const levelStr = metadata.level.toUpperCase(); if (colorize) { const color = COLORS[metadata.level]; parts.push(`${color}(${levelStr})${COLORS.reset}:`); } else { parts.push(`(${levelStr}):`); } if (colorize) { parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`); } else { parts.push(metadata.message); } let output = parts.join(" "); if (metadata.error) { output += "\n" + formatError(metadata.error); } return output; } function formatJSON(metadata) { const obj = { timestamp: formatTimestamp(metadata.timestamp), level: metadata.level, message: metadata.message }; if (metadata.module) { obj.module = metadata.module; } if (metadata.context) { obj.context = metadata.context; } if (metadata.error) { obj.error = { name: metadata.error.name, message: metadata.error.message, stack: metadata.error.stack }; } return JSON.stringify(obj); } // src/logger/logger.ts var Logger = class _Logger { config; module; constructor(config) { this.config = config; this.module = config.module; } /** * Get current log level */ get level() { return this.config.level; } /** * Create child logger (per module) */ child(module) { return new _Logger({ ...this.config, module }); } /** * Debug log */ debug(message, context) { this.log("debug", message, void 0, context); } /** * Info log */ info(message, context) { this.log("info", message, void 0, context); } warn(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.log("warn", message, errorOrContext, context); } else { this.log("warn", message, void 0, errorOrContext); } } error(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.log("error", message, errorOrContext, context); } else { this.log("error", message, void 0, errorOrContext); } } fatal(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.log("fatal", message, errorOrContext, context); } else { this.log("fatal", message, void 0, errorOrContext); } } /** * Log processing (internal) */ log(level, message, error, context) { if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level]) { return; } const metadata = { timestamp: /* @__PURE__ */ new Date(), level, message, module: this.module, error, // Mask sensitive information in context to prevent credential leaks context: context ? maskSensitiveData(context) : void 0 }; this.processTransports(metadata); } /** * Process Transports */ processTransports(metadata) { const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata)); Promise.all(promises).catch((error) => { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[Logger] Transport error: ${errorMessage} `); }); } /** * Transport log (error-safe) */ async safeTransportLog(transport, metadata) { try { await transport.log(metadata); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage} `); } } /** * Close all Transports */ async close() { const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close()); await Promise.all(closePromises); } }; // src/logger/transports/console.ts var ConsoleTransport = class { name = "console"; level; enabled; colorize; constructor(config) { this.level = config.level; this.enabled = config.enabled; this.colorize = config.colorize ?? true; } async log(metadata) { if (!this.enabled) { return; } if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) { return; } const message = formatConsole(metadata, this.colorize); if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") { console.error(message); } else { console.log(message); } } }; var FileTransport = class { name = "file"; level; enabled; logDir; maxFileSize; maxFiles; currentStream = null; currentFilename = null; constructor(config) { this.level = config.level; this.enabled = config.enabled; this.logDir = config.logDir; this.maxFileSize = config.maxFileSize ?? 10 * 1024 * 1024; this.maxFiles = config.maxFiles ?? 10; if (!existsSync(this.logDir)) { mkdirSync(this.logDir, { recursive: true }); } } async log(metadata) { if (!this.enabled) { return; } if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) { return; } const message = formatJSON(metadata); const filename = this.getLogFilename(metadata.timestamp); if (this.currentFilename !== filename) { await this.rotateStream(filename); await this.cleanOldFiles(); } else if (this.currentFilename) { await this.checkAndRotateBySize(); } if (this.currentStream) { return new Promise((resolve, reject) => { this.currentStream.write(message + "\n", "utf-8", (error) => { if (error) { process.stderr.write(`[FileTransport] Failed to write log: ${error.message} `); reject(error); } else { resolve(); } }); }); } } /** * 스트림 교체 (날짜 변경 시) */ async rotateStream(filename) { if (this.currentStream) { await this.closeStream(); } const filepath = join(this.logDir, filename); this.currentStream = createWriteStream(filepath, { flags: "a", // append mode encoding: "utf-8" }); this.currentFilename = filename; this.currentStream.on("error", (error) => { process.stderr.write(`[FileTransport] Stream error: ${error.message} `); this.currentStream = null; this.currentFilename = null; }); } /** * 현재 스트림 닫기 */ async closeStream() { if (!this.currentStream) { return; } return new Promise((resolve, reject) => { this.currentStream.end((error) => { if (error) { reject(error); } else { this.currentStream = null; this.currentFilename = null; resolve(); } }); }); } /** * 파일 크기 체크 및 크기 기반 로테이션 */ async checkAndRotateBySize() { if (!this.currentFilename) { return; } const filepath = join(this.logDir, this.currentFilename); if (!existsSync(filepath)) { return; } try { const stats = statSync(filepath); if (stats.size >= this.maxFileSize) { await this.rotateBySize(); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[FileTransport] Failed to check file size: ${errorMessage} `); } } /** * 크기 기반 로테이션 수행 * 예: 2025-01-01.log -> 2025-01-01.1.log, 2025-01-01.1.log -> 2025-01-01.2.log */ async rotateBySize() { if (!this.currentFilename) { return; } await this.closeStream(); const baseName = this.currentFilename.replace(/\.log$/, ""); const files = readdirSync(this.logDir); const relatedFiles = files.filter((file) => file.startsWith(baseName) && file.endsWith(".log")).sort().reverse(); for (const file of relatedFiles) { const match = file.match(/\.(\d+)\.log$/); if (match) { const oldNum = parseInt(match[1], 10); const newNum = oldNum + 1; const oldPath = join(this.logDir, file); const newPath2 = join(this.logDir, `${baseName}.${newNum}.log`); try { renameSync(oldPath, newPath2); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[FileTransport] Failed to rotate file: ${errorMessage} `); } } } const currentPath = join(this.logDir, this.currentFilename); const newPath = join(this.logDir, `${baseName}.1.log`); try { if (existsSync(currentPath)) { renameSync(currentPath, newPath); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[FileTransport] Failed to rotate current file: ${errorMessage} `); } await this.rotateStream(this.currentFilename); } /** * 오래된 로그 파일 정리 * maxFiles 개수를 초과하는 로그 파일 삭제 */ async cleanOldFiles() { try { if (!existsSync(this.logDir)) { return; } const files = readdirSync(this.logDir); const logFiles = files.filter((file) => file.endsWith(".log")).map((file) => { const filepath = join(this.logDir, file); const stats = statSync(filepath); return { file, mtime: stats.mtime }; }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); if (logFiles.length > this.maxFiles) { const filesToDelete = logFiles.slice(this.maxFiles); for (const { file } of filesToDelete) { const filepath = join(this.logDir, file); try { unlinkSync(filepath); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[FileTransport] Failed to delete old file "${file}": ${errorMessage} `); } } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); process.stderr.write(`[FileTransport] Failed to clean old files: ${errorMessage} `); } } /** * 날짜별 로그 파일명 생성 */ getLogFilename(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}.log`; } async close() { await this.closeStream(); } }; function isFileLoggingEnabled() { return process.env.LOGGER_FILE_ENABLED === "true"; } function getDefaultLogLevel() { const isProduction = process.env.NODE_ENV === "production"; const isDevelopment = process.env.NODE_ENV === "development"; if (isDevelopment) { return "debug"; } if (isProduction) { return "info"; } return "warn"; } function getConsoleConfig() { const isProduction = process.env.NODE_ENV === "production"; return { level: "debug", enabled: true, colorize: !isProduction // Dev: colored output, Production: plain text }; } function getFileConfig() { const isProduction = process.env.NODE_ENV === "production"; return { level: "info", enabled: isProduction, // File logging in production only logDir: process.env.LOG_DIR || "./logs", maxFileSize: 10 * 1024 * 1024, // 10MB maxFiles: 10 }; } function validateDirectoryWritable(dirPath) { if (!existsSync(dirPath)) { try { mkdirSync(dirPath, { recursive: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to create log directory "${dirPath}": ${errorMessage}`); } } try { accessSync(dirPath, constants.W_OK); } catch { throw new Error(`Log directory "${dirPath}" is not writable. Please check permissions.`); } const testFile = join(dirPath, ".logger-write-test"); try { writeFileSync(testFile, "test", "utf-8"); unlinkSync(testFile); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Cannot write to log directory "${dirPath}": ${errorMessage}`); } } function validateFileConfig() { if (!isFileLoggingEnabled()) { return; } const logDir = process.env.LOG_DIR; if (!logDir) { throw new Error( "LOG_DIR environment variable is required when LOGGER_FILE_ENABLED=true. Example: LOG_DIR=/var/log/myapp" ); } validateDirectoryWritable(logDir); } function validateSlackConfig() { const webhookUrl = process.env.SLACK_WEBHOOK_URL; if (!webhookUrl) { return; } if (!webhookUrl.startsWith("https://hooks.slack.com/")) { throw new Error( `Invalid SLACK_WEBHOOK_URL: "${webhookUrl}". Slack webhook URLs must start with "https://hooks.slack.com/"` ); } } function validateEmailConfig() { const smtpHost = process.env.SMTP_HOST; const smtpPort = process.env.SMTP_PORT; const emailFrom = process.env.EMAIL_FROM; const emailTo = process.env.EMAIL_TO; const hasAnyEmailConfig = smtpHost || smtpPort || emailFrom || emailTo; if (!hasAnyEmailConfig) { return; } const missingFields = []; if (!smtpHost) missingFields.push("SMTP_HOST"); if (!smtpPort) missingFields.push("SMTP_PORT"); if (!emailFrom) missingFields.push("EMAIL_FROM"); if (!emailTo) missingFields.push("EMAIL_TO"); if (missingFields.length > 0) { throw new Error( `Email transport configuration incomplete. Missing: ${missingFields.join(", ")}. Either set all required fields or remove all email configuration.` ); } const port = parseInt(smtpPort, 10); if (isNaN(port) || port < 1 || port > 65535) { throw new Error( `Invalid SMTP_PORT: "${smtpPort}". Must be a number between 1 and 65535.` ); } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(emailFrom)) { throw new Error(`Invalid EMAIL_FROM format: "${emailFrom}"`); } const recipients = emailTo.split(",").map((e) => e.trim()); for (const email of recipients) { if (!emailRegex.test(email)) { throw new Error(`Invalid email address in EMAIL_TO: "${email}"`); } } } function validateEnvironment() { const nodeEnv = process.env.NODE_ENV; if (!nodeEnv) { process.stderr.write( "[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\n" ); } } function validateConfig() { try { validateEnvironment(); validateFileConfig(); validateSlackConfig(); validateEmailConfig(); } catch (error) { if (error instanceof Error) { throw new Error(`[Logger] Configuration validation failed: ${error.message}`); } throw error; } } // src/logger/adapters/custom.ts function initializeTransports() { const transports = []; const consoleConfig = getConsoleConfig(); transports.push(new ConsoleTransport(consoleConfig)); const fileConfig = getFileConfig(); if (fileConfig.enabled) { transports.push(new FileTransport(fileConfig)); } return transports; } var CustomAdapter = class _CustomAdapter { logger; constructor(config) { this.logger = new Logger({ level: config.level, module: config.module, transports: initializeTransports() }); } child(module) { const adapter = new _CustomAdapter({ level: this.logger.level, module }); adapter.logger = this.logger.child(module); return adapter; } debug(message, context) { this.logger.debug(message, context); } info(message, context) { this.logger.info(message, context); } warn(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.logger.warn(message, errorOrContext, context); } else { this.logger.warn(message, errorOrContext); } } error(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.logger.error(message, errorOrContext, context); } else { this.logger.error(message, errorOrContext); } } fatal(message, errorOrContext, context) { if (errorOrContext instanceof Error) { this.logger.fatal(message, errorOrContext, context); } else { this.logger.fatal(message, errorOrContext); } } async close() { await this.logger.close(); } }; // src/logger/adapter-factory.ts function createAdapter(type) { const level = getDefaultLogLevel(); switch (type) { case "pino": return new PinoAdapter({ level }); case "custom": return new CustomAdapter({ level }); default: return new PinoAdapter({ level }); } } function getAdapterType() { const adapterEnv = process.env.LOGGER_ADAPTER; if (adapterEnv === "custom" || adapterEnv === "pino") { return adapterEnv; } return "pino"; } function initializeLogger() { validateConfig(); return createAdapter(getAdapterType()); } var logger = initializeLogger(); // src/cache/cache-factory.ts var cacheLogger = logger.child("cache"); function hasCacheConfig() { return !!// Modern (Valkey/Cache) (process.env.VALKEY_URL || process.env.CACHE_URL || process.env.VALKEY_WRITE_URL || process.env.VALKEY_READ_URL || process.env.CACHE_WRITE_URL || process.env.CACHE_READ_URL || process.env.VALKEY_SENTINEL_HOSTS || process.env.VALKEY_CLUSTER_NODES || // Legacy (Redis - backward compatibility) process.env.REDIS_URL || process.env.REDIS_WRITE_URL || process.env.REDIS_READ_URL || process.env.REDIS_SENTINEL_HOSTS || process.env.REDIS_CLUSTER_NODES); } function getEnv(valkeyKey, cacheKey, redisKey) { return process.env[valkeyKey] || process.env[cacheKey] || process.env[redisKey]; } function createClient(RedisClient, url) { const options = {}; if (url.startsWith("rediss://") || url.startsWith("valkeys://")) { const rejectUnauthorized = getEnv( "VALKEY_TLS_REJECT_UNAUTHORIZED", "CACHE_TLS_REJECT_UNAUTHORIZED", "REDIS_TLS_REJECT_UNAUTHORIZED" ); options.tls = { rejectUnauthorized: rejectUnauthorized !== "false" }; } return new RedisClient(url, options); } async function createCacheFromEnv() { if (!hasCacheConfig()) { cacheLogger.info("No cache configuration found - running without cache"); return { write: void 0, read: void 0 }; } try { const ioredis = await import('ioredis'); const RedisClient = ioredis.default; const singleUrl = getEnv("VALKEY_URL", "CACHE_URL", "REDIS_URL"); const writeUrl = getEnv("VALKEY_WRITE_URL", "CACHE_WRITE_URL", "REDIS_WRITE_URL"); const readUrl = getEnv("VALKEY_READ_URL", "CACHE_READ_URL", "REDIS_READ_URL"); const clusterNodes = getEnv("VALKEY_CLUSTER_NODES", "CACHE_CLUSTER_NODES", "REDIS_CLUSTER_NODES"); const sentinelHosts = getEnv("VALKEY_SENTINEL_HOSTS", "CACHE_SENTINEL_HOSTS", "REDIS_SENTINEL_HOSTS"); const masterName = getEnv("VALKEY_MASTER_NAME", "CACHE_MASTER_NAME", "REDIS_MASTER_NAME"); const password = getEnv("VALKEY_PASSWORD", "CACHE_PASSWORD", "REDIS_PASSWORD"); if (singleUrl && !writeUrl && !readUrl && !clusterNodes) { const client = createClient(RedisClient, singleUrl); cacheLogger.debug("Created single cache instance", { url: singleUrl.replace(/:[^:@]+@/, ":***@") }); return { write: client, read: client }; } if (writeUrl && readUrl) { const write = createClient(RedisClient, writeUrl); const read = createClient(RedisClient, readUrl); cacheLogger.debug("Created master-replica cache instances"); return { write, read }; } if (sentinelHosts && masterName) { const sentinels = sentinelHosts.split(",").map((host) => { const [hostname, port] = host.trim().split(":"); return { host: hostname, port: Number(port) || 26379 }; }); const options = { sentinels, name: masterName, password }; const client = new RedisClient(options); cacheLogger.debug("Created sentinel cache instance", { masterName, sentinels: sentinels.length }); return { write: client, read: client }; } if (clusterNodes) { const nodes = clusterNodes.split(",").map((node) => { const [host, port] = node.trim().split(":"); return { host, port: Number(port) || 6379 }; }); const clusterOptions = { redisOptions: { password } }; const cluster = new RedisClient.Cluster(nodes, clusterOptions); cacheLogger.debug("Created cluster cache instance", { nodes: nodes.length }); return { write: cluster, read: cluster }; } if (singleUrl) { const client = createClient(RedisClient, singleUrl); cacheLogger.debug("Created cache instance (fallback)", { url: singleUrl.replace(/:[^:@]+@/, ":***@") }); return { write: client, read: client }; } cacheLogger.info("No valid cache configuration found - running without cache"); return { write: void 0, read: void 0 }; } catch (error) { if (error instanceof Error) { if (error.message.includes("Cannot find module")) { cacheLogger.warn( "Cache client library not installed", error, { suggestion: "Install ioredis to enable cache: pnpm install ioredis", mode: "disabled" } ); } else { cacheLogger.warn( "Failed to create cache client", error, { mode: "disabled" } ); } } else { cacheLogger.warn( "Failed to create cache client", { error: String(error), mode: "disabled" } ); } return { write: void 0, read: void 0 }; } } async function createSingleCacheFromEnv() { const { write } = await createCacheFromEnv(); return write; } // src/cache/cache-manager.ts var cacheLogger2 = logger.child("cache"); var writeInstance; var readInstance; var isDisabled = false; function getCache() { return writeInstance; } function getCacheRead() { return readInstance ?? writeInstance; } function isCacheDisabled() { return isDisabled; } function setCache(write, read) { writeInstance = write; readInstance = read ?? write; isDisabled = !write; } async function initCache() { if (writeInstance) { return { write: writeInstance, read: readInstance, disabled: isDisabled }; } const { write, read } = await createCacheFromEnv(); if (write) { try { await write.ping(); if (read && read !== write) { await read.ping(); } writeInstance = write; readInstance = read; isDisabled = false; const hasReplica = read && read !== write; cacheLogger2.info( hasReplica ? "Cache connected (Master-Replica)" : "Cache connected", { mode: "enabled" } ); return { write: writeInstance, read: readInstance, disabled: false }; } catch (error) { cacheLogger2.error( "Cache connection failed - running in disabled mode", error instanceof Error ? error : new Error(String(error)), { mode: "disabled" } ); try { await write.quit(); if (read && read !== write) { await read.quit(); } } catch { } isDisabled = true; return { write: void 0, read: void 0, disabled: true }; } } isDisabled = true; cacheLogger2.info("Cache disabled - no configuration or library not installed", { mode: "disabled" }); return { write: void 0, read: void 0, disabled: true }; } async function closeCache() { if (isDisabled) { cacheLogger2.debug("Cache already disabled, nothing to close"); return; } const closePromises = []; if (writeInstance) { closePromises.push( writeInstance.quit().catch((err) => { cacheLogger2.error("Error closing cache write instance", err); }) ); } if (readInstance && readInstance !== writeInstance) { closePromises.push( readInstance.quit().catch((err) => { cacheLogger2.error("Error closing cache read instance", err); }) ); } await Promise.all(closePromises); writeInstance = void 0; readInstance = void 0; isDisabled = true; cacheLogger2.info("Cache connections closed", { mode: "disabled" }); } function getCacheInfo() { return { hasWrite: !!writeInstance, hasRead: !!readInstance, isReplica: !!(readInstance && readInstance !== writeInstance), disabled: isDisabled }; } var getRedis = getCache; var getRedisRead = getCacheRead; var setRedis = setCache; var initRedis = initCache; var closeRedis = closeCache; var getRedisInfo = getCacheInfo; export { closeCache, closeRedis, createCacheFromEnv, createCacheFromEnv as createRedisFromEnv, createSingleCacheFromEnv, createSingleCacheFromEnv as createSingleRedisFromEnv, getCache, getCacheInfo, getCacheRead, getRedis, getRedisInfo, getRedisRead, initCache, initRedis, isCacheDisabled, setCache, setRedis }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map