@spfn/core 0.2.0-beta.3 → 0.2.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +260 -1175
  2. package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
  3. package/dist/codegen/index.d.ts +55 -8
  4. package/dist/codegen/index.js +179 -5
  5. package/dist/codegen/index.js.map +1 -1
  6. package/dist/config/index.d.ts +204 -6
  7. package/dist/config/index.js +44 -11
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/db/index.d.ts +24 -3
  10. package/dist/db/index.js +118 -45
  11. package/dist/db/index.js.map +1 -1
  12. package/dist/env/index.d.ts +83 -3
  13. package/dist/env/index.js +83 -15
  14. package/dist/env/index.js.map +1 -1
  15. package/dist/env/loader.d.ts +95 -0
  16. package/dist/env/loader.js +78 -0
  17. package/dist/env/loader.js.map +1 -0
  18. package/dist/event/index.d.ts +29 -70
  19. package/dist/event/index.js +15 -1
  20. package/dist/event/index.js.map +1 -1
  21. package/dist/event/sse/client.d.ts +157 -0
  22. package/dist/event/sse/client.js +169 -0
  23. package/dist/event/sse/client.js.map +1 -0
  24. package/dist/event/sse/index.d.ts +46 -0
  25. package/dist/event/sse/index.js +238 -0
  26. package/dist/event/sse/index.js.map +1 -0
  27. package/dist/job/index.d.ts +23 -8
  28. package/dist/job/index.js +108 -23
  29. package/dist/job/index.js.map +1 -1
  30. package/dist/logger/index.js +9 -0
  31. package/dist/logger/index.js.map +1 -1
  32. package/dist/middleware/index.d.ts +23 -1
  33. package/dist/middleware/index.js +58 -5
  34. package/dist/middleware/index.js.map +1 -1
  35. package/dist/nextjs/index.d.ts +2 -2
  36. package/dist/nextjs/index.js +37 -5
  37. package/dist/nextjs/index.js.map +1 -1
  38. package/dist/nextjs/server.d.ts +44 -23
  39. package/dist/nextjs/server.js +87 -66
  40. package/dist/nextjs/server.js.map +1 -1
  41. package/dist/route/index.d.ts +168 -5
  42. package/dist/route/index.js +262 -17
  43. package/dist/route/index.js.map +1 -1
  44. package/dist/router-Di7ENoah.d.ts +151 -0
  45. package/dist/server/index.d.ts +316 -5
  46. package/dist/server/index.js +892 -200
  47. package/dist/server/index.js.map +1 -1
  48. package/dist/{types-BVxUIkcU.d.ts → types-7Mhoxnnt.d.ts} +68 -2
  49. package/dist/types-DAVwA-_7.d.ts +339 -0
  50. package/docs/cache.md +133 -0
  51. package/docs/codegen.md +74 -0
  52. package/docs/database.md +346 -0
  53. package/docs/entity.md +539 -0
  54. package/docs/env.md +499 -0
  55. package/docs/errors.md +319 -0
  56. package/docs/event.md +443 -0
  57. package/docs/file-upload.md +717 -0
  58. package/docs/job.md +131 -0
  59. package/docs/logger.md +108 -0
  60. package/docs/middleware.md +337 -0
  61. package/docs/nextjs.md +247 -0
  62. package/docs/repository.md +496 -0
  63. package/docs/route.md +497 -0
  64. package/docs/server.md +429 -0
  65. package/package.json +18 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/logger/types.ts","../../src/logger/formatters.ts","../../src/logger/logger.ts","../../src/logger/transports/console.ts","../../src/logger/config.ts","../../src/logger/factory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logger Type Definitions\n *\n * 로깅 시스템 타입 정의\n *\n * ✅ 구현 완료:\n * - LogLevel 타입 정의\n * - LogMetadata 인터페이스\n * - Transport 인터페이스\n * - 환경별 설정 타입\n *\n * 🔗 관련 파일:\n * - src/logger/logger.ts (Logger 클래스)\n * - src/logger/transports/ (Transport 구현체)\n * - src/logger/config.ts (설정)\n */\n\n/**\n * 로그 레벨\n * debug < info < warn < error < fatal\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * 로그 레벨 우선순위\n */\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\n/**\n * 로그 메타데이터\n */\nexport interface LogMetadata\n{\n timestamp: Date;\n level: LogLevel;\n message: string;\n module?: string;\n error?: Error;\n context?: Record<string, unknown>;\n}\n\n/**\n * Transport 인터페이스\n * 모든 Transport는 이 인터페이스를 구현해야 함\n */\nexport interface Transport\n{\n /**\n * Transport 이름\n */\n name: string;\n\n /**\n * 최소 로그 레벨 (이 레벨 이상만 처리)\n */\n level: LogLevel;\n\n /**\n * 활성화 여부\n */\n enabled: boolean;\n\n /**\n * 로그 처리 함수\n */\n log(metadata: LogMetadata): Promise<void>;\n\n /**\n * Transport 종료 (리소스 정리)\n */\n close?(): Promise<void>;\n}\n\n/**\n * Logger 설정\n */\nexport interface LoggerConfig\n{\n /**\n * 기본 로그 레벨\n */\n level: LogLevel;\n\n /**\n * 모듈명 (context)\n */\n module?: string;\n\n /**\n * Transport 리스트\n */\n transports: Transport[];\n}\n\n/**\n * Transport 설정 (공통)\n */\nexport interface TransportConfig\n{\n level: LogLevel;\n enabled: boolean;\n}\n\n/**\n * Console Transport 설정\n */\nexport interface ConsoleTransportConfig extends TransportConfig\n{\n colorize?: boolean;\n}","/**\n * Logger Formatters\n *\n * Log formatting utilities for console and JSON outputs with sensitive data masking.\n */\n\nimport type { LogLevel, LogMetadata } from './types';\n\n/**\n * 민감 정보로 간주되는 키 목록\n * 이 키들을 포함하는 필드는 자동으로 마스킹됨\n */\nconst SENSITIVE_KEYS = [\n 'password',\n 'passwd',\n 'pwd',\n 'secret',\n 'token',\n 'apikey',\n 'api_key',\n 'accesstoken',\n 'access_token',\n 'refreshtoken',\n 'refresh_token',\n 'authorization',\n 'auth',\n 'cookie',\n 'session',\n 'sessionid',\n 'session_id',\n 'privatekey',\n 'private_key',\n 'creditcard',\n 'credit_card',\n 'cardnumber',\n 'card_number',\n 'cvv',\n 'ssn',\n 'pin',\n];\n\n/**\n * 마스킹된 값\n */\nconst MASKED_VALUE = '***MASKED***';\n\n/**\n * 키가 민감 정보를 포함하는지 확인\n */\nfunction isSensitiveKey(key: string): boolean\n{\n const lowerKey = key.toLowerCase();\n return SENSITIVE_KEYS.some(sensitive => lowerKey.includes(sensitive));\n}\n\n/**\n * 민감 정보 마스킹\n * Context 객체에서 민감한 정보(비밀번호, 토큰 등)를 마스킹\n * Circular reference를 안전하게 처리\n *\n * @param data - 원본 데이터\n * @param seen - 순환 참조 감지용 WeakSet (내부 사용)\n * @returns 마스킹된 데이터\n */\nexport function maskSensitiveData(data: unknown, seen = new WeakSet<object>()): unknown\n{\n // null, undefined 처리\n if (data === null || data === undefined)\n {\n return data;\n }\n\n // 기본 타입은 그대로 반환\n if (typeof data !== 'object')\n {\n return data;\n }\n\n // Circular reference 감지\n if (seen.has(data as object))\n {\n return '[Circular]';\n }\n seen.add(data as object);\n\n // 배열 처리\n if (Array.isArray(data))\n {\n return data.map(item => maskSensitiveData(item, seen));\n }\n\n // 객체 처리\n const masked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data))\n {\n if (isSensitiveKey(key))\n {\n // 민감 정보 키는 마스킹\n masked[key] = MASKED_VALUE;\n }\n else if (typeof value === 'object' && value !== null)\n {\n // 중첩된 객체는 재귀 처리 (seen 전달)\n masked[key] = maskSensitiveData(value, seen);\n }\n else\n {\n // 일반 값은 그대로 유지\n masked[key] = value;\n }\n }\n\n return masked;\n}\n\n/**\n * ANSI 컬러 코드\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n\n // 로그 레벨 컬러\n debug: '\\x1b[36m', // cyan\n info: '\\x1b[32m', // green\n warn: '\\x1b[33m', // yellow\n error: '\\x1b[31m', // red\n fatal: '\\x1b[35m', // magenta\n\n // 추가 컬러\n gray: '\\x1b[90m',\n};\n\n/**\n * 로그 레벨을 컬러 문자열로 변환\n */\nexport function colorizeLevel(level: LogLevel): string\n{\n const color = COLORS[level];\n const levelStr = level.toUpperCase().padEnd(5);\n return `${color}${levelStr}${COLORS.reset}`;\n}\n\n/**\n * 타임스탬프 포맷 (ISO 8601)\n */\nexport function formatTimestamp(date: Date): string\n{\n return date.toISOString();\n}\n\n/**\n * 타임스탬프 포맷 (사람이 읽기 쉬운 형식)\n */\nexport function formatTimestampHuman(date: Date): string\n{\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hours = String(date.getHours()).padStart(2, '0');\n const minutes = String(date.getMinutes()).padStart(2, '0');\n const seconds = String(date.getSeconds()).padStart(2, '0');\n const ms = String(date.getMilliseconds()).padStart(3, '0');\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;\n}\n\n/**\n * 에러 객체를 문자열로 변환 (스택 트레이스 포함)\n */\nexport function formatError(error: Error): string\n{\n const lines: string[] = [];\n\n lines.push(`${error.name}: ${error.message}`);\n\n if (error.stack)\n {\n const stackLines = error.stack.split('\\n').slice(1);\n lines.push(...stackLines);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Context 객체를 문자열로 변환\n */\nexport function formatContext(context: Record<string, unknown>): string\n{\n try\n {\n return JSON.stringify(context, null, 2);\n }\n catch (error)\n {\n return '[Context serialization failed]';\n }\n}\n\n/**\n * 콘솔용 컬러 포맷\n */\nexport function formatConsole(metadata: LogMetadata, colorize = true): string\n{\n const parts: string[] = [];\n\n // [타임스탬프]\n const timestamp = formatTimestampHuman(metadata.timestamp);\n if (colorize)\n {\n parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${timestamp}]`);\n }\n\n // [pid=12345]\n const pid = process.pid;\n if (colorize)\n {\n parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[pid=${pid}]`);\n }\n\n // [module=value]\n if (metadata.module)\n {\n if (colorize)\n {\n parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[module=${metadata.module}]`);\n }\n }\n\n // Context를 각각 [key=value] 형태로 추가\n if (metadata.context && Object.keys(metadata.context).length > 0)\n {\n Object.entries(metadata.context).forEach(([key, value]) =>\n {\n let valueStr: string;\n if (typeof value === 'string')\n {\n valueStr = value;\n }\n else if (typeof value === 'object' && value !== null)\n {\n try\n {\n valueStr = JSON.stringify(value);\n }\n catch (error)\n {\n valueStr = '[circular]';\n }\n }\n else\n {\n valueStr = String(value);\n }\n\n if (colorize)\n {\n parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${key}=${valueStr}]`);\n }\n });\n }\n\n // (LEVEL):\n const levelStr = metadata.level.toUpperCase();\n if (colorize)\n {\n const color = COLORS[metadata.level];\n parts.push(`${color}(${levelStr})${COLORS.reset}:`);\n }\n else\n {\n parts.push(`(${levelStr}):`);\n }\n\n // 메시지\n if (colorize)\n {\n parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);\n }\n else\n {\n parts.push(metadata.message);\n }\n\n let output = parts.join(' ');\n\n // 에러는 별도 줄로 추가\n if (metadata.error)\n {\n output += '\\n' + formatError(metadata.error);\n }\n\n return output;\n}\n\n/**\n * JSON 포맷 (파일 저장 및 전송용)\n */\nexport function formatJSON(metadata: LogMetadata): string\n{\n const obj: Record<string, unknown> = {\n timestamp: formatTimestamp(metadata.timestamp),\n level: metadata.level,\n message: metadata.message,\n };\n\n if (metadata.module)\n {\n obj.module = metadata.module;\n }\n\n if (metadata.context)\n {\n obj.context = metadata.context;\n }\n\n if (metadata.error)\n {\n obj.error = {\n name: metadata.error.name,\n message: metadata.error.message,\n stack: metadata.error.stack,\n };\n }\n\n return JSON.stringify(obj);\n}\n\n/**\n * Drizzle ORM 에러에서 쿼리 정보 추출\n *\n * Drizzle ORM은 에러 메시지에 다음 형식으로 정보를 포함:\n * - \"Failed query: <QUERY>\\nparams: <PARAMS>\"\n * - \"Query: <QUERY>\"\n *\n * @param error - Error 객체\n * @returns 쿼리 정보 (query, params, table)\n */\nexport function extractQueryInfo(error: Error): {\n query?: string;\n params?: unknown;\n table?: string;\n} | null\n{\n const message = error.message;\n\n if (!message) return null;\n\n const result: {\n query?: string;\n params?: unknown;\n table?: string;\n } = {};\n\n // Extract query from \"Failed query: ...\" or \"Query: ...\"\n const queryMatch = message.match(/(?:Failed query:|Query:)\\s*([^\\n]+)/);\n if (queryMatch)\n {\n result.query = queryMatch[1].trim();\n\n // Extract table name from query (e.g., UPDATE \"table_name\" or INSERT INTO \"table_name\")\n const tableMatch = result.query.match(/(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?\\.\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?|(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?/i);\n if (tableMatch)\n {\n // Schema.Table or just Table\n result.table = tableMatch[2] || tableMatch[3] || tableMatch[1];\n }\n }\n\n // Extract params from \"params: ...\"\n const paramsMatch = message.match(/params:\\s*(.+?)(?:\\n|$)/);\n if (paramsMatch)\n {\n const paramsStr = paramsMatch[1].trim();\n try\n {\n // Try to parse as comma-separated values\n result.params = paramsStr.split(',').map(p => p.trim());\n }\n catch (e)\n {\n result.params = paramsStr;\n }\n }\n\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Promise rejection의 호출 스택에서 실제 발생 위치 추출\n *\n * 스택 트레이스에서 다음 정보를 추출:\n * - 실제 에러 발생 파일 경로\n * - 라인 번호\n * - 함수명/메서드명\n * - Repository 정보 (있는 경우)\n *\n * @param error - Error 객체\n * @returns Promise context 정보\n */\nexport function extractPromiseContext(error: Error): Record<string, unknown>\n{\n const context: Record<string, unknown> = {};\n\n if (!error.stack) return context;\n\n const stackLines = error.stack.split('\\n');\n\n // Skip first line (error message) and find first meaningful stack frame\n // Ignore node_modules and internal Node.js paths\n for (let i = 1; i < stackLines.length; i++)\n {\n const line = stackLines[i].trim();\n\n // Skip node_modules and node internals\n if (line.includes('node_modules') || line.includes('node:internal')) continue;\n\n // Extract file, line number, and function name\n // Format: \"at ClassName.methodName (file.ts:line:col)\"\n // or: \"at functionName (file.ts:line:col)\"\n // or: \"at file.ts:line:col\"\n\n const match = line.match(/at\\s+(?:([a-zA-Z_$][\\w$]*(?:\\.[a-zA-Z_$][\\w$]*)*)\\s+)?\\(?([^)]+):(\\d+):(\\d+)\\)?/);\n\n if (match)\n {\n const [, functionName, filePath, lineNumber, columnNumber] = match;\n\n // Extract just the filename from the full path\n const fileNameMatch = filePath.match(/([^/\\\\]+)$/);\n const fileName = fileNameMatch ? fileNameMatch[1] : filePath;\n\n context.file = fileName;\n context.line = parseInt(lineNumber, 10);\n context.column = parseInt(columnNumber, 10);\n\n if (functionName)\n {\n // Check if it's a class method (e.g., \"ClassName.methodName\")\n const methodMatch = functionName.match(/^(.+)\\.([^.]+)$/);\n if (methodMatch)\n {\n const [, className, methodName] = methodMatch;\n\n context.class = className;\n context.method = methodName;\n\n // Check if it's a Repository\n if (className.includes('Repository'))\n {\n context.repository = className;\n }\n }\n else\n {\n context.function = functionName;\n }\n }\n\n // Found first relevant frame, stop here\n break;\n }\n }\n\n return context;\n}\n\n/**\n * Unhandled rejection 에러를 상세하게 포맷팅\n *\n * Promise context와 DB 쿼리 정보를 자동으로 추출하여\n * 에러 발생 위치와 원인을 명확하게 파악할 수 있도록 함\n *\n * @param reason - Rejection 원인 (Error 또는 기타)\n * @param promise - Promise 객체\n * @returns 상세 context 정보\n */\nexport function formatUnhandledRejection(reason: unknown, promise: Promise<unknown>): {\n error: Error;\n context: Record<string, unknown>;\n}\n{\n // Convert reason to Error if not already\n let error: Error;\n if (reason instanceof Error)\n {\n error = reason;\n }\n else if (typeof reason === 'string')\n {\n error = new Error(reason);\n }\n else\n {\n error = new Error(JSON.stringify(reason));\n }\n\n const context: Record<string, unknown> = {\n promise: String(promise),\n };\n\n // Extract promise context (file, line, function, etc.)\n const promiseContext = extractPromiseContext(error);\n if (Object.keys(promiseContext).length > 0)\n {\n context.promiseContext = promiseContext;\n }\n\n // Extract DB query info if available\n const queryInfo = extractQueryInfo(error);\n if (queryInfo)\n {\n context.queryInfo = queryInfo;\n }\n\n return { error, context };\n}","/**\n * Logger Class\n *\n * Central logging class with multiple transports, child loggers, and sensitive data masking.\n */\n\nimport type { LogLevel, LogMetadata, LoggerConfig, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\nimport { maskSensitiveData } from './formatters';\n\n/**\n * Logger class\n */\nexport class Logger\n{\n private readonly config: LoggerConfig;\n private readonly module?: string;\n\n constructor(config: LoggerConfig)\n {\n this.config = config;\n this.module = config.module;\n }\n\n /**\n * Convert unknown error to Error object\n */\n private toError(error: unknown): Error\n {\n if (error instanceof Error) return error;\n if (typeof error === 'string') return new Error(error);\n if (typeof error === 'object' && error !== null)\n {\n return new Error(JSON.stringify(error));\n }\n\n return new Error(String(error));\n }\n\n /**\n * Check if value is a context object (not an error)\n */\n private isContext(value: unknown): value is Record<string, unknown>\n {\n if (typeof value !== 'object' || value === null) return false;\n if (value instanceof Error) return false;\n\n // Use stack as the primary indicator for error detection\n // - Almost all Error objects have stack (auto-generated by JS engines)\n // - Regular objects rarely use 'stack' as a property name\n // - This avoids false positives for objects with 'name' or 'message' properties\n const hasStack = 'stack' in value && typeof (value as any).stack === 'string';\n\n if (hasStack)\n {\n return false; // Likely an error object\n }\n\n // No stack -> treat as context\n // This allows {name: \"...\", method: \"...\"} to be treated as context\n return true;\n }\n\n /**\n * Get current log level\n */\n get level(): LogLevel\n {\n return this.config.level;\n }\n\n /**\n * Create child logger (per module)\n */\n child(module: string): Logger\n {\n return new Logger({\n ...this.config,\n module,\n });\n }\n\n /**\n * Common log method with error/context detection\n */\n private logWithLevel(\n level: LogLevel,\n message: string,\n errorOrContext?: Error | unknown | Record<string, unknown>,\n context?: Record<string, unknown>\n ): void\n {\n if (errorOrContext instanceof Error)\n {\n this.log(level, message, errorOrContext, context);\n }\n else if (errorOrContext !== undefined && typeof errorOrContext === 'object' && !this.isContext(errorOrContext))\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else if (typeof errorOrContext === 'string' || typeof errorOrContext === 'number' || typeof errorOrContext === 'boolean')\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else\n {\n this.log(level, message, undefined, errorOrContext as Record<string, unknown>);\n }\n }\n\n /**\n * Debug log\n */\n debug(message: string, context?: Record<string, unknown>): void;\n debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n debug(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('debug', message, errorOrContext, context);\n }\n\n /**\n * Info log\n */\n info(message: string, context?: Record<string, unknown>): void;\n info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n info(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('info', message, errorOrContext, context);\n }\n\n /**\n * Warn log\n */\n warn(message: string, context?: Record<string, unknown>): void;\n warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n warn(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('warn', message, errorOrContext, context);\n }\n\n /**\n * Error log\n */\n error(message: string, context?: Record<string, unknown>): void;\n error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n error(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('error', message, errorOrContext, context);\n }\n\n /**\n * Fatal log\n */\n fatal(message: string, context?: Record<string, unknown>): void;\n fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n fatal(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('fatal', message, errorOrContext, context);\n }\n\n /**\n * Log processing (internal)\n */\n private log(level: LogLevel, message: string, error?: Error, context?: Record<string, unknown>): void\n {\n // Early return if log level is below configured level\n // This prevents unnecessary metadata creation and processing\n if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level])\n {\n return;\n }\n\n const metadata: LogMetadata = {\n timestamp: new Date(),\n level,\n message,\n module: this.module,\n error,\n // Mask sensitive information in context to prevent credential leaks\n context: context ? maskSensitiveData(context) as Record<string, unknown> : undefined,\n };\n\n // Pass to all enabled Transports\n this.processTransports(metadata);\n }\n\n /**\n * Process Transports\n */\n private processTransports(metadata: LogMetadata): void\n {\n const promises = this.config.transports\n .filter(transport => transport.enabled)\n .map(transport => this.safeTransportLog(transport, metadata));\n\n // Async processing to prevent Transport errors from blocking logs\n Promise.all(promises).catch(error =>\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport error: ${errorMessage}\\n`);\n });\n }\n\n /**\n * Transport log (error-safe)\n */\n private async safeTransportLog(transport: Transport, metadata: LogMetadata): Promise<void>\n {\n try\n {\n await transport.log(metadata);\n }\n catch (error)\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport \"${transport.name}\" failed: ${errorMessage}\\n`);\n }\n }\n\n /**\n * Close all Transports\n */\n async close(): Promise<void>\n {\n const closePromises = this.config.transports\n .filter(transport => transport.close)\n .map(transport => transport.close!());\n\n await Promise.all(closePromises);\n }\n}","/**\n * Console Transport\n *\n * 콘솔 출력 Transport\n *\n * ✅ 구현 완료:\n * - 콘솔 출력 (stdout/stderr)\n * - 컬러 출력 지원\n * - 로그 레벨별 스트림 분리 (warn/error/fatal → stderr)\n *\n * 🔗 관련 파일:\n * - src/logger/types.ts (Transport 인터페이스)\n * - src/logger/formatters.ts (포맷터)\n * - src/logger/config.ts (설정)\n */\n\nimport type { Transport, LogMetadata, LogLevel, ConsoleTransportConfig } from '../types';\nimport { LOG_LEVEL_PRIORITY } from '../types';\nimport { formatConsole } from '../formatters';\n\n/**\n * Console Transport\n */\nexport class ConsoleTransport implements Transport\n{\n public readonly name = 'console';\n public readonly level: LogLevel;\n public readonly enabled: boolean;\n private readonly colorize: boolean;\n\n constructor(config: ConsoleTransportConfig)\n {\n this.level = config.level;\n this.enabled = config.enabled;\n this.colorize = config.colorize ?? true;\n }\n\n async log(metadata: LogMetadata): Promise<void>\n {\n // Enabled 상태 체크\n if (!this.enabled)\n {\n return;\n }\n\n // 로그 레벨 체크\n if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level])\n {\n return;\n }\n\n // 포맷팅\n const message = formatConsole(metadata, this.colorize);\n\n // warn/error/fatal은 stderr로, 나머지는 stdout으로\n if (metadata.level === 'warn' || metadata.level === 'error' || metadata.level === 'fatal')\n {\n console.error(message);\n }\n else\n {\n console.log(message);\n }\n }\n}","/**\n * Logger Configuration\n *\n * Environment-based logger configuration with validation for console transport.\n */\n\nimport type {\n ConsoleTransportConfig,\n} from './types';\n\n/**\n * Console Transport configuration\n */\nexport function getConsoleConfig(): ConsoleTransportConfig\n{\n const isProduction = process.env.NODE_ENV === 'production';\n\n return {\n level: 'debug',\n enabled: true,\n colorize: !isProduction, // Dev: colored output, Production: plain text\n };\n}\n\n/**\n * Validate environment variables\n */\nfunction validateEnvironment(): void\n{\n const nodeEnv = process.env.NODE_ENV;\n\n if (!nodeEnv)\n {\n process.stderr.write(\n '[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\\n'\n );\n }\n // Allow any NODE_ENV value (development, production, test, staging, local, etc.)\n // No validation needed - users can use custom environments\n}\n\n/**\n * Validate all logger configuration\n */\nexport function validateConfig(): void\n{\n validateEnvironment();\n}","/**\n * Logger Factory\n *\n * Creates and initializes the logger instance with configured transports\n */\n\nimport { Logger } from './logger';\nimport { ConsoleTransport } from './transports/console';\nimport { getConsoleConfig, validateConfig } from './config';\nimport type { LogLevel, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\n\n/**\n * Initialize transports based on environment and configuration\n */\nfunction initializeTransports(): Transport[]\n{\n const transports: Transport[] = [];\n\n // Console Transport (always enabled)\n const consoleConfig = getConsoleConfig();\n transports.push(new ConsoleTransport(consoleConfig));\n\n // Future: Add more transports (Slack, Email, etc.)\n // if (config.slack?.enabled) {\n // transports.push(new SlackTransport(config.slack));\n // }\n\n return transports;\n}\n\n/**\n * Get validated log level from environment variables\n */\nfunction getLogLevel(): LogLevel\n{\n const envLevel = process.env.SPFN_LOG_LEVEL\n || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL\n || 'info';\n\n if (envLevel in LOG_LEVEL_PRIORITY)\n {\n return envLevel as LogLevel;\n }\n\n process.stderr.write(\n `[Logger] Invalid log level \"${envLevel}\", defaulting to \"info\"\\n`\n );\n return 'info';\n}\n\n/**\n * Initialize logger with configuration validation\n */\nfunction initializeLogger(): Logger\n{\n // Validate configuration before creating logger\n validateConfig();\n\n // Create logger with configured transports\n return new Logger({\n level: getLogLevel(),\n transports: initializeTransports(),\n });\n}\n\n/**\n * Singleton Logger instance\n */\nexport const logger: Logger = initializeLogger();"]}
1
+ {"version":3,"sources":["../../src/logger/types.ts","../../src/logger/formatters.ts","../../src/logger/logger.ts","../../src/logger/transports/console.ts","../../src/logger/config.ts","../../src/logger/factory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logger Type Definitions\n *\n * 로깅 시스템 타입 정의\n *\n * ✅ 구현 완료:\n * - LogLevel 타입 정의\n * - LogMetadata 인터페이스\n * - Transport 인터페이스\n * - 환경별 설정 타입\n *\n * 🔗 관련 파일:\n * - src/logger/logger.ts (Logger 클래스)\n * - src/logger/transports/ (Transport 구현체)\n * - src/logger/config.ts (설정)\n */\n\n/**\n * 로그 레벨\n * debug < info < warn < error < fatal\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * 로그 레벨 우선순위\n */\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\n/**\n * 로그 메타데이터\n */\nexport interface LogMetadata\n{\n timestamp: Date;\n level: LogLevel;\n message: string;\n module?: string;\n error?: Error;\n context?: Record<string, unknown>;\n}\n\n/**\n * Transport 인터페이스\n * 모든 Transport는 이 인터페이스를 구현해야 함\n */\nexport interface Transport\n{\n /**\n * Transport 이름\n */\n name: string;\n\n /**\n * 최소 로그 레벨 (이 레벨 이상만 처리)\n */\n level: LogLevel;\n\n /**\n * 활성화 여부\n */\n enabled: boolean;\n\n /**\n * 로그 처리 함수\n */\n log(metadata: LogMetadata): Promise<void>;\n\n /**\n * Transport 종료 (리소스 정리)\n */\n close?(): Promise<void>;\n}\n\n/**\n * Logger 설정\n */\nexport interface LoggerConfig\n{\n /**\n * 기본 로그 레벨\n */\n level: LogLevel;\n\n /**\n * 모듈명 (context)\n */\n module?: string;\n\n /**\n * Transport 리스트\n */\n transports: Transport[];\n}\n\n/**\n * Transport 설정 (공통)\n */\nexport interface TransportConfig\n{\n level: LogLevel;\n enabled: boolean;\n}\n\n/**\n * Console Transport 설정\n */\nexport interface ConsoleTransportConfig extends TransportConfig\n{\n colorize?: boolean;\n}","/**\n * Logger Formatters\n *\n * Log formatting utilities for console and JSON outputs with sensitive data masking.\n */\n\nimport type { LogLevel, LogMetadata } from './types';\n\n/**\n * 민감 정보로 간주되는 키 목록\n * 이 키들을 포함하는 필드는 자동으로 마스킹됨\n */\nconst SENSITIVE_KEYS = [\n 'password',\n 'passwd',\n 'pwd',\n 'secret',\n 'token',\n 'apikey',\n 'api_key',\n 'accesstoken',\n 'access_token',\n 'refreshtoken',\n 'refresh_token',\n 'authorization',\n 'auth',\n 'cookie',\n 'session',\n 'sessionid',\n 'session_id',\n 'privatekey',\n 'private_key',\n 'creditcard',\n 'credit_card',\n 'cardnumber',\n 'card_number',\n 'cvv',\n 'ssn',\n 'pin',\n];\n\n/**\n * 마스킹된 값\n */\nconst MASKED_VALUE = '***MASKED***';\n\n/**\n * 키가 민감 정보를 포함하는지 확인\n */\nfunction isSensitiveKey(key: string): boolean\n{\n const lowerKey = key.toLowerCase();\n return SENSITIVE_KEYS.some(sensitive => lowerKey.includes(sensitive));\n}\n\n/**\n * 민감 정보 마스킹\n * Context 객체에서 민감한 정보(비밀번호, 토큰 등)를 마스킹\n * Circular reference를 안전하게 처리\n *\n * @param data - 원본 데이터\n * @param seen - 순환 참조 감지용 WeakSet (내부 사용)\n * @returns 마스킹된 데이터\n */\nexport function maskSensitiveData(data: unknown, seen = new WeakSet<object>()): unknown\n{\n // null, undefined 처리\n if (data === null || data === undefined)\n {\n return data;\n }\n\n // 기본 타입은 그대로 반환\n if (typeof data !== 'object')\n {\n return data;\n }\n\n // Circular reference 감지\n if (seen.has(data as object))\n {\n return '[Circular]';\n }\n seen.add(data as object);\n\n // 배열 처리\n if (Array.isArray(data))\n {\n return data.map(item => maskSensitiveData(item, seen));\n }\n\n // 객체 처리\n const masked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data))\n {\n if (isSensitiveKey(key))\n {\n // 민감 정보 키는 마스킹\n masked[key] = MASKED_VALUE;\n }\n else if (typeof value === 'object' && value !== null)\n {\n // 중첩된 객체는 재귀 처리 (seen 전달)\n masked[key] = maskSensitiveData(value, seen);\n }\n else\n {\n // 일반 값은 그대로 유지\n masked[key] = value;\n }\n }\n\n return masked;\n}\n\n/**\n * ANSI 컬러 코드\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n\n // 로그 레벨 컬러\n debug: '\\x1b[36m', // cyan\n info: '\\x1b[32m', // green\n warn: '\\x1b[33m', // yellow\n error: '\\x1b[31m', // red\n fatal: '\\x1b[35m', // magenta\n\n // 추가 컬러\n gray: '\\x1b[90m',\n};\n\n/**\n * 로그 레벨을 컬러 문자열로 변환\n */\nexport function colorizeLevel(level: LogLevel): string\n{\n const color = COLORS[level];\n const levelStr = level.toUpperCase().padEnd(5);\n return `${color}${levelStr}${COLORS.reset}`;\n}\n\n/**\n * 타임스탬프 포맷 (ISO 8601)\n */\nexport function formatTimestamp(date: Date): string\n{\n return date.toISOString();\n}\n\n/**\n * 타임스탬프 포맷 (사람이 읽기 쉬운 형식)\n */\nexport function formatTimestampHuman(date: Date): string\n{\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hours = String(date.getHours()).padStart(2, '0');\n const minutes = String(date.getMinutes()).padStart(2, '0');\n const seconds = String(date.getSeconds()).padStart(2, '0');\n const ms = String(date.getMilliseconds()).padStart(3, '0');\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;\n}\n\n/**\n * 에러 객체를 문자열로 변환 (스택 트레이스 포함)\n */\nexport function formatError(error: Error): string\n{\n const lines: string[] = [];\n\n lines.push(`${error.name}: ${error.message}`);\n\n if (error.stack)\n {\n const stackLines = error.stack.split('\\n').slice(1);\n lines.push(...stackLines);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Context 객체를 문자열로 변환\n */\nexport function formatContext(context: Record<string, unknown>): string\n{\n try\n {\n return JSON.stringify(context, null, 2);\n }\n catch (error)\n {\n return '[Context serialization failed]';\n }\n}\n\n/**\n * 콘솔용 컬러 포맷\n */\nexport function formatConsole(metadata: LogMetadata, colorize = true): string\n{\n const parts: string[] = [];\n\n // [타임스탬프]\n const timestamp = formatTimestampHuman(metadata.timestamp);\n if (colorize)\n {\n parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${timestamp}]`);\n }\n\n // [pid=12345]\n const pid = process.pid;\n if (colorize)\n {\n parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[pid=${pid}]`);\n }\n\n // [module=value]\n if (metadata.module)\n {\n if (colorize)\n {\n parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[module=${metadata.module}]`);\n }\n }\n\n // Context를 각각 [key=value] 형태로 추가\n if (metadata.context && Object.keys(metadata.context).length > 0)\n {\n Object.entries(metadata.context).forEach(([key, value]) =>\n {\n let valueStr: string;\n if (typeof value === 'string')\n {\n valueStr = value;\n }\n else if (typeof value === 'object' && value !== null)\n {\n try\n {\n valueStr = JSON.stringify(value);\n }\n catch (error)\n {\n valueStr = '[circular]';\n }\n }\n else\n {\n valueStr = String(value);\n }\n\n if (colorize)\n {\n parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${key}=${valueStr}]`);\n }\n });\n }\n\n // (LEVEL):\n const levelStr = metadata.level.toUpperCase();\n if (colorize)\n {\n const color = COLORS[metadata.level];\n parts.push(`${color}(${levelStr})${COLORS.reset}:`);\n }\n else\n {\n parts.push(`(${levelStr}):`);\n }\n\n // 메시지\n if (colorize)\n {\n parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);\n }\n else\n {\n parts.push(metadata.message);\n }\n\n let output = parts.join(' ');\n\n // 에러는 별도 줄로 추가\n if (metadata.error)\n {\n output += '\\n' + formatError(metadata.error);\n }\n\n return output;\n}\n\n/**\n * JSON 포맷 (파일 저장 및 전송용)\n */\nexport function formatJSON(metadata: LogMetadata): string\n{\n const obj: Record<string, unknown> = {\n timestamp: formatTimestamp(metadata.timestamp),\n level: metadata.level,\n message: metadata.message,\n };\n\n if (metadata.module)\n {\n obj.module = metadata.module;\n }\n\n if (metadata.context)\n {\n obj.context = metadata.context;\n }\n\n if (metadata.error)\n {\n obj.error = {\n name: metadata.error.name,\n message: metadata.error.message,\n stack: metadata.error.stack,\n };\n }\n\n return JSON.stringify(obj);\n}\n\n/**\n * Drizzle ORM 에러에서 쿼리 정보 추출\n *\n * Drizzle ORM은 에러 메시지에 다음 형식으로 정보를 포함:\n * - \"Failed query: <QUERY>\\nparams: <PARAMS>\"\n * - \"Query: <QUERY>\"\n *\n * @param error - Error 객체\n * @returns 쿼리 정보 (query, params, table)\n */\nexport function extractQueryInfo(error: Error): {\n query?: string;\n params?: unknown;\n table?: string;\n} | null\n{\n const message = error.message;\n\n if (!message) return null;\n\n const result: {\n query?: string;\n params?: unknown;\n table?: string;\n } = {};\n\n // Extract query from \"Failed query: ...\" or \"Query: ...\"\n const queryMatch = message.match(/(?:Failed query:|Query:)\\s*([^\\n]+)/);\n if (queryMatch)\n {\n result.query = queryMatch[1].trim();\n\n // Extract table name from query (e.g., UPDATE \"table_name\" or INSERT INTO \"table_name\")\n const tableMatch = result.query.match(/(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?\\.\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?|(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?/i);\n if (tableMatch)\n {\n // Schema.Table or just Table\n result.table = tableMatch[2] || tableMatch[3] || tableMatch[1];\n }\n }\n\n // Extract params from \"params: ...\"\n const paramsMatch = message.match(/params:\\s*(.+?)(?:\\n|$)/);\n if (paramsMatch)\n {\n const paramsStr = paramsMatch[1].trim();\n try\n {\n // Try to parse as comma-separated values\n result.params = paramsStr.split(',').map(p => p.trim());\n }\n catch (e)\n {\n result.params = paramsStr;\n }\n }\n\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Promise rejection의 호출 스택에서 실제 발생 위치 추출\n *\n * 스택 트레이스에서 다음 정보를 추출:\n * - 실제 에러 발생 파일 경로\n * - 라인 번호\n * - 함수명/메서드명\n * - Repository 정보 (있는 경우)\n *\n * @param error - Error 객체\n * @returns Promise context 정보\n */\nexport function extractPromiseContext(error: Error): Record<string, unknown>\n{\n const context: Record<string, unknown> = {};\n\n if (!error.stack) return context;\n\n const stackLines = error.stack.split('\\n');\n\n // Skip first line (error message) and find first meaningful stack frame\n // Ignore node_modules and internal Node.js paths\n for (let i = 1; i < stackLines.length; i++)\n {\n const line = stackLines[i].trim();\n\n // Skip node_modules and node internals\n if (line.includes('node_modules') || line.includes('node:internal')) continue;\n\n // Extract file, line number, and function name\n // Format: \"at ClassName.methodName (file.ts:line:col)\"\n // or: \"at functionName (file.ts:line:col)\"\n // or: \"at file.ts:line:col\"\n\n const match = line.match(/at\\s+(?:([a-zA-Z_$][\\w$]*(?:\\.[a-zA-Z_$][\\w$]*)*)\\s+)?\\(?([^)]+):(\\d+):(\\d+)\\)?/);\n\n if (match)\n {\n const [, functionName, filePath, lineNumber, columnNumber] = match;\n\n // Extract just the filename from the full path\n const fileNameMatch = filePath.match(/([^/\\\\]+)$/);\n const fileName = fileNameMatch ? fileNameMatch[1] : filePath;\n\n context.file = fileName;\n context.line = parseInt(lineNumber, 10);\n context.column = parseInt(columnNumber, 10);\n\n if (functionName)\n {\n // Check if it's a class method (e.g., \"ClassName.methodName\")\n const methodMatch = functionName.match(/^(.+)\\.([^.]+)$/);\n if (methodMatch)\n {\n const [, className, methodName] = methodMatch;\n\n context.class = className;\n context.method = methodName;\n\n // Check if it's a Repository\n if (className.includes('Repository'))\n {\n context.repository = className;\n }\n }\n else\n {\n context.function = functionName;\n }\n }\n\n // Found first relevant frame, stop here\n break;\n }\n }\n\n return context;\n}\n\n/**\n * Unhandled rejection 에러를 상세하게 포맷팅\n *\n * Promise context와 DB 쿼리 정보를 자동으로 추출하여\n * 에러 발생 위치와 원인을 명확하게 파악할 수 있도록 함\n *\n * @param reason - Rejection 원인 (Error 또는 기타)\n * @param promise - Promise 객체\n * @returns 상세 context 정보\n */\nexport function formatUnhandledRejection(reason: unknown, promise: Promise<unknown>): {\n error: Error;\n context: Record<string, unknown>;\n}\n{\n // Convert reason to Error if not already\n let error: Error;\n if (reason instanceof Error)\n {\n error = reason;\n }\n else if (typeof reason === 'string')\n {\n error = new Error(reason);\n }\n else\n {\n error = new Error(JSON.stringify(reason));\n }\n\n const context: Record<string, unknown> = {\n promise: String(promise),\n };\n\n // Extract promise context (file, line, function, etc.)\n const promiseContext = extractPromiseContext(error);\n if (Object.keys(promiseContext).length > 0)\n {\n context.promiseContext = promiseContext;\n }\n\n // Extract DB query info if available\n const queryInfo = extractQueryInfo(error);\n if (queryInfo)\n {\n context.queryInfo = queryInfo;\n }\n\n return { error, context };\n}","/**\n * Logger Class\n *\n * Central logging class with multiple transports, child loggers, and sensitive data masking.\n */\n\nimport { format } from 'node:util';\nimport type { LogLevel, LogMetadata, LoggerConfig, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\nimport { maskSensitiveData } from './formatters';\n\nconst FORMAT_PATTERN = /%[sdifjoOc%]/;\n\n/**\n * Logger class\n */\nexport class Logger\n{\n private readonly config: LoggerConfig;\n private readonly module?: string;\n\n constructor(config: LoggerConfig)\n {\n this.config = config;\n this.module = config.module;\n }\n\n /**\n * Convert unknown error to Error object\n */\n private toError(error: unknown): Error\n {\n if (error instanceof Error) return error;\n if (typeof error === 'string') return new Error(error);\n if (typeof error === 'object' && error !== null)\n {\n return new Error(JSON.stringify(error));\n }\n\n return new Error(String(error));\n }\n\n /**\n * Check if value is a context object (not an error)\n */\n private isContext(value: unknown): value is Record<string, unknown>\n {\n if (typeof value !== 'object' || value === null) return false;\n if (value instanceof Error) return false;\n\n // Use stack as the primary indicator for error detection\n // - Almost all Error objects have stack (auto-generated by JS engines)\n // - Regular objects rarely use 'stack' as a property name\n // - This avoids false positives for objects with 'name' or 'message' properties\n const hasStack = 'stack' in value && typeof (value as any).stack === 'string';\n\n if (hasStack)\n {\n return false; // Likely an error object\n }\n\n // No stack -> treat as context\n // This allows {name: \"...\", method: \"...\"} to be treated as context\n return true;\n }\n\n /**\n * Get current log level\n */\n get level(): LogLevel\n {\n return this.config.level;\n }\n\n /**\n * Create child logger (per module)\n */\n child(module: string): Logger\n {\n return new Logger({\n ...this.config,\n module,\n });\n }\n\n /**\n * Common log method with error/context detection\n */\n private logWithLevel(\n level: LogLevel,\n message: string,\n errorOrContext?: Error | unknown | Record<string, unknown>,\n context?: Record<string, unknown>\n ): void\n {\n // printf-style format string: logger.info('url: %s', value)\n if (errorOrContext !== undefined && FORMAT_PATTERN.test(message))\n {\n this.log(level, format(message, errorOrContext), undefined, context);\n return;\n }\n\n if (errorOrContext instanceof Error)\n {\n this.log(level, message, errorOrContext, context);\n }\n else if (errorOrContext !== undefined && typeof errorOrContext === 'object' && !this.isContext(errorOrContext))\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else if (typeof errorOrContext === 'string' || typeof errorOrContext === 'number' || typeof errorOrContext === 'boolean')\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else\n {\n this.log(level, message, undefined, errorOrContext as Record<string, unknown>);\n }\n }\n\n /**\n * Debug log\n */\n debug(message: string, context?: Record<string, unknown>): void;\n debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n debug(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('debug', message, errorOrContext, context);\n }\n\n /**\n * Info log\n */\n info(message: string, context?: Record<string, unknown>): void;\n info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n info(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('info', message, errorOrContext, context);\n }\n\n /**\n * Warn log\n */\n warn(message: string, context?: Record<string, unknown>): void;\n warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n warn(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('warn', message, errorOrContext, context);\n }\n\n /**\n * Error log\n */\n error(message: string, context?: Record<string, unknown>): void;\n error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n error(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('error', message, errorOrContext, context);\n }\n\n /**\n * Fatal log\n */\n fatal(message: string, context?: Record<string, unknown>): void;\n fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n fatal(message: string, errorOrContext?: Error | unknown | Record<string, unknown>, context?: Record<string, unknown>): void\n {\n this.logWithLevel('fatal', message, errorOrContext, context);\n }\n\n /**\n * Log processing (internal)\n */\n private log(level: LogLevel, message: string, error?: Error, context?: Record<string, unknown>): void\n {\n // Early return if log level is below configured level\n // This prevents unnecessary metadata creation and processing\n if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level])\n {\n return;\n }\n\n const metadata: LogMetadata = {\n timestamp: new Date(),\n level,\n message,\n module: this.module,\n error,\n // Mask sensitive information in context to prevent credential leaks\n context: context ? maskSensitiveData(context) as Record<string, unknown> : undefined,\n };\n\n // Pass to all enabled Transports\n this.processTransports(metadata);\n }\n\n /**\n * Process Transports\n */\n private processTransports(metadata: LogMetadata): void\n {\n const promises = this.config.transports\n .filter(transport => transport.enabled)\n .map(transport => this.safeTransportLog(transport, metadata));\n\n // Async processing to prevent Transport errors from blocking logs\n Promise.all(promises).catch(error =>\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport error: ${errorMessage}\\n`);\n });\n }\n\n /**\n * Transport log (error-safe)\n */\n private async safeTransportLog(transport: Transport, metadata: LogMetadata): Promise<void>\n {\n try\n {\n await transport.log(metadata);\n }\n catch (error)\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport \"${transport.name}\" failed: ${errorMessage}\\n`);\n }\n }\n\n /**\n * Close all Transports\n */\n async close(): Promise<void>\n {\n const closePromises = this.config.transports\n .filter(transport => transport.close)\n .map(transport => transport.close!());\n\n await Promise.all(closePromises);\n }\n}","/**\n * Console Transport\n *\n * 콘솔 출력 Transport\n *\n * ✅ 구현 완료:\n * - 콘솔 출력 (stdout/stderr)\n * - 컬러 출력 지원\n * - 로그 레벨별 스트림 분리 (warn/error/fatal → stderr)\n *\n * 🔗 관련 파일:\n * - src/logger/types.ts (Transport 인터페이스)\n * - src/logger/formatters.ts (포맷터)\n * - src/logger/config.ts (설정)\n */\n\nimport type { Transport, LogMetadata, LogLevel, ConsoleTransportConfig } from '../types';\nimport { LOG_LEVEL_PRIORITY } from '../types';\nimport { formatConsole } from '../formatters';\n\n/**\n * Console Transport\n */\nexport class ConsoleTransport implements Transport\n{\n public readonly name = 'console';\n public readonly level: LogLevel;\n public readonly enabled: boolean;\n private readonly colorize: boolean;\n\n constructor(config: ConsoleTransportConfig)\n {\n this.level = config.level;\n this.enabled = config.enabled;\n this.colorize = config.colorize ?? true;\n }\n\n async log(metadata: LogMetadata): Promise<void>\n {\n // Enabled 상태 체크\n if (!this.enabled)\n {\n return;\n }\n\n // 로그 레벨 체크\n if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level])\n {\n return;\n }\n\n // 포맷팅\n const message = formatConsole(metadata, this.colorize);\n\n // warn/error/fatal은 stderr로, 나머지는 stdout으로\n if (metadata.level === 'warn' || metadata.level === 'error' || metadata.level === 'fatal')\n {\n console.error(message);\n }\n else\n {\n console.log(message);\n }\n }\n}","/**\n * Logger Configuration\n *\n * Environment-based logger configuration with validation for console transport.\n */\n\nimport type {\n ConsoleTransportConfig,\n} from './types';\n\n/**\n * Console Transport configuration\n */\nexport function getConsoleConfig(): ConsoleTransportConfig\n{\n const isProduction = process.env.NODE_ENV === 'production';\n\n return {\n level: 'debug',\n enabled: true,\n colorize: !isProduction, // Dev: colored output, Production: plain text\n };\n}\n\n/**\n * Validate environment variables\n */\nfunction validateEnvironment(): void\n{\n const nodeEnv = process.env.NODE_ENV;\n\n if (!nodeEnv)\n {\n process.stderr.write(\n '[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\\n'\n );\n }\n // Allow any NODE_ENV value (development, production, test, staging, local, etc.)\n // No validation needed - users can use custom environments\n}\n\n/**\n * Validate all logger configuration\n */\nexport function validateConfig(): void\n{\n validateEnvironment();\n}","/**\n * Logger Factory\n *\n * Creates and initializes the logger instance with configured transports\n */\n\nimport { Logger } from './logger';\nimport { ConsoleTransport } from './transports/console';\nimport { getConsoleConfig, validateConfig } from './config';\nimport type { LogLevel, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\n\n/**\n * Initialize transports based on environment and configuration\n */\nfunction initializeTransports(): Transport[]\n{\n const transports: Transport[] = [];\n\n // Console Transport (always enabled)\n const consoleConfig = getConsoleConfig();\n transports.push(new ConsoleTransport(consoleConfig));\n\n // Future: Add more transports (Slack, Email, etc.)\n // if (config.slack?.enabled) {\n // transports.push(new SlackTransport(config.slack));\n // }\n\n return transports;\n}\n\n/**\n * Get validated log level from environment variables\n */\nfunction getLogLevel(): LogLevel\n{\n const envLevel = process.env.SPFN_LOG_LEVEL\n || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL\n || 'info';\n\n if (envLevel in LOG_LEVEL_PRIORITY)\n {\n return envLevel as LogLevel;\n }\n\n process.stderr.write(\n `[Logger] Invalid log level \"${envLevel}\", defaulting to \"info\"\\n`\n );\n return 'info';\n}\n\n/**\n * Initialize logger with configuration validation\n */\nfunction initializeLogger(): Logger\n{\n // Validate configuration before creating logger\n validateConfig();\n\n // Create logger with configured transports\n return new Logger({\n level: getLogLevel(),\n transports: initializeTransports(),\n });\n}\n\n/**\n * Singleton Logger instance\n */\nexport const logger: Logger = initializeLogger();"]}
@@ -26,6 +26,28 @@ interface ErrorHandlerOptions {
26
26
  * @default true
27
27
  */
28
28
  enableLogging?: boolean;
29
+ /**
30
+ * Callback invoked when an error occurs
31
+ *
32
+ * Called asynchronously without blocking the response.
33
+ * Useful for external error notifications (Slack, PagerDuty, etc.)
34
+ */
35
+ onError?: (err: Error, context: OnErrorContext) => Promise<void> | void;
36
+ }
37
+ /**
38
+ * Context passed to onError callback
39
+ */
40
+ interface OnErrorContext {
41
+ statusCode: number;
42
+ path: string;
43
+ method: string;
44
+ requestId?: string;
45
+ timestamp: string;
46
+ userId?: string;
47
+ request: {
48
+ headers: Record<string, string>;
49
+ query: Record<string, string>;
50
+ };
29
51
  }
30
52
  /**
31
53
  * Error handler middleware for Hono
@@ -139,4 +161,4 @@ declare function maskSensitiveData(obj: any, sensitiveFields: string[], seen?: W
139
161
  */
140
162
  declare function RequestLogger(options?: RequestLoggerOptions): (c: Context, next: Next) => Promise<void>;
141
163
 
142
- export { ErrorHandler, type ErrorHandlerOptions, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, maskSensitiveData };
164
+ export { ErrorHandler, type ErrorHandlerOptions, type OnErrorContext, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, maskSensitiveData };
@@ -5,6 +5,42 @@ import { randomBytes } from 'crypto';
5
5
 
6
6
  // src/middleware/error-handler.ts
7
7
  var errorLogger = logger.child("@spfn/core:error-handler");
8
+ function extractCauseMessage(err) {
9
+ const cause = err.cause;
10
+ if (!cause) {
11
+ return void 0;
12
+ }
13
+ if (cause instanceof Error) {
14
+ return cause.message;
15
+ }
16
+ if (typeof cause === "string") {
17
+ return cause;
18
+ }
19
+ return String(cause);
20
+ }
21
+ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "x-api-key", "x-auth-token"]);
22
+ function extractHeaders(c) {
23
+ const headers = {};
24
+ c.req.raw.headers.forEach((value, key) => {
25
+ headers[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? "***" : value;
26
+ });
27
+ return headers;
28
+ }
29
+ function buildOnErrorContext(c, statusCode) {
30
+ const auth = c.get("auth");
31
+ return {
32
+ statusCode,
33
+ path: c.req.path,
34
+ method: c.req.method,
35
+ requestId: c.get("requestId"),
36
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
37
+ userId: auth?.userId,
38
+ request: {
39
+ headers: extractHeaders(c),
40
+ query: c.req.query()
41
+ }
42
+ };
43
+ }
8
44
  function logError(err, logData, includeStack) {
9
45
  const logLevel = logData.statusCode >= 500 ? "error" : "warn";
10
46
  if (includeStack) {
@@ -19,20 +55,29 @@ function isSerializableError(err) {
19
55
  function ErrorHandler(options = {}) {
20
56
  const {
21
57
  includeStack = env.NODE_ENV !== "production",
22
- enableLogging = true
58
+ enableLogging = true,
59
+ onError
23
60
  } = options;
24
61
  return (err, c) => {
62
+ const path = c.req.path;
63
+ const method = c.req.method;
64
+ const causeMessage = extractCauseMessage(err);
25
65
  if (isSerializableError(err)) {
26
66
  const { statusCode: statusCode2 } = err;
27
67
  if (enableLogging) {
28
68
  logError(err, {
29
69
  type: err.constructor.name,
30
70
  message: err.message,
71
+ cause: causeMessage,
31
72
  statusCode: statusCode2,
32
- path: c.req.path,
33
- method: c.req.method
73
+ path,
74
+ method
34
75
  }, includeStack);
35
76
  }
77
+ if (onError) {
78
+ const ctx = buildOnErrorContext(c, statusCode2);
79
+ Promise.resolve(onError(err, ctx)).catch((e) => errorLogger.warn("onError callback failed", e));
80
+ }
36
81
  const serialized = err.toJSON();
37
82
  if (includeStack && err.stack) {
38
83
  serialized.stack = err.stack;
@@ -45,15 +90,23 @@ function ErrorHandler(options = {}) {
45
90
  logError(err, {
46
91
  type: err.name || "Error",
47
92
  message: err.message,
93
+ cause: causeMessage,
48
94
  statusCode,
49
- path: c.req.path,
50
- method: c.req.method
95
+ path,
96
+ method
51
97
  }, includeStack);
52
98
  }
99
+ if (onError) {
100
+ const ctx = buildOnErrorContext(c, statusCode);
101
+ Promise.resolve(onError(err, ctx)).catch((e) => errorLogger.warn("onError callback failed", e));
102
+ }
53
103
  const response = {
54
104
  __type: "Error",
55
105
  message: err.message || "Internal Server Error"
56
106
  };
107
+ if (causeMessage) {
108
+ response.cause = causeMessage;
109
+ }
57
110
  if (includeStack && err.stack) {
58
111
  response.stack = err.stack;
59
112
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/error-handler.ts","../../src/middleware/request-logger.ts"],"names":["statusCode","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error Handler Middleware\n *\n * Handles SerializableError with automatic serialization and standard errors\n */\nimport type { Context } from 'hono';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { SerializableError } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\n\nconst errorLogger = logger.child('@spfn/core:error-handler');\n\n/**\n * Options for ErrorHandler middleware\n */\nexport interface ErrorHandlerOptions\n{\n /**\n * Include stack trace in error response\n *\n * Useful for debugging in development, should be disabled in production.\n *\n * @default env.NODE_ENV !== 'production'\n */\n includeStack?: boolean;\n\n /**\n * Enable error logging to console\n *\n * Logs errors with appropriate level (warn for 4xx, error for 5xx).\n *\n * @default true\n */\n enableLogging?: boolean;\n}\n\ninterface ErrorWithStatusCode extends Error\n{\n statusCode?: number;\n details?: Record<string, unknown>;\n}\n\ninterface SerializableErrorLike extends Error\n{\n statusCode: number;\n toJSON(): Record<string, unknown>;\n}\n\ninterface ErrorLogData extends Record<string, unknown>\n{\n type: string;\n message: string;\n statusCode: number;\n path: string;\n method: string;\n}\n\ninterface StandardErrorResponse\n{\n __type: string;\n message: string;\n stack?: string;\n}\n\n/**\n * Log error with appropriate level based on status code\n */\nfunction logError(\n err: Error,\n logData: ErrorLogData,\n includeStack: boolean\n): void\n{\n const logLevel = logData.statusCode >= 500 ? 'error' : 'warn';\n\n if (includeStack)\n {\n errorLogger[logLevel]('Error occurred', err, logData);\n }\n else\n {\n errorLogger[logLevel]('Error occurred', logData);\n }\n}\n\n/**\n * Type guard for SerializableError\n *\n * Uses duck typing to handle module duplication issues in dev mode (tsx)\n */\nfunction isSerializableError(err: Error): err is SerializableErrorLike\n{\n return err instanceof SerializableError ||\n (typeof (err as any).toJSON === 'function' &&\n typeof (err as any).statusCode === 'number');\n}\n\n/**\n * Error handler middleware for Hono\n *\n * Handles SerializableError with automatic serialization and standard errors.\n * SerializableError instances are serialized using their toJSON() method,\n * preserving custom fields like `resource`, `fields`, etc.\n *\n * @param options - Configuration options\n * @returns Error handler function for Hono's onError hook\n *\n * @example\n * ```typescript\n * import { Hono } from 'hono';\n * import { ErrorHandler } from '@spfn/core/middleware';\n *\n * const app = new Hono();\n *\n * // Register error handler\n * app.onError(ErrorHandler({\n * includeStack: process.env.NODE_ENV !== 'production',\n * enableLogging: true,\n * }));\n *\n * // Throw SerializableError in routes\n * app.get('/users/:id', (c) => {\n * throw new NotFoundError({ message: 'User not found', resource: 'User' });\n * // Response: { __type: 'NotFoundError', message: 'User not found', resource: 'User' }\n * });\n * ```\n */\nexport function ErrorHandler(options: ErrorHandlerOptions = {}): (err: Error, c: Context) => Response | Promise<Response>\n{\n const {\n includeStack = env.NODE_ENV !== 'production',\n enableLogging = true,\n } = options;\n\n return (err: Error, c: Context) =>\n {\n // Handle SerializableError with automatic serialization\n if (isSerializableError(err))\n {\n const { statusCode } = err;\n\n if (enableLogging)\n {\n logError(err, {\n type: err.constructor.name,\n message: err.message,\n statusCode,\n path: c.req.path,\n method: c.req.method,\n }, includeStack);\n }\n\n // Use toJSON() for automatic serialization\n const serialized = err.toJSON();\n\n // Add stack trace in development\n if (includeStack && err.stack)\n {\n serialized.stack = err.stack;\n }\n\n return c.json(serialized, statusCode as ContentfulStatusCode);\n }\n\n // Handle standard errors (fallback)\n const errorWithCode = err as ErrorWithStatusCode;\n const statusCode = errorWithCode.statusCode || 500;\n\n if (enableLogging)\n {\n logError(err, {\n type: err.name || 'Error',\n message: err.message,\n statusCode,\n path: c.req.path,\n method: c.req.method,\n }, includeStack);\n }\n\n // Standard error response\n const response: StandardErrorResponse = {\n __type: 'Error',\n message: err.message || 'Internal Server Error',\n };\n\n if (includeStack && err.stack)\n {\n response.stack = err.stack;\n }\n\n return c.json(response, statusCode as ContentfulStatusCode);\n };\n}\n","/**\n * Request Logger Middleware\n *\n * Automatic API request/response logging with performance monitoring\n */\nimport { randomBytes } from 'crypto';\nimport type { Context, Next } from 'hono';\nimport { logger } from '@spfn/core/logger';\n\n/**\n * Options for RequestLogger middleware\n */\nexport interface RequestLoggerOptions\n{\n /**\n * Paths to exclude from logging\n *\n * Supports exact match and prefix match (e.g., '/health' excludes '/health/db').\n *\n * @default ['/health', '/ping', '/favicon.ico']\n *\n * @example\n * ```typescript\n * excludePaths: ['/health', '/metrics', '/_next']\n * ```\n */\n excludePaths?: string[];\n\n /**\n * Field names to mask in logged request bodies\n *\n * Case-insensitive partial matching (e.g., 'password' masks 'userPassword').\n *\n * @default ['password', 'token', 'apiKey', 'secret', 'authorization']\n *\n * @example\n * ```typescript\n * sensitiveFields: ['password', 'creditCard', 'ssn']\n * ```\n */\n sensitiveFields?: string[];\n\n /**\n * Threshold in milliseconds for marking requests as slow\n *\n * Slow requests are logged with `slow: true` flag.\n *\n * @default 1000\n */\n slowRequestThreshold?: number;\n}\n\n/**\n * @deprecated Use RequestLoggerOptions instead\n */\nexport type RequestLoggerConfig = RequestLoggerOptions;\n\nconst DEFAULT_CONFIG: Required<RequestLoggerConfig> = {\n excludePaths: ['/health', '/ping', '/favicon.ico'],\n sensitiveFields: ['password', 'token', 'apiKey', 'secret', 'authorization'],\n slowRequestThreshold: 1000,\n};\n\n/**\n * Generate cryptographically secure request ID\n */\nfunction generateRequestId(): string\n{\n const timestamp = Date.now();\n const randomPart = randomBytes(6).toString('hex');\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Mask sensitive data with circular reference handling\n */\nexport function maskSensitiveData(\n obj: any,\n sensitiveFields: string[],\n seen = new WeakSet()\n): any\n{\n if (!obj || typeof obj !== 'object') return obj;\n\n if (seen.has(obj)) return '[Circular]';\n seen.add(obj);\n\n const lowerFields = sensitiveFields.map(f => f.toLowerCase());\n const masked = Array.isArray(obj) ? [...obj] : { ...obj };\n\n for (const key in masked)\n {\n const lowerKey = key.toLowerCase();\n\n if (lowerFields.some(field => lowerKey.includes(field)))\n {\n masked[key] = '***MASKED***';\n }\n else if (typeof masked[key] === 'object' && masked[key] !== null)\n {\n masked[key] = maskSensitiveData(masked[key], sensitiveFields, seen);\n }\n }\n\n return masked;\n}\n\n/**\n * Request logger middleware for Hono\n *\n * Logs incoming requests with method, path, IP, and user agent.\n * Logs completed requests with status code and duration.\n * Automatically generates unique request IDs and masks sensitive data.\n *\n * @param options - Configuration options\n * @returns Hono middleware function\n *\n * @example\n * ```typescript\n * import { Hono } from 'hono';\n * import { RequestLogger } from '@spfn/core/middleware';\n *\n * const app = new Hono();\n *\n * // Add request logging\n * app.use(RequestLogger({\n * excludePaths: ['/health', '/metrics'],\n * sensitiveFields: ['password', 'token'],\n * slowRequestThreshold: 2000,\n * }));\n *\n * // Access request ID in handlers\n * app.get('/users', (c) => {\n * const requestId = c.get('requestId');\n * return c.json({ requestId });\n * });\n * ```\n */\nexport function RequestLogger(options?: RequestLoggerOptions)\n{\n const cfg = { ...DEFAULT_CONFIG, ...options };\n const apiLogger = logger.child('@spfn/core:api');\n\n return async (c: Context, next: Next) =>\n {\n const path = new URL(c.req.url).pathname;\n\n // Support both exact match and prefix match for excluded paths\n const isExcluded = cfg.excludePaths.some(excludePath =>\n path === excludePath || path.startsWith(excludePath + '/')\n );\n\n if (isExcluded)\n {\n return next();\n }\n\n const requestId = generateRequestId();\n c.set('requestId', requestId);\n\n const method = c.req.method;\n const userAgent = c.req.header('user-agent');\n\n // Extract client IP from proxy chain (first IP is the original client)\n const forwardedFor = c.req.header('x-forwarded-for');\n const ip = forwardedFor?.split(',')[0]?.trim()\n || c.req.header('x-real-ip')\n || 'unknown';\n\n const startTime = Date.now();\n\n apiLogger.info('Request received', {\n requestId,\n method,\n path,\n ip,\n userAgent,\n });\n\n try\n {\n await next();\n\n const duration = Date.now() - startTime;\n const status = c.res.status;\n\n const logData: Record<string, any> = {\n requestId,\n method,\n path,\n status,\n duration,\n };\n\n const isSlowRequest = duration >= cfg.slowRequestThreshold;\n if (isSlowRequest)\n {\n logData.slow = true;\n }\n\n // Add detailed error information for 4xx/5xx responses\n if (status >= 400)\n {\n try\n {\n // Clone response to read body without consuming it\n logData.response = await c.res.clone().json();\n }\n catch\n {\n // Response is not JSON or already consumed - ignore\n }\n\n // Add request body for POST/PUT/PATCH to see what data caused the error\n if (['POST', 'PUT', 'PATCH'].includes(method))\n {\n try\n {\n // Try to get the already parsed body from context\n const requestBody = await c.req.json();\n logData.request = maskSensitiveData(requestBody, cfg.sensitiveFields);\n }\n catch\n {\n // Body is not JSON or already consumed - ignore\n }\n }\n }\n\n const logLevel = status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info';\n apiLogger[logLevel]('Request completed', logData);\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n\n apiLogger.error('Request failed', error as Error, {\n requestId,\n method,\n path,\n duration,\n });\n\n throw error;\n }\n };\n}"]}
1
+ {"version":3,"sources":["../../src/middleware/error-handler.ts","../../src/middleware/request-logger.ts"],"names":["statusCode","logger"],"mappings":";;;;;;AAWA,IAAM,WAAA,GAAc,MAAA,CAAO,KAAA,CAAM,0BAA0B,CAAA;AAuF3D,SAAS,oBAAoB,GAAA,EAC7B;AACI,EAAA,MAAM,QAAS,GAAA,CAAY,KAAA;AAE3B,EAAA,IAAI,CAAC,KAAA,EACL;AACI,IAAA,OAAO,MAAA;AAAA,EACX;AAEA,EAAA,IAAI,iBAAiB,KAAA,EACrB;AACI,IAAA,OAAO,KAAA,CAAM,OAAA;AAAA,EACjB;AAEA,EAAA,IAAI,OAAO,UAAU,QAAA,EACrB;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,OAAO,KAAK,CAAA;AACvB;AAEA,IAAM,iBAAA,uBAAwB,GAAA,CAAI,CAAC,iBAAiB,QAAA,EAAU,WAAA,EAAa,cAAc,CAAC,CAAA;AAK1F,SAAS,eAAe,CAAA,EACxB;AACI,EAAA,MAAM,UAAkC,EAAC;AAEzC,EAAA,CAAA,CAAE,IAAI,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,CAAC,OAAO,GAAA,KAClC;AACI,IAAA,OAAA,CAAQ,GAAG,IAAI,iBAAA,CAAkB,GAAA,CAAI,IAAI,WAAA,EAAa,IAAI,KAAA,GAAQ,KAAA;AAAA,EACtE,CAAC,CAAA;AAED,EAAA,OAAO,OAAA;AACX;AAKA,SAAS,mBAAA,CAAoB,GAAY,UAAA,EACzC;AACI,EAAA,MAAM,IAAA,GAAO,CAAA,CAAE,GAAA,CAAI,MAAM,CAAA;AAEzB,EAAA,OAAO;AAAA,IACH,UAAA;AAAA,IACA,IAAA,EAAM,EAAE,GAAA,CAAI,IAAA;AAAA,IACZ,MAAA,EAAQ,EAAE,GAAA,CAAI,MAAA;AAAA,IACd,SAAA,EAAW,CAAA,CAAE,GAAA,CAAI,WAAW,CAAA;AAAA,IAC5B,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,IAClC,QAAQ,IAAA,EAAM,MAAA;AAAA,IACd,OAAA,EAAS;AAAA,MACL,OAAA,EAAS,eAAe,CAAC,CAAA;AAAA,MACzB,KAAA,EAAO,CAAA,CAAE,GAAA,CAAI,KAAA;AAAM;AACvB,GACJ;AACJ;AAKA,SAAS,QAAA,CACL,GAAA,EACA,OAAA,EACA,YAAA,EAEJ;AACI,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,UAAA,IAAc,GAAA,GAAM,OAAA,GAAU,MAAA;AAEvD,EAAA,IAAI,YAAA,EACJ;AACI,IAAA,WAAA,CAAY,QAAQ,CAAA,CAAE,gBAAA,EAAkB,GAAA,EAAK,OAAO,CAAA;AAAA,EACxD,CAAA,MAEA;AACI,IAAA,WAAA,CAAY,QAAQ,CAAA,CAAE,gBAAA,EAAkB,OAAO,CAAA;AAAA,EACnD;AACJ;AAOA,SAAS,oBAAoB,GAAA,EAC7B;AACI,EAAA,OAAO,GAAA,YAAe,qBACjB,OAAQ,GAAA,CAAY,WAAW,UAAA,IAC/B,OAAQ,IAAY,UAAA,KAAe,QAAA;AAC5C;AAgCO,SAAS,YAAA,CAAa,OAAA,GAA+B,EAAC,EAC7D;AACI,EAAA,MAAM;AAAA,IACF,YAAA,GAAe,IAAI,QAAA,KAAa,YAAA;AAAA,IAChC,aAAA,GAAgB,IAAA;AAAA,IAChB;AAAA,GACJ,GAAI,OAAA;AAEJ,EAAA,OAAO,CAAC,KAAY,CAAA,KACpB;AACI,IAAA,MAAM,IAAA,GAAO,EAAE,GAAA,CAAI,IAAA;AACnB,IAAA,MAAM,MAAA,GAAS,EAAE,GAAA,CAAI,MAAA;AAErB,IAAA,MAAM,YAAA,GAAe,oBAAoB,GAAG,CAAA;AAG5C,IAAA,IAAI,mBAAA,CAAoB,GAAG,CAAA,EAC3B;AACI,MAAA,MAAM,EAAE,UAAA,EAAAA,WAAAA,EAAW,GAAI,GAAA;AAEvB,MAAA,IAAI,aAAA,EACJ;AACI,QAAA,QAAA,CAAS,GAAA,EAAK;AAAA,UACV,IAAA,EAAM,IAAI,WAAA,CAAY,IAAA;AAAA,UACtB,SAAS,GAAA,CAAI,OAAA;AAAA,UACb,KAAA,EAAO,YAAA;AAAA,UACP,UAAA,EAAAA,WAAAA;AAAA,UACA,IAAA;AAAA,UACA;AAAA,WACD,YAAY,CAAA;AAAA,MACnB;AAGA,MAAA,IAAI,OAAA,EACJ;AACI,QAAA,MAAM,GAAA,GAAM,mBAAA,CAAoB,CAAA,EAAGA,WAAU,CAAA;AAC7C,QAAA,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAC,CAAA,CAC5B,KAAA,CAAM,CAAA,CAAA,KAAK,WAAA,CAAY,IAAA,CAAK,yBAAA,EAA2B,CAAU,CAAC,CAAA;AAAA,MAC3E;AAGA,MAAA,MAAM,UAAA,GAAa,IAAI,MAAA,EAAO;AAG9B,MAAA,IAAI,YAAA,IAAgB,IAAI,KAAA,EACxB;AACI,QAAA,UAAA,CAAW,QAAQ,GAAA,CAAI,KAAA;AAAA,MAC3B;AAEA,MAAA,OAAO,CAAA,CAAE,IAAA,CAAK,UAAA,EAAYA,WAAkC,CAAA;AAAA,IAChE;AAGA,IAAA,MAAM,aAAA,GAAgB,GAAA;AACtB,IAAA,MAAM,UAAA,GAAa,cAAc,UAAA,IAAc,GAAA;AAE/C,IAAA,IAAI,aAAA,EACJ;AACI,MAAA,QAAA,CAAS,GAAA,EAAK;AAAA,QACV,IAAA,EAAM,IAAI,IAAA,IAAQ,OAAA;AAAA,QAClB,SAAS,GAAA,CAAI,OAAA;AAAA,QACb,KAAA,EAAO,YAAA;AAAA,QACP,UAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,SACD,YAAY,CAAA;AAAA,IACnB;AAGA,IAAA,IAAI,OAAA,EACJ;AACI,MAAA,MAAM,GAAA,GAAM,mBAAA,CAAoB,CAAA,EAAG,UAAU,CAAA;AAC7C,MAAA,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAC,CAAA,CAC5B,KAAA,CAAM,CAAA,CAAA,KAAK,WAAA,CAAY,IAAA,CAAK,yBAAA,EAA2B,CAAU,CAAC,CAAA;AAAA,IAC3E;AAGA,IAAA,MAAM,QAAA,GAAkC;AAAA,MACpC,MAAA,EAAQ,OAAA;AAAA,MACR,OAAA,EAAS,IAAI,OAAA,IAAW;AAAA,KAC5B;AAEA,IAAA,IAAI,YAAA,EACJ;AACI,MAAA,QAAA,CAAS,KAAA,GAAQ,YAAA;AAAA,IACrB;AAEA,IAAA,IAAI,YAAA,IAAgB,IAAI,KAAA,EACxB;AACI,MAAA,QAAA,CAAS,QAAQ,GAAA,CAAI,KAAA;AAAA,IACzB;AAEA,IAAA,OAAO,CAAA,CAAE,IAAA,CAAK,QAAA,EAAU,UAAkC,CAAA;AAAA,EAC9D,CAAA;AACJ;AClQA,IAAM,cAAA,GAAgD;AAAA,EAClD,YAAA,EAAc,CAAC,SAAA,EAAW,OAAA,EAAS,cAAc,CAAA;AAAA,EACjD,iBAAiB,CAAC,UAAA,EAAY,OAAA,EAAS,QAAA,EAAU,UAAU,eAAe,CAAA;AAAA,EAC1E,oBAAA,EAAsB;AAC1B,CAAA;AAKA,SAAS,iBAAA,GACT;AACI,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,EAAA,MAAM,UAAA,GAAa,WAAA,CAAY,CAAC,CAAA,CAAE,SAAS,KAAK,CAAA;AAChD,EAAA,OAAO,CAAA,IAAA,EAAO,SAAS,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA;AACzC;AAKO,SAAS,kBACZ,GAAA,EACA,eAAA,EACA,IAAA,mBAAO,IAAI,SAAQ,EAEvB;AACI,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAU,OAAO,GAAA;AAE5C,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,YAAA;AAC1B,EAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AAEZ,EAAA,MAAM,cAAc,eAAA,CAAgB,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,aAAa,CAAA;AAC5D,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,CAAC,GAAG,GAAG,CAAA,GAAI,EAAE,GAAG,GAAA,EAAI;AAExD,EAAA,KAAA,MAAW,OAAO,MAAA,EAClB;AACI,IAAA,MAAM,QAAA,GAAW,IAAI,WAAA,EAAY;AAEjC,IAAA,IAAI,YAAY,IAAA,CAAK,CAAA,KAAA,KAAS,SAAS,QAAA,CAAS,KAAK,CAAC,CAAA,EACtD;AACI,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,cAAA;AAAA,IAClB,CAAA,MAAA,IACS,OAAO,MAAA,CAAO,GAAG,MAAM,QAAA,IAAY,MAAA,CAAO,GAAG,CAAA,KAAM,IAAA,EAC5D;AACI,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,iBAAA,CAAkB,OAAO,GAAG,CAAA,EAAG,iBAAiB,IAAI,CAAA;AAAA,IACtE;AAAA,EACJ;AAEA,EAAA,OAAO,MAAA;AACX;AAiCO,SAAS,cAAc,OAAA,EAC9B;AACI,EAAA,MAAM,GAAA,GAAM,EAAE,GAAG,cAAA,EAAgB,GAAG,OAAA,EAAQ;AAC5C,EAAA,MAAM,SAAA,GAAYC,MAAAA,CAAO,KAAA,CAAM,gBAAgB,CAAA;AAE/C,EAAA,OAAO,OAAO,GAAY,IAAA,KAC1B;AACI,IAAA,MAAM,OAAO,IAAI,GAAA,CAAI,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA;AAGhC,IAAA,MAAM,UAAA,GAAa,IAAI,YAAA,CAAa,IAAA;AAAA,MAAK,iBACrC,IAAA,KAAS,WAAA,IAAe,IAAA,CAAK,UAAA,CAAW,cAAc,GAAG;AAAA,KAC7D;AAEA,IAAA,IAAI,UAAA,EACJ;AACI,MAAA,OAAO,IAAA,EAAK;AAAA,IAChB;AAEA,IAAA,MAAM,YAAY,iBAAA,EAAkB;AACpC,IAAA,CAAA,CAAE,GAAA,CAAI,aAAa,SAAS,CAAA;AAE5B,IAAA,MAAM,MAAA,GAAS,EAAE,GAAA,CAAI,MAAA;AACrB,IAAA,MAAM,SAAA,GAAY,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,YAAY,CAAA;AAG3C,IAAA,MAAM,YAAA,GAAe,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,iBAAiB,CAAA;AACnD,IAAA,MAAM,EAAA,GAAK,YAAA,EAAc,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,EAAG,IAAA,EAAK,IACtC,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,WAAW,CAAA,IACxB,SAAA;AAEP,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,IAAA,SAAA,CAAU,KAAK,kBAAA,EAAoB;AAAA,MAC/B,SAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA;AAAA,MACA,EAAA;AAAA,MACA;AAAA,KACH,CAAA;AAED,IAAA,IACA;AACI,MAAA,MAAM,IAAA,EAAK;AAEX,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAC9B,MAAA,MAAM,MAAA,GAAS,EAAE,GAAA,CAAI,MAAA;AAErB,MAAA,MAAM,OAAA,GAA+B;AAAA,QACjC,SAAA;AAAA,QACA,MAAA;AAAA,QACA,IAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACJ;AAEA,MAAA,MAAM,aAAA,GAAgB,YAAY,GAAA,CAAI,oBAAA;AACtC,MAAA,IAAI,aAAA,EACJ;AACI,QAAA,OAAA,CAAQ,IAAA,GAAO,IAAA;AAAA,MACnB;AAGA,MAAA,IAAI,UAAU,GAAA,EACd;AACI,QAAA,IACA;AAEI,UAAA,OAAA,CAAQ,WAAW,MAAM,CAAA,CAAE,GAAA,CAAI,KAAA,GAAQ,IAAA,EAAK;AAAA,QAChD,CAAA,CAAA,MAEA;AAAA,QAEA;AAGA,QAAA,IAAI,CAAC,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAC5C;AACI,UAAA,IACA;AAEI,YAAA,MAAM,WAAA,GAAc,MAAM,CAAA,CAAE,GAAA,CAAI,IAAA,EAAK;AACrC,YAAA,OAAA,CAAQ,OAAA,GAAU,iBAAA,CAAkB,WAAA,EAAa,GAAA,CAAI,eAAe,CAAA;AAAA,UACxE,CAAA,CAAA,MAEA;AAAA,UAEA;AAAA,QACJ;AAAA,MACJ;AAEA,MAAA,MAAM,WAAW,MAAA,IAAU,GAAA,GAAM,OAAA,GAAU,MAAA,IAAU,MAAM,MAAA,GAAS,MAAA;AACpE,MAAA,SAAA,CAAU,QAAQ,CAAA,CAAE,mBAAA,EAAqB,OAAO,CAAA;AAAA,IACpD,SACO,KAAA,EACP;AACI,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAE9B,MAAA,SAAA,CAAU,KAAA,CAAM,kBAAkB,KAAA,EAAgB;AAAA,QAC9C,SAAA;AAAA,QACA,MAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,OACH,CAAA;AAED,MAAA,MAAM,KAAA;AAAA,IACV;AAAA,EACJ,CAAA;AACJ","file":"index.js","sourcesContent":["/**\n * Error Handler Middleware\n *\n * Handles SerializableError with automatic serialization and standard errors\n */\nimport type { Context } from 'hono';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { SerializableError } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\n\nconst errorLogger = logger.child('@spfn/core:error-handler');\n\n/**\n * Options for ErrorHandler middleware\n */\nexport interface ErrorHandlerOptions\n{\n /**\n * Include stack trace in error response\n *\n * Useful for debugging in development, should be disabled in production.\n *\n * @default env.NODE_ENV !== 'production'\n */\n includeStack?: boolean;\n\n /**\n * Enable error logging to console\n *\n * Logs errors with appropriate level (warn for 4xx, error for 5xx).\n *\n * @default true\n */\n enableLogging?: boolean;\n\n /**\n * Callback invoked when an error occurs\n *\n * Called asynchronously without blocking the response.\n * Useful for external error notifications (Slack, PagerDuty, etc.)\n */\n onError?: (\n err: Error,\n context: OnErrorContext\n ) => Promise<void> | void;\n}\n\n/**\n * Context passed to onError callback\n */\nexport interface OnErrorContext\n{\n statusCode: number;\n path: string;\n method: string;\n requestId?: string;\n timestamp: string;\n userId?: string;\n request: {\n headers: Record<string, string>;\n query: Record<string, string>;\n };\n}\n\ninterface ErrorWithStatusCode extends Error\n{\n statusCode?: number;\n details?: Record<string, unknown>;\n}\n\ninterface SerializableErrorLike extends Error\n{\n statusCode: number;\n toJSON(): Record<string, unknown>;\n}\n\ninterface ErrorLogData extends Record<string, unknown>\n{\n type: string;\n message: string;\n statusCode: number;\n path: string;\n method: string;\n cause?: string;\n}\n\ninterface StandardErrorResponse\n{\n __type: string;\n message: string;\n cause?: string;\n stack?: string;\n}\n\n/**\n * Extract root cause message from nested Error.cause chain\n */\nfunction extractCauseMessage(err: Error): string | undefined\n{\n const cause = (err as any).cause;\n\n if (!cause)\n {\n return undefined;\n }\n\n if (cause instanceof Error)\n {\n return cause.message;\n }\n\n if (typeof cause === 'string')\n {\n return cause;\n }\n\n return String(cause);\n}\n\nconst SENSITIVE_HEADERS = new Set(['authorization', 'cookie', 'x-api-key', 'x-auth-token']);\n\n/**\n * Extract headers from request, masking sensitive values\n */\nfunction extractHeaders(c: Context): Record<string, string>\n{\n const headers: Record<string, string> = {};\n\n c.req.raw.headers.forEach((value, key) =>\n {\n headers[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? '***' : value;\n });\n\n return headers;\n}\n\n/**\n * Build onError context from Hono context\n */\nfunction buildOnErrorContext(c: Context, statusCode: number): OnErrorContext\n{\n const auth = c.get('auth') as { userId?: string } | undefined;\n\n return {\n statusCode,\n path: c.req.path,\n method: c.req.method,\n requestId: c.get('requestId') as string | undefined,\n timestamp: new Date().toISOString(),\n userId: auth?.userId,\n request: {\n headers: extractHeaders(c),\n query: c.req.query(),\n },\n };\n}\n\n/**\n * Log error with appropriate level based on status code\n */\nfunction logError(\n err: Error,\n logData: ErrorLogData,\n includeStack: boolean\n): void\n{\n const logLevel = logData.statusCode >= 500 ? 'error' : 'warn';\n\n if (includeStack)\n {\n errorLogger[logLevel]('Error occurred', err, logData);\n }\n else\n {\n errorLogger[logLevel]('Error occurred', logData);\n }\n}\n\n/**\n * Type guard for SerializableError\n *\n * Uses duck typing to handle module duplication issues in dev mode (tsx)\n */\nfunction isSerializableError(err: Error): err is SerializableErrorLike\n{\n return err instanceof SerializableError ||\n (typeof (err as any).toJSON === 'function' &&\n typeof (err as any).statusCode === 'number');\n}\n\n/**\n * Error handler middleware for Hono\n *\n * Handles SerializableError with automatic serialization and standard errors.\n * SerializableError instances are serialized using their toJSON() method,\n * preserving custom fields like `resource`, `fields`, etc.\n *\n * @param options - Configuration options\n * @returns Error handler function for Hono's onError hook\n *\n * @example\n * ```typescript\n * import { Hono } from 'hono';\n * import { ErrorHandler } from '@spfn/core/middleware';\n *\n * const app = new Hono();\n *\n * // Register error handler\n * app.onError(ErrorHandler({\n * includeStack: process.env.NODE_ENV !== 'production',\n * enableLogging: true,\n * }));\n *\n * // Throw SerializableError in routes\n * app.get('/users/:id', (c) => {\n * throw new NotFoundError({ message: 'User not found', resource: 'User' });\n * // Response: { __type: 'NotFoundError', message: 'User not found', resource: 'User' }\n * });\n * ```\n */\nexport function ErrorHandler(options: ErrorHandlerOptions = {}): (err: Error, c: Context) => Response | Promise<Response>\n{\n const {\n includeStack = env.NODE_ENV !== 'production',\n enableLogging = true,\n onError,\n } = options;\n\n return (err: Error, c: Context) =>\n {\n const path = c.req.path;\n const method = c.req.method;\n\n const causeMessage = extractCauseMessage(err);\n\n // Handle SerializableError with automatic serialization\n if (isSerializableError(err))\n {\n const { statusCode } = err;\n\n if (enableLogging)\n {\n logError(err, {\n type: err.constructor.name,\n message: err.message,\n cause: causeMessage,\n statusCode,\n path,\n method,\n }, includeStack);\n }\n\n // Fire onError callback (non-blocking)\n if (onError)\n {\n const ctx = buildOnErrorContext(c, statusCode);\n Promise.resolve(onError(err, ctx))\n .catch(e => errorLogger.warn('onError callback failed', e as Error));\n }\n\n // Use toJSON() for automatic serialization\n const serialized = err.toJSON();\n\n // Add stack trace in development\n if (includeStack && err.stack)\n {\n serialized.stack = err.stack;\n }\n\n return c.json(serialized, statusCode as ContentfulStatusCode);\n }\n\n // Handle standard errors (fallback)\n const errorWithCode = err as ErrorWithStatusCode;\n const statusCode = errorWithCode.statusCode || 500;\n\n if (enableLogging)\n {\n logError(err, {\n type: err.name || 'Error',\n message: err.message,\n cause: causeMessage,\n statusCode,\n path,\n method,\n }, includeStack);\n }\n\n // Fire onError callback (non-blocking)\n if (onError)\n {\n const ctx = buildOnErrorContext(c, statusCode);\n Promise.resolve(onError(err, ctx))\n .catch(e => errorLogger.warn('onError callback failed', e as Error));\n }\n\n // Standard error response\n const response: StandardErrorResponse = {\n __type: 'Error',\n message: err.message || 'Internal Server Error',\n };\n\n if (causeMessage)\n {\n response.cause = causeMessage;\n }\n\n if (includeStack && err.stack)\n {\n response.stack = err.stack;\n }\n\n return c.json(response, statusCode as ContentfulStatusCode);\n };\n}\n","/**\n * Request Logger Middleware\n *\n * Automatic API request/response logging with performance monitoring\n */\nimport { randomBytes } from 'crypto';\nimport type { Context, Next } from 'hono';\nimport { logger } from '@spfn/core/logger';\n\n/**\n * Options for RequestLogger middleware\n */\nexport interface RequestLoggerOptions\n{\n /**\n * Paths to exclude from logging\n *\n * Supports exact match and prefix match (e.g., '/health' excludes '/health/db').\n *\n * @default ['/health', '/ping', '/favicon.ico']\n *\n * @example\n * ```typescript\n * excludePaths: ['/health', '/metrics', '/_next']\n * ```\n */\n excludePaths?: string[];\n\n /**\n * Field names to mask in logged request bodies\n *\n * Case-insensitive partial matching (e.g., 'password' masks 'userPassword').\n *\n * @default ['password', 'token', 'apiKey', 'secret', 'authorization']\n *\n * @example\n * ```typescript\n * sensitiveFields: ['password', 'creditCard', 'ssn']\n * ```\n */\n sensitiveFields?: string[];\n\n /**\n * Threshold in milliseconds for marking requests as slow\n *\n * Slow requests are logged with `slow: true` flag.\n *\n * @default 1000\n */\n slowRequestThreshold?: number;\n}\n\n/**\n * @deprecated Use RequestLoggerOptions instead\n */\nexport type RequestLoggerConfig = RequestLoggerOptions;\n\nconst DEFAULT_CONFIG: Required<RequestLoggerConfig> = {\n excludePaths: ['/health', '/ping', '/favicon.ico'],\n sensitiveFields: ['password', 'token', 'apiKey', 'secret', 'authorization'],\n slowRequestThreshold: 1000,\n};\n\n/**\n * Generate cryptographically secure request ID\n */\nfunction generateRequestId(): string\n{\n const timestamp = Date.now();\n const randomPart = randomBytes(6).toString('hex');\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Mask sensitive data with circular reference handling\n */\nexport function maskSensitiveData(\n obj: any,\n sensitiveFields: string[],\n seen = new WeakSet()\n): any\n{\n if (!obj || typeof obj !== 'object') return obj;\n\n if (seen.has(obj)) return '[Circular]';\n seen.add(obj);\n\n const lowerFields = sensitiveFields.map(f => f.toLowerCase());\n const masked = Array.isArray(obj) ? [...obj] : { ...obj };\n\n for (const key in masked)\n {\n const lowerKey = key.toLowerCase();\n\n if (lowerFields.some(field => lowerKey.includes(field)))\n {\n masked[key] = '***MASKED***';\n }\n else if (typeof masked[key] === 'object' && masked[key] !== null)\n {\n masked[key] = maskSensitiveData(masked[key], sensitiveFields, seen);\n }\n }\n\n return masked;\n}\n\n/**\n * Request logger middleware for Hono\n *\n * Logs incoming requests with method, path, IP, and user agent.\n * Logs completed requests with status code and duration.\n * Automatically generates unique request IDs and masks sensitive data.\n *\n * @param options - Configuration options\n * @returns Hono middleware function\n *\n * @example\n * ```typescript\n * import { Hono } from 'hono';\n * import { RequestLogger } from '@spfn/core/middleware';\n *\n * const app = new Hono();\n *\n * // Add request logging\n * app.use(RequestLogger({\n * excludePaths: ['/health', '/metrics'],\n * sensitiveFields: ['password', 'token'],\n * slowRequestThreshold: 2000,\n * }));\n *\n * // Access request ID in handlers\n * app.get('/users', (c) => {\n * const requestId = c.get('requestId');\n * return c.json({ requestId });\n * });\n * ```\n */\nexport function RequestLogger(options?: RequestLoggerOptions)\n{\n const cfg = { ...DEFAULT_CONFIG, ...options };\n const apiLogger = logger.child('@spfn/core:api');\n\n return async (c: Context, next: Next) =>\n {\n const path = new URL(c.req.url).pathname;\n\n // Support both exact match and prefix match for excluded paths\n const isExcluded = cfg.excludePaths.some(excludePath =>\n path === excludePath || path.startsWith(excludePath + '/')\n );\n\n if (isExcluded)\n {\n return next();\n }\n\n const requestId = generateRequestId();\n c.set('requestId', requestId);\n\n const method = c.req.method;\n const userAgent = c.req.header('user-agent');\n\n // Extract client IP from proxy chain (first IP is the original client)\n const forwardedFor = c.req.header('x-forwarded-for');\n const ip = forwardedFor?.split(',')[0]?.trim()\n || c.req.header('x-real-ip')\n || 'unknown';\n\n const startTime = Date.now();\n\n apiLogger.info('Request received', {\n requestId,\n method,\n path,\n ip,\n userAgent,\n });\n\n try\n {\n await next();\n\n const duration = Date.now() - startTime;\n const status = c.res.status;\n\n const logData: Record<string, any> = {\n requestId,\n method,\n path,\n status,\n duration,\n };\n\n const isSlowRequest = duration >= cfg.slowRequestThreshold;\n if (isSlowRequest)\n {\n logData.slow = true;\n }\n\n // Add detailed error information for 4xx/5xx responses\n if (status >= 400)\n {\n try\n {\n // Clone response to read body without consuming it\n logData.response = await c.res.clone().json();\n }\n catch\n {\n // Response is not JSON or already consumed - ignore\n }\n\n // Add request body for POST/PUT/PATCH to see what data caused the error\n if (['POST', 'PUT', 'PATCH'].includes(method))\n {\n try\n {\n // Try to get the already parsed body from context\n const requestBody = await c.req.json();\n logData.request = maskSensitiveData(requestBody, cfg.sensitiveFields);\n }\n catch\n {\n // Body is not JSON or already consumed - ignore\n }\n }\n }\n\n const logLevel = status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info';\n apiLogger[logLevel]('Request completed', logData);\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n\n apiLogger.error('Request failed', error as Error, {\n requestId,\n method,\n path,\n duration,\n });\n\n throw error;\n }\n };\n}"]}
@@ -1,6 +1,6 @@
1
1
  import { Router, RouteDef } from '@spfn/core/route';
2
- import { R as RequestInterceptor, a as ResponseInterceptor, I as InferRouteInput, b as InferRouteOutput, A as ApiConfig } from '../types-BVxUIkcU.js';
3
- export { C as CallOptions, c as CookieOptions, d as SetCookie, S as StructuredInput } from '../types-BVxUIkcU.js';
2
+ import { R as RequestInterceptor, a as ResponseInterceptor, I as InferRouteInput, b as InferRouteOutput, A as ApiConfig } from '../types-7Mhoxnnt.js';
3
+ export { C as CallOptions, e as CookieOptions, c as RouterInput, d as RouterOutput, f as SetCookie, S as StructuredInput } from '../types-7Mhoxnnt.js';
4
4
  import '@sinclair/typebox';
5
5
  import '@spfn/core/errors';
6
6
 
@@ -84,6 +84,9 @@ function buildCookieHeader(cookies) {
84
84
  return Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
85
85
  }
86
86
  async function parseResponseBody(response) {
87
+ if (response.status === 204) {
88
+ return null;
89
+ }
87
90
  const contentType = response.headers.get("content-type");
88
91
  if (contentType?.includes("application/json")) {
89
92
  const text = await response.text();
@@ -151,7 +154,7 @@ async function handleErrorResponse(response, body, fullUrl, errorRegistry, debug
151
154
  }
152
155
  if (response.status === 404 && process.env.NODE_ENV !== "production") {
153
156
  logger2.warn(
154
- "\n\u26A0\uFE0F 404 Not Found\n\nCheck if routes are registered in server.config.ts:\n \u2192 defineServerConfig().routes(appRouter)\n"
157
+ "\n\u26A0\uFE0F 404 Not Found\n\nCheck the following:\n 1. Routes are registered in server.config.ts:\n \u2192 defineServerConfig().routes(appRouter)\n 2. Delete .spfn cache if you recently added new routes:\n \u2192 rm -rf .spfn\n"
155
158
  );
156
159
  }
157
160
  throw new ApiError(
@@ -264,7 +267,7 @@ function createApi(config = {}) {
264
267
  const {
265
268
  baseUrl = "/api/rpc",
266
269
  headers: defaultHeaders = {},
267
- timeout = 3e4,
270
+ timeout = env.SERVER_TIMEOUT,
268
271
  fetch: customFetch = fetch,
269
272
  onRequest: globalOnRequest,
270
273
  onResponse: globalOnResponse,
@@ -277,7 +280,8 @@ function createApi(config = {}) {
277
280
  }
278
281
  async function executeCall(routeName, input = {}, options = {}) {
279
282
  const hasBody = input.body !== void 0;
280
- const method = hasBody ? "POST" : "GET";
283
+ const hasFormData = input.formData !== void 0 && Object.keys(input.formData).length > 0;
284
+ const method = hasBody || hasFormData ? "POST" : "GET";
281
285
  let appUrl = env.SPFN_APP_URL || "";
282
286
  if (!appUrl && typeof window === "undefined") {
283
287
  try {
@@ -305,7 +309,7 @@ function createApi(config = {}) {
305
309
  fullUrl = `${appUrl}${baseUrl}/${routeName}`;
306
310
  }
307
311
  const headers = {
308
- "Content-Type": "application/json",
312
+ ...hasFormData ? {} : { "Content-Type": "application/json" },
309
313
  ...defaultHeaders,
310
314
  ...options.headers
311
315
  };
@@ -327,7 +331,35 @@ function createApi(config = {}) {
327
331
  ...options.fetchOptions
328
332
  };
329
333
  if (method === "POST") {
330
- requestInit.body = JSON.stringify(input);
334
+ if (hasFormData) {
335
+ const formData = new FormData();
336
+ const metadata = {};
337
+ if (input.params) metadata.params = input.params;
338
+ if (input.query) metadata.query = input.query;
339
+ if (input.headers) metadata.headers = input.headers;
340
+ if (input.cookies) metadata.cookies = input.cookies;
341
+ if (Object.keys(metadata).length > 0) {
342
+ formData.append("__metadata", JSON.stringify(metadata));
343
+ }
344
+ for (const [key, value] of Object.entries(input.formData)) {
345
+ if (value instanceof File) {
346
+ formData.append(key, value);
347
+ } else if (Array.isArray(value)) {
348
+ for (const item of value) {
349
+ if (item instanceof File) {
350
+ formData.append(key, item);
351
+ } else {
352
+ formData.append(key, String(item));
353
+ }
354
+ }
355
+ } else if (value !== void 0 && value !== null) {
356
+ formData.append(key, String(value));
357
+ }
358
+ }
359
+ requestInit.body = formData;
360
+ } else {
361
+ requestInit.body = JSON.stringify(input);
362
+ }
331
363
  }
332
364
  let init = requestInit;
333
365
  if (globalOnRequest) {