UNPKG

@analog-tools/auth

Version:

Authentication module for AnalogJS applications

1 lines 159 kB
{"version":3,"file":"index.cjs","sources":["../../../packages/logger/src/lib/logger.types.ts","../../../packages/logger/src/lib/error-serialization/error-serializer.ts","../../../packages/logger/src/lib/logger.config.ts","../../../packages/logger/src/lib/logger-style-engine.ts","../../../packages/logger/src/lib/errors.ts","../../../packages/logger/src/lib/deduplication/deduplication.types.ts","../../../packages/logger/src/lib/deduplication/deduplicator.ts","../../../packages/logger/src/lib/logger.service.ts","../../../packages/inject/src/lib/service-registry.ts","../../../packages/inject/src/lib/inject.util.ts","../../../packages/auth/src/server/services/session.service.ts","../../../packages/auth/src/server/services/oauth-authentication.service.ts","../../../packages/auth/src/server/routes/authenticated.ts","../../../packages/auth/src/server/routes/callback.ts","../../uncrypto/dist/crypto.web.mjs","../../../packages/auth/src/server/routes/login.ts","../../../packages/auth/src/server/routes/logout.ts","../../../packages/auth/src/server/routes/protected-data.ts","../../../packages/auth/src/server/routes/refresh-tokens.ts","../../../packages/auth/src/server/routes/user.ts","../../../packages/auth/src/server/functions/registerRoutes.ts","../../../packages/auth/src/server/utils/getLastPathSegment.ts","../../../packages/auth/src/server/functions/handleAuthRoute.ts","../../../packages/auth/src/server/functions/checkAuthentication.ts","../../../packages/auth/src/server/functions/useAnalogAuthMiddleware.ts","../../../packages/auth/src/server/functions/useAnalogAuth.ts"],"sourcesContent":["/**\n * Enum for dreamy color names with ANSI codes for terminal\n * Each color has 3 shades and a background variant\n */\nexport enum ColorEnum {\n // Blue shades\n SkyBlue = '\\x1b[94m', // Bright blue\n OceanBlue = '\\x1b[34m', // Standard blue\n MidnightBlue = '\\x1b[38;5;17m', // Deep blue (unique)\n SkyBlueBg = '\\x1b[104m',\n OceanBlueBg = '\\x1b[44m',\n MidnightBlueBg = '\\x1b[48;5;17m',\n\n // Green shades\n MintGreen = '\\x1b[92m', // Bright green\n ForestGreen = '\\x1b[32m', // Standard green\n EmeraldGreen = '\\x1b[38;5;28m', // Deep green (unique)\n MintGreenBg = '\\x1b[102m',\n ForestGreenBg = '\\x1b[42m',\n EmeraldGreenBg = '\\x1b[48;5;28m',\n\n // Yellow shades\n LemonYellow = '\\x1b[93m', // Bright yellow\n SunflowerYellow = '\\x1b[33m', // Standard yellow\n GoldYellow = '\\x1b[38;5;220m', // Gold (unique)\n LemonYellowBg = '\\x1b[103m',\n SunflowerYellowBg = '\\x1b[43m',\n GoldYellowBg = '\\x1b[48;5;220m',\n\n // Red shades\n RoseRed = '\\x1b[91m', // Bright red\n FireRed = '\\x1b[31m', // Standard red\n BurgundyRed = '\\x1b[38;5;88m',// Deep red (unique)\n RoseRedBg = '\\x1b[101m',\n FireRedBg = '\\x1b[41m',\n BurgundyRedBg = '\\x1b[48;5;88m',\n\n // Purple shades\n LavenderPurple = '\\x1b[95m', // Bright purple\n RoyalPurple = '\\x1b[38;5;93m', // Medium purple (unique)\n DeepPurple = '\\x1b[38;5;54m', // Deep purple (unique)\n LavenderPurpleBg = '\\x1b[105m',\n RoyalPurpleBg = '\\x1b[48;5;93m',\n DeepPurpleBg = '\\x1b[48;5;54m',\n\n // Orange shades\n PeachOrange = '\\x1b[38;5;215m', // Light orange\n TangerineOrange = '\\x1b[38;5;208m', // Standard orange\n AmberOrange = '\\x1b[38;5;214m', // Deep orange\n PeachOrangeBg = '\\x1b[48;5;215m',\n TangerineOrangeBg = '\\x1b[48;5;208m',\n AmberOrangeBg = '\\x1b[48;5;214m',\n\n // Gray shades\n SilverGray = '\\x1b[37m', // Light gray\n SlateGray = '\\x1b[90m', // Medium gray\n CharcoalGray = '\\x1b[38;5;238m', // Dark gray (unique)\n SilverGrayBg = '\\x1b[47m',\n SlateGrayBg = '\\x1b[100m',\n CharcoalGrayBg = '\\x1b[48;5;238m',\n\n // Black and White for completeness\n PureBlack = '\\x1b[30m',\n PureWhite = '\\x1b[97m',\n PureBlackBg = '\\x1b[40m',\n PureWhiteBg = '\\x1b[107m',\n\n // Cyan shade\n Cyan = '\\x1b[36m',\n\n // Formatting codes\n Reset = '\\x1b[0m',\n Bold = '\\x1b[1m',\n Dim = '\\x1b[2m',\n Underline = '\\x1b[4m',\n Blink = '\\x1b[5m',\n Reverse = '\\x1b[7m',\n Hidden = '\\x1b[8m',\n}\n\n/**\n * Semantic style names for logger styling\n */\nexport type SemanticStyleName =\n | 'highlight'\n | 'accent'\n | 'attention'\n | 'success'\n | 'warning'\n | 'error'\n | 'info'\n | 'debug';\n\n\n/**\n * Icon type for logger styling (all available emojis as string literals)\n */\nexport type Icon =\n | '✅' | '⚠️' | '❌' | 'ℹ️' | '🐞' | '⭐️' | '🚀' | '🔥' | '✔️' | '✖️' | '❓' | '🔒' | '🔓' | '⏳' | '🕒' | '⬆️' | '⬇️' | '➡️' | '⬅️' | '📁' | '📄' | '👤' | '👥' | '✏️' | '➕' | '➖' | '🔔' | '⚡️' | '🎁' | '🐛' | '🌟' | '❤️' | '👀' | '⚙️' | '🔧' | '🔨' | '🔑' | '🎉' | '📝' | '🚨' | '📅' | '💡' | '🔍' | '🔗' | '🔖' | '📌' | '📎' | '✉️' | '📞' | '🌍' | '☁️' | '🌈' | '🌙' | '☀️' | '❄️' | '✨' | '🎵' | '📷' | '🎥' | '🎤' | '🔊' | '🔋' | '🗑️' | '💰' | '💳' | '🎂' | '🏅' | '🏆' | '👑' | '🛸' | '🛡️' | '🛑' | '▶️' | '⏸️' | '⏺️' | '⏪' | '⏩' | '🔁' | '🔀' | '🎲' | '🎈' | '🍪' | '☕️' | '🍵' | '🍺' | '🍷' | '🍕' | '🍔' | '🍟' | '🍎' | '🍌' | '🍒' | '🍋' | '🥕' | '🌽' | '🥦' | '🥚' | '🧀' | '🍞' | '🍰' | '🍦' | '🍫' | '🍿' | '🥓' | '🍤' | '🐟' | '🦀' | '🐙' | '🐋' | '🐬' | '🐧' | '🐸' | '🐢' | '🐍' | '🐉' | '🦄' | '🐱' | '🐶' | '🐭' | '🐰' | '🐻' | '🐼' | '🐨' | '🐯' | '🦁' | '🐒' | '🐘' | '🐎' | '🐄' | '🐖' | '🐑' | '🐔' | '🦆' | '🦢' | '🦉' | '🦅' | '🦜' | '🦚' | '🦩' | '🦋' | '🐝' | '🐜' | '🐞' | '🕷️' | '🦂' | '🐌' | '🪱' | '🐛' | '🦗' | '🦟' | '🪰' | '🪳' | '🪲';\n\n/**\n * LogStyling for visual styling and icons in log output\n */\nexport interface LogStyling {\n style?: SemanticStyleName | { color: ColorEnum; bold?: boolean; underline?: boolean };\n icon?: Icon | string;\n}\n/**\n * Valid log level strings\n */\nexport type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent';\n\n// Log level enumeration to match standard console methods\nexport enum LogLevelEnum {\n trace = 0,\n debug = 1,\n info = 2,\n warn = 3,\n error = 4,\n fatal = 5,\n silent = 6,\n}\n\n/**\n * Contextual data for structured logging\n * \n * A plain object containing key-value pairs that provide\n * additional context for log entries. Used to attach\n * structured data to log messages for better debugging\n * and monitoring.\n * \n * @example\n * ```typescript\n * const context: LogContext = {\n * userId: '12345',\n * operation: 'login',\n * duration: 150,\n * success: true\n * };\n * \n * logger.error('Login failed', context);\n * ```\n */\nexport interface LogContext {\n [key: string]: unknown;\n correlationId?: string;\n userId?: string;\n requestId?: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * Type guard to check if a string is a valid log level\n * @param level - The string to check\n * @returns True if the string is a valid log level\n */\nexport function isValidLogLevel(level: string): level is LogLevel {\n return ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent'].includes(level);\n}\n\n/**\n * Configuration options for the LoggerService\n */\nexport interface LoggerConfig {\n /**\n * The log level to use\n * @default 'info'\n */\n level?: LogLevel;\n\n /**\n * The name of the logger to use in logs\n * @default 'analog-tools'\n */\n name?: string;\n\n /**\n * The contexts you don't want to log\n */\n disabledContexts?: string[];\n\n /**\n * Whether to use colors in the console output\n * @default true\n */\n useColors?: boolean;\n\n /**\n * Global style configuration for semantic styles\n */\n styles?: Partial<Record<SemanticStyleName, { color: ColorEnum; bold?: boolean; underline?: boolean }>>;\n\n /**\n * Global icon configuration for semantic icons\n */\n icons?: Partial<Record<'success' | 'warning' | 'error' | 'info' | 'debug', Icon>>;\n\n /**\n * Log deduplication configuration\n */\n deduplication?: {\n /** Whether deduplication is enabled (default: false) */\n enabled: boolean;\n /** Time window in milliseconds for batching messages (default: 5000) */\n windowMs?: number;\n /** Whether to flush immediately on critical levels (default: true) */\n flushOnCritical?: boolean;\n };\n}\n","/**\n * Error serialization utilities for safe logging with circular reference handling\n */\n\nimport { StructuredError, ErrorSerializationOptions } from './error.types';\n\n/**\n * Utility class for safely serializing errors and objects for logging\n * \n * Provides robust error serialization with support for:\n * - Standard Error objects with stack traces\n * - Circular reference detection and handling\n * - Maximum depth limits to prevent infinite recursion\n * - Non-enumerable property inclusion options\n * - Safe stringification of any value type\n * \n * @example\n * ```typescript\n * // Serialize a standard Error\n * const error = new Error('Something went wrong');\n * const serialized = ErrorSerializer.serialize(error);\n * \n * // Serialize with custom options\n * const serialized = ErrorSerializer.serialize(error, {\n * includeStack: false,\n * maxDepth: 5,\n * includeNonEnumerable: true\n * });\n * \n * // Serialize any object\n * const obj = { nested: { data: 'value' } };\n * const serialized = ErrorSerializer.serialize(obj);\n * ```\n */\nexport class ErrorSerializer {\n private static readonly DEFAULT_MAX_DEPTH = 10;\n private static readonly CIRCULAR_REF_PLACEHOLDER = '[Circular Reference]';\n private static readonly MAX_DEPTH_PLACEHOLDER = '[Max Depth Reached]';\n private static readonly UNABLE_TO_SERIALIZE = '[Unable to serialize]';\n \n // Simple memoization cache for frequently serialized errors (limited size to prevent memory leaks)\n private static readonly serializationCache = new Map<string, StructuredError | string>();\n private static readonly MAX_CACHE_SIZE = 100;\n \n /**\n * Generate a cache key for memoization (includes serialization options)\n * @private\n */\n private static getCacheKey(error: Error, includeStack: boolean, maxDepth: number, includeNonEnumerable: boolean): string | null {\n // Only cache if error has a stack trace (more stable identity)\n if (!error.stack) return null;\n \n // Create a simple hash based on message + first line of stack + options\n const stackFirstLine = error.stack.split('\\n')[0] || '';\n return `${error.name}:${error.message}:${stackFirstLine}:${includeStack}:${maxDepth}:${includeNonEnumerable}`;\n }\n \n /**\n * Add to cache with size limit enforcement\n * @private\n */\n private static addToCache(key: string, value: StructuredError | string): void {\n if (this.serializationCache.size >= this.MAX_CACHE_SIZE) {\n // Remove oldest entry (first in Map)\n const firstKey = this.serializationCache.keys().next().value;\n if (firstKey) {\n this.serializationCache.delete(firstKey);\n }\n }\n this.serializationCache.set(key, value);\n }\n \n /**\n * Safely serialize any value (Error objects, plain objects, primitives) for logging\n * \n * @param error - The value to serialize (Error, object, string, etc.)\n * @param options - Configuration options for serialization behavior\n * @returns Serialized representation - StructuredError for Error objects, string for others\n * \n * @example\n * ```typescript\n * // Serialize an Error object\n * const error = new Error('Failed to connect');\n * const result = ErrorSerializer.serialize(error);\n * // Returns: { message: 'Failed to connect', name: 'Error', stack: '...' }\n * \n * // Serialize a plain object\n * const obj = { userId: '123', action: 'login' };\n * const result = ErrorSerializer.serialize(obj);\n * // Returns: '{\\n \"userId\": \"123\",\\n \"action\": \"login\"\\n}'\n * \n * // Serialize with options\n * const result = ErrorSerializer.serialize(error, { includeStack: false });\n * ```\n */\n static serialize(\n error: unknown, \n options: ErrorSerializationOptions = {}\n ): StructuredError | string {\n const {\n includeStack = true,\n maxDepth = ErrorSerializer.DEFAULT_MAX_DEPTH,\n includeNonEnumerable = false\n } = options;\n \n // Handle Error instances\n if (error instanceof Error) {\n // Check cache first\n const cacheKey = this.getCacheKey(error, includeStack, maxDepth, includeNonEnumerable);\n if (cacheKey) {\n const cached = this.serializationCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n }\n \n const result = this.serializeError(error, includeStack, maxDepth, includeNonEnumerable, new WeakSet());\n \n // Add to cache\n if (cacheKey) {\n this.addToCache(cacheKey, result);\n }\n \n return result;\n }\n \n // Handle string inputs\n if (typeof error === 'string') {\n return error;\n }\n \n // Handle all other types\n const stringified = this.safeStringify(error, maxDepth, new WeakSet());\n if (typeof stringified === 'string') {\n return stringified;\n }\n \n try {\n return JSON.stringify(stringified, null, 2);\n } catch {\n return String(stringified);\n }\n }\n \n /**\n * Serialize a standard Error object\n * @private\n */\n private static serializeError(\n error: Error, \n includeStack: boolean, \n maxDepth: number,\n includeNonEnumerable: boolean,\n seen: WeakSet<object> = new WeakSet()\n ): StructuredError {\n // Check for circular reference first\n if (seen.has(error)) {\n return { message: error.message, name: error.name, [Symbol.for('circular')]: this.CIRCULAR_REF_PLACEHOLDER };\n }\n \n seen.add(error);\n \n const result: StructuredError = {\n message: error.message,\n name: error.name\n };\n \n // Include stack trace if requested\n if (includeStack && error.stack) {\n result.stack = error.stack;\n }\n \n // Handle Error.cause (Node.js 16+)\n if ('cause' in error && error.cause !== undefined) {\n if (error.cause instanceof Error) {\n result.cause = this.serializeError(error.cause, includeStack, maxDepth - 1, includeNonEnumerable, seen);\n } else {\n result.cause = this.safeStringify(error.cause, maxDepth - 1, seen);\n }\n }\n \n // Include enumerable properties\n Object.keys(error).forEach(key => {\n if (!(key in result)) {\n try {\n const errorRecord = error as unknown as Record<string, unknown>;\n result[key] = this.safeStringify(errorRecord[key], maxDepth - 1, seen);\n } catch {\n result[key] = this.UNABLE_TO_SERIALIZE;\n }\n }\n });\n \n // Include non-enumerable properties if requested\n if (includeNonEnumerable) {\n Object.getOwnPropertyNames(error).forEach(key => {\n if (!(key in result) && key !== 'stack' && key !== 'message' && key !== 'name') {\n try {\n const descriptor = Object.getOwnPropertyDescriptor(error, key);\n if (descriptor && descriptor.enumerable === false) {\n const errorRecord = error as unknown as Record<string, unknown>;\n result[key] = this.safeStringify(errorRecord[key], maxDepth - 1, seen);\n }\n } catch {\n result[key] = this.UNABLE_TO_SERIALIZE;\n }\n }\n });\n }\n \n return result;\n }\n \n /**\n * Safely stringify any object with circular reference detection\n * @private\n */\n private static safeStringify(obj: unknown, maxDepth: number, seen: WeakSet<object> = new WeakSet()): unknown {\n if (maxDepth <= 0) {\n return this.MAX_DEPTH_PLACEHOLDER;\n }\n \n // Handle primitives\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n \n // Handle circular references\n if (seen.has(obj as object)) {\n return this.CIRCULAR_REF_PLACEHOLDER;\n }\n \n seen.add(obj as object);\n \n try {\n if (Array.isArray(obj)) {\n const result = obj.map(item => this.safeStringify(item, maxDepth - 1, seen));\n seen.delete(obj as object);\n return result;\n }\n \n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n try {\n result[key] = this.safeStringify(value, maxDepth - 1, seen);\n } catch {\n result[key] = this.UNABLE_TO_SERIALIZE;\n }\n }\n \n seen.delete(obj as object);\n return result;\n } catch {\n seen.delete(obj as object);\n return this.UNABLE_TO_SERIALIZE;\n }\n }\n}\n","/**\n * Default logger configuration for metadata-based styling\n */\n\nimport { ColorEnum, Icon } from './logger.types';\n\n/**\n * Style configuration for semantic style names\n */\nexport interface StyleConfig {\n color: ColorEnum;\n bold?: boolean;\n underline?: boolean;\n}\n\n/**\n * Global style scheme configuration\n */\nexport interface StyleScheme {\n highlight: StyleConfig;\n accent: StyleConfig;\n attention: StyleConfig;\n success: StyleConfig;\n warning: StyleConfig;\n error: StyleConfig;\n info: StyleConfig;\n debug: StyleConfig;\n}\n\n/**\n * Icon scheme configuration\n */\nexport interface IconScheme {\n success: Icon;\n warning: Icon;\n error: Icon;\n info: Icon;\n debug: Icon;\n}\n\n/**\n * Global logger styling configuration\n */\nexport interface LoggerStyleConfig {\n styles: Partial<StyleScheme>;\n icons: Partial<IconScheme>;\n}\n\n/**\n * Default style scheme\n */\nexport const DEFAULT_STYLE_SCHEME: StyleScheme = {\n highlight: { color: ColorEnum.LemonYellow, bold: true },\n accent: { color: ColorEnum.SkyBlue },\n attention: { color: ColorEnum.RoyalPurple, bold: true },\n success: { color: ColorEnum.ForestGreen },\n warning: { color: ColorEnum.TangerineOrange },\n error: { color: ColorEnum.FireRed },\n info: { color: ColorEnum.OceanBlue },\n debug: { color: ColorEnum.SlateGray },\n};\n\n/**\n * Default icon scheme\n */\nexport const DEFAULT_ICON_SCHEME: IconScheme = {\n success: '✅',\n warning: '⚠️',\n error: '❌',\n info: 'ℹ️',\n debug: '🐞',\n};\n","/**\n * LoggerStyleEngine - Dedicated service for styling and formatting log messages\n * Extracted from LoggerService to follow Single Responsibility Principle\n */\n\nimport { LogLevelEnum, ColorEnum, Icon, LogStyling, SemanticStyleName } from './logger.types';\nimport { DEFAULT_ICON_SCHEME, DEFAULT_STYLE_SCHEME, StyleScheme, IconScheme } from './logger.config';\nimport {\n ILoggerStyleEngine,\n LoggerStyleEngineConfig,\n MetadataParseResult,\n StyleApplication,\n} from './logger-style-engine.types';\n\n/**\n * Color mapping for different log levels using ColorEnum values\n */\nconst LogLevelColors: Record<keyof typeof LogLevelEnum, ColorEnum> = {\n trace: ColorEnum.SlateGray, // Gray for trace (lowest level)\n debug: ColorEnum.Cyan, // Cyan for debug info\n info: ColorEnum.ForestGreen, // Green for general info\n warn: ColorEnum.SunflowerYellow, // Standard yellow for warnings\n error: ColorEnum.FireRed, // Red for errors\n fatal: ColorEnum.FireRed, // Bold + standard red for fatal errors\n silent: ColorEnum.Reset, // Reset for silent\n};\n\n/**\n * LoggerStyleEngine - Handles all styling, formatting, and color management for logger output\n * \n * This service is responsible for:\n * - ANSI color code management\n * - Message formatting with colors and prefixes\n * - Metadata-based styling application\n * - Icon resolution and validation\n * - Semantic style management\n * - Color application for log levels\n */\nexport class LoggerStyleEngine implements ILoggerStyleEngine {\n /**\n * Public method to resolve a style definition and return the ANSI color code (with caching)\n * @param style Style definition (semantic name or custom config)\n * @returns ANSI color code or undefined if invalid\n */\n public resolveStyle(style: StyleApplication, loggerName = 'test', context?: string): string | undefined {\n return this.applyStyle(style, loggerName, context);\n }\n /**\n * Cache for resolved style definitions (memoization)\n * Key: string for semantic style, object reference for custom style\n */\n private styleCache: Map<string | object, string | undefined> = new Map();\n /**\n * Mark this service as injectable for @analog-tools/inject\n */\n static INJECTABLE = true;\n\n private useColors: boolean;\n private globalStyles: Partial<StyleScheme>;\n private globalIcons: Partial<IconScheme>;\n\n /**\n * Create a new LoggerStyleEngine\n * @param config Configuration for the style engine\n */\n constructor(config: LoggerStyleEngineConfig = {}) {\n // Detect test environment and disable colors in tests unless explicitly enabled\n const isTestEnvironment =\n process.env['NODE_ENV'] === 'test' || process.env['VITEST'] === 'true';\n \n this.useColors = config.useColors !== undefined ? config.useColors : !isTestEnvironment;\n \n // Initialize global styles and icons\n this.globalStyles = { ...DEFAULT_STYLE_SCHEME, ...config.styles };\n this.globalIcons = { ...DEFAULT_ICON_SCHEME, ...config.icons };\n }\n\n /**\n * Enable or disable colored output\n * @param enabled Whether colors should be enabled\n */\n setUseColors(enabled: boolean): void {\n this.useColors = enabled;\n }\n\n /**\n * Check if colors are enabled\n * @returns Whether colors are enabled\n */\n getUseColors(): boolean {\n return this.useColors;\n }\n\n /**\n * Update style and icon configuration\n * @param styles Partial style configuration to merge\n * @param icons Partial icon configuration to merge\n */\n updateStyleConfig(\n styles: Partial<StyleScheme>,\n icons: Partial<IconScheme>\n ): void {\n this.globalStyles = { ...this.globalStyles, ...styles };\n this.globalIcons = { ...this.globalIcons, ...icons };\n }\n\n /**\n * Format a log message with color and proper prefix\n * @param level Log level for the message\n * @param message The message to format\n * @param loggerName The name of the logger\n * @param context Optional context for the logger\n * @param overrideColor Optional color override for the message\n * @returns Formatted message with color\n */\n formatMessage(\n level: LogLevelEnum,\n message: string,\n loggerName: string,\n context?: string,\n overrideColor?: string\n ): string {\n const prefix = context\n ? `[${loggerName}:${context}]`\n : `[${loggerName}]`;\n\n if (this.useColors) {\n let color = this.getColorForLevel(level);\n if (overrideColor) {\n color = overrideColor;\n }\n return `${color}${prefix} ${message}${ColorEnum.Reset}`;\n } else {\n return `${prefix} ${message}`;\n }\n }\n\n /**\n * Format message with metadata-based styling and icons\n * @param level Log level\n * @param message Message to format\n * @param loggerName The name of the logger\n * @param styling Optional metadata for styling\n * @param context Optional context for the logger\n * @returns Formatted message with styling and icons\n */\n formatMessageWithMetadata(\n level: LogLevelEnum,\n message: string,\n loggerName: string,\n styling?: LogStyling,\n context?: string\n ): string {\n let formattedMessage = message;\n let color = this.getColorForLevel(level);\n\n // Apply styling if provided\n if (styling?.style) {\n const appliedColor = this.applyStyle(styling.style, loggerName, context);\n if (appliedColor) {\n color = appliedColor;\n }\n }\n\n // Add icon if provided\n if (styling?.icon) {\n const icon = this.resolveIcon(styling.icon, loggerName, context);\n formattedMessage = `${icon} ${message}`;\n }\n\n // Use existing formatMessage with override color\n return this.formatMessage(level, formattedMessage, loggerName, context, color);\n }\n\n /**\n * Parse metadata parameter to separate metadata from additional data\n * @param metadataOrData Could be LogStyling or additional data\n * @param data Additional data parameters\n * @returns Parsed metadata and remaining data\n */\n parseMetadataParameter(\n metadataOrData?: LogStyling | unknown,\n data: unknown[] = []\n ): MetadataParseResult {\n // Check if the first parameter is LogStyling\n if (\n metadataOrData &&\n typeof metadataOrData === 'object' &&\n !Array.isArray(metadataOrData) &&\n ('style' in metadataOrData || 'icon' in metadataOrData)\n ) {\n return {\n metadata: metadataOrData as LogStyling,\n restData: data,\n };\n }\n\n // Build all parameters array\n const allParams =\n metadataOrData !== undefined ? [metadataOrData, ...data] : data;\n\n // Check if the last parameter is LogStyling\n if (allParams.length > 0) {\n const lastParam = allParams[allParams.length - 1];\n\n if (\n lastParam &&\n typeof lastParam === 'object' &&\n !Array.isArray(lastParam) &&\n ('style' in lastParam || 'icon' in lastParam)\n ) {\n return {\n metadata: lastParam as LogStyling,\n restData: allParams.slice(0, -1), // Return all but the last parameter\n };\n }\n }\n\n // If no metadata found, return all parameters as data\n return {\n metadata: undefined,\n restData: allParams,\n };\n }\n\n /**\n * Get color for a specific log level\n * @param level The log level\n * @returns ANSI color code for the log level\n * @private\n */\n private getColorForLevel(level: LogLevelEnum): string {\n // Convert enum to string key\n const levelKey = LogLevelEnum[level] as keyof typeof LogLevelColors;\n const baseColor = LogLevelColors[levelKey];\n // For fatal errors, add bold formatting to make them stand out\n if (level === LogLevelEnum.fatal) {\n return `${ColorEnum.Bold}${baseColor}`;\n }\n // If baseColor is undefined (invalid log level), return reset\n if (!baseColor) {\n return ColorEnum.Reset;\n }\n return baseColor;\n }\n\n /**\n * Apply style configuration and return ANSI color code\n * @param style Style configuration (semantic name or custom config)\n * @param loggerName Logger name for error messages\n * @param context Optional context for error messages\n * @returns ANSI color code or undefined if invalid\n * @private\n */\n private applyStyle(\n style: StyleApplication,\n loggerName: string,\n context?: string\n ): string | undefined {\n // Memoization: cache by string or object reference\n const cached = this.getStyleCacheValue(style);\n if (cached !== undefined) return cached;\n \n if (typeof style === 'string') {\n const resolved = this.getSemanticStyleColor(style, loggerName, context);\n this.setStyleCache(style, resolved);\n return resolved;\n }\n if (typeof style === 'object' && 'color' in style) {\n if (!this.isValidColor(style.color)) {\n this.setStyleCache(style, undefined);\n this.logWarning(`Invalid color provided. Only predefined ColorEnum values are allowed.`, loggerName, context);\n return undefined;\n }\n const styleCode = this.constructStyleCode(style);\n this.setStyleCache(style, styleCode);\n return styleCode;\n }\n this.setStyleCache(style, undefined);\n this.logWarning(`Unknown style configuration provided. Only semantic style names or valid ColorEnum objects are allowed.`, loggerName, context);\n return undefined;\n }\n // Helper: Validate color\n private isValidColor(color: unknown): boolean {\n return Object.values(ColorEnum).includes(color as ColorEnum);\n }\n\n // Helper: Validate icon\n private isValidIcon(icon: unknown): boolean {\n return this.isEmojiIcon(icon as string);\n }\n\n // Helper: Memoization get/set\n private getStyleCacheValue(key: string | object): string | undefined {\n return this.styleCache.has(key) ? this.styleCache.get(key) : undefined;\n }\n private setStyleCache(key: string | object, value: string | undefined): void {\n this.styleCache.set(key, value);\n }\n\n // Helper: Error logging\n private logWarning(message: string, loggerName: string, context?: string): void {\n const loggerPrefix = context ? `${loggerName}:${context}` : loggerName;\n console.warn(`[${loggerPrefix}] ${message}`);\n }\n\n // Helper: Style code construction\n private constructStyleCode(style: { color: ColorEnum; bold?: boolean; underline?: boolean }): string {\n let code = style.color.toString();\n if (style.bold) code += ColorEnum.Bold;\n if (style.underline) code += ColorEnum.Underline;\n return code;\n }\n\n /**\n * Get ANSI color code for semantic style names\n * @param styleName Semantic style name\n * @param loggerName Logger name for error messages\n * @param context Optional context for error messages\n * @returns ANSI color code or undefined if unknown\n * @private\n */\n private getSemanticStyleColor(\n styleName: SemanticStyleName,\n loggerName: string,\n context?: string\n ): string | undefined {\n // Get style from global configuration\n // Memoization: cache by styleName string\n if (this.styleCache.has(styleName)) {\n return this.styleCache.get(styleName);\n }\n const styleConfig = this.globalStyles[styleName];\n\n if (styleConfig) {\n let styleCode = styleConfig.color.toString();\n if (styleConfig.bold) {\n styleCode += ColorEnum.Bold;\n }\n if (styleConfig.underline) {\n styleCode += ColorEnum.Underline;\n }\n this.styleCache.set(styleName, styleCode);\n return styleCode;\n }\n\n // Log warning for unknown semantic style and fallback\n const loggerPrefix = context ? `${loggerName}:${context}` : loggerName;\n console.warn(\n `[${loggerPrefix}] Unknown semantic style: ${styleName}. Falling back to default.`\n );\n this.styleCache.set(styleName, undefined);\n return undefined;\n }\n\n /**\n * Expose style cache for diagnostics/testing\n */\n getStyleCache(): Map<string | object, string | undefined> {\n return this.styleCache;\n }\n\n /**\n * Resolve icon to emoji character\n * @param icon Icon name or custom string\n * @param loggerName Logger name for error messages\n * @param context Optional context for error messages\n * @returns Emoji character\n * @private\n */\n private resolveIcon(\n icon: Icon | string,\n loggerName: string,\n context?: string\n ): string {\n if (this.isValidIcon(icon)) {\n return icon;\n }\n const semanticIconKeys = [\n 'success', 'warning', 'error', 'info', 'debug',\n ] as const;\n const semanticIcon = semanticIconKeys.find((key) => key === icon);\n if (semanticIcon && this.globalIcons[semanticIcon]) {\n return this.globalIcons[semanticIcon] as string;\n }\n if (semanticIconKeys.includes(icon as typeof semanticIconKeys[number])) {\n this.logWarning(`Unknown icon: ${icon}. Expected a valid emoji or semantic icon name.`, loggerName, context);\n } else {\n this.logWarning(`Invalid icon: ${icon}. Expected a valid emoji or semantic icon name.`, loggerName, context);\n }\n return icon;\n }\n\n /**\n * Check if the provided icon is a valid emoji from our Icon type\n * @param icon Icon to check\n * @returns True if it's a valid emoji icon\n * @private\n */\n private isEmojiIcon(icon: string): icon is Icon {\n const validEmojis: Icon[] = [\n '✅',\n '⚠️',\n '❌',\n 'ℹ️',\n '🐞',\n '⭐️',\n '🚀',\n '🔥',\n '✔️',\n '✖️',\n '❓',\n '🔒',\n '🔓',\n '⏳',\n '🕒',\n '⬆️',\n '⬇️',\n '➡️',\n '⬅️',\n '📁',\n '📄',\n '👤',\n '👥',\n '✏️',\n '➕',\n '➖',\n '🔔',\n '⚡️',\n '🎁',\n '🐛',\n '🌟',\n '❤️',\n '👀',\n '⚙️',\n '🔧',\n '🔨',\n '🔑',\n '🎉',\n '📝',\n '🚨',\n '📅',\n '💡',\n '🔍',\n '🔗',\n '🔖',\n '📌',\n '📎',\n '✉️',\n '📞',\n '🌍',\n '☁️',\n '🌈',\n '🌙',\n '☀️',\n '❄️',\n '✨',\n '🎵',\n '📷',\n '🎥',\n '🎤',\n '🔊',\n '🔋',\n '🗑️',\n '💰',\n '💳',\n '🎂',\n '🏅',\n '🏆',\n '👑',\n '🛸',\n '🛡️',\n '🛑',\n '▶️',\n '⏸️',\n '⏺️',\n '⏪',\n '⏩',\n '🔁',\n '🔀',\n '🎲',\n '🎈',\n '🍪',\n '☕️',\n '🍵',\n '🍺',\n '🍷',\n '🍕',\n '🍔',\n '🍟',\n '🍎',\n '🍌',\n '🍒',\n '🍋',\n '🥕',\n '🌽',\n '🥦',\n '🥚',\n '🧀',\n '🍞',\n '🍰',\n '🍦',\n '🍫',\n '🍿',\n '🥓',\n '🍤',\n '🐟',\n '🦀',\n '🐙',\n '🐋',\n '🐬',\n '🐧',\n '🐸',\n '🐢',\n '🐍',\n '🐉',\n '🦄',\n '🐱',\n '🐶',\n '🐭',\n '🐰',\n '🐻',\n '🐼',\n '🐨',\n '🐯',\n '🦁',\n '🐒',\n '🐘',\n '🐎',\n '🐄',\n '🐖',\n '🐑',\n '🐔',\n '🦆',\n '🦢',\n '🦉',\n '🦅',\n '🦜',\n '🦚',\n '🦩',\n '🦋',\n '🐝',\n '🐜',\n '🐞',\n '🕷️',\n '🦂',\n '🐌',\n '🪱',\n '🐛',\n '🦗',\n '🦟',\n '🪰',\n '🪳',\n '🪲',\n ];\n\n return validEmojis.includes(icon as Icon);\n }\n}\n","// Custom error types for LoggerService\n\nexport class LoggerError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LoggerError';\n }\n}\n\nexport class LoggerContextError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LoggerContextError';\n }\n}\n","/**\n * Simple types for log deduplication functionality\n * Following YAGNI principle - minimal, focused implementation\n */\n\nimport { LogLevelEnum } from '../logger.types';\n\n/**\n * Configuration for log deduplication\n */\nexport interface DeduplicationConfig {\n /** Whether deduplication is enabled */\n enabled: boolean;\n /** Time window in milliseconds for batching messages */\n windowMs: number;\n /** Whether to flush immediately on critical levels (error/fatal) */\n flushOnCritical: boolean;\n}\n\n/**\n * Default deduplication configuration\n */\nexport const DEFAULT_DEDUPLICATION_CONFIG: DeduplicationConfig = {\n enabled: false,\n windowMs: 5000, // 5 seconds\n flushOnCritical: true,\n};\n\n/**\n * Tracked log entry for deduplication\n */\nexport interface LogEntry {\n /** Original message */\n message: string;\n /** Log level */\n level: LogLevelEnum;\n /** Logger context (if any) */\n context: string;\n /** When first seen */\n firstSeen: number;\n /** How many times seen */\n count: number;\n}\n\n/**\n * Critical levels that bypass deduplication\n */\nexport const CRITICAL_LEVELS = [LogLevelEnum.error, LogLevelEnum.fatal];\n\n/**\n * Interface for the log deduplicator\n */\nexport interface ILogDeduplicator {\n /**\n * Add a message to the deduplicator\n * @param level Log level\n * @param message Message text\n * @param context Logger context\n * @returns True if message should be logged immediately, false if batched\n */\n addMessage(level: LogLevelEnum, message: string, context?: string): boolean;\n\n /**\n * Flush all pending messages immediately\n */\n flush(): void;\n\n /**\n * Destroy the deduplicator and clean up resources\n */\n destroy(): void;\n}\n","/**\n * Simple log deduplicator implementation\n * Following YAGNI principle - minimal, focused functionality\n */\n\nimport { LogLevelEnum } from '../logger.types';\nimport {\n DeduplicationConfig,\n LogEntry,\n CRITICAL_LEVELS,\n ILogDeduplicator,\n} from './deduplication.types';\n\n/**\n * Type for message formatter function\n */\nexport type MessageFormatter = (\n level: LogLevelEnum,\n message: string,\n context?: string\n) => string;\n\n/**\n * Simple log deduplicator that batches identical messages\n */\nexport class LogDeduplicator implements ILogDeduplicator {\n private entries = new Map<string, LogEntry>();\n private flushTimer?: NodeJS.Timeout;\n\n constructor(\n private config: DeduplicationConfig,\n private formatMessage: MessageFormatter\n ) {}\n\n /**\n * Add a message to the deduplicator\n */\n addMessage(level: LogLevelEnum, message: string, context = ''): boolean {\n // If deduplication is disabled, log immediately\n if (!this.config.enabled) {\n return true;\n }\n\n // Critical levels bypass deduplication\n if (this.config.flushOnCritical && CRITICAL_LEVELS.includes(level)) {\n return true;\n }\n\n // Generate fingerprint for the message\n const fingerprint = this.generateFingerprint(message, level, context);\n const existing = this.entries.get(fingerprint);\n\n if (existing) {\n // Update existing entry\n existing.count++;\n } else {\n // Create new entry\n this.entries.set(fingerprint, {\n message,\n level,\n context,\n firstSeen: Date.now(),\n count: 1,\n });\n }\n\n // Schedule flush if not already scheduled\n this.scheduleFlush();\n\n // Return false to indicate message was batched\n return false;\n }\n\n /**\n * Flush all pending messages immediately\n */\n flush(): void {\n if (this.entries.size === 0) {\n return;\n }\n\n // Clear any pending timer\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n\n // Output all batched messages\n for (const entry of this.entries.values()) {\n this.outputMessage(entry);\n }\n\n // Clear entries after flushing\n this.entries.clear();\n }\n\n /**\n * Destroy the deduplicator and clean up resources\n */\n destroy(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n this.entries.clear();\n }\n\n /**\n * Generate a fingerprint for message deduplication\n */\n private generateFingerprint(\n message: string,\n level: LogLevelEnum,\n context: string\n ): string {\n // Simple concatenation - sufficient for exact matching\n return `${level}:${context}:${message}`;\n }\n\n /**\n * Schedule a flush after the configured window\n */\n private scheduleFlush(): void {\n if (this.flushTimer) {\n return; // Already scheduled\n }\n\n this.flushTimer = setTimeout(() => {\n this.flush();\n }, this.config.windowMs);\n }\n\n /**\n * Output a batched message with count if needed\n */\n private outputMessage(entry: LogEntry): void {\n let message = entry.message;\n \n // Add count suffix if message occurred multiple times\n if (entry.count > 1) {\n message = `${message} (×${entry.count})`;\n }\n\n // Format and output the message\n const formattedMessage = this.formatMessage(entry.level, message, entry.context);\n\n // Use appropriate console method based on level\n switch (entry.level) {\n case LogLevelEnum.trace:\n console.trace(formattedMessage);\n break;\n case LogLevelEnum.debug:\n console.debug(formattedMessage);\n break;\n case LogLevelEnum.info:\n console.info(formattedMessage);\n break;\n case LogLevelEnum.warn:\n console.warn(formattedMessage);\n break;\n case LogLevelEnum.error:\n console.error(formattedMessage);\n break;\n case LogLevelEnum.fatal:\n console.error(formattedMessage);\n break;\n }\n }\n}\n","/**\n * Logger service using standard console for logging\n * Designed to be used with the @analog-tools/inject package\n * Refactored to use LoggerStyleEngine for styling and formatting\n */\n\nimport {\n isValidLogLevel,\n LoggerConfig,\n LogLevelEnum,\n LogStyling,\n LogContext,\n} from './logger.types';\nimport {\n ErrorParam,\n ErrorSerializer,\n StructuredError,\n} from './error-serialization';\nimport { LoggerStyleEngine } from './logger-style-engine';\nimport { DEFAULT_ICON_SCHEME, DEFAULT_STYLE_SCHEME } from './logger.config';\nimport { LoggerError } from './errors';\nimport { \n LogDeduplicator, \n MessageFormatter \n} from './deduplication/deduplicator';\nimport { \n DeduplicationConfig, \n DEFAULT_DEDUPLICATION_CONFIG \n} from './deduplication/deduplication.types';\n\n/**\n * Logger service implementation using standard console\n * Can be injected using the @analog-tools/inject package\n * Serves as both the main logger and child logger with context\n * Uses LoggerStyleEngine for all formatting and styling operations\n */\nexport class LoggerService {\n /**\n * Mark this service as injectable\n * This is required for the @analog-tools/inject package to recognize it\n */\n static INJECTABLE = true;\n\n private logLevel: LogLevelEnum;\n private name: string;\n private childLoggers: Record<string, LoggerService> = {};\n private disabledContexts: string[] = [];\n // Track active groups for indentation\n private activeGroups: string[] = [];\n\n // Properties for child loggers\n private context?: string;\n private parentLogger?: LoggerService;\n // Style engine for formatting\n private styleEngine: LoggerStyleEngine;\n // Deduplicator for batching repeated messages\n private deduplicator?: LogDeduplicator;\n\n /**\n * Create a new LoggerService\n * @param config The logger configuration\n * @param styleEngine Optional style engine (will be created if not provided)\n * @param context Optional context for child logger\n * @param parent Optional parent logger for child logger\n */\n constructor(\n private config: LoggerConfig = {},\n styleEngine?: LoggerStyleEngine,\n context?: string,\n parent?: LoggerService\n ) {\n if (parent) {\n // Child logger setup\n this.parentLogger = parent;\n this.name = parent.name;\n this.context = context;\n // Inherit settings from parent\n this.logLevel = parent.getLogLevel();\n this.styleEngine = parent.styleEngine; // Share style engine with parent\n } else {\n // Root logger setup\n if (typeof config.level === 'string' && !Object.keys(LogLevelEnum).includes(config.level)) {\n throw new LoggerError(`Invalid log level: ${config.level}`);\n }\n this.logLevel = this.castLoglevel(\n config.level || process.env['LOG_LEVEL'] || 'info'\n );\n this.name = config.name || 'analog-tools';\n this.setDisabledContexts(\n config.disabledContexts ??\n process.env['LOG_DISABLED_CONTEXTS']?.split(',') ??\n []\n );\n\n // Initialize style engine\n this.styleEngine = styleEngine || new LoggerStyleEngine({\n useColors: config.useColors,\n styles: { ...DEFAULT_STYLE_SCHEME, ...config.styles },\n icons: { ...DEFAULT_ICON_SCHEME, ...config.icons },\n });\n\n // Initialize deduplicator if enabled\n if (config.deduplication?.enabled) {\n const dedupeConfig: DeduplicationConfig = {\n enabled: true,\n windowMs: config.deduplication.windowMs ?? DEFAULT_DEDUPLICATION_CONFIG.windowMs,\n flushOnCritical: config.deduplication.flushOnCritical ?? DEFAULT_DEDUPLICATION_CONFIG.flushOnCritical,\n };\n\n // Create formatter function for deduplicator\n const formatter: MessageFormatter = (level, message, context) => {\n return this.styleEngine.formatMessage(level, message, this.name, context);\n };\n\n this.deduplicator = new LogDeduplicator(dedupeConfig, formatter);\n }\n }\n }\n\n /**\n * Check if this logger's context is enabled\n * Always returns true for root logger\n */\n isContextEnabled(): boolean {\n if (!this.context) return true;\n\n // For child loggers, check the root logger's disabled contexts\n const rootLogger = this.parentLogger || this;\n return !rootLogger.disabledContexts.includes(this.context);\n }\n\n /**\n * Set disabled contexts for this logger\n * @param contexts Array of context names to disable\n */\n setDisabledContexts(contexts: string[]): void {\n this.disabledContexts = contexts;\n }\n\n /**\n * Get the current log level\n * @returns The current log level\n */\n getLogLevel(): LogLevelEnum {\n return this.logLevel;\n }\n\n /**\n * Get disabled contexts\n * @returns Array of disabled context names\n */\n getDisabledContexts(): string[] {\n // For child loggers, get from root logger\n const rootLogger = this.parentLogger || this;\n return rootLogger.disabledContexts || [];\n }\n\n /**\n * Enable or disable colored output\n * @param enabled Whether colors should be enabled\n */\n setUseColors(enabled: boolean): void {\n // For root logger, update style engine\n const rootLogger = this.parentLogger || this;\n rootLogger.styleEngine.setUseColors(enabled);\n }\n\n /**\n * Check if colors are enabled\n * @returns Whether colors are enabled\n */\n getUseColors(): boolean {\n // For child loggers, get from root logger's style engine\n const rootLogger = this.parentLogger || this;\n return rootLogger.styleEngine.getUseColors();\n }\n\n /**\n * Create a child logger with a specific context\n * @param context The context for the child logger\n * @returns A new logger instance with the given context\n */\n forContext(context: string): LoggerService {\n if (!this.childLoggers[context]) {\n this.childLoggers[context] = new LoggerService({}, undefined, context, this);\n }\n\n return this.childLoggers[context];\n }\n\n /**\n * Start a new log group with the given name\n * All subsequent log messages will be indented\n * @param groupName The name of the group\n */\n group(groupName: string): void {\n if (!this.isContextEnabled()) return;\n\n // Use parent's active groups for child loggers\n const groups = this.parentLogger?.activeGroups || this.activeGroups;\n groups.push(groupName);\n\n const formattedMessage = this.styleEngine.formatMessage(\n LogLevelEnum.info,\n `Group: ${groupName}`,\n this.name,\n this.context\n );\n console.group(`${formattedMessage} ▼`);\n }\n\n /**\n * End a log group with the given name\n * Subsequent log messages will no longer be indented\n * @param groupName The name of the group (optional, will end the most recent group if not provided)\n */\n groupEnd(groupName?: string): void {\n if (!this.isContextEnabled()) return;\n\n // Use parent's active groups for child loggers\n const groups = this.parentLogger?.activeGroups || this.activeGroups;\n\n if (groupName) {\n // Find the specific group to end\n const index = groups.lastIndexOf(groupName);\n if (index !== -1) {\n // Remove this group and all nested groups after it\n const removed = groups.splice(index);\n for (let i = 0; i < removed.length; i++) {\n console.groupEnd();\n console.log('');\n }\n }\n } else if (groups.length > 0) {\n // End the most recent group\n groups.pop();\n console.groupEnd();\n console.log('');\n }\n }\n\n /**\n * Log a trace message\n * @param message The message to log\n * @param data Additional data to log\n * @param metadata Optional metadata for styling and icons\n */\n trace(message: string, ...data: unknown[]): void;\n trace(message: string, metadata: LogStyling, ...data: unknown[]): void;\n trace(\n message: string,\n metadataOrData?: LogStyling | unknown,\n ...data: unknown[]\n ): void {\n if (!this.isContextEnabled() || this.logLevel > LogLevelEnum.trace) return;\n\n const { metadata, restData } = this.styleEngine.parseMetadataParameter(\n metadataOrData,\n data\n );\n\n // Handle deduplication logic\n if (!this.handleDeduplication(LogLevelEnum.trace, message, metadata, restData)) {\n return; // Message was batched\n }\n\n if (metadata) {\n const formattedMessage = this.styleEngine.formatMessageWithMetadata(\n LogLevelEnum.trace,\n message,\n this.name,\n metadata,\n this.context\n );\n console.trace(formattedMessage, ...(restData || []));\n } else {\n const formattedMessage = this.styleEngine.formatMessage(\n LogLevelEnum.trace,\n message,\n this.name,\n this.context\n );\n console.trace(formattedMessage, ...(restData || []));\n }\n }\n\n /**\n * Log a debug message\n * @param message The message to log\n * @param data Additional data to log\n * @param metadata Optional metadata for styling and icons\n */\n debug(message: string, ...data: unknown[]): void;\n debug(message: string, metadata: LogStyling, ...data: unknown[]): void;\n debug(\n message: string,\n metadataOrData?: LogStyling | unknown,\n ...data: unknown[]\n ): void {\n if (!this.isContextEnabled() || this.logLevel > LogLevelEnum.debug) return;\n\n const { metadata, restData } = this.styleEngine.parseMetadataParameter(\n metadataOrData,\n data\n );\n\n // Handle deduplication logic\n if (!this.handleDeduplication(LogLevelEnum.debug, message, metadata, restData)) {\n return; // Message was batched\n }\n\n if (metadata) {\n const formattedMessage = this.styleEngine.formatMessageWithMetadata(\n LogL