@spfn/core 0.2.0-beta.57 → 0.2.0-beta.59
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.
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +415 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/config/index.d.ts +132 -0
- package/dist/config/index.js +29 -1
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +20 -1
- package/dist/db/index.js.map +1 -1
- package/dist/middleware/index.d.ts +56 -6
- package/dist/middleware/index.js +85 -4
- package/dist/middleware/index.js.map +1 -1
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +64 -1
- package/dist/server/index.js +59 -2
- package/dist/server/index.js.map +1 -1
- package/package.json +11 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/middleware/error-handler.ts","../../src/security/proxy-signature.ts","../../src/route/define-middleware.ts","../../src/cache/cache-factory.ts","../../src/cache/cache-manager.ts","../../src/errors/error-registry.ts","../../src/errors/serializable-error.ts","../../src/errors/http-errors.ts","../../src/errors/database-errors.ts","../../src/errors/index.ts","../../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","../../src/middleware/rate-limit.ts","../../src/middleware/request-logger.ts","../../src/middleware/proxy-guard.ts"],"names":["statusCode","logger","SerializableError","randomBytes","maskSensitiveData","env"],"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 /**\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\nconst SENSITIVE_QUERY_PARAMS = new Set([\n 'token', 'access_token', 'refresh_token', 'id_token', 'code',\n 'secret', 'client_secret', 'password', 'passwd', 'pwd',\n 'api_key', 'apikey', 'key', 'signature', 'sig',\n 'session', 'sessionid', 'auth', 'authorization',\n]);\n\n/**\n * Extract query params from request, masking sensitive values so credentials in\n * the URL (?token=…, ?code=…) don't reach error logs / the onError callback.\n */\nfunction extractQuery(c: Context): Record<string, string>\n{\n const query = c.req.query();\n const masked: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(query))\n {\n masked[key] = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()) ? '***' : value;\n }\n\n return masked;\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: extractQuery(c),\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 // Internal (DB-driver-derived) errors must not leak SQL text, schema\n // names, or parameter values to clients in production. Full detail is\n // kept in the log above; the client gets a generic message.\n if ((err as { internal?: boolean }).internal === true && !includeStack)\n {\n return c.json(\n { __type: err.constructor.name, message: 'Internal server error' },\n statusCode as ContentfulStatusCode,\n );\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 (non-SerializableError) errors are uncaught/internal — their\n // message and cause may contain raw SQL or driver text (e.g. an uncaught\n // Drizzle error), so only expose them in development.\n const response: StandardErrorResponse = {\n __type: 'Error',\n message: includeStack ? (err.message || 'Internal Server Error') : 'Internal Server Error',\n };\n\n if (causeMessage && includeStack)\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 * Proxy → Backend request signing (HMAC-SHA256) with key rotation\n *\n * Shared between the Next.js RPC proxy (which signs outbound requests) and the\n * backend proxy-guard middleware (which verifies them). Keeping the canonical\n * string and HMAC logic in one pure module guarantees both sides agree.\n *\n * The signature proves a request passed through a trusted proxy that holds the\n * shared secret. It does NOT authenticate the end user — that is the JWT layer's\n * job — and it cannot stop a client from calling the proxy itself. Its role is to\n * stop direct-to-backend calls that bypass the proxy.\n *\n * Keys are identified by a short keyId so secrets can be rotated without downtime:\n * the proxy signs with the active key and stamps its keyId in a header; the\n * backend keeps a set of accepted keys (current + previous) and selects by keyId.\n * Roll a new key in, let both sides pick it up, switch the proxy to it, then drop\n * the old one after a grace period — no request is ever rejected mid-rotation.\n *\n * @module security/proxy-signature\n */\nimport { createHmac, randomBytes, timingSafeEqual, createHash } from 'node:crypto';\n\n// ============================================================================\n// Header names\n// ============================================================================\n\nexport const PROXY_SIGNATURE_HEADER = 'x-spfn-proxy-signature';\nexport const PROXY_TIMESTAMP_HEADER = 'x-spfn-proxy-timestamp';\nexport const PROXY_NONCE_HEADER = 'x-spfn-proxy-nonce';\nexport const PROXY_KEY_ID_HEADER = 'x-spfn-proxy-key-id';\n/**\n * The real client IP, forwarded by the proxy. The backend trusts this only when\n * the request is proxy-verified (proxy-guard `clientType` ≠ untrusted) — the proxy\n * itself sees no spoofable hop, so a verified request's value is the true client.\n */\nexport const PROXY_CLIENT_IP_HEADER = 'x-spfn-proxy-client-ip';\n\n/** keyId used when a raw secret carries no `keyId:` prefix (back-compat). */\nexport const DEFAULT_KEY_ID = 'default';\n\n// ============================================================================\n// Keys\n// ============================================================================\n\n/**\n * A signing key: a short identifier plus its secret. Encoded in env as\n * `<keyId>:<secret>` (e.g. `v2:9f3c...`); a bare secret gets `DEFAULT_KEY_ID`.\n */\nexport interface ProxyKey\n{\n keyId: string;\n secret: string;\n}\n\n/**\n * Parse one `<keyId>:<secret>` (or bare `<secret>`) string into a ProxyKey.\n * keyId is everything before the FIRST colon; the secret keeps any later colons.\n */\nexport function parseProxyKey(raw: string): ProxyKey\n{\n const idx = raw.indexOf(':');\n if (idx === -1)\n {\n return { keyId: DEFAULT_KEY_ID, secret: raw };\n }\n\n // A leading colon (':secret') yields an empty keyId; the backend treats an empty\n // keyId header as \"missing\" and rejects, so normalize it to DEFAULT_KEY_ID.\n return { keyId: raw.slice(0, idx) || DEFAULT_KEY_ID, secret: raw.slice(idx + 1) };\n}\n\n/**\n * Build the backend's accepted key set from raw env values. Each input may be a\n * single key or a comma-separated list (for multiple grace keys). Earlier inputs\n * win on keyId collision, so pass the active secret first.\n */\nexport function parseProxyKeySet(raws: Array<string | undefined | null>): ProxyKey[]\n{\n const keys: ProxyKey[] = [];\n const seen = new Set<string>();\n\n for (const raw of raws)\n {\n if (!raw)\n {\n continue;\n }\n\n for (const part of raw.split(','))\n {\n const trimmed = part.trim();\n if (!trimmed)\n {\n continue;\n }\n\n const key = parseProxyKey(trimmed);\n if (!key.secret || seen.has(key.keyId))\n {\n continue;\n }\n\n seen.add(key.keyId);\n keys.push(key);\n }\n }\n\n return keys;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * The parts of a request that are bound into the signature.\n */\nexport interface SignatureParts\n{\n /** Resolved backend HTTP method (GET/POST/PUT/...) */\n method: string;\n\n /**\n * Raw (wire) backend path, percent-encoding preserved, leading slash, NO query.\n * Proxy signs its outbound request-target path; the backend reconstructs the\n * SAME bytes from `new URL(c.req.url).pathname` (NOT the decoded `c.req.path`).\n */\n path: string;\n\n /**\n * Raw (wire) query string including leading `?`, or '' when there is none.\n * Both sides use the verbatim search string so query params are authenticated.\n */\n query: string;\n\n /** Unix epoch milliseconds, as a string */\n timestamp: string;\n\n /** Random per-request nonce */\n nonce: string;\n\n /**\n * SHA-256 hex of the raw request body, or empty string when there is no body\n * (GET/HEAD, or multipart uploads which are excluded by design). Bound for ANY\n * non-multipart content-type, not just application/json.\n *\n * Multipart is excluded so the proxy can stream large uploads without buffering\n * them to hash. The signature therefore proves *provenance* (came through the\n * trusted proxy) and binds method/path/query/timestamp for a multipart request,\n * but NOT the body bytes. Two consequences, by design:\n * - Multipart *content* integrity/validation is the application's job (size,\n * type, checksum, business rules) — not the transport signature's.\n * - If you don't trust the proxy→backend hop itself, secure it with TLS/mTLS;\n * signing a prefix of the stream would be a half-measure (the tail stays\n * unprotected) and is intentionally not done.\n */\n bodyHash: string;\n}\n\n/**\n * Headers produced by signing — ready to set on the outbound fetch.\n */\nexport interface SignatureHeaders\n{\n [PROXY_SIGNATURE_HEADER]: string;\n [PROXY_TIMESTAMP_HEADER]: string;\n [PROXY_NONCE_HEADER]: string;\n [PROXY_KEY_ID_HEADER]: string;\n}\n\n// ============================================================================\n// Canonical string + HMAC\n// ============================================================================\n\n/**\n * Build the canonical string that gets signed. Field order is fixed and each\n * field is newline-separated, so a captured signature cannot be replayed against\n * a different method, path, or body.\n */\nexport function buildCanonicalString(parts: SignatureParts): string\n{\n return [parts.method.toUpperCase(), parts.path, parts.query, parts.timestamp, parts.nonce, parts.bodyHash].join('\\n');\n}\n\n/**\n * Hash a request body. Accepts a string (proxy side, from JSON.stringify) or a\n * Buffer (backend side, straight from the stream — avoids a bytes→string→bytes\n * round-trip). Returns '' for empty/no body. A utf8 string and its Buffer hash\n * identically, so both sides agree.\n */\nexport function hashBody(body: string | Buffer | undefined | null): string\n{\n if (!body || body.length === 0)\n {\n return '';\n }\n\n return typeof body === 'string'\n ? createHash('sha256').update(body, 'utf8').digest('hex')\n : createHash('sha256').update(body).digest('hex');\n}\n\n/**\n * Compute the HMAC-SHA256 signature (hex) for the given parts.\n */\nexport function computeSignature(secret: string, parts: SignatureParts): string\n{\n return createHmac('sha256', secret).update(buildCanonicalString(parts)).digest('hex');\n}\n\n/**\n * Generate a fresh random nonce (hex).\n */\nexport function generateNonce(): string\n{\n return randomBytes(16).toString('hex');\n}\n\n// ============================================================================\n// Sign (proxy side)\n// ============================================================================\n\nexport interface SignInput\n{\n /** The active signing key. */\n key: ProxyKey;\n method: string;\n /** Raw (wire) path, leading slash, percent-encoding preserved, NO query. */\n path: string;\n /** Raw (wire) query string including leading `?`, or '' / omitted for none. */\n query?: string;\n /** Raw request body string, if any. Omit for GET / multipart uploads. */\n body?: string | null;\n /** Override the timestamp (testing). Defaults to Date.now(). */\n timestamp?: string;\n /** Override the nonce (testing). Defaults to a random nonce. */\n nonce?: string;\n}\n\n/**\n * Sign an outbound proxy request and return the headers to attach (including the\n * keyId so the backend knows which key to verify against).\n */\nexport function signProxyRequest(input: SignInput): SignatureHeaders\n{\n const timestamp = input.timestamp ?? String(Date.now());\n const nonce = input.nonce ?? generateNonce();\n const signature = computeSignature(input.key.secret, {\n method: input.method,\n path: input.path,\n query: input.query ?? '',\n timestamp,\n nonce,\n bodyHash: hashBody(input.body),\n });\n\n return {\n [PROXY_SIGNATURE_HEADER]: signature,\n [PROXY_TIMESTAMP_HEADER]: timestamp,\n [PROXY_NONCE_HEADER]: nonce,\n [PROXY_KEY_ID_HEADER]: input.key.keyId,\n };\n}\n\n// ============================================================================\n// Verify (backend side)\n// ============================================================================\n\nexport type VerifyFailureReason =\n | 'missing-headers'\n | 'stale-timestamp'\n | 'bad-timestamp'\n | 'unknown-key'\n | 'signature-mismatch';\n\n// ('body-read-error' / 'origin-not-allowed' / 'nonce-replay' are guard-level\n// reasons carried separately by the middleware, not produced by verify itself.)\n\nexport interface VerifyResult\n{\n valid: boolean;\n reason?: VerifyFailureReason;\n /** The verified nonce, for optional replay tracking by the caller. */\n nonce?: string;\n /** The keyId the request was verified against (for logging/metrics). */\n keyId?: string;\n}\n\nexport interface VerifyInput\n{\n /** Accepted keys (active + grace). Selected by the request's keyId header. */\n keys: ProxyKey[];\n method: string;\n /** Raw (wire) path from `new URL(c.req.url).pathname` — NOT decoded `c.req.path`. */\n path: string;\n /** Raw (wire) query string from `new URL(c.req.url).search`, or '' for none. */\n query?: string;\n /** Raw request body (string or Buffer) as received, if any. */\n body?: string | Buffer | null;\n signature: string | null | undefined;\n timestamp: string | null | undefined;\n nonce: string | null | undefined;\n keyId: string | null | undefined;\n /** Allowed clock skew / replay window in ms. Default 30s. */\n windowMs?: number;\n /** Current time (testing). Defaults to Date.now(). */\n now?: number;\n}\n\n/**\n * Constant-time hex string comparison. Returns false on any length diff.\n *\n * Buffer.from(_, 'hex') does NOT throw on invalid hex — it silently truncates at\n * the first bad char — so a malformed signature yields a shorter buffer that the\n * length check rejects. No try/catch needed.\n */\nfunction safeEqualHex(a: string, b: string): boolean\n{\n const bufA = Buffer.from(a, 'hex');\n const bufB = Buffer.from(b, 'hex');\n\n if (bufA.length === 0 || bufA.length !== bufB.length)\n {\n return false;\n }\n\n return timingSafeEqual(bufA, bufB);\n}\n\n/**\n * Verify an inbound request's proxy signature against the accepted key set.\n *\n * Checks, in order: headers present, timestamp within window (replay guard),\n * keyId known, and HMAC match. Nonce-level replay rejection is left to the caller\n * (optional, Redis-backed) since not every deployment has a shared store.\n */\nexport function verifyProxyRequest(input: VerifyInput): VerifyResult\n{\n const { signature, timestamp, nonce, keyId } = input;\n\n if (!signature || !timestamp || !nonce || !keyId)\n {\n return { valid: false, reason: 'missing-headers' };\n }\n\n const ts = Number(timestamp);\n if (!Number.isFinite(ts))\n {\n return { valid: false, reason: 'bad-timestamp' };\n }\n\n const now = input.now ?? Date.now();\n const windowMs = input.windowMs ?? 30_000;\n if (Math.abs(now - ts) > windowMs)\n {\n return { valid: false, reason: 'stale-timestamp' };\n }\n\n const key = input.keys.find(k => k.keyId === keyId);\n if (!key)\n {\n return { valid: false, reason: 'unknown-key' };\n }\n\n const expected = computeSignature(key.secret, {\n method: input.method,\n path: input.path,\n query: input.query ?? '',\n timestamp,\n nonce,\n bodyHash: hashBody(input.body),\n });\n\n if (!safeEqualHex(signature, expected))\n {\n return { valid: false, reason: 'signature-mismatch' };\n }\n\n return { valid: true, nonce, keyId };\n}\n","/**\n * Middleware Definition Helper\n *\n * Provides type-safe middleware definition with name inference\n */\n\nimport type { MiddlewareHandler } from 'hono';\n\n/**\n * Named middleware with type inference\n *\n * @example\n * ```ts\n * export const authMiddleware = defineMiddleware('auth', async (c, next) => {\n * // auth logic\n * c.set('user', { id: 1 });\n * await next();\n * });\n *\n * export const rateLimitMiddleware = defineMiddleware('rateLimit', async (c, next) => {\n * // rate limit logic\n * await next();\n * });\n * ```\n */\nexport type NamedMiddleware<TName extends string = string> = {\n name: TName;\n handler: MiddlewareHandler;\n _name: TName; // Type inference helper\n skips?: string[]; // Server-level middlewares to auto-skip when this middleware is used\n};\n\n/**\n * Named middleware factory with type inference\n *\n * Factory function that creates middleware instances with parameters\n *\n * @example\n * ```ts\n * export const requirePermissions = defineMiddleware('permission',\n * (...permissions: string[]) => async (c, next) => {\n * // permission check logic\n * await next();\n * }\n * );\n * ```\n */\nexport type NamedMiddlewareFactory<TName extends string = string, TArgs extends any[] = any[]> = {\n name: TName;\n _name: TName; // Type inference helper\n} & ((...args: TArgs) => MiddlewareHandler);\n\n/**\n * Define a named middleware\n *\n * Creates a middleware with a unique name that can be referenced\n * in route-level skip() calls with full type safety.\n *\n * Supports two patterns:\n * 1. Regular middleware: Direct middleware handler\n * 2. Factory middleware: Function that creates middleware with parameters\n *\n * @param name - Unique middleware name\n * @param handler - Middleware handler function\n * @returns Named middleware with type inference\n *\n * @example\n * ```ts\n * // Regular middleware\n * export const authMiddleware = defineMiddleware('auth', async (c, next) => {\n * const token = c.req.header('authorization');\n * if (!token) {\n * return c.json({ error: 'Unauthorized' }, 401);\n * }\n * c.set('user', await verifyToken(token));\n * await next();\n * });\n *\n * // Factory middleware\n * export const requirePermissions = defineMiddleware('permission',\n * (...permissions: string[]) => async (c, next) => {\n * const user = c.get('user');\n * if (!hasPermissions(user, permissions)) {\n * return c.json({ error: 'Forbidden' }, 403);\n * }\n * await next();\n * }\n * );\n *\n * // server.config.ts\n * export default defineServerConfig()\n * .middlewares([authMiddleware])\n * .routes(appRouter)\n * .build();\n *\n * // routes.ts - skip by name\n * export const publicRoute = route.get('/health')\n * .skip(['auth']) // ✅ Type-safe! Autocomplete!\n * .handler(async (c) => c.success({ status: 'ok' }));\n *\n * // Use factory middleware inline\n * export const protectedRoute = route.get('/admin')\n * .use([requirePermissions('admin:write')])\n * .handler(async (c) => { ... });\n * ```\n */\n/**\n * Options for defineMiddleware\n */\nexport interface DefineMiddlewareOptions\n{\n /**\n * Server-level middleware names to auto-skip when this middleware is used at route level.\n *\n * @example\n * ```ts\n * // optionalAuth auto-skips the global 'auth' middleware\n * export const optionalAuth = defineMiddleware('optionalAuth', handler, {\n * skips: ['auth']\n * });\n *\n * // Usage: .use([optionalAuth]) — no need for .skip(['auth'])\n * ```\n */\n skips?: string[];\n}\n\nexport function defineMiddleware<TName extends string>(\n name: TName,\n handler: MiddlewareHandler,\n options?: DefineMiddlewareOptions,\n): NamedMiddleware<TName>;\n\nexport function defineMiddleware<TName extends string, TArgs extends any[]>(\n name: TName,\n factory: (...args: TArgs) => MiddlewareHandler,\n options?: DefineMiddlewareOptions,\n): NamedMiddlewareFactory<TName, TArgs>;\n\nexport function defineMiddleware<TName extends string, TArgs extends any[] = []>(\n name: TName,\n handlerOrFactory: MiddlewareHandler | ((...args: TArgs) => MiddlewareHandler),\n options?: DefineMiddlewareOptions,\n): NamedMiddleware<TName> | NamedMiddlewareFactory<TName, TArgs>\n{\n const skips = options?.skips;\n\n // Distinguish between regular middleware and factory by parameter count\n // MiddlewareHandler always has exactly 2 parameters: (c, next)\n // Factory has any other number of parameters\n if (typeof handlerOrFactory === 'function')\n {\n const paramCount = handlerOrFactory.length;\n\n // Regular middleware handler (c, next) => ...\n if (paramCount === 2)\n {\n return {\n name,\n handler: handlerOrFactory as MiddlewareHandler,\n _name: name as TName,\n ...(skips && { skips }),\n };\n }\n // Factory (...args) => (c, next) => ...\n else\n {\n // Create a new wrapper function to avoid \"Cannot assign to read only property 'name'\" error\n const factory = handlerOrFactory as (...args: TArgs) => MiddlewareHandler;\n const wrapper = (...args: TArgs) => factory(...args);\n\n // Use Object.defineProperty to set name property (which is read-only by default)\n Object.defineProperty(wrapper, 'name', {\n value: name,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(wrapper, '_name', {\n value: name as TName,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n if (skips)\n {\n Object.defineProperty(wrapper, 'skips', {\n value: skips,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n }\n\n return wrapper as NamedMiddlewareFactory<TName, TArgs>;\n }\n }\n\n // Fallback: treat as regular middleware\n return {\n name,\n handler: handlerOrFactory as MiddlewareHandler,\n _name: name as TName,\n ...(skips && { skips }),\n };\n}\n\n/**\n * Define a middleware factory explicitly\n *\n * Use this when your factory function has exactly 2 parameters,\n * which would be incorrectly detected as a regular middleware handler.\n *\n * @param name - Unique middleware name\n * @param factory - Factory function that returns a middleware handler\n * @returns Named middleware factory with type inference\n *\n * @example\n * ```ts\n * // Factory with 2 params (would be misdetected by defineMiddleware)\n * export const rateLimiter = defineMiddlewareFactory('rateLimit',\n * (limit: number, window: number) => async (c, next) => {\n * // rate limit logic using limit and window\n * await next();\n * }\n * );\n *\n * // Usage\n * route.get('/api')\n * .use([rateLimiter(100, 60000)]) // 100 requests per minute\n * .handler(...)\n * ```\n */\nexport function defineMiddlewareFactory<TName extends string, TArgs extends unknown[]>(\n name: TName,\n factory: (...args: TArgs) => MiddlewareHandler,\n): NamedMiddlewareFactory<TName, TArgs>\n{\n const wrapper = (...args: TArgs) => factory(...args);\n\n Object.defineProperty(wrapper, 'name', {\n value: name,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(wrapper, '_name', {\n value: name as TName,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n return wrapper as NamedMiddlewareFactory<TName, TArgs>;\n}\n\n/**\n * Extract middleware names from an array of named middlewares\n *\n * @example\n * ```ts\n * type Middlewares = [\n * NamedMiddleware<'auth'>,\n * NamedMiddleware<'rateLimit'>\n * ];\n * type Names = ExtractMiddlewareNames<Middlewares>; // 'auth' | 'rateLimit'\n * ```\n */\nexport type ExtractMiddlewareNames<T extends readonly NamedMiddleware<string>[]> =\n T[number]['_name'];\n","/**\n * Cache factory with automatic environment variable detection\n */\n\nimport type { Redis, Cluster, RedisOptions, ClusterOptions } from 'ioredis';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\n\n/**\n * Connection-resilience options applied to every cache client.\n *\n * ioredis defaults to an unbounded offline command queue and 20 retries per\n * request, so a post-startup cache outage can buffer every command and hang\n * awaits. Bound retries (CACHE_MAX_RETRIES_PER_REQUEST, default 3) so commands\n * fail fast instead of hanging; CACHE_ENABLE_OFFLINE_QUEUE (default true) can be\n * set false for strict fail-fast (reject immediately while disconnected).\n */\nfunction resilienceOptions(): Pick<RedisOptions, 'maxRetriesPerRequest' | 'enableOfflineQueue'>\n{\n return {\n maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,\n enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE,\n };\n}\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nexport interface CacheClients {\n /** Primary cache for writes (or both read/write if no replica) */\n write?: Redis | Cluster;\n /** Replica cache for reads (optional, falls back to write) */\n read?: Redis | Cluster;\n}\n\n/**\n * Check if any cache configuration exists in environment\n */\nfunction hasCacheConfig(): boolean\n{\n return !!(\n process.env.CACHE_URL ||\n process.env.CACHE_WRITE_URL ||\n process.env.CACHE_READ_URL ||\n process.env.CACHE_SENTINEL_HOSTS ||\n process.env.CACHE_CLUSTER_NODES\n );\n}\n\n/**\n * Create cache client with TLS support\n */\nfunction createClient(\n RedisClient: new (url: string, options?: RedisOptions) => Redis,\n url: string,\n): Redis\n{\n const options: RedisOptions = { ...resilienceOptions() };\n\n // TLS support for secure connections\n if (url.startsWith('rediss://'))\n {\n options.tls = {\n rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== 'false',\n };\n }\n\n return new RedisClient(url, options);\n}\n\n/**\n * Create cache client(s) from environment variables\n *\n * Supported patterns (priority order):\n * 1. Single instance: CACHE_URL\n * 2. Master-Replica: CACHE_WRITE_URL + CACHE_READ_URL\n * 3. Sentinel: CACHE_SENTINEL_HOSTS + CACHE_MASTER_NAME\n * 4. Cluster: CACHE_CLUSTER_NODES\n *\n * @returns Cache client(s) or undefined if no configuration found\n *\n * @example\n * ```bash\n * # Single (most common)\n * CACHE_URL=redis://localhost:6379\n * CACHE_URL=rediss://secure.cache.com:6380 # TLS\n *\n * # Master-Replica\n * CACHE_WRITE_URL=redis://master:6379\n * CACHE_READ_URL=redis://replica:6379\n *\n * # Sentinel\n * CACHE_SENTINEL_HOSTS=sentinel1:26379,sentinel2:26379\n * CACHE_MASTER_NAME=mymaster\n * CACHE_PASSWORD=secret\n *\n * # Cluster\n * CACHE_CLUSTER_NODES=node1:6379,node2:6379,node3:6379\n * CACHE_PASSWORD=secret\n * ```\n */\nexport async function createCacheFromEnv(): Promise<CacheClients>\n{\n // Quick exit if no cache config\n if (!hasCacheConfig())\n {\n cacheLogger.info('No cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n\n try\n {\n // Dynamic import to avoid bundling if not used\n const ioredis = await import('ioredis');\n const RedisClient = ioredis.default;\n\n // Get environment variables\n const singleUrl = process.env.CACHE_URL;\n const writeUrl = process.env.CACHE_WRITE_URL;\n const readUrl = process.env.CACHE_READ_URL;\n const clusterNodes = process.env.CACHE_CLUSTER_NODES;\n const sentinelHosts = process.env.CACHE_SENTINEL_HOSTS;\n const masterName = process.env.CACHE_MASTER_NAME;\n const password = process.env.CACHE_PASSWORD;\n\n // 1. Single instance (most common - highest priority)\n if (singleUrl && !writeUrl && !readUrl && !clusterNodes)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created single cache instance', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // 2. Master-Replica pattern (both URLs required)\n if (writeUrl && readUrl)\n {\n const write = createClient(RedisClient, writeUrl);\n const read = createClient(RedisClient, readUrl);\n cacheLogger.debug('Created master-replica cache instances');\n\n return { write, read };\n }\n\n // 3. Sentinel pattern\n if (sentinelHosts && masterName)\n {\n const sentinels = sentinelHosts.split(',').map((host) =>\n {\n const [hostname, port] = host.trim().split(':');\n\n return { host: hostname, port: Number(port) || 26379 };\n });\n\n const options: RedisOptions = {\n ...resilienceOptions(),\n sentinels,\n name: masterName,\n password,\n };\n\n const client = new RedisClient(options);\n cacheLogger.debug('Created sentinel cache instance', { masterName, sentinels: sentinels.length });\n\n return { write: client, read: client };\n }\n\n // 4. Cluster pattern\n if (clusterNodes)\n {\n const nodes = clusterNodes.split(',').map((node) =>\n {\n const [host, port] = node.trim().split(':');\n\n return { host, port: Number(port) || 6379 };\n });\n\n const clusterOptions: ClusterOptions = {\n redisOptions: {\n ...resilienceOptions(),\n password,\n },\n };\n\n const cluster = new RedisClient.Cluster(nodes, clusterOptions);\n cacheLogger.debug('Created cluster cache instance', { nodes: nodes.length });\n\n return { write: cluster, read: cluster };\n }\n\n // 5. Fallback: Single URL with other configs present\n if (singleUrl)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created cache instance (fallback)', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // No valid configuration\n cacheLogger.info('No valid cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n catch (error)\n {\n if (error instanceof Error)\n {\n // Check if it's a missing dependency error\n if (error.message.includes('Cannot find module'))\n {\n cacheLogger.warn(\n 'Cache client library not installed',\n error,\n {\n suggestion: 'Install ioredis to enable cache: pnpm install ioredis',\n mode: 'disabled',\n },\n );\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n error,\n { mode: 'disabled' },\n );\n }\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n { error: String(error), mode: 'disabled' },\n );\n }\n\n return { write: undefined, read: undefined };\n }\n}\n\n/**\n * Create single cache client (backward compatibility)\n * Only returns write instance\n */\nexport async function createSingleCacheFromEnv(): Promise<Redis | Cluster | undefined>\n{\n const { write } = await createCacheFromEnv();\n\n return write;\n}\n","/**\n * Global cache instance manager\n * Provides singleton access to cache (Valkey/Redis) across all modules\n * Supports Master-Replica pattern with separate read/write instances\n *\n * When cache is unavailable, falls back to disabled mode gracefully\n */\n\nimport type { Redis, Cluster } from 'ioredis';\n\nimport { createCacheFromEnv } from './cache-factory';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\n// ── globalThis singleton ──────────────────────────────────────────\n// CJS/ESM dual-package hazard: 모듈이 두 번 로드되어도\n// Symbol.for() + globalThis로 동일 상태를 공유한다.\ninterface CacheState\n{\n write: Redis | Cluster | undefined;\n read: Redis | Cluster | undefined;\n disabled: boolean;\n}\n\nconst CACHE_KEY = Symbol.for('@spfn/core:cache');\n\nconst state: CacheState = ((globalThis as any)[CACHE_KEY] ??= {\n write: undefined,\n read: undefined,\n disabled: false,\n});\n\n/**\n * Get global cache write instance\n *\n * @returns Cache write instance or undefined if disabled/not initialized\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * await cache.set('key', 'value');\n * } else {\n * // Cache disabled - handle gracefully\n * console.log('Cache unavailable, skipping...');\n * }\n * ```\n */\nexport function getCache(): Redis | Cluster | undefined\n{\n return state.write;\n}\n\n/**\n * Get global cache read instance (falls back to write if no replica)\n *\n * @returns Cache read instance or write instance as fallback, undefined if disabled\n *\n * @example\n * ```typescript\n * import { getCacheRead } from '@spfn/core/cache';\n *\n * const cache = getCacheRead();\n * if (cache) {\n * const value = await cache.get('key');\n * }\n * ```\n */\nexport function getCacheRead(): Redis | Cluster | undefined\n{\n return state.read ?? state.write;\n}\n\n/**\n * Check if cache is disabled (connection failed or not configured)\n *\n * @example\n * ```typescript\n * import { isCacheDisabled } from '@spfn/core/cache';\n *\n * if (isCacheDisabled()) {\n * // Use alternative strategy (e.g., in-memory cache, database)\n * return await fetchFromDatabase();\n * }\n * ```\n */\nexport function isCacheDisabled(): boolean\n{\n return state.disabled;\n}\n\n/**\n * Set global cache instances (for testing or manual configuration)\n *\n * @param write - Cache write instance\n * @param read - Cache read instance (optional, defaults to write)\n *\n * @example\n * ```typescript\n * import { setCache } from '@spfn/core/cache';\n * import Redis from 'ioredis';\n *\n * const write = new Redis('redis://master:6379');\n * const read = new Redis('redis://replica:6379');\n * setCache(write, read);\n * ```\n */\nexport function setCache(\n write: Redis | Cluster | undefined,\n read?: Redis | Cluster | undefined,\n): void\n{\n state.write = write;\n state.read = read ?? write;\n state.disabled = !write;\n}\n\n/**\n * Initialize cache from environment variables\n * Automatically called by startServer()\n *\n * Supported environment variables (priority order):\n * - VALKEY_URL / CACHE_URL (single instance)\n * - VALKEY_WRITE_URL + VALKEY_READ_URL (master-replica)\n * - VALKEY_SENTINEL_HOSTS + VALKEY_MASTER_NAME (sentinel)\n * - VALKEY_CLUSTER_NODES (cluster)\n * - VALKEY_TLS_REJECT_UNAUTHORIZED (TLS config)\n * - Legacy: REDIS_* (backward compatibility)\n *\n * @returns Object with write and read instances, or undefined if disabled\n *\n * @example\n * ```typescript\n * import { initCache } from '@spfn/core/cache';\n *\n * // Manual initialization (not needed if using startServer)\n * const { write, read, disabled } = await initCache();\n * if (!disabled) {\n * console.log('Cache available');\n * }\n * ```\n */\nexport async function initCache(): Promise<{\n write?: Redis | Cluster;\n read?: Redis | Cluster;\n disabled: boolean;\n}>\n{\n // Already initialized\n if (state.write)\n {\n return { write: state.write, read: state.read, disabled: state.disabled };\n }\n\n // Auto-detect from environment\n const { write, read } = await createCacheFromEnv();\n\n if (write)\n {\n try\n {\n // Test connection\n await write.ping();\n\n // Test read instance if different\n if (read && read !== write)\n {\n await read.ping();\n }\n\n state.write = write;\n state.read = read;\n state.disabled = false;\n\n const hasReplica = read && read !== write;\n cacheLogger.info(\n hasReplica\n ? 'Cache connected (Master-Replica)'\n : 'Cache connected',\n { mode: 'enabled' },\n );\n\n return { write: state.write, read: state.read, disabled: false };\n }\n catch (error)\n {\n cacheLogger.error(\n 'Cache connection failed - running in disabled mode',\n error instanceof Error ? error : new Error(String(error)),\n { mode: 'disabled' },\n );\n\n // Clean up failed connections\n try\n {\n await write.quit();\n if (read && read !== write)\n {\n await read.quit();\n }\n }\n catch\n {\n // Ignore cleanup errors\n }\n\n state.disabled = true;\n\n return { write: undefined, read: undefined, disabled: true };\n }\n }\n\n // No configuration or library not installed\n state.disabled = true;\n cacheLogger.info('Cache disabled - no configuration or library not installed', { mode: 'disabled' });\n\n return { write: undefined, read: undefined, disabled: true };\n}\n\n/**\n * Close all cache connections and cleanup\n *\n * @example\n * ```typescript\n * import { closeCache } from '@spfn/core/cache';\n *\n * // During graceful shutdown\n * await closeCache();\n * ```\n */\nexport async function closeCache(): Promise<void>\n{\n if (state.disabled)\n {\n cacheLogger.debug('Cache already disabled, nothing to close');\n\n return;\n }\n\n const closePromises: Promise<unknown>[] = [];\n\n if (state.write)\n {\n closePromises.push(\n state.write.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache write instance', err);\n }),\n );\n }\n\n if (state.read && state.read !== state.write)\n {\n closePromises.push(\n state.read.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache read instance', err);\n }),\n );\n }\n\n await Promise.all(closePromises);\n\n state.write = undefined;\n state.read = undefined;\n state.disabled = true;\n\n cacheLogger.info('Cache connections closed', { mode: 'disabled' });\n}\n\n/**\n * Get cache connection info (for debugging)\n *\n * @example\n * ```typescript\n * import { getCacheInfo } from '@spfn/core/cache';\n *\n * const info = getCacheInfo();\n * console.log(info);\n * // {\n * // hasWrite: true,\n * // hasRead: true,\n * // isReplica: true,\n * // disabled: false\n * // }\n * ```\n */\nexport function getCacheInfo(): {\n hasWrite: boolean;\n hasRead: boolean;\n isReplica: boolean;\n disabled: boolean;\n}\n{\n return {\n hasWrite: !!state.write,\n hasRead: !!state.read,\n isReplica: !!(state.read && state.read !== state.write),\n disabled: state.disabled,\n };\n}\n","/**\n * Error Registry\n *\n * Central registry for serializable error types.\n * Enables automatic error deserialization on the client side.\n */\n\nimport type { SerializableError } from './serializable-error';\n\n/**\n * Error constructor type\n */\nexport type SerializableErrorConstructor = new (data: any) => SerializableError;\n\n/**\n * Input type for ErrorRegistry constructor\n */\nexport type ErrorRegistryInput =\n | SerializableErrorConstructor\n | SerializableErrorConstructor[]\n | ErrorRegistry;\n\n/**\n * Error registry for serialization/deserialization\n *\n * @example\n * ```typescript\n * // Create registry with initial errors\n * const registry = new ErrorRegistry([\n * errorRegistry, // Another ErrorRegistry\n * PaymentFailedError, // Single error class\n * [RefundError, OrderError], // Array of error classes\n * ]);\n *\n * // Or create empty and append\n * const registry = new ErrorRegistry();\n * registry\n * .append(ValidationError)\n * .append([PaymentFailedError, RefundError]);\n *\n * // Deserialize from JSON\n * const error = registry.deserialize({\n * __type: 'PaymentFailedError',\n * message: 'Payment failed',\n * transactionId: 'tx_123',\n * reason: 'insufficient_funds'\n * });\n *\n * // error instanceof PaymentFailedError === true\n * ```\n */\nexport class ErrorRegistry\n{\n private errors = new Map<string, SerializableErrorConstructor>();\n\n constructor(initialErrors?: ErrorRegistryInput[])\n {\n if (initialErrors)\n {\n for (const input of initialErrors)\n {\n if (input instanceof ErrorRegistry)\n {\n this.concat(input);\n }\n else if (Array.isArray(input))\n {\n this.append(input);\n }\n else\n {\n this.append(input);\n }\n }\n }\n }\n\n /**\n * Append error class(es) to the registry\n *\n * @param ErrorClass - Single error constructor\n * @returns This registry for chaining\n */\n append(ErrorClass: SerializableErrorConstructor): this;\n\n /**\n * Append error class(es) to the registry\n *\n * @param ErrorClasses - Array of error constructors\n * @returns This registry for chaining\n */\n append(ErrorClasses: SerializableErrorConstructor[]): this;\n\n /**\n * Append error class(es) to the registry\n *\n * @param input - Error constructor or array of constructors\n * @returns This registry for chaining\n */\n append(input: SerializableErrorConstructor | SerializableErrorConstructor[]): this\n {\n if (Array.isArray(input))\n {\n for (const ErrorClass of input)\n {\n this.errors.set(ErrorClass.name, ErrorClass);\n }\n }\n else\n {\n this.errors.set(input.name, input);\n }\n\n return this;\n }\n\n /**\n * Concatenate another ErrorRegistry into this one\n *\n * @param registry - Another ErrorRegistry to merge\n * @returns This registry for chaining\n */\n concat(registry: ErrorRegistry): this\n {\n for (const [name, ErrorClass] of registry.errors)\n {\n this.errors.set(name, ErrorClass);\n }\n\n return this;\n }\n\n /**\n * Check if error type is registered\n *\n * @param name - Error class name\n */\n has(name: string): boolean\n {\n return this.errors.has(name);\n }\n\n /**\n * Deserialize error from JSON data\n *\n * @param data - Serialized error data with __type field\n * @returns Deserialized error instance\n * @throws Error if error type is not registered\n */\n deserialize(data: { __type: string; [key: string]: any }): SerializableError\n {\n const ErrorClass = this.errors.get(data.__type);\n\n if (!ErrorClass)\n {\n throw new Error(`Unknown error type: ${data.__type}`);\n }\n\n // Pass entire data object to constructor\n return new ErrorClass(data);\n }\n\n /**\n * Try to deserialize error, return null if type unknown\n *\n * @param data - Serialized error data\n * @returns Deserialized error or null\n */\n tryDeserialize(data: { __type?: string; [key: string]: any }): SerializableError | null\n {\n if (!data.__type || !this.has(data.__type))\n {\n return null;\n }\n\n try\n {\n return this.deserialize(data as any);\n }\n catch\n {\n return null;\n }\n }\n\n /**\n * Get all registered error types\n */\n getRegisteredTypes(): string[]\n {\n return Array.from(this.errors.keys());\n }\n}\n","/**\n * Serializable Error Base Class\n *\n * Base class for errors that can be serialized/deserialized across HTTP boundary.\n * Automatically serializes public fields for transmission to client.\n */\n\n/**\n * Serialized error format for JSON transmission\n */\nexport interface SerializedError\n{\n __type: string;\n message: string;\n [key: string]: unknown;\n}\n\n/**\n * Base class for all serializable errors\n *\n * Features:\n * - Auto-serialization of public fields via toJSON()\n * - Constructor accepts object with all fields\n * - Type-safe error handling with instanceof\n *\n * @example\n * ```typescript\n * class PaymentFailedError extends SerializableError\n * {\n * readonly statusCode = 402;\n * transactionId!: string;\n * reason!: 'insufficient_funds' | 'card_declined';\n *\n * constructor(data: {\n * message: string;\n * transactionId: string;\n * reason: 'insufficient_funds' | 'card_declined';\n * })\n * {\n * super(data.message);\n * this.name = 'PaymentFailedError';\n * Object.assign(this, data);\n * }\n * }\n * ```\n */\nexport abstract class SerializableError extends Error\n{\n /**\n * HTTP status code for this error type\n */\n abstract readonly statusCode: number;\n\n /**\n * Serialize error to JSON-compatible object\n *\n * Automatically includes:\n * - __type: Constructor name for deserialization\n * - message: Error message\n * - All public instance properties (except name, stack)\n */\n toJSON(): SerializedError\n {\n const json: SerializedError = {\n __type: this.constructor.name,\n message: this.message,\n };\n\n // Extract all public instance properties\n for (const key of Object.keys(this))\n {\n // Skip Error built-ins and statusCode (inferred from type)\n if (key !== 'name' && key !== 'message' && key !== 'stack' && key !== 'statusCode')\n {\n json[key] = (this as unknown as Record<string, unknown>)[key];\n }\n }\n\n return json;\n }\n}\n","/**\n * HTTP Error Classes\n *\n * Standard HTTP error classes for API responses\n * All errors are serializable for type-safe client-side error handling\n */\n\nimport { SerializableError } from './serializable-error';\n\n/**\n * Base HTTP Error\n *\n * Base class for all HTTP-related errors\n */\nexport class HttpError extends SerializableError\n{\n public readonly statusCode: number;\n public details?: Record<string, unknown>;\n\n constructor(data: {\n message: string;\n statusCode: number;\n details?: Record<string, unknown>;\n })\n {\n super(data.message);\n\n this.name = 'HttpError';\n this.statusCode = data.statusCode;\n\n if (data.details)\n {\n this.details = data.details;\n }\n\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n/**\n * Bad Request Error (400)\n *\n * Generic bad request - malformed syntax, invalid parameters, etc.\n */\nexport class BadRequestError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Bad request',\n statusCode: 400,\n details: data.details,\n });\n\n this.name = 'BadRequestError';\n }\n}\n\n/**\n * Validation Error (400)\n *\n * Input validation failure (request params, query, body)\n * Used by define-route system for automatic validation\n */\nexport class ValidationError extends HttpError\n{\n fields?: Array<{ path: string; message: string; value?: any }>;\n\n constructor(data: {\n message: string;\n fields?: Array<{ path: string; message: string; value?: any }>;\n details?: Record<string, any>;\n })\n {\n super({\n message: data.message,\n statusCode: 400,\n details: data.details,\n });\n\n this.name = 'ValidationError';\n\n if (data.fields)\n {\n this.fields = data.fields;\n }\n }\n}\n\n/**\n * Unauthorized Error (401)\n *\n * Authentication required or authentication failed\n */\nexport class UnauthorizedError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Authentication required',\n statusCode: 401,\n details: data.details,\n });\n\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Forbidden Error (403)\n *\n * Authenticated but lacks permission to access resource\n */\nexport class ForbiddenError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Access forbidden',\n statusCode: 403,\n details: data.details,\n });\n\n this.name = 'ForbiddenError';\n }\n}\n\n/**\n * Not Found Error (404)\n *\n * Requested resource does not exist\n */\nexport class NotFoundError extends HttpError\n{\n resource?: string;\n\n constructor(data: { message?: string; resource?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource not found',\n statusCode: 404,\n details: data.details,\n });\n\n this.name = 'NotFoundError';\n\n if (data.resource)\n {\n this.resource = data.resource;\n }\n }\n}\n\n/**\n * Conflict Error (409)\n *\n * Generic conflict - resource state conflict, concurrent modification, etc.\n */\nexport class ConflictError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource conflict',\n statusCode: 409,\n details: data.details,\n });\n\n this.name = 'ConflictError';\n }\n}\n\n/**\n * Gone Error (410)\n *\n * Resource permanently deleted and no longer available\n */\nexport class GoneError extends HttpError\n{\n resource?: string;\n\n constructor(data: { message?: string; resource?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource permanently deleted',\n statusCode: 410,\n details: data.details,\n });\n\n this.name = 'GoneError';\n\n if (data.resource)\n {\n this.resource = data.resource;\n }\n }\n}\n\n/**\n * Too Many Requests Error (429)\n *\n * Rate limit exceeded\n */\nexport class TooManyRequestsError extends HttpError\n{\n retryAfter?: number;\n\n constructor(data: {\n message?: string;\n retryAfter?: number;\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Too many requests',\n statusCode: 429,\n details: data.details,\n });\n\n this.name = 'TooManyRequestsError';\n\n if (data.retryAfter)\n {\n this.retryAfter = data.retryAfter;\n }\n }\n}\n\n/**\n * Internal Server Error (500)\n *\n * Generic server error when no specific error type applies\n */\nexport class InternalServerError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Internal server error',\n statusCode: 500,\n details: data.details,\n });\n\n this.name = 'InternalServerError';\n }\n}\n\n/**\n * Unsupported Media Type Error (415)\n *\n * Media type not supported - invalid file type, content type, etc.\n */\nexport class UnsupportedMediaTypeError extends HttpError\n{\n mediaType?: string;\n supportedTypes?: string[];\n\n constructor(data: {\n message?: string;\n mediaType?: string;\n supportedTypes?: string[];\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Unsupported media type',\n statusCode: 415,\n details: data.details,\n });\n\n this.name = 'UnsupportedMediaTypeError';\n\n if (data.mediaType)\n {\n this.mediaType = data.mediaType;\n }\n\n if (data.supportedTypes)\n {\n this.supportedTypes = data.supportedTypes;\n }\n }\n}\n\n/**\n * Unprocessable Entity Error (422)\n *\n * Request is well-formed but contains semantic errors\n */\nexport class UnprocessableEntityError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Unprocessable entity',\n statusCode: 422,\n details: data.details,\n });\n\n this.name = 'UnprocessableEntityError';\n }\n}\n\n/**\n * Service Unavailable Error (503)\n *\n * Service temporarily unavailable (maintenance, overload, etc.)\n */\nexport class ServiceUnavailableError extends HttpError\n{\n retryAfter?: number;\n\n constructor(data: {\n message?: string;\n retryAfter?: number;\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Service unavailable',\n statusCode: 503,\n details: data.details,\n });\n\n this.name = 'ServiceUnavailableError';\n\n if (data.retryAfter)\n {\n this.retryAfter = data.retryAfter;\n }\n }\n}\n","/**\n * Database Error Classes\n *\n * Type-safe error handling with custom error class hierarchy\n * Mapped to HTTP status codes for API responses\n * All errors extend SerializableError for consistent JSON serialization\n */\n\nimport { SerializableError } from './serializable-error';\n\n/**\n * Base Database Error\n *\n * Base class for all database-related errors\n */\nexport class DatabaseError<TDetails extends Record<string, unknown> = Record<string, unknown>>\n extends SerializableError\n{\n public readonly statusCode: number;\n public readonly details?: TDetails;\n\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: TDetails;\n })\n {\n super(data.message);\n this.name = 'DatabaseError';\n this.statusCode = data.statusCode ?? 500;\n this.details = data.details;\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Whether this error's message/details are derived from the raw database\n * driver (SQL text, table/column/constraint names, parameter values) and\n * therefore must NOT be exposed to clients in production. Defined as a\n * prototype getter so it is never serialized into the response by toJSON().\n * Subclasses with a safe, constructed message override this to `false`.\n */\n get internal(): boolean\n {\n return true;\n }\n}\n\n/**\n * Connection Error (503 Service Unavailable)\n *\n * Database connection failure, connection pool exhaustion, etc.\n */\nexport class ConnectionError extends DatabaseError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 503, details: data.details });\n this.name = 'ConnectionError';\n }\n}\n\n/**\n * Query Error (500 Internal Server Error)\n *\n * SQL query execution failure, syntax errors, etc.\n */\nexport class QueryError extends DatabaseError\n{\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n })\n {\n super({\n message: data.message,\n statusCode: data.statusCode ?? 500,\n details: data.details,\n });\n this.name = 'QueryError';\n }\n}\n\n/**\n * Entity Not Found Error (404 Not Found)\n *\n * Database entity does not exist\n */\nexport class EntityNotFoundError extends QueryError\n{\n public readonly resource: string;\n public readonly id: string | number;\n\n constructor(data: { resource: string; id: string | number })\n {\n super({\n message: `${data.resource} with id ${data.id} not found`,\n statusCode: 404,\n details: { resource: data.resource, id: data.id },\n });\n this.name = 'EntityNotFoundError';\n this.resource = data.resource;\n this.id = data.id;\n }\n\n // Message/details are constructed from the resource + id, not driver text\n override get internal(): boolean\n {\n return false;\n }\n}\n\n/**\n * Constraint Violation Error (400 Bad Request)\n *\n * Database constraint violation (NOT NULL, CHECK, FOREIGN KEY, etc.)\n * This is different from HTTP ValidationError which validates request input\n */\nexport class ConstraintViolationError extends QueryError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 400, details: data.details });\n this.name = 'ConstraintViolationError';\n }\n}\n\n/**\n * Transaction Error (500 Internal Server Error)\n *\n * Transaction start/commit/rollback failure\n */\nexport class TransactionError extends DatabaseError\n{\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n })\n {\n super({\n message: data.message,\n statusCode: data.statusCode ?? 500,\n details: data.details,\n });\n this.name = 'TransactionError';\n }\n}\n\n/**\n * Deadlock Error (409 Conflict)\n *\n * Database deadlock detected\n */\nexport class DeadlockError extends TransactionError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 409, details: data.details });\n this.name = 'DeadlockError';\n }\n}\n\n/**\n * Duplicate Entry Error (409 Conflict)\n *\n * Unique constraint violation (e.g., duplicate email)\n */\nexport class DuplicateEntryError extends QueryError\n{\n public readonly field: string;\n public readonly value: string | number;\n\n constructor(data: { field: string; value: string | number })\n {\n // The client-facing message/details name the field but NOT the value: echoing\n // the value back (e.g. an email) confirms it exists, an enumeration aid. The\n // raw value stays on the instance for server-side handling/logging.\n super({\n message: `${data.field} already exists`,\n statusCode: 409,\n details: { field: data.field },\n });\n this.name = 'DuplicateEntryError';\n this.field = data.field;\n this.value = data.value;\n }\n\n // Field-only message/details — safe to surface to the client (not internal)\n override get internal(): boolean\n {\n return false;\n }\n}\n","/**\n * Error Module Exports\n *\n * Entry point for error handling module with serialization support\n */\n\n// Core Error Registry\n// Pre-configured registry with all built-in HTTP and Database errors\nimport { ErrorRegistry } from './error-registry';\nimport {\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n} from './http-errors';\nimport {\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n} from './database-errors';\n\n// Base Classes\nexport { SerializableError } from './serializable-error';\nexport type { SerializedError } from './serializable-error';\nexport { ErrorRegistry } from './error-registry';\nexport type { SerializableErrorConstructor, ErrorRegistryInput } from './error-registry';\n\n// Database Error Classes\nexport {\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n} from './database-errors';\n\n// HTTP Error Classes\nexport {\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n} from './http-errors';\n\n// Error Utilities\nexport {\n isDatabaseError,\n isHttpError,\n hasStatusCode,\n} from './error-utils';\n\nexport const errorRegistry = new ErrorRegistry();\n\n// HTTP Errors\nerrorRegistry.append([\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n]);\n\n// Database Errors\nerrorRegistry.append([\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n]);\n\nexport * as HttpErrors from './http-errors';\nexport * as DatabaseErrors from './database-errors';\n","/**\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","/**\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\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\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 // Cause chain — surfaces original PG errors hidden by DrizzleQueryError\n if (error.cause instanceof Error)\n {\n lines.push(`Caused by: ${formatError(error.cause)}`);\n }\n else if (error.cause !== undefined)\n {\n lines.push(`Caused by: ${String(error.cause)}`);\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 * Error 객체를 cause 체인까지 포함한 직렬화 가능 객체로 변환\n */\nfunction formatErrorCauseChain(error: Error): Record<string, unknown>\n{\n const result: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n\n if (error.cause instanceof Error)\n {\n result.cause = formatErrorCauseChain(error.cause);\n }\n else if (error.cause !== undefined)\n {\n result.cause = String(error.cause);\n }\n\n return result;\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 = formatErrorCauseChain(metadata.error);\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","/**\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\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, formatArg: string | number | boolean): void;\n debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n debug(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n info(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n warn(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n error(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n fatal(message: string, errorOrContext?: 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","/**\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","/**\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","/**\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\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();\n","/**\n * @spfn/core - Rate limit middleware\n *\n * Redis-backed fixed-window rate limiter, registered under the named\n * 'rateLimit' middleware so routes can opt out with `.skip(['rateLimit'])`.\n *\n * Storage is the shared cache (ioredis). The counter is incremented and given\n * its window expiry in a single atomic Lua call, so concurrent requests cannot\n * race between INCR and PEXPIRE. When no cache is configured the limiter fails\n * OPEN by default (logs a warning) — matching the proxy-guard nonce store's\n * graceful degradation — so development without Redis still works. Set\n * `failClosed` to reject instead.\n */\n\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { PROXY_CLIENT_IP_HEADER } from '../security/proxy-signature';\nimport { defineMiddlewareFactory } from '../route/define-middleware';\nimport { getCache, isCacheDisabled } from '../cache';\nimport { TooManyRequestsError } from '../errors';\nimport { logger } from '../logger';\n\nconst rateLimitLogger = logger.child('@spfn/core:rate-limit');\n\n/**\n * Atomic fixed-window counter: increment, set the window expiry on the first\n * hit, and return [count, pttlMs]. Keeping incr+expire in one round trip avoids\n * a leaked key that never expires when a process dies between the two calls.\n */\nconst FIXED_WINDOW_LUA = `\nlocal count = redis.call('INCR', KEYS[1])\nif count == 1 then\n redis.call('PEXPIRE', KEYS[1], ARGV[1])\nend\nreturn { count, redis.call('PTTL', KEYS[1]) }\n`;\n\nexport interface RateLimitOptions\n{\n /** Max requests allowed per window, applied to each identity dimension. */\n limit: number;\n\n /** Window length in milliseconds. */\n windowMs: number;\n\n /**\n * Namespace for the counter keys. Defaults to `${method} ${routePath}` so\n * each route is limited independently.\n */\n scope?: string;\n\n /**\n * Identity dimensions to limit on. Each non-empty value is counted\n * separately and the strictest wins (e.g. by IP and by account). Defaults\n * to the client IP only.\n */\n by?: (c: Context) => (string | null | undefined)[] | Promise<(string | null | undefined)[]>;\n\n /** Reject with 429 instead of allowing through when the cache is unavailable. */\n failClosed?: boolean;\n\n /** Message for the 429 response. */\n message?: string;\n}\n\n/**\n * Best-effort client IP for rate-limit keying.\n *\n * Prefers the proxy-forwarded real client IP, but ONLY when proxy-guard verified\n * the request came through our proxy (`clientType` set and not 'untrusted') — on a\n * verified request the proxy is the sole hop, so the value is the true client and\n * isn't client-spoofable. Otherwise (no proxy-guard, or unverified/direct request)\n * the forwarded header is attacker-settable, so fall back to the raw chain — whose\n * leftmost hop is itself spoofable, so still pair with an account/target dimension\n * for anything security-sensitive.\n */\nexport function getClientIp(c: Context): string\n{\n const clientType = c.get('clientType');\n if (clientType && clientType !== 'untrusted')\n {\n const forwarded = c.req.header(PROXY_CLIENT_IP_HEADER);\n if (forwarded)\n {\n return forwarded;\n }\n }\n\n const forwardedFor = c.req.header('x-forwarded-for');\n\n return forwardedFor?.split(',')[0]?.trim()\n || c.req.header('x-real-ip')\n || 'unknown';\n}\n\n/**\n * Redis-backed fixed-window rate limiter.\n *\n * @example\n * ```typescript\n * route.post('/_auth/login')\n * .use([rateLimit({ limit: 10, windowMs: 60_000 })])\n * .handler(...);\n * ```\n */\nexport const rateLimit = defineMiddlewareFactory(\n 'rateLimit',\n (options: RateLimitOptions): MiddlewareHandler =>\n {\n const { limit, windowMs, scope, by, failClosed = false, message } = options;\n\n return async (c, next) =>\n {\n const cache = getCache();\n\n if (!cache || isCacheDisabled())\n {\n if (failClosed)\n {\n throw new TooManyRequestsError({ message: message || 'Rate limiter unavailable' });\n }\n\n rateLimitLogger.warn('Cache unavailable — rate limit not enforced (fail-open)', {\n path: c.req.path,\n });\n\n return next();\n }\n\n const dimensions = (by ? await by(c) : [getClientIp(c)])\n .filter((d): d is string => Boolean(d));\n\n const ns = scope || `${c.req.method} ${c.req.routePath || c.req.path}`;\n\n for (const dimension of dimensions)\n {\n const [count, pttl] = await cache.eval(\n FIXED_WINDOW_LUA,\n 1,\n `ratelimit:${ns}:${dimension}`,\n String(windowMs),\n ) as [number, number];\n\n if (count > limit)\n {\n const retryAfter = Math.max(1, Math.ceil((pttl > 0 ? pttl : windowMs) / 1000));\n c.header('Retry-After', String(retryAfter));\n\n throw new TooManyRequestsError({\n message: message || 'Too many requests, please try again later',\n retryAfter,\n });\n }\n }\n\n return next();\n };\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';\nimport { getClientIp } from './rate-limit';\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\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 // Same resolution as rate limiting: the proxy-forwarded client IP when the\n // request is proxy-verified, else the (spoofable) raw chain.\n const ip = getClientIp(c);\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}\n","/**\n * Proxy-guard middleware\n *\n * Verifies that an inbound request came through a trusted Next.js RPC proxy\n * (HMAC signature) and/or from an allowed browser origin, then tags the request\n * with a `clientType`. Lets the backend reject direct-to-backend calls that\n * bypass the proxy.\n *\n * This does NOT replace user authentication (JWT) — it answers \"what kind of\n * client is this?\", not \"who is the user?\". It also cannot stop a client from\n * calling the proxy itself; that is the web's inherent limit (see\n * PROXY-BACKEND-AUTH-SPEC.md). Mobile clients call the backend directly and are\n * handled by a future attestation path, OR'd into this same check.\n *\n * @module middleware/proxy-guard\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { env } from '@spfn/core/config';\nimport { logger } from '@spfn/core/logger';\n\nimport {\n verifyProxyRequest,\n parseProxyKey,\n parseProxyKeySet,\n PROXY_SIGNATURE_HEADER,\n PROXY_TIMESTAMP_HEADER,\n PROXY_NONCE_HEADER,\n PROXY_KEY_ID_HEADER,\n type VerifyFailureReason,\n} from '../security/proxy-signature';\n\nconst guardLogger = logger.child('@spfn/core:proxy-guard');\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * How the request reached the backend.\n * - `web`: verified via a trusted proxy signature\n * - `untrusted`: could not be verified (only reached handlers in `tag` mode)\n * - future: `ios` | `android` once attestation lands\n */\nexport type ClientType = 'web' | 'untrusted' | (string & {});\n\n/**\n * Enforcement mode.\n * - `off`: middleware is a no-op (default — keeps existing apps working).\n * - `tag`: verify and set `clientType`, but never reject (observe first).\n * - `strict`: reject requests that fail verification.\n */\nexport type ProxyGuardMode = 'off' | 'tag' | 'strict';\n\n/**\n * Optional replay store. Returns `true` when the nonce is fresh (and records it),\n * `false` when it has been seen before. Backed by Redis in multi-instance setups.\n */\nexport interface NonceStore\n{\n checkAndSet(nonce: string, ttlMs: number): Promise<boolean>;\n}\n\nexport interface ProxyGuardConfig\n{\n /**\n * Active shared HMAC secret, as `<keyId>:<secret>` (or a bare secret).\n * Defaults to `env.SPFN_PROXY_SECRET`. Without it (and no previous keys),\n * signatures cannot be verified and every request is tagged `untrusted`.\n */\n secret?: string;\n\n /**\n * Previous (grace) keys still accepted for verification during rotation, as a\n * comma-separated list of `<keyId>:<secret>`. The proxy never signs with\n * these — they exist only so in-flight requests signed with the prior key\n * keep verifying until the rollout settles. Defaults to\n * `env.SPFN_PROXY_SECRET_PREVIOUS`. Backend-only, so it can live in\n * `.env.server` (never exposed to the Next.js process).\n */\n previousSecrets?: string;\n\n /** Enforcement mode. @default 'off' */\n mode?: ProxyGuardMode;\n\n /** Allowed clock skew / replay window in ms. @default 30000 */\n windowMs?: number;\n\n /**\n * Browser origin allowlist. When set, a request carrying an `Origin` header\n * not in this list is rejected (strict) / tagged (tag). Requests without an\n * `Origin` (server-to-server, mobile) skip this check and fall back to the\n * signature.\n */\n allowedOrigins?: string[];\n\n /** Optional nonce store for hard replay rejection. Redis (multi-instance) or in-memory (single). */\n nonceStore?: NonceStore;\n\n /**\n * When the nonce store throws (e.g. Redis down), reject the request instead of\n * falling back to the timestamp window. Trades availability for strictness.\n * @default false (fall back to the timestamp window)\n */\n nonceFailClosed?: boolean;\n\n /** Paths to skip entirely (e.g. health checks). */\n skipPaths?: string[];\n\n /**\n * Reject (413) once the streamed body exceeds this many bytes. Measured AS IT\n * STREAMS — Content-Length is NOT trusted, so a missing/chunked/under-reported\n * length can't bypass it. Bounds the per-request memory the guard buffers.\n * Undefined = no cap (default). Multipart is exempt (unsigned).\n */\n maxBodyBytes?: number;\n}\n\n// ============================================================================\n// Hono context augmentation\n// ============================================================================\n\ndeclare module 'hono'\n{\n interface ContextVariableMap\n {\n clientType?: ClientType;\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Sentinel: body exceeded maxBodyBytes while streaming (never fully buffered). */\nconst BODY_OVERSIZE = Symbol('proxy-guard:body-oversize');\n\n/** Sentinel: the body stream errored mid-read (e.g. client abort). */\nconst BODY_READ_ERROR = Symbol('proxy-guard:body-read-error');\n\n/**\n * Read the raw request body without consuming it for downstream handlers.\n * Bound for ANY non-multipart content-type (not just application/json) so the\n * backend hashes exactly what the proxy signed — otherwise the body could be\n * tampered by flipping content-type. Multipart uploads are excluded on both\n * sides (their large bodies are never hashed).\n *\n * When maxBytes is set the body is measured AS IT STREAMS and aborted past the\n * limit — so a missing/chunked/under-reported Content-Length can't bypass the cap\n * (the header is never trusted). Returns BODY_OVERSIZE in that case.\n */\nasync function readRawBody(\n c: Context,\n maxBytes?: number,\n): Promise<Buffer | undefined | typeof BODY_OVERSIZE | typeof BODY_READ_ERROR>\n{\n const method = c.req.method;\n if (method === 'GET' || method === 'HEAD')\n {\n return undefined;\n }\n\n const contentType = c.req.header('content-type') || '';\n if (contentType.includes('multipart/form-data'))\n {\n return undefined;\n }\n\n const stream = c.req.raw.clone().body;\n if (!stream)\n {\n return undefined;\n }\n\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n\n try\n {\n for (;;)\n {\n const { done, value } = await reader.read();\n if (done)\n {\n break;\n }\n\n total += value.byteLength;\n if (maxBytes !== undefined && total > maxBytes)\n {\n // Fire-and-forget: awaiting cancel() can hang under some stream\n // implementations, and this is a clone so the original is untouched.\n void reader.cancel().catch(() => undefined);\n\n return BODY_OVERSIZE;\n }\n\n chunks.push(value);\n }\n }\n catch\n {\n // A mid-stream error (client abort, network blip) RETURNS a sentinel — not an\n // empty body (would 403 valid traffic) and not a re-throw (would 500 + page\n // on-call and break tag mode's never-reject contract).\n guardLogger.debug('Request body read failed (client abort?)');\n\n return BODY_READ_ERROR;\n }\n\n return Buffer.concat(chunks);\n}\n\nfunction isOriginAllowed(c: Context, allowedOrigins: string[] | undefined): boolean\n{\n if (!allowedOrigins || allowedOrigins.length === 0)\n {\n return true;\n }\n\n const origin = c.req.header('origin');\n if (!origin)\n {\n // No browser Origin (server-to-server / mobile) — defer to signature.\n return true;\n }\n\n return allowedOrigins.includes(origin);\n}\n\n// ============================================================================\n// Middleware\n// ============================================================================\n\n/**\n * Create the proxy-guard middleware.\n *\n * @example\n * ```typescript\n * export default defineServerConfig()\n * .proxyGuard({ mode: 'strict', allowedOrigins: ['https://app.example.com'] })\n * .routes(appRouter)\n * .build();\n * ```\n */\nexport function createProxyGuard(config: ProxyGuardConfig = {}): MiddlewareHandler\n{\n const mode = config.mode ?? 'off';\n const windowMs = config.windowMs ?? 30_000;\n const allowedOrigins = config.allowedOrigins;\n const nonceStore = config.nonceStore;\n const nonceFailClosed = config.nonceFailClosed ?? false;\n const skipPaths = new Set(config.skipPaths ?? []);\n const maxBodyBytes = config.maxBodyBytes;\n\n // Accepted key set: active secret first (wins on keyId collision), then grace keys.\n const activeRaw = config.secret ?? env.SPFN_PROXY_SECRET;\n const previousRaw = config.previousSecrets ?? env.SPFN_PROXY_SECRET_PREVIOUS;\n const keys = parseProxyKeySet([activeRaw, previousRaw]);\n\n // Fail CLOSED on misconfiguration: strict with no key would otherwise let every\n // request (including direct-to-backend) through. Refuse to start instead.\n if (mode === 'strict' && keys.length === 0)\n {\n throw new Error(\n '[proxy-guard] mode \"strict\" requires a proxy key but none is configured '\n + '(SPFN_PROXY_SECRET is empty/unset). Refusing to start with the guard open — '\n + 'set the secret, or use mode \"tag\" / \"off\".',\n );\n }\n\n // Warn when rotation can't work: bare secrets (no \"keyId:\" prefix) on both active\n // and previous collapse to the same keyId, so the grace key is silently dropped.\n if (activeRaw && previousRaw)\n {\n const activeId = parseProxyKey(activeRaw).keyId;\n const collides = previousRaw.split(',').some(p => p.trim() && parseProxyKey(p.trim()).keyId === activeId);\n if (collides)\n {\n guardLogger.warn(\n 'Previous proxy key shares keyId with the active key (likely bare secrets without a '\n + '\"keyId:\" prefix) — grace key ignored, rotation will drop in-flight requests. '\n + 'Use \"<keyId>:<secret>\" on both keys.',\n { keyId: activeId },\n );\n }\n }\n\n return async (c: Context, next: Next) =>\n {\n if (mode === 'off' || skipPaths.has(c.req.path))\n {\n return next();\n }\n\n // A genuine CORS preflight (OPTIONS + Access-Control-Request-Method) belongs to\n // the CORS layer, not the signature guard — exempt it FIRST so it isn't 403'd\n // here without CORS headers. A NON-preflight OPTIONS (rare OPTIONS-as-API) falls\n // through to normal verification, so exempting preflight is never an unguarded\n // bypass. Tagged 'untrusted' (unsigned) so downstream never sees it unset.\n if (c.req.method === 'OPTIONS' && c.req.header('access-control-request-method'))\n {\n c.set('clientType', 'untrusted');\n\n return next();\n }\n\n // Every gate below is EVALUATED in both modes; only enforcement differs\n // (reject() = 403 in strict, tag clientType='untrusted' + continue in tag).\n // So tag mode observes exactly what strict would reject — no metric skew.\n\n // 1. Origin allowlist (browser cross-origin guard)\n if (!isOriginAllowed(c, allowedOrigins))\n {\n return reject(c, mode, next, 'origin-not-allowed');\n }\n\n // 2. No accepted key — only reachable in tag mode (strict throws at construction).\n if (keys.length === 0)\n {\n c.set('clientType', 'untrusted');\n\n return next();\n }\n\n // 3. Cheap header-presence check BEFORE buffering the body, so unsigned requests\n // (direct-to-backend attacks, tag-mode rollout traffic) reject/tag without\n // paying the body read.\n const signature = c.req.header(PROXY_SIGNATURE_HEADER);\n const timestamp = c.req.header(PROXY_TIMESTAMP_HEADER);\n const nonce = c.req.header(PROXY_NONCE_HEADER);\n const keyId = c.req.header(PROXY_KEY_ID_HEADER);\n if (!signature || !timestamp || !nonce || !keyId)\n {\n return reject(c, mode, next, 'missing-headers');\n }\n\n // 4. Read body with a streaming size cap (Content-Length is never trusted, so a\n // missing/chunked/under-reported length can't bypass the bound).\n const body = await readRawBody(c, maxBodyBytes);\n if (body === BODY_OVERSIZE)\n {\n if (mode === 'strict')\n {\n return c.json({ error: 'Payload Too Large' }, 413);\n }\n\n c.set('clientType', 'untrusted');\n\n return next();\n }\n if (body === BODY_READ_ERROR)\n {\n // Body stream failed (client abort) — can't verify, so don't hash an empty\n // body. Reject in strict / tag untrusted, never a 500.\n return reject(c, mode, next, 'body-read-error');\n }\n\n // 5. HMAC over the wire request-target (raw path + query) and body. Use the raw\n // URL, NOT the decoded c.req.path, so it matches the bytes the proxy signed.\n const url = new URL(c.req.url);\n const result = verifyProxyRequest({\n keys,\n method: c.req.method,\n path: url.pathname,\n query: url.search,\n body,\n signature,\n timestamp,\n nonce,\n keyId,\n windowMs,\n });\n\n if (!result.valid)\n {\n return reject(c, mode, next, result.reason);\n }\n\n // 6. Hard replay rejection via nonce store (both modes — tag observes replays).\n // Degrade to the timestamp window if the store is briefly unavailable rather\n // than 500-ing valid traffic.\n if (nonceStore && result.nonce)\n {\n try\n {\n const fresh = await nonceStore.checkAndSet(result.nonce, windowMs * 2);\n if (!fresh)\n {\n return reject(c, mode, next, 'nonce-replay');\n }\n }\n catch (err)\n {\n if (nonceFailClosed)\n {\n guardLogger.warn('Nonce store unavailable — rejecting (fail-closed)', {\n error: (err as Error).message,\n });\n\n return reject(c, mode, next, 'nonce-store-unavailable');\n }\n\n guardLogger.warn('Nonce store unavailable — falling back to timestamp window', {\n error: (err as Error).message,\n });\n }\n }\n\n c.set('clientType', 'web');\n\n return next();\n };\n}\n\n// ============================================================================\n// Redis-backed nonce store (optional)\n// ============================================================================\n\n/** Minimal cache client (compatible with ioredis Redis | Cluster). */\ntype CacheClient = {\n set(key: string, value: string, ...args: any[]): Promise<any>;\n};\n\n/**\n * Build a Redis-backed nonce store for hard replay rejection.\n *\n * Uses `SET key 1 PX ttl NX` — an atomic \"set if absent\". A fresh nonce returns\n * 'OK'; a replayed one returns null. TTL keeps the key set bounded (only nonces\n * within the replay window matter).\n */\nexport function createCacheNonceStore(cache: CacheClient, prefix = 'spfn:proxy-nonce:'): NonceStore\n{\n return {\n async checkAndSet(nonce: string, ttlMs: number): Promise<boolean>\n {\n const res = await cache.set(`${prefix}${nonce}`, '1', 'PX', Math.ceil(ttlMs), 'NX');\n\n return res === 'OK';\n },\n };\n}\n\n/**\n * In-process nonce store for hard replay rejection without Redis.\n *\n * Good enough for a single instance — each process only knows its own nonces, so\n * across multiple instances a replay routed to a different pod isn't caught. Use a\n * cache-backed store (createCacheNonceStore) for multi-instance hard rejection.\n * Entries self-expire after their TTL; the map is swept opportunistically so it\n * stays bounded by the request rate within the replay window.\n */\nexport function createInMemoryNonceStore(): NonceStore\n{\n const seen = new Map<string, number>(); // nonce -> expiry (epoch ms)\n let lastSweep = 0;\n\n return {\n async checkAndSet(nonce: string, ttlMs: number): Promise<boolean>\n {\n const now = Date.now();\n\n if (now - lastSweep > 60_000)\n {\n for (const [n, exp] of seen)\n {\n if (exp <= now) seen.delete(n);\n }\n lastSweep = now;\n }\n\n const existing = seen.get(nonce);\n if (existing !== undefined && existing > now)\n {\n return false; // replay within window\n }\n\n seen.set(nonce, now + ttlMs);\n\n return true;\n },\n };\n}\n\n/**\n * Reject (strict) or tag-and-continue (tag). Keeps the rejection response generic\n * so it leaks no detail about why verification failed.\n */\nfunction reject(\n c: Context,\n mode: ProxyGuardMode,\n next: Next,\n reason: VerifyFailureReason | 'origin-not-allowed' | 'nonce-replay' | 'nonce-store-unavailable' | 'body-read-error' | undefined,\n)\n{\n if (mode === 'strict')\n {\n guardLogger.warn('Rejected unverified request', { reason, path: c.req.path, method: c.req.method });\n\n return c.json({ error: 'Forbidden', message: 'Request origin could not be verified' }, 403);\n }\n\n // tag mode — observe only, never block\n guardLogger.debug('Unverified request (would reject in strict mode)', { reason, path: c.req.path });\n c.set('clientType', 'untrusted');\n\n return next();\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/error-handler.ts","../../src/security/proxy-signature.ts","../../src/route/define-middleware.ts","../../src/cache/cache-factory.ts","../../src/cache/cache-manager.ts","../../src/errors/error-registry.ts","../../src/errors/serializable-error.ts","../../src/errors/http-errors.ts","../../src/errors/database-errors.ts","../../src/errors/index.ts","../../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","../../src/middleware/rate-limit.ts","../../src/middleware/request-logger.ts","../../src/middleware/proxy-guard.ts"],"names":["statusCode","logger","SerializableError","env","randomBytes","maskSensitiveData"],"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 /**\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\nconst SENSITIVE_QUERY_PARAMS = new Set([\n 'token', 'access_token', 'refresh_token', 'id_token', 'code',\n 'secret', 'client_secret', 'password', 'passwd', 'pwd',\n 'api_key', 'apikey', 'key', 'signature', 'sig',\n 'session', 'sessionid', 'auth', 'authorization',\n]);\n\n/**\n * Extract query params from request, masking sensitive values so credentials in\n * the URL (?token=…, ?code=…) don't reach error logs / the onError callback.\n */\nfunction extractQuery(c: Context): Record<string, string>\n{\n const query = c.req.query();\n const masked: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(query))\n {\n masked[key] = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()) ? '***' : value;\n }\n\n return masked;\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: extractQuery(c),\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 // Internal (DB-driver-derived) errors must not leak SQL text, schema\n // names, or parameter values to clients in production. Full detail is\n // kept in the log above; the client gets a generic message.\n if ((err as { internal?: boolean }).internal === true && !includeStack)\n {\n return c.json(\n { __type: err.constructor.name, message: 'Internal server error' },\n statusCode as ContentfulStatusCode,\n );\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 (non-SerializableError) errors are uncaught/internal — their\n // message and cause may contain raw SQL or driver text (e.g. an uncaught\n // Drizzle error), so only expose them in development.\n const response: StandardErrorResponse = {\n __type: 'Error',\n message: includeStack ? (err.message || 'Internal Server Error') : 'Internal Server Error',\n };\n\n if (causeMessage && includeStack)\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 * Proxy → Backend request signing (HMAC-SHA256) with key rotation\n *\n * Shared between the Next.js RPC proxy (which signs outbound requests) and the\n * backend proxy-guard middleware (which verifies them). Keeping the canonical\n * string and HMAC logic in one pure module guarantees both sides agree.\n *\n * The signature proves a request passed through a trusted proxy that holds the\n * shared secret. It does NOT authenticate the end user — that is the JWT layer's\n * job — and it cannot stop a client from calling the proxy itself. Its role is to\n * stop direct-to-backend calls that bypass the proxy.\n *\n * Keys are identified by a short keyId so secrets can be rotated without downtime:\n * the proxy signs with the active key and stamps its keyId in a header; the\n * backend keeps a set of accepted keys (current + previous) and selects by keyId.\n * Roll a new key in, let both sides pick it up, switch the proxy to it, then drop\n * the old one after a grace period — no request is ever rejected mid-rotation.\n *\n * @module security/proxy-signature\n */\nimport { createHmac, randomBytes, timingSafeEqual, createHash } from 'node:crypto';\n\n// ============================================================================\n// Header names\n// ============================================================================\n\nexport const PROXY_SIGNATURE_HEADER = 'x-spfn-proxy-signature';\nexport const PROXY_TIMESTAMP_HEADER = 'x-spfn-proxy-timestamp';\nexport const PROXY_NONCE_HEADER = 'x-spfn-proxy-nonce';\nexport const PROXY_KEY_ID_HEADER = 'x-spfn-proxy-key-id';\n/**\n * The real client IP, forwarded by the proxy. The backend trusts this only when\n * the request is proxy-verified (proxy-guard `clientType` ≠ untrusted) — the proxy\n * itself sees no spoofable hop, so a verified request's value is the true client.\n */\nexport const PROXY_CLIENT_IP_HEADER = 'x-spfn-proxy-client-ip';\n\n/** keyId used when a raw secret carries no `keyId:` prefix (back-compat). */\nexport const DEFAULT_KEY_ID = 'default';\n\n// ============================================================================\n// Keys\n// ============================================================================\n\n/**\n * A signing key: a short identifier plus its secret. Encoded in env as\n * `<keyId>:<secret>` (e.g. `v2:9f3c...`); a bare secret gets `DEFAULT_KEY_ID`.\n */\nexport interface ProxyKey\n{\n keyId: string;\n secret: string;\n}\n\n/**\n * Parse one `<keyId>:<secret>` (or bare `<secret>`) string into a ProxyKey.\n * keyId is everything before the FIRST colon; the secret keeps any later colons.\n */\nexport function parseProxyKey(raw: string): ProxyKey\n{\n const idx = raw.indexOf(':');\n if (idx === -1)\n {\n return { keyId: DEFAULT_KEY_ID, secret: raw };\n }\n\n // A leading colon (':secret') yields an empty keyId; the backend treats an empty\n // keyId header as \"missing\" and rejects, so normalize it to DEFAULT_KEY_ID.\n return { keyId: raw.slice(0, idx) || DEFAULT_KEY_ID, secret: raw.slice(idx + 1) };\n}\n\n/**\n * Build the backend's accepted key set from raw env values. Each input may be a\n * single key or a comma-separated list (for multiple grace keys). Earlier inputs\n * win on keyId collision, so pass the active secret first.\n */\nexport function parseProxyKeySet(raws: Array<string | undefined | null>): ProxyKey[]\n{\n const keys: ProxyKey[] = [];\n const seen = new Set<string>();\n\n for (const raw of raws)\n {\n if (!raw)\n {\n continue;\n }\n\n for (const part of raw.split(','))\n {\n const trimmed = part.trim();\n if (!trimmed)\n {\n continue;\n }\n\n const key = parseProxyKey(trimmed);\n if (!key.secret || seen.has(key.keyId))\n {\n continue;\n }\n\n seen.add(key.keyId);\n keys.push(key);\n }\n }\n\n return keys;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * The parts of a request that are bound into the signature.\n */\nexport interface SignatureParts\n{\n /** Resolved backend HTTP method (GET/POST/PUT/...) */\n method: string;\n\n /**\n * Raw (wire) backend path, percent-encoding preserved, leading slash, NO query.\n * Proxy signs its outbound request-target path; the backend reconstructs the\n * SAME bytes from `new URL(c.req.url).pathname` (NOT the decoded `c.req.path`).\n */\n path: string;\n\n /**\n * Raw (wire) query string including leading `?`, or '' when there is none.\n * Both sides use the verbatim search string so query params are authenticated.\n */\n query: string;\n\n /** Unix epoch milliseconds, as a string */\n timestamp: string;\n\n /** Random per-request nonce */\n nonce: string;\n\n /**\n * SHA-256 hex of the raw request body, or empty string when there is no body\n * (GET/HEAD, or multipart uploads which are excluded by design). Bound for ANY\n * non-multipart content-type, not just application/json.\n *\n * Multipart is excluded so the proxy can stream large uploads without buffering\n * them to hash. The signature therefore proves *provenance* (came through the\n * trusted proxy) and binds method/path/query/timestamp for a multipart request,\n * but NOT the body bytes. Two consequences, by design:\n * - Multipart *content* integrity/validation is the application's job (size,\n * type, checksum, business rules) — not the transport signature's.\n * - If you don't trust the proxy→backend hop itself, secure it with TLS/mTLS;\n * signing a prefix of the stream would be a half-measure (the tail stays\n * unprotected) and is intentionally not done.\n */\n bodyHash: string;\n}\n\n/**\n * Headers produced by signing — ready to set on the outbound fetch.\n */\nexport interface SignatureHeaders\n{\n [PROXY_SIGNATURE_HEADER]: string;\n [PROXY_TIMESTAMP_HEADER]: string;\n [PROXY_NONCE_HEADER]: string;\n [PROXY_KEY_ID_HEADER]: string;\n}\n\n// ============================================================================\n// Canonical string + HMAC\n// ============================================================================\n\n/**\n * Build the canonical string that gets signed. Field order is fixed and each\n * field is newline-separated, so a captured signature cannot be replayed against\n * a different method, path, or body.\n */\nexport function buildCanonicalString(parts: SignatureParts): string\n{\n return [parts.method.toUpperCase(), parts.path, parts.query, parts.timestamp, parts.nonce, parts.bodyHash].join('\\n');\n}\n\n/**\n * Hash a request body. Accepts a string (proxy side, from JSON.stringify) or a\n * Buffer (backend side, straight from the stream — avoids a bytes→string→bytes\n * round-trip). Returns '' for empty/no body. A utf8 string and its Buffer hash\n * identically, so both sides agree.\n */\nexport function hashBody(body: string | Buffer | undefined | null): string\n{\n if (!body || body.length === 0)\n {\n return '';\n }\n\n return typeof body === 'string'\n ? createHash('sha256').update(body, 'utf8').digest('hex')\n : createHash('sha256').update(body).digest('hex');\n}\n\n/**\n * Compute the HMAC-SHA256 signature (hex) for the given parts.\n */\nexport function computeSignature(secret: string, parts: SignatureParts): string\n{\n return createHmac('sha256', secret).update(buildCanonicalString(parts)).digest('hex');\n}\n\n/**\n * Generate a fresh random nonce (hex).\n */\nexport function generateNonce(): string\n{\n return randomBytes(16).toString('hex');\n}\n\n// ============================================================================\n// Sign (proxy side)\n// ============================================================================\n\nexport interface SignInput\n{\n /** The active signing key. */\n key: ProxyKey;\n method: string;\n /** Raw (wire) path, leading slash, percent-encoding preserved, NO query. */\n path: string;\n /** Raw (wire) query string including leading `?`, or '' / omitted for none. */\n query?: string;\n /** Raw request body string, if any. Omit for GET / multipart uploads. */\n body?: string | null;\n /** Override the timestamp (testing). Defaults to Date.now(). */\n timestamp?: string;\n /** Override the nonce (testing). Defaults to a random nonce. */\n nonce?: string;\n}\n\n/**\n * Sign an outbound proxy request and return the headers to attach (including the\n * keyId so the backend knows which key to verify against).\n */\nexport function signProxyRequest(input: SignInput): SignatureHeaders\n{\n const timestamp = input.timestamp ?? String(Date.now());\n const nonce = input.nonce ?? generateNonce();\n const signature = computeSignature(input.key.secret, {\n method: input.method,\n path: input.path,\n query: input.query ?? '',\n timestamp,\n nonce,\n bodyHash: hashBody(input.body),\n });\n\n return {\n [PROXY_SIGNATURE_HEADER]: signature,\n [PROXY_TIMESTAMP_HEADER]: timestamp,\n [PROXY_NONCE_HEADER]: nonce,\n [PROXY_KEY_ID_HEADER]: input.key.keyId,\n };\n}\n\n// ============================================================================\n// Verify (backend side)\n// ============================================================================\n\nexport type VerifyFailureReason =\n | 'missing-headers'\n | 'stale-timestamp'\n | 'bad-timestamp'\n | 'unknown-key'\n | 'signature-mismatch';\n\n// ('body-read-error' / 'origin-not-allowed' / 'nonce-replay' are guard-level\n// reasons carried separately by the middleware, not produced by verify itself.)\n\nexport interface VerifyResult\n{\n valid: boolean;\n reason?: VerifyFailureReason;\n /** The verified nonce, for optional replay tracking by the caller. */\n nonce?: string;\n /** The keyId the request was verified against (for logging/metrics). */\n keyId?: string;\n}\n\nexport interface VerifyInput\n{\n /** Accepted keys (active + grace). Selected by the request's keyId header. */\n keys: ProxyKey[];\n method: string;\n /** Raw (wire) path from `new URL(c.req.url).pathname` — NOT decoded `c.req.path`. */\n path: string;\n /** Raw (wire) query string from `new URL(c.req.url).search`, or '' for none. */\n query?: string;\n /** Raw request body (string or Buffer) as received, if any. */\n body?: string | Buffer | null;\n signature: string | null | undefined;\n timestamp: string | null | undefined;\n nonce: string | null | undefined;\n keyId: string | null | undefined;\n /** Allowed clock skew / replay window in ms. Default 30s. */\n windowMs?: number;\n /** Current time (testing). Defaults to Date.now(). */\n now?: number;\n}\n\n/**\n * Constant-time hex string comparison. Returns false on any length diff.\n *\n * Buffer.from(_, 'hex') does NOT throw on invalid hex — it silently truncates at\n * the first bad char — so a malformed signature yields a shorter buffer that the\n * length check rejects. No try/catch needed.\n */\nfunction safeEqualHex(a: string, b: string): boolean\n{\n const bufA = Buffer.from(a, 'hex');\n const bufB = Buffer.from(b, 'hex');\n\n if (bufA.length === 0 || bufA.length !== bufB.length)\n {\n return false;\n }\n\n return timingSafeEqual(bufA, bufB);\n}\n\n/**\n * Verify an inbound request's proxy signature against the accepted key set.\n *\n * Checks, in order: headers present, timestamp within window (replay guard),\n * keyId known, and HMAC match. Nonce-level replay rejection is left to the caller\n * (optional, Redis-backed) since not every deployment has a shared store.\n */\nexport function verifyProxyRequest(input: VerifyInput): VerifyResult\n{\n const { signature, timestamp, nonce, keyId } = input;\n\n if (!signature || !timestamp || !nonce || !keyId)\n {\n return { valid: false, reason: 'missing-headers' };\n }\n\n const ts = Number(timestamp);\n if (!Number.isFinite(ts))\n {\n return { valid: false, reason: 'bad-timestamp' };\n }\n\n const now = input.now ?? Date.now();\n const windowMs = input.windowMs ?? 30_000;\n if (Math.abs(now - ts) > windowMs)\n {\n return { valid: false, reason: 'stale-timestamp' };\n }\n\n const key = input.keys.find(k => k.keyId === keyId);\n if (!key)\n {\n return { valid: false, reason: 'unknown-key' };\n }\n\n const expected = computeSignature(key.secret, {\n method: input.method,\n path: input.path,\n query: input.query ?? '',\n timestamp,\n nonce,\n bodyHash: hashBody(input.body),\n });\n\n if (!safeEqualHex(signature, expected))\n {\n return { valid: false, reason: 'signature-mismatch' };\n }\n\n return { valid: true, nonce, keyId };\n}\n","/**\n * Middleware Definition Helper\n *\n * Provides type-safe middleware definition with name inference\n */\n\nimport type { MiddlewareHandler } from 'hono';\n\n/**\n * Named middleware with type inference\n *\n * @example\n * ```ts\n * export const authMiddleware = defineMiddleware('auth', async (c, next) => {\n * // auth logic\n * c.set('user', { id: 1 });\n * await next();\n * });\n *\n * export const rateLimitMiddleware = defineMiddleware('rateLimit', async (c, next) => {\n * // rate limit logic\n * await next();\n * });\n * ```\n */\nexport type NamedMiddleware<TName extends string = string> = {\n name: TName;\n handler: MiddlewareHandler;\n _name: TName; // Type inference helper\n skips?: string[]; // Server-level middlewares to auto-skip when this middleware is used\n};\n\n/**\n * Named middleware factory with type inference\n *\n * Factory function that creates middleware instances with parameters\n *\n * @example\n * ```ts\n * export const requirePermissions = defineMiddleware('permission',\n * (...permissions: string[]) => async (c, next) => {\n * // permission check logic\n * await next();\n * }\n * );\n * ```\n */\nexport type NamedMiddlewareFactory<TName extends string = string, TArgs extends any[] = any[]> = {\n name: TName;\n _name: TName; // Type inference helper\n} & ((...args: TArgs) => MiddlewareHandler);\n\n/**\n * Define a named middleware\n *\n * Creates a middleware with a unique name that can be referenced\n * in route-level skip() calls with full type safety.\n *\n * Supports two patterns:\n * 1. Regular middleware: Direct middleware handler\n * 2. Factory middleware: Function that creates middleware with parameters\n *\n * @param name - Unique middleware name\n * @param handler - Middleware handler function\n * @returns Named middleware with type inference\n *\n * @example\n * ```ts\n * // Regular middleware\n * export const authMiddleware = defineMiddleware('auth', async (c, next) => {\n * const token = c.req.header('authorization');\n * if (!token) {\n * return c.json({ error: 'Unauthorized' }, 401);\n * }\n * c.set('user', await verifyToken(token));\n * await next();\n * });\n *\n * // Factory middleware\n * export const requirePermissions = defineMiddleware('permission',\n * (...permissions: string[]) => async (c, next) => {\n * const user = c.get('user');\n * if (!hasPermissions(user, permissions)) {\n * return c.json({ error: 'Forbidden' }, 403);\n * }\n * await next();\n * }\n * );\n *\n * // server.config.ts\n * export default defineServerConfig()\n * .middlewares([authMiddleware])\n * .routes(appRouter)\n * .build();\n *\n * // routes.ts - skip by name\n * export const publicRoute = route.get('/health')\n * .skip(['auth']) // ✅ Type-safe! Autocomplete!\n * .handler(async (c) => c.success({ status: 'ok' }));\n *\n * // Use factory middleware inline\n * export const protectedRoute = route.get('/admin')\n * .use([requirePermissions('admin:write')])\n * .handler(async (c) => { ... });\n * ```\n */\n/**\n * Options for defineMiddleware\n */\nexport interface DefineMiddlewareOptions\n{\n /**\n * Server-level middleware names to auto-skip when this middleware is used at route level.\n *\n * @example\n * ```ts\n * // optionalAuth auto-skips the global 'auth' middleware\n * export const optionalAuth = defineMiddleware('optionalAuth', handler, {\n * skips: ['auth']\n * });\n *\n * // Usage: .use([optionalAuth]) — no need for .skip(['auth'])\n * ```\n */\n skips?: string[];\n}\n\nexport function defineMiddleware<TName extends string>(\n name: TName,\n handler: MiddlewareHandler,\n options?: DefineMiddlewareOptions,\n): NamedMiddleware<TName>;\n\nexport function defineMiddleware<TName extends string, TArgs extends any[]>(\n name: TName,\n factory: (...args: TArgs) => MiddlewareHandler,\n options?: DefineMiddlewareOptions,\n): NamedMiddlewareFactory<TName, TArgs>;\n\nexport function defineMiddleware<TName extends string, TArgs extends any[] = []>(\n name: TName,\n handlerOrFactory: MiddlewareHandler | ((...args: TArgs) => MiddlewareHandler),\n options?: DefineMiddlewareOptions,\n): NamedMiddleware<TName> | NamedMiddlewareFactory<TName, TArgs>\n{\n const skips = options?.skips;\n\n // Distinguish between regular middleware and factory by parameter count\n // MiddlewareHandler always has exactly 2 parameters: (c, next)\n // Factory has any other number of parameters\n if (typeof handlerOrFactory === 'function')\n {\n const paramCount = handlerOrFactory.length;\n\n // Regular middleware handler (c, next) => ...\n if (paramCount === 2)\n {\n return {\n name,\n handler: handlerOrFactory as MiddlewareHandler,\n _name: name as TName,\n ...(skips && { skips }),\n };\n }\n // Factory (...args) => (c, next) => ...\n else\n {\n // Create a new wrapper function to avoid \"Cannot assign to read only property 'name'\" error\n const factory = handlerOrFactory as (...args: TArgs) => MiddlewareHandler;\n const wrapper = (...args: TArgs) => factory(...args);\n\n // Use Object.defineProperty to set name property (which is read-only by default)\n Object.defineProperty(wrapper, 'name', {\n value: name,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(wrapper, '_name', {\n value: name as TName,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n if (skips)\n {\n Object.defineProperty(wrapper, 'skips', {\n value: skips,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n }\n\n return wrapper as NamedMiddlewareFactory<TName, TArgs>;\n }\n }\n\n // Fallback: treat as regular middleware\n return {\n name,\n handler: handlerOrFactory as MiddlewareHandler,\n _name: name as TName,\n ...(skips && { skips }),\n };\n}\n\n/**\n * Define a middleware factory explicitly\n *\n * Use this when your factory function has exactly 2 parameters,\n * which would be incorrectly detected as a regular middleware handler.\n *\n * @param name - Unique middleware name\n * @param factory - Factory function that returns a middleware handler\n * @returns Named middleware factory with type inference\n *\n * @example\n * ```ts\n * // Factory with 2 params (would be misdetected by defineMiddleware)\n * export const rateLimiter = defineMiddlewareFactory('rateLimit',\n * (limit: number, window: number) => async (c, next) => {\n * // rate limit logic using limit and window\n * await next();\n * }\n * );\n *\n * // Usage\n * route.get('/api')\n * .use([rateLimiter(100, 60000)]) // 100 requests per minute\n * .handler(...)\n * ```\n */\nexport function defineMiddlewareFactory<TName extends string, TArgs extends unknown[]>(\n name: TName,\n factory: (...args: TArgs) => MiddlewareHandler,\n): NamedMiddlewareFactory<TName, TArgs>\n{\n const wrapper = (...args: TArgs) => factory(...args);\n\n Object.defineProperty(wrapper, 'name', {\n value: name,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(wrapper, '_name', {\n value: name as TName,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n return wrapper as NamedMiddlewareFactory<TName, TArgs>;\n}\n\n/**\n * Extract middleware names from an array of named middlewares\n *\n * @example\n * ```ts\n * type Middlewares = [\n * NamedMiddleware<'auth'>,\n * NamedMiddleware<'rateLimit'>\n * ];\n * type Names = ExtractMiddlewareNames<Middlewares>; // 'auth' | 'rateLimit'\n * ```\n */\nexport type ExtractMiddlewareNames<T extends readonly NamedMiddleware<string>[]> =\n T[number]['_name'];\n","/**\n * Cache factory with automatic environment variable detection\n */\n\nimport type { Redis, Cluster, RedisOptions, ClusterOptions } from 'ioredis';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\n\n/**\n * Connection-resilience options applied to every cache client.\n *\n * ioredis defaults to an unbounded offline command queue and 20 retries per\n * request, so a post-startup cache outage can buffer every command and hang\n * awaits. Bound retries (CACHE_MAX_RETRIES_PER_REQUEST, default 3) so commands\n * fail fast instead of hanging; CACHE_ENABLE_OFFLINE_QUEUE (default true) can be\n * set false for strict fail-fast (reject immediately while disconnected).\n */\nfunction resilienceOptions(): Pick<RedisOptions, 'maxRetriesPerRequest' | 'enableOfflineQueue'>\n{\n return {\n maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,\n enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE,\n };\n}\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nexport interface CacheClients {\n /** Primary cache for writes (or both read/write if no replica) */\n write?: Redis | Cluster;\n /** Replica cache for reads (optional, falls back to write) */\n read?: Redis | Cluster;\n}\n\n/**\n * Check if any cache configuration exists in environment\n */\nfunction hasCacheConfig(): boolean\n{\n return !!(\n process.env.CACHE_URL ||\n process.env.CACHE_WRITE_URL ||\n process.env.CACHE_READ_URL ||\n process.env.CACHE_SENTINEL_HOSTS ||\n process.env.CACHE_CLUSTER_NODES\n );\n}\n\n/**\n * Create cache client with TLS support\n */\nfunction createClient(\n RedisClient: new (url: string, options?: RedisOptions) => Redis,\n url: string,\n): Redis\n{\n const options: RedisOptions = { ...resilienceOptions() };\n\n // TLS support for secure connections\n if (url.startsWith('rediss://'))\n {\n options.tls = {\n rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== 'false',\n };\n }\n\n return new RedisClient(url, options);\n}\n\n/**\n * Create cache client(s) from environment variables\n *\n * Supported patterns (priority order):\n * 1. Single instance: CACHE_URL\n * 2. Master-Replica: CACHE_WRITE_URL + CACHE_READ_URL\n * 3. Sentinel: CACHE_SENTINEL_HOSTS + CACHE_MASTER_NAME\n * 4. Cluster: CACHE_CLUSTER_NODES\n *\n * @returns Cache client(s) or undefined if no configuration found\n *\n * @example\n * ```bash\n * # Single (most common)\n * CACHE_URL=redis://localhost:6379\n * CACHE_URL=rediss://secure.cache.com:6380 # TLS\n *\n * # Master-Replica\n * CACHE_WRITE_URL=redis://master:6379\n * CACHE_READ_URL=redis://replica:6379\n *\n * # Sentinel\n * CACHE_SENTINEL_HOSTS=sentinel1:26379,sentinel2:26379\n * CACHE_MASTER_NAME=mymaster\n * CACHE_PASSWORD=secret\n *\n * # Cluster\n * CACHE_CLUSTER_NODES=node1:6379,node2:6379,node3:6379\n * CACHE_PASSWORD=secret\n * ```\n */\nexport async function createCacheFromEnv(): Promise<CacheClients>\n{\n // Quick exit if no cache config\n if (!hasCacheConfig())\n {\n cacheLogger.info('No cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n\n try\n {\n // Dynamic import to avoid bundling if not used\n const ioredis = await import('ioredis');\n const RedisClient = ioredis.default;\n\n // Get environment variables\n const singleUrl = process.env.CACHE_URL;\n const writeUrl = process.env.CACHE_WRITE_URL;\n const readUrl = process.env.CACHE_READ_URL;\n const clusterNodes = process.env.CACHE_CLUSTER_NODES;\n const sentinelHosts = process.env.CACHE_SENTINEL_HOSTS;\n const masterName = process.env.CACHE_MASTER_NAME;\n const password = process.env.CACHE_PASSWORD;\n\n // 1. Single instance (most common - highest priority)\n if (singleUrl && !writeUrl && !readUrl && !clusterNodes)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created single cache instance', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // 2. Master-Replica pattern (both URLs required)\n if (writeUrl && readUrl)\n {\n const write = createClient(RedisClient, writeUrl);\n const read = createClient(RedisClient, readUrl);\n cacheLogger.debug('Created master-replica cache instances');\n\n return { write, read };\n }\n\n // 3. Sentinel pattern\n if (sentinelHosts && masterName)\n {\n const sentinels = sentinelHosts.split(',').map((host) =>\n {\n const [hostname, port] = host.trim().split(':');\n\n return { host: hostname, port: Number(port) || 26379 };\n });\n\n const options: RedisOptions = {\n ...resilienceOptions(),\n sentinels,\n name: masterName,\n password,\n };\n\n const client = new RedisClient(options);\n cacheLogger.debug('Created sentinel cache instance', { masterName, sentinels: sentinels.length });\n\n return { write: client, read: client };\n }\n\n // 4. Cluster pattern\n if (clusterNodes)\n {\n const nodes = clusterNodes.split(',').map((node) =>\n {\n const [host, port] = node.trim().split(':');\n\n return { host, port: Number(port) || 6379 };\n });\n\n const clusterOptions: ClusterOptions = {\n redisOptions: {\n ...resilienceOptions(),\n password,\n },\n };\n\n const cluster = new RedisClient.Cluster(nodes, clusterOptions);\n cacheLogger.debug('Created cluster cache instance', { nodes: nodes.length });\n\n return { write: cluster, read: cluster };\n }\n\n // 5. Fallback: Single URL with other configs present\n if (singleUrl)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created cache instance (fallback)', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // No valid configuration\n cacheLogger.info('No valid cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n catch (error)\n {\n if (error instanceof Error)\n {\n // Check if it's a missing dependency error\n if (error.message.includes('Cannot find module'))\n {\n cacheLogger.warn(\n 'Cache client library not installed',\n error,\n {\n suggestion: 'Install ioredis to enable cache: pnpm install ioredis',\n mode: 'disabled',\n },\n );\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n error,\n { mode: 'disabled' },\n );\n }\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n { error: String(error), mode: 'disabled' },\n );\n }\n\n return { write: undefined, read: undefined };\n }\n}\n\n/**\n * Create single cache client (backward compatibility)\n * Only returns write instance\n */\nexport async function createSingleCacheFromEnv(): Promise<Redis | Cluster | undefined>\n{\n const { write } = await createCacheFromEnv();\n\n return write;\n}\n","/**\n * Global cache instance manager\n * Provides singleton access to cache (Valkey/Redis) across all modules\n * Supports Master-Replica pattern with separate read/write instances\n *\n * When cache is unavailable, falls back to disabled mode gracefully\n */\n\nimport type { Redis, Cluster } from 'ioredis';\n\nimport { createCacheFromEnv } from './cache-factory';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\n// ── globalThis singleton ──────────────────────────────────────────\n// CJS/ESM dual-package hazard: 모듈이 두 번 로드되어도\n// Symbol.for() + globalThis로 동일 상태를 공유한다.\ninterface CacheState\n{\n write: Redis | Cluster | undefined;\n read: Redis | Cluster | undefined;\n disabled: boolean;\n}\n\nconst CACHE_KEY = Symbol.for('@spfn/core:cache');\n\nconst state: CacheState = ((globalThis as any)[CACHE_KEY] ??= {\n write: undefined,\n read: undefined,\n disabled: false,\n});\n\n/**\n * Get global cache write instance\n *\n * @returns Cache write instance or undefined if disabled/not initialized\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * await cache.set('key', 'value');\n * } else {\n * // Cache disabled - handle gracefully\n * console.log('Cache unavailable, skipping...');\n * }\n * ```\n */\nexport function getCache(): Redis | Cluster | undefined\n{\n return state.write;\n}\n\n/**\n * Get global cache read instance (falls back to write if no replica)\n *\n * @returns Cache read instance or write instance as fallback, undefined if disabled\n *\n * @example\n * ```typescript\n * import { getCacheRead } from '@spfn/core/cache';\n *\n * const cache = getCacheRead();\n * if (cache) {\n * const value = await cache.get('key');\n * }\n * ```\n */\nexport function getCacheRead(): Redis | Cluster | undefined\n{\n return state.read ?? state.write;\n}\n\n/**\n * Check if cache is disabled (connection failed or not configured)\n *\n * @example\n * ```typescript\n * import { isCacheDisabled } from '@spfn/core/cache';\n *\n * if (isCacheDisabled()) {\n * // Use alternative strategy (e.g., in-memory cache, database)\n * return await fetchFromDatabase();\n * }\n * ```\n */\nexport function isCacheDisabled(): boolean\n{\n return state.disabled;\n}\n\n/**\n * Set global cache instances (for testing or manual configuration)\n *\n * @param write - Cache write instance\n * @param read - Cache read instance (optional, defaults to write)\n *\n * @example\n * ```typescript\n * import { setCache } from '@spfn/core/cache';\n * import Redis from 'ioredis';\n *\n * const write = new Redis('redis://master:6379');\n * const read = new Redis('redis://replica:6379');\n * setCache(write, read);\n * ```\n */\nexport function setCache(\n write: Redis | Cluster | undefined,\n read?: Redis | Cluster | undefined,\n): void\n{\n state.write = write;\n state.read = read ?? write;\n state.disabled = !write;\n}\n\n/**\n * Initialize cache from environment variables\n * Automatically called by startServer()\n *\n * Supported environment variables (priority order):\n * - VALKEY_URL / CACHE_URL (single instance)\n * - VALKEY_WRITE_URL + VALKEY_READ_URL (master-replica)\n * - VALKEY_SENTINEL_HOSTS + VALKEY_MASTER_NAME (sentinel)\n * - VALKEY_CLUSTER_NODES (cluster)\n * - VALKEY_TLS_REJECT_UNAUTHORIZED (TLS config)\n * - Legacy: REDIS_* (backward compatibility)\n *\n * @returns Object with write and read instances, or undefined if disabled\n *\n * @example\n * ```typescript\n * import { initCache } from '@spfn/core/cache';\n *\n * // Manual initialization (not needed if using startServer)\n * const { write, read, disabled } = await initCache();\n * if (!disabled) {\n * console.log('Cache available');\n * }\n * ```\n */\nexport async function initCache(): Promise<{\n write?: Redis | Cluster;\n read?: Redis | Cluster;\n disabled: boolean;\n}>\n{\n // Already initialized\n if (state.write)\n {\n return { write: state.write, read: state.read, disabled: state.disabled };\n }\n\n // Auto-detect from environment\n const { write, read } = await createCacheFromEnv();\n\n if (write)\n {\n try\n {\n // Test connection\n await write.ping();\n\n // Test read instance if different\n if (read && read !== write)\n {\n await read.ping();\n }\n\n state.write = write;\n state.read = read;\n state.disabled = false;\n\n const hasReplica = read && read !== write;\n cacheLogger.info(\n hasReplica\n ? 'Cache connected (Master-Replica)'\n : 'Cache connected',\n { mode: 'enabled' },\n );\n\n return { write: state.write, read: state.read, disabled: false };\n }\n catch (error)\n {\n cacheLogger.error(\n 'Cache connection failed - running in disabled mode',\n error instanceof Error ? error : new Error(String(error)),\n { mode: 'disabled' },\n );\n\n // Clean up failed connections\n try\n {\n await write.quit();\n if (read && read !== write)\n {\n await read.quit();\n }\n }\n catch\n {\n // Ignore cleanup errors\n }\n\n state.disabled = true;\n\n return { write: undefined, read: undefined, disabled: true };\n }\n }\n\n // No configuration or library not installed\n state.disabled = true;\n cacheLogger.info('Cache disabled - no configuration or library not installed', { mode: 'disabled' });\n\n return { write: undefined, read: undefined, disabled: true };\n}\n\n/**\n * Close all cache connections and cleanup\n *\n * @example\n * ```typescript\n * import { closeCache } from '@spfn/core/cache';\n *\n * // During graceful shutdown\n * await closeCache();\n * ```\n */\nexport async function closeCache(): Promise<void>\n{\n if (state.disabled)\n {\n cacheLogger.debug('Cache already disabled, nothing to close');\n\n return;\n }\n\n const closePromises: Promise<unknown>[] = [];\n\n if (state.write)\n {\n closePromises.push(\n state.write.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache write instance', err);\n }),\n );\n }\n\n if (state.read && state.read !== state.write)\n {\n closePromises.push(\n state.read.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache read instance', err);\n }),\n );\n }\n\n await Promise.all(closePromises);\n\n state.write = undefined;\n state.read = undefined;\n state.disabled = true;\n\n cacheLogger.info('Cache connections closed', { mode: 'disabled' });\n}\n\n/**\n * Get cache connection info (for debugging)\n *\n * @example\n * ```typescript\n * import { getCacheInfo } from '@spfn/core/cache';\n *\n * const info = getCacheInfo();\n * console.log(info);\n * // {\n * // hasWrite: true,\n * // hasRead: true,\n * // isReplica: true,\n * // disabled: false\n * // }\n * ```\n */\nexport function getCacheInfo(): {\n hasWrite: boolean;\n hasRead: boolean;\n isReplica: boolean;\n disabled: boolean;\n}\n{\n return {\n hasWrite: !!state.write,\n hasRead: !!state.read,\n isReplica: !!(state.read && state.read !== state.write),\n disabled: state.disabled,\n };\n}\n","/**\n * Error Registry\n *\n * Central registry for serializable error types.\n * Enables automatic error deserialization on the client side.\n */\n\nimport type { SerializableError } from './serializable-error';\n\n/**\n * Error constructor type\n */\nexport type SerializableErrorConstructor = new (data: any) => SerializableError;\n\n/**\n * Input type for ErrorRegistry constructor\n */\nexport type ErrorRegistryInput =\n | SerializableErrorConstructor\n | SerializableErrorConstructor[]\n | ErrorRegistry;\n\n/**\n * Error registry for serialization/deserialization\n *\n * @example\n * ```typescript\n * // Create registry with initial errors\n * const registry = new ErrorRegistry([\n * errorRegistry, // Another ErrorRegistry\n * PaymentFailedError, // Single error class\n * [RefundError, OrderError], // Array of error classes\n * ]);\n *\n * // Or create empty and append\n * const registry = new ErrorRegistry();\n * registry\n * .append(ValidationError)\n * .append([PaymentFailedError, RefundError]);\n *\n * // Deserialize from JSON\n * const error = registry.deserialize({\n * __type: 'PaymentFailedError',\n * message: 'Payment failed',\n * transactionId: 'tx_123',\n * reason: 'insufficient_funds'\n * });\n *\n * // error instanceof PaymentFailedError === true\n * ```\n */\nexport class ErrorRegistry\n{\n private errors = new Map<string, SerializableErrorConstructor>();\n\n constructor(initialErrors?: ErrorRegistryInput[])\n {\n if (initialErrors)\n {\n for (const input of initialErrors)\n {\n if (input instanceof ErrorRegistry)\n {\n this.concat(input);\n }\n else if (Array.isArray(input))\n {\n this.append(input);\n }\n else\n {\n this.append(input);\n }\n }\n }\n }\n\n /**\n * Append error class(es) to the registry\n *\n * @param ErrorClass - Single error constructor\n * @returns This registry for chaining\n */\n append(ErrorClass: SerializableErrorConstructor): this;\n\n /**\n * Append error class(es) to the registry\n *\n * @param ErrorClasses - Array of error constructors\n * @returns This registry for chaining\n */\n append(ErrorClasses: SerializableErrorConstructor[]): this;\n\n /**\n * Append error class(es) to the registry\n *\n * @param input - Error constructor or array of constructors\n * @returns This registry for chaining\n */\n append(input: SerializableErrorConstructor | SerializableErrorConstructor[]): this\n {\n if (Array.isArray(input))\n {\n for (const ErrorClass of input)\n {\n this.errors.set(ErrorClass.name, ErrorClass);\n }\n }\n else\n {\n this.errors.set(input.name, input);\n }\n\n return this;\n }\n\n /**\n * Concatenate another ErrorRegistry into this one\n *\n * @param registry - Another ErrorRegistry to merge\n * @returns This registry for chaining\n */\n concat(registry: ErrorRegistry): this\n {\n for (const [name, ErrorClass] of registry.errors)\n {\n this.errors.set(name, ErrorClass);\n }\n\n return this;\n }\n\n /**\n * Check if error type is registered\n *\n * @param name - Error class name\n */\n has(name: string): boolean\n {\n return this.errors.has(name);\n }\n\n /**\n * Deserialize error from JSON data\n *\n * @param data - Serialized error data with __type field\n * @returns Deserialized error instance\n * @throws Error if error type is not registered\n */\n deserialize(data: { __type: string; [key: string]: any }): SerializableError\n {\n const ErrorClass = this.errors.get(data.__type);\n\n if (!ErrorClass)\n {\n throw new Error(`Unknown error type: ${data.__type}`);\n }\n\n // Pass entire data object to constructor\n return new ErrorClass(data);\n }\n\n /**\n * Try to deserialize error, return null if type unknown\n *\n * @param data - Serialized error data\n * @returns Deserialized error or null\n */\n tryDeserialize(data: { __type?: string; [key: string]: any }): SerializableError | null\n {\n if (!data.__type || !this.has(data.__type))\n {\n return null;\n }\n\n try\n {\n return this.deserialize(data as any);\n }\n catch\n {\n return null;\n }\n }\n\n /**\n * Get all registered error types\n */\n getRegisteredTypes(): string[]\n {\n return Array.from(this.errors.keys());\n }\n}\n","/**\n * Serializable Error Base Class\n *\n * Base class for errors that can be serialized/deserialized across HTTP boundary.\n * Automatically serializes public fields for transmission to client.\n */\n\n/**\n * Serialized error format for JSON transmission\n */\nexport interface SerializedError\n{\n __type: string;\n message: string;\n [key: string]: unknown;\n}\n\n/**\n * Base class for all serializable errors\n *\n * Features:\n * - Auto-serialization of public fields via toJSON()\n * - Constructor accepts object with all fields\n * - Type-safe error handling with instanceof\n *\n * @example\n * ```typescript\n * class PaymentFailedError extends SerializableError\n * {\n * readonly statusCode = 402;\n * transactionId!: string;\n * reason!: 'insufficient_funds' | 'card_declined';\n *\n * constructor(data: {\n * message: string;\n * transactionId: string;\n * reason: 'insufficient_funds' | 'card_declined';\n * })\n * {\n * super(data.message);\n * this.name = 'PaymentFailedError';\n * Object.assign(this, data);\n * }\n * }\n * ```\n */\nexport abstract class SerializableError extends Error\n{\n /**\n * HTTP status code for this error type\n */\n abstract readonly statusCode: number;\n\n /**\n * Serialize error to JSON-compatible object\n *\n * Automatically includes:\n * - __type: Constructor name for deserialization\n * - message: Error message\n * - All public instance properties (except name, stack)\n */\n toJSON(): SerializedError\n {\n const json: SerializedError = {\n __type: this.constructor.name,\n message: this.message,\n };\n\n // Extract all public instance properties\n for (const key of Object.keys(this))\n {\n // Skip Error built-ins and statusCode (inferred from type)\n if (key !== 'name' && key !== 'message' && key !== 'stack' && key !== 'statusCode')\n {\n json[key] = (this as unknown as Record<string, unknown>)[key];\n }\n }\n\n return json;\n }\n}\n","/**\n * HTTP Error Classes\n *\n * Standard HTTP error classes for API responses\n * All errors are serializable for type-safe client-side error handling\n */\n\nimport { SerializableError } from './serializable-error';\n\n/**\n * Base HTTP Error\n *\n * Base class for all HTTP-related errors\n */\nexport class HttpError extends SerializableError\n{\n public readonly statusCode: number;\n public details?: Record<string, unknown>;\n\n constructor(data: {\n message: string;\n statusCode: number;\n details?: Record<string, unknown>;\n })\n {\n super(data.message);\n\n this.name = 'HttpError';\n this.statusCode = data.statusCode;\n\n if (data.details)\n {\n this.details = data.details;\n }\n\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n/**\n * Bad Request Error (400)\n *\n * Generic bad request - malformed syntax, invalid parameters, etc.\n */\nexport class BadRequestError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Bad request',\n statusCode: 400,\n details: data.details,\n });\n\n this.name = 'BadRequestError';\n }\n}\n\n/**\n * Validation Error (400)\n *\n * Input validation failure (request params, query, body)\n * Used by define-route system for automatic validation\n */\nexport class ValidationError extends HttpError\n{\n fields?: Array<{ path: string; message: string; value?: any }>;\n\n constructor(data: {\n message: string;\n fields?: Array<{ path: string; message: string; value?: any }>;\n details?: Record<string, any>;\n })\n {\n super({\n message: data.message,\n statusCode: 400,\n details: data.details,\n });\n\n this.name = 'ValidationError';\n\n if (data.fields)\n {\n this.fields = data.fields;\n }\n }\n}\n\n/**\n * Unauthorized Error (401)\n *\n * Authentication required or authentication failed\n */\nexport class UnauthorizedError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Authentication required',\n statusCode: 401,\n details: data.details,\n });\n\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Forbidden Error (403)\n *\n * Authenticated but lacks permission to access resource\n */\nexport class ForbiddenError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Access forbidden',\n statusCode: 403,\n details: data.details,\n });\n\n this.name = 'ForbiddenError';\n }\n}\n\n/**\n * Not Found Error (404)\n *\n * Requested resource does not exist\n */\nexport class NotFoundError extends HttpError\n{\n resource?: string;\n\n constructor(data: { message?: string; resource?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource not found',\n statusCode: 404,\n details: data.details,\n });\n\n this.name = 'NotFoundError';\n\n if (data.resource)\n {\n this.resource = data.resource;\n }\n }\n}\n\n/**\n * Conflict Error (409)\n *\n * Generic conflict - resource state conflict, concurrent modification, etc.\n */\nexport class ConflictError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource conflict',\n statusCode: 409,\n details: data.details,\n });\n\n this.name = 'ConflictError';\n }\n}\n\n/**\n * Gone Error (410)\n *\n * Resource permanently deleted and no longer available\n */\nexport class GoneError extends HttpError\n{\n resource?: string;\n\n constructor(data: { message?: string; resource?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Resource permanently deleted',\n statusCode: 410,\n details: data.details,\n });\n\n this.name = 'GoneError';\n\n if (data.resource)\n {\n this.resource = data.resource;\n }\n }\n}\n\n/**\n * Too Many Requests Error (429)\n *\n * Rate limit exceeded\n */\nexport class TooManyRequestsError extends HttpError\n{\n retryAfter?: number;\n\n constructor(data: {\n message?: string;\n retryAfter?: number;\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Too many requests',\n statusCode: 429,\n details: data.details,\n });\n\n this.name = 'TooManyRequestsError';\n\n if (data.retryAfter)\n {\n this.retryAfter = data.retryAfter;\n }\n }\n}\n\n/**\n * Internal Server Error (500)\n *\n * Generic server error when no specific error type applies\n */\nexport class InternalServerError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Internal server error',\n statusCode: 500,\n details: data.details,\n });\n\n this.name = 'InternalServerError';\n }\n}\n\n/**\n * Unsupported Media Type Error (415)\n *\n * Media type not supported - invalid file type, content type, etc.\n */\nexport class UnsupportedMediaTypeError extends HttpError\n{\n mediaType?: string;\n supportedTypes?: string[];\n\n constructor(data: {\n message?: string;\n mediaType?: string;\n supportedTypes?: string[];\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Unsupported media type',\n statusCode: 415,\n details: data.details,\n });\n\n this.name = 'UnsupportedMediaTypeError';\n\n if (data.mediaType)\n {\n this.mediaType = data.mediaType;\n }\n\n if (data.supportedTypes)\n {\n this.supportedTypes = data.supportedTypes;\n }\n }\n}\n\n/**\n * Unprocessable Entity Error (422)\n *\n * Request is well-formed but contains semantic errors\n */\nexport class UnprocessableEntityError extends HttpError\n{\n constructor(data: { message?: string; details?: Record<string, any> } = {})\n {\n super({\n message: data.message || 'Unprocessable entity',\n statusCode: 422,\n details: data.details,\n });\n\n this.name = 'UnprocessableEntityError';\n }\n}\n\n/**\n * Service Unavailable Error (503)\n *\n * Service temporarily unavailable (maintenance, overload, etc.)\n */\nexport class ServiceUnavailableError extends HttpError\n{\n retryAfter?: number;\n\n constructor(data: {\n message?: string;\n retryAfter?: number;\n details?: Record<string, any>;\n } = {})\n {\n super({\n message: data.message || 'Service unavailable',\n statusCode: 503,\n details: data.details,\n });\n\n this.name = 'ServiceUnavailableError';\n\n if (data.retryAfter)\n {\n this.retryAfter = data.retryAfter;\n }\n }\n}\n","/**\n * Database Error Classes\n *\n * Type-safe error handling with custom error class hierarchy\n * Mapped to HTTP status codes for API responses\n * All errors extend SerializableError for consistent JSON serialization\n */\n\nimport { SerializableError } from './serializable-error';\n\n/**\n * Base Database Error\n *\n * Base class for all database-related errors\n */\nexport class DatabaseError<TDetails extends Record<string, unknown> = Record<string, unknown>>\n extends SerializableError\n{\n public readonly statusCode: number;\n public readonly details?: TDetails;\n\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: TDetails;\n })\n {\n super(data.message);\n this.name = 'DatabaseError';\n this.statusCode = data.statusCode ?? 500;\n this.details = data.details;\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Whether this error's message/details are derived from the raw database\n * driver (SQL text, table/column/constraint names, parameter values) and\n * therefore must NOT be exposed to clients in production. Defined as a\n * prototype getter so it is never serialized into the response by toJSON().\n * Subclasses with a safe, constructed message override this to `false`.\n */\n get internal(): boolean\n {\n return true;\n }\n}\n\n/**\n * Connection Error (503 Service Unavailable)\n *\n * Database connection failure, connection pool exhaustion, etc.\n */\nexport class ConnectionError extends DatabaseError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 503, details: data.details });\n this.name = 'ConnectionError';\n }\n}\n\n/**\n * Query Error (500 Internal Server Error)\n *\n * SQL query execution failure, syntax errors, etc.\n */\nexport class QueryError extends DatabaseError\n{\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n })\n {\n super({\n message: data.message,\n statusCode: data.statusCode ?? 500,\n details: data.details,\n });\n this.name = 'QueryError';\n }\n}\n\n/**\n * Entity Not Found Error (404 Not Found)\n *\n * Database entity does not exist\n */\nexport class EntityNotFoundError extends QueryError\n{\n public readonly resource: string;\n public readonly id: string | number;\n\n constructor(data: { resource: string; id: string | number })\n {\n super({\n message: `${data.resource} with id ${data.id} not found`,\n statusCode: 404,\n details: { resource: data.resource, id: data.id },\n });\n this.name = 'EntityNotFoundError';\n this.resource = data.resource;\n this.id = data.id;\n }\n\n // Message/details are constructed from the resource + id, not driver text\n override get internal(): boolean\n {\n return false;\n }\n}\n\n/**\n * Constraint Violation Error (400 Bad Request)\n *\n * Database constraint violation (NOT NULL, CHECK, FOREIGN KEY, etc.)\n * This is different from HTTP ValidationError which validates request input\n */\nexport class ConstraintViolationError extends QueryError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 400, details: data.details });\n this.name = 'ConstraintViolationError';\n }\n}\n\n/**\n * Transaction Error (500 Internal Server Error)\n *\n * Transaction start/commit/rollback failure\n */\nexport class TransactionError extends DatabaseError\n{\n constructor(data: {\n message: string;\n statusCode?: number;\n details?: Record<string, unknown>;\n })\n {\n super({\n message: data.message,\n statusCode: data.statusCode ?? 500,\n details: data.details,\n });\n this.name = 'TransactionError';\n }\n}\n\n/**\n * Deadlock Error (409 Conflict)\n *\n * Database deadlock detected\n */\nexport class DeadlockError extends TransactionError\n{\n constructor(data: { message: string; details?: Record<string, unknown> })\n {\n super({ message: data.message, statusCode: 409, details: data.details });\n this.name = 'DeadlockError';\n }\n}\n\n/**\n * Duplicate Entry Error (409 Conflict)\n *\n * Unique constraint violation (e.g., duplicate email)\n */\nexport class DuplicateEntryError extends QueryError\n{\n public readonly field: string;\n public readonly value: string | number;\n\n constructor(data: { field: string; value: string | number })\n {\n // The client-facing message/details name the field but NOT the value: echoing\n // the value back (e.g. an email) confirms it exists, an enumeration aid. The\n // raw value stays on the instance for server-side handling/logging.\n super({\n message: `${data.field} already exists`,\n statusCode: 409,\n details: { field: data.field },\n });\n this.name = 'DuplicateEntryError';\n this.field = data.field;\n this.value = data.value;\n }\n\n // Field-only message/details — safe to surface to the client (not internal)\n override get internal(): boolean\n {\n return false;\n }\n}\n","/**\n * Error Module Exports\n *\n * Entry point for error handling module with serialization support\n */\n\n// Core Error Registry\n// Pre-configured registry with all built-in HTTP and Database errors\nimport { ErrorRegistry } from './error-registry';\nimport {\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n} from './http-errors';\nimport {\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n} from './database-errors';\n\n// Base Classes\nexport { SerializableError } from './serializable-error';\nexport type { SerializedError } from './serializable-error';\nexport { ErrorRegistry } from './error-registry';\nexport type { SerializableErrorConstructor, ErrorRegistryInput } from './error-registry';\n\n// Database Error Classes\nexport {\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n} from './database-errors';\n\n// HTTP Error Classes\nexport {\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n} from './http-errors';\n\n// Error Utilities\nexport {\n isDatabaseError,\n isHttpError,\n hasStatusCode,\n} from './error-utils';\n\nexport const errorRegistry = new ErrorRegistry();\n\n// HTTP Errors\nerrorRegistry.append([\n HttpError,\n BadRequestError,\n ValidationError,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n GoneError,\n TooManyRequestsError,\n UnsupportedMediaTypeError,\n UnprocessableEntityError,\n InternalServerError,\n ServiceUnavailableError,\n]);\n\n// Database Errors\nerrorRegistry.append([\n DatabaseError,\n ConnectionError,\n QueryError,\n EntityNotFoundError,\n ConstraintViolationError,\n TransactionError,\n DeadlockError,\n DuplicateEntryError,\n]);\n\nexport * as HttpErrors from './http-errors';\nexport * as DatabaseErrors from './database-errors';\n","/**\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","/**\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\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\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 // Cause chain — surfaces original PG errors hidden by DrizzleQueryError\n if (error.cause instanceof Error)\n {\n lines.push(`Caused by: ${formatError(error.cause)}`);\n }\n else if (error.cause !== undefined)\n {\n lines.push(`Caused by: ${String(error.cause)}`);\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 * Error 객체를 cause 체인까지 포함한 직렬화 가능 객체로 변환\n */\nfunction formatErrorCauseChain(error: Error): Record<string, unknown>\n{\n const result: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n\n if (error.cause instanceof Error)\n {\n result.cause = formatErrorCauseChain(error.cause);\n }\n else if (error.cause !== undefined)\n {\n result.cause = String(error.cause);\n }\n\n return result;\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 = formatErrorCauseChain(metadata.error);\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","/**\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\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, formatArg: string | number | boolean): void;\n debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n debug(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n info(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n warn(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n error(message: string, errorOrContext?: 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, formatArg: string | number | boolean): void;\n fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n fatal(message: string, errorOrContext?: 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","/**\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","/**\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","/**\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\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();\n","/**\n * @spfn/core - Rate limit middleware\n *\n * Redis-backed fixed-window rate limiter, registered under the named\n * 'rateLimit' middleware so routes can opt out with `.skip(['rateLimit'])`.\n *\n * Storage is the shared cache (ioredis). The counter is incremented and given\n * its window expiry in a single atomic Lua call, so concurrent requests cannot\n * race between INCR and PEXPIRE. When no cache is configured the limiter fails\n * OPEN by default (logs a warning) — matching the proxy-guard nonce store's\n * graceful degradation — so development without Redis still works. Set\n * `failClosed` to reject instead.\n */\n\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { PROXY_CLIENT_IP_HEADER } from '../security/proxy-signature';\nimport { defineMiddleware, defineMiddlewareFactory } from '../route/define-middleware';\nimport type { NamedMiddleware } from '../route/define-middleware';\nimport { getCache, isCacheDisabled } from '../cache';\nimport { TooManyRequestsError } from '../errors';\nimport { logger } from '../logger';\n\nconst rateLimitLogger = logger.child('@spfn/core:rate-limit');\n\n/**\n * Atomic fixed-window counter: increment, set the window expiry on the first\n * hit, and return [count, pttlMs]. Keeping incr+expire in one round trip avoids\n * a leaked key that never expires when a process dies between the two calls.\n */\nconst FIXED_WINDOW_LUA = `\nlocal count = redis.call('INCR', KEYS[1])\nif count == 1 then\n redis.call('PEXPIRE', KEYS[1], ARGV[1])\nend\nreturn { count, redis.call('PTTL', KEYS[1]) }\n`;\n\n/**\n * One identity dimension to limit on. A bare string uses the top-level `limit`;\n * an object can carry its own `limit` so dimensions can differ (e.g. a loose\n * per-IP cap alongside a tight per-account cap).\n */\nexport type RateLimitDimension = string | { key: string; limit?: number };\n\nexport interface RateLimitOptions\n{\n /** Max requests allowed per window, applied to each dimension without its own `limit`. */\n limit: number;\n\n /** Window length in milliseconds. */\n windowMs: number;\n\n /**\n * Namespace for the counter keys. Defaults to `${method} ${routePath}` so\n * each route is limited independently.\n */\n scope?: string;\n\n /**\n * Identity dimensions to limit on. Each non-empty value is counted\n * separately and the strictest wins (e.g. by IP and by account). A dimension\n * may override the top-level `limit` via `{ key, limit }`. Defaults to the\n * client IP only.\n */\n by?: (c: Context) => (RateLimitDimension | null | undefined)[] | Promise<(RateLimitDimension | null | undefined)[]>;\n\n /** Reject with 429 instead of allowing through when the cache is unavailable. */\n failClosed?: boolean;\n\n /** Message for the 429 response. */\n message?: string;\n}\n\n/**\n * Best-effort client IP for rate-limit keying.\n *\n * Prefers the proxy-forwarded real client IP, but ONLY when proxy-guard verified\n * the request came through our proxy (`clientType` set and not 'untrusted') — on a\n * verified request the proxy is the sole hop, so the value is the true client and\n * isn't client-spoofable. Otherwise (no proxy-guard, or unverified/direct request)\n * the forwarded header is attacker-settable, so fall back to the raw chain — whose\n * leftmost hop is itself spoofable, so still pair with an account/target dimension\n * for anything security-sensitive.\n *\n * Last resort before giving up is the TCP peer address from the Node adapter\n * socket. Without it, a deployment that sets no forwarding header would collapse\n * every client onto a single `'unknown'` bucket.\n */\nexport function getClientIp(c: Context): string\n{\n const clientType = c.get('clientType');\n if (clientType && clientType !== 'untrusted')\n {\n const forwarded = c.req.header(PROXY_CLIENT_IP_HEADER);\n if (forwarded)\n {\n return forwarded;\n }\n }\n\n const forwardedFor = c.req.header('x-forwarded-for');\n\n return forwardedFor?.split(',')[0]?.trim()\n || c.req.header('x-real-ip')\n || socketRemoteAddress(c)\n || 'unknown';\n}\n\n/**\n * TCP peer address from the @hono/node-server adapter, when present. Mirrors the\n * adapter's own getConnInfo access path and degrades to undefined on other\n * runtimes (Bun/edge) so callers fall through to 'unknown'.\n */\nfunction socketRemoteAddress(c: Context): string | undefined\n{\n const env = c.env as {\n server?: { incoming?: { socket?: { remoteAddress?: string } } };\n incoming?: { socket?: { remoteAddress?: string } };\n } | undefined;\n const bindings = env?.server ?? env;\n\n return bindings?.incoming?.socket?.remoteAddress;\n}\n\n/**\n * Redis-backed fixed-window rate limiter.\n *\n * @example\n * ```typescript\n * route.post('/_auth/login')\n * .use([rateLimit({ limit: 10, windowMs: 60_000 })])\n * .handler(...);\n * ```\n */\nexport const rateLimit = defineMiddlewareFactory(\n 'rateLimit',\n (options: RateLimitOptions): MiddlewareHandler =>\n {\n const { limit, windowMs, scope, by, failClosed = false, message } = options;\n\n return async (c, next) =>\n {\n const cache = getCache();\n\n if (!cache || isCacheDisabled())\n {\n if (failClosed)\n {\n throw new TooManyRequestsError({ message: message || 'Rate limiter unavailable' });\n }\n\n rateLimitLogger.warn('Cache unavailable — rate limit not enforced (fail-open)', {\n path: c.req.path,\n });\n\n return next();\n }\n\n const dimensions = (by ? await by(c) : [getClientIp(c)])\n .filter((d): d is RateLimitDimension => Boolean(d));\n\n const ns = scope || `${c.req.method} ${c.req.routePath || c.req.path}`;\n\n for (const dimension of dimensions)\n {\n const key = typeof dimension === 'string' ? dimension : dimension.key;\n if (!key)\n {\n continue;\n }\n\n const dimLimit = typeof dimension === 'string' ? limit : (dimension.limit ?? limit);\n\n const [count, pttl] = await cache.eval(\n FIXED_WINDOW_LUA,\n 1,\n `ratelimit:${ns}:${key}`,\n String(windowMs),\n ) as [number, number];\n\n if (count > dimLimit)\n {\n const retryAfter = Math.max(1, Math.ceil((pttl > 0 ? pttl : windowMs) / 1000));\n c.header('Retry-After', String(retryAfter));\n\n throw new TooManyRequestsError({\n message: message || 'Too many requests, please try again later',\n retryAfter,\n });\n }\n }\n\n return next();\n };\n },\n);\n\n/**\n * Named rate-limit policies, populated once at boot from\n * `defineServerConfig().rateLimit({ policies })`. A package tags a route with\n * rateLimitPolicy() and the consuming app supplies the numbers here, so policy\n * tuning lives in one place rather than being hard-coded in each package.\n */\nconst policyRegistry = new Map<string, RateLimitOptions>();\n\n/**\n * App-wide fail-closed default applied to policy tags that don't set `failClosed`\n * themselves. Set at boot from RATE_LIMIT_FAIL_CLOSED so operators can make ALL\n * limiters (including named policies on auth routes) reject on cache outage — the\n * env flag would otherwise only reach the global default limiter.\n */\nlet policyFailClosedDefault = false;\n\n/** Set the fail-closed default for named policies. Called by the server at boot. */\nexport function setRateLimitFailClosedDefault(failClosed: boolean): void\n{\n policyFailClosedDefault = failClosed;\n}\n\n/**\n * Replace the named-policy registry. Called by the server at boot; passing\n * undefined clears it (so a restart without policies doesn't keep stale ones).\n */\nexport function setRateLimitPolicies(policies?: Record<string, RateLimitOptions>): void\n{\n policyRegistry.clear();\n\n if (!policies)\n {\n return;\n }\n\n for (const [name, options] of Object.entries(policies))\n {\n policyRegistry.set(name, options);\n }\n}\n\n/** Look up a configured policy by name — undefined when the app didn't set it. */\nexport function getRateLimitPolicy(name: string): RateLimitOptions | undefined\n{\n return policyRegistry.get(name);\n}\n\n/**\n * Rate-limit a route under a named policy.\n *\n * A package author tags a sensitive route with a policy name plus a safe\n * fallback; the consuming app tunes the numbers centrally via\n * `defineServerConfig().rateLimit({ policies: { [name]: {...} } })`. When the\n * app configures the policy, its fields override the fallback (shallow merge);\n * otherwise the fallback applies, so the route is protected out of the box.\n *\n * LAYERS on top of the global default limiter rather than replacing it: the tag\n * has a distinct name (`rateLimit:<name>`) and does not skip the global, so a\n * route gets both the global per-IP floor (when enabled) and this policy's own\n * bucket — whichever is stricter trips first. Opt out of the global floor on a\n * route with `.skip(['rateLimit'])`.\n *\n * The counter scope defaults to the policy name, so (a) every route sharing a\n * policy shares one bucket, and (b) the key never collides with the global\n * default's per-route `${method} ${path}` scope.\n *\n * @example\n * ```typescript\n * route.post('/_auth/login')\n * .use([rateLimitPolicy('auth-login', { limit: 5, windowMs: 60_000 })])\n * .handler(...);\n * ```\n */\nexport function rateLimitPolicy(name: string, fallback: RateLimitOptions): NamedMiddleware<string>\n{\n // Resolution is deferred to the first request: the registry is populated at\n // server boot, which runs after route modules are imported. Cached after the\n // first hit since the registry is static once the server is up.\n let resolved: MiddlewareHandler | undefined;\n\n const handler: MiddlewareHandler = (c, next) =>\n {\n if (!resolved)\n {\n const configured = getRateLimitPolicy(name);\n const merged: RateLimitOptions = configured ? { ...fallback, ...configured } : { ...fallback };\n\n if (merged.scope === undefined)\n {\n merged.scope = name;\n }\n\n if (merged.failClosed === undefined)\n {\n merged.failClosed = policyFailClosedDefault;\n }\n\n resolved = rateLimit(merged);\n }\n\n return resolved(c, next);\n };\n\n return defineMiddleware(`rateLimit:${name}`, handler);\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';\nimport { getClientIp } from './rate-limit';\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\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 // Same resolution as rate limiting: the proxy-forwarded client IP when the\n // request is proxy-verified, else the (spoofable) raw chain.\n const ip = getClientIp(c);\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}\n","/**\n * Proxy-guard middleware\n *\n * Verifies that an inbound request came through a trusted Next.js RPC proxy\n * (HMAC signature) and/or from an allowed browser origin, then tags the request\n * with a `clientType`. Lets the backend reject direct-to-backend calls that\n * bypass the proxy.\n *\n * This does NOT replace user authentication (JWT) — it answers \"what kind of\n * client is this?\", not \"who is the user?\". It also cannot stop a client from\n * calling the proxy itself; that is the web's inherent limit (see\n * PROXY-BACKEND-AUTH-SPEC.md). Mobile clients call the backend directly and are\n * handled by a future attestation path, OR'd into this same check.\n *\n * @module middleware/proxy-guard\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { env } from '@spfn/core/config';\nimport { logger } from '@spfn/core/logger';\n\nimport {\n verifyProxyRequest,\n parseProxyKey,\n parseProxyKeySet,\n PROXY_SIGNATURE_HEADER,\n PROXY_TIMESTAMP_HEADER,\n PROXY_NONCE_HEADER,\n PROXY_KEY_ID_HEADER,\n type VerifyFailureReason,\n} from '../security/proxy-signature';\n\nconst guardLogger = logger.child('@spfn/core:proxy-guard');\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * How the request reached the backend.\n * - `web`: verified via a trusted proxy signature\n * - `untrusted`: could not be verified (only reached handlers in `tag` mode)\n * - future: `ios` | `android` once attestation lands\n */\nexport type ClientType = 'web' | 'untrusted' | (string & {});\n\n/**\n * Enforcement mode.\n * - `off`: middleware is a no-op (default — keeps existing apps working).\n * - `tag`: verify and set `clientType`, but never reject (observe first).\n * - `strict`: reject requests that fail verification.\n */\nexport type ProxyGuardMode = 'off' | 'tag' | 'strict';\n\n/**\n * Optional replay store. Returns `true` when the nonce is fresh (and records it),\n * `false` when it has been seen before. Backed by Redis in multi-instance setups.\n */\nexport interface NonceStore\n{\n checkAndSet(nonce: string, ttlMs: number): Promise<boolean>;\n}\n\nexport interface ProxyGuardConfig\n{\n /**\n * Active shared HMAC secret, as `<keyId>:<secret>` (or a bare secret).\n * Defaults to `env.SPFN_PROXY_SECRET`. Without it (and no previous keys),\n * signatures cannot be verified and every request is tagged `untrusted`.\n */\n secret?: string;\n\n /**\n * Previous (grace) keys still accepted for verification during rotation, as a\n * comma-separated list of `<keyId>:<secret>`. The proxy never signs with\n * these — they exist only so in-flight requests signed with the prior key\n * keep verifying until the rollout settles. Defaults to\n * `env.SPFN_PROXY_SECRET_PREVIOUS`. Backend-only, so it can live in\n * `.env.server` (never exposed to the Next.js process).\n */\n previousSecrets?: string;\n\n /** Enforcement mode. @default 'off' */\n mode?: ProxyGuardMode;\n\n /** Allowed clock skew / replay window in ms. @default 30000 */\n windowMs?: number;\n\n /**\n * Browser origin allowlist. When set, a request carrying an `Origin` header\n * not in this list is rejected (strict) / tagged (tag). Requests without an\n * `Origin` (server-to-server, mobile) skip this check and fall back to the\n * signature.\n */\n allowedOrigins?: string[];\n\n /** Optional nonce store for hard replay rejection. Redis (multi-instance) or in-memory (single). */\n nonceStore?: NonceStore;\n\n /**\n * When the nonce store throws (e.g. Redis down), reject the request instead of\n * falling back to the timestamp window. Trades availability for strictness.\n * @default false (fall back to the timestamp window)\n */\n nonceFailClosed?: boolean;\n\n /** Paths to skip entirely (e.g. health checks). */\n skipPaths?: string[];\n\n /**\n * Reject (413) once the streamed body exceeds this many bytes. Measured AS IT\n * STREAMS — Content-Length is NOT trusted, so a missing/chunked/under-reported\n * length can't bypass it. Bounds the per-request memory the guard buffers.\n * Undefined = no cap (default). Multipart is exempt (unsigned).\n */\n maxBodyBytes?: number;\n}\n\n// ============================================================================\n// Hono context augmentation\n// ============================================================================\n\ndeclare module 'hono'\n{\n interface ContextVariableMap\n {\n clientType?: ClientType;\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Sentinel: body exceeded maxBodyBytes while streaming (never fully buffered). */\nconst BODY_OVERSIZE = Symbol('proxy-guard:body-oversize');\n\n/** Sentinel: the body stream errored mid-read (e.g. client abort). */\nconst BODY_READ_ERROR = Symbol('proxy-guard:body-read-error');\n\n/**\n * Read the raw request body without consuming it for downstream handlers.\n * Bound for ANY non-multipart content-type (not just application/json) so the\n * backend hashes exactly what the proxy signed — otherwise the body could be\n * tampered by flipping content-type. Multipart uploads are excluded on both\n * sides (their large bodies are never hashed).\n *\n * When maxBytes is set the body is measured AS IT STREAMS and aborted past the\n * limit — so a missing/chunked/under-reported Content-Length can't bypass the cap\n * (the header is never trusted). Returns BODY_OVERSIZE in that case.\n */\nasync function readRawBody(\n c: Context,\n maxBytes?: number,\n): Promise<Buffer | undefined | typeof BODY_OVERSIZE | typeof BODY_READ_ERROR>\n{\n const method = c.req.method;\n if (method === 'GET' || method === 'HEAD')\n {\n return undefined;\n }\n\n const contentType = c.req.header('content-type') || '';\n if (contentType.includes('multipart/form-data'))\n {\n return undefined;\n }\n\n const stream = c.req.raw.clone().body;\n if (!stream)\n {\n return undefined;\n }\n\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n\n try\n {\n for (;;)\n {\n const { done, value } = await reader.read();\n if (done)\n {\n break;\n }\n\n total += value.byteLength;\n if (maxBytes !== undefined && total > maxBytes)\n {\n // Fire-and-forget: awaiting cancel() can hang under some stream\n // implementations, and this is a clone so the original is untouched.\n void reader.cancel().catch(() => undefined);\n\n return BODY_OVERSIZE;\n }\n\n chunks.push(value);\n }\n }\n catch\n {\n // A mid-stream error (client abort, network blip) RETURNS a sentinel — not an\n // empty body (would 403 valid traffic) and not a re-throw (would 500 + page\n // on-call and break tag mode's never-reject contract).\n guardLogger.debug('Request body read failed (client abort?)');\n\n return BODY_READ_ERROR;\n }\n\n return Buffer.concat(chunks);\n}\n\nfunction isOriginAllowed(c: Context, allowedOrigins: string[] | undefined): boolean\n{\n if (!allowedOrigins || allowedOrigins.length === 0)\n {\n return true;\n }\n\n const origin = c.req.header('origin');\n if (!origin)\n {\n // No browser Origin (server-to-server / mobile) — defer to signature.\n return true;\n }\n\n return allowedOrigins.includes(origin);\n}\n\n// ============================================================================\n// Middleware\n// ============================================================================\n\n/**\n * Create the proxy-guard middleware.\n *\n * @example\n * ```typescript\n * export default defineServerConfig()\n * .proxyGuard({ mode: 'strict', allowedOrigins: ['https://app.example.com'] })\n * .routes(appRouter)\n * .build();\n * ```\n */\nexport function createProxyGuard(config: ProxyGuardConfig = {}): MiddlewareHandler\n{\n const mode = config.mode ?? 'off';\n const windowMs = config.windowMs ?? 30_000;\n const allowedOrigins = config.allowedOrigins;\n const nonceStore = config.nonceStore;\n const nonceFailClosed = config.nonceFailClosed ?? false;\n const skipPaths = new Set(config.skipPaths ?? []);\n const maxBodyBytes = config.maxBodyBytes;\n\n // Accepted key set: active secret first (wins on keyId collision), then grace keys.\n const activeRaw = config.secret ?? env.SPFN_PROXY_SECRET;\n const previousRaw = config.previousSecrets ?? env.SPFN_PROXY_SECRET_PREVIOUS;\n const keys = parseProxyKeySet([activeRaw, previousRaw]);\n\n // Fail CLOSED on misconfiguration: strict with no key would otherwise let every\n // request (including direct-to-backend) through. Refuse to start instead.\n if (mode === 'strict' && keys.length === 0)\n {\n throw new Error(\n '[proxy-guard] mode \"strict\" requires a proxy key but none is configured '\n + '(SPFN_PROXY_SECRET is empty/unset). Refusing to start with the guard open — '\n + 'set the secret, or use mode \"tag\" / \"off\".',\n );\n }\n\n // Warn when rotation can't work: bare secrets (no \"keyId:\" prefix) on both active\n // and previous collapse to the same keyId, so the grace key is silently dropped.\n if (activeRaw && previousRaw)\n {\n const activeId = parseProxyKey(activeRaw).keyId;\n const collides = previousRaw.split(',').some(p => p.trim() && parseProxyKey(p.trim()).keyId === activeId);\n if (collides)\n {\n guardLogger.warn(\n 'Previous proxy key shares keyId with the active key (likely bare secrets without a '\n + '\"keyId:\" prefix) — grace key ignored, rotation will drop in-flight requests. '\n + 'Use \"<keyId>:<secret>\" on both keys.',\n { keyId: activeId },\n );\n }\n }\n\n return async (c: Context, next: Next) =>\n {\n if (mode === 'off' || skipPaths.has(c.req.path))\n {\n return next();\n }\n\n // A genuine CORS preflight (OPTIONS + Access-Control-Request-Method) belongs to\n // the CORS layer, not the signature guard — exempt it FIRST so it isn't 403'd\n // here without CORS headers. A NON-preflight OPTIONS (rare OPTIONS-as-API) falls\n // through to normal verification, so exempting preflight is never an unguarded\n // bypass. Tagged 'untrusted' (unsigned) so downstream never sees it unset.\n if (c.req.method === 'OPTIONS' && c.req.header('access-control-request-method'))\n {\n c.set('clientType', 'untrusted');\n\n return next();\n }\n\n // Every gate below is EVALUATED in both modes; only enforcement differs\n // (reject() = 403 in strict, tag clientType='untrusted' + continue in tag).\n // So tag mode observes exactly what strict would reject — no metric skew.\n\n // 1. Origin allowlist (browser cross-origin guard)\n if (!isOriginAllowed(c, allowedOrigins))\n {\n return reject(c, mode, next, 'origin-not-allowed');\n }\n\n // 2. No accepted key — only reachable in tag mode (strict throws at construction).\n if (keys.length === 0)\n {\n c.set('clientType', 'untrusted');\n\n return next();\n }\n\n // 3. Cheap header-presence check BEFORE buffering the body, so unsigned requests\n // (direct-to-backend attacks, tag-mode rollout traffic) reject/tag without\n // paying the body read.\n const signature = c.req.header(PROXY_SIGNATURE_HEADER);\n const timestamp = c.req.header(PROXY_TIMESTAMP_HEADER);\n const nonce = c.req.header(PROXY_NONCE_HEADER);\n const keyId = c.req.header(PROXY_KEY_ID_HEADER);\n if (!signature || !timestamp || !nonce || !keyId)\n {\n return reject(c, mode, next, 'missing-headers');\n }\n\n // 4. Read body with a streaming size cap (Content-Length is never trusted, so a\n // missing/chunked/under-reported length can't bypass the bound).\n const body = await readRawBody(c, maxBodyBytes);\n if (body === BODY_OVERSIZE)\n {\n if (mode === 'strict')\n {\n return c.json({ error: 'Payload Too Large' }, 413);\n }\n\n c.set('clientType', 'untrusted');\n\n return next();\n }\n if (body === BODY_READ_ERROR)\n {\n // Body stream failed (client abort) — can't verify, so don't hash an empty\n // body. Reject in strict / tag untrusted, never a 500.\n return reject(c, mode, next, 'body-read-error');\n }\n\n // 5. HMAC over the wire request-target (raw path + query) and body. Use the raw\n // URL, NOT the decoded c.req.path, so it matches the bytes the proxy signed.\n const url = new URL(c.req.url);\n const result = verifyProxyRequest({\n keys,\n method: c.req.method,\n path: url.pathname,\n query: url.search,\n body,\n signature,\n timestamp,\n nonce,\n keyId,\n windowMs,\n });\n\n if (!result.valid)\n {\n return reject(c, mode, next, result.reason);\n }\n\n // 6. Hard replay rejection via nonce store (both modes — tag observes replays).\n // Degrade to the timestamp window if the store is briefly unavailable rather\n // than 500-ing valid traffic.\n if (nonceStore && result.nonce)\n {\n try\n {\n const fresh = await nonceStore.checkAndSet(result.nonce, windowMs * 2);\n if (!fresh)\n {\n return reject(c, mode, next, 'nonce-replay');\n }\n }\n catch (err)\n {\n if (nonceFailClosed)\n {\n guardLogger.warn('Nonce store unavailable — rejecting (fail-closed)', {\n error: (err as Error).message,\n });\n\n return reject(c, mode, next, 'nonce-store-unavailable');\n }\n\n guardLogger.warn('Nonce store unavailable — falling back to timestamp window', {\n error: (err as Error).message,\n });\n }\n }\n\n c.set('clientType', 'web');\n\n return next();\n };\n}\n\n// ============================================================================\n// Redis-backed nonce store (optional)\n// ============================================================================\n\n/** Minimal cache client (compatible with ioredis Redis | Cluster). */\ntype CacheClient = {\n set(key: string, value: string, ...args: any[]): Promise<any>;\n};\n\n/**\n * Build a Redis-backed nonce store for hard replay rejection.\n *\n * Uses `SET key 1 PX ttl NX` — an atomic \"set if absent\". A fresh nonce returns\n * 'OK'; a replayed one returns null. TTL keeps the key set bounded (only nonces\n * within the replay window matter).\n */\nexport function createCacheNonceStore(cache: CacheClient, prefix = 'spfn:proxy-nonce:'): NonceStore\n{\n return {\n async checkAndSet(nonce: string, ttlMs: number): Promise<boolean>\n {\n const res = await cache.set(`${prefix}${nonce}`, '1', 'PX', Math.ceil(ttlMs), 'NX');\n\n return res === 'OK';\n },\n };\n}\n\n/**\n * In-process nonce store for hard replay rejection without Redis.\n *\n * Good enough for a single instance — each process only knows its own nonces, so\n * across multiple instances a replay routed to a different pod isn't caught. Use a\n * cache-backed store (createCacheNonceStore) for multi-instance hard rejection.\n * Entries self-expire after their TTL; the map is swept opportunistically so it\n * stays bounded by the request rate within the replay window.\n */\nexport function createInMemoryNonceStore(): NonceStore\n{\n const seen = new Map<string, number>(); // nonce -> expiry (epoch ms)\n let lastSweep = 0;\n\n return {\n async checkAndSet(nonce: string, ttlMs: number): Promise<boolean>\n {\n const now = Date.now();\n\n if (now - lastSweep > 60_000)\n {\n for (const [n, exp] of seen)\n {\n if (exp <= now) seen.delete(n);\n }\n lastSweep = now;\n }\n\n const existing = seen.get(nonce);\n if (existing !== undefined && existing > now)\n {\n return false; // replay within window\n }\n\n seen.set(nonce, now + ttlMs);\n\n return true;\n },\n };\n}\n\n/**\n * Reject (strict) or tag-and-continue (tag). Keeps the rejection response generic\n * so it leaks no detail about why verification failed.\n */\nfunction reject(\n c: Context,\n mode: ProxyGuardMode,\n next: Next,\n reason: VerifyFailureReason | 'origin-not-allowed' | 'nonce-replay' | 'nonce-store-unavailable' | 'body-read-error' | undefined,\n)\n{\n if (mode === 'strict')\n {\n guardLogger.warn('Rejected unverified request', { reason, path: c.req.path, method: c.req.method });\n\n return c.json({ error: 'Forbidden', message: 'Request origin could not be verified' }, 403);\n }\n\n // tag mode — observe only, never block\n guardLogger.debug('Unverified request (would reject in strict mode)', { reason, path: c.req.path });\n c.set('clientType', 'untrusted');\n\n return next();\n}\n"]}
|