@luckystack/server 0.1.8 → 0.2.0
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/CLAUDE.md +85 -83
- package/LICENSE +21 -21
- package/README.md +198 -198
- package/dist/chunk-X3OSC5W3.js +44 -0
- package/dist/chunk-X3OSC5W3.js.map +1 -0
- package/dist/index.d.ts +43 -40
- package/dist/index.js +840 -395
- package/dist/index.js.map +1 -1
- package/dist/parseArgv.js +3 -34
- package/dist/parseArgv.js.map +1 -1
- package/docs/argv-parsing.md +256 -256
- package/docs/create-server.md +295 -295
- package/docs/http-routes.md +309 -309
- package/docs/request-pipeline.md +198 -198
- package/docs/runtime-maps.md +206 -206
- package/docs/security-defaults.md +236 -236
- package/package.json +21 -11
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/createServer.ts","../src/httpHandler.ts","../src/logSanitize.ts","../src/securityHeadersRegistry.ts","../src/httpRoutes/csrfMiddleware.ts","../src/httpRoutes/csrfRoute.ts","../src/httpRoutes/faviconRoute.ts","../src/httpRoutes/healthRoutes.ts","../src/httpRoutes/testResetRoute.ts","../src/httpRoutes/uploadsRoute.ts","../src/httpRoutes/authApiRoute.ts","../src/httpRoutes/authProvidersRoute.ts","../src/httpRoutes/authCallbackRoute.ts","../src/httpRoutes/apiRoute.ts","../src/sse.ts","../src/httpRoutes/syncRoute.ts","../src/httpRoutes/customRoutes.ts","../src/customRoutesRegistry.ts","../src/httpRoutes/staticRoutes.ts","../src/originExemptRegistry.ts","../src/loadSocket.ts","../src/verifyBootstrap.ts","../src/runtimeMapsLoader.ts","../src/argv.ts","../src/bootstrap.ts","../src/errorFormatterRegistry.ts"],"sourcesContent":["import http, { type Server as HttpServer } from 'node:http';\r\nimport { registerBindAddress, writeBootUuid, getLogger, getProjectConfig, tryCatch } from '@luckystack/core';\r\nimport { handleHttpRequest } from './httpHandler';\r\nimport { loadSocket } from './loadSocket';\r\nimport { verifyBootstrap } from './verifyBootstrap';\r\nimport { registerProdRuntimeMapsProvider } from './runtimeMapsLoader';\r\nimport { getParsedPort } from './argv';\r\nimport type {\r\n CreateLuckyStackServerOptions,\r\n RunningLuckyStackServer,\r\n} from './types';\r\n\r\n/**\r\n * One-call server bootstrap for a LuckyStack project.\r\n *\r\n * Wires together:\r\n * - HTTP server with framework routes (`/api/*`, `/sync/*`, `/_health`,\r\n * `/_test/reset`, `/uploads/*`, `/auth/api`, `/auth/callback`)\r\n * - Socket.io server with the Redis adapter, room handlers, presence\r\n * integration, location sync\r\n * - Boot-UUID write so the router's handshake can verify topology\r\n * - Optional dev-mode tooling (devkit hot reload + REPL)\r\n *\r\n * Project responsibilities (passed in as options):\r\n * - `serveFile` / `serveFavicon` — your project's static file handlers\r\n * - `customRoutes` — any additional HTTP routes your app needs\r\n *\r\n * Pre-conditions:\r\n * - `registerProjectConfig(...)` must have run (side-effect import of your\r\n * `config.ts` does this).\r\n * - `registerDeployConfig(...)` must have run (side-effect import of your\r\n * `deploy.config.ts`).\r\n * - `registerRuntimeMapsProvider(...)` must have run (side-effect import of\r\n * your `server/prod/runtimeMaps.ts`).\r\n * - `registerLocalizedNormalizer(...)` must have run (side-effect import of\r\n * your `server/utils/responseNormalizer.ts`).\r\n *\r\n * @example\r\n * ```ts\r\n * import './config';\r\n * import './deploy.config';\r\n * import './prod/runtimeMaps';\r\n * import './utils/responseNormalizer';\r\n * import { createLuckyStackServer } from '@luckystack/server';\r\n * import { serveFile, serveFavicon } from './prod/serveFile';\r\n *\r\n * const server = await createLuckyStackServer({ serveFile, serveFavicon });\r\n * await server.listen();\r\n * ```\r\n */\r\nexport const createLuckyStackServer = async (\r\n options: CreateLuckyStackServerOptions = {}\r\n): Promise<RunningLuckyStackServer> => {\r\n //? Auto-register the framework-shipped runtime maps provider when the\r\n //? consumer supplied a `loadGeneratedMaps` callback. Runs before\r\n //? `verifyBootstrap` so the registration counts toward the boot check.\r\n //? Consumers who hand-rolled their own provider via\r\n //? `registerRuntimeMapsProvider` can simply omit `loadGeneratedMaps` and\r\n //? the framework leaves their registration alone.\r\n if (options.loadGeneratedMaps) {\r\n registerProdRuntimeMapsProvider({\r\n loadGenerated: options.loadGeneratedMaps,\r\n preset: options.runtimeMapsPreset,\r\n });\r\n }\r\n\r\n //? Fail fast if a project's overlay forgot to register a critical piece.\r\n //? Surface a single readable error instead of a stack trace deep inside\r\n //? a request handler.\r\n await verifyBootstrap({\r\n requireDeployConfig: options.requireDeployConfig,\r\n requireServicesConfig: options.requireServicesConfig,\r\n requireOAuthProviders: options.requireOAuthProviders,\r\n });\r\n\r\n const port = options.port ?? getParsedPort() ?? process.env.SERVER_PORT ?? 80;\r\n const ip = options.ip ?? process.env.SERVER_IP ?? '127.0.0.1';\r\n const enableDevTools = options.enableDevTools ?? process.env.NODE_ENV !== 'production';\r\n\r\n //? Register the resolved bind address so framework code that needs it\r\n //? (e.g. `checkOrigin` building the same-origin entry) doesn't drift when\r\n //? the consumer passed `options.ip`/`options.port` without also setting\r\n //? the legacy `SERVER_IP`/`SERVER_PORT` env vars.\r\n registerBindAddress({\r\n ip,\r\n port: typeof port === 'string' ? Number.parseInt(port, 10) : port,\r\n });\r\n\r\n if (enableDevTools) {\r\n //? Dev-only: console-log color tagger + devkit hot reload + REPL.\r\n //? Kept dynamic so tier-A consumers in production never load the\r\n //? typescript compiler API or chokidar's filesystem watchers.\r\n const { initConsolelog } = await import('@luckystack/core');\r\n initConsolelog();\r\n const devkitModuleId = '@luckystack/devkit';\r\n const devkit = (await import(devkitModuleId)) as {\r\n initializeAll: () => Promise<void>;\r\n setupWatchers: () => void;\r\n };\r\n await devkit.initializeAll();\r\n devkit.setupWatchers();\r\n //? Belt-and-braces: explicit SIGINT/SIGTERM handler so Ctrl+C is honored\r\n //? even if a sync CPU burst (TS Program build, large require chain) is\r\n //? still in flight when the signal arrives.\r\n process.once('SIGINT', () => process.exit(0));\r\n process.once('SIGTERM', () => process.exit(0));\r\n }\r\n\r\n //? Boot UUID must be written before /_health can answer truthfully. Router\r\n //? boot handshake consumes /_health to verify shared-Redis topology. A\r\n //? failure here is almost always Redis being unreachable or misconfigured\r\n //? (bad credentials, wrong host); throw a clear, actionable error so boot\r\n //? halts on THIS message instead of the raw ioredis `ReplyError` dump that\r\n //? confused operators before. Library code must not `process.exit()`; the\r\n //? throw propagates to the boot entry and the dev supervisor respawns.\r\n //? `tryCatch` already captured the underlying error to the error tracker.\r\n const [bootUuidError] = await tryCatch(() => writeBootUuid());\r\n if (bootUuidError) {\r\n throw new Error(\r\n 'Failed to write the boot UUID to Redis. Check REDIS_HOST / REDIS_PORT / REDIS_USER / REDIS_PASSWORD and that Redis is reachable.',\r\n { cause: bootUuidError },\r\n );\r\n }\r\n\r\n const httpServer: HttpServer = http.createServer((req, res) => {\r\n void handleHttpRequest(req, res, options);\r\n });\r\n\r\n const ioServer = loadSocket(httpServer, {\r\n maxHttpBufferSize: options.maxHttpBufferSize,\r\n });\r\n\r\n const listen = (callback?: () => void): Promise<HttpServer> =>\r\n new Promise<HttpServer>((resolve) => {\r\n const portValue = typeof port === 'string' ? Number.parseInt(port, 10) : port;\r\n httpServer.listen(portValue, ip, () => {\r\n const config = getProjectConfig();\r\n if (config.logging.socketStartup || config.logging.devLogs) {\r\n getLogger().info(`Server is running on http://${ip}:${String(port)}/`);\r\n }\r\n callback?.();\r\n resolve(httpServer);\r\n });\r\n });\r\n\r\n return { httpServer, ioServer, listen };\r\n};\r\n","import type { IncomingMessage, ServerResponse } from 'node:http';\r\nimport { randomUUID } from 'node:crypto';\r\nimport {\r\n allowedOrigin,\r\n dispatchHook,\r\n extractTokenFromRequest,\r\n getLogger,\r\n getParams,\r\n getProjectConfig,\r\n hasCookie,\r\n} from '@luckystack/core';\r\nimport { getSession } from '@luckystack/login';\r\nimport { sanitizeForLog } from './logSanitize';\r\nimport { getSecurityHeadersBuilder } from './securityHeadersRegistry';\r\nimport type { CreateLuckyStackServerOptions } from './types';\r\nimport { enforceCsrfOnStateChangingRequest } from './httpRoutes/csrfMiddleware';\r\nimport { handleCsrfRoute } from './httpRoutes/csrfRoute';\r\nimport { handleFaviconRoute } from './httpRoutes/faviconRoute';\r\nimport { handleHealthRoute, handleLivezRoute, handleReadyzRoute } from './httpRoutes/healthRoutes';\r\nimport { handleTestResetRoute } from './httpRoutes/testResetRoute';\r\nimport { handleUploadsRoute } from './httpRoutes/uploadsRoute';\r\nimport { handleAuthApiRoute } from './httpRoutes/authApiRoute';\r\nimport { handleAuthProvidersRoute } from './httpRoutes/authProvidersRoute';\r\nimport { handleAuthCallbackRoute } from './httpRoutes/authCallbackRoute';\r\nimport { handleApiRoute } from './httpRoutes/apiRoute';\r\nimport { handleSyncRoute } from './httpRoutes/syncRoute';\r\nimport { handleCustomRoutes, handlePreParamsCustomRoutes } from './httpRoutes/customRoutes';\r\nimport { handleStaticAndSpaFallback } from './httpRoutes/staticRoutes';\r\nimport { isOriginExemptPath } from './originExemptRegistry';\r\nimport type { HttpRouteContext, HttpRouteHandler } from './httpRoutes/types';\r\n\r\nconst buildSessionCookieOptions = (\r\n sessionExpiryDays: number,\r\n secure: boolean,\r\n http: ReturnType<typeof getProjectConfig>['http'],\r\n): string =>\r\n `HttpOnly; SameSite=${http.sessionCookieSameSite}; Path=${http.sessionCookiePath}; Max-Age=${60 * 60 * 24 * sessionExpiryDays}; ${secure ? 'Secure;' : ''}`;\r\n\r\nconst setSecurityHeaders = (req: IncomingMessage, res: ServerResponse, origin: string) => {\r\n const { cors, securityHeaders } = getProjectConfig().http;\r\n res.setHeader('Access-Control-Allow-Origin', origin);\r\n res.setHeader('Access-Control-Allow-Methods', cors.allowedMethods);\r\n res.setHeader('Access-Control-Allow-Headers', cors.allowedHeaders);\r\n res.setHeader('Access-Control-Expose-Headers', cors.exposedHeaders);\r\n if (cors.credentials) {\r\n res.setHeader('Access-Control-Allow-Credentials', 'true');\r\n }\r\n res.setHeader('Referrer-Policy', securityHeaders.referrerPolicy);\r\n res.setHeader('X-Frame-Options', securityHeaders.frameOptions);\r\n res.setHeader('X-XSS-Protection', securityHeaders.xssProtection);\r\n res.setHeader('X-Content-Type-Options', securityHeaders.contentTypeOptions);\r\n\r\n //? Consumer-registered builder runs AFTER framework defaults so it can\r\n //? override (CSP, HSTS, Permissions-Policy) or extend. Errors fall\r\n //? through to defaults so a buggy builder can't kill response delivery.\r\n const builder = getSecurityHeadersBuilder();\r\n if (builder) {\r\n try {\r\n const custom = builder(req);\r\n if (custom) {\r\n for (const [name, value] of Object.entries(custom)) {\r\n res.setHeader(name, value);\r\n }\r\n }\r\n } catch (error) {\r\n getLogger().warn('securityHeadersBuilder threw — falling back to defaults', { err: error });\r\n }\r\n }\r\n};\r\n\r\n//? Routes that run BEFORE params parsing — they don't need (and shouldn't\r\n//? consume) the request body. The framework fast-paths run first; consumer\r\n//? `'pre-params'` custom routes (webhooks / streaming uploads) run last, after\r\n//? the probes but before `getParams` drains the body.\r\nconst PRE_PARAMS_ROUTES: HttpRouteHandler[] = [\r\n handleCsrfRoute,\r\n handleFaviconRoute,\r\n handleLivezRoute,\r\n handleReadyzRoute,\r\n handleHealthRoute,\r\n handleTestResetRoute,\r\n handleAuthProvidersRoute,\r\n handlePreParamsCustomRoutes,\r\n];\r\n\r\n//? Routes that run AFTER params parsing.\r\nconst POST_PARAMS_ROUTES: HttpRouteHandler[] = [\r\n handleUploadsRoute,\r\n handleAuthApiRoute,\r\n handleAuthCallbackRoute,\r\n handleApiRoute,\r\n handleSyncRoute,\r\n handleCustomRoutes,\r\n handleStaticAndSpaFallback,\r\n];\r\n\r\nconst dispatchRoutes = async (handlers: HttpRouteHandler[], ctx: HttpRouteContext): Promise<boolean> => {\r\n for (const handler of handlers) {\r\n const handled = await handler(ctx);\r\n if (handled || ctx.res.writableEnded) return true;\r\n }\r\n return false;\r\n};\r\n\r\nconst enforceOriginPolicy = (\r\n req: IncomingMessage,\r\n res: ServerResponse,\r\n routePath: string,\r\n): { origin: string; rejected: boolean } => {\r\n //? Do NOT fall back to `req.headers.host` — host always equals the bound\r\n //? origin so non-browser callers (curl, native apps) would silently bypass\r\n //? `allowedOrigin()`. Only browsers attach Origin/Referer.\r\n const origin = req.headers.origin ?? req.headers.referer ?? '';\r\n const isStateChangingMethod = req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS';\r\n\r\n //? Registered webhook / server-to-server prefixes opt out of the browser\r\n //? origin gate — they authenticate via signature/HMAC in the handler, not via\r\n //? Origin (which they never send). Empty by default; opt-in only. The handler\r\n //? is still responsible for verifying the caller. See originExemptRegistry.\r\n if (isOriginExemptPath(routePath)) {\r\n return { origin, rejected: false };\r\n }\r\n\r\n if (!origin) {\r\n //? No browser-attributable origin: fail-close on state-changing methods,\r\n //? allow read-only (GET/HEAD/OPTIONS) so health probes and asset fetches\r\n //? from non-browser tooling continue to work.\r\n if (isStateChangingMethod) {\r\n res.statusCode = 403;\r\n res.setHeader('Content-Type', 'text/plain');\r\n res.end('Forbidden');\r\n return { origin, rejected: true };\r\n }\r\n } else if (!allowedOrigin(origin)) {\r\n res.statusCode = 403;\r\n res.setHeader('Content-Type', 'text/plain');\r\n res.end('Forbidden');\r\n return { origin, rejected: true };\r\n }\r\n return { origin, rejected: false };\r\n};\r\n\r\nconst refreshSessionCookieIfPresent = async ({\r\n req,\r\n res,\r\n token,\r\n sessionCookieName,\r\n sessionCookieOptions,\r\n}: {\r\n req: IncomingMessage;\r\n res: ServerResponse;\r\n token: string | null;\r\n sessionCookieName: string;\r\n sessionCookieOptions: string;\r\n}) => {\r\n const hasTokenCookie = hasCookie(req.headers.cookie, sessionCookieName);\r\n if (!hasTokenCookie || !token) return;\r\n const currentSession = await getSession(token);\r\n if (currentSession?.id) {\r\n //? Sliding expiration in cookie mode: keep browser token lifetime\r\n //? aligned with Redis TTL.\r\n res.setHeader('Set-Cookie', `${sessionCookieName}=${token}; ${sessionCookieOptions}`);\r\n }\r\n};\r\n\r\nconst parseRequestParams = async ({\r\n req,\r\n res,\r\n method,\r\n routePath,\r\n queryString,\r\n requestId,\r\n shouldLogDev,\r\n}: {\r\n req: IncomingMessage;\r\n res: ServerResponse;\r\n method: 'GET' | 'POST' | 'PUT' | 'DELETE';\r\n routePath: string;\r\n queryString: string | undefined;\r\n requestId: string;\r\n shouldLogDev: boolean;\r\n}): Promise<object | null> => {\r\n let params: object | null = await getParams({ method, req, res, queryString });\r\n if (res.writableEnded) return null;\r\n\r\n if (params && typeof params === 'object' && Object.keys(params).length > 0) {\r\n if (shouldLogDev) {\r\n getLogger().debug(`[${requestId}] ${method} ${routePath}`, {\r\n params: sanitizeForLog(params) as Record<string, unknown>,\r\n });\r\n }\r\n } else {\r\n if (shouldLogDev) {\r\n getLogger().debug(`[${requestId}] ${method} ${routePath}`);\r\n }\r\n params = {};\r\n }\r\n return params;\r\n};\r\n\r\nexport const handleHttpRequest = async (\r\n req: IncomingMessage,\r\n res: ServerResponse,\r\n options: CreateLuckyStackServerOptions\r\n): Promise<void> => {\r\n const config = getProjectConfig();\r\n const shouldLogDev = config.logging.devLogs;\r\n const sessionCookieName = config.http.sessionCookieName;\r\n const sessionCookieOptions = buildSessionCookieOptions(\r\n config.session.expiryDays,\r\n process.env.SECURE === 'true',\r\n config.http,\r\n );\r\n\r\n //? Parse the path up-front so the origin gate can consult the exempt-path\r\n //? registry (registered webhooks) before it would otherwise 403 a\r\n //? header-less, state-changing request.\r\n const url = req.url ?? '/';\r\n const [routePathRaw, queryStringRaw] = url.split('?');\r\n const routePath = routePathRaw ?? '/';\r\n const queryString = queryStringRaw ?? '';\r\n\r\n const { origin, rejected } = enforceOriginPolicy(req, res, routePath);\r\n if (rejected) return;\r\n\r\n setSecurityHeaders(req, res, origin);\r\n\r\n //? Honor an incoming x-request-id (idempotent for proxies/retries) or\r\n //? generate a fresh UUID. Echo back as a response header so client-side\r\n //? logs and Sentry can correlate.\r\n const incomingRequestId = req.headers['x-request-id'];\r\n const requestId = (Array.isArray(incomingRequestId) ? incomingRequestId[0] : incomingRequestId) ?? randomUUID();\r\n res.setHeader('X-Request-Id', requestId);\r\n\r\n //? `preHttpRequest` fires before any route dispatch. Use to instrument\r\n //? requests (latency timer, audit log), enforce IP allow-lists, or stop\r\n //? specific paths with a custom error. Header subset excludes auth/cookie.\r\n const safeHeaders: Record<string, string> = {};\r\n for (const [k, v] of Object.entries(req.headers)) {\r\n if (k === 'authorization' || k === 'cookie' || k === 'set-cookie' || k === 'x-csrf-token') continue;\r\n safeHeaders[k] = Array.isArray(v) ? v.join(', ') : (v ?? '');\r\n }\r\n const preHttpResult = await dispatchHook('preHttpRequest', {\r\n method: req.method?.toUpperCase() ?? 'GET',\r\n url: req.url ?? '/',\r\n requestId,\r\n origin,\r\n headers: safeHeaders,\r\n });\r\n if (preHttpResult.stopped) {\r\n res.statusCode = preHttpResult.signal.httpStatus ?? 403;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ status: 'error', errorCode: preHttpResult.signal.errorCode }));\r\n return;\r\n }\r\n\r\n if (req.method === 'OPTIONS') {\r\n res.writeHead(204);\r\n res.end();\r\n return;\r\n }\r\n\r\n const method = req.method;\r\n\r\n if (method !== 'GET' && method !== 'POST' && method !== 'PUT' && method !== 'DELETE') {\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'text/plain');\r\n res.end(`method: ${String(method)} not supported, use one of: GET, POST, PUT, DELETE`);\r\n return;\r\n }\r\n\r\n const token = extractTokenFromRequest(req);\r\n await refreshSessionCookieIfPresent({ req, res, token, sessionCookieName, sessionCookieOptions });\r\n\r\n const csrfRejected = await enforceCsrfOnStateChangingRequest({ req, res, routePath, token, requestId });\r\n if (csrfRejected) return;\r\n\r\n const baseCtx: Omit<HttpRouteContext, 'params'> = {\r\n req,\r\n res,\r\n options,\r\n routePath,\r\n queryString,\r\n method,\r\n token,\r\n requestId,\r\n sessionCookieOptions,\r\n };\r\n\r\n //? Route /auth/csrf, health probes, _test/reset, favicon — all fast paths\r\n //? that should not consume the request body.\r\n if (await dispatchRoutes(PRE_PARAMS_ROUTES, { ...baseCtx, params: {} })) return;\r\n\r\n const params = await parseRequestParams({\r\n req, res, method, routePath, queryString, requestId, shouldLogDev,\r\n });\r\n if (params === null) return;\r\n\r\n await dispatchRoutes(POST_PARAMS_ROUTES, { ...baseCtx, params });\r\n};\r\n","//? Recursive log sanitizer: never log known-sensitive keys. Reads the\r\n//? extensible redacted-key registry from core so feature packages can register\r\n//? their own domain-specific keys (password, apiKey, mrn, etc.) at boot.\r\n\r\nimport { getProjectConfig, isRedactedLogKey } from '@luckystack/core';\r\n\r\nconst REDACTED_PLACEHOLDER = '[REDACTED]';\r\n\r\nconst isRedactedKey = (key: string): boolean => {\r\n if (isRedactedLogKey(key)) return true;\r\n return key.toLowerCase() === getProjectConfig().http.sessionCookieName.toLowerCase();\r\n};\r\n\r\nexport const sanitizeForLog = (value: unknown): unknown => {\r\n if (value === null || typeof value !== 'object') return value;\r\n if (Array.isArray(value)) return value.map((entry) => sanitizeForLog(entry));\r\n\r\n const out: Record<string, unknown> = {};\r\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\r\n out[key] = isRedactedKey(key) ? REDACTED_PLACEHOLDER : sanitizeForLog(val);\r\n }\r\n return out;\r\n};\r\n","//? Security-headers builder registry. The framework ships sensible defaults\r\n//? (Referrer-Policy, X-Frame-Options, X-XSS-Protection, X-Content-Type-Options)\r\n//? read from `projectConfig.http.securityHeaders`. Consumers can register\r\n//? a custom builder to add Content-Security-Policy, Strict-Transport-Security,\r\n//? Permissions-Policy, or to override defaults per request.\r\n//?\r\n//? Resolution order:\r\n//? 1. Built-in defaults from `projectConfig.http.securityHeaders`.\r\n//? 2. Headers from the registered builder (override OR augment).\r\n//?\r\n//? A nullish return from the builder means \"use defaults only\". An object\r\n//? merges on top of defaults (later wins per key).\r\n\r\nimport type { IncomingMessage } from 'node:http';\r\n\r\nexport type SecurityHeadersBuilder = (req: IncomingMessage) => Record<string, string> | null | undefined;\r\n\r\nlet registeredBuilder: SecurityHeadersBuilder | null = null;\r\n\r\n/**\r\n * Register a custom security-headers builder. Called for every HTTP\r\n * request; return a plain object that gets merged on top of the framework\r\n * defaults. Use for Content-Security-Policy, HSTS, Permissions-Policy.\r\n *\r\n * Last-write-wins: subsequent calls replace the previous builder. Pass\r\n * `null` to unregister.\r\n */\r\nexport const registerSecurityHeaders = (builder: SecurityHeadersBuilder | null): void => {\r\n registeredBuilder = builder;\r\n};\r\n\r\n/** Read the active builder (or null). */\r\nexport const getSecurityHeadersBuilder = (): SecurityHeadersBuilder | null => registeredBuilder;\r\n","import type { IncomingMessage, ServerResponse } from 'node:http';\r\nimport { dispatchHook, getCsrfConfig, getProjectConfig } from '@luckystack/core';\r\nimport { getSession } from '@luckystack/login';\r\n\r\n//? Returns true when the request was rejected (CSRF mismatch) and the response\r\n//? has been ended. Caller should bail out of the request loop.\r\nexport const enforceCsrfOnStateChangingRequest = async ({\r\n req,\r\n res,\r\n routePath,\r\n token,\r\n requestId,\r\n}: {\r\n req: IncomingMessage;\r\n res: ServerResponse;\r\n routePath: string;\r\n token: string | null;\r\n requestId?: string;\r\n}): Promise<boolean> => {\r\n const config = getProjectConfig();\r\n const isCookieMode = !config.session.basedToken;\r\n const isStateChanging = req.method !== 'GET' && req.method !== 'OPTIONS';\r\n const isCallbackPath = routePath.startsWith('/auth/callback');\r\n //? The credentials login/register endpoint is the session BOOTSTRAP. Requiring a\r\n //? pre-existing session's CSRF token to authenticate is circular, and it blocks\r\n //? legitimate same-site re-login / register while a (stale) session cookie is\r\n //? present. Cross-site abuse is already prevented by the SameSite=Strict session\r\n //? cookie — a cross-site POST never carries it, so `token` would be absent here\r\n //? and this guard wouldn't fire anyway. Exempting it removes no real protection.\r\n const isAuthBootstrap = routePath === '/auth/api/credentials';\r\n const looksLikeFrameworkRoute =\r\n routePath.startsWith('/api/')\r\n || routePath.startsWith('/sync/')\r\n || (routePath.startsWith('/auth/api/') && !isAuthBootstrap);\r\n\r\n if (!(isCookieMode && isStateChanging && looksLikeFrameworkRoute && !isCallbackPath && token)) {\r\n return false;\r\n }\r\n\r\n const csrfSession = await getSession(token);\r\n if (!csrfSession?.id) return false;\r\n\r\n //? Read the active CSRF header name from the registry so consumers\r\n //? can rename it (e.g. legacy `x-xsrf-token`, custom `x-app-csrf`).\r\n const csrfConfig = getCsrfConfig();\r\n const headerKey = csrfConfig.headerName.toLowerCase();\r\n const headerValue = req.headers[headerKey];\r\n const provided = Array.isArray(headerValue) ? headerValue[0] : headerValue;\r\n if (provided && provided === csrfSession.csrfToken) return false;\r\n\r\n void dispatchHook('csrfMismatch', {\r\n route: routePath,\r\n method: req.method,\r\n requestId,\r\n userId: csrfSession.id,\r\n providedToken: Boolean(provided),\r\n });\r\n\r\n res.statusCode = 403;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({\r\n status: 'error',\r\n errorCode: 'auth.csrfMismatch',\r\n message: 'CSRF token missing or invalid. Fetch /auth/csrf first.',\r\n }));\r\n return true;\r\n};\r\n","import { getSession } from '@luckystack/login';\nimport type { HttpRouteHandler } from './types';\n\nexport const handleCsrfRoute: HttpRouteHandler = async ({ res, routePath, token }) => {\n if (routePath !== '/auth/csrf') return false;\n\n if (!token) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'auth.unauthenticated' }));\n return true;\n }\n const csrfSession = await getSession(token);\n if (!csrfSession?.id) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'auth.unauthenticated' }));\n return true;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({\n status: 'success',\n csrfToken: csrfSession.csrfToken ?? null,\n }));\n return true;\n};\n","import type { HttpRouteHandler } from './types';\n\nexport const handleFaviconRoute: HttpRouteHandler = async ({ res, routePath, options }) => {\n if (routePath !== '/favicon.ico') return false;\n if (options.serveFavicon) {\n await options.serveFavicon(res);\n return true;\n }\n res.writeHead(404);\n res.end();\n return true;\n};\n","import {\r\n computeSynchronizedEnvHashes,\r\n getProjectConfig,\r\n prisma,\r\n readBootUuid,\r\n redis,\r\n resolveEnvKey,\r\n tryCatch,\r\n} from '@luckystack/core';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\n//? Cross-provider connectivity ping. Prisma's generated TypeScript surface\r\n//? differs per provider: SQL providers expose `$queryRaw`, MongoDB exposes\r\n//? `$runCommandRaw`. The framework can't know which one the consumer's schema\r\n//? uses, so this single seam asserts both as optional and probes at runtime.\r\n//? Detecting the provider via `_engineConfig.activeProvider` is private API\r\n//? and has drifted between Prisma major versions — runtime probing is more\r\n//? robust.\r\ninterface PrismaPingShape {\r\n $queryRaw?: (template: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;\r\n $runCommandRaw?: (command: Record<string, unknown>) => Promise<unknown>;\r\n}\r\n\r\nconst pingPrisma = async (): Promise<boolean> => {\r\n //? Prisma's generated client typings don't expose `$queryRaw` /\r\n //? `$runCommandRaw` uniformly (depends on the active datasource). The\r\n //? `PrismaPingShape` is our minimal local probe interface; the boundary\r\n //? cast is the structural exception the strict-typing policy allows.\r\n // eslint-disable-next-line no-restricted-syntax -- Prisma datasource-conditional shape\r\n const client = prisma as unknown as PrismaPingShape;\r\n //? Capture each function into a local before calling so the typeof\r\n //? narrow holds without `!`. The shape `PrismaPingShape` declares both\r\n //? methods optional because Prisma's generated types include one or the\r\n //? other depending on active provider — capturing into a local also\r\n //? removes the assertion-style cast that the strict-typing policy disallows.\r\n const queryRaw = client.$queryRaw;\r\n if (typeof queryRaw === 'function') {\r\n const [sqlError] = await tryCatch(() => queryRaw`SELECT 1`);\r\n if (!sqlError) return true;\r\n }\r\n const runCommandRaw = client.$runCommandRaw;\r\n if (typeof runCommandRaw === 'function') {\r\n const [mongoError] = await tryCatch(() => runCommandRaw({ ping: 1 }));\r\n return !mongoError;\r\n }\r\n return false;\r\n};\r\n\r\nexport const handleLivezRoute: HttpRouteHandler = ({ res, routePath }) => {\r\n if (routePath !== getProjectConfig().http.liveEndpoint) return Promise.resolve(false);\r\n res.statusCode = 200;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ status: 'live' }));\r\n return Promise.resolve(true);\r\n};\r\n\r\nexport const handleReadyzRoute: HttpRouteHandler = async ({ res, routePath }) => {\r\n if (routePath !== getProjectConfig().http.readyEndpoint) return false;\r\n\r\n const bootUuid = await readBootUuid();\r\n\r\n const [redisError, pong] = await tryCatch(() => redis.ping());\r\n const redisOk = !redisError && (pong === 'PONG' || Boolean(pong));\r\n\r\n const prismaOk = await pingPrisma();\r\n\r\n const ready = Boolean(bootUuid) && redisOk && prismaOk;\r\n res.statusCode = ready ? 200 : 503;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({\r\n status: ready ? 'ready' : 'not-ready',\r\n checks: { bootUuid: Boolean(bootUuid), redis: redisOk, prisma: prismaOk },\r\n }));\r\n return true;\r\n};\r\n\r\nexport const handleHealthRoute: HttpRouteHandler = async ({ res, routePath }) => {\r\n if (routePath !== getProjectConfig().http.healthEndpoint) return false;\r\n\r\n const bootUuid = await readBootUuid();\r\n const synchronizedHashes = computeSynchronizedEnvHashes();\r\n res.statusCode = bootUuid ? 200 : 503;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({\r\n status: bootUuid ? 'ok' : 'degraded',\r\n bootUuid,\r\n envKey: resolveEnvKey(),\r\n synchronizedHashes,\r\n }));\r\n return true;\r\n};\r\n","import {\r\n clearAllHooks,\r\n clearAllRateLimits,\r\n getProjectConfig,\r\n formatKey,\r\n redis,\r\n tryCatch,\r\n} from '@luckystack/core';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\nexport const handleTestResetRoute: HttpRouteHandler = async ({ req, res, routePath }) => {\r\n if (routePath !== getProjectConfig().http.testResetEndpoint) return false;\r\n\r\n //? Fail-closed: require explicit dev/test NODE_ENV (not just \"anything but\r\n //? production\") so a missing or misconfigured NODE_ENV cannot expose this\r\n //? destructive endpoint. Also require TEST_RESET_TOKEN unconditionally —\r\n //? an unset token must NOT mean \"no auth required\".\r\n const nodeEnv = process.env.NODE_ENV;\r\n if (nodeEnv !== 'development' && nodeEnv !== 'test') {\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ status: 'error', errorCode: 'notFound' }));\r\n return true;\r\n }\r\n const requiredToken = process.env.TEST_RESET_TOKEN;\r\n if (!requiredToken || req.headers['x-test-reset-token'] !== requiredToken) {\r\n res.statusCode = 403;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ status: 'error', errorCode: 'auth.forbidden' }));\r\n return true;\r\n }\r\n\r\n const cleared: string[] = [];\r\n await clearAllRateLimits();\r\n cleared.push('rateLimits');\r\n\r\n //? Flush sessions + activeUsers Redis keys so integration tests start from\r\n //? a clean slate. Patterns are derived through the shared `formatKey(...)`\r\n //? authority so they track any registered key formatter (see also\r\n //? session.ts, sessionAdapter.ts, rateLimiter.ts).\r\n const sessionPattern = `${formatKey('-session', '')}:*`;\r\n const activeUsersPattern = `${formatKey('-activeUsers', '')}:*`;\r\n\r\n const scanAndDelete = async (pattern: string, label: string): Promise<number> => {\r\n const [error, deleted] = await tryCatch(async () => {\r\n let cursor = '0';\r\n let total = 0;\r\n do {\r\n const [next, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 200);\r\n cursor = next;\r\n if (Array.isArray(keys) && keys.length > 0) {\r\n await redis.del(...keys);\r\n total += keys.length;\r\n }\r\n } while (cursor !== '0');\r\n return total;\r\n });\r\n if (error || !deleted) return 0;\r\n if (deleted > 0) cleared.push(label);\r\n return deleted;\r\n };\r\n\r\n await scanAndDelete(sessionPattern, 'sessions');\r\n await scanAndDelete(activeUsersPattern, 'activeUsers');\r\n\r\n //? Opt-in hook clear via `?include=hooks` because clearing all hooks would\r\n //? also drop framework-internal handlers (e.g. presence postLogout). URL\r\n //? parsing failure is the expected branch for malformed `req.url`, so use\r\n //? `URL.canParse` instead of try/catch.\r\n const rawUrl = req.url ?? '/';\r\n const base = `http://${req.headers.host ?? 'localhost'}`;\r\n const includeFlag = URL.canParse(rawUrl, base)\r\n ? new URL(rawUrl, base).searchParams.get('include') ?? ''\r\n : '';\r\n if (includeFlag.split(',').map((s) => s.trim()).includes('hooks')) {\r\n clearAllHooks();\r\n cleared.push('hooks');\r\n }\r\n\r\n res.statusCode = 200;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ status: 'success', cleared }));\r\n return true;\r\n};\r\n","import { serveAvatar } from '@luckystack/core';\nimport type { HttpRouteHandler } from './types';\n\nexport const handleUploadsRoute: HttpRouteHandler = async ({ res, routePath }) => {\n if (!routePath.startsWith('/uploads/')) return false;\n await serveAvatar({ routePath, res });\n return true;\n};\n","import {\r\n checkRateLimit,\r\n dispatchHook,\r\n getLogger,\r\n getProjectConfig,\r\n} from '@luckystack/core';\r\nimport {\r\n createOAuthState,\r\n deleteSession,\r\n getOAuthProviders,\r\n isFullOAuthProvider,\r\n loginWithCredentials,\r\n} from '@luckystack/login';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\nconst parseSessionBasedTokenHeader = (headerValue: string | string[] | undefined): boolean | null => {\r\n if (headerValue === undefined) return null;\r\n const value = Array.isArray(headerValue) ? headerValue[0] : headerValue;\r\n if (value === '1' || value === 'true') return true;\r\n if (value === '0' || value === 'false') return false;\r\n return null;\r\n};\r\n\r\nexport const handleAuthApiRoute: HttpRouteHandler = async ({\r\n req,\r\n res,\r\n routePath,\r\n token,\r\n params,\r\n sessionCookieOptions,\r\n}) => {\r\n if (!routePath.startsWith('/auth/api')) return false;\r\n\r\n const config = getProjectConfig();\r\n const sessionCookieName = config.http.sessionCookieName;\r\n const shouldLogDev = config.logging.devLogs;\r\n\r\n const providerName = routePath.split('/')[3];\r\n const provider = getOAuthProviders().find((p) => p.name === providerName);\r\n if (!provider?.name) {\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ status: false, reason: 'login.providerNotFound' }));\r\n return true;\r\n }\r\n\r\n if (isFullOAuthProvider(provider)) {\r\n const oauthState = await createOAuthState(provider.name);\r\n if (!oauthState) {\r\n res.writeHead(500, { 'Content-Type': 'application/json' });\r\n res.end(JSON.stringify({ status: false, reason: 'login.oauthStateInitFailed' }));\r\n return true;\r\n }\r\n\r\n const clientId = encodeURIComponent(provider.clientID);\r\n const callbackUrl = encodeURIComponent(provider.callbackURL);\r\n const scope = encodeURIComponent((provider.scope).join(' '));\r\n const state = encodeURIComponent(oauthState);\r\n\r\n res.writeHead(302, {\r\n Location: `${provider.authorizationURL}?client_id=${clientId}&redirect_uri=${callbackUrl}&scope=${scope}&response_type=code&prompt=select_account&state=${state}`,\r\n });\r\n res.end();\r\n return true;\r\n }\r\n\r\n const rateLimiting = config.rateLimiting;\r\n if (rateLimiting.defaultApiLimit !== false && rateLimiting.defaultApiLimit > 0) {\r\n const requesterIp = req.socket.remoteAddress ?? 'unknown';\r\n const { allowed, resetIn } = await checkRateLimit({\r\n key: `ip:${requesterIp}:auth:credentials`,\r\n limit: rateLimiting.defaultApiLimit,\r\n windowMs: rateLimiting.windowMs,\r\n });\r\n\r\n if (!allowed) {\r\n void dispatchHook('rateLimitExceeded', {\r\n scope: 'auth',\r\n key: `ip:${requesterIp}:auth:credentials`,\r\n limit: rateLimiting.defaultApiLimit,\r\n windowMs: rateLimiting.windowMs,\r\n count: rateLimiting.defaultApiLimit + 1,\r\n route: routePath,\r\n ip: requesterIp,\r\n });\r\n res.setHeader('content-type', 'application/json; charset=utf-8');\r\n res.end(JSON.stringify({\r\n status: false,\r\n reason: 'api.rateLimitExceeded',\r\n errorParams: [{ key: 'seconds', value: resetIn }],\r\n }));\r\n return true;\r\n }\r\n }\r\n\r\n const result = (await loginWithCredentials(params)) as {\r\n status: boolean;\r\n reason: string;\r\n newToken: string | null;\r\n session: unknown;\r\n } | undefined;\r\n\r\n if (!result?.status) {\r\n const reasonKey =\r\n typeof result?.reason === 'string' && result.reason.length > 0\r\n ? result.reason\r\n : 'api.internalServerError';\r\n res.setHeader('content-type', 'application/json; charset=utf-8');\r\n res.end(JSON.stringify({ status: false, reason: reasonKey }));\r\n return true;\r\n }\r\n\r\n if (result.newToken) {\r\n if (token) await deleteSession(token);\r\n\r\n const requestedSessionMode = parseSessionBasedTokenHeader(req.headers['x-session-based-token']);\r\n const useSessionBasedToken = requestedSessionMode ?? config.session.basedToken;\r\n\r\n if (shouldLogDev) getLogger().debug('http: setting cookie with new token');\r\n\r\n if (useSessionBasedToken) {\r\n res.setHeader('X-Session-Token', result.newToken);\r\n } else {\r\n res.setHeader('Set-Cookie', `${sessionCookieName}=${result.newToken}; ${sessionCookieOptions}`);\r\n }\r\n }\r\n\r\n res.end(JSON.stringify({\r\n status: result.status,\r\n reason: result.reason,\r\n session: result.session,\r\n authenticated: Boolean(result.newToken),\r\n }));\r\n return true;\r\n};\r\n","import { getOAuthProviders } from '@luckystack/login';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\n//? GET /auth/providers — public list of enabled auth-provider names, derived\r\n//? from the env-driven OAuth registry (see the consumer's\r\n//? `luckystack/login/oauthProviders.ts`, which registers a provider only when\r\n//? its credentials env vars are present). The login form fetches this to decide\r\n//? which OAuth buttons to render WITHOUT ever shipping client secrets to the\r\n//? browser. Read-only + bodyless, so it runs in the pre-params phase.\r\nexport const handleAuthProvidersRoute: HttpRouteHandler = ({ req, res, routePath }) => {\r\n if (routePath !== '/auth/providers' || req.method !== 'GET') return Promise.resolve(false);\r\n\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ providers: getOAuthProviders().map((provider) => provider.name) }));\r\n return Promise.resolve(true);\r\n};\r\n","import { getLogger, getProjectConfig } from '@luckystack/core';\r\nimport { deleteSession, loginCallback } from '@luckystack/login';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\nexport const handleAuthCallbackRoute: HttpRouteHandler = async ({\r\n req,\r\n res,\r\n routePath,\r\n token,\r\n sessionCookieOptions,\r\n}) => {\r\n if (!routePath.startsWith('/auth/callback')) return false;\r\n\r\n const config = getProjectConfig();\r\n const sessionCookieName = config.http.sessionCookieName;\r\n const shouldLogDev = config.logging.devLogs;\r\n\r\n //? Fallback redirect target when no `postLoginRedirect` resolver returns a URL.\r\n //? `projectConfig.app.publicUrl` is the public origin where users browse (also\r\n //? used for transactional email links) — dev: the frontend dev server, prod:\r\n //? your domain. After an OAuth callback (handled on the BACKEND origin) we must\r\n //? send the browser back to this public origin, not the backend. `||` so an\r\n //? empty publicUrl falls through to '/'.\r\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string must fall through\r\n const baseLocation = config.app.publicUrl || '/';\r\n const callbackResult = await loginCallback(routePath, req, res, {\r\n defaultRedirectUrl: baseLocation,\r\n });\r\n\r\n if (!callbackResult) {\r\n res.writeHead(401, { 'Content-Type': 'text/plain' });\r\n res.end('Login failed');\r\n return true;\r\n }\r\n\r\n if (token) await deleteSession(token);\r\n\r\n if (shouldLogDev) getLogger().debug('http: setting cookie or redirect with new token');\r\n\r\n const { token: newToken, redirectUrl } = callbackResult;\r\n if (config.session.basedToken) {\r\n const separator = redirectUrl.includes('?') ? '&' : '?';\r\n res.writeHead(302, { Location: `${redirectUrl}${separator}token=${newToken}` });\r\n } else {\r\n res.setHeader('Set-Cookie', `${sessionCookieName}=${newToken}; ${sessionCookieOptions}`);\r\n res.writeHead(302, { Location: redirectUrl });\r\n }\r\n res.end();\r\n return true;\r\n};\r\n","import {\r\n captureException,\r\n dispatchHook,\r\n extractTokenFromRequest,\r\n getLogger,\r\n tryCatch,\r\n} from '@luckystack/core';\r\nimport { handleHttpApiRequest } from '@luckystack/api';\r\nimport { initSseResponse, sendSseEvent, shouldUseHttpStream } from '../sse';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\nexport const handleApiRoute: HttpRouteHandler = async ({\r\n req,\r\n res,\r\n routePath,\r\n queryString,\r\n method,\r\n params,\r\n requestId,\r\n}) => {\r\n if (!routePath.startsWith('/api/')) return false;\r\n\r\n const useHttpStream = shouldUseHttpStream({ acceptHeader: req.headers.accept, queryString });\r\n let streamClosed = false;\r\n if (useHttpStream) {\r\n initSseResponse(res);\r\n req.on('close', () => {\r\n streamClosed = true;\r\n });\r\n }\r\n\r\n const [error, handled] = await tryCatch(async () => {\r\n const httpToken = extractTokenFromRequest(req);\r\n const apiName = routePath.slice(5); // strip \"/api/\"\r\n\r\n if (!apiName) {\r\n const response = {\r\n status: 'error' as const,\r\n httpStatus: 400,\r\n message: 'api.invalidName',\r\n errorCode: 'api.invalidName',\r\n };\r\n if (useHttpStream) {\r\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: response });\r\n res.end();\r\n return true;\r\n }\r\n res.setHeader('Content-Type', 'application/json');\r\n res.writeHead(400);\r\n res.end(JSON.stringify(response));\r\n return true;\r\n }\r\n\r\n const apiData = typeof params === 'object'\r\n ? { ...(params as Record<string, unknown>) }\r\n : {};\r\n delete (apiData as Record<string, unknown>).stream;\r\n\r\n const result = await handleHttpApiRequest({\r\n name: apiName,\r\n data: apiData,\r\n token: httpToken,\r\n requesterIp: req.socket.remoteAddress ?? undefined,\r\n xLanguageHeader: req.headers['x-language'],\r\n acceptLanguageHeader: req.headers['accept-language'],\r\n method,\r\n stream: useHttpStream\r\n ? (payload) => {\r\n if (streamClosed || res.writableEnded) return;\r\n sendSseEvent({ res, event: 'stream', data: payload });\r\n }\r\n : undefined,\r\n });\r\n\r\n if (useHttpStream) {\r\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: result });\r\n res.end();\r\n return true;\r\n }\r\n\r\n res.setHeader('Content-Type', 'application/json');\r\n res.writeHead(result.httpStatus);\r\n res.end(JSON.stringify(result));\r\n return true;\r\n }, undefined, { routePath, method, requestId, source: 'httpHandler.api' });\r\n\r\n if (!error) {\r\n return handled ?? true;\r\n }\r\n\r\n getLogger().error('http-api: top-level handler threw', error, { routePath, method, requestId });\r\n captureException(error, { routePath, method, requestId, source: 'httpHandler.api' });\r\n void dispatchHook('apiError', {\r\n route: routePath,\r\n method,\r\n requestId,\r\n error,\r\n });\r\n\r\n const errResponse = {\r\n status: 'error' as const,\r\n httpStatus: 500,\r\n message: 'api.invalidRequestFormat',\r\n errorCode: 'api.invalidRequestFormat',\r\n };\r\n\r\n if (useHttpStream) {\r\n if (!res.writableEnded) sendSseEvent({ res, event: 'error', data: errResponse });\r\n res.end();\r\n return true;\r\n }\r\n\r\n res.setHeader('Content-Type', 'application/json');\r\n res.writeHead(500);\r\n res.end(JSON.stringify(errResponse));\r\n return true;\r\n};\r\n","import type { ServerResponse } from 'node:http';\r\nimport { getProjectConfig } from '@luckystack/core';\r\n\r\n//? Server-Sent Events (SSE) helpers shared by /api/* and /sync/* HTTP\r\n//? streaming. Only used when the client opted in via Accept: text/event-stream\r\n//? or the configured ?<queryParam>=<enabledValue>.\r\n\r\nconst isExpectingEventStream = (acceptHeader: string | string[] | undefined): boolean => {\r\n if (!acceptHeader) return false;\r\n const value = Array.isArray(acceptHeader) ? acceptHeader.join(',') : acceptHeader;\r\n return value.toLowerCase().includes('text/event-stream');\r\n};\r\n\r\nconst queryRequestsStream = (queryString: string | undefined): boolean => {\r\n if (!queryString) return false;\r\n const { queryParam, enabledValue } = getProjectConfig().http.stream;\r\n const params = new URLSearchParams(queryString);\r\n const value = params.get(queryParam);\r\n return value === enabledValue || value === '1';\r\n};\r\n\r\nexport const shouldUseHttpStream = ({\r\n acceptHeader,\r\n queryString,\r\n}: {\r\n acceptHeader: string | string[] | undefined;\r\n queryString: string | undefined;\r\n}): boolean => isExpectingEventStream(acceptHeader) || queryRequestsStream(queryString);\r\n\r\nexport const initSseResponse = (res: ServerResponse): void => {\r\n res.writeHead(200, {\r\n 'Content-Type': 'text/event-stream',\r\n 'Cache-Control': 'no-cache, no-transform',\r\n Connection: 'keep-alive',\r\n 'X-Accel-Buffering': 'no',\r\n });\r\n const connectedComment = getProjectConfig().http.stream.connectedComment;\r\n if (connectedComment) {\r\n res.write(`${connectedComment}\\n\\n`);\r\n }\r\n};\r\n\r\nexport const sendSseEvent = ({\r\n res,\r\n event,\r\n data,\r\n}: {\r\n res: ServerResponse;\r\n event: string;\r\n data: unknown;\r\n}): void => {\r\n if (res.writableEnded) return;\r\n res.write(`event: ${event}\\n`);\r\n res.write(`data: ${JSON.stringify(data)}\\n\\n`);\r\n};\r\n","import {\n captureException,\n dispatchHook,\n extractTokenFromRequest,\n getLogger,\n tryCatch,\n} from '@luckystack/core';\nimport { handleHttpSyncRequest, type HttpSyncStreamEvent } from '@luckystack/sync';\nimport { initSseResponse, sendSseEvent, shouldUseHttpStream } from '../sse';\nimport type { HttpRouteHandler } from './types';\n\ninterface NormalizedHttpSyncParams {\n data: Record<string, unknown>;\n receiver: string;\n ignoreSelf?: boolean;\n cb?: string;\n}\n\nconst normalizeHttpSyncParams = (params: object | null): NormalizedHttpSyncParams => {\n const source = (params ?? {}) as Record<string, unknown>;\n const data = (source.data && typeof source.data === 'object'\n ? (source.data as Record<string, unknown>)\n : {});\n return {\n data,\n receiver: typeof source.receiver === 'string' ? source.receiver : '',\n ignoreSelf: typeof source.ignoreSelf === 'boolean' ? source.ignoreSelf : undefined,\n cb: typeof source.cb === 'string' ? source.cb : undefined,\n };\n};\n\nexport const handleSyncRoute: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n queryString,\n method,\n params,\n requestId,\n}) => {\n if (!routePath.startsWith('/sync/')) return false;\n\n const useHttpStream = shouldUseHttpStream({ acceptHeader: req.headers.accept, queryString });\n let streamClosed = false;\n if (useHttpStream) {\n initSseResponse(res);\n req.on('close', () => {\n streamClosed = true;\n });\n }\n\n const [error, handled] = await tryCatch(async () => {\n if (method !== 'POST') {\n const response = {\n status: 'error' as const,\n message: 'sync.methodNotAllowed',\n errorCode: 'sync.methodNotAllowed',\n };\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: response });\n res.end();\n return true;\n }\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(405);\n res.end(JSON.stringify(response));\n return true;\n }\n\n const httpToken = extractTokenFromRequest(req);\n const syncName = routePath.slice(6); // strip \"/sync/\"\n\n if (!syncName) {\n const response = {\n status: 'error' as const,\n message: 'sync.invalidName',\n errorCode: 'sync.invalidName',\n };\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: response });\n res.end();\n return true;\n }\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(400);\n res.end(JSON.stringify(response));\n return true;\n }\n\n const syncParams = normalizeHttpSyncParams(params);\n\n const result = await handleHttpSyncRequest({\n name: `sync/${syncName}`,\n cb: syncParams.cb,\n data: syncParams.data,\n receiver: syncParams.receiver,\n ignoreSelf: syncParams.ignoreSelf,\n token: httpToken,\n requesterIp: req.socket.remoteAddress ?? undefined,\n xLanguageHeader: req.headers['x-language'],\n acceptLanguageHeader: req.headers['accept-language'],\n stream: useHttpStream\n ? (payload: HttpSyncStreamEvent) => {\n if (streamClosed || res.writableEnded) return;\n sendSseEvent({ res, event: 'stream', data: payload });\n }\n : undefined,\n });\n\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: result });\n res.end();\n return true;\n }\n\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(result.httpStatus ?? (result.status === 'success' ? 200 : 400));\n res.end(JSON.stringify(result));\n return true;\n }, undefined, { routePath, method, requestId, source: 'httpHandler.sync' });\n\n if (!error) {\n return handled ?? true;\n }\n\n getLogger().error('http-sync: top-level handler threw', error, { routePath, method, requestId });\n captureException(error, { routePath, method, requestId, source: 'httpHandler.sync' });\n void dispatchHook('syncError', {\n route: routePath,\n method,\n requestId,\n error,\n });\n\n const errResponse = {\n status: 'error' as const,\n message: 'sync.invalidRequestFormat',\n errorCode: 'sync.invalidRequestFormat',\n };\n\n if (useHttpStream) {\n if (!res.writableEnded) sendSseEvent({ res, event: 'error', data: errResponse });\n res.end();\n return true;\n }\n\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(500);\n res.end(JSON.stringify(errResponse));\n return true;\n};\n","import { captureException, getLogger, tryCatch } from '@luckystack/core';\r\nimport type { IncomingMessage, ServerResponse } from 'node:http';\r\nimport type { CustomRouteHandler, RouteContext } from '../types';\r\nimport { getCustomRoutes, getPreParamsCustomRoutes } from '../customRoutesRegistry';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\nconst runHandler = async (\r\n handler: CustomRouteHandler,\r\n req: IncomingMessage,\r\n res: ServerResponse,\r\n ctx: RouteContext,\r\n source: string,\r\n): Promise<boolean> => {\r\n const [error, handled] = await tryCatch(() => handler(req, res, ctx));\r\n if (error) {\r\n //? Custom route handlers come from third-party overlay packages — one\r\n //? misbehaving handler must not crash the request loop or leak the\r\n //? error to the client. Surface to logger + Sentry, then return as\r\n //? handled so the caller short-circuits.\r\n getLogger().error(`${source} threw`, error, { routePath: ctx.routePath, method: ctx.method });\r\n captureException(error, { routePath: ctx.routePath, method: ctx.method, source });\r\n if (!res.writableEnded) {\r\n res.writeHead(500, { 'Content-Type': 'application/json' });\r\n res.end(JSON.stringify({ status: 'error', errorCode: 'server.customRouteFailed' }));\r\n }\r\n return true;\r\n }\r\n return Boolean(handled) || res.writableEnded;\r\n};\r\n\r\n//? PRE-PARAMS phase: consumer routes registered with `{ phase: 'pre-params' }`,\r\n//? dispatched BEFORE the body is parsed so the handler can read the raw `req`\r\n//? stream (webhook HMAC verification, streaming/multipart uploads). A handler\r\n//? that returns `false` without consuming the body falls through to the normal\r\n//? pipeline. Runs after the framework's own pre-params fast-paths.\r\nexport const handlePreParamsCustomRoutes: HttpRouteHandler = async ({\r\n req,\r\n res,\r\n routePath,\r\n queryString,\r\n method,\r\n token,\r\n}) => {\r\n const ctx: RouteContext = { routePath, method, queryString, token };\r\n for (const handler of getPreParamsCustomRoutes()) {\r\n const handled = await runHandler(handler, req, res, ctx, 'preParamsCustomRoute');\r\n if (handled) return true;\r\n }\r\n return false;\r\n};\r\n\r\nexport const handleCustomRoutes: HttpRouteHandler = async ({\r\n req,\r\n res,\r\n options,\r\n routePath,\r\n queryString,\r\n method,\r\n token,\r\n}) => {\r\n //? Two sources, evaluated in order: (1) handlers registered via\r\n //? `registerCustomRoute(...)` from overlay packages (`@luckystack/docs-ui`,\r\n //? etc.); (2) the legacy `customRoutes` option on\r\n //? `CreateLuckyStackServerOptions`. First one to return `true` (or end\r\n //? the response) wins.\r\n const ctx: RouteContext = { routePath, method, queryString, token };\r\n\r\n for (const handler of getCustomRoutes()) {\r\n const handled = await runHandler(handler, req, res, ctx, 'customRoutesRegistry');\r\n if (handled) return true;\r\n }\r\n if (options.customRoutes) {\r\n const handled = await runHandler(options.customRoutes, req, res, ctx, 'createServer.customRoutes');\r\n if (handled) return true;\r\n }\r\n return false;\r\n};\r\n","//? Custom-route registry. Lets overlay files (luckystack/<package>/index.ts)\r\n//? register HTTP route handlers without each one touching server/server.ts.\r\n//?\r\n//? Why a registry instead of a single function on CreateLuckyStackServerOptions:\r\n//? The overlay-folder pattern means multiple packages may want to add\r\n//? routes (docs-ui, an admin panel, a webhook receiver, ...). Each\r\n//? registers independently; the framework iterates them in the order they\r\n//? were registered, returning the first one that handles the request.\r\n//?\r\n//? The original `customRoutes` option on `CreateLuckyStackServerOptions`\r\n//? still works — it's appended to the registry at boot and runs last.\r\n//?\r\n//? Two phases (see `CustomRoutePhase`): `'post-params'` (default — runs after\r\n//? the body is parsed) and `'pre-params'` (runs before the body is read, so\r\n//? the handler gets the raw `req` stream — webhooks + streaming uploads).\r\n\r\nimport type { CustomRouteHandler, CustomRoutePhase } from './types';\r\n\r\nexport interface RegisterCustomRouteOptions {\r\n /** Pipeline phase the handler runs in. Defaults to `'post-params'`. */\r\n phase?: CustomRoutePhase;\r\n}\r\n\r\n//? `handlers` backs the default `'post-params'` phase. `getCustomRoutes()`\r\n//? returns this array by reference (callers + tests rely on the clear-in-place\r\n//? semantics), so keep it as the post-params store.\r\nconst handlers: CustomRouteHandler[] = [];\r\nconst preParamsHandlers: CustomRouteHandler[] = [];\r\n\r\nexport const registerCustomRoute = (\r\n handler: CustomRouteHandler,\r\n options: RegisterCustomRouteOptions = {},\r\n): void => {\r\n if (options.phase === 'pre-params') {\r\n preParamsHandlers.push(handler);\r\n return;\r\n }\r\n handlers.push(handler);\r\n};\r\n\r\nexport const getCustomRoutes = (): readonly CustomRouteHandler[] => handlers;\r\n\r\nexport const getPreParamsCustomRoutes = (): readonly CustomRouteHandler[] => preParamsHandlers;\r\n\r\nexport const clearCustomRoutes = (): void => {\r\n handlers.length = 0;\r\n preParamsHandlers.length = 0;\r\n};\r\n","import path from 'node:path';\r\nimport type { IncomingMessage, ServerResponse } from 'node:http';\r\nimport type { StaticFileHandler } from '../types';\r\nimport type { HttpRouteHandler } from './types';\r\n\r\nconst KNOWN_STATIC_FILE_REGEX = /^\\/(assets\\/[a-zA-Z0-9_/-]+|[a-zA-Z0-9_-]+)\\.(png|jpg|jpeg|gif|svg|html|css|js)$/;\r\n\r\n//? `serveFile` consumers (Vite middleware, custom static handlers) read\r\n//? `req.url`, so we need to swap it for the rewritten asset path. We swap\r\n//? around the call and restore on return so anything downstream that\r\n//? observes `req.url` (request loggers, metrics middleware) still sees the\r\n//? original incoming URL after this handler.\r\nconst serveWithRewrittenUrl = async (\r\n serveFile: StaticFileHandler,\r\n req: IncomingMessage,\r\n res: ServerResponse,\r\n rewrittenUrl: string,\r\n): Promise<void> => {\r\n const originalUrl = req.url;\r\n req.url = rewrittenUrl;\r\n try {\r\n await serveFile(req, res);\r\n } finally {\r\n req.url = originalUrl;\r\n }\r\n};\r\n\r\nexport const handleStaticAndSpaFallback: HttpRouteHandler = async ({\r\n req,\r\n res,\r\n routePath,\r\n options,\r\n}) => {\r\n // ── /assets/* — static assets ──────────────────────────────────────────\r\n if (routePath.includes('/assets/')) {\r\n if (!options.serveFile) {\r\n res.writeHead(404);\r\n res.end('Not Found');\r\n return true;\r\n }\r\n const assetPath = routePath.slice(routePath.indexOf('/assets/'));\r\n await serveWithRewrittenUrl(options.serveFile, req, res, assetPath);\r\n return true;\r\n }\r\n\r\n // ── *.{png,jpg,...} — known static file extensions ────────────────────\r\n if (KNOWN_STATIC_FILE_REGEX.test(routePath)) {\r\n if (!options.serveFile) {\r\n res.writeHead(404);\r\n res.end('Not Found');\r\n return true;\r\n }\r\n await options.serveFile(req, res);\r\n return true;\r\n }\r\n\r\n // ── path with extension we don't recognize — 404 ──────────────────────\r\n if (path.extname(routePath)) {\r\n res.writeHead(404, { 'Content-Type': 'text/plain' });\r\n res.end('Not Found');\r\n return true;\r\n }\r\n\r\n // ── catch-all (index.html for SPA routing) ────────────────────────────\r\n if (!options.serveFile) {\r\n res.writeHead(404, { 'Content-Type': 'text/plain' });\r\n res.end('Not Found');\r\n return true;\r\n }\r\n\r\n await serveWithRewrittenUrl(options.serveFile, req, res, '/');\r\n return true;\r\n};\r\n","//? Origin-exempt path registry. `enforceOriginPolicy` fail-closes every\r\n//? state-changing POST that carries no browser-attributable Origin/Referer —\r\n//? which is exactly a legitimate server-to-server webhook (GitLab, Stripe, ...).\r\n//? Registering a path prefix here lets such an endpoint through the browser\r\n//? CSRF/origin gate.\r\n//?\r\n//? SECURITY: origin exemption is NOT authentication. It only removes the\r\n//? browser-origin check; the exempted handler MUST authenticate the caller\r\n//? itself (HMAC over the raw body, a shared-secret header, mTLS, ...). Pair\r\n//? with a `'pre-params'` custom route so the handler can read the raw body for\r\n//? signature verification. Empty by default — opt-in only. Do NOT register a\r\n//? prefix that overlaps framework routes (`/api`, `/auth`, `/sync`); keep\r\n//? webhooks on a dedicated prefix like `/webhooks/`. See\r\n//? docs/ARCHITECTURE_HTTP.md.\r\n\r\nexport interface OriginExemptMatcher {\r\n /** A route is exempt when its path starts with this prefix. */\r\n pathPrefix: string;\r\n}\r\n\r\nconst exemptPaths: OriginExemptMatcher[] = [];\r\n\r\nexport const registerOriginExemptPath = (matcher: OriginExemptMatcher): void => {\r\n exemptPaths.push(matcher);\r\n};\r\n\r\nexport const getOriginExemptPaths = (): readonly OriginExemptMatcher[] => exemptPaths;\r\n\r\nexport const clearOriginExemptPaths = (): void => {\r\n exemptPaths.length = 0;\r\n};\r\n\r\n//? True when `routePath` matches a registered exempt prefix. Consulted by\r\n//? `enforceOriginPolicy` before it can 403 a header-less request.\r\nexport const isOriginExemptPath = (routePath: string): boolean =>\r\n exemptPaths.some((matcher) => routePath.startsWith(matcher.pathPrefix));\r\n","import type { Server as HttpServer } from 'node:http';\r\nimport { Server as SocketIOServer } from 'socket.io';\r\nimport {\r\n abortAllForSocket,\r\n abortApiByResponseIndex,\r\n abortSyncByCb,\r\n allowedOrigin,\r\n applySocketMiddlewares,\r\n attachSocketRedisAdapter,\r\n setIoInstance,\r\n socketEventNames,\r\n buildJoinRoomResponseEventName,\r\n buildLeaveRoomResponseEventName,\r\n buildGetJoinedRoomsResponseEventName,\r\n extractLanguageFromHeader,\r\n extractTokenFromSocket,\r\n getLogger,\r\n getProjectConfig,\r\n dispatchHook,\r\n normalizeErrorResponse,\r\n tryCatch,\r\n type apiMessage,\r\n type syncMessage,\r\n type BaseSessionLayout,\r\n} from '@luckystack/core';\r\nimport { handleApiRequest } from '@luckystack/api';\r\nimport { handleSyncRequest } from '@luckystack/sync';\r\nimport { getSession, saveSession } from '@luckystack/login';\r\nimport {\r\n initActivityBroadcaster,\r\n socketConnected,\r\n socketDisconnecting,\r\n socketLeaveRoom,\r\n} from '@luckystack/presence';\r\n\r\n//? Per-token lock to serialize session mutations (prevents read-modify-write races\r\n//? when multiple room joins/leaves arrive in quick succession from the same socket).\r\nconst sessionLocks = new Map<string, Promise<void>>();\r\nconst withSessionLock = async (token: string, fn: () => Promise<void>) => {\r\n const prev = sessionLocks.get(token) ?? Promise.resolve();\r\n //? Both `then` handlers point to `fn` so a previous lock failure (rejected\r\n //? promise) doesn't block the next caller — we want each `withSessionLock`\r\n //? invocation to run regardless of whether the prior one threw.\r\n const next = prev.then(fn, fn);\r\n sessionLocks.set(token, next);\r\n await tryCatch(() => next);\r\n //? Cleanup runs unconditionally — same semantics the prior `try/finally`\r\n //? provided. Only delete if we still own the slot (a later caller may\r\n //? have already overwritten it with their own `next`).\r\n if (sessionLocks.get(token) === next) sessionLocks.delete(token);\r\n};\r\n\r\nconst getVisibleSocketRooms = (\r\n socket: { rooms: Set<string>; id: string },\r\n token: string | null\r\n): string[] => {\r\n return [...socket.rooms]\r\n .filter((room): room is string => typeof room === 'string')\r\n .filter((room) => room !== socket.id)\r\n .filter((room) => !token || room !== token);\r\n};\r\n\r\nconst getSessionRoomCodes = (session: BaseSessionLayout): string[] => {\r\n const roomCodes = Array.isArray(session.roomCodes)\r\n ? session.roomCodes.filter(\r\n (roomCode): roomCode is string => typeof roomCode === 'string' && roomCode.length > 0\r\n )\r\n : [];\r\n return [...new Set(roomCodes)];\r\n};\r\n\r\nconst sanitizeSessionRoomKeys = (session: BaseSessionLayout): BaseSessionLayout => {\r\n const { code: _legacyCode, codes: _legacyCodes, ...sanitizedSession } = session as BaseSessionLayout & {\r\n code?: string;\r\n codes?: string[];\r\n };\r\n return sanitizedSession;\r\n};\r\n\r\nexport interface LoadSocketOptions {\r\n maxHttpBufferSize?: number;\r\n}\r\n\r\nexport const loadSocket = (httpServer: HttpServer, options: LoadSocketOptions = {}): SocketIOServer => {\r\n const config = getProjectConfig();\r\n const shouldLogDev = config.logging.devLogs;\r\n const shouldLogSocketStartup = config.logging.socketStartup;\r\n\r\n const io = new SocketIOServer(httpServer, {\r\n cors: {\r\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\r\n origin: (origin, callback) => {\r\n if (!origin || allowedOrigin(origin)) {\r\n callback(null, true);\r\n } else {\r\n callback(new Error('Not allowed by CORS'));\r\n }\r\n },\r\n credentials: true,\r\n },\r\n maxHttpBufferSize: options.maxHttpBufferSize ?? config.socket.maxHttpBufferSize,\r\n pingTimeout: config.socket.pingTimeout,\r\n pingInterval: config.socket.pingInterval,\r\n });\r\n\r\n setIoInstance(io);\r\n\r\n //? Apply consumer-registered Socket.io middlewares BEFORE the connect\r\n //? handler is attached so they run on the handshake of every incoming\r\n //? socket — same contract as a direct `io.use(...)` call.\r\n applySocketMiddlewares(io);\r\n\r\n //? Redis adapter so room broadcasts fan out across instances. Required for\r\n //? split/fallback deployments; safe overhead in single-instance deploys.\r\n attachSocketRedisAdapter(io);\r\n\r\n if (shouldLogSocketStartup) {\r\n getLogger().info('SocketIO server initialized (redis adapter attached)');\r\n }\r\n\r\n io.on(socketEventNames.connect, (socket) => {\r\n const token = extractTokenFromSocket(socket);\r\n //? Cache per-connection feature flags so we don't refetch the project\r\n //? config on every event.\r\n const activityBroadcasterEnabled = config.socketActivityBroadcaster ?? false;\r\n const locationProviderEnabled = config.locationProviderEnabled ?? false;\r\n //? `extractLanguageFromHeader` can return an empty string for unparseable\r\n //? headers — those should fall through to the next source, so keep `||`.\r\n /* eslint-disable @typescript-eslint/prefer-nullish-coalescing -- see comment above */\r\n const preferredLocale =\r\n extractLanguageFromHeader(socket.handshake.headers['x-language'])\r\n || extractLanguageFromHeader(socket.handshake.headers['accept-language'])\r\n || undefined;\r\n /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */\r\n\r\n if (token) {\r\n void socketConnected({ token, io });\r\n }\r\n\r\n void dispatchHook('onSocketConnect', {\r\n socketId: socket.id,\r\n token,\r\n ip: socket.handshake.address,\r\n });\r\n\r\n socket.on(socketEventNames.apiRequest, (msg: apiMessage) => {\r\n void handleApiRequest({ msg, socket, token });\r\n });\r\n\r\n socket.on(socketEventNames.sync, (msg: syncMessage) => {\r\n void handleSyncRequest({ msg, socket, token });\r\n });\r\n\r\n //? B1 — cancellation events. Client emits `{ cb }` (sync) or\r\n //? `{ responseIndex }` (api) on the matching cancel channel; we look up\r\n //? the in-flight AbortController by `${socket.id}:<key>` and abort it.\r\n //? Server-side handler chains gate further chunk emits on the signal\r\n //? and exit early via the cleanup paths registered in each handler.\r\n socket.on(socketEventNames.syncCancel, (data: { cb?: string }) => {\r\n const cb = typeof data.cb === 'string' ? data.cb : null;\r\n if (!cb) return;\r\n abortSyncByCb(socket.id, cb);\r\n });\r\n socket.on(socketEventNames.apiCancel, (data: { responseIndex?: number | string }) => {\r\n const responseIndex = data.responseIndex;\r\n if (typeof responseIndex !== 'number' && typeof responseIndex !== 'string') return;\r\n abortApiByResponseIndex(socket.id, responseIndex);\r\n });\r\n\r\n socket.on(socketEventNames.joinRoom, (data: { group?: string; responseIndex?: number }) => {\r\n const group = typeof data.group === 'string' ? data.group.trim() : '';\r\n const responseIndex = data.responseIndex;\r\n if (typeof responseIndex !== 'number') return;\r\n\r\n if (!token) {\r\n socket.emit(buildJoinRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'auth.required' },\r\n preferredLocale,\r\n }));\r\n return;\r\n }\r\n if (!group) {\r\n socket.emit(buildJoinRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'room.invalid' },\r\n preferredLocale,\r\n }));\r\n return;\r\n }\r\n\r\n void withSessionLock(token, async () => {\r\n const session = await getSession(token);\r\n if (!session) {\r\n socket.emit(buildJoinRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'session.notFound' },\r\n preferredLocale,\r\n }));\r\n return;\r\n }\r\n\r\n //? Allow consumers to veto a join (auth check, allowlist, ...).\r\n const preResult = await dispatchHook('preRoomJoin', { token, room: group });\r\n if (preResult.stopped) {\r\n socket.emit(buildJoinRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: {\r\n status: 'error',\r\n errorCode: preResult.signal.errorCode || 'room.joinBlocked',\r\n },\r\n preferredLocale,\r\n userLanguage: session.language,\r\n }));\r\n return;\r\n }\r\n\r\n const existingRoomCodes = getSessionRoomCodes(session);\r\n const nextRoomCodes = [...new Set([...existingRoomCodes, group])];\r\n\r\n await socket.join(group);\r\n const sanitizedSession = sanitizeSessionRoomKeys(session);\r\n await saveSession(token, { ...sanitizedSession, roomCodes: nextRoomCodes });\r\n const visibleRooms = getVisibleSocketRooms(socket, token);\r\n socket.emit(buildJoinRoomResponseEventName(responseIndex), { rooms: visibleRooms });\r\n if (shouldLogDev) {\r\n getLogger().debug(`Socket ${socket.id} joined group ${group}`);\r\n }\r\n\r\n void dispatchHook('postRoomJoin', { token, room: group, allRooms: visibleRooms });\r\n });\r\n });\r\n\r\n socket.on(socketEventNames.leaveRoom, (data: { group?: string; responseIndex?: number }) => {\r\n const group = typeof data.group === 'string' ? data.group.trim() : '';\r\n const responseIndex = data.responseIndex;\r\n if (typeof responseIndex !== 'number') return;\r\n\r\n if (!token) {\r\n socket.emit(buildLeaveRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'auth.required' },\r\n preferredLocale,\r\n }));\r\n return;\r\n }\r\n if (!group) {\r\n socket.emit(buildLeaveRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'room.invalid' },\r\n preferredLocale,\r\n }));\r\n return;\r\n }\r\n\r\n void withSessionLock(token, async () => {\r\n const session = await getSession(token);\r\n if (!session) {\r\n socket.emit(buildLeaveRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'session.notFound' },\r\n preferredLocale,\r\n }));\r\n return;\r\n }\r\n\r\n const preResult = await dispatchHook('preRoomLeave', { token, room: group });\r\n if (preResult.stopped) {\r\n socket.emit(buildLeaveRoomResponseEventName(responseIndex), normalizeErrorResponse({\r\n response: {\r\n status: 'error',\r\n errorCode: preResult.signal.errorCode || 'room.leaveBlocked',\r\n },\r\n preferredLocale,\r\n userLanguage: session.language,\r\n }));\r\n return;\r\n }\r\n\r\n const existingRoomCodes = getSessionRoomCodes(session);\r\n const nextRoomCodes = existingRoomCodes.filter((roomCode) => roomCode !== group);\r\n\r\n await socket.leave(group);\r\n const sanitizedSession = sanitizeSessionRoomKeys(session);\r\n await saveSession(token, { ...sanitizedSession, roomCodes: nextRoomCodes });\r\n\r\n const visibleRooms = getVisibleSocketRooms(socket, token);\r\n socket.emit(buildLeaveRoomResponseEventName(responseIndex), { rooms: visibleRooms });\r\n if (shouldLogDev) {\r\n getLogger().debug(`Socket ${socket.id} left group ${group}`);\r\n }\r\n\r\n void dispatchHook('postRoomLeave', { token, room: group, allRooms: visibleRooms });\r\n });\r\n });\r\n\r\n socket.on(socketEventNames.getJoinedRooms, (data: { responseIndex?: number }) => {\r\n const responseIndex = data.responseIndex;\r\n if (typeof responseIndex !== 'number') return;\r\n\r\n if (!token) {\r\n socket.emit(buildGetJoinedRoomsResponseEventName(responseIndex), {\r\n ...normalizeErrorResponse({\r\n response: { status: 'error', errorCode: 'auth.required' },\r\n preferredLocale,\r\n }),\r\n rooms: [],\r\n });\r\n return;\r\n }\r\n\r\n socket.emit(buildGetJoinedRoomsResponseEventName(responseIndex), {\r\n rooms: getVisibleSocketRooms(socket, token),\r\n });\r\n });\r\n\r\n socket.on(socketEventNames.disconnect, (reason: string) => {\r\n //? B1 — safety-net sweep. Per-request handlers also register their\r\n //? own `socket.once(disconnect, ...)` listeners that abort + clean up,\r\n //? but `abortAllForSocket` covers anything that slipped through (e.g.\r\n //? handler crashed before registering its disconnect listener).\r\n abortAllForSocket(socket.id);\r\n void dispatchHook('onSocketDisconnect', { socketId: socket.id, token, reason });\r\n\r\n if (activityBroadcasterEnabled && token) {\r\n void socketDisconnecting({ token, socket, reason });\r\n } else {\r\n if (!token) return;\r\n if (shouldLogDev) {\r\n getLogger().debug(`user disconnected`, { reason });\r\n }\r\n }\r\n });\r\n\r\n socket.on(\r\n socketEventNames.updateLocation,\r\n (newLocation: { pathName: string; searchParams?: Record<string, string> }) => {\r\n if (!token) return;\r\n if (!locationProviderEnabled) return;\r\n if (shouldLogDev) {\r\n getLogger().debug('updating location', { pathName: newLocation.pathName });\r\n }\r\n\r\n void withSessionLock(token, async () => {\r\n let returnedUser: BaseSessionLayout | null = null;\r\n if (activityBroadcasterEnabled) {\r\n returnedUser = await socketLeaveRoom({ token, socket, newPath: newLocation.pathName });\r\n }\r\n\r\n const user = returnedUser ?? (await getSession(token));\r\n if (!user) return;\r\n\r\n const extendedUser = user as BaseSessionLayout & { location?: typeof newLocation };\r\n const oldLocation = extendedUser.location;\r\n extendedUser.location = newLocation;\r\n await saveSession(token, user);\r\n\r\n void dispatchHook('onLocationUpdate', { token, oldLocation, newLocation });\r\n });\r\n }\r\n );\r\n\r\n if (activityBroadcasterEnabled && token) {\r\n initActivityBroadcaster({ socket, token });\r\n }\r\n\r\n if (token) {\r\n //? Rebuild this session's room membership on (re)connect. Socket.io rooms\r\n //? are per-connection + in-memory, so a page refresh (brand-new socket) or\r\n //? a server restart drops them while `session.roomCodes` (persisted in\r\n //? Redis) still lists them. Without replaying them here the reconnected\r\n //? socket only sits in its private token room, so a `syncRequest` fan-out\r\n //? (`io.in(room).fetchSockets()`) finds zero members and returns\r\n //? `sync.noReceiversFound`. Sequenced (await) so membership is restored\r\n //? ASAP, and logged so a failed/empty rejoin is visible. Idempotent —\r\n //? re-joining an already-joined room is a no-op.\r\n void (async () => {\r\n const [rejoinError, codes] = await tryCatch(async () => {\r\n await socket.join(token);\r\n const session = await getSession(token);\r\n const roomCodes = session ? getSessionRoomCodes(session) : [];\r\n for (const roomCode of roomCodes) {\r\n await socket.join(roomCode);\r\n }\r\n return roomCodes;\r\n });\r\n if (rejoinError) {\r\n getLogger().warn(`socket room rejoin failed for ${socket.id}`, { error: rejoinError.message });\r\n return;\r\n }\r\n if (shouldLogDev) {\r\n getLogger().debug(`socket ${socket.id} (re)joined rooms: ${(codes ?? []).join(', ') || '(none)'}`);\r\n }\r\n })();\r\n }\r\n });\r\n\r\n return io;\r\n};\r\n","//? Boot-time validation that every registry the framework relies on has been\r\n//? populated by the project's overlay files. Replaces silent runtime crashes\r\n//? deep inside a request handler with a single, descriptive error thrown\r\n//? before `httpServer.listen()` is called.\r\n//?\r\n//? The check is intentionally lightweight: it only confirms that the required\r\n//? registrations happened. It does not validate the contents of those\r\n//? registrations — that's done by the registries' own type / runtime\r\n//? validators (e.g. `getProjectConfig()` always returns a deeply-merged value).\r\n\r\nimport {\r\n getLogger,\r\n isDeployConfigRegistered,\r\n isLocalizedNormalizerRegistered,\r\n isProjectConfigRegistered,\r\n isRuntimeMapsProviderRegistered,\r\n} from '@luckystack/core';\r\n\r\nexport interface BootstrapRequirements {\r\n /**\r\n * If true, require a deploy config to have been registered. Defaults to\r\n * false because single-instance deployments don't need one.\r\n */\r\n requireDeployConfig?: boolean;\r\n /**\r\n * If true, require a services config to have been registered. Defaults to\r\n * false; only needed when running the router.\r\n */\r\n requireServicesConfig?: boolean;\r\n /**\r\n * If true, require an OAuth provider list to have been registered.\r\n * Defaults to false (an app may use credentials only).\r\n */\r\n requireOAuthProviders?: boolean;\r\n}\r\n\r\nexport const verifyBootstrap = async (requirements: BootstrapRequirements = {}): Promise<void> => {\r\n const missing: string[] = [];\r\n\r\n if (!isProjectConfigRegistered()) {\r\n missing.push(\r\n 'ProjectConfig — call `registerProjectConfig({...})` from `luckystack/core/bootstrap.ts` (or your config.ts).'\r\n );\r\n }\r\n\r\n if (requirements.requireDeployConfig && !isDeployConfigRegistered()) {\r\n missing.push(\r\n 'DeployConfig — call `registerDeployConfig({...})` from `luckystack/deploy/deploy.config.ts`.'\r\n );\r\n }\r\n\r\n if (requirements.requireServicesConfig) {\r\n const { isServicesConfigRegistered } = await import('@luckystack/core');\r\n if (!isServicesConfigRegistered()) {\r\n missing.push(\r\n 'ServicesConfig — call `registerServicesConfig({...})` from `services.config.ts`.'\r\n );\r\n }\r\n }\r\n\r\n if (requirements.requireOAuthProviders) {\r\n const { getOAuthProviders } = await import('@luckystack/login');\r\n if (getOAuthProviders().length <= 1) {\r\n // Default registry is `[{ name: 'credentials' }]` — anything ≤1 means\r\n // the consumer never registered providers.\r\n missing.push(\r\n 'OAuth providers — call `registerOAuthProviders([...])` from `luckystack/login/oauthProviders.ts` (or skip this check if your app uses credentials only).'\r\n );\r\n }\r\n }\r\n\r\n //? Runtime maps provider — without it, every API/sync request silently\r\n //? returns `notFound`. Hard-fail in production; loud-warn in dev because\r\n //? tests and the bare-server dev mode legitimately boot without one.\r\n if (!isRuntimeMapsProviderRegistered()) {\r\n if (process.env.NODE_ENV === 'production') {\r\n missing.push(\r\n 'RuntimeMapsProvider — call `registerRuntimeMapsProvider({...})` from `server/prod/runtimeMaps.ts`. Without it, every api/sync request returns notFound.'\r\n );\r\n } else {\r\n getLogger().warn(\r\n '[LuckyStack] No RuntimeMapsProvider registered — api/sync requests will resolve to empty maps. Devkit hot-reload usually registers one automatically.',\r\n );\r\n }\r\n }\r\n\r\n //? Localized normalizer — without it, error responses degrade to\r\n //? errorCode-as-message (no i18n). Hard-fail in production; warn in dev.\r\n if (!isLocalizedNormalizerRegistered()) {\r\n if (process.env.NODE_ENV === 'production') {\r\n missing.push(\r\n 'LocalizedNormalizer — call `registerLocalizedNormalizer({...})` from your bootstrap. Without it, error response messages will be the raw errorCode (no i18n).'\r\n );\r\n } else {\r\n getLogger().warn(\r\n '[LuckyStack] No LocalizedNormalizer registered — error messages will pass through as the raw errorCode.',\r\n );\r\n }\r\n }\r\n\r\n if (missing.length === 0) return;\r\n\r\n const detail = missing.map((line, idx) => ` ${idx + 1}. ${line}`).join('\\n');\r\n throw new Error(\r\n [\r\n '[LuckyStack] Bootstrap incomplete — the following registrations are missing:',\r\n detail,\r\n '',\r\n 'See docs/ARCHITECTURE_PACKAGING.md (overlay layout) for the recommended bootstrap order.',\r\n ].join('\\n')\r\n );\r\n};\r\n","//? Production runtime-maps loader shipped by the framework so consumers\r\n//? don't have to maintain their own `server/prod/runtimeMaps.ts`. The\r\n//? consumer only supplies a `loadGenerated` callback because dynamic-import\r\n//? path resolution is module-scoped — the framework cannot resolve\r\n//? `./generatedApis.<preset>` on the consumer's behalf.\r\n//?\r\n//? Multiple presets can be loaded into a single process by passing a\r\n//? comma-separated list as the first positional argv (parsed by\r\n//? `@luckystack/server/parseArgv`). All resolved maps are shallow-merged\r\n//? into one runtime view; collisions throw at boot (services own one\r\n//? preset by design, see docs/ARCHITECTURE_PACKAGING.md §10).\r\n//?\r\n//? Dev branch lazy-imports `@luckystack/devkit` to pull the auto-discovered\r\n//? api/sync/function maps. Devkit is excluded from the production bundle, so\r\n//? this dynamic import is never reached when NODE_ENV === 'production'.\r\n\r\nimport {\r\n registerRuntimeMapsProvider,\r\n type RuntimeApiMapsResult,\r\n type RuntimeMapsProvider,\r\n type RuntimeSyncMapsResult,\r\n} from '@luckystack/core';\r\nimport { getParsedBundles } from './argv';\r\n\r\ntype RuntimeMapRecord = Record<string, unknown>;\r\n\r\ninterface LoadedRuntimeMaps {\r\n apisObject: RuntimeMapRecord;\r\n syncObject: RuntimeMapRecord;\r\n functionsObject: RuntimeMapRecord;\r\n}\r\n\r\ninterface DevkitRuntimeMaps {\r\n devApis: RuntimeMapRecord;\r\n devSyncs: RuntimeMapRecord;\r\n devFunctions: RuntimeMapRecord;\r\n}\r\n\r\nconst emptyRuntimeMaps: LoadedRuntimeMaps = {\r\n apisObject: {},\r\n syncObject: {},\r\n functionsObject: {},\r\n};\r\n\r\nconst isRuntimeMapRecord = (value: unknown): value is RuntimeMapRecord =>\r\n Boolean(value) && typeof value === 'object';\r\n\r\nconst normalizeGeneratedModule = (moduleValue: unknown): LoadedRuntimeMaps => {\r\n const moduleRecord = moduleValue && typeof moduleValue === 'object'\r\n ? (moduleValue as Record<string, unknown>)\r\n : {};\r\n\r\n const apiCandidate = moduleRecord.apis;\r\n const syncCandidate = moduleRecord.syncs;\r\n const functionCandidate = moduleRecord.functions;\r\n\r\n return {\r\n apisObject: isRuntimeMapRecord(apiCandidate) ? apiCandidate : {},\r\n syncObject: isRuntimeMapRecord(syncCandidate) ? syncCandidate : {},\r\n functionsObject: isRuntimeMapRecord(functionCandidate) ? functionCandidate : {},\r\n };\r\n};\r\n\r\nexport interface ProdRuntimeMapsLoaderOptions {\r\n /**\r\n * Dynamic-import callback for the generated maps module of a given preset.\r\n * Called once per preset per process lifetime; the result is cached.\r\n *\r\n * The resolved module must have shape `{ apis, syncs, functions }` (the\r\n * shape `scripts/generateServerRequests.ts` emits).\r\n *\r\n * Pass a function that calls `import()` with a path relative to YOUR\r\n * server-side module — the framework cannot resolve a relative path on\r\n * your behalf because dynamic-import resolution is module-scoped.\r\n *\r\n * @example\r\n * loadGenerated: (preset) => import(`./prod/generatedApis.${preset}`)\r\n */\r\n loadGenerated: (preset: string) => Promise<unknown>;\r\n /**\r\n * Override the preset(s) to load. Skips the argv lookup. Accepts a single\r\n * preset name or an array. Useful in tests or when the preset list comes\r\n * from a non-argv source.\r\n */\r\n preset?: string | string[];\r\n}\r\n\r\nconst resolvePresets = (options: ProdRuntimeMapsLoaderOptions): string[] => {\r\n const fromOptions = options.preset;\r\n if (typeof fromOptions === 'string' && fromOptions.length > 0) {\r\n return [fromOptions];\r\n }\r\n if (Array.isArray(fromOptions) && fromOptions.length > 0) {\r\n return [...new Set(fromOptions)];\r\n }\r\n const fromArgv = getParsedBundles();\r\n if (fromArgv.length > 0) {\r\n return fromArgv;\r\n }\r\n return ['default'];\r\n};\r\n\r\nconst mergeInto = (\r\n target: RuntimeMapRecord,\r\n source: RuntimeMapRecord,\r\n kind: 'api' | 'sync' | 'function',\r\n fromPreset: string,\r\n keyOrigin: Map<string, string>,\r\n): void => {\r\n for (const key of Object.keys(source)) {\r\n const previousPreset = keyOrigin.get(key);\r\n if (previousPreset !== undefined && previousPreset !== fromPreset) {\r\n throw new Error(\r\n `[luckystack:runtimeMaps] ${kind} key collision: \"${key}\" present in both ` +\r\n `preset \"${previousPreset}\" and preset \"${fromPreset}\". ` +\r\n `Services must belong to exactly one preset (see docs/ARCHITECTURE_PACKAGING.md §10).`,\r\n );\r\n }\r\n keyOrigin.set(key, fromPreset);\r\n target[key] = source[key];\r\n }\r\n};\r\n\r\n/**\r\n * Build a `RuntimeMapsProvider` that loads generated maps in production and\r\n * delegates to `@luckystack/devkit`'s discovery in dev. Same shape consumers\r\n * used to hand-roll in `server/prod/runtimeMaps.ts`.\r\n *\r\n * Call `registerRuntimeMapsProvider(...)` with the result, or use\r\n * `registerProdRuntimeMapsProvider(...)` (this module) to do both in one\r\n * step.\r\n */\r\nconst isProduction = (): boolean => process.env.NODE_ENV === 'production';\r\n\r\nexport const createProdRuntimeMapsProvider = (\r\n options: ProdRuntimeMapsLoaderOptions,\r\n): RuntimeMapsProvider => {\r\n let prodMapsPromise: Promise<LoadedRuntimeMaps> | null = null;\r\n\r\n let devkitModulePromise: Promise<DevkitRuntimeMaps> | null = null;\r\n const getDevkit = async (): Promise<DevkitRuntimeMaps> => {\r\n devkitModulePromise ??= import('@luckystack/devkit') as Promise<DevkitRuntimeMaps>;\r\n return await devkitModulePromise;\r\n };\r\n\r\n const loadProdRuntimeMaps = async (): Promise<LoadedRuntimeMaps> => {\r\n if (prodMapsPromise) return await prodMapsPromise;\r\n\r\n prodMapsPromise = (async () => {\r\n const presets = resolvePresets(options);\r\n const merged: LoadedRuntimeMaps = {\r\n apisObject: {},\r\n syncObject: {},\r\n functionsObject: {},\r\n };\r\n const apiOrigin = new Map<string, string>();\r\n const syncOrigin = new Map<string, string>();\r\n const functionOrigin = new Map<string, string>();\r\n\r\n const loadedModules = await Promise.all(\r\n presets.map(async (preset) => ({\r\n preset,\r\n mod: await options.loadGenerated(preset).catch(() => null),\r\n })),\r\n );\r\n\r\n let loadedAny = false;\r\n for (const { preset, mod } of loadedModules) {\r\n if (!mod) {\r\n console.warn(\r\n `[luckystack:runtimeMaps] preset \"${preset}\" failed to load — skipping. ` +\r\n `Calls owned by that preset will return notFound until the generated module resolves.`,\r\n );\r\n continue;\r\n }\r\n loadedAny = true;\r\n const normalized = normalizeGeneratedModule(mod);\r\n mergeInto(merged.apisObject, normalized.apisObject, 'api', preset, apiOrigin);\r\n mergeInto(merged.syncObject, normalized.syncObject, 'sync', preset, syncOrigin);\r\n mergeInto(merged.functionsObject, normalized.functionsObject, 'function', preset, functionOrigin);\r\n }\r\n\r\n if (!loadedAny) {\r\n console.warn(\r\n `[luckystack:runtimeMaps] no presets resolved (tried: ${presets.join(', ')}). ` +\r\n `Every api/sync request will return notFound until at least one generated module loads.`,\r\n );\r\n return emptyRuntimeMaps;\r\n }\r\n\r\n return merged;\r\n })();\r\n\r\n return await prodMapsPromise;\r\n };\r\n\r\n const getRuntimeApiMaps = async (): Promise<RuntimeApiMapsResult> => {\r\n if (!isProduction()) {\r\n const { devApis, devFunctions } = await getDevkit();\r\n return {\r\n apisObject: isRuntimeMapRecord(devApis) ? devApis : {},\r\n functionsObject: isRuntimeMapRecord(devFunctions) ? devFunctions : {},\r\n };\r\n }\r\n const { apisObject, functionsObject } = await loadProdRuntimeMaps();\r\n return { apisObject, functionsObject };\r\n };\r\n\r\n const getRuntimeSyncMaps = async (): Promise<RuntimeSyncMapsResult> => {\r\n if (!isProduction()) {\r\n const { devSyncs, devFunctions } = await getDevkit();\r\n return {\r\n syncObject: isRuntimeMapRecord(devSyncs) ? devSyncs : {},\r\n functionsObject: isRuntimeMapRecord(devFunctions) ? devFunctions : {},\r\n };\r\n }\r\n const { syncObject, functionsObject } = await loadProdRuntimeMaps();\r\n return { syncObject, functionsObject };\r\n };\r\n\r\n return { getRuntimeApiMaps, getRuntimeSyncMaps };\r\n};\r\n\r\n/**\r\n * Convenience wrapper around `createProdRuntimeMapsProvider` +\r\n * `registerRuntimeMapsProvider`. Most consumers want this — pass the\r\n * loader callback once and the runtime is wired.\r\n */\r\nexport const registerProdRuntimeMapsProvider = (\r\n options: ProdRuntimeMapsLoaderOptions,\r\n): RuntimeMapsProvider => {\r\n const provider = createProdRuntimeMapsProvider(options);\r\n registerRuntimeMapsProvider(provider);\r\n return provider;\r\n};\r\n","//? Positional argv parser for the LuckyStack server boot. Replaces\r\n//? `LUCKYSTACK_BUNDLE` + `SERVER_PORT` env-var reads with a single shape:\r\n//?\r\n//? npm run server -- billing,vehicles 4001\r\n//?\r\n//? Arg 0 = comma-separated preset list (runtime maps merged across them).\r\n//? Arg 1 = listen port (numeric).\r\n//?\r\n//? `applyServerArgv()` is called by the side-effect module\r\n//? `@luckystack/server/parseArgv`, which consumers import as the FIRST line\r\n//? of their `server.ts` so the parsed port lands in `process.env.SERVER_PORT`\r\n//? before `config.ts` (top-level `backendUrl` const) is evaluated.\r\n\r\nexport interface ParsedServerArgv {\r\n bundles: string[];\r\n port: number | null;\r\n}\r\n\r\nlet parsedBundles: string[] = [];\r\nlet parsedPort: number | null = null;\r\nlet hasRun = false;\r\n\r\nconst PORT_PATTERN = /^\\d+$/;\r\n\r\nexport const parseServerArgv = (argv: string[]): ParsedServerArgv => {\r\n if (argv.length > 2) {\r\n throw new Error(\r\n `[luckystack:argv] unexpected positional argument(s): \"${argv.slice(2).join(' ')}\". ` +\r\n `Usage: npm run server -- <bundle[,bundle...]> [port]`,\r\n );\r\n }\r\n\r\n const bundles = argv[0] && argv[0].length > 0\r\n ? [...new Set(argv[0].split(',').map((s) => s.trim()).filter(Boolean))]\r\n : [];\r\n\r\n let port: number | null = null;\r\n const portArg = argv[1];\r\n if (portArg !== undefined) {\r\n if (!PORT_PATTERN.test(portArg)) {\r\n throw new Error(\r\n `[luckystack:argv] port argument must be numeric, got: \"${portArg}\". ` +\r\n `Usage: npm run server -- <bundle[,bundle...]> [port]`,\r\n );\r\n }\r\n port = Number.parseInt(portArg, 10);\r\n }\r\n\r\n return { bundles, port };\r\n};\r\n\r\nexport const applyServerArgv = (): void => {\r\n if (hasRun) return;\r\n hasRun = true;\r\n\r\n const result = parseServerArgv(process.argv.slice(2));\r\n parsedBundles = result.bundles;\r\n parsedPort = result.port;\r\n\r\n //? Writeback so the downstream env-readers (`core/env.ts` Zod schema,\r\n //? `core/bindAddress.ts` fallback, consumer `config.ts` backendUrl,\r\n //? `oauthProviders.ts` callback URL) see the resolved port without us\r\n //? having to refactor those four call sites. This is a deliberate\r\n //? implementation detail — argv is the public source of truth.\r\n if (parsedPort !== null) {\r\n process.env.SERVER_PORT = String(parsedPort);\r\n }\r\n};\r\n\r\nexport const getParsedBundles = (): string[] => parsedBundles;\r\nexport const getParsedPort = (): number | null => parsedPort;\r\n","//? One-call bootstrap helper. Auto-imports the consumer's `luckystack/`\r\n//? overlay folder in the canonical order, then starts the server.\r\n//?\r\n//? The convention is documented in `docs/ARCHITECTURE_PACKAGING.md`. Each\r\n//? overlay file calls one of the framework's `register*` functions at module\r\n//? load (side-effect import), so by the time `createLuckyStackServer` runs\r\n//? the registries are populated.\r\n//?\r\n//? Order matters because some registries depend on others (e.g. login's\r\n//? userAdapter needs the Prisma client registered first). The framework\r\n//? imports them in topological order.\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport { pathToFileURL } from 'node:url';\r\nimport { ROOT_DIR } from '@luckystack/core';\r\nimport { createLuckyStackServer } from './createServer';\r\nimport type {\r\n CreateLuckyStackServerOptions,\r\n RunningLuckyStackServer,\r\n} from './types';\r\n\r\nexport interface BootstrapLuckyStackOptions extends CreateLuckyStackServerOptions {\r\n /**\r\n * Folder name (relative to project root) that contains the overlay files.\r\n * Default: `luckystack`. Each subfolder mirrors a framework package and\r\n * holds the project's overrides for that package.\r\n */\r\n overlayRoot?: string;\r\n /**\r\n * Skip auto-loading the overlay folder. Useful for tests that build the\r\n * registries by hand.\r\n */\r\n skipOverlayLoad?: boolean;\r\n}\r\n\r\nconst OVERLAY_ORDER = [\r\n // Core registries first — clients, paths, routing rules. Anything below\r\n // depends on these being in place.\r\n 'core',\r\n // Deploy + services topology — needed by the router but harmless for\r\n // single-instance deploys.\r\n 'deploy',\r\n // Auth — OAuth providers + user adapter sit on top of core.\r\n 'login',\r\n // Transactional email sender registration (optional @luckystack/email).\r\n 'email',\r\n // Sentry / docs-ui / presence sit on top of core but don't block boot.\r\n 'sentry',\r\n 'presence',\r\n 'docs-ui',\r\n // Server overlay last — typically empty, but a place to wire framework\r\n // hooks (`registerHook('onSocketConnect', ...)` etc.) before listen().\r\n 'server',\r\n];\r\n\r\nconst importIfExists = async (filePath: string): Promise<void> => {\r\n if (!fs.existsSync(filePath)) return;\r\n await import(pathToFileURL(filePath).href);\r\n};\r\n\r\nconst loadOverlayFolder = async (overlayRoot: string): Promise<void> => {\r\n const overlayAbs = path.isAbsolute(overlayRoot)\r\n ? overlayRoot\r\n : path.join(ROOT_DIR, overlayRoot);\r\n\r\n if (!fs.existsSync(overlayAbs)) {\r\n //? No overlay folder is fine — projects can register everything from a\r\n //? single `config.ts` if they prefer the legacy layout.\r\n return;\r\n }\r\n\r\n for (const packageName of OVERLAY_ORDER) {\r\n const packageDir = path.join(overlayAbs, packageName);\r\n if (!fs.existsSync(packageDir)) continue;\r\n\r\n //? Load `index.ts` first if present, then any other `*.ts` files in\r\n //? alphabetical order. Each file is responsible for its own\r\n //? side-effect registration.\r\n const indexCandidates = ['index.ts', 'index.js'];\r\n for (const candidate of indexCandidates) {\r\n await importIfExists(path.join(packageDir, candidate));\r\n }\r\n\r\n const entries = fs.readdirSync(packageDir).toSorted();\r\n for (const entry of entries) {\r\n if (indexCandidates.includes(entry)) continue;\r\n if (!entry.endsWith('.ts') && !entry.endsWith('.js')) continue;\r\n await importIfExists(path.join(packageDir, entry));\r\n }\r\n }\r\n};\r\n\r\nexport const bootstrapLuckyStack = async (\r\n options: BootstrapLuckyStackOptions = {}\r\n): Promise<RunningLuckyStackServer> => {\r\n const overlayRoot = options.overlayRoot ?? 'luckystack';\r\n\r\n if (!options.skipOverlayLoad) {\r\n await loadOverlayFolder(overlayRoot);\r\n }\r\n\r\n const server = await createLuckyStackServer(options);\r\n return server;\r\n};\r\n","//? Backward-compatibility re-export. The error-formatter registry moved\r\n//? to @luckystack/core so transport handlers in @luckystack/api and\r\n//? @luckystack/sync can dispatch per-route + global formatters without\r\n//? depending on this server package (which would cycle). Consumers'\r\n//? existing `import { registerErrorFormatter } from '@luckystack/server'`\r\n//? continues to resolve through this shim.\r\n\r\nexport {\r\n registerErrorFormatter,\r\n getErrorFormatter,\r\n applyErrorFormatter,\r\n} from '@luckystack/core';\r\nexport type { ErrorFormatter, ErrorFormatterContext } from '@luckystack/core';\r\n"],"mappings":"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","names":["getLogger","getProjectConfig","tryCatch","dispatchHook","extractTokenFromRequest","getLogger","getProjectConfig","getSession","getProjectConfig","getSession","getProjectConfig","getProjectConfig","redis","tryCatch","dispatchHook","getProjectConfig","getOAuthProviders","getLogger","getProjectConfig","deleteSession","dispatchHook","getLogger","tryCatch","getProjectConfig","tryCatch","getLogger","dispatchHook","captureException","dispatchHook","extractTokenFromRequest","getLogger","tryCatch","tryCatch","extractTokenFromRequest","getLogger","captureException","dispatchHook","captureException","getLogger","tryCatch","tryCatch","getLogger","captureException","http","getProjectConfig","getLogger","handlers","getSession","dispatchHook","extractTokenFromRequest","allowedOrigin","getLogger","getProjectConfig","dispatchHook","tryCatch","getSession","getLogger","getOAuthProviders","tryCatch","getProjectConfig","getLogger","path","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/createServer.ts","../src/httpHandler.ts","../src/logSanitize.ts","../src/securityHeadersRegistry.ts","../src/httpRoutes/csrfMiddleware.ts","../src/capabilities.ts","../src/originExemptRegistry.ts","../src/httpRoutes/timingSafeEqual.ts","../src/httpRoutes/csrfRoute.ts","../src/httpRoutes/faviconRoute.ts","../src/httpRoutes/healthRoutes.ts","../src/httpRoutes/testResetRoute.ts","../src/httpRoutes/uploadsRoute.ts","../src/httpRoutes/authApiRoute.ts","../src/httpRoutes/sessionCookie.ts","../src/httpRoutes/authLogoutRoute.ts","../src/httpRoutes/authProvidersRoute.ts","../src/httpRoutes/authCallbackRoute.ts","../src/httpRoutes/apiRoute.ts","../src/sse.ts","../src/httpRoutes/resolveRequesterIp.ts","../src/httpRoutes/syncRoute.ts","../src/httpRoutes/customRoutes.ts","../src/customRoutesRegistry.ts","../src/httpRoutes/staticRoutes.ts","../src/loadSocket.ts","../src/verifyBootstrap.ts","../src/runtimeMapsLoader.ts","../src/stopServer.ts","../src/bootstrap.ts","../src/errorFormatterRegistry.ts"],"sourcesContent":["import http, { type Server as HttpServer } from 'node:http';\nimport { registerBindAddress, writeBootUuid, getLogger, getProjectConfig, tryCatch } from '@luckystack/core';\nimport { handleHttpRequest } from './httpHandler';\nimport { loadSocket } from './loadSocket';\nimport { verifyBootstrap } from './verifyBootstrap';\nimport { registerProdRuntimeMapsProvider } from './runtimeMapsLoader';\nimport { getParsedPort } from './argv';\nimport { runGracefulShutdown } from './stopServer';\nimport type {\n CreateLuckyStackServerOptions,\n RunningLuckyStackServer,\n StopLuckyStackServerOptions,\n} from './types';\n\n/**\n * One-call server bootstrap for a LuckyStack project.\n *\n * Wires together:\n * - HTTP server with framework routes (`/api/*`, `/sync/*`, `/_health`,\n * `/_test/reset`, `/uploads/*`, `/auth/api`, `/auth/callback`)\n * - Socket.io server with the Redis adapter, room handlers, presence\n * integration, location sync\n * - Boot-UUID write so the router's handshake can verify topology\n * - Optional dev-mode tooling (devkit hot reload + REPL)\n *\n * Project responsibilities (passed in as options):\n * - `serveFile` / `serveFavicon` — your project's static file handlers\n * - `customRoutes` — any additional HTTP routes your app needs\n *\n * Pre-conditions:\n * - `registerProjectConfig(...)` must have run (side-effect import of your\n * `config.ts` does this).\n * - `registerDeployConfig(...)` must have run (side-effect import of your\n * `deploy.config.ts`).\n * - `registerRuntimeMapsProvider(...)` must have run (side-effect import of\n * your `server/prod/runtimeMaps.ts`).\n * - `registerLocalizedNormalizer(...)` must have run (side-effect import of\n * your `server/utils/responseNormalizer.ts`).\n *\n * @example\n * ```ts\n * import './config';\n * import './deploy.config';\n * import './prod/runtimeMaps';\n * import './utils/responseNormalizer';\n * import { createLuckyStackServer } from '@luckystack/server';\n * import { serveFile, serveFavicon } from './prod/serveFile';\n *\n * const server = await createLuckyStackServer({ serveFile, serveFavicon });\n * await server.listen();\n * ```\n */\n//? Extracted verbatim from the previous inline `if (enableDevTools) { ... }`\n//? block. Same dynamic imports, same ordering, same SIGINT/SIGTERM handlers —\n//? hoisted so the bootstrap function reads as a sequence of named steps.\nconst initDevTools = async (): Promise<void> => {\n //? Dev-only: console-log color tagger + devkit hot reload + REPL.\n //? Kept dynamic so tier-A consumers in production never load the\n //? typescript compiler API or chokidar's filesystem watchers.\n const { initConsolelog } = await import('@luckystack/core');\n initConsolelog();\n const devkitModuleId = '@luckystack/devkit';\n const devkit = (await import(devkitModuleId)) as {\n initializeAll: () => Promise<void>;\n setupWatchers: () => void;\n };\n await devkit.initializeAll();\n devkit.setupWatchers();\n //? Belt-and-braces: explicit SIGINT/SIGTERM handler so Ctrl+C is honored\n //? even if a sync CPU burst (TS Program build, large require chain) is\n //? still in flight when the signal arrives.\n process.once('SIGINT', () => process.exit(0));\n process.once('SIGTERM', () => process.exit(0));\n};\n\n//? Extracted verbatim from the previous inline `listen` closure. Same\n//? auto-increment opt-in, same EADDRINUSE retry, same truthful-failure\n//? logging, same success log + callback + resolve. `httpServer`, `ip`, and\n//? `port` are threaded in as parameters instead of closed over.\nexport const listenLuckyStackServer = (\n httpServer: HttpServer,\n ip: string,\n port: string | number,\n callback?: () => void,\n): Promise<HttpServer> =>\n new Promise<HttpServer>((resolve, reject) => {\n const startPort = typeof port === 'string' ? Number.parseInt(port, 10) : port;\n //? Opt-in: only auto-pick the next free port when explicitly enabled.\n //? Off by default because `SERVER_PORT` also drives `config.ts`'s\n //? `backendOrigin` / OAuth callback base and the Vite dev proxy target —\n //? silently moving the listen port would leave the frontend talking to the\n //? old one. Safe to enable for standalone / `npm run cluster` use where\n //? nothing else hardcodes the port.\n const autoIncrement = ['1', 'true'].includes(\n (process.env.SERVER_PORT_AUTO_INCREMENT ?? '').toLowerCase(),\n );\n\n const tryListen = (attemptPort: number): void => {\n const onError = (err: NodeJS.ErrnoException): void => {\n if (err.code !== 'EADDRINUSE') {\n reject(err);\n return;\n }\n if (autoIncrement) {\n getLogger().warn(\n `Port ${String(attemptPort)} is in use — trying ${String(attemptPort + 1)} (SERVER_PORT_AUTO_INCREMENT=1)`,\n );\n tryListen(attemptPort + 1);\n return;\n }\n //? Truthful failure. The old code unconditionally logged \"running on\n //? :<port>\" inside the listen callback even when the bind never\n //? succeeded, so an in-use port looked like a healthy boot. Surface\n //? the real problem and the two ways out instead.\n getLogger().error(\n `Port ${String(attemptPort)} is already in use — the server did NOT start. ` +\n `Another \\`npm run server\\` is probably still running (stop it), or set ` +\n `SERVER_PORT to a free port, or set SERVER_PORT_AUTO_INCREMENT=1 to auto-pick the next free port.`,\n );\n reject(err);\n };\n\n httpServer.once('error', onError);\n httpServer.listen(attemptPort, ip, () => {\n httpServer.off('error', onError);\n const config = getProjectConfig();\n if (config.logging.socketStartup || config.logging.devLogs) {\n getLogger().info(`Server is running on http://${ip}:${String(attemptPort)}/`);\n }\n callback?.();\n resolve(httpServer);\n });\n };\n\n tryListen(startPort);\n });\n\nexport const createLuckyStackServer = async (\n options: CreateLuckyStackServerOptions = {}\n): Promise<RunningLuckyStackServer> => {\n //? Auto-register the framework-shipped runtime maps provider when the\n //? consumer supplied a `loadGeneratedMaps` callback. Runs before\n //? `verifyBootstrap` so the registration counts toward the boot check.\n //? Consumers who hand-rolled their own provider via\n //? `registerRuntimeMapsProvider` can simply omit `loadGeneratedMaps` and\n //? the framework leaves their registration alone.\n if (options.loadGeneratedMaps) {\n registerProdRuntimeMapsProvider({\n loadGenerated: options.loadGeneratedMaps,\n preset: options.runtimeMapsPreset,\n });\n }\n\n //? Fail fast if a project's overlay forgot to register a critical piece.\n //? Surface a single readable error instead of a stack trace deep inside\n //? a request handler.\n await verifyBootstrap({\n requireDeployConfig: options.requireDeployConfig,\n requireServicesConfig: options.requireServicesConfig,\n requireOAuthProviders: options.requireOAuthProviders,\n });\n\n const port = options.port ?? getParsedPort() ?? process.env.SERVER_PORT ?? 80;\n const ip = options.ip ?? process.env.SERVER_IP ?? '127.0.0.1';\n const enableDevTools = options.enableDevTools ?? process.env.NODE_ENV !== 'production';\n\n //? Register the resolved bind address so framework code that needs it\n //? (e.g. `checkOrigin` building the same-origin entry) doesn't drift when\n //? the consumer passed `options.ip`/`options.port` without also setting\n //? the legacy `SERVER_IP`/`SERVER_PORT` env vars.\n registerBindAddress({\n ip,\n port: typeof port === 'string' ? Number.parseInt(port, 10) : port,\n });\n\n if (enableDevTools) {\n await initDevTools();\n }\n\n //? Boot UUID must be written before /_health can answer truthfully. Router\n //? boot handshake consumes /_health to verify shared-Redis topology. A\n //? failure here is almost always Redis being unreachable or misconfigured\n //? (bad credentials, wrong host); throw a clear, actionable error so boot\n //? halts on THIS message instead of the raw ioredis `ReplyError` dump that\n //? confused operators before. Library code must not `process.exit()`; the\n //? throw propagates to the boot entry and the dev supervisor respawns.\n //? `tryCatch` already captured the underlying error to the error tracker.\n const [bootUuidError] = await tryCatch(() => writeBootUuid());\n if (bootUuidError) {\n throw new Error(\n 'Failed to write the boot UUID to Redis. Check REDIS_HOST / REDIS_PORT / REDIS_USER / REDIS_PASSWORD and that Redis is reachable.',\n { cause: bootUuidError },\n );\n }\n\n const httpServer: HttpServer = http.createServer((req, res) => {\n void handleHttpRequest(req, res, options);\n });\n\n //? Persistent error listener. `listenLuckyStackServer` attaches a ONE-OFF\n //? `error` handler only around the bind attempt; once listening, an async\n //? socket error (e.g. EMFILE under load, an abrupt peer reset surfacing at the\n //? server level) would otherwise be an unhandled `'error'` event and crash the\n //? process. Log it instead so the server stays up and the cause is visible.\n httpServer.on('error', (err: NodeJS.ErrnoException) => {\n //? EADDRINUSE during bind is owned by the one-off handler in\n //? `listenLuckyStackServer` (which rejects/retries); don't double-log it.\n if (err.code === 'EADDRINUSE') return;\n getLogger().error('[http-server] runtime error', err);\n });\n\n const { io: ioServer, adapterClients } = loadSocket(httpServer, {\n maxHttpBufferSize: options.maxHttpBufferSize,\n });\n\n const listen = (callback?: () => void): Promise<HttpServer> =>\n listenLuckyStackServer(httpServer, ip, port, callback);\n\n //? Idempotent graceful shutdown. A second call (e.g. SIGINT then SIGTERM, or a\n //? programmatic `stop()` racing a signal) returns the in-flight promise rather\n //? than running the teardown twice.\n let shutdownPromise: Promise<void> | null = null;\n const stop = (stopOptions: StopLuckyStackServerOptions = {}): Promise<void> => {\n shutdownPromise ??= runGracefulShutdown({ httpServer, ioServer, adapterClients }, stopOptions);\n return shutdownPromise;\n };\n\n //? Production signal wiring (MIS-016). In dev, `initDevTools` already installs\n //? fast `process.exit(0)` handlers (hot-reload supervisor restarts). In prod\n //? we run the FULL graceful shutdown and exit only after it settles — an\n //? orchestrator's SIGTERM should drain connections + flush trackers, not hard-\n //? kill. `process.once` so a repeated signal doesn't stack handlers; the\n //? `stop()` idempotency covers a SIGINT-then-SIGTERM sequence.\n if (!enableDevTools) {\n const handleSignal = (reason: 'SIGTERM' | 'SIGINT'): void => {\n void (async () => {\n await stop({ reason });\n //? Terminate AFTER the graceful drain completes. This is the process\n //? entry's signal handler (not deep library code) — an orchestrator's\n //? SIGTERM expects the process to exit once it has drained, so exiting\n //? here is correct. Mirrors the dev handlers in `initDevTools`.\n // eslint-disable-next-line unicorn/no-process-exit -- top-level signal handler, exits after graceful drain\n process.exit(0);\n })();\n };\n process.once('SIGTERM', () => { handleSignal('SIGTERM'); });\n process.once('SIGINT', () => { handleSignal('SIGINT'); });\n }\n\n return { httpServer, ioServer, listen, stop, close: stop };\n};\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport {\n allowedOrigin,\n dispatchHook,\n extractTokenFromRequest,\n getLogger,\n getParams,\n getProjectConfig,\n hasCookie,\n readSession,\n tryCatchSync,\n} from '@luckystack/core';\nimport { sanitizeForLog } from './logSanitize';\nimport { getSecurityHeadersBuilder } from './securityHeadersRegistry';\nimport type { CreateLuckyStackServerOptions } from './types';\nimport { enforceCsrfOnStateChangingRequest } from './httpRoutes/csrfMiddleware';\nimport { handleCsrfRoute } from './httpRoutes/csrfRoute';\nimport { handleFaviconRoute } from './httpRoutes/faviconRoute';\nimport { handleHealthRoute, handleLivezRoute, handleReadyzRoute } from './httpRoutes/healthRoutes';\nimport { handleTestResetRoute } from './httpRoutes/testResetRoute';\nimport { handleUploadsRoute } from './httpRoutes/uploadsRoute';\nimport { handleAuthApiRoute } from './httpRoutes/authApiRoute';\nimport { handleAuthLogoutRoute } from './httpRoutes/authLogoutRoute';\nimport { handleAuthProvidersRoute } from './httpRoutes/authProvidersRoute';\nimport { handleAuthCallbackRoute } from './httpRoutes/authCallbackRoute';\nimport { handleApiRoute } from './httpRoutes/apiRoute';\nimport { handleSyncRoute } from './httpRoutes/syncRoute';\nimport { handleCustomRoutes, handlePreParamsCustomRoutes } from './httpRoutes/customRoutes';\nimport { handleStaticAndSpaFallback } from './httpRoutes/staticRoutes';\nimport { isOriginExemptPath } from './originExemptRegistry';\nimport { resolveCookieSecure } from './httpRoutes/sessionCookie';\nimport type { HttpRouteContext, HttpRouteHandler } from './httpRoutes/types';\n\nconst buildSessionCookieOptions = (\n sessionExpiryDays: number,\n secure: boolean,\n http: ReturnType<typeof getProjectConfig>['http'],\n): string =>\n `HttpOnly; SameSite=${http.sessionCookieSameSite}; Path=${http.sessionCookiePath}; Max-Age=${60 * 60 * 24 * sessionExpiryDays}; ${secure ? 'Secure;' : ''}`;\n\nconst setSecurityHeaders = (req: IncomingMessage, res: ServerResponse, origin: string) => {\n const { cors, securityHeaders } = getProjectConfig().http;\n res.setHeader('Access-Control-Allow-Origin', origin);\n res.setHeader('Access-Control-Allow-Methods', cors.allowedMethods);\n res.setHeader('Access-Control-Allow-Headers', cors.allowedHeaders);\n res.setHeader('Access-Control-Expose-Headers', cors.exposedHeaders);\n if (cors.credentials) {\n res.setHeader('Access-Control-Allow-Credentials', 'true');\n }\n res.setHeader('Referrer-Policy', securityHeaders.referrerPolicy);\n res.setHeader('X-Frame-Options', securityHeaders.frameOptions);\n res.setHeader('X-XSS-Protection', securityHeaders.xssProtection);\n res.setHeader('X-Content-Type-Options', securityHeaders.contentTypeOptions);\n\n //? Consumer-registered builder runs AFTER framework defaults so it can\n //? override (CSP, HSTS, Permissions-Policy) or extend. Errors fall\n //? through to defaults so a buggy builder can't kill response delivery.\n const builder = getSecurityHeadersBuilder();\n if (builder) {\n //? Wrap both the builder call AND the header writes so a buggy builder (or\n //? an invalid header name/value it returns) can't kill response delivery —\n //? same guarded scope as the original raw try/catch, just via tryCatchSync.\n const [error] = tryCatchSync(() => {\n const custom = builder(req);\n if (custom) {\n for (const [name, value] of Object.entries(custom)) {\n res.setHeader(name, value);\n }\n }\n });\n if (error) {\n getLogger().warn('securityHeadersBuilder threw — falling back to defaults', { err: error });\n }\n }\n};\n\n//? Routes that run BEFORE params parsing — they don't need (and shouldn't\n//? consume) the request body. The framework fast-paths run first; consumer\n//? `'pre-params'` custom routes (webhooks / streaming uploads) run last, after\n//? the probes but before `getParams` drains the body.\nconst PRE_PARAMS_ROUTES: HttpRouteHandler[] = [\n handleCsrfRoute,\n handleFaviconRoute,\n handleLivezRoute,\n handleReadyzRoute,\n handleHealthRoute,\n handleTestResetRoute,\n handleAuthProvidersRoute,\n handleAuthLogoutRoute,\n handlePreParamsCustomRoutes,\n];\n\n//? Routes that run AFTER params parsing.\nconst POST_PARAMS_ROUTES: HttpRouteHandler[] = [\n handleUploadsRoute,\n handleAuthApiRoute,\n handleAuthCallbackRoute,\n handleApiRoute,\n handleSyncRoute,\n handleCustomRoutes,\n handleStaticAndSpaFallback,\n];\n\nconst dispatchRoutes = async (handlers: HttpRouteHandler[], ctx: HttpRouteContext): Promise<boolean> => {\n for (const handler of handlers) {\n const handled = await handler(ctx);\n if (handled || ctx.res.writableEnded) return true;\n }\n return false;\n};\n\nconst enforceOriginPolicy = (\n req: IncomingMessage,\n res: ServerResponse,\n routePath: string,\n): { origin: string; rejected: boolean } => {\n //? Do NOT fall back to `req.headers.host` — host always equals the bound\n //? origin so non-browser callers (curl, native apps) would silently bypass\n //? `allowedOrigin()`. Only browsers attach Origin/Referer.\n const origin = req.headers.origin ?? req.headers.referer ?? '';\n const isStateChangingMethod = req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS';\n\n //? Registered webhook / server-to-server prefixes opt out of the browser\n //? origin gate — they authenticate via signature/HMAC in the handler, not via\n //? Origin (which they never send). Empty by default; opt-in only. The handler\n //? is still responsible for verifying the caller. See originExemptRegistry.\n if (isOriginExemptPath(routePath)) {\n return { origin, rejected: false };\n }\n\n if (!origin) {\n //? No browser-attributable origin: fail-close on state-changing methods,\n //? allow read-only (GET/HEAD/OPTIONS) so health probes and asset fetches\n //? from non-browser tooling continue to work.\n if (isStateChangingMethod) {\n res.statusCode = 403;\n res.setHeader('Content-Type', 'text/plain');\n res.end('Forbidden');\n return { origin, rejected: true };\n }\n } else if (!allowedOrigin(origin)) {\n res.statusCode = 403;\n res.setHeader('Content-Type', 'text/plain');\n res.end('Forbidden');\n return { origin, rejected: true };\n }\n return { origin, rejected: false };\n};\n\nconst refreshSessionCookieIfPresent = async ({\n req,\n res,\n token,\n sessionCookieName,\n sessionCookieOptions,\n}: {\n req: IncomingMessage;\n res: ServerResponse;\n token: string | null;\n sessionCookieName: string;\n sessionCookieOptions: string;\n}) => {\n const hasTokenCookie = hasCookie(req.headers.cookie, sessionCookieName);\n if (!hasTokenCookie || !token) return;\n const currentSession = await readSession(token);\n if (currentSession?.id) {\n //? Sliding expiration in cookie mode: keep browser token lifetime\n //? aligned with Redis TTL.\n res.setHeader('Set-Cookie', `${sessionCookieName}=${token}; ${sessionCookieOptions}`);\n }\n};\n\nconst parseRequestParams = async ({\n req,\n res,\n method,\n routePath,\n queryString,\n requestId,\n shouldLogDev,\n}: {\n req: IncomingMessage;\n res: ServerResponse;\n method: 'GET' | 'POST' | 'PUT' | 'DELETE';\n routePath: string;\n queryString: string | undefined;\n requestId: string;\n shouldLogDev: boolean;\n}): Promise<object | null> => {\n let params: object | null = await getParams({ method, req, res, queryString });\n if (res.writableEnded) return null;\n\n if (params && typeof params === 'object' && Object.keys(params).length > 0) {\n if (shouldLogDev) {\n getLogger().debug(`[${requestId}] ${method} ${routePath}`, {\n params: sanitizeForLog(params) as Record<string, unknown>,\n });\n }\n } else {\n if (shouldLogDev) {\n getLogger().debug(`[${requestId}] ${method} ${routePath}`);\n }\n params = {};\n }\n return params;\n};\n\nexport const handleHttpRequest = async (\n req: IncomingMessage,\n res: ServerResponse,\n options: CreateLuckyStackServerOptions\n): Promise<void> => {\n const config = getProjectConfig();\n const shouldLogDev = config.logging.devLogs;\n const sessionCookieName = config.http.sessionCookieName;\n const sessionCookieOptions = buildSessionCookieOptions(\n config.session.expiryDays,\n //? Honor the explicit `http.sessionCookieSecure` override (CORE-39), else the\n //? `SECURE` env flag — shared with the OAuth state cookie via\n //? `resolveCookieSecure` so the two can never drift (WAVE4).\n resolveCookieSecure(config.http.sessionCookieSecure, process.env.SECURE),\n config.http,\n );\n\n //? Parse the path up-front so the origin gate can consult the exempt-path\n //? registry (registered webhooks) before it would otherwise 403 a\n //? header-less, state-changing request.\n const url = req.url ?? '/';\n const [routePathRaw, queryStringRaw] = url.split('?');\n const routePath = routePathRaw ?? '/';\n const queryString = queryStringRaw ?? '';\n\n const { origin, rejected } = enforceOriginPolicy(req, res, routePath);\n if (rejected) return;\n\n setSecurityHeaders(req, res, origin);\n\n //? Honor an incoming x-request-id (idempotent for proxies/retries) or\n //? generate a fresh UUID. Echo back as a response header so client-side\n //? logs and Sentry can correlate.\n const incomingRequestId = req.headers['x-request-id'];\n const requestId = (Array.isArray(incomingRequestId) ? incomingRequestId[0] : incomingRequestId) ?? randomUUID();\n res.setHeader('X-Request-Id', requestId);\n\n //? `preHttpRequest` fires before any route dispatch. Use to instrument\n //? requests (latency timer, audit log), enforce IP allow-lists, or stop\n //? specific paths with a custom error. Header subset excludes auth/cookie.\n const safeHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (k === 'authorization' || k === 'cookie' || k === 'set-cookie' || k === 'x-csrf-token') continue;\n safeHeaders[k] = Array.isArray(v) ? v.join(', ') : (v ?? '');\n }\n const preHttpResult = await dispatchHook('preHttpRequest', {\n method: req.method?.toUpperCase() ?? 'GET',\n url: req.url ?? '/',\n requestId,\n origin,\n headers: safeHeaders,\n });\n if (preHttpResult.stopped) {\n res.statusCode = preHttpResult.signal.httpStatus ?? 403;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: preHttpResult.signal.errorCode }));\n return;\n }\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const method = req.method;\n\n if (method !== 'GET' && method !== 'POST' && method !== 'PUT' && method !== 'DELETE') {\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain');\n res.end(`method: ${String(method)} not supported, use one of: GET, POST, PUT, DELETE`);\n return;\n }\n\n const token = extractTokenFromRequest(req);\n await refreshSessionCookieIfPresent({ req, res, token, sessionCookieName, sessionCookieOptions });\n\n const csrfRejected = await enforceCsrfOnStateChangingRequest({ req, res, routePath, token, requestId });\n if (csrfRejected) return;\n\n const baseCtx: Omit<HttpRouteContext, 'params'> = {\n req,\n res,\n options,\n routePath,\n queryString,\n method,\n token,\n requestId,\n sessionCookieOptions,\n };\n\n //? Route /auth/csrf, health probes, _test/reset, favicon — all fast paths\n //? that should not consume the request body.\n if (await dispatchRoutes(PRE_PARAMS_ROUTES, { ...baseCtx, params: {} })) return;\n\n const params = await parseRequestParams({\n req, res, method, routePath, queryString, requestId, shouldLogDev,\n });\n if (params === null) return;\n\n await dispatchRoutes(POST_PARAMS_ROUTES, { ...baseCtx, params });\n};\n","//? Recursive log sanitizer: never log known-sensitive keys. Reads the\n//? extensible redacted-key registry from core so feature packages can register\n//? their own domain-specific keys (password, apiKey, mrn, etc.) at boot.\n\nimport { getProjectConfig, isRedactedLogKey } from '@luckystack/core';\n\nconst REDACTED_PLACEHOLDER = '[REDACTED]';\nconst TRUNCATED_PLACEHOLDER = '[TRUNCATED]';\n\n//? Bound recursion so an attacker-supplied deeply-nested JSON body (within the\n//? request body cap) cannot stack-overflow the dev-log sanitizer. Past this\n//? depth we emit a sentinel instead of recursing further.\nconst MAX_SANITIZE_DEPTH = 40;\n\nconst isRedactedKey = (key: string): boolean => {\n if (isRedactedLogKey(key)) return true;\n return key.toLowerCase() === getProjectConfig().http.sessionCookieName.toLowerCase();\n};\n\nconst sanitizeWithGuards = (value: unknown, depth: number, seen: WeakSet<object>): unknown => {\n if (value === null || typeof value !== 'object') return value;\n //? Cycle guard: a self-referential object would otherwise recurse forever.\n if (seen.has(value)) return TRUNCATED_PLACEHOLDER;\n if (depth >= MAX_SANITIZE_DEPTH) return TRUNCATED_PLACEHOLDER;\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((entry) => sanitizeWithGuards(entry, depth + 1, seen));\n }\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isRedactedKey(key) ? REDACTED_PLACEHOLDER : sanitizeWithGuards(val, depth + 1, seen);\n }\n return out;\n } finally {\n //? Allow the same object to appear in sibling branches (shared, not cyclic).\n seen.delete(value);\n }\n};\n\nexport const sanitizeForLog = (value: unknown): unknown => sanitizeWithGuards(value, 0, new WeakSet());\n","//? Security-headers builder registry. The framework ships sensible defaults\n//? (Referrer-Policy, X-Frame-Options, X-XSS-Protection, X-Content-Type-Options)\n//? read from `projectConfig.http.securityHeaders`. Consumers can register\n//? a custom builder to add Content-Security-Policy, Strict-Transport-Security,\n//? Permissions-Policy, or to override defaults per request.\n//?\n//? Resolution order:\n//? 1. Built-in defaults from `projectConfig.http.securityHeaders`.\n//? 2. Headers from the registered builder (override OR augment).\n//?\n//? A nullish return from the builder means \"use defaults only\". An object\n//? merges on top of defaults (later wins per key).\n\nimport type { IncomingMessage } from 'node:http';\nimport { createRegistry } from '@luckystack/core';\n\nexport type SecurityHeadersBuilder = (req: IncomingMessage) => Record<string, string> | null | undefined;\n\n//? Single-slot registry: one builder at a time, last-write-wins, `null` is the\n//? unregistered baseline. Backed by core's `createRegistry` so the register /\n//? read / reset triad isn't hand-rolled. The public `registerSecurityHeaders` /\n//? `getSecurityHeadersBuilder` signatures below are preserved verbatim.\nconst builderRegistry = createRegistry<SecurityHeadersBuilder | null>(null);\n\n/**\n * Register a custom security-headers builder. Called for every HTTP\n * request; return a plain object that gets merged on top of the framework\n * defaults. Use for Content-Security-Policy, HSTS, Permissions-Policy.\n *\n * Last-write-wins: subsequent calls replace the previous builder. Pass\n * `null` to unregister.\n */\nexport const registerSecurityHeaders = (builder: SecurityHeadersBuilder | null): void => {\n builderRegistry.register(builder);\n};\n\n/** Read the active builder (or null). */\nexport const getSecurityHeadersBuilder = (): SecurityHeadersBuilder | null => builderRegistry.get();\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { dispatchHook, getCookieValue, getCsrfConfig, getProjectConfig, readSession } from '@luckystack/core';\nimport { capabilities } from '../capabilities';\nimport { isOriginExemptPath } from '../originExemptRegistry';\nimport { timingSafeStringEqual } from './timingSafeEqual';\n\n//? Returns true when the request was rejected (CSRF mismatch) and the response\n//? has been ended. Caller should bail out of the request loop.\nexport const enforceCsrfOnStateChangingRequest = async ({\n req,\n res,\n routePath,\n token,\n requestId,\n}: {\n req: IncomingMessage;\n res: ServerResponse;\n routePath: string;\n token: string | null;\n requestId?: string;\n}): Promise<boolean> => {\n const config = getProjectConfig();\n const isCookieMode = !config.session.basedToken;\n //? HEAD is read-only and excluded from `enforceOriginPolicy`; mirror that\n //? here so the two state-changing predicates agree (HEAD is 404'd at the\n //? method gate before this runs, so this is parity, not a behavior change).\n const isStateChanging = req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS';\n const isCallbackPath = routePath.startsWith('/auth/callback');\n //? The credentials login/register endpoint is the session BOOTSTRAP. Requiring a\n //? pre-existing session's CSRF token to authenticate is circular, and it blocks\n //? legitimate same-site re-login / register while a (stale) session cookie is\n //? present. Cross-site abuse is already prevented by the SameSite=Strict session\n //? cookie — a cross-site POST never carries it, so `token` would be absent here\n //? and this guard wouldn't fire anyway. Exempting it removes no real protection.\n const isAuthBootstrap = routePath === '/auth/api/credentials';\n //? CSRF covers all framework routes (/api/, /sync/, /auth/api/) AND\n //? state-changing custom routes registered via `registerCustomRoute` that\n //? are not marked origin-exempt (those authenticate via HMAC/signature, not\n //? the session cookie, so the double-submit check is irrelevant there).\n const isExemptFromCsrf =\n isAuthBootstrap\n || isCallbackPath\n || isOriginExemptPath(routePath);\n const isCsrfCandidate =\n routePath.startsWith('/api/')\n || routePath.startsWith('/sync/')\n || routePath.startsWith('/auth/api/')\n || (!routePath.startsWith('/auth/') && !routePath.startsWith('/assets/'));\n\n //? CSRF only applies to cookie-mode, state-changing routes not already\n //? exempted by another auth mechanism (origin-exempt webhooks, bootstrap).\n if (!(isCookieMode && isStateChanging && isCsrfCandidate && !isExemptFromCsrf)) {\n return false;\n }\n\n //? Read the active CSRF header name from the registry so consumers\n //? can rename it (e.g. legacy `x-xsrf-token`, custom `x-app-csrf`).\n const csrfConfig = getCsrfConfig();\n const headerKey = csrfConfig.headerName.toLowerCase();\n const headerValue = req.headers[headerKey];\n const provided = Array.isArray(headerValue) ? headerValue[0] : headerValue;\n\n //? Login-ABSENT path: stateless DOUBLE-SUBMIT. The session-bound token store\n //? lives in @luckystack/login; without it we compare the csrf COOKIE value\n //? against the x-csrf-token HEADER (both seeded by GET /auth/csrf). No session\n //? read. A cross-site POST can't read the cookie value to forge the header.\n if (!capabilities.login) {\n const cookieValue = getCookieValue(req.headers.cookie, csrfConfig.cookieName);\n if (cookieValue && provided && timingSafeStringEqual(provided, cookieValue)) return false;\n\n void dispatchHook('csrfMismatch', {\n route: routePath,\n method: req.method,\n requestId,\n userId: undefined,\n providedToken: Boolean(provided),\n });\n\n res.statusCode = 403;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({\n status: 'error',\n errorCode: 'auth.csrfMismatch',\n message: 'CSRF token missing or invalid. Fetch /auth/csrf first.',\n }));\n return true;\n }\n\n //? Login-PRESENT path: session-bound CSRF (unchanged). Requires a session\n //? token — without one there is no session to protect, so do not enforce.\n if (!token) return false;\n\n const csrfSession = await readSession(token);\n if (!csrfSession?.id) return false;\n\n if (provided && csrfSession.csrfToken && timingSafeStringEqual(provided, csrfSession.csrfToken)) return false;\n\n void dispatchHook('csrfMismatch', {\n route: routePath,\n method: req.method,\n requestId,\n userId: csrfSession.id,\n providedToken: Boolean(provided),\n });\n\n res.statusCode = 403;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({\n status: 'error',\n errorCode: 'auth.csrfMismatch',\n message: 'CSRF token missing or invalid. Fetch /auth/csrf first.',\n }));\n return true;\n};\n","//? Optional-package capability layer (0.2.0). `@luckystack/login`, `presence`,\n//? and `sync` are OPTIONAL peers of `@luckystack/server`. We detect each once at\n//? boot via `require.resolve` (cheap, cached) and lazy-import it only when\n//? present, so a consumer who omits one gets graceful degradation instead of an\n//? `ERR_MODULE_NOT_FOUND` crash. Mirrors `@luckystack/error-tracking`'s resolve\n//? guard. Session reads/writes do NOT go through here — they use core's\n//? `readSession`/`writeSession` accessors, which login populates via the session\n//? provider registry. This layer is for the ROUTE/SOCKET wiring that must be\n//? conditionally registered (auth routes, the sync listener, presence lifecycle).\n\nimport { createRequire } from 'node:module';\n\nconst localRequire = createRequire(import.meta.url);\n\n//? Detect whether an optional package (or one of its subpaths) is installed.\n//?\n//? IMPORTANT: every `@luckystack/*` package ships an **import-only** `exports`\n//? map (`{ \"import\": ..., \"types\": ... }` — no `\"require\"`/`\"default\"`). A\n//? CJS `require.resolve()` resolves with CJS conditions and therefore THROWS\n//? `ERR_PACKAGE_PATH_NOT_EXPORTED` on those maps — i.e. it reports every\n//? installed `@luckystack/*` package as ABSENT. We must resolve with the ESM\n//? resolver (`import.meta.resolve`, which honors the `\"import\"` condition).\n//? `createRequire().resolve` is kept only as a fallback for Node < 20.6 where\n//? synchronous `import.meta.resolve` is unavailable.\nconst esmResolve = (import.meta as { resolve?: (specifier: string) => string }).resolve;\n\n//? Warn once when `import.meta.resolve` is absent (Node < 20.6). The CJS\n//? fallback misreports all import-only packages as absent, so optional\n//? features (login, presence, sync) silently degrade. Surface this so the\n//? operator can upgrade Node rather than debugging mysterious capability gaps.\nlet _warnedResolverMissing = false;\nconst warnResolverMissingOnce = (): void => {\n if (_warnedResolverMissing) return;\n _warnedResolverMissing = true;\n // eslint-disable-next-line no-console -- intentional boot diagnostic; logger not yet available\n console.warn(\n '[luckystack:capabilities] import.meta.resolve is unavailable (Node < 20.6). ' +\n 'Optional package detection falls back to require.resolve, which cannot resolve ' +\n 'import-only exports maps and may misreport @luckystack/* packages as absent. ' +\n 'Upgrade to Node >= 20.6 to ensure correct capability detection.',\n );\n};\n\nconst has = (pkg: string): boolean => {\n if (typeof esmResolve === 'function') {\n try {\n esmResolve(pkg);\n return true;\n } catch {\n return false;\n }\n }\n //? CJS fallback: `ERR_PACKAGE_PATH_NOT_EXPORTED` means the package IS\n //? installed but its exports map has no CJS condition — treat as PRESENT.\n //? Any other error (MODULE_NOT_FOUND, ERR_MODULE_NOT_FOUND) means absent.\n warnResolverMissingOnce();\n try {\n localRequire.resolve(pkg);\n return true;\n } catch (error: unknown) {\n if (error instanceof Error && (error as NodeJS.ErrnoException).code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {\n return true;\n }\n return false;\n }\n};\n\n//? Resolved once at module load. `as const` so callers branch on literal booleans.\nexport const capabilities = {\n login: has('@luckystack/login'),\n presence: has('@luckystack/presence'),\n sync: has('@luckystack/sync'),\n} as const;\n\n//? Optional packages that ship a side-effect `@luckystack/<pkg>/register`\n//? subpath. `bootstrapLuckyStack` resolve-guards + imports each one BEFORE the\n//? consumer overlay folder so a hand-written overlay (last writer) still wins.\n//? Each `./register` is an env-driven, idempotent no-op when its env is unset.\n//? Order is topological (mirrors `OVERLAY_ORDER`). NOTES:\n//? - `sync` is excluded: it has no server-side register; its add-later wiring\n//? is the client receive bridge (`@luckystack/sync/client` attachSyncReceiver).\n//? - `secret-manager` is excluded: it is resolved explicitly in the consumer\n//? entry (fails-OPEN), not via a register subpath.\nexport const OPTIONAL_PACKAGES = [\n 'login',\n 'email',\n 'error-tracking',\n 'presence',\n 'docs-ui',\n] as const;\n\n//? Cheap resolve guard reused by the bootstrap auto-detect loop: true when the\n//? given specifier (e.g. `@luckystack/login/register`) is installed + resolvable.\nexport const canResolve = (specifier: string): boolean => has(specifier);\n\nlet loginMod: typeof import('@luckystack/login') | null | undefined;\nexport const getLogin = async (): Promise<typeof import('@luckystack/login') | null> => {\n if (loginMod !== undefined) return loginMod;\n loginMod = capabilities.login ? await import('@luckystack/login') : null;\n return loginMod;\n};\n\nlet presenceMod: typeof import('@luckystack/presence') | null | undefined;\nexport const getPresence = async (): Promise<typeof import('@luckystack/presence') | null> => {\n if (presenceMod !== undefined) return presenceMod;\n presenceMod = capabilities.presence ? await import('@luckystack/presence') : null;\n return presenceMod;\n};\n\nlet syncMod: typeof import('@luckystack/sync') | null | undefined;\nexport const getSync = async (): Promise<typeof import('@luckystack/sync') | null> => {\n if (syncMod !== undefined) return syncMod;\n syncMod = capabilities.sync ? await import('@luckystack/sync') : null;\n return syncMod;\n};\n","//? Origin-exempt path registry. `enforceOriginPolicy` fail-closes every\n//? state-changing POST that carries no browser-attributable Origin/Referer —\n//? which is exactly a legitimate server-to-server webhook (GitLab, Stripe, ...).\n//? Registering a path prefix here lets such an endpoint through the browser\n//? CSRF/origin gate.\n//?\n//? SECURITY: origin exemption is NOT authentication. It only removes the\n//? browser-origin check; the exempted handler MUST authenticate the caller\n//? itself (HMAC over the raw body, a shared-secret header, mTLS, ...). Pair\n//? with a `'pre-params'` custom route so the handler can read the raw body for\n//? signature verification. Empty by default — opt-in only. Do NOT register a\n//? prefix that overlaps framework routes (`/api`, `/auth`, `/sync`); keep\n//? webhooks on a dedicated prefix like `/webhooks/`. See\n//? docs/ARCHITECTURE_HTTP.md.\n\nexport interface OriginExemptMatcher {\n /** A route is exempt when its path starts with this prefix. */\n pathPrefix: string;\n}\n\nconst exemptPaths: OriginExemptMatcher[] = [];\n\nexport const registerOriginExemptPath = (matcher: OriginExemptMatcher): void => {\n exemptPaths.push(matcher);\n};\n\nexport const getOriginExemptPaths = (): readonly OriginExemptMatcher[] => exemptPaths;\n\nexport const clearOriginExemptPaths = (): void => {\n exemptPaths.length = 0;\n};\n\n//? True when `routePath` matches a registered exempt prefix. Consulted by\n//? `enforceOriginPolicy` before it can 403 a header-less request.\nexport const isOriginExemptPath = (routePath: string): boolean =>\n exemptPaths.some((matcher) => routePath.startsWith(matcher.pathPrefix));\n","import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto';\n\n//? Constant-time string comparison for security tokens (CSRF tokens, the\n//? test-reset token). Plain `===` short-circuits on the first differing byte,\n//? leaking length + prefix-match information through response timing. Compare\n//? the UTF-8 byte buffers with `crypto.timingSafeEqual`, which requires equal\n//? lengths — so we length-check first (itself not secret-dependent) and only\n//? run the constant-time compare on equal-length inputs.\nexport const timingSafeStringEqual = (a: string, b: string): boolean => {\n const aBuf = Buffer.from(a, 'utf8');\n const bBuf = Buffer.from(b, 'utf8');\n if (aBuf.length !== bBuf.length) return false;\n return cryptoTimingSafeEqual(aBuf, bBuf);\n};\n","import { randomBytes } from 'node:crypto';\nimport { getCsrfConfig, readSession, type CsrfCookieOptions } from '@luckystack/core';\nimport { capabilities } from '../capabilities';\nimport type { HttpRouteHandler } from './types';\n\n//? Serialize the configured CSRF cookie options into a Set-Cookie string. Only\n//? used on the login-ABSENT double-submit path; the login-present path delivers\n//? the token in the JSON body (session-bound, no cookie write here).\nconst serializeCsrfCookie = (name: string, value: string, opts: CsrfCookieOptions): string => {\n const parts = [`${name}=${value}`];\n if (opts.httpOnly) parts.push('HttpOnly');\n if (opts.sameSite) parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase()}${opts.sameSite.slice(1)}`);\n if (opts.secure) parts.push('Secure');\n parts.push(`Path=${opts.path ?? '/'}`);\n if (typeof opts.maxAgeMs === 'number') parts.push(`Max-Age=${String(Math.floor(opts.maxAgeMs / 1000))}`);\n return parts.join('; ');\n};\n\nexport const handleCsrfRoute: HttpRouteHandler = async ({ res, routePath, token }) => {\n if (routePath !== '/auth/csrf') return false;\n\n const csrfConfig = getCsrfConfig();\n\n //? Login-ABSENT (unauthenticated app): there is no per-session CSRF token, so\n //? issue a stateless DOUBLE-SUBMIT token — set it as the csrf cookie AND return\n //? it in the body. `enforceCsrfOnStateChangingRequest` later compares the cookie\n //? value against the `x-csrf-token` header (no session read). A cross-site\n //? attacker can neither read the body (CORS) nor forge the header to match the\n //? victim's cookie, so this blocks cross-site state changes without login.\n if (!capabilities.login) {\n //? Stateless double-submit: the SAME random value is set as the CSRF cookie\n //? and echoed in the JSON body. `enforceCsrfOnStateChangingRequest` later\n //? compares the cookie against the `x-csrf-token` request header. A\n //? cross-site attacker cannot read the cookie (SameSite + HttpOnly) or\n //? the body (CORS), so they cannot forge a matching header.\n //?\n //? KNOWN LIMITATION: without HMAC binding to a server secret this token\n //? cannot survive a subdomain compromise (an attacker on sub.example.com\n //? can set a cookie on .example.com). This is accepted in the login-absent\n //? posture; add `registerCsrfConfig({ sign: true })` to enable HMAC signing\n //? when that threat model applies.\n const doubleSubmit = randomBytes(csrfConfig.tokenLength).toString('hex');\n res.statusCode = 200;\n res.setHeader('Set-Cookie', serializeCsrfCookie(csrfConfig.cookieName, doubleSubmit, csrfConfig.cookieOptions));\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'success', csrfToken: doubleSubmit }));\n return true;\n }\n\n //? Login-PRESENT: session-bound CSRF token (unchanged behaviour).\n if (!token) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'auth.unauthenticated' }));\n return true;\n }\n const csrfSession = await readSession(token);\n if (!csrfSession?.id) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'auth.unauthenticated' }));\n return true;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({\n status: 'success',\n csrfToken: csrfSession.csrfToken ?? null,\n }));\n return true;\n};\n","import type { HttpRouteHandler } from './types';\n\nexport const handleFaviconRoute: HttpRouteHandler = async ({ res, routePath, options }) => {\n if (routePath !== '/favicon.ico') return false;\n if (options.serveFavicon) {\n await options.serveFavicon(res);\n return true;\n }\n res.writeHead(404);\n res.end();\n return true;\n};\n","import {\n computeSynchronizedEnvHashes,\n describeHealthHashConfig,\n getProjectConfig,\n prisma,\n readBootUuid,\n redis,\n resolveEnvKey,\n tryCatch,\n} from '@luckystack/core';\nimport type { HttpRouteHandler } from './types';\n\n//? Cross-provider connectivity ping. Prisma's generated TypeScript surface\n//? differs per provider: SQL providers expose `$queryRaw`, MongoDB exposes\n//? `$runCommandRaw`. The framework can't know which one the consumer's schema\n//? uses, so this single seam asserts both as optional and probes at runtime.\n//? Detecting the provider via `_engineConfig.activeProvider` is private API\n//? and has drifted between Prisma major versions — runtime probing is more\n//? robust.\ninterface PrismaPingShape {\n $queryRaw?: (template: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;\n $runCommandRaw?: (command: Record<string, unknown>) => Promise<unknown>;\n}\n\nconst pingPrisma = async (): Promise<boolean> => {\n //? Prisma's generated client typings don't expose `$queryRaw` /\n //? `$runCommandRaw` uniformly (depends on the active datasource). The\n //? `PrismaPingShape` is our minimal local probe interface; the boundary\n //? cast is the structural exception the strict-typing policy allows.\n // eslint-disable-next-line no-restricted-syntax -- Prisma datasource-conditional shape\n const client = prisma as unknown as PrismaPingShape;\n //? Capture each function into a local before calling so the typeof\n //? narrow holds without `!`. The shape `PrismaPingShape` declares both\n //? methods optional because Prisma's generated types include one or the\n //? other depending on active provider — capturing into a local also\n //? removes the assertion-style cast that the strict-typing policy disallows.\n const queryRaw = client.$queryRaw;\n if (typeof queryRaw === 'function') {\n const [sqlError] = await tryCatch(() => queryRaw`SELECT 1`);\n if (!sqlError) return true;\n }\n const runCommandRaw = client.$runCommandRaw;\n if (typeof runCommandRaw === 'function') {\n const [mongoError] = await tryCatch(() => runCommandRaw({ ping: 1 }));\n return !mongoError;\n }\n return false;\n};\n\nexport const handleLivezRoute: HttpRouteHandler = ({ res, routePath }) => {\n if (routePath !== getProjectConfig().http.liveEndpoint) return Promise.resolve(false);\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'live' }));\n return Promise.resolve(true);\n};\n\nexport const handleReadyzRoute: HttpRouteHandler = async ({ res, routePath }) => {\n if (routePath !== getProjectConfig().http.readyEndpoint) return false;\n\n //? SEC: this endpoint is intentionally unauthenticated (orchestrators and load\n //? balancers probe it without credentials). Each call pings Redis + Prisma, so\n //? callers can trigger non-trivial backend load. Mitigate at the infra layer\n //? (network policy, rate-limiting ingress) rather than here, to keep the probe\n //? surface simple and avoid circular-dependency on session/auth bootstrap.\n const bootUuid = await readBootUuid();\n\n const [redisError, pong] = await tryCatch(() => redis.ping());\n const redisOk = !redisError && (pong === 'PONG' || Boolean(pong));\n\n const prismaOk = await pingPrisma();\n\n const ready = Boolean(bootUuid) && redisOk && prismaOk;\n res.statusCode = ready ? 200 : 503;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({\n status: ready ? 'ready' : 'not-ready',\n checks: { bootUuid: Boolean(bootUuid), redis: redisOk, prisma: prismaOk },\n }));\n return true;\n};\n\nexport const handleHealthRoute: HttpRouteHandler = async ({ res, routePath }) => {\n if (routePath !== getProjectConfig().http.healthEndpoint) return false;\n\n //? SEC: unauthenticated by design (router and monitoring systems probe without\n //? session tokens). Consider binding this to an internal/loopback interface only\n //? in production, or protecting it with `registerCustomRoute` + a probe token,\n //? to prevent external amplification of the Prisma + Redis ping path.\n const bootUuid = await readBootUuid();\n //? SEC-13: pass the boot UUID so the `'@bootUuid'` salt sentinel (the 0.2.0\n //? default `http.healthHash` = `{ mode: 'hmac', salt: '@bootUuid' }`) resolves\n //? to a per-boot HMAC key. Previously the arg was omitted, so the sentinel\n //? always collapsed to `'plain'` and `/_health` leaked a stable, unsalted\n //? `sha256(secret)` fingerprint of every synchronized env value.\n const synchronizedHashes = computeSynchronizedEnvHashes(bootUuid);\n res.statusCode = bootUuid ? 200 : 503;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({\n status: bootUuid ? 'ok' : 'degraded',\n bootUuid,\n envKey: resolveEnvKey(),\n synchronizedHashes,\n //? Tell the router HOW these hashes were produced (mode + whether the salt is\n //? the `@bootUuid` sentinel) so it can hash its local values with the SAME\n //? config instead of its own default. Never exposes a static salt (a secret).\n healthHash: describeHealthHashConfig(),\n }));\n return true;\n};\n","import {\n clearAllHooks,\n clearAllRateLimits,\n getProjectConfig,\n formatKey,\n redis,\n tryCatch,\n} from '@luckystack/core';\nimport { timingSafeStringEqual } from './timingSafeEqual';\nimport type { HttpRouteHandler } from './types';\n\nexport const handleTestResetRoute: HttpRouteHandler = async ({ req, res, routePath }) => {\n if (routePath !== getProjectConfig().http.testResetEndpoint) return false;\n\n //? Fail-closed: require explicit dev/test NODE_ENV (not just \"anything but\n //? production\") so a missing or misconfigured NODE_ENV cannot expose this\n //? destructive endpoint. Also require TEST_RESET_TOKEN unconditionally —\n //? an unset token must NOT mean \"no auth required\".\n const nodeEnv = process.env.NODE_ENV;\n if (nodeEnv !== 'development' && nodeEnv !== 'test') {\n res.statusCode = 404;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'notFound' }));\n return true;\n }\n const requiredToken = process.env.TEST_RESET_TOKEN;\n const providedToken = req.headers['x-test-reset-token'];\n const tokenValue = Array.isArray(providedToken) ? providedToken[0] : providedToken;\n if (!requiredToken || !tokenValue || !timingSafeStringEqual(tokenValue, requiredToken)) {\n res.statusCode = 403;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'auth.forbidden' }));\n return true;\n }\n\n const cleared: string[] = [];\n await clearAllRateLimits();\n cleared.push('rateLimits');\n\n //? Flush sessions + activeUsers Redis keys so integration tests start from\n //? a clean slate. Patterns are derived through the shared `formatKey(...)`\n //? authority so they track any registered key formatter (see also\n //? session.ts, sessionAdapter.ts, rateLimiter.ts).\n const sessionPattern = `${formatKey('-session', '')}:*`;\n const activeUsersPattern = `${formatKey('-activeUsers', '')}:*`;\n\n const scanAndDelete = async (pattern: string, label: string): Promise<number> => {\n const [error, deleted] = await tryCatch(async () => {\n let cursor = '0';\n let total = 0;\n do {\n const [next, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 200);\n cursor = next;\n if (Array.isArray(keys) && keys.length > 0) {\n await redis.del(...keys);\n total += keys.length;\n }\n } while (cursor !== '0');\n return total;\n });\n if (error || !deleted) return 0;\n if (deleted > 0) cleared.push(label);\n return deleted;\n };\n\n await scanAndDelete(sessionPattern, 'sessions');\n await scanAndDelete(activeUsersPattern, 'activeUsers');\n\n //? Opt-in hook clear via `?include=hooks` because clearing all hooks would\n //? also drop framework-internal handlers (e.g. presence postLogout). URL\n //? parsing failure is the expected branch for malformed `req.url`, so use\n //? `URL.canParse` instead of try/catch.\n const rawUrl = req.url ?? '/';\n //? Use a fixed loopback base — `req.headers.host` is client-controlled and\n //? must not influence URL resolution; only the path and query matter here.\n const includeFlag = URL.canParse(rawUrl, 'http://localhost')\n ? new URL(rawUrl, 'http://localhost').searchParams.get('include') ?? ''\n : '';\n if (includeFlag.split(',').map((s) => s.trim()).includes('hooks')) {\n clearAllHooks();\n cleared.push('hooks');\n }\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'success', cleared }));\n return true;\n};\n","import { serveAvatar } from '@luckystack/core';\nimport type { HttpRouteHandler } from './types';\n\nexport const handleUploadsRoute: HttpRouteHandler = async ({ res, routePath }) => {\n if (!routePath.startsWith('/uploads/')) return false;\n await serveAvatar({ routePath, res });\n return true;\n};\n","import {\n checkRateLimit,\n dispatchHook,\n getLogger,\n getProjectConfig,\n resolveClientIp,\n} from '@luckystack/core';\nimport { getLogin } from '../capabilities';\nimport { resolveCookieSecure } from './sessionCookie';\nimport type { HttpRouteHandler } from './types';\n\nconst parseSessionBasedTokenHeader = (headerValue: string | string[] | undefined): boolean | null => {\n if (headerValue === undefined) return null;\n const value = Array.isArray(headerValue) ? headerValue[0] : headerValue;\n if (value === '1' || value === 'true') return true;\n if (value === '0' || value === 'false') return false;\n return null;\n};\n\nexport const handleAuthApiRoute: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n token,\n params,\n sessionCookieOptions,\n}) => {\n if (!routePath.startsWith('/auth/api')) return false;\n\n //? @luckystack/login is optional. Without it there is no auth surface, so every\n //? /auth/api/* route reports the disabled contract instead of crashing.\n const login = await getLogin();\n if (!login) {\n res.setHeader('content-type', 'application/json; charset=utf-8');\n res.end(JSON.stringify({ status: false, reason: 'auth.disabled' }));\n return true;\n }\n\n const config = getProjectConfig();\n const sessionCookieName = config.http.sessionCookieName;\n const shouldLogDev = config.logging.devLogs;\n\n const providerName = routePath.split('/')[3];\n const provider = login.getOAuthProviders().find((p) => p.name === providerName);\n if (!provider?.name) {\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: false, reason: 'login.providerNotFound' }));\n return true;\n }\n\n if (login.isFullOAuthProvider(provider)) {\n //? Read the optional `return_url` query param set by the frontend when it\n //? initiates the OAuth flow. The value is the full URL the browser should\n //? land on AFTER the callback (e.g. http://localhost:5174/playground).\n //? Stored server-side in Redis alongside the state — NOT echoed from the\n //? client at callback time — so it cannot be tampered with mid-flight.\n //? Validation against allowedOrigins + allowLocalhost happens in loginCallback\n //? before the redirect is issued (isAllowedRedirectUrl gate in login.ts).\n const reqUrl = new URL(req.url ?? '/', 'http://placeholder');\n const returnUrl = reqUrl.searchParams.get('return_url') ?? undefined;\n\n const oauthState = await login.createOAuthState(provider.name, { usePkce: provider.usePkce, returnUrl });\n if (!oauthState) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: false, reason: 'login.oauthStateInitFailed' }));\n return true;\n }\n\n const clientId = encodeURIComponent(provider.clientID);\n const callbackUrl = encodeURIComponent(provider.callbackURL);\n const scope = encodeURIComponent((provider.scope).join(' '));\n const state = encodeURIComponent(oauthState.state);\n\n //? Bind the OAuth flow to THIS browser: the callback only accepts the flow\n //? when the same browser presents back the nonce we stash in the\n //? short-lived state cookie. Same Secure derivation as the session-token\n //? cookie (`resolveCookieSecure`, shared seam) so the binding cookie isn't\n //? sent over plaintext when the deployment is HTTPS.\n const stateTtl = config.auth.oauthStateTtlSeconds;\n const secureFlag = resolveCookieSecure(config.http.sessionCookieSecure, process.env.SECURE) ? ' Secure;' : '';\n res.setHeader(\n 'Set-Cookie',\n `${login.OAUTH_STATE_COOKIE_NAME}=${oauthState.stateCookie}; Path=/; HttpOnly;${secureFlag} SameSite=Lax; Max-Age=${stateTtl}`,\n );\n\n //? Append the PKCE S256 challenge when the provider opted into PKCE — the\n //? callback redeems the matching verifier stashed in the state entry.\n const pkceParams = oauthState.codeChallenge\n ? `&code_challenge=${encodeURIComponent(oauthState.codeChallenge)}&code_challenge_method=S256`\n : '';\n\n res.writeHead(302, {\n Location: `${provider.authorizationURL}?client_id=${clientId}&redirect_uri=${callbackUrl}&scope=${scope}&response_type=code&prompt=select_account&state=${state}${pkceParams}`,\n });\n res.end();\n return true;\n }\n\n const rateLimiting = config.rateLimiting;\n //? DD-LOGIN-F5: resolve the client IP once, unconditionally, so it can be\n //? threaded into `loginWithCredentials` for the IP+account composite lockout\n //? key even when the per-IP rate-limit gate is disabled. When trustProxy is\n //? false this returns the raw socket address (sentinel `'unknown'` when absent).\n const requesterIp = resolveClientIp({\n rawAddress: req.socket.remoteAddress,\n headers: req.headers,\n trustProxy: config.http.trustProxy,\n trustedProxyHopCount: config.http.trustedProxyHopCount,\n });\n\n //? Derive the per-IP limit for this credentials endpoint. When the global\n //? `defaultApiLimit` is disabled (set to `false`), fall back to the auth-\n //? specific `rateLimiting.auth` slot so an IP spraying across accounts is\n //? still throttled — even when consumers explicitly disable the general limit\n //? for performance reasons. The auth slot defaults to `{ enabled: false }`,\n //? so the fallback is also a no-op unless the consumer opts in.\n const ipLimitCount =\n rateLimiting.defaultApiLimit !== false && rateLimiting.defaultApiLimit > 0\n ? rateLimiting.defaultApiLimit\n : (rateLimiting.auth.enabled && rateLimiting.auth.maxAttempts > 0\n ? rateLimiting.auth.maxAttempts\n : null);\n if (ipLimitCount !== null) {\n const ipWindowMs =\n rateLimiting.defaultApiLimit === false\n ? rateLimiting.auth.windowMs\n : rateLimiting.windowMs;\n const { allowed, resetIn } = await checkRateLimit({\n key: `ip:${requesterIp}:auth:credentials`,\n limit: ipLimitCount,\n windowMs: ipWindowMs,\n });\n\n if (!allowed) {\n void dispatchHook('rateLimitExceeded', {\n scope: 'auth',\n key: `ip:${requesterIp}:auth:credentials`,\n limit: ipLimitCount,\n windowMs: ipWindowMs,\n count: ipLimitCount + 1,\n route: routePath,\n ip: requesterIp,\n });\n res.setHeader('content-type', 'application/json; charset=utf-8');\n res.end(JSON.stringify({\n status: false,\n reason: 'api.rateLimitExceeded',\n errorParams: [{ key: 'seconds', value: resetIn }],\n }));\n return true;\n }\n }\n\n //? Pass the requester's current session token as `supersedeToken` and the\n //? resolved `requesterIp` (DD-LOGIN-F5 composite lockout key) so that on\n //? a re-login while already signed in, single-session enforcement does NOT\n //? kick this same browser's old session.\n const result = (await login.loginWithCredentials(params, { supersedeToken: token ?? undefined, requesterIp })) as {\n status: boolean;\n reason: string;\n newToken: string | null;\n session: unknown;\n } | undefined;\n\n if (!result?.status) {\n const reasonKey =\n typeof result?.reason === 'string' && result.reason.length > 0\n ? result.reason\n : 'api.internalServerError';\n res.setHeader('content-type', 'application/json; charset=utf-8');\n res.end(JSON.stringify({ status: false, reason: reasonKey }));\n return true;\n }\n\n if (result.newToken) {\n //? Old session was excluded from enforcement above (supersedeToken). Clean it\n //? up silently — `skipSocketLogout` prevents a `logout` emit to this same\n //? browser's live socket, which would bounce it to /login and null the new\n //? session before the success redirect runs.\n if (token) await login.deleteSession(token, { skipSocketLogout: true });\n\n const requestedSessionMode = parseSessionBasedTokenHeader(req.headers['x-session-based-token']);\n const useSessionBasedToken = requestedSessionMode ?? config.session.basedToken;\n\n if (shouldLogDev) getLogger().debug('http: setting cookie with new token');\n\n if (useSessionBasedToken) {\n res.setHeader('X-Session-Token', result.newToken);\n } else {\n res.setHeader('Set-Cookie', `${sessionCookieName}=${result.newToken}; ${sessionCookieOptions}`);\n }\n }\n\n res.end(JSON.stringify({\n status: result.status,\n reason: result.reason,\n session: result.session,\n authenticated: Boolean(result.newToken),\n }));\n return true;\n};\n","//? Single source of truth for the session / OAuth-state cookie `Secure` flag:\n//? honor the explicit `http.sessionCookieSecure` override (CORE-39), else fall\n//? back to the `SECURE` env flag. Used by BOTH the session-token cookie\n//? (`httpHandler`) and the OAuth state cookie (`authApiRoute`) so the two can\n//? never drift again (WAVE4 — the M2 fix had updated only the OAuth cookie,\n//? leaving the security-critical session cookie ignoring the override).\nexport const resolveCookieSecure = (\n sessionCookieSecure: boolean | undefined,\n secureEnv: string | undefined,\n): boolean => sessionCookieSecure ?? secureEnv === 'true';\n","import { getLogger, getProjectConfig, tryCatch } from '@luckystack/core';\nimport { getLogin } from '../capabilities';\nimport type { HttpRouteHandler } from './types';\n\n//? HTTP logout endpoint — POST /auth/logout.\n//?\n//? Logout must terminate over HTTP in cookie mode because only an HTTP\n//? response can clear the HttpOnly session cookie — the socket transport the\n//? rest of the logout flow uses cannot touch cookies. The route deletes the\n//? session when the request still carries a live token (tolerating an\n//? already-deleted one — the socket logout usually ran first) and ALWAYS\n//? answers with an expiring Set-Cookie so the browser drops the stale\n//? credential.\n//?\n//? CSRF: deliberately outside the `/auth/api` prefix the CSRF middleware\n//? guards. The SameSite=Strict session cookie never rides on a cross-site\n//? POST, so a forged request arrives token-less and clears nothing — same\n//? reasoning as the credentials-bootstrap exemption in csrfMiddleware.ts.\nexport const handleAuthLogoutRoute: HttpRouteHandler = async ({ res, routePath, method, token }) => {\n if (routePath !== '/auth/logout') return false;\n\n if (method !== 'POST') {\n res.statusCode = 405;\n res.setHeader('Allow', 'POST');\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'error', errorCode: 'api.methodNotAllowed' }));\n return true;\n }\n\n if (token) {\n const login = await getLogin();\n if (login) {\n //? deleteSession dispatches the session-delete hooks and reuses the\n //? socket logout for any still-connected sockets. A dead/unknown token\n //? no-ops; an adapter blip must not block the cookie clear below.\n const [deleteError] = await tryCatch(() => login.deleteSession(token));\n if (deleteError) {\n getLogger().warn('http logout: deleteSession failed — clearing cookie anyway', { err: deleteError });\n }\n }\n }\n\n //? Clearing cookie: identity = name + path (+ domain). Mirror the attributes\n //? of buildSessionCookieOptions in httpHandler.ts, with Max-Age=0 so the\n //? browser expires it immediately. setHeader (not append) intentionally\n //? overrides the sliding-expiration refresh that ran earlier this request.\n const http = getProjectConfig().http;\n const secure = process.env.SECURE === 'true';\n res.setHeader(\n 'Set-Cookie',\n `${http.sessionCookieName}=; HttpOnly; SameSite=${http.sessionCookieSameSite}; Path=${http.sessionCookiePath}; Max-Age=0; ${secure ? 'Secure;' : ''}`,\n );\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'success', result: true }));\n return true;\n};\n","import { getLogin } from '../capabilities';\nimport type { HttpRouteHandler } from './types';\n\n//? GET /auth/providers — public list of enabled auth-provider names, derived\n//? from the env-driven OAuth registry (see the consumer's\n//? `luckystack/login/oauthProviders.ts`, which registers a provider only when\n//? its credentials env vars are present). The login form fetches this to decide\n//? which OAuth buttons to render WITHOUT ever shipping client secrets to the\n//? browser. Read-only + bodyless, so it runs in the pre-params phase.\nexport const handleAuthProvidersRoute: HttpRouteHandler = async ({ req, res, routePath }) => {\n if (routePath !== '/auth/providers' || req.method !== 'GET') return false;\n\n //? @luckystack/login optional: when absent there are no providers to advertise.\n const login = await getLogin();\n const providers = login ? login.getOAuthProviders().map((provider) => provider.name) : [];\n\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ providers }));\n return true;\n};\n","import { getLogger, getProjectConfig } from '@luckystack/core';\nimport { getLogin } from '../capabilities';\nimport type { HttpRouteHandler } from './types';\n\nexport const handleAuthCallbackRoute: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n token,\n sessionCookieOptions,\n}) => {\n if (!routePath.startsWith('/auth/callback')) return false;\n\n //? @luckystack/login optional — no auth package means no OAuth callback.\n const login = await getLogin();\n if (!login) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Auth is not enabled');\n return true;\n }\n\n const config = getProjectConfig();\n const sessionCookieName = config.http.sessionCookieName;\n const shouldLogDev = config.logging.devLogs;\n\n //? Fallback redirect target when no `postLoginRedirect` resolver returns a URL.\n //? `projectConfig.app.publicUrl` is the public origin where users browse (also\n //? used for transactional email links) — dev: the frontend dev server, prod:\n //? your domain. After an OAuth callback (handled on the BACKEND origin) we must\n //? send the browser back to this public origin, not the backend.\n //?\n //? `loginRedirectUrl` is the configured post-login PATH (e.g. '/dashboard') and\n //? lives on the public origin, so we join it onto `publicUrl` to land the user\n //? where credentials login also lands them — not the bare public root. An\n //? already-absolute `loginRedirectUrl` is used as-is; an empty result falls\n //? through to '/dashboard' or whichever path the consumer configured.\n const publicOrigin = (config.app.publicUrl || '').replace(/\\/+$/, '');\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string must fall through\n const loginRedirect = config.loginRedirectUrl || '/';\n const baseLocation = /^https?:\\/\\//i.test(loginRedirect)\n ? loginRedirect\n : `${publicOrigin}${loginRedirect.startsWith('/') ? loginRedirect : `/${loginRedirect}`}`;\n const callbackResult = await login.loginCallback(routePath, req, res, {\n defaultRedirectUrl: baseLocation,\n supersedeToken: token ?? undefined,\n });\n\n if (!callbackResult) {\n res.writeHead(401, { 'Content-Type': 'text/plain' });\n res.end('Login failed');\n return true;\n }\n\n //? Re-login while already signed in: the new session was just created with\n //? `supersedeToken` so single-session enforcement did NOT kick this browser's\n //? old token. Clean the old session up silently — `skipSocketLogout` avoids\n //? emitting a `logout` to this same browser's live socket, which would\n //? otherwise bounce the user back to the login page and null their session.\n if (token) await login.deleteSession(token, { skipSocketLogout: true });\n\n if (shouldLogDev) getLogger().debug('http: setting cookie or redirect with new token');\n\n const { token: newToken, redirectUrl } = callbackResult;\n if (config.session.basedToken) {\n //? Deliver the based-token in the URL fragment, never the query string: a\n //? `#token=` fragment is not sent to the server, not logged, and not leaked\n //? via the `Referer` header on the next navigation. Drop any pre-existing\n //? fragment on the redirect target so we never emit a double-`#`.\n const hashIndex = redirectUrl.indexOf('#');\n const baseUrl = hashIndex === -1 ? redirectUrl : redirectUrl.slice(0, hashIndex);\n res.writeHead(302, { Location: `${baseUrl}#token=${newToken}` });\n } else {\n res.setHeader('Set-Cookie', `${sessionCookieName}=${newToken}; ${sessionCookieOptions}`);\n res.writeHead(302, { Location: redirectUrl });\n }\n res.end();\n return true;\n};\n","import {\n captureException,\n dispatchHook,\n extractTokenFromRequest,\n getLogger,\n tryCatch,\n} from '@luckystack/core';\nimport { handleHttpApiRequest } from '@luckystack/api';\nimport { initSseResponse, sendSseEvent, shouldUseHttpStream } from '../sse';\nimport { resolveRequesterIp } from './resolveRequesterIp';\nimport type { HttpRouteHandler } from './types';\n\nexport const handleApiRoute: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n queryString,\n method,\n params,\n requestId,\n}) => {\n if (!routePath.startsWith('/api/')) return false;\n\n const useHttpStream = shouldUseHttpStream({ acceptHeader: req.headers.accept, queryString });\n let streamClosed = false;\n if (useHttpStream) {\n initSseResponse(res);\n //? SRV-O1 — mirror the four-listener pattern from syncRoute: req 'error' and\n //? 'aborted' + res 'error' must all flip streamClosed so a broken client or\n //? a write error on the ServerResponse doesn't crash the worker with an\n //? unhandled 'error' event.\n const markClosed = () => {\n streamClosed = true;\n };\n req.on('close', markClosed);\n req.on('error', markClosed);\n req.on('aborted', markClosed);\n res.on('error', markClosed);\n }\n\n const [error, handled] = await tryCatch(async () => {\n const httpToken = extractTokenFromRequest(req);\n const apiName = routePath.slice(5); // strip \"/api/\"\n\n if (!apiName) {\n const response = {\n status: 'error' as const,\n httpStatus: 400,\n message: 'api.invalidName',\n errorCode: 'api.invalidName',\n };\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: response });\n res.end();\n return true;\n }\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(400);\n res.end(JSON.stringify(response));\n return true;\n }\n\n const apiData = typeof params === 'object'\n ? { ...(params as Record<string, unknown>) }\n : {};\n delete (apiData as Record<string, unknown>).stream;\n\n //? Resolve the real client IP for per-IP rate limiting (honors\n //? `http.trustProxy`; preserves the historical `undefined` fallback when\n //? there is genuinely no address). See `resolveRequesterIp`.\n const requesterIp = resolveRequesterIp(req);\n\n const result = await handleHttpApiRequest({\n name: apiName,\n data: apiData,\n token: httpToken,\n requesterIp,\n xLanguageHeader: req.headers['x-language'],\n acceptLanguageHeader: req.headers['accept-language'],\n method,\n stream: useHttpStream\n ? (payload) => {\n if (streamClosed || res.writableEnded) return;\n sendSseEvent({ res, event: 'stream', data: payload });\n }\n : undefined,\n });\n\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: result });\n res.end();\n return true;\n }\n\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(result.httpStatus);\n res.end(JSON.stringify(result));\n return true;\n }, undefined, { routePath, method, requestId, source: 'httpHandler.api' });\n\n if (!error) {\n return handled ?? true;\n }\n\n getLogger().error('http-api: top-level handler threw', error, { routePath, method, requestId });\n captureException(error, { routePath, method, requestId, source: 'httpHandler.api' });\n void dispatchHook('apiError', {\n route: routePath,\n method,\n requestId,\n error,\n });\n\n const errResponse = {\n status: 'error' as const,\n httpStatus: 500,\n message: 'api.invalidRequestFormat',\n errorCode: 'api.invalidRequestFormat',\n };\n\n if (useHttpStream) {\n if (!res.writableEnded) sendSseEvent({ res, event: 'error', data: errResponse });\n res.end();\n return true;\n }\n\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(500);\n res.end(JSON.stringify(errResponse));\n return true;\n};\n","import type { ServerResponse } from 'node:http';\nimport { getProjectConfig } from '@luckystack/core';\n\n//? Server-Sent Events (SSE) helpers shared by /api/* and /sync/* HTTP\n//? streaming. Only used when the client opted in via Accept: text/event-stream\n//? or the configured ?<queryParam>=<enabledValue>.\n\nconst isExpectingEventStream = (acceptHeader: string | string[] | undefined): boolean => {\n if (!acceptHeader) return false;\n const value = Array.isArray(acceptHeader) ? acceptHeader.join(',') : acceptHeader;\n return value.toLowerCase().includes('text/event-stream');\n};\n\nconst queryRequestsStream = (queryString: string | undefined): boolean => {\n if (!queryString) return false;\n const { queryParam, enabledValue } = getProjectConfig().http.stream;\n const params = new URLSearchParams(queryString);\n const value = params.get(queryParam);\n return value === enabledValue || value === '1';\n};\n\nexport const shouldUseHttpStream = ({\n acceptHeader,\n queryString,\n}: {\n acceptHeader: string | string[] | undefined;\n queryString: string | undefined;\n}): boolean => isExpectingEventStream(acceptHeader) || queryRequestsStream(queryString);\n\nexport const initSseResponse = (res: ServerResponse): void => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n });\n const connectedComment = getProjectConfig().http.stream.connectedComment;\n if (connectedComment) {\n res.write(`${connectedComment}\\n\\n`);\n }\n};\n\nexport const sendSseEvent = ({\n res,\n event,\n data,\n}: {\n res: ServerResponse;\n event: string;\n data: unknown;\n}): void => {\n if (res.writableEnded) return;\n //? Defense-in-depth: only write SSE frames to a response that was actually\n //? opened as an event-stream via `initSseResponse`. Every framework caller\n //? does init first, so this changes no real path — it just prevents a future\n //? caller from emitting `event:`/`data:` lines onto a non-SSE response (which\n //? would corrupt the body the client expected).\n const contentType = res.getHeader('Content-Type');\n if (typeof contentType !== 'string' || !contentType.includes('text/event-stream')) return;\n res.write(`event: ${event}\\n`);\n res.write(`data: ${JSON.stringify(data)}\\n\\n`);\n};\n","import type { IncomingMessage } from 'node:http';\nimport { getProjectConfig, resolveClientIp } from '@luckystack/core';\n\n/**\n * Resolve the real client IP for per-IP rate limiting on the HTTP api/sync\n * transports.\n *\n * Extracted verbatim from the previously-duplicated inline block in\n * `apiRoute.ts` and `syncRoute.ts` (both were byte-identical). Behaviour is\n * preserved exactly:\n *\n * - Reads `http.trustProxy` from the active project config. Default\n * `trustProxy: false` returns the raw `req.socket.remoteAddress` (with only\n * IPv4-mapped IPv6 canonicalization via `resolveClientIp`); a trusted proxy\n * honours `X-Forwarded-For` / `X-Real-IP`.\n * - Preserves the historical `undefined` fallback when there is genuinely no\n * address to resolve (no raw socket address AND — when proxied — no trusted\n * forwarded header), so downstream `?? 'anonymous'` / `?? 'unknown'`\n * bucketing stays byte-identical. This `undefined` short-circuit is the\n * reason this helper exists rather than calling `resolveClientIp` directly:\n * `resolveClientIp` would return the `'unknown'` sentinel here, which the\n * api/sync transports historically did NOT key on.\n *\n * NOTE: the `/auth/api` route intentionally does NOT use this helper — it\n * keys on `resolveClientIp(...)` unconditionally (returning the `'unknown'`\n * sentinel when nothing resolves), which is a different, deliberate\n * contract. Unifying the two would change behaviour, so it is left as-is.\n */\nexport const resolveRequesterIp = (req: IncomingMessage): string | undefined => {\n const trustProxy = getProjectConfig().http.trustProxy;\n const trustedProxyHopCount = getProjectConfig().http.trustedProxyHopCount;\n const rawRemoteAddress = req.socket.remoteAddress;\n return (rawRemoteAddress || (trustProxy && (req.headers['x-forwarded-for'] || req.headers['x-real-ip'])))\n ? resolveClientIp({ rawAddress: rawRemoteAddress, headers: req.headers, trustProxy, trustedProxyHopCount })\n : undefined;\n};\n","import {\n captureException,\n dispatchHook,\n extractTokenFromRequest,\n getLogger,\n tryCatch,\n} from '@luckystack/core';\nimport type { HttpSyncStreamEvent } from '@luckystack/sync';\nimport { capabilities, getSync } from '../capabilities';\nimport { initSseResponse, sendSseEvent, shouldUseHttpStream } from '../sse';\nimport { resolveRequesterIp } from './resolveRequesterIp';\nimport type { HttpRouteHandler } from './types';\n\ninterface NormalizedHttpSyncParams {\n data: Record<string, unknown>;\n receiver: string;\n ignoreSelf?: boolean;\n cb?: string;\n}\n\nconst normalizeHttpSyncParams = (params: object | null): NormalizedHttpSyncParams => {\n const source = (params ?? {}) as Record<string, unknown>;\n const data = (source.data && typeof source.data === 'object'\n ? (source.data as Record<string, unknown>)\n : {});\n return {\n data,\n receiver: typeof source.receiver === 'string' ? source.receiver : '',\n ignoreSelf: typeof source.ignoreSelf === 'boolean' ? source.ignoreSelf : undefined,\n cb: typeof source.cb === 'string' ? source.cb : undefined,\n };\n};\n\nexport const handleSyncRoute: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n queryString,\n method,\n params,\n requestId,\n}) => {\n if (!routePath.startsWith('/sync/')) return false;\n\n //? @luckystack/sync is optional. Absent => no real-time fanout; report the\n //? disabled contract so a developer hitting /sync/* gets a clear signal.\n const sync = capabilities.sync ? await getSync() : null;\n if (!sync) {\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(404);\n res.end(JSON.stringify({ status: 'error', errorCode: 'sync.disabled', message: 'sync.disabled' }));\n return true;\n }\n\n const useHttpStream = shouldUseHttpStream({ acceptHeader: req.headers.accept, queryString });\n let streamClosed = false;\n if (useHttpStream) {\n initSseResponse(res);\n //? Mark the SSE stream closed on EVERY terminal connection event, not just\n //? 'close': an aborted or errored client (or a write error on res) must\n //? also flip the flag so subsequent `sendSseEvent` calls become no-ops and\n //? the connection can't leak or crash the worker with an unhandled 'error'.\n const markClosed = () => {\n streamClosed = true;\n };\n req.on('close', markClosed);\n req.on('error', markClosed);\n req.on('aborted', markClosed);\n res.on('error', markClosed);\n }\n\n const [error, handled] = await tryCatch(async () => {\n if (method !== 'POST') {\n const response = {\n status: 'error' as const,\n message: 'sync.methodNotAllowed',\n errorCode: 'sync.methodNotAllowed',\n };\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: response });\n res.end();\n return true;\n }\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(405);\n res.end(JSON.stringify(response));\n return true;\n }\n\n const httpToken = extractTokenFromRequest(req);\n const syncName = routePath.slice(6); // strip \"/sync/\"\n\n if (!syncName) {\n const response = {\n status: 'error' as const,\n message: 'sync.invalidName',\n errorCode: 'sync.invalidName',\n };\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: response });\n res.end();\n return true;\n }\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(400);\n res.end(JSON.stringify(response));\n return true;\n }\n\n const syncParams = normalizeHttpSyncParams(params);\n\n //? Resolve the real client IP for per-IP rate limiting (honors\n //? `http.trustProxy`; preserves the historical `undefined` fallback when\n //? there is genuinely no address). See `resolveRequesterIp`.\n const requesterIp = resolveRequesterIp(req);\n\n const result = await sync.handleHttpSyncRequest({\n name: `sync/${syncName}`,\n cb: syncParams.cb,\n data: syncParams.data,\n receiver: syncParams.receiver,\n ignoreSelf: syncParams.ignoreSelf,\n token: httpToken,\n requesterIp,\n xLanguageHeader: req.headers['x-language'],\n acceptLanguageHeader: req.headers['accept-language'],\n stream: useHttpStream\n ? (payload: HttpSyncStreamEvent) => {\n if (streamClosed || res.writableEnded) return;\n sendSseEvent({ res, event: 'stream', data: payload });\n }\n : undefined,\n });\n\n if (useHttpStream) {\n if (!streamClosed) sendSseEvent({ res, event: 'final', data: result });\n res.end();\n return true;\n }\n\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(result.httpStatus ?? (result.status === 'success' ? 200 : 400));\n res.end(JSON.stringify(result));\n return true;\n }, undefined, { routePath, method, requestId, source: 'httpHandler.sync' });\n\n if (!error) {\n return handled ?? true;\n }\n\n getLogger().error('http-sync: top-level handler threw', error, { routePath, method, requestId });\n captureException(error, { routePath, method, requestId, source: 'httpHandler.sync' });\n void dispatchHook('syncError', {\n route: routePath,\n method,\n requestId,\n error,\n });\n\n const errResponse = {\n status: 'error' as const,\n message: 'sync.invalidRequestFormat',\n errorCode: 'sync.invalidRequestFormat',\n };\n\n if (useHttpStream) {\n if (!res.writableEnded) sendSseEvent({ res, event: 'error', data: errResponse });\n res.end();\n return true;\n }\n\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(500);\n res.end(JSON.stringify(errResponse));\n return true;\n};\n","import { captureException, getLogger, tryCatch } from '@luckystack/core';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { CustomRouteHandler, RouteContext } from '../types';\nimport { getCustomRoutes, getPreParamsCustomRoutes } from '../customRoutesRegistry';\nimport type { HttpRouteHandler } from './types';\n\nconst runHandler = async (\n handler: CustomRouteHandler,\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n source: string,\n): Promise<boolean> => {\n const [error, handled] = await tryCatch(() => handler(req, res, ctx));\n if (error) {\n //? Custom route handlers come from third-party overlay packages — one\n //? misbehaving handler must not crash the request loop or leak the\n //? error to the client. Surface to logger + Sentry, then return as\n //? handled so the caller short-circuits.\n getLogger().error(`${source} threw`, error, { routePath: ctx.routePath, method: ctx.method });\n captureException(error, { routePath: ctx.routePath, method: ctx.method, source });\n if (!res.writableEnded) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'error', errorCode: 'server.customRouteFailed' }));\n }\n return true;\n }\n return Boolean(handled) || res.writableEnded;\n};\n\n//? PRE-PARAMS phase: consumer routes registered with `{ phase: 'pre-params' }`,\n//? dispatched BEFORE the body is parsed so the handler can read the raw `req`\n//? stream (webhook HMAC verification, streaming/multipart uploads). A handler\n//? that returns `false` without consuming the body falls through to the normal\n//? pipeline. Runs after the framework's own pre-params fast-paths.\nexport const handlePreParamsCustomRoutes: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n queryString,\n method,\n token,\n}) => {\n const ctx: RouteContext = { routePath, method, queryString, token };\n for (const handler of getPreParamsCustomRoutes()) {\n const handled = await runHandler(handler, req, res, ctx, 'preParamsCustomRoute');\n if (handled) return true;\n }\n return false;\n};\n\nexport const handleCustomRoutes: HttpRouteHandler = async ({\n req,\n res,\n options,\n routePath,\n queryString,\n method,\n token,\n}) => {\n //? Two sources, evaluated in order: (1) handlers registered via\n //? `registerCustomRoute(...)` from overlay packages (`@luckystack/docs-ui`,\n //? etc.); (2) the legacy `customRoutes` option on\n //? `CreateLuckyStackServerOptions`. First one to return `true` (or end\n //? the response) wins.\n const ctx: RouteContext = { routePath, method, queryString, token };\n\n for (const handler of getCustomRoutes()) {\n const handled = await runHandler(handler, req, res, ctx, 'customRoutesRegistry');\n if (handled) return true;\n }\n if (options.customRoutes) {\n const handled = await runHandler(options.customRoutes, req, res, ctx, 'createServer.customRoutes');\n if (handled) return true;\n }\n return false;\n};\n","//? Custom-route registry. Lets overlay files (luckystack/<package>/index.ts)\n//? register HTTP route handlers without each one touching server/server.ts.\n//?\n//? Why a registry instead of a single function on CreateLuckyStackServerOptions:\n//? The overlay-folder pattern means multiple packages may want to add\n//? routes (docs-ui, an admin panel, a webhook receiver, ...). Each\n//? registers independently; the framework iterates them in the order they\n//? were registered, returning the first one that handles the request.\n//?\n//? The original `customRoutes` option on `CreateLuckyStackServerOptions`\n//? still works — it's appended to the registry at boot and runs last.\n//?\n//? Two phases (see `CustomRoutePhase`): `'post-params'` (default — runs after\n//? the body is parsed) and `'pre-params'` (runs before the body is read, so\n//? the handler gets the raw `req` stream — webhooks + streaming uploads).\n\nimport type { CustomRouteHandler, CustomRoutePhase } from './types';\n\nexport interface RegisterCustomRouteOptions {\n /** Pipeline phase the handler runs in. Defaults to `'post-params'`. */\n phase?: CustomRoutePhase;\n}\n\n//? `handlers` backs the default `'post-params'` phase. `getCustomRoutes()`\n//? returns this array by reference (callers + tests rely on the clear-in-place\n//? semantics), so keep it as the post-params store.\nconst handlers: CustomRouteHandler[] = [];\nconst preParamsHandlers: CustomRouteHandler[] = [];\n\nexport const registerCustomRoute = (\n handler: CustomRouteHandler,\n options: RegisterCustomRouteOptions = {},\n): void => {\n if (options.phase === 'pre-params') {\n preParamsHandlers.push(handler);\n return;\n }\n handlers.push(handler);\n};\n\nexport const getCustomRoutes = (): readonly CustomRouteHandler[] => handlers;\n\nexport const getPreParamsCustomRoutes = (): readonly CustomRouteHandler[] => preParamsHandlers;\n\nexport const clearCustomRoutes = (): void => {\n handlers.length = 0;\n preParamsHandlers.length = 0;\n};\n","import path from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { StaticFileHandler } from '../types';\nimport type { HttpRouteHandler } from './types';\n\nconst KNOWN_STATIC_FILE_REGEX = /^\\/(assets\\/[a-zA-Z0-9_/-]+|[a-zA-Z0-9_-]+)\\.(png|jpg|jpeg|gif|svg|html|css|js)$/;\n\n//? Source-disclosure denylist. The server bundle (`dist/server.js`) and ANY\n//? source map (`*.map`) must never be served by a framework static path —\n//? leaking either hands an attacker the readable server source. This guards\n//? every branch below (assets, known-extension, SPA catch-all) regardless of\n//? which `serveFile` the consumer wired, so the protection is structural and\n//? does not rely on the default noop handler.\nconst SERVE_DENYLIST_REGEX = /(^\\/server\\.js$)|(\\.map$)/;\n\n//? `serveFile` consumers (Vite middleware, custom static handlers) read\n//? `req.url`, so we need to swap it for the rewritten asset path. We swap\n//? around the call and restore on return so anything downstream that\n//? observes `req.url` (request loggers, metrics middleware) still sees the\n//? original incoming URL after this handler.\nconst serveWithRewrittenUrl = async (\n serveFile: StaticFileHandler,\n req: IncomingMessage,\n res: ServerResponse,\n rewrittenUrl: string,\n): Promise<void> => {\n const originalUrl = req.url;\n req.url = rewrittenUrl;\n try {\n await serveFile(req, res);\n } finally {\n req.url = originalUrl;\n }\n};\n\nexport const handleStaticAndSpaFallback: HttpRouteHandler = async ({\n req,\n res,\n routePath,\n options,\n}) => {\n // ── denylist — never serve the server bundle or source maps ───────────\n if (SERVE_DENYLIST_REGEX.test(routePath)) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return true;\n }\n\n // ── /assets/* — static assets ──────────────────────────────────────────\n //? `startsWith` not `includes` — avoids matching `/other/assets/foo` and\n //? prevents the indexOf slice from drifting to an interior segment.\n if (routePath.startsWith('/assets/')) {\n if (!options.serveFile) {\n res.writeHead(404);\n res.end('Not Found');\n return true;\n }\n await serveWithRewrittenUrl(options.serveFile, req, res, routePath);\n return true;\n }\n\n // ── *.{png,jpg,...} — known static file extensions ────────────────────\n if (KNOWN_STATIC_FILE_REGEX.test(routePath)) {\n if (!options.serveFile) {\n res.writeHead(404);\n res.end('Not Found');\n return true;\n }\n await options.serveFile(req, res);\n return true;\n }\n\n // ── path with extension we don't recognize — 404 ──────────────────────\n if (path.extname(routePath)) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return true;\n }\n\n // ── catch-all (index.html for SPA routing) ────────────────────────────\n if (!options.serveFile) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return true;\n }\n\n await serveWithRewrittenUrl(options.serveFile, req, res, '/');\n return true;\n};\n","import type { Server as HttpServer } from 'node:http';\nimport type { Socket } from 'socket.io';\nimport type { Redis as RedisClient } from 'ioredis';\nimport { Server as SocketIOServer } from 'socket.io';\nimport {\n abortAllForSocket,\n abortApiByResponseIndex,\n abortSyncByCb,\n allowedOrigin,\n applySocketMiddlewares,\n attachSocketRedisAdapter,\n formatRoomName,\n redis,\n setIoInstance,\n socketEventNames,\n buildJoinRoomResponseEventName,\n buildLeaveRoomResponseEventName,\n buildGetJoinedRoomsResponseEventName,\n extractLanguageFromHeader,\n extractTokenFromSocket,\n getLogger,\n getProjectConfig,\n dispatchHook,\n normalizeErrorResponse,\n readSession,\n writeSession,\n tryCatch,\n type apiMessage,\n type syncMessage,\n type BaseSessionLayout,\n} from '@luckystack/core';\nimport { handleApiRequest } from '@luckystack/api';\n//? login / presence / sync are OPTIONAL peers — resolved + lazy-loaded through\n//? the capability layer so the server boots + runs without them. Session reads\n//? use core's `readSession`/`writeSession` (login populates the provider).\nimport { capabilities, getPresence, getSync } from './capabilities';\n\n//? Per-token lock to serialize session mutations (prevents read-modify-write races\n//? when multiple room joins/leaves arrive in quick succession from the same socket).\nconst sessionLocks = new Map<string, Promise<void>>();\nconst withSessionLock = async (token: string, fn: () => Promise<void>) => {\n const prev = sessionLocks.get(token) ?? Promise.resolve();\n //? Both `then` handlers point to `fn` so a previous lock failure (rejected\n //? promise) doesn't block the next caller — we want each `withSessionLock`\n //? invocation to run regardless of whether the prior one threw.\n const next = prev.then(fn, fn);\n sessionLocks.set(token, next);\n await tryCatch(() => next);\n //? Cleanup runs unconditionally — same semantics the prior `try/finally`\n //? provided. Only delete if we still own the slot (a later caller may\n //? have already overwritten it with their own `next`).\n if (sessionLocks.get(token) === next) sessionLocks.delete(token);\n};\n\nconst getVisibleSocketRooms = (\n socket: { rooms: Set<string>; id: string },\n token: string | null\n): string[] => {\n return [...socket.rooms]\n .filter((room): room is string => typeof room === 'string')\n .filter((room) => room !== socket.id)\n .filter((room) => !token || room !== token);\n};\n\nconst getSessionRoomCodes = (session: BaseSessionLayout): string[] => {\n const roomCodes = Array.isArray(session.roomCodes)\n ? session.roomCodes.filter(\n (roomCode): roomCode is string => typeof roomCode === 'string' && roomCode.length > 0\n )\n : [];\n return [...new Set(roomCodes)];\n};\n\nconst sanitizeSessionRoomKeys = (session: BaseSessionLayout): BaseSessionLayout => {\n const { code: _legacyCode, codes: _legacyCodes, ...sanitizedSession } = session as BaseSessionLayout & {\n code?: string;\n codes?: string[];\n };\n return sanitizedSession;\n};\n\nexport interface LoadSocketOptions {\n maxHttpBufferSize?: number;\n}\n\nexport interface LoadSocketResult {\n io: SocketIOServer;\n /**\n * The duplicated Redis pub/sub clients backing the Socket.io adapter. The\n * server bootstrap owns them so graceful shutdown can `quit()` them — they are\n * NOT closed by `io.close()` (which only tears down the engine + connections).\n * Created here (instead of letting `attachSocketRedisAdapter` duplicate\n * internally) precisely so the server holds a handle to disconnect them.\n */\n adapterClients: { pubClient: RedisClient; subClient: RedisClient };\n}\n\n// ---------------------------------------------------------------------------\n// Per-socket context passed to each per-event registrar\n// ---------------------------------------------------------------------------\n\ninterface SocketContext {\n socket: Socket;\n token: string | null;\n preferredLocale: string | undefined;\n activityBroadcasterEnabled: boolean;\n locationProviderEnabled: boolean;\n shouldLogDev: boolean;\n io: SocketIOServer;\n}\n\n// ---------------------------------------------------------------------------\n// Shared guard: validates the common room-mutation preconditions before the\n// async locked body runs. Returns false and emits the error when invalid.\n// ---------------------------------------------------------------------------\n\nconst validateRoomRequest = (\n socket: Socket,\n responseIndex: number | undefined,\n token: string | null,\n group: string,\n preferredLocale: string | undefined,\n buildResponseEventName: (idx: number) => string\n): responseIndex is number => {\n if (typeof responseIndex !== 'number') return false;\n\n if (!token) {\n socket.emit(buildResponseEventName(responseIndex), normalizeErrorResponse({\n response: { status: 'error', errorCode: 'auth.required' },\n preferredLocale,\n }));\n return false;\n }\n if (!group) {\n socket.emit(buildResponseEventName(responseIndex), normalizeErrorResponse({\n response: { status: 'error', errorCode: 'room.invalid' },\n preferredLocale,\n }));\n return false;\n }\n return true;\n};\n\n// ---------------------------------------------------------------------------\n// Shared async body for room mutations (join / leave). The `mutate` callback\n// performs the transport-specific operation (join or leave) and computes the\n// next roomCodes array; the hook names and blocked error code differ per direction.\n// ---------------------------------------------------------------------------\n\ninterface RoomMutationOptions {\n socket: Socket;\n token: string;\n group: string;\n responseIndex: number;\n preferredLocale: string | undefined;\n shouldLogDev: boolean;\n buildResponseEventName: (idx: number) => string;\n preHook: 'preRoomJoin' | 'preRoomLeave';\n postHook: 'postRoomJoin' | 'postRoomLeave';\n blockedErrorCode: string;\n logVerb: string;\n mutate: (socket: Socket, physicalRoom: string, rawGroup: string, existingRoomCodes: string[]) => Promise<string[]>;\n}\n\nconst executeRoomMutation = async (opts: RoomMutationOptions): Promise<void> => {\n const {\n socket, token, group, responseIndex, preferredLocale, shouldLogDev,\n buildResponseEventName, preHook, postHook, blockedErrorCode, logVerb, mutate,\n } = opts;\n\n const session = await readSession(token);\n if (!session) {\n socket.emit(buildResponseEventName(responseIndex), normalizeErrorResponse({\n response: { status: 'error', errorCode: 'session.notFound' },\n preferredLocale,\n }));\n return;\n }\n\n const preResult = await dispatchHook(preHook, { token, room: group });\n if (preResult.stopped) {\n socket.emit(buildResponseEventName(responseIndex), normalizeErrorResponse({\n response: {\n status: 'error',\n errorCode: preResult.signal.errorCode || blockedErrorCode,\n },\n preferredLocale,\n userLanguage: session.language,\n }));\n return;\n }\n\n const existingRoomCodes = getSessionRoomCodes(session);\n //? Route the raw room code through the core room-name formatter so a\n //? non-identity `registerRoomNameFormatter` (e.g. per-tenant prefixing) applies\n //? to the socket.io room name the socket physically joins/leaves. The session\n //? stores the RAW code; only the Socket.io room name uses the physical form.\n const roomPurpose = preHook === 'preRoomJoin' ? 'join' as const : 'leave' as const;\n const physicalRoom = formatRoomName(group, { purpose: roomPurpose, userId: session.id });\n const nextRoomCodes = await mutate(socket, physicalRoom, group, existingRoomCodes);\n\n const sanitizedSession = sanitizeSessionRoomKeys(session);\n await writeSession(token, { ...sanitizedSession, roomCodes: nextRoomCodes });\n\n const visibleRooms = getVisibleSocketRooms(socket, token);\n socket.emit(buildResponseEventName(responseIndex), { rooms: visibleRooms });\n if (shouldLogDev) {\n getLogger().debug(`Socket ${socket.id} ${logVerb} group ${group}`);\n }\n\n void dispatchHook(postHook, { token, room: group, allRooms: visibleRooms });\n};\n\n// ---------------------------------------------------------------------------\n// Per-event registrar helpers\n// ---------------------------------------------------------------------------\n\nconst registerApiAndSyncEvents = (ctx: SocketContext): void => {\n const { socket, token } = ctx;\n\n socket.on(socketEventNames.apiRequest, (msg: apiMessage) => {\n void handleApiRequest({ msg, socket, token });\n });\n\n //? Only wire the sync listener when @luckystack/sync is installed. Absent =>\n //? clients that emit `sync` get no handler (their request times out / the\n //? HTTP fallback returns `sync.disabled`). Lazy-loaded once on first event.\n if (capabilities.sync) {\n socket.on(socketEventNames.sync, (msg: syncMessage) => {\n void (async () => {\n const sync = await getSync();\n if (sync) await sync.handleSyncRequest({ msg, socket, token });\n })();\n });\n }\n};\n\nconst registerCancellationEvents = (ctx: SocketContext): void => {\n const { socket } = ctx;\n\n //? B1 — cancellation events. Client emits `{ cb }` (sync) or\n //? `{ responseIndex }` (api) on the matching cancel channel; we look up\n //? the in-flight AbortController by `${socket.id}:<key>` and abort it.\n //? Server-side handler chains gate further chunk emits on the signal\n //? and exit early via the cleanup paths registered in each handler.\n socket.on(socketEventNames.syncCancel, (data: { cb?: string }) => {\n const cb = typeof data.cb === 'string' ? data.cb : null;\n if (!cb) return;\n abortSyncByCb(socket.id, cb);\n });\n socket.on(socketEventNames.apiCancel, (data: { responseIndex?: number | string }) => {\n const responseIndex = data.responseIndex;\n if (typeof responseIndex !== 'number' && typeof responseIndex !== 'string') return;\n abortApiByResponseIndex(socket.id, responseIndex);\n });\n};\n\nconst registerRoomEvents = (ctx: SocketContext): void => {\n const { socket, token, preferredLocale, shouldLogDev } = ctx;\n\n socket.on(socketEventNames.joinRoom, (data: { group?: string; responseIndex?: number }) => {\n const group = typeof data.group === 'string' ? data.group.trim() : '';\n const responseIndex = data.responseIndex;\n\n if (!validateRoomRequest(socket, responseIndex, token, group, preferredLocale, buildJoinRoomResponseEventName)) return;\n // validateRoomRequest already rejects null tokens, so this guard is redundant but narrows the type\n if (!token) return;\n\n void withSessionLock(token, async () => {\n await executeRoomMutation({\n socket,\n token,\n group,\n responseIndex,\n preferredLocale,\n shouldLogDev,\n buildResponseEventName: buildJoinRoomResponseEventName,\n preHook: 'preRoomJoin',\n postHook: 'postRoomJoin',\n blockedErrorCode: 'room.joinBlocked',\n logVerb: 'joined',\n mutate: async (sock, physicalRoom, rawGroup, existingCodes) => {\n const nextCodes = [...new Set([...existingCodes, rawGroup])];\n await sock.join(physicalRoom);\n return nextCodes;\n },\n });\n });\n });\n\n socket.on(socketEventNames.leaveRoom, (data: { group?: string; responseIndex?: number }) => {\n const group = typeof data.group === 'string' ? data.group.trim() : '';\n const responseIndex = data.responseIndex;\n\n if (!validateRoomRequest(socket, responseIndex, token, group, preferredLocale, buildLeaveRoomResponseEventName)) return;\n // validateRoomRequest already rejects null tokens, so this guard is redundant but narrows the type\n if (!token) return;\n\n void withSessionLock(token, async () => {\n await executeRoomMutation({\n socket,\n token,\n group,\n responseIndex,\n preferredLocale,\n shouldLogDev,\n buildResponseEventName: buildLeaveRoomResponseEventName,\n preHook: 'preRoomLeave',\n postHook: 'postRoomLeave',\n blockedErrorCode: 'room.leaveBlocked',\n logVerb: 'left',\n mutate: async (sock, physicalRoom, rawGroup, existingCodes) => {\n const nextCodes = existingCodes.filter((c) => c !== rawGroup);\n await sock.leave(physicalRoom);\n return nextCodes;\n },\n });\n });\n });\n\n socket.on(socketEventNames.getJoinedRooms, (data: { responseIndex?: number }) => {\n const responseIndex = data.responseIndex;\n if (typeof responseIndex !== 'number') return;\n\n if (!token) {\n socket.emit(buildGetJoinedRoomsResponseEventName(responseIndex), {\n ...normalizeErrorResponse({\n response: { status: 'error', errorCode: 'auth.required' },\n preferredLocale,\n }),\n rooms: [],\n });\n return;\n }\n\n socket.emit(buildGetJoinedRoomsResponseEventName(responseIndex), {\n rooms: getVisibleSocketRooms(socket, token),\n });\n });\n};\n\nconst registerDisconnectEvent = (ctx: SocketContext): void => {\n const { socket, token, activityBroadcasterEnabled, shouldLogDev } = ctx;\n\n socket.on(socketEventNames.disconnect, (reason: string) => {\n //? B1 — safety-net sweep. Per-request handlers also register their\n //? own `socket.once(disconnect, ...)` listeners that abort + clean up,\n //? but `abortAllForSocket` covers anything that slipped through (e.g.\n //? handler crashed before registering its disconnect listener).\n abortAllForSocket(socket.id);\n void dispatchHook('onSocketDisconnect', { socketId: socket.id, token, reason });\n\n if (activityBroadcasterEnabled) {\n void getPresence().then((presence) => { presence?.clearActivity(socket.id); });\n }\n\n if (activityBroadcasterEnabled && token) {\n void (async () => {\n const presence = await getPresence();\n if (presence) presence.socketDisconnecting({ token, socket, reason });\n })();\n } else {\n if (!token) return;\n if (shouldLogDev) {\n getLogger().debug(`user disconnected`, { reason });\n }\n }\n });\n};\n\nconst registerUpdateLocationEvent = (ctx: SocketContext): void => {\n const { socket, token, activityBroadcasterEnabled, locationProviderEnabled, shouldLogDev } = ctx;\n\n socket.on(\n socketEventNames.updateLocation,\n (newLocation: { pathName: string; searchParams?: Record<string, string> }) => {\n if (!token) return;\n if (!locationProviderEnabled) return;\n if (shouldLogDev) {\n getLogger().debug('updating location', { pathName: newLocation.pathName });\n }\n\n void withSessionLock(token, async () => {\n let returnedUser: BaseSessionLayout | null = null;\n if (activityBroadcasterEnabled) {\n const presence = await getPresence();\n if (presence) {\n returnedUser = await presence.socketLeaveRoom({ token, socket, newPath: newLocation.pathName });\n }\n }\n\n const user = returnedUser ?? (await readSession(token));\n if (!user) return;\n\n const extendedUser = user as BaseSessionLayout & { location?: typeof newLocation };\n const oldLocation = extendedUser.location;\n //? Spread into a fresh object (no in-place mutation) and sanitize\n //? legacy `code`/`codes` keys — same hygiene as the join/leave paths.\n const sanitizedUser = sanitizeSessionRoomKeys({ ...extendedUser, location: newLocation });\n await writeSession(token, sanitizedUser);\n\n void dispatchHook('onLocationUpdate', { token, oldLocation, newLocation });\n });\n }\n );\n};\n\nconst registerActivityEvents = (ctx: SocketContext): void => {\n const { socket, token, activityBroadcasterEnabled, io } = ctx;\n\n if (activityBroadcasterEnabled && token) {\n void (async () => {\n const presence = await getPresence();\n if (presence) presence.initActivityBroadcaster({ socket, token });\n })();\n }\n\n //? Activity tracking (production AFK + custom activity events). Seed the\n //? socket's last-activity on connect, start the single sampler interval\n //? (idempotent), and refresh last-activity on every client heartbeat /\n //? tab-return. The sampler walks all sockets and fires registered events\n //? (built-in AFK + any consumer pause/kick events).\n if (activityBroadcasterEnabled) {\n void (async () => {\n const presence = await getPresence();\n if (!presence) return;\n presence.startActivitySampler({ io });\n presence.recordActivity(socket.id);\n })();\n socket.on(socketEventNames.activity, () => {\n void getPresence().then((presence) => { presence?.recordActivity(socket.id); });\n });\n socket.on(socketEventNames.intentionalReconnect, () => {\n void getPresence().then((presence) => { presence?.recordActivity(socket.id); });\n });\n }\n};\n\nconst rejoinPersistedRooms = (ctx: SocketContext): void => {\n const { socket, token, shouldLogDev } = ctx;\n\n if (!token) return;\n\n //? Rebuild this session's room membership on (re)connect. Socket.io rooms\n //? are per-connection + in-memory, so a page refresh (brand-new socket) or\n //? a server restart drops them while `session.roomCodes` (persisted in\n //? Redis) still lists them. Without replaying them here the reconnected\n //? socket only sits in its private token room, so a `syncRequest` fan-out\n //? (`io.in(room).fetchSockets()`) finds zero members and returns\n //? `sync.noReceiversFound`. Sequenced (await) so membership is restored\n //? ASAP, and logged so a failed/empty rejoin is visible. Idempotent —\n //? re-joining an already-joined room is a no-op.\n void (async () => {\n const [rejoinError, codes] = await tryCatch(async () => {\n //? The token room is a private identity room (not a user-visible room code),\n //? so it is NOT routed through the room-name formatter.\n await socket.join(token);\n const session = await readSession(token);\n const roomCodes = session ? getSessionRoomCodes(session) : [];\n const userId = session?.id ?? null;\n for (const roomCode of roomCodes) {\n //? Route through the formatter so a multi-tenant prefix applies on\n //? reconnect exactly as it did on the original join (PRESENCE-1).\n await socket.join(formatRoomName(roomCode, { purpose: 'join', userId }));\n }\n return roomCodes;\n });\n if (rejoinError) {\n getLogger().warn(`socket room rejoin failed for ${socket.id}`, { error: rejoinError.message });\n return;\n }\n if (shouldLogDev) {\n getLogger().debug(`socket ${socket.id} (re)joined rooms: ${(codes ?? []).join(', ') || '(none)'}`);\n }\n })();\n};\n\nexport const loadSocket = (httpServer: HttpServer, options: LoadSocketOptions = {}): LoadSocketResult => {\n const config = getProjectConfig();\n const shouldLogDev = config.logging.devLogs;\n const shouldLogSocketStartup = config.logging.socketStartup;\n\n const io = new SocketIOServer(httpServer, {\n cors: {\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n origin: (origin, callback) => {\n //? An absent `origin` header on a WebSocket upgrade comes from\n //? server-to-server callers (native WS clients, proxies). Allow them\n //? only when `cors.allowOriginless` is explicitly opted in, or when\n //? the caller is a loopback address — not by default, because\n //? Socket.io's CORS layer is the last browser-origin gate on the WS\n //? path and silently accepting all non-browser upgrades is a\n //? CSRF-analogue vector when the session token is in the handshake.\n if (!origin) {\n if (config.http.cors.allowOriginless) {\n callback(null, true);\n } else {\n callback(new Error('Origin-less WebSocket upgrades are not allowed'));\n }\n return;\n }\n if (allowedOrigin(origin)) {\n callback(null, true);\n } else {\n callback(new Error('Not allowed by CORS'));\n }\n },\n credentials: true,\n },\n maxHttpBufferSize: options.maxHttpBufferSize ?? config.socket.maxHttpBufferSize,\n pingTimeout: config.socket.pingTimeout,\n pingInterval: config.socket.pingInterval,\n });\n\n setIoInstance(io);\n\n //? Apply consumer-registered Socket.io middlewares BEFORE the connect\n //? handler is attached so they run on the handshake of every incoming\n //? socket — same contract as a direct `io.use(...)` call.\n applySocketMiddlewares(io);\n\n //? Redis adapter so room broadcasts fan out across instances. Required for\n //? split/fallback deployments; safe overhead in single-instance deploys.\n //? We duplicate the pub/sub clients HERE and pass them in (rather than letting\n //? `attachSocketRedisAdapter` duplicate internally) so the server bootstrap\n //? holds the handles and can `quit()` them on graceful shutdown — `io.close()`\n //? does not close these adapter connections.\n const pubClient = redis.duplicate();\n const subClient = redis.duplicate();\n attachSocketRedisAdapter(io, { pubClient, subClient });\n\n if (shouldLogSocketStartup) {\n getLogger().info('SocketIO server initialized (redis adapter attached)');\n }\n\n io.on(socketEventNames.connect, (socket) => {\n const token = extractTokenFromSocket(socket);\n //? Cache per-connection feature flags so we don't refetch the project\n //? config on every event.\n const activityBroadcasterEnabled = config.socketActivityBroadcaster ?? false;\n const locationProviderEnabled = config.locationProviderEnabled ?? false;\n //? `extractLanguageFromHeader` can return an empty string for unparseable\n //? headers — those should fall through to the next source, so keep `||`.\n /* eslint-disable @typescript-eslint/prefer-nullish-coalescing -- see comment above */\n const preferredLocale =\n extractLanguageFromHeader(socket.handshake.headers['x-language'])\n || extractLanguageFromHeader(socket.handshake.headers['accept-language'])\n || undefined;\n /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */\n\n if (token && capabilities.presence) {\n void (async () => {\n const presence = await getPresence();\n if (presence) await presence.socketConnected({ token, io });\n })();\n }\n\n void dispatchHook('onSocketConnect', {\n socketId: socket.id,\n token,\n ip: socket.handshake.address,\n });\n\n const ctx: SocketContext = {\n socket,\n token,\n preferredLocale,\n activityBroadcasterEnabled,\n locationProviderEnabled,\n shouldLogDev,\n io,\n };\n\n registerApiAndSyncEvents(ctx);\n registerCancellationEvents(ctx);\n registerRoomEvents(ctx);\n registerDisconnectEvent(ctx);\n registerUpdateLocationEvent(ctx);\n registerActivityEvents(ctx);\n rejoinPersistedRooms(ctx);\n });\n\n return { io, adapterClients: { pubClient, subClient } };\n};\n","//? Boot-time validation that every registry the framework relies on has been\n//? populated by the project's overlay files. Replaces silent runtime crashes\n//? deep inside a request handler with a single, descriptive error thrown\n//? before `httpServer.listen()` is called.\n//?\n//? The check is intentionally lightweight: it only confirms that the required\n//? registrations happened. It does not validate the contents of those\n//? registrations — that's done by the registries' own type / runtime\n//? validators (e.g. `getProjectConfig()` always returns a deeply-merged value).\n\nimport {\n collectSynchronizedEnvKeys,\n getLogger,\n getProjectConfig,\n isDeployConfigRegistered,\n isLocalizedNormalizerRegistered,\n isProjectConfigRegistered,\n isRuntimeMapsProviderRegistered,\n} from '@luckystack/core';\nimport { getLogin } from './capabilities';\n\nexport interface BootstrapRequirements {\n /**\n * If true, require a deploy config to have been registered. Defaults to\n * false because single-instance deployments don't need one.\n */\n requireDeployConfig?: boolean;\n /**\n * If true, require a services config to have been registered. Defaults to\n * false; only needed when running the router.\n */\n requireServicesConfig?: boolean;\n /**\n * If true, require an OAuth provider list to have been registered.\n * Defaults to false (an app may use credentials only).\n */\n requireOAuthProviders?: boolean;\n}\n\nexport const verifyBootstrap = async (requirements: BootstrapRequirements = {}): Promise<void> => {\n const missing: string[] = [];\n\n if (!isProjectConfigRegistered()) {\n missing.push(\n 'ProjectConfig — call `registerProjectConfig({...})` from `luckystack/core/bootstrap.ts` (or your config.ts).'\n );\n }\n\n if (requirements.requireDeployConfig && !isDeployConfigRegistered()) {\n missing.push(\n 'DeployConfig — call `registerDeployConfig({...})` from `luckystack/deploy/deploy.config.ts`.'\n );\n }\n\n if (requirements.requireServicesConfig) {\n const { isServicesConfigRegistered } = await import('@luckystack/core');\n if (!isServicesConfigRegistered()) {\n missing.push(\n 'ServicesConfig — call `registerServicesConfig({...})` from `services.config.ts`.'\n );\n }\n }\n\n if (requirements.requireOAuthProviders) {\n const login = await getLogin();\n if (!login) {\n missing.push(\n 'OAuth providers required (`requireOAuthProviders`) but `@luckystack/login` is not installed. Install it, or drop the requirement if this app has no auth.'\n );\n } else if (login.getOAuthProviders().length <= 1) {\n // Default registry is `[{ name: 'credentials' }]` — anything ≤1 means\n // the consumer never registered providers.\n missing.push(\n 'OAuth providers — call `registerOAuthProviders([...])` from `luckystack/login/oauthProviders.ts` (or skip this check if your app uses credentials only).'\n );\n }\n }\n\n //? Runtime maps provider — without it, every API/sync request silently\n //? returns `notFound`. Hard-fail in production; loud-warn in dev because\n //? tests and the bare-server dev mode legitimately boot without one.\n if (!isRuntimeMapsProviderRegistered()) {\n if (process.env.NODE_ENV === 'production') {\n missing.push(\n 'RuntimeMapsProvider — call `registerRuntimeMapsProvider({...})` from `server/prod/runtimeMaps.ts`. Without it, every api/sync request returns notFound.'\n );\n } else {\n getLogger().warn(\n '[LuckyStack] No RuntimeMapsProvider registered — api/sync requests will resolve to empty maps. Devkit hot-reload usually registers one automatically.',\n );\n }\n }\n\n //? Localized normalizer — without it, error responses degrade to\n //? errorCode-as-message (no i18n). Hard-fail in production; warn in dev.\n if (!isLocalizedNormalizerRegistered()) {\n if (process.env.NODE_ENV === 'production') {\n missing.push(\n 'LocalizedNormalizer — call `registerLocalizedNormalizer({...})` from your bootstrap. Without it, error response messages will be the raw errorCode (no i18n).'\n );\n } else {\n getLogger().warn(\n '[LuckyStack] No LocalizedNormalizer registered — error messages will pass through as the raw errorCode.',\n );\n }\n }\n\n //? SEC-13: the unauthenticated `/_health` endpoint emits `sha256(<secret>)`\n //? fingerprints of every `synchronizedEnvKeys` value so the router can detect\n //? cross-env drift. With the default `healthHash.mode` of `'plain'` those\n //? digests are UNSALTED — a public, offline-bruteforceable fingerprint of\n //? production secrets. Loudly warn (not hard-fail, to preserve boot behavior)\n //? so the operator opts into `http.healthHash.mode: 'hmac'` (or `'salted'`\n //? with salt `'@bootUuid'`) when synchronized secrets are in play.\n if (isProjectConfigRegistered()) {\n const synchronizedKeyCount = collectSynchronizedEnvKeys().length;\n const healthHashMode = getProjectConfig().http.healthHash.mode;\n if (synchronizedKeyCount > 0 && healthHashMode === 'plain') {\n getLogger().warn(\n '[LuckyStack] SECURITY: /_health exposes UNSALTED sha256 fingerprints of '\n + `${String(synchronizedKeyCount)} synchronized env secret(s) by default. `\n + \"Set `http.healthHash.mode` to 'hmac' (or 'salted' with salt '@bootUuid') \"\n + 'to stop publishing brute-forceable secret fingerprints to unauthenticated callers.',\n );\n }\n }\n\n if (missing.length === 0) return;\n\n const detail = missing.map((line, idx) => ` ${idx + 1}. ${line}`).join('\\n');\n throw new Error(\n [\n '[LuckyStack] Bootstrap incomplete — the following registrations are missing:',\n detail,\n '',\n 'See docs/ARCHITECTURE_PACKAGING.md (overlay layout) for the recommended bootstrap order.',\n ].join('\\n')\n );\n};\n","//? Production runtime-maps loader shipped by the framework so consumers\n//? don't have to maintain their own `server/prod/runtimeMaps.ts`. The\n//? consumer only supplies a `loadGenerated` callback because dynamic-import\n//? path resolution is module-scoped — the framework cannot resolve\n//? `./generatedApis.<preset>` on the consumer's behalf.\n//?\n//? Multiple presets can be loaded into a single process by passing a\n//? comma-separated list as the first positional argv (parsed by\n//? `@luckystack/server/parseArgv`). All resolved maps are shallow-merged\n//? into one runtime view; collisions throw at boot (services own one\n//? preset by design, see docs/ARCHITECTURE_PACKAGING.md §10).\n//?\n//? Dev branch lazy-imports `@luckystack/devkit` to pull the auto-discovered\n//? api/sync/function maps. Devkit is excluded from the production bundle, so\n//? this dynamic import is never reached when NODE_ENV === 'production'.\n\nimport {\n getLogger,\n registerRuntimeMapsProvider,\n type RuntimeApiMapsResult,\n type RuntimeMapsProvider,\n type RuntimeSyncMapsResult,\n} from '@luckystack/core';\nimport { getParsedBundles } from './argv';\n\ntype RuntimeMapRecord = Record<string, unknown>;\n\n/**\n * The shape that the generated maps module must export. `scripts/generateServerRequests.ts`\n * emits exactly this shape: `{ apis, syncs, functions }` where each value is a\n * `Record<string, Handler>`. Consumers can import this type to validate their\n * generated file in CI or type-check the `loadGenerated` callback return.\n */\nexport interface GeneratedRuntimeMapsModule {\n apis: RuntimeMapRecord;\n syncs: RuntimeMapRecord;\n functions: RuntimeMapRecord;\n}\n\ninterface LoadedRuntimeMaps {\n apisObject: RuntimeMapRecord;\n syncObject: RuntimeMapRecord;\n functionsObject: RuntimeMapRecord;\n}\n\ninterface DevkitRuntimeMaps {\n devApis: RuntimeMapRecord;\n devSyncs: RuntimeMapRecord;\n devFunctions: RuntimeMapRecord;\n}\n\nconst emptyRuntimeMaps: LoadedRuntimeMaps = {\n apisObject: {},\n syncObject: {},\n functionsObject: {},\n};\n\nconst isRuntimeMapRecord = (value: unknown): value is RuntimeMapRecord =>\n Boolean(value) && typeof value === 'object';\n\nconst normalizeGeneratedModule = (moduleValue: unknown, preset: string): LoadedRuntimeMaps => {\n const moduleRecord = moduleValue && typeof moduleValue === 'object'\n ? (moduleValue as Record<string, unknown>)\n : {};\n\n const apiCandidate = moduleRecord.apis;\n const syncCandidate = moduleRecord.syncs;\n const functionCandidate = moduleRecord.functions;\n\n //? Warn when none of the expected keys are present — this is almost certainly\n //? a mis-shaped module (wrong export, stale generated file, default-export\n //? wrapper) rather than a legitimately empty app. Silent coercion to `{}`\n //? causes every route to silently 404, which is hard to diagnose.\n if (\n !isRuntimeMapRecord(apiCandidate)\n && !isRuntimeMapRecord(syncCandidate)\n && !isRuntimeMapRecord(functionCandidate)\n ) {\n getLogger().warn(\n `[luckystack:runtimeMaps] preset \"${preset}\" loaded but has no recognised shape ` +\n `(expected { apis, syncs, functions }). ` +\n `Verify that generateServerRequests has been run and the correct file is imported. ` +\n `All routes for this preset will return notFound.`,\n );\n }\n\n return {\n apisObject: isRuntimeMapRecord(apiCandidate) ? apiCandidate : {},\n syncObject: isRuntimeMapRecord(syncCandidate) ? syncCandidate : {},\n functionsObject: isRuntimeMapRecord(functionCandidate) ? functionCandidate : {},\n };\n};\n\nexport interface ProdRuntimeMapsLoaderOptions {\n /**\n * Dynamic-import callback for the generated maps module of a given preset.\n * Called once per preset per process lifetime; the result is cached.\n *\n * The resolved module must have shape `{ apis, syncs, functions }` (the\n * shape `scripts/generateServerRequests.ts` emits — see\n * {@link GeneratedRuntimeMapsModule}). A mis-shaped module logs a warning\n * and results in every route for that preset returning `notFound`.\n *\n * Pass a function that calls `import()` with a path relative to YOUR\n * server-side module — the framework cannot resolve a relative path on\n * your behalf because dynamic-import resolution is module-scoped.\n *\n * @example\n * loadGenerated: (preset) => import(`./prod/generatedApis.${preset}`)\n */\n //? Returns `unknown` because the consumer hands us an `import()` of a generated\n //? module whose shape we cannot statically guarantee; `normalizeGeneratedModule`\n //? validates it at runtime against `GeneratedRuntimeMapsModule` and warns on drift.\n loadGenerated: (preset: string) => Promise<unknown>;\n /**\n * Override the preset(s) to load. Skips the argv lookup. Accepts a single\n * preset name or an array. Useful in tests or when the preset list comes\n * from a non-argv source.\n */\n preset?: string | string[];\n}\n\nconst resolvePresets = (options: ProdRuntimeMapsLoaderOptions): string[] => {\n const fromOptions = options.preset;\n if (typeof fromOptions === 'string' && fromOptions.length > 0) {\n return [fromOptions];\n }\n if (Array.isArray(fromOptions) && fromOptions.length > 0) {\n return [...new Set(fromOptions)];\n }\n const fromArgv = getParsedBundles();\n if (fromArgv.length > 0) {\n return fromArgv;\n }\n return ['default'];\n};\n\nconst mergeInto = (\n target: RuntimeMapRecord,\n source: RuntimeMapRecord,\n kind: 'api' | 'sync' | 'function',\n fromPreset: string,\n keyOrigin: Map<string, string>,\n): void => {\n for (const key of Object.keys(source)) {\n const previousPreset = keyOrigin.get(key);\n if (previousPreset !== undefined && previousPreset !== fromPreset) {\n throw new Error(\n `[luckystack:runtimeMaps] ${kind} key collision: \"${key}\" present in both ` +\n `preset \"${previousPreset}\" and preset \"${fromPreset}\". ` +\n `Services must belong to exactly one preset (see docs/ARCHITECTURE_PACKAGING.md §10).`,\n );\n }\n keyOrigin.set(key, fromPreset);\n target[key] = source[key];\n }\n};\n\n/**\n * Build a `RuntimeMapsProvider` that loads generated maps in production and\n * delegates to `@luckystack/devkit`'s discovery in dev. Same shape consumers\n * used to hand-roll in `server/prod/runtimeMaps.ts`.\n *\n * Call `registerRuntimeMapsProvider(...)` with the result, or use\n * `registerProdRuntimeMapsProvider(...)` (this module) to do both in one\n * step.\n */\nconst isProduction = (): boolean => process.env.NODE_ENV === 'production';\n\nexport const createProdRuntimeMapsProvider = (\n options: ProdRuntimeMapsLoaderOptions,\n): RuntimeMapsProvider => {\n let prodMapsPromise: Promise<LoadedRuntimeMaps> | null = null;\n\n let devkitModulePromise: Promise<DevkitRuntimeMaps> | null = null;\n const getDevkit = async (): Promise<DevkitRuntimeMaps> => {\n devkitModulePromise ??= import('@luckystack/devkit') as Promise<DevkitRuntimeMaps>;\n return await devkitModulePromise;\n };\n\n const loadProdRuntimeMaps = async (): Promise<LoadedRuntimeMaps> => {\n if (prodMapsPromise) return await prodMapsPromise;\n\n prodMapsPromise = (async () => {\n const presets = resolvePresets(options);\n const merged: LoadedRuntimeMaps = {\n apisObject: {},\n syncObject: {},\n functionsObject: {},\n };\n const apiOrigin = new Map<string, string>();\n const syncOrigin = new Map<string, string>();\n const functionOrigin = new Map<string, string>();\n\n const loadedModules = await Promise.all(\n presets.map(async (preset) => ({\n preset,\n mod: await options.loadGenerated(preset).catch(() => null),\n })),\n );\n\n let loadedAny = false;\n for (const { preset, mod } of loadedModules) {\n if (!mod) {\n getLogger().warn(\n `[luckystack:runtimeMaps] preset \"${preset}\" failed to load — skipping. ` +\n `Calls owned by that preset will return notFound until the generated module resolves.`,\n );\n continue;\n }\n loadedAny = true;\n const normalized = normalizeGeneratedModule(mod, preset);\n mergeInto(merged.apisObject, normalized.apisObject, 'api', preset, apiOrigin);\n mergeInto(merged.syncObject, normalized.syncObject, 'sync', preset, syncOrigin);\n mergeInto(merged.functionsObject, normalized.functionsObject, 'function', preset, functionOrigin);\n }\n\n if (!loadedAny) {\n getLogger().warn(\n `[luckystack:runtimeMaps] no presets resolved (tried: ${presets.join(', ')}). ` +\n `Every api/sync request will return notFound until at least one generated module loads.`,\n );\n return emptyRuntimeMaps;\n }\n\n return merged;\n })();\n\n return await prodMapsPromise;\n };\n\n const getRuntimeApiMaps = async (): Promise<RuntimeApiMapsResult> => {\n if (!isProduction()) {\n const { devApis, devFunctions } = await getDevkit();\n return {\n apisObject: isRuntimeMapRecord(devApis) ? devApis : {},\n functionsObject: isRuntimeMapRecord(devFunctions) ? devFunctions : {},\n };\n }\n const { apisObject, functionsObject } = await loadProdRuntimeMaps();\n return { apisObject, functionsObject };\n };\n\n const getRuntimeSyncMaps = async (): Promise<RuntimeSyncMapsResult> => {\n if (!isProduction()) {\n const { devSyncs, devFunctions } = await getDevkit();\n return {\n syncObject: isRuntimeMapRecord(devSyncs) ? devSyncs : {},\n functionsObject: isRuntimeMapRecord(devFunctions) ? devFunctions : {},\n };\n }\n const { syncObject, functionsObject } = await loadProdRuntimeMaps();\n return { syncObject, functionsObject };\n };\n\n return { getRuntimeApiMaps, getRuntimeSyncMaps };\n};\n\n/**\n * Convenience wrapper around `createProdRuntimeMapsProvider` +\n * `registerRuntimeMapsProvider`. Most consumers want this — pass the\n * loader callback once and the runtime is wired.\n */\nexport const registerProdRuntimeMapsProvider = (\n options: ProdRuntimeMapsLoaderOptions,\n): RuntimeMapsProvider => {\n const provider = createProdRuntimeMapsProvider(options);\n registerRuntimeMapsProvider(provider);\n return provider;\n};\n","import type { Server as HttpServer } from 'node:http';\nimport type { Server as SocketIOServer } from 'socket.io';\nimport type { Redis as RedisClient } from 'ioredis';\nimport {\n dispatchHook,\n flushErrorTrackers,\n getLogger,\n tryCatch,\n} from '@luckystack/core';\nimport type { StopLuckyStackServerOptions } from './types';\n\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;\n\nexport interface GracefulShutdownDeps {\n httpServer: HttpServer;\n ioServer: SocketIOServer;\n adapterClients: { pubClient: RedisClient; subClient: RedisClient };\n}\n\n//? Race a promise against a timeout so one hanging shutdown step (a stuck\n//? `flush`, a socket that never closes, a half-dead Redis connection) can never\n//? stall the whole sequence past the deadline. Resolves `true` if `fn`\n//? completed, `false` if the timeout won. Never rejects — shutdown is\n//? best-effort and a thrown step must not abort the remaining steps.\nconst withTimeout = async (\n label: string,\n timeoutMs: number,\n fn: () => Promise<void>,\n): Promise<boolean> => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<false>((resolve) => {\n timer = setTimeout(() => {\n getLogger().warn(`[shutdown] step \"${label}\" exceeded ${String(timeoutMs)}ms — moving on`);\n resolve(false);\n }, timeoutMs);\n //? Don't let the timer keep the event loop alive once everything else is done.\n timer.unref();\n });\n const run = (async (): Promise<true> => {\n const [error] = await tryCatch(fn);\n if (error) getLogger().warn(`[shutdown] step \"${label}\" failed: ${error.message}`);\n return true;\n })();\n const completed = await Promise.race([run, timeout]);\n if (timer) clearTimeout(timer);\n return completed;\n};\n\nconst closeHttpServer = (httpServer: HttpServer): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n //? `close()` stops accepting NEW connections and fires the callback once all\n //? existing connections end. The timeout race in the caller bounds the wait\n //? for slow keep-alive clients. Surface the callback error (e.g.\n //? `ERR_SERVER_NOT_RUNNING` when close() is called before listen()) so the\n //? caller's `withTimeout` tryCatch logs it like every other shutdown step,\n //? instead of resolving silently.\n httpServer.close((error) => { if (error) reject(error); else resolve(); });\n });\n\nconst closeIoServer = (ioServer: SocketIOServer): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n //? `io.close(cb)` also returns a promise in socket.io 4.x; we drive\n //? completion off the callback, so explicitly ignore the returned promise.\n //? Surface the callback error (forwarded from the underlying http close) so\n //? the caller's `withTimeout` tryCatch logs it instead of swallowing it.\n void ioServer.close((error) => { if (error) reject(error); else resolve(); });\n });\n\nconst quitRedisClient = async (client: RedisClient): Promise<void> => {\n await client.quit();\n};\n\n/**\n * Run the graceful-shutdown sequence (MIS-016). Ordered + isolated:\n * 1. Stop accepting NEW HTTP connections (`httpServer.close()` is kicked off\n * immediately; we await it last so in-flight requests can drain).\n * 2. Dispatch the core `preServerStop` hook so consumers can flush queues /\n * release leases / close pools.\n * 3. `flushErrorTrackers()` so buffered events aren't lost — bounded by the\n * timeout race.\n * 4. Close the Socket.io server, then the HTTP server, then `quit()` the\n * Redis-adapter pub/sub clients.\n *\n * Every step is wrapped in {@link withTimeout} so a single failing/hanging step\n * cannot hang the whole shutdown — the sequence always settles within\n * `timeoutMs` per step. Never rejects.\n */\nexport const runGracefulShutdown = async (\n deps: GracefulShutdownDeps,\n options: StopLuckyStackServerOptions = {},\n): Promise<void> => {\n const { httpServer, ioServer, adapterClients } = deps;\n const reason = options.reason ?? 'manual';\n const timeoutMs = options.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\n getLogger().info(`[shutdown] graceful shutdown started (reason=${reason})`);\n\n //? Kick off \"stop accepting new connections\" first. `httpServer.close()` only\n //? finishes once existing connections drain, so START it now and AWAIT it near\n //? the end — that way the hook + flush run while connections wind down.\n const httpClosed = withTimeout('http-close', timeoutMs, () => closeHttpServer(httpServer));\n\n //? Best-effort consumer shutdown hook. A returned stop signal is ignored — the\n //? process is going down — and the dispatcher already swallows per-handler\n //? throws; the timeout guards against a handler that simply never resolves.\n await withTimeout('preServerStop-hook', timeoutMs, async () => {\n await dispatchHook('preServerStop', { reason, timeoutMs });\n });\n\n //? Flush buffered error-tracker events before tearing down. Bounded so a\n //? wedged transport (e.g. Sentry unreachable) can't hang shutdown.\n await withTimeout('flush-error-trackers', timeoutMs, () => flushErrorTrackers());\n\n //? Close the socket layer before the HTTP server fully settles so no new\n //? socket upgrades sneak in.\n await withTimeout('io-close', timeoutMs, () => closeIoServer(ioServer));\n\n await httpClosed;\n\n //? Disconnect the Redis-adapter pub/sub clients — `io.close()` leaves them\n //? open, so without this the process keeps two live Redis sockets.\n await withTimeout('redis-pub-quit', timeoutMs, () => quitRedisClient(adapterClients.pubClient));\n await withTimeout('redis-sub-quit', timeoutMs, () => quitRedisClient(adapterClients.subClient));\n\n getLogger().info('[shutdown] graceful shutdown complete');\n};\n","//? One-call bootstrap helper. Auto-imports the consumer's `luckystack/`\n//? overlay folder in the canonical order, then starts the server.\n//?\n//? The convention is documented in `docs/ARCHITECTURE_PACKAGING.md`. Each\n//? overlay file calls one of the framework's `register*` functions at module\n//? load (side-effect import), so by the time `createLuckyStackServer` runs\n//? the registries are populated.\n//?\n//? Order matters because some registries depend on others (e.g. login's\n//? userAdapter needs the Prisma client registered first). The framework\n//? imports them in topological order.\n\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport { ROOT_DIR } from '@luckystack/core';\nimport { createLuckyStackServer } from './createServer';\nimport { OPTIONAL_PACKAGES, canResolve, getLogin } from './capabilities';\nimport type {\n CreateLuckyStackServerOptions,\n RunningLuckyStackServer,\n} from './types';\n\nexport interface BootstrapLuckyStackOptions extends CreateLuckyStackServerOptions {\n /**\n * Folder name (relative to project root) that contains the overlay files.\n * Default: `luckystack`. Each subfolder mirrors a framework package and\n * holds the project's overrides for that package.\n */\n overlayRoot?: string;\n /**\n * Skip auto-loading the overlay folder. Useful for tests that build the\n * registries by hand.\n */\n skipOverlayLoad?: boolean;\n}\n\nconst OVERLAY_ORDER = [\n // Core registries first — clients, paths, routing rules. Anything below\n // depends on these being in place.\n 'core',\n // Deploy + services topology — needed by the router but harmless for\n // single-instance deploys.\n 'deploy',\n // Auth — OAuth providers + user adapter sit on top of core.\n 'login',\n // Transactional email sender registration (optional @luckystack/email).\n 'email',\n // Sentry / docs-ui / presence sit on top of core but don't block boot.\n 'sentry',\n 'presence',\n 'docs-ui',\n // Server overlay last — typically empty, but a place to wire framework\n // hooks (`registerHook('onSocketConnect', ...)` etc.) before listen().\n 'server',\n];\n\nconst importIfExists = async (filePath: string): Promise<void> => {\n if (!fs.existsSync(filePath)) return;\n await import(pathToFileURL(filePath).href);\n};\n\nconst loadOverlayFolder = async (overlayRoot: string): Promise<void> => {\n const overlayAbs = path.isAbsolute(overlayRoot)\n ? overlayRoot\n : path.join(ROOT_DIR, overlayRoot);\n\n if (!fs.existsSync(overlayAbs)) {\n //? No overlay folder is fine — projects can register everything from a\n //? single `config.ts` if they prefer the legacy layout.\n return;\n }\n\n for (const packageName of OVERLAY_ORDER) {\n const packageDir = path.join(overlayAbs, packageName);\n if (!fs.existsSync(packageDir)) continue;\n\n //? Load `index.ts` first if present, then any other `*.ts` files in\n //? alphabetical order. Each file is responsible for its own\n //? side-effect registration.\n const indexCandidates = ['index.ts', 'index.js'];\n for (const candidate of indexCandidates) {\n await importIfExists(path.join(packageDir, candidate));\n }\n\n const entries = fs.readdirSync(packageDir).toSorted();\n for (const entry of entries) {\n if (indexCandidates.includes(entry)) continue;\n if (!entry.endsWith('.ts') && !entry.endsWith('.js')) continue;\n await importIfExists(path.join(packageDir, entry));\n }\n }\n};\n\n//? Auto-detect phase (0.2.0 \"install-anything-anytime\"). For each optional\n//? package that ships a `./register` side-effect subpath, import it so the\n//? package self-wires from env WITHOUT any consumer code edit — `npm i\n//? @luckystack/<pkg>` + env + restart is enough. Resolve-guarded so an absent\n//? package is a silent no-op; a single register's internal failure must never\n//? crash boot (register modules log their own errors). Runs BEFORE\n//? `loadOverlayFolder` so a consumer overlay file (last writer) can override the\n//? auto-wired defaults.\nconst importOptionalPackageRegisters = async (): Promise<void> => {\n for (const pkg of OPTIONAL_PACKAGES) {\n const specifier = `@luckystack/${pkg}/register`;\n if (!canResolve(specifier)) continue;\n await importIfExistsSpecifier(specifier);\n }\n};\n\nconst importIfExistsSpecifier = async (specifier: string): Promise<void> => {\n try {\n await import(specifier);\n } catch {\n //? A register module that throws at load (e.g. an internal peer-dep error)\n //? should not take the whole server down — the module is responsible for\n //? logging its own failure. Boot continues with that feature degraded.\n }\n};\n\nexport const bootstrapLuckyStack = async (\n options: BootstrapLuckyStackOptions = {}\n): Promise<RunningLuckyStackServer> => {\n const overlayRoot = options.overlayRoot ?? 'luckystack';\n\n if (!options.skipOverlayLoad) {\n //? Package self-wiring first, consumer overlay second (overlay overrides).\n await importOptionalPackageRegisters();\n await loadOverlayFolder(overlayRoot);\n }\n\n //? Force login to load when installed so its session provider registers into\n //? core (`registerSessionProvider`, side-effect of the package index) even in\n //? an app that never imports any `@luckystack/login` export directly. No-op\n //? when login is absent — the app runs unauthenticated.\n await getLogin();\n\n const server = await createLuckyStackServer(options);\n return server;\n};\n","//? Backward-compatibility re-export. The error-formatter registry moved\n//? to @luckystack/core so transport handlers in @luckystack/api and\n//? @luckystack/sync can dispatch per-route + global formatters without\n//? depending on this server package (which would cycle). Consumers'\n//? existing `import { registerErrorFormatter } from '@luckystack/server'`\n//? continues to resolve through this shim.\n\nexport {\n registerErrorFormatter,\n getErrorFormatter,\n applyErrorFormatter,\n} from '@luckystack/core';\nexport type { ErrorFormatter, ErrorFormatterContext } from '@luckystack/core';\n"],"mappings":";;;;;;;;AAAA,OAAO,UAAyC;AAChD,SAAS,qBAAqB,eAAe,aAAAA,aAAW,oBAAAC,oBAAkB,YAAAC,iBAAgB;;;ACA1F,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA,gBAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,aAAAC;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA,eAAAC;AAAA,EACA;AAAA,OACK;;;ACRP,SAAS,kBAAkB,wBAAwB;AAEnD,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAK9B,IAAM,qBAAqB;AAE3B,IAAM,gBAAgB,CAAC,QAAyB;AAC9C,MAAI,iBAAiB,GAAG,EAAG,QAAO;AAClC,SAAO,IAAI,YAAY,MAAM,iBAAiB,EAAE,KAAK,kBAAkB,YAAY;AACrF;AAEA,IAAM,qBAAqB,CAAC,OAAgB,OAAe,SAAmC;AAC5F,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AAExD,MAAI,KAAK,IAAI,KAAK,EAAG,QAAO;AAC5B,MAAI,SAAS,mBAAoB,QAAO;AAExC,OAAK,IAAI,KAAK;AACd,MAAI;AACF,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,aAAO,MAAM,IAAI,CAAC,UAAU,mBAAmB,OAAO,QAAQ,GAAG,IAAI,CAAC;AAAA,IACxE;AACA,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACzE,UAAI,GAAG,IAAI,cAAc,GAAG,IAAI,uBAAuB,mBAAmB,KAAK,QAAQ,GAAG,IAAI;AAAA,IAChG;AACA,WAAO;AAAA,EACT,UAAE;AAEA,SAAK,OAAO,KAAK;AAAA,EACnB;AACF;AAEO,IAAM,iBAAiB,CAAC,UAA4B,mBAAmB,OAAO,GAAG,oBAAI,QAAQ,CAAC;;;AC3BrG,SAAS,sBAAsB;AAQ/B,IAAM,kBAAkB,eAA8C,IAAI;AAUnE,IAAM,0BAA0B,CAAC,YAAiD;AACvF,kBAAgB,SAAS,OAAO;AAClC;AAGO,IAAM,4BAA4B,MAAqC,gBAAgB,IAAI;;;ACpClG,SAAS,cAAc,gBAAgB,eAAe,oBAAAC,mBAAkB,mBAAmB;;;ACS3F,SAAS,qBAAqB;AAE9B,IAAM,eAAe,cAAc,YAAY,GAAG;AAYlD,IAAM,aAAc,YAA4D;AAMhF,IAAI,yBAAyB;AAC7B,IAAM,0BAA0B,MAAY;AAC1C,MAAI,uBAAwB;AAC5B,2BAAyB;AAEzB,UAAQ;AAAA,IACN;AAAA,EAIF;AACF;AAEA,IAAM,MAAM,CAAC,QAAyB;AACpC,MAAI,OAAO,eAAe,YAAY;AACpC,QAAI;AACF,iBAAW,GAAG;AACd,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAIA,0BAAwB;AACxB,MAAI;AACF,iBAAa,QAAQ,GAAG;AACxB,WAAO;AAAA,EACT,SAAS,OAAgB;AACvB,QAAI,iBAAiB,SAAU,MAAgC,SAAS,iCAAiC;AACvG,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;AAGO,IAAM,eAAe;AAAA,EAC1B,OAAO,IAAI,mBAAmB;AAAA,EAC9B,UAAU,IAAI,sBAAsB;AAAA,EACpC,MAAM,IAAI,kBAAkB;AAC9B;AAWO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,aAAa,CAAC,cAA+B,IAAI,SAAS;AAEvE,IAAI;AACG,IAAM,WAAW,YAAgE;AACtF,MAAI,aAAa,OAAW,QAAO;AACnC,aAAW,aAAa,QAAQ,MAAM,OAAO,mBAAmB,IAAI;AACpE,SAAO;AACT;AAEA,IAAI;AACG,IAAM,cAAc,YAAmE;AAC5F,MAAI,gBAAgB,OAAW,QAAO;AACtC,gBAAc,aAAa,WAAW,MAAM,OAAO,sBAAsB,IAAI;AAC7E,SAAO;AACT;AAEA,IAAI;AACG,IAAM,UAAU,YAA+D;AACpF,MAAI,YAAY,OAAW,QAAO;AAClC,YAAU,aAAa,OAAO,MAAM,OAAO,kBAAkB,IAAI;AACjE,SAAO;AACT;;;AC9FA,IAAM,cAAqC,CAAC;AAErC,IAAM,2BAA2B,CAAC,YAAuC;AAC9E,cAAY,KAAK,OAAO;AAC1B;AAEO,IAAM,uBAAuB,MAAsC;AAEnE,IAAM,yBAAyB,MAAY;AAChD,cAAY,SAAS;AACvB;AAIO,IAAM,qBAAqB,CAAC,cACjC,YAAY,KAAK,CAAC,YAAY,UAAU,WAAW,QAAQ,UAAU,CAAC;;;ACnCxE,SAAS,mBAAmB,6BAA6B;AAQlD,IAAM,wBAAwB,CAAC,GAAW,MAAuB;AACtE,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,SAAO,sBAAsB,MAAM,IAAI;AACzC;;;AHLO,IAAM,oCAAoC,OAAO;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMwB;AACtB,QAAM,SAASC,kBAAiB;AAChC,QAAM,eAAe,CAAC,OAAO,QAAQ;AAIrC,QAAM,kBAAkB,IAAI,WAAW,SAAS,IAAI,WAAW,UAAU,IAAI,WAAW;AACxF,QAAM,iBAAiB,UAAU,WAAW,gBAAgB;AAO5D,QAAM,kBAAkB,cAAc;AAKtC,QAAM,mBACJ,mBACG,kBACA,mBAAmB,SAAS;AACjC,QAAM,kBACJ,UAAU,WAAW,OAAO,KACzB,UAAU,WAAW,QAAQ,KAC7B,UAAU,WAAW,YAAY,KAChC,CAAC,UAAU,WAAW,QAAQ,KAAK,CAAC,UAAU,WAAW,UAAU;AAIzE,MAAI,EAAE,gBAAgB,mBAAmB,mBAAmB,CAAC,mBAAmB;AAC9E,WAAO;AAAA,EACT;AAIA,QAAM,aAAa,cAAc;AACjC,QAAM,YAAY,WAAW,WAAW,YAAY;AACpD,QAAM,cAAc,IAAI,QAAQ,SAAS;AACzC,QAAM,WAAW,MAAM,QAAQ,WAAW,IAAI,YAAY,CAAC,IAAI;AAM/D,MAAI,CAAC,aAAa,OAAO;AACvB,UAAM,cAAc,eAAe,IAAI,QAAQ,QAAQ,WAAW,UAAU;AAC5E,QAAI,eAAe,YAAY,sBAAsB,UAAU,WAAW,EAAG,QAAO;AAEpF,SAAK,aAAa,gBAAgB;AAAA,MAChC,OAAO;AAAA,MACP,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,eAAe,QAAQ,QAAQ;AAAA,IACjC,CAAC;AAED,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU;AAAA,MACrB,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC,CAAC;AACF,WAAO;AAAA,EACT;AAIA,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,cAAc,MAAM,YAAY,KAAK;AAC3C,MAAI,CAAC,aAAa,GAAI,QAAO;AAE7B,MAAI,YAAY,YAAY,aAAa,sBAAsB,UAAU,YAAY,SAAS,EAAG,QAAO;AAExG,OAAK,aAAa,gBAAgB;AAAA,IAChC,OAAO;AAAA,IACP,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA,QAAQ,YAAY;AAAA,IACpB,eAAe,QAAQ,QAAQ;AAAA,EACjC,CAAC;AAED,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,EACX,CAAC,CAAC;AACF,SAAO;AACT;;;AIjHA,SAAS,mBAAmB;AAC5B,SAAS,iBAAAC,gBAAe,eAAAC,oBAA2C;AAOnE,IAAM,sBAAsB,CAAC,MAAc,OAAe,SAAoC;AAC5F,QAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE;AACjC,MAAI,KAAK,SAAU,OAAM,KAAK,UAAU;AACxC,MAAI,KAAK,SAAU,OAAM,KAAK,YAAY,KAAK,SAAS,OAAO,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,SAAS,MAAM,CAAC,CAAC,EAAE;AAC1G,MAAI,KAAK,OAAQ,OAAM,KAAK,QAAQ;AACpC,QAAM,KAAK,QAAQ,KAAK,QAAQ,GAAG,EAAE;AACrC,MAAI,OAAO,KAAK,aAAa,SAAU,OAAM,KAAK,WAAW,OAAO,KAAK,MAAM,KAAK,WAAW,GAAI,CAAC,CAAC,EAAE;AACvG,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,IAAM,kBAAoC,OAAO,EAAE,KAAK,WAAW,MAAM,MAAM;AACpF,MAAI,cAAc,aAAc,QAAO;AAEvC,QAAM,aAAaC,eAAc;AAQjC,MAAI,CAAC,aAAa,OAAO;AAYvB,UAAM,eAAe,YAAY,WAAW,WAAW,EAAE,SAAS,KAAK;AACvE,QAAI,aAAa;AACjB,QAAI,UAAU,cAAc,oBAAoB,WAAW,YAAY,cAAc,WAAW,aAAa,CAAC;AAC9G,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,WAAW,WAAW,aAAa,CAAC,CAAC;AACtE,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,OAAO;AACV,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,uBAAuB,CAAC,CAAC;AAC9E,WAAO;AAAA,EACT;AACA,QAAM,cAAc,MAAMC,aAAY,KAAK;AAC3C,MAAI,CAAC,aAAa,IAAI;AACpB,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,uBAAuB,CAAC,CAAC;AAC9E,WAAO;AAAA,EACT;AACA,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW,YAAY,aAAa;AAAA,EACtC,CAAC,CAAC;AACF,SAAO;AACT;;;ACpEO,IAAM,qBAAuC,OAAO,EAAE,KAAK,WAAW,QAAQ,MAAM;AACzF,MAAI,cAAc,eAAgB,QAAO;AACzC,MAAI,QAAQ,cAAc;AACxB,UAAM,QAAQ,aAAa,GAAG;AAC9B,WAAO;AAAA,EACT;AACA,MAAI,UAAU,GAAG;AACjB,MAAI,IAAI;AACR,SAAO;AACT;;;ACXA;AAAA,EACE;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAeP,IAAM,aAAa,YAA8B;AAM/C,QAAM,SAAS;AAMf,QAAM,WAAW,OAAO;AACxB,MAAI,OAAO,aAAa,YAAY;AAClC,UAAM,CAAC,QAAQ,IAAI,MAAM,SAAS,MAAM,kBAAkB;AAC1D,QAAI,CAAC,SAAU,QAAO;AAAA,EACxB;AACA,QAAM,gBAAgB,OAAO;AAC7B,MAAI,OAAO,kBAAkB,YAAY;AACvC,UAAM,CAAC,UAAU,IAAI,MAAM,SAAS,MAAM,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;AACpE,WAAO,CAAC;AAAA,EACV;AACA,SAAO;AACT;AAEO,IAAM,mBAAqC,CAAC,EAAE,KAAK,UAAU,MAAM;AACxE,MAAI,cAAcA,kBAAiB,EAAE,KAAK,aAAc,QAAO,QAAQ,QAAQ,KAAK;AACpF,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC,CAAC;AAC1C,SAAO,QAAQ,QAAQ,IAAI;AAC7B;AAEO,IAAM,oBAAsC,OAAO,EAAE,KAAK,UAAU,MAAM;AAC/E,MAAI,cAAcA,kBAAiB,EAAE,KAAK,cAAe,QAAO;AAOhE,QAAM,WAAW,MAAM,aAAa;AAEpC,QAAM,CAAC,YAAY,IAAI,IAAI,MAAM,SAAS,MAAM,MAAM,KAAK,CAAC;AAC5D,QAAM,UAAU,CAAC,eAAe,SAAS,UAAU,QAAQ,IAAI;AAE/D,QAAM,WAAW,MAAM,WAAW;AAElC,QAAM,QAAQ,QAAQ,QAAQ,KAAK,WAAW;AAC9C,MAAI,aAAa,QAAQ,MAAM;AAC/B,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU;AAAA,IACrB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,QAAQ,EAAE,UAAU,QAAQ,QAAQ,GAAG,OAAO,SAAS,QAAQ,SAAS;AAAA,EAC1E,CAAC,CAAC;AACF,SAAO;AACT;AAEO,IAAM,oBAAsC,OAAO,EAAE,KAAK,UAAU,MAAM;AAC/E,MAAI,cAAcA,kBAAiB,EAAE,KAAK,eAAgB,QAAO;AAMjE,QAAM,WAAW,MAAM,aAAa;AAMpC,QAAM,qBAAqB,6BAA6B,QAAQ;AAChE,MAAI,aAAa,WAAW,MAAM;AAClC,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU;AAAA,IACrB,QAAQ,WAAW,OAAO;AAAA,IAC1B;AAAA,IACA,QAAQ,cAAc;AAAA,IACtB;AAAA;AAAA;AAAA;AAAA,IAIA,YAAY,yBAAyB;AAAA,EACvC,CAAC,CAAC;AACF,SAAO;AACT;;;AC7GA;AAAA,EACE;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA,SAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AAIA,IAAM,uBAAyC,OAAO,EAAE,KAAK,KAAK,UAAU,MAAM;AACvF,MAAI,cAAcC,kBAAiB,EAAE,KAAK,kBAAmB,QAAO;AAMpE,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,YAAY,iBAAiB,YAAY,QAAQ;AACnD,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,WAAW,CAAC,CAAC;AAClE,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,QAAQ,IAAI;AAClC,QAAM,gBAAgB,IAAI,QAAQ,oBAAoB;AACtD,QAAM,aAAa,MAAM,QAAQ,aAAa,IAAI,cAAc,CAAC,IAAI;AACrE,MAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,sBAAsB,YAAY,aAAa,GAAG;AACtF,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,iBAAiB,CAAC,CAAC;AACxE,WAAO;AAAA,EACT;AAEA,QAAM,UAAoB,CAAC;AAC3B,QAAM,mBAAmB;AACzB,UAAQ,KAAK,YAAY;AAMzB,QAAM,iBAAiB,GAAG,UAAU,YAAY,EAAE,CAAC;AACnD,QAAM,qBAAqB,GAAG,UAAU,gBAAgB,EAAE,CAAC;AAE3D,QAAM,gBAAgB,OAAO,SAAiB,UAAmC;AAC/E,UAAM,CAAC,OAAO,OAAO,IAAI,MAAMC,UAAS,YAAY;AAClD,UAAI,SAAS;AACb,UAAI,QAAQ;AACZ,SAAG;AACD,cAAM,CAAC,MAAM,IAAI,IAAI,MAAMC,OAAM,KAAK,QAAQ,SAAS,SAAS,SAAS,GAAG;AAC5E,iBAAS;AACT,YAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,GAAG;AAC1C,gBAAMA,OAAM,IAAI,GAAG,IAAI;AACvB,mBAAS,KAAK;AAAA,QAChB;AAAA,MACF,SAAS,WAAW;AACpB,aAAO;AAAA,IACT,CAAC;AACD,QAAI,SAAS,CAAC,QAAS,QAAO;AAC9B,QAAI,UAAU,EAAG,SAAQ,KAAK,KAAK;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,gBAAgB,UAAU;AAC9C,QAAM,cAAc,oBAAoB,aAAa;AAMrD,QAAM,SAAS,IAAI,OAAO;AAG1B,QAAM,cAAc,IAAI,SAAS,QAAQ,kBAAkB,IACvD,IAAI,IAAI,QAAQ,kBAAkB,EAAE,aAAa,IAAI,SAAS,KAAK,KACnE;AACJ,MAAI,YAAY,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,OAAO,GAAG;AACjE,kBAAc;AACd,YAAQ,KAAK,OAAO;AAAA,EACtB;AAEA,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,WAAW,QAAQ,CAAC,CAAC;AACtD,SAAO;AACT;;;ACvFA,SAAS,mBAAmB;AAGrB,IAAM,qBAAuC,OAAO,EAAE,KAAK,UAAU,MAAM;AAChF,MAAI,CAAC,UAAU,WAAW,WAAW,EAAG,QAAO;AAC/C,QAAM,YAAY,EAAE,WAAW,IAAI,CAAC;AACpC,SAAO;AACT;;;ACPA;AAAA,EACE;AAAA,EACA,gBAAAC;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,OACK;;;ACAA,IAAM,sBAAsB,CACjC,qBACA,cACY,uBAAuB,cAAc;;;ADEnD,IAAM,+BAA+B,CAAC,gBAA+D;AACnG,MAAI,gBAAgB,OAAW,QAAO;AACtC,QAAM,QAAQ,MAAM,QAAQ,WAAW,IAAI,YAAY,CAAC,IAAI;AAC5D,MAAI,UAAU,OAAO,UAAU,OAAQ,QAAO;AAC9C,MAAI,UAAU,OAAO,UAAU,QAAS,QAAO;AAC/C,SAAO;AACT;AAEO,IAAM,qBAAuC,OAAO;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,CAAC,UAAU,WAAW,WAAW,EAAG,QAAO;AAI/C,QAAM,QAAQ,MAAM,SAAS;AAC7B,MAAI,CAAC,OAAO;AACV,QAAI,UAAU,gBAAgB,iCAAiC;AAC/D,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,QAAQ,gBAAgB,CAAC,CAAC;AAClE,WAAO;AAAA,EACT;AAEA,QAAM,SAASC,kBAAiB;AAChC,QAAM,oBAAoB,OAAO,KAAK;AACtC,QAAM,eAAe,OAAO,QAAQ;AAEpC,QAAM,eAAe,UAAU,MAAM,GAAG,EAAE,CAAC;AAC3C,QAAM,WAAW,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY;AAC9E,MAAI,CAAC,UAAU,MAAM;AACnB,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,QAAQ,yBAAyB,CAAC,CAAC;AAC3E,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,oBAAoB,QAAQ,GAAG;AAQvC,UAAM,SAAS,IAAI,IAAI,IAAI,OAAO,KAAK,oBAAoB;AAC3D,UAAM,YAAY,OAAO,aAAa,IAAI,YAAY,KAAK;AAE3D,UAAM,aAAa,MAAM,MAAM,iBAAiB,SAAS,MAAM,EAAE,SAAS,SAAS,SAAS,UAAU,CAAC;AACvG,QAAI,CAAC,YAAY;AACf,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,QAAQ,6BAA6B,CAAC,CAAC;AAC/E,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,mBAAmB,SAAS,QAAQ;AACrD,UAAM,cAAc,mBAAmB,SAAS,WAAW;AAC3D,UAAM,QAAQ,mBAAoB,SAAS,MAAO,KAAK,GAAG,CAAC;AAC3D,UAAM,QAAQ,mBAAmB,WAAW,KAAK;AAOjD,UAAM,WAAW,OAAO,KAAK;AAC7B,UAAM,aAAa,oBAAoB,OAAO,KAAK,qBAAqB,QAAQ,IAAI,MAAM,IAAI,aAAa;AAC3G,QAAI;AAAA,MACF;AAAA,MACA,GAAG,MAAM,uBAAuB,IAAI,WAAW,WAAW,sBAAsB,UAAU,0BAA0B,QAAQ;AAAA,IAC9H;AAIA,UAAM,aAAa,WAAW,gBAC1B,mBAAmB,mBAAmB,WAAW,aAAa,CAAC,gCAC/D;AAEJ,QAAI,UAAU,KAAK;AAAA,MACjB,UAAU,GAAG,SAAS,gBAAgB,cAAc,QAAQ,iBAAiB,WAAW,UAAU,KAAK,mDAAmD,KAAK,GAAG,UAAU;AAAA,IAC9K,CAAC;AACD,QAAI,IAAI;AACR,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO;AAK5B,QAAM,cAAc,gBAAgB;AAAA,IAClC,YAAY,IAAI,OAAO;AAAA,IACvB,SAAS,IAAI;AAAA,IACb,YAAY,OAAO,KAAK;AAAA,IACxB,sBAAsB,OAAO,KAAK;AAAA,EACpC,CAAC;AAQD,QAAM,eACJ,aAAa,oBAAoB,SAAS,aAAa,kBAAkB,IACrE,aAAa,kBACZ,aAAa,KAAK,WAAW,aAAa,KAAK,cAAc,IAC5D,aAAa,KAAK,cAClB;AACR,MAAI,iBAAiB,MAAM;AACzB,UAAM,aACJ,aAAa,oBAAoB,QAC7B,aAAa,KAAK,WAClB,aAAa;AACnB,UAAM,EAAE,SAAS,QAAQ,IAAI,MAAM,eAAe;AAAA,MAChD,KAAK,MAAM,WAAW;AAAA,MACtB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAED,QAAI,CAAC,SAAS;AACZ,WAAKC,cAAa,qBAAqB;AAAA,QACrC,OAAO;AAAA,QACP,KAAK,MAAM,WAAW;AAAA,QACtB,OAAO;AAAA,QACP,UAAU;AAAA,QACV,OAAO,eAAe;AAAA,QACtB,OAAO;AAAA,QACP,IAAI;AAAA,MACN,CAAC;AACD,UAAI,UAAU,gBAAgB,iCAAiC;AAC/D,UAAI,IAAI,KAAK,UAAU;AAAA,QACrB,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,CAAC,EAAE,KAAK,WAAW,OAAO,QAAQ,CAAC;AAAA,MAClD,CAAC,CAAC;AACF,aAAO;AAAA,IACT;AAAA,EACF;AAMA,QAAM,SAAU,MAAM,MAAM,qBAAqB,QAAQ,EAAE,gBAAgB,SAAS,QAAW,YAAY,CAAC;AAO5G,MAAI,CAAC,QAAQ,QAAQ;AACnB,UAAM,YACJ,OAAO,QAAQ,WAAW,YAAY,OAAO,OAAO,SAAS,IACzD,OAAO,SACP;AACN,QAAI,UAAU,gBAAgB,iCAAiC;AAC/D,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,OAAO,QAAQ,UAAU,CAAC,CAAC;AAC5D,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,UAAU;AAKnB,QAAI,MAAO,OAAM,MAAM,cAAc,OAAO,EAAE,kBAAkB,KAAK,CAAC;AAEtE,UAAM,uBAAuB,6BAA6B,IAAI,QAAQ,uBAAuB,CAAC;AAC9F,UAAM,uBAAuB,wBAAwB,OAAO,QAAQ;AAEpE,QAAI,aAAc,WAAU,EAAE,MAAM,qCAAqC;AAEzE,QAAI,sBAAsB;AACxB,UAAI,UAAU,mBAAmB,OAAO,QAAQ;AAAA,IAClD,OAAO;AACL,UAAI,UAAU,cAAc,GAAG,iBAAiB,IAAI,OAAO,QAAQ,KAAK,oBAAoB,EAAE;AAAA,IAChG;AAAA,EACF;AAEA,MAAI,IAAI,KAAK,UAAU;AAAA,IACrB,QAAQ,OAAO;AAAA,IACf,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,eAAe,QAAQ,OAAO,QAAQ;AAAA,EACxC,CAAC,CAAC;AACF,SAAO;AACT;;;AExMA,SAAS,aAAAC,YAAW,oBAAAC,mBAAkB,YAAAC,iBAAgB;AAkB/C,IAAM,wBAA0C,OAAO,EAAE,KAAK,WAAW,QAAQ,MAAM,MAAM;AAClG,MAAI,cAAc,eAAgB,QAAO;AAEzC,MAAI,WAAW,QAAQ;AACrB,QAAI,aAAa;AACjB,QAAI,UAAU,SAAS,MAAM;AAC7B,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,uBAAuB,CAAC,CAAC;AAC9E,WAAO;AAAA,EACT;AAEA,MAAI,OAAO;AACT,UAAM,QAAQ,MAAM,SAAS;AAC7B,QAAI,OAAO;AAIT,YAAM,CAAC,WAAW,IAAI,MAAMC,UAAS,MAAM,MAAM,cAAc,KAAK,CAAC;AACrE,UAAI,aAAa;AACf,QAAAC,WAAU,EAAE,KAAK,mEAA8D,EAAE,KAAK,YAAY,CAAC;AAAA,MACrG;AAAA,IACF;AAAA,EACF;AAMA,QAAMC,QAAOC,kBAAiB,EAAE;AAChC,QAAM,SAAS,QAAQ,IAAI,WAAW;AACtC,MAAI;AAAA,IACF;AAAA,IACA,GAAGD,MAAK,iBAAiB,yBAAyBA,MAAK,qBAAqB,UAAUA,MAAK,iBAAiB,gBAAgB,SAAS,YAAY,EAAE;AAAA,EACrJ;AACA,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,WAAW,QAAQ,KAAK,CAAC,CAAC;AAC3D,SAAO;AACT;;;AC/CO,IAAM,2BAA6C,OAAO,EAAE,KAAK,KAAK,UAAU,MAAM;AAC3F,MAAI,cAAc,qBAAqB,IAAI,WAAW,MAAO,QAAO;AAGpE,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,YAAY,QAAQ,MAAM,kBAAkB,EAAE,IAAI,CAAC,aAAa,SAAS,IAAI,IAAI,CAAC;AAExF,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU,EAAE,UAAU,CAAC,CAAC;AACrC,SAAO;AACT;;;ACnBA,SAAS,aAAAE,YAAW,oBAAAC,yBAAwB;AAIrC,IAAM,0BAA4C,OAAO;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,CAAC,UAAU,WAAW,gBAAgB,EAAG,QAAO;AAGpD,QAAM,QAAQ,MAAM,SAAS;AAC7B,MAAI,CAAC,OAAO;AACV,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,qBAAqB;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,SAASC,kBAAiB;AAChC,QAAM,oBAAoB,OAAO,KAAK;AACtC,QAAM,eAAe,OAAO,QAAQ;AAapC,QAAM,gBAAgB,OAAO,IAAI,aAAa,IAAI,QAAQ,QAAQ,EAAE;AAEpE,QAAM,gBAAgB,OAAO,oBAAoB;AACjD,QAAM,eAAe,gBAAgB,KAAK,aAAa,IACnD,gBACA,GAAG,YAAY,GAAG,cAAc,WAAW,GAAG,IAAI,gBAAgB,IAAI,aAAa,EAAE;AACzF,QAAM,iBAAiB,MAAM,MAAM,cAAc,WAAW,KAAK,KAAK;AAAA,IACpE,oBAAoB;AAAA,IACpB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AAED,MAAI,CAAC,gBAAgB;AACnB,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,cAAc;AACtB,WAAO;AAAA,EACT;AAOA,MAAI,MAAO,OAAM,MAAM,cAAc,OAAO,EAAE,kBAAkB,KAAK,CAAC;AAEtE,MAAI,aAAc,CAAAC,WAAU,EAAE,MAAM,iDAAiD;AAErF,QAAM,EAAE,OAAO,UAAU,YAAY,IAAI;AACzC,MAAI,OAAO,QAAQ,YAAY;AAK7B,UAAM,YAAY,YAAY,QAAQ,GAAG;AACzC,UAAM,UAAU,cAAc,KAAK,cAAc,YAAY,MAAM,GAAG,SAAS;AAC/E,QAAI,UAAU,KAAK,EAAE,UAAU,GAAG,OAAO,UAAU,QAAQ,GAAG,CAAC;AAAA,EACjE,OAAO;AACL,QAAI,UAAU,cAAc,GAAG,iBAAiB,IAAI,QAAQ,KAAK,oBAAoB,EAAE;AACvF,QAAI,UAAU,KAAK,EAAE,UAAU,YAAY,CAAC;AAAA,EAC9C;AACA,MAAI,IAAI;AACR,SAAO;AACT;;;AC7EA;AAAA,EACE;AAAA,EACA,gBAAAC;AAAA,EACA;AAAA,EACA,aAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AACP,SAAS,4BAA4B;;;ACNrC,SAAS,oBAAAC,yBAAwB;AAMjC,IAAM,yBAAyB,CAAC,iBAAyD;AACvF,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QAAQ,MAAM,QAAQ,YAAY,IAAI,aAAa,KAAK,GAAG,IAAI;AACrE,SAAO,MAAM,YAAY,EAAE,SAAS,mBAAmB;AACzD;AAEA,IAAM,sBAAsB,CAAC,gBAA6C;AACxE,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,EAAE,YAAY,aAAa,IAAIA,kBAAiB,EAAE,KAAK;AAC7D,QAAM,SAAS,IAAI,gBAAgB,WAAW;AAC9C,QAAM,QAAQ,OAAO,IAAI,UAAU;AACnC,SAAO,UAAU,gBAAgB,UAAU;AAC7C;AAEO,IAAM,sBAAsB,CAAC;AAAA,EAClC;AAAA,EACA;AACF,MAGe,uBAAuB,YAAY,KAAK,oBAAoB,WAAW;AAE/E,IAAM,kBAAkB,CAAC,QAA8B;AAC5D,MAAI,UAAU,KAAK;AAAA,IACjB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,YAAY;AAAA,IACZ,qBAAqB;AAAA,EACvB,CAAC;AACD,QAAM,mBAAmBA,kBAAiB,EAAE,KAAK,OAAO;AACxD,MAAI,kBAAkB;AACpB,QAAI,MAAM,GAAG,gBAAgB;AAAA;AAAA,CAAM;AAAA,EACrC;AACF;AAEO,IAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,MAIY;AACV,MAAI,IAAI,cAAe;AAMvB,QAAM,cAAc,IAAI,UAAU,cAAc;AAChD,MAAI,OAAO,gBAAgB,YAAY,CAAC,YAAY,SAAS,mBAAmB,EAAG;AACnF,MAAI,MAAM,UAAU,KAAK;AAAA,CAAI;AAC7B,MAAI,MAAM,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM;AAC/C;;;AC5DA,SAAS,oBAAAC,mBAAkB,mBAAAC,wBAAuB;AA2B3C,IAAM,qBAAqB,CAAC,QAA6C;AAC9E,QAAM,aAAaD,kBAAiB,EAAE,KAAK;AAC3C,QAAM,uBAAuBA,kBAAiB,EAAE,KAAK;AACrD,QAAM,mBAAmB,IAAI,OAAO;AACpC,SAAQ,oBAAqB,eAAe,IAAI,QAAQ,iBAAiB,KAAK,IAAI,QAAQ,WAAW,KACjGC,iBAAgB,EAAE,YAAY,kBAAkB,SAAS,IAAI,SAAS,YAAY,qBAAqB,CAAC,IACxG;AACN;;;AFvBO,IAAM,iBAAmC,OAAO;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,CAAC,UAAU,WAAW,OAAO,EAAG,QAAO;AAE3C,QAAM,gBAAgB,oBAAoB,EAAE,cAAc,IAAI,QAAQ,QAAQ,YAAY,CAAC;AAC3F,MAAI,eAAe;AACnB,MAAI,eAAe;AACjB,oBAAgB,GAAG;AAKnB,UAAM,aAAa,MAAM;AACvB,qBAAe;AAAA,IACjB;AACA,QAAI,GAAG,SAAS,UAAU;AAC1B,QAAI,GAAG,SAAS,UAAU;AAC1B,QAAI,GAAG,WAAW,UAAU;AAC5B,QAAI,GAAG,SAAS,UAAU;AAAA,EAC5B;AAEA,QAAM,CAAC,OAAO,OAAO,IAAI,MAAMC,UAAS,YAAY;AAClD,UAAM,YAAY,wBAAwB,GAAG;AAC7C,UAAM,UAAU,UAAU,MAAM,CAAC;AAEjC,QAAI,CAAC,SAAS;AACZ,YAAM,WAAW;AAAA,QACf,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AACA,UAAI,eAAe;AACjB,YAAI,CAAC,aAAc,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,SAAS,CAAC;AACvE,YAAI,IAAI;AACR,eAAO;AAAA,MACT;AACA,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,QAAQ,CAAC;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,OAAO,WAAW,WAC9B,EAAE,GAAI,OAAmC,IACzC,CAAC;AACL,WAAQ,QAAoC;AAK5C,UAAM,cAAc,mBAAmB,GAAG;AAE1C,UAAM,SAAS,MAAM,qBAAqB;AAAA,MACxC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP;AAAA,MACA,iBAAiB,IAAI,QAAQ,YAAY;AAAA,MACzC,sBAAsB,IAAI,QAAQ,iBAAiB;AAAA,MACnD;AAAA,MACA,QAAQ,gBACJ,CAAC,YAAY;AACX,YAAI,gBAAgB,IAAI,cAAe;AACvC,qBAAa,EAAE,KAAK,OAAO,UAAU,MAAM,QAAQ,CAAC;AAAA,MACtD,IACA;AAAA,IACN,CAAC;AAED,QAAI,eAAe;AACjB,UAAI,CAAC,aAAc,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,OAAO,CAAC;AACrE,UAAI,IAAI;AACR,aAAO;AAAA,IACT;AAEA,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,UAAU,OAAO,UAAU;AAC/B,QAAI,IAAI,KAAK,UAAU,MAAM,CAAC;AAC9B,WAAO;AAAA,EACT,GAAG,QAAW,EAAE,WAAW,QAAQ,WAAW,QAAQ,kBAAkB,CAAC;AAEzE,MAAI,CAAC,OAAO;AACV,WAAO,WAAW;AAAA,EACpB;AAEA,EAAAC,WAAU,EAAE,MAAM,qCAAqC,OAAO,EAAE,WAAW,QAAQ,UAAU,CAAC;AAC9F,mBAAiB,OAAO,EAAE,WAAW,QAAQ,WAAW,QAAQ,kBAAkB,CAAC;AACnF,OAAKC,cAAa,YAAY;AAAA,IAC5B,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,cAAc;AAAA,IAClB,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAEA,MAAI,eAAe;AACjB,QAAI,CAAC,IAAI,cAAe,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,YAAY,CAAC;AAC/E,QAAI,IAAI;AACR,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,UAAU,GAAG;AACjB,MAAI,IAAI,KAAK,UAAU,WAAW,CAAC;AACnC,SAAO;AACT;;;AGlIA;AAAA,EACE,oBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,aAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AAcP,IAAM,0BAA0B,CAAC,WAAoD;AACnF,QAAM,SAAU,UAAU,CAAC;AAC3B,QAAM,OAAQ,OAAO,QAAQ,OAAO,OAAO,SAAS,WAC/C,OAAO,OACR,CAAC;AACL,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;AAAA,IAClE,YAAY,OAAO,OAAO,eAAe,YAAY,OAAO,aAAa;AAAA,IACzE,IAAI,OAAO,OAAO,OAAO,WAAW,OAAO,KAAK;AAAA,EAClD;AACF;AAEO,IAAM,kBAAoC,OAAO;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,CAAC,UAAU,WAAW,QAAQ,EAAG,QAAO;AAI5C,QAAM,OAAO,aAAa,OAAO,MAAM,QAAQ,IAAI;AACnD,MAAI,CAAC,MAAM;AACT,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,iBAAiB,SAAS,gBAAgB,CAAC,CAAC;AACjG,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,oBAAoB,EAAE,cAAc,IAAI,QAAQ,QAAQ,YAAY,CAAC;AAC3F,MAAI,eAAe;AACnB,MAAI,eAAe;AACjB,oBAAgB,GAAG;AAKnB,UAAM,aAAa,MAAM;AACvB,qBAAe;AAAA,IACjB;AACA,QAAI,GAAG,SAAS,UAAU;AAC1B,QAAI,GAAG,SAAS,UAAU;AAC1B,QAAI,GAAG,WAAW,UAAU;AAC5B,QAAI,GAAG,SAAS,UAAU;AAAA,EAC5B;AAEA,QAAM,CAAC,OAAO,OAAO,IAAI,MAAMC,UAAS,YAAY;AAClD,QAAI,WAAW,QAAQ;AACrB,YAAM,WAAW;AAAA,QACf,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AACA,UAAI,eAAe;AACjB,YAAI,CAAC,aAAc,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,SAAS,CAAC;AACvE,YAAI,IAAI;AACR,eAAO;AAAA,MACT;AACA,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,QAAQ,CAAC;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,YAAYC,yBAAwB,GAAG;AAC7C,UAAM,WAAW,UAAU,MAAM,CAAC;AAElC,QAAI,CAAC,UAAU;AACb,YAAM,WAAW;AAAA,QACf,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AACA,UAAI,eAAe;AACjB,YAAI,CAAC,aAAc,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,SAAS,CAAC;AACvE,YAAI,IAAI;AACR,eAAO;AAAA,MACT;AACA,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,QAAQ,CAAC;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,wBAAwB,MAAM;AAKjD,UAAM,cAAc,mBAAmB,GAAG;AAE1C,UAAM,SAAS,MAAM,KAAK,sBAAsB;AAAA,MAC9C,MAAM,QAAQ,QAAQ;AAAA,MACtB,IAAI,WAAW;AAAA,MACf,MAAM,WAAW;AAAA,MACjB,UAAU,WAAW;AAAA,MACrB,YAAY,WAAW;AAAA,MACvB,OAAO;AAAA,MACP;AAAA,MACA,iBAAiB,IAAI,QAAQ,YAAY;AAAA,MACzC,sBAAsB,IAAI,QAAQ,iBAAiB;AAAA,MACnD,QAAQ,gBACJ,CAAC,YAAiC;AAChC,YAAI,gBAAgB,IAAI,cAAe;AACvC,qBAAa,EAAE,KAAK,OAAO,UAAU,MAAM,QAAQ,CAAC;AAAA,MACtD,IACA;AAAA,IACN,CAAC;AAED,QAAI,eAAe;AACjB,UAAI,CAAC,aAAc,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,OAAO,CAAC;AACrE,UAAI,IAAI;AACR,aAAO;AAAA,IACT;AAEA,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,UAAU,OAAO,eAAe,OAAO,WAAW,YAAY,MAAM,IAAI;AAC5E,QAAI,IAAI,KAAK,UAAU,MAAM,CAAC;AAC9B,WAAO;AAAA,EACT,GAAG,QAAW,EAAE,WAAW,QAAQ,WAAW,QAAQ,mBAAmB,CAAC;AAE1E,MAAI,CAAC,OAAO;AACV,WAAO,WAAW;AAAA,EACpB;AAEA,EAAAC,WAAU,EAAE,MAAM,sCAAsC,OAAO,EAAE,WAAW,QAAQ,UAAU,CAAC;AAC/F,EAAAC,kBAAiB,OAAO,EAAE,WAAW,QAAQ,WAAW,QAAQ,mBAAmB,CAAC;AACpF,OAAKC,cAAa,aAAa;AAAA,IAC7B,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,cAAc;AAAA,IAClB,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAEA,MAAI,eAAe;AACjB,QAAI,CAAC,IAAI,cAAe,cAAa,EAAE,KAAK,OAAO,SAAS,MAAM,YAAY,CAAC;AAC/E,QAAI,IAAI;AACR,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,UAAU,GAAG;AACjB,MAAI,IAAI,KAAK,UAAU,WAAW,CAAC;AACnC,SAAO;AACT;;;AC/KA,SAAS,oBAAAC,mBAAkB,aAAAC,YAAW,YAAAC,iBAAgB;;;AC0BtD,IAAM,WAAiC,CAAC;AACxC,IAAM,oBAA0C,CAAC;AAE1C,IAAM,sBAAsB,CACjC,SACA,UAAsC,CAAC,MAC9B;AACT,MAAI,QAAQ,UAAU,cAAc;AAClC,sBAAkB,KAAK,OAAO;AAC9B;AAAA,EACF;AACA,WAAS,KAAK,OAAO;AACvB;AAEO,IAAM,kBAAkB,MAAqC;AAE7D,IAAM,2BAA2B,MAAqC;AAEtE,IAAM,oBAAoB,MAAY;AAC3C,WAAS,SAAS;AAClB,oBAAkB,SAAS;AAC7B;;;ADzCA,IAAM,aAAa,OACjB,SACA,KACA,KACA,KACA,WACqB;AACrB,QAAM,CAAC,OAAO,OAAO,IAAI,MAAMC,UAAS,MAAM,QAAQ,KAAK,KAAK,GAAG,CAAC;AACpE,MAAI,OAAO;AAKT,IAAAC,WAAU,EAAE,MAAM,GAAG,MAAM,UAAU,OAAO,EAAE,WAAW,IAAI,WAAW,QAAQ,IAAI,OAAO,CAAC;AAC5F,IAAAC,kBAAiB,OAAO,EAAE,WAAW,IAAI,WAAW,QAAQ,IAAI,QAAQ,OAAO,CAAC;AAChF,QAAI,CAAC,IAAI,eAAe;AACtB,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,2BAA2B,CAAC,CAAC;AAAA,IACpF;AACA,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,OAAO,KAAK,IAAI;AACjC;AAOO,IAAM,8BAAgD,OAAO;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,MAAoB,EAAE,WAAW,QAAQ,aAAa,MAAM;AAClE,aAAW,WAAW,yBAAyB,GAAG;AAChD,UAAM,UAAU,MAAM,WAAW,SAAS,KAAK,KAAK,KAAK,sBAAsB;AAC/E,QAAI,QAAS,QAAO;AAAA,EACtB;AACA,SAAO;AACT;AAEO,IAAM,qBAAuC,OAAO;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AAMJ,QAAM,MAAoB,EAAE,WAAW,QAAQ,aAAa,MAAM;AAElE,aAAW,WAAW,gBAAgB,GAAG;AACvC,UAAM,UAAU,MAAM,WAAW,SAAS,KAAK,KAAK,KAAK,sBAAsB;AAC/E,QAAI,QAAS,QAAO;AAAA,EACtB;AACA,MAAI,QAAQ,cAAc;AACxB,UAAM,UAAU,MAAM,WAAW,QAAQ,cAAc,KAAK,KAAK,KAAK,2BAA2B;AACjG,QAAI,QAAS,QAAO;AAAA,EACtB;AACA,SAAO;AACT;;;AE5EA,OAAO,UAAU;AAKjB,IAAM,0BAA0B;AAQhC,IAAM,uBAAuB;AAO7B,IAAM,wBAAwB,OAC5B,WACA,KACA,KACA,iBACkB;AAClB,QAAM,cAAc,IAAI;AACxB,MAAI,MAAM;AACV,MAAI;AACF,UAAM,UAAU,KAAK,GAAG;AAAA,EAC1B,UAAE;AACA,QAAI,MAAM;AAAA,EACZ;AACF;AAEO,IAAM,6BAA+C,OAAO;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AAEJ,MAAI,qBAAqB,KAAK,SAAS,GAAG;AACxC,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,WAAW;AACnB,WAAO;AAAA,EACT;AAKA,MAAI,UAAU,WAAW,UAAU,GAAG;AACpC,QAAI,CAAC,QAAQ,WAAW;AACtB,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AACnB,aAAO;AAAA,IACT;AACA,UAAM,sBAAsB,QAAQ,WAAW,KAAK,KAAK,SAAS;AAClE,WAAO;AAAA,EACT;AAGA,MAAI,wBAAwB,KAAK,SAAS,GAAG;AAC3C,QAAI,CAAC,QAAQ,WAAW;AACtB,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AACnB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,UAAU,KAAK,GAAG;AAChC,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,WAAW;AACnB,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,QAAQ,WAAW;AACtB,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,WAAW;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,sBAAsB,QAAQ,WAAW,KAAK,KAAK,GAAG;AAC5D,SAAO;AACT;;;AvBtDA,IAAM,4BAA4B,CAChC,mBACA,QACAC,UAEA,sBAAsBA,MAAK,qBAAqB,UAAUA,MAAK,iBAAiB,aAAa,KAAK,KAAK,KAAK,iBAAiB,KAAK,SAAS,YAAY,EAAE;AAE3J,IAAM,qBAAqB,CAAC,KAAsB,KAAqB,WAAmB;AACxF,QAAM,EAAE,MAAM,gBAAgB,IAAIC,mBAAiB,EAAE;AACrD,MAAI,UAAU,+BAA+B,MAAM;AACnD,MAAI,UAAU,gCAAgC,KAAK,cAAc;AACjE,MAAI,UAAU,gCAAgC,KAAK,cAAc;AACjE,MAAI,UAAU,iCAAiC,KAAK,cAAc;AAClE,MAAI,KAAK,aAAa;AACpB,QAAI,UAAU,oCAAoC,MAAM;AAAA,EAC1D;AACA,MAAI,UAAU,mBAAmB,gBAAgB,cAAc;AAC/D,MAAI,UAAU,mBAAmB,gBAAgB,YAAY;AAC7D,MAAI,UAAU,oBAAoB,gBAAgB,aAAa;AAC/D,MAAI,UAAU,0BAA0B,gBAAgB,kBAAkB;AAK1E,QAAM,UAAU,0BAA0B;AAC1C,MAAI,SAAS;AAIX,UAAM,CAAC,KAAK,IAAI,aAAa,MAAM;AACjC,YAAM,SAAS,QAAQ,GAAG;AAC1B,UAAI,QAAQ;AACV,mBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,cAAI,UAAU,MAAM,KAAK;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,CAAC;AACD,QAAI,OAAO;AACT,MAAAC,WAAU,EAAE,KAAK,gEAA2D,EAAE,KAAK,MAAM,CAAC;AAAA,IAC5F;AAAA,EACF;AACF;AAMA,IAAM,oBAAwC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,qBAAyC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,iBAAiB,OAAOC,WAA8B,QAA4C;AACtG,aAAW,WAAWA,WAAU;AAC9B,UAAM,UAAU,MAAM,QAAQ,GAAG;AACjC,QAAI,WAAW,IAAI,IAAI,cAAe,QAAO;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,IAAM,sBAAsB,CAC1B,KACA,KACA,cAC0C;AAI1C,QAAM,SAAS,IAAI,QAAQ,UAAU,IAAI,QAAQ,WAAW;AAC5D,QAAM,wBAAwB,IAAI,WAAW,SAAS,IAAI,WAAW,UAAU,IAAI,WAAW;AAM9F,MAAI,mBAAmB,SAAS,GAAG;AACjC,WAAO,EAAE,QAAQ,UAAU,MAAM;AAAA,EACnC;AAEA,MAAI,CAAC,QAAQ;AAIX,QAAI,uBAAuB;AACzB,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,YAAY;AAC1C,UAAI,IAAI,WAAW;AACnB,aAAO,EAAE,QAAQ,UAAU,KAAK;AAAA,IAClC;AAAA,EACF,WAAW,CAAC,cAAc,MAAM,GAAG;AACjC,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,YAAY;AAC1C,QAAI,IAAI,WAAW;AACnB,WAAO,EAAE,QAAQ,UAAU,KAAK;AAAA,EAClC;AACA,SAAO,EAAE,QAAQ,UAAU,MAAM;AACnC;AAEA,IAAM,gCAAgC,OAAO;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMM;AACJ,QAAM,iBAAiB,UAAU,IAAI,QAAQ,QAAQ,iBAAiB;AACtE,MAAI,CAAC,kBAAkB,CAAC,MAAO;AAC/B,QAAM,iBAAiB,MAAMC,aAAY,KAAK;AAC9C,MAAI,gBAAgB,IAAI;AAGtB,QAAI,UAAU,cAAc,GAAG,iBAAiB,IAAI,KAAK,KAAK,oBAAoB,EAAE;AAAA,EACtF;AACF;AAEA,IAAM,qBAAqB,OAAO;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAQ8B;AAC5B,MAAI,SAAwB,MAAM,UAAU,EAAE,QAAQ,KAAK,KAAK,YAAY,CAAC;AAC7E,MAAI,IAAI,cAAe,QAAO;AAE9B,MAAI,UAAU,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAC1E,QAAI,cAAc;AAChB,MAAAF,WAAU,EAAE,MAAM,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,IAAI;AAAA,QACzD,QAAQ,eAAe,MAAM;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,EACF,OAAO;AACL,QAAI,cAAc;AAChB,MAAAA,WAAU,EAAE,MAAM,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,EAAE;AAAA,IAC3D;AACA,aAAS,CAAC;AAAA,EACZ;AACA,SAAO;AACT;AAEO,IAAM,oBAAoB,OAC/B,KACA,KACA,YACkB;AAClB,QAAM,SAASD,mBAAiB;AAChC,QAAM,eAAe,OAAO,QAAQ;AACpC,QAAM,oBAAoB,OAAO,KAAK;AACtC,QAAM,uBAAuB;AAAA,IAC3B,OAAO,QAAQ;AAAA;AAAA;AAAA;AAAA,IAIf,oBAAoB,OAAO,KAAK,qBAAqB,QAAQ,IAAI,MAAM;AAAA,IACvE,OAAO;AAAA,EACT;AAKA,QAAM,MAAM,IAAI,OAAO;AACvB,QAAM,CAAC,cAAc,cAAc,IAAI,IAAI,MAAM,GAAG;AACpD,QAAM,YAAY,gBAAgB;AAClC,QAAM,cAAc,kBAAkB;AAEtC,QAAM,EAAE,QAAQ,SAAS,IAAI,oBAAoB,KAAK,KAAK,SAAS;AACpE,MAAI,SAAU;AAEd,qBAAmB,KAAK,KAAK,MAAM;AAKnC,QAAM,oBAAoB,IAAI,QAAQ,cAAc;AACpD,QAAM,aAAa,MAAM,QAAQ,iBAAiB,IAAI,kBAAkB,CAAC,IAAI,sBAAsB,WAAW;AAC9G,MAAI,UAAU,gBAAgB,SAAS;AAKvC,QAAM,cAAsC,CAAC;AAC7C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AAChD,QAAI,MAAM,mBAAmB,MAAM,YAAY,MAAM,gBAAgB,MAAM,eAAgB;AAC3F,gBAAY,CAAC,IAAI,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,IAAI,IAAK,KAAK;AAAA,EAC3D;AACA,QAAM,gBAAgB,MAAMI,cAAa,kBAAkB;AAAA,IACzD,QAAQ,IAAI,QAAQ,YAAY,KAAK;AAAA,IACrC,KAAK,IAAI,OAAO;AAAA,IAChB;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX,CAAC;AACD,MAAI,cAAc,SAAS;AACzB,QAAI,aAAa,cAAc,OAAO,cAAc;AACpD,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,WAAW,cAAc,OAAO,UAAU,CAAC,CAAC;AACtF;AAAA,EACF;AAEA,MAAI,IAAI,WAAW,WAAW;AAC5B,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI;AACR;AAAA,EACF;AAEA,QAAM,SAAS,IAAI;AAEnB,MAAI,WAAW,SAAS,WAAW,UAAU,WAAW,SAAS,WAAW,UAAU;AACpF,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,YAAY;AAC1C,QAAI,IAAI,WAAW,OAAO,MAAM,CAAC,oDAAoD;AACrF;AAAA,EACF;AAEA,QAAM,QAAQC,yBAAwB,GAAG;AACzC,QAAM,8BAA8B,EAAE,KAAK,KAAK,OAAO,mBAAmB,qBAAqB,CAAC;AAEhG,QAAM,eAAe,MAAM,kCAAkC,EAAE,KAAK,KAAK,WAAW,OAAO,UAAU,CAAC;AACtG,MAAI,aAAc;AAElB,QAAM,UAA4C;AAAA,IAChD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAIA,MAAI,MAAM,eAAe,mBAAmB,EAAE,GAAG,SAAS,QAAQ,CAAC,EAAE,CAAC,EAAG;AAEzE,QAAM,SAAS,MAAM,mBAAmB;AAAA,IACtC;AAAA,IAAK;AAAA,IAAK;AAAA,IAAQ;AAAA,IAAW;AAAA,IAAa;AAAA,IAAW;AAAA,EACvD,CAAC;AACD,MAAI,WAAW,KAAM;AAErB,QAAM,eAAe,oBAAoB,EAAE,GAAG,SAAS,OAAO,CAAC;AACjE;;;AwBnTA,SAAS,UAAU,sBAAsB;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAAC;AAAA,EACA,oBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA;AAAA,EACA,eAAAC;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,OAIK;AACP,SAAS,wBAAwB;AAQjC,IAAM,eAAe,oBAAI,IAA2B;AACpD,IAAM,kBAAkB,OAAO,OAAe,OAA4B;AACxE,QAAM,OAAO,aAAa,IAAI,KAAK,KAAK,QAAQ,QAAQ;AAIxD,QAAM,OAAO,KAAK,KAAK,IAAI,EAAE;AAC7B,eAAa,IAAI,OAAO,IAAI;AAC5B,QAAMC,UAAS,MAAM,IAAI;AAIzB,MAAI,aAAa,IAAI,KAAK,MAAM,KAAM,cAAa,OAAO,KAAK;AACjE;AAEA,IAAM,wBAAwB,CAC5B,QACA,UACa;AACb,SAAO,CAAC,GAAG,OAAO,KAAK,EACpB,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,EACzD,OAAO,CAAC,SAAS,SAAS,OAAO,EAAE,EACnC,OAAO,CAAC,SAAS,CAAC,SAAS,SAAS,KAAK;AAC9C;AAEA,IAAM,sBAAsB,CAAC,YAAyC;AACpE,QAAM,YAAY,MAAM,QAAQ,QAAQ,SAAS,IAC7C,QAAQ,UAAU;AAAA,IAChB,CAAC,aAAiC,OAAO,aAAa,YAAY,SAAS,SAAS;AAAA,EACtF,IACA,CAAC;AACL,SAAO,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAC/B;AAEA,IAAM,0BAA0B,CAAC,YAAkD;AACjF,QAAM,EAAE,MAAM,aAAa,OAAO,cAAc,GAAG,iBAAiB,IAAI;AAIxE,SAAO;AACT;AAqCA,IAAM,sBAAsB,CAC1B,QACA,eACA,OACA,OACA,iBACA,2BAC4B;AAC5B,MAAI,OAAO,kBAAkB,SAAU,QAAO;AAE9C,MAAI,CAAC,OAAO;AACV,WAAO,KAAK,uBAAuB,aAAa,GAAG,uBAAuB;AAAA,MACxE,UAAU,EAAE,QAAQ,SAAS,WAAW,gBAAgB;AAAA,MACxD;AAAA,IACF,CAAC,CAAC;AACF,WAAO;AAAA,EACT;AACA,MAAI,CAAC,OAAO;AACV,WAAO,KAAK,uBAAuB,aAAa,GAAG,uBAAuB;AAAA,MACxE,UAAU,EAAE,QAAQ,SAAS,WAAW,eAAe;AAAA,MACvD;AAAA,IACF,CAAC,CAAC;AACF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAuBA,IAAM,sBAAsB,OAAO,SAA6C;AAC9E,QAAM;AAAA,IACJ;AAAA,IAAQ;AAAA,IAAO;AAAA,IAAO;AAAA,IAAe;AAAA,IAAiB;AAAA,IACtD;AAAA,IAAwB;AAAA,IAAS;AAAA,IAAU;AAAA,IAAkB;AAAA,IAAS;AAAA,EACxE,IAAI;AAEJ,QAAM,UAAU,MAAMC,aAAY,KAAK;AACvC,MAAI,CAAC,SAAS;AACZ,WAAO,KAAK,uBAAuB,aAAa,GAAG,uBAAuB;AAAA,MACxE,UAAU,EAAE,QAAQ,SAAS,WAAW,mBAAmB;AAAA,MAC3D;AAAA,IACF,CAAC,CAAC;AACF;AAAA,EACF;AAEA,QAAM,YAAY,MAAMC,cAAa,SAAS,EAAE,OAAO,MAAM,MAAM,CAAC;AACpE,MAAI,UAAU,SAAS;AACrB,WAAO,KAAK,uBAAuB,aAAa,GAAG,uBAAuB;AAAA,MACxE,UAAU;AAAA,QACR,QAAQ;AAAA,QACR,WAAW,UAAU,OAAO,aAAa;AAAA,MAC3C;AAAA,MACA;AAAA,MACA,cAAc,QAAQ;AAAA,IACxB,CAAC,CAAC;AACF;AAAA,EACF;AAEA,QAAM,oBAAoB,oBAAoB,OAAO;AAKrD,QAAM,cAAc,YAAY,gBAAgB,SAAkB;AAClE,QAAM,eAAe,eAAe,OAAO,EAAE,SAAS,aAAa,QAAQ,QAAQ,GAAG,CAAC;AACvF,QAAM,gBAAgB,MAAM,OAAO,QAAQ,cAAc,OAAO,iBAAiB;AAEjF,QAAM,mBAAmB,wBAAwB,OAAO;AACxD,QAAM,aAAa,OAAO,EAAE,GAAG,kBAAkB,WAAW,cAAc,CAAC;AAE3E,QAAM,eAAe,sBAAsB,QAAQ,KAAK;AACxD,SAAO,KAAK,uBAAuB,aAAa,GAAG,EAAE,OAAO,aAAa,CAAC;AAC1E,MAAI,cAAc;AAChB,IAAAC,WAAU,EAAE,MAAM,UAAU,OAAO,EAAE,IAAI,OAAO,UAAU,KAAK,EAAE;AAAA,EACnE;AAEA,OAAKD,cAAa,UAAU,EAAE,OAAO,MAAM,OAAO,UAAU,aAAa,CAAC;AAC5E;AAMA,IAAM,2BAA2B,CAAC,QAA6B;AAC7D,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,SAAO,GAAG,iBAAiB,YAAY,CAAC,QAAoB;AAC1D,SAAK,iBAAiB,EAAE,KAAK,QAAQ,MAAM,CAAC;AAAA,EAC9C,CAAC;AAKD,MAAI,aAAa,MAAM;AACrB,WAAO,GAAG,iBAAiB,MAAM,CAAC,QAAqB;AACrD,YAAM,YAAY;AAChB,cAAM,OAAO,MAAM,QAAQ;AAC3B,YAAI,KAAM,OAAM,KAAK,kBAAkB,EAAE,KAAK,QAAQ,MAAM,CAAC;AAAA,MAC/D,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACF;AAEA,IAAM,6BAA6B,CAAC,QAA6B;AAC/D,QAAM,EAAE,OAAO,IAAI;AAOnB,SAAO,GAAG,iBAAiB,YAAY,CAAC,SAA0B;AAChE,UAAM,KAAK,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AACnD,QAAI,CAAC,GAAI;AACT,kBAAc,OAAO,IAAI,EAAE;AAAA,EAC7B,CAAC;AACD,SAAO,GAAG,iBAAiB,WAAW,CAAC,SAA8C;AACnF,UAAM,gBAAgB,KAAK;AAC3B,QAAI,OAAO,kBAAkB,YAAY,OAAO,kBAAkB,SAAU;AAC5E,4BAAwB,OAAO,IAAI,aAAa;AAAA,EAClD,CAAC;AACH;AAEA,IAAM,qBAAqB,CAAC,QAA6B;AACvD,QAAM,EAAE,QAAQ,OAAO,iBAAiB,aAAa,IAAI;AAEzD,SAAO,GAAG,iBAAiB,UAAU,CAAC,SAAqD;AACzF,UAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AACnE,UAAM,gBAAgB,KAAK;AAE3B,QAAI,CAAC,oBAAoB,QAAQ,eAAe,OAAO,OAAO,iBAAiB,8BAA8B,EAAG;AAEhH,QAAI,CAAC,MAAO;AAEZ,SAAK,gBAAgB,OAAO,YAAY;AACtC,YAAM,oBAAoB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,wBAAwB;AAAA,QACxB,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB;AAAA,QAClB,SAAS;AAAA,QACT,QAAQ,OAAO,MAAM,cAAc,UAAU,kBAAkB;AAC7D,gBAAM,YAAY,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,eAAe,QAAQ,CAAC,CAAC;AAC3D,gBAAM,KAAK,KAAK,YAAY;AAC5B,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AAED,SAAO,GAAG,iBAAiB,WAAW,CAAC,SAAqD;AAC1F,UAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AACnE,UAAM,gBAAgB,KAAK;AAE3B,QAAI,CAAC,oBAAoB,QAAQ,eAAe,OAAO,OAAO,iBAAiB,+BAA+B,EAAG;AAEjH,QAAI,CAAC,MAAO;AAEZ,SAAK,gBAAgB,OAAO,YAAY;AACtC,YAAM,oBAAoB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,wBAAwB;AAAA,QACxB,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB;AAAA,QAClB,SAAS;AAAA,QACT,QAAQ,OAAO,MAAM,cAAc,UAAU,kBAAkB;AAC7D,gBAAM,YAAY,cAAc,OAAO,CAAC,MAAM,MAAM,QAAQ;AAC5D,gBAAM,KAAK,MAAM,YAAY;AAC7B,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AAED,SAAO,GAAG,iBAAiB,gBAAgB,CAAC,SAAqC;AAC/E,UAAM,gBAAgB,KAAK;AAC3B,QAAI,OAAO,kBAAkB,SAAU;AAEvC,QAAI,CAAC,OAAO;AACV,aAAO,KAAK,qCAAqC,aAAa,GAAG;AAAA,QAC/D,GAAG,uBAAuB;AAAA,UACxB,UAAU,EAAE,QAAQ,SAAS,WAAW,gBAAgB;AAAA,UACxD;AAAA,QACF,CAAC;AAAA,QACD,OAAO,CAAC;AAAA,MACV,CAAC;AACD;AAAA,IACF;AAEA,WAAO,KAAK,qCAAqC,aAAa,GAAG;AAAA,MAC/D,OAAO,sBAAsB,QAAQ,KAAK;AAAA,IAC5C,CAAC;AAAA,EACH,CAAC;AACH;AAEA,IAAM,0BAA0B,CAAC,QAA6B;AAC5D,QAAM,EAAE,QAAQ,OAAO,4BAA4B,aAAa,IAAI;AAEpE,SAAO,GAAG,iBAAiB,YAAY,CAAC,WAAmB;AAKzD,sBAAkB,OAAO,EAAE;AAC3B,SAAKA,cAAa,sBAAsB,EAAE,UAAU,OAAO,IAAI,OAAO,OAAO,CAAC;AAE9E,QAAI,4BAA4B;AAC9B,WAAK,YAAY,EAAE,KAAK,CAAC,aAAa;AAAE,kBAAU,cAAc,OAAO,EAAE;AAAA,MAAG,CAAC;AAAA,IAC/E;AAEA,QAAI,8BAA8B,OAAO;AACvC,YAAM,YAAY;AAChB,cAAM,WAAW,MAAM,YAAY;AACnC,YAAI,SAAU,UAAS,oBAAoB,EAAE,OAAO,QAAQ,OAAO,CAAC;AAAA,MACtE,GAAG;AAAA,IACL,OAAO;AACL,UAAI,CAAC,MAAO;AACZ,UAAI,cAAc;AAChB,QAAAC,WAAU,EAAE,MAAM,qBAAqB,EAAE,OAAO,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,IAAM,8BAA8B,CAAC,QAA6B;AAChE,QAAM,EAAE,QAAQ,OAAO,4BAA4B,yBAAyB,aAAa,IAAI;AAE7F,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,CAAC,gBAA6E;AAC5E,UAAI,CAAC,MAAO;AACZ,UAAI,CAAC,wBAAyB;AAC9B,UAAI,cAAc;AAChB,QAAAA,WAAU,EAAE,MAAM,qBAAqB,EAAE,UAAU,YAAY,SAAS,CAAC;AAAA,MAC3E;AAEA,WAAK,gBAAgB,OAAO,YAAY;AACtC,YAAI,eAAyC;AAC7C,YAAI,4BAA4B;AAC9B,gBAAM,WAAW,MAAM,YAAY;AACnC,cAAI,UAAU;AACZ,2BAAe,MAAM,SAAS,gBAAgB,EAAE,OAAO,QAAQ,SAAS,YAAY,SAAS,CAAC;AAAA,UAChG;AAAA,QACF;AAEA,cAAM,OAAO,gBAAiB,MAAMF,aAAY,KAAK;AACrD,YAAI,CAAC,KAAM;AAEX,cAAM,eAAe;AACrB,cAAM,cAAc,aAAa;AAGjC,cAAM,gBAAgB,wBAAwB,EAAE,GAAG,cAAc,UAAU,YAAY,CAAC;AACxF,cAAM,aAAa,OAAO,aAAa;AAEvC,aAAKC,cAAa,oBAAoB,EAAE,OAAO,aAAa,YAAY,CAAC;AAAA,MAC3E,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAM,yBAAyB,CAAC,QAA6B;AAC3D,QAAM,EAAE,QAAQ,OAAO,4BAA4B,GAAG,IAAI;AAE1D,MAAI,8BAA8B,OAAO;AACvC,UAAM,YAAY;AAChB,YAAM,WAAW,MAAM,YAAY;AACnC,UAAI,SAAU,UAAS,wBAAwB,EAAE,QAAQ,MAAM,CAAC;AAAA,IAClE,GAAG;AAAA,EACL;AAOA,MAAI,4BAA4B;AAC9B,UAAM,YAAY;AAChB,YAAM,WAAW,MAAM,YAAY;AACnC,UAAI,CAAC,SAAU;AACf,eAAS,qBAAqB,EAAE,GAAG,CAAC;AACpC,eAAS,eAAe,OAAO,EAAE;AAAA,IACnC,GAAG;AACH,WAAO,GAAG,iBAAiB,UAAU,MAAM;AACzC,WAAK,YAAY,EAAE,KAAK,CAAC,aAAa;AAAE,kBAAU,eAAe,OAAO,EAAE;AAAA,MAAG,CAAC;AAAA,IAChF,CAAC;AACD,WAAO,GAAG,iBAAiB,sBAAsB,MAAM;AACrD,WAAK,YAAY,EAAE,KAAK,CAAC,aAAa;AAAE,kBAAU,eAAe,OAAO,EAAE;AAAA,MAAG,CAAC;AAAA,IAChF,CAAC;AAAA,EACH;AACF;AAEA,IAAM,uBAAuB,CAAC,QAA6B;AACzD,QAAM,EAAE,QAAQ,OAAO,aAAa,IAAI;AAExC,MAAI,CAAC,MAAO;AAWZ,QAAM,YAAY;AAChB,UAAM,CAAC,aAAa,KAAK,IAAI,MAAMF,UAAS,YAAY;AAGtD,YAAM,OAAO,KAAK,KAAK;AACvB,YAAM,UAAU,MAAMC,aAAY,KAAK;AACvC,YAAM,YAAY,UAAU,oBAAoB,OAAO,IAAI,CAAC;AAC5D,YAAM,SAAS,SAAS,MAAM;AAC9B,iBAAW,YAAY,WAAW;AAGhC,cAAM,OAAO,KAAK,eAAe,UAAU,EAAE,SAAS,QAAQ,OAAO,CAAC,CAAC;AAAA,MACzE;AACA,aAAO;AAAA,IACT,CAAC;AACD,QAAI,aAAa;AACf,MAAAE,WAAU,EAAE,KAAK,iCAAiC,OAAO,EAAE,IAAI,EAAE,OAAO,YAAY,QAAQ,CAAC;AAC7F;AAAA,IACF;AACA,QAAI,cAAc;AAChB,MAAAA,WAAU,EAAE,MAAM,UAAU,OAAO,EAAE,uBAAuB,SAAS,CAAC,GAAG,KAAK,IAAI,KAAK,QAAQ,EAAE;AAAA,IACnG;AAAA,EACF,GAAG;AACL;AAEO,IAAM,aAAa,CAAC,YAAwB,UAA6B,CAAC,MAAwB;AACvG,QAAM,SAASC,mBAAiB;AAChC,QAAM,eAAe,OAAO,QAAQ;AACpC,QAAM,yBAAyB,OAAO,QAAQ;AAE9C,QAAM,KAAK,IAAI,eAAe,YAAY;AAAA,IACxC,MAAM;AAAA,MACJ,SAAS,CAAC,OAAO,QAAQ,OAAO,UAAU,SAAS;AAAA,MACnD,QAAQ,CAAC,QAAQ,aAAa;AAQ5B,YAAI,CAAC,QAAQ;AACX,cAAI,OAAO,KAAK,KAAK,iBAAiB;AACpC,qBAAS,MAAM,IAAI;AAAA,UACrB,OAAO;AACL,qBAAS,IAAI,MAAM,gDAAgD,CAAC;AAAA,UACtE;AACA;AAAA,QACF;AACA,YAAIC,eAAc,MAAM,GAAG;AACzB,mBAAS,MAAM,IAAI;AAAA,QACrB,OAAO;AACL,mBAAS,IAAI,MAAM,qBAAqB,CAAC;AAAA,QAC3C;AAAA,MACF;AAAA,MACA,aAAa;AAAA,IACf;AAAA,IACA,mBAAmB,QAAQ,qBAAqB,OAAO,OAAO;AAAA,IAC9D,aAAa,OAAO,OAAO;AAAA,IAC3B,cAAc,OAAO,OAAO;AAAA,EAC9B,CAAC;AAED,gBAAc,EAAE;AAKhB,yBAAuB,EAAE;AAQzB,QAAM,YAAYC,OAAM,UAAU;AAClC,QAAM,YAAYA,OAAM,UAAU;AAClC,2BAAyB,IAAI,EAAE,WAAW,UAAU,CAAC;AAErD,MAAI,wBAAwB;AAC1B,IAAAH,WAAU,EAAE,KAAK,sDAAsD;AAAA,EACzE;AAEA,KAAG,GAAG,iBAAiB,SAAS,CAAC,WAAW;AAC1C,UAAM,QAAQ,uBAAuB,MAAM;AAG3C,UAAM,6BAA6B,OAAO,6BAA6B;AACvE,UAAM,0BAA0B,OAAO,2BAA2B;AAIlE,UAAM,kBACJ,0BAA0B,OAAO,UAAU,QAAQ,YAAY,CAAC,KAC7D,0BAA0B,OAAO,UAAU,QAAQ,iBAAiB,CAAC,KACrE;AAGL,QAAI,SAAS,aAAa,UAAU;AAClC,YAAM,YAAY;AAChB,cAAM,WAAW,MAAM,YAAY;AACnC,YAAI,SAAU,OAAM,SAAS,gBAAgB,EAAE,OAAO,GAAG,CAAC;AAAA,MAC5D,GAAG;AAAA,IACL;AAEA,SAAKD,cAAa,mBAAmB;AAAA,MACnC,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,IAAI,OAAO,UAAU;AAAA,IACvB,CAAC;AAED,UAAM,MAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,6BAAyB,GAAG;AAC5B,+BAA2B,GAAG;AAC9B,uBAAmB,GAAG;AACtB,4BAAwB,GAAG;AAC3B,gCAA4B,GAAG;AAC/B,2BAAuB,GAAG;AAC1B,yBAAqB,GAAG;AAAA,EAC1B,CAAC;AAED,SAAO,EAAE,IAAI,gBAAgB,EAAE,WAAW,UAAU,EAAE;AACxD;;;AC7jBA;AAAA,EACE;AAAA,EACA,aAAAK;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAqBA,IAAM,kBAAkB,OAAO,eAAsC,CAAC,MAAqB;AAChG,QAAM,UAAoB,CAAC;AAE3B,MAAI,CAAC,0BAA0B,GAAG;AAChC,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,uBAAuB,CAAC,yBAAyB,GAAG;AACnE,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,uBAAuB;AACtC,UAAM,EAAE,2BAA2B,IAAI,MAAM,OAAO,kBAAkB;AACtE,QAAI,CAAC,2BAA2B,GAAG;AACjC,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,uBAAuB;AACtC,UAAM,QAAQ,MAAM,SAAS;AAC7B,QAAI,CAAC,OAAO;AACV,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF,WAAW,MAAM,kBAAkB,EAAE,UAAU,GAAG;AAGhD,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAKA,MAAI,CAAC,gCAAgC,GAAG;AACtC,QAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF,OAAO;AACL,MAAAC,WAAU,EAAE;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,MAAI,CAAC,gCAAgC,GAAG;AACtC,QAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF,OAAO;AACL,MAAAA,WAAU,EAAE;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AASA,MAAI,0BAA0B,GAAG;AAC/B,UAAM,uBAAuB,2BAA2B,EAAE;AAC1D,UAAM,iBAAiBC,mBAAiB,EAAE,KAAK,WAAW;AAC1D,QAAI,uBAAuB,KAAK,mBAAmB,SAAS;AAC1D,MAAAD,WAAU,EAAE;AAAA,QACV,2EACK,OAAO,oBAAoB,CAAC;AAAA,MAGnC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,EAAG;AAE1B,QAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,IAAI;AAC5E,QAAM,IAAI;AAAA,IACR;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;;;AC1HA;AAAA,EACE,aAAAE;AAAA,EACA;AAAA,OAIK;AA6BP,IAAM,mBAAsC;AAAA,EAC1C,YAAY,CAAC;AAAA,EACb,YAAY,CAAC;AAAA,EACb,iBAAiB,CAAC;AACpB;AAEA,IAAM,qBAAqB,CAAC,UAC1B,QAAQ,KAAK,KAAK,OAAO,UAAU;AAErC,IAAM,2BAA2B,CAAC,aAAsB,WAAsC;AAC5F,QAAM,eAAe,eAAe,OAAO,gBAAgB,WACtD,cACD,CAAC;AAEL,QAAM,eAAe,aAAa;AAClC,QAAM,gBAAgB,aAAa;AACnC,QAAM,oBAAoB,aAAa;AAMvC,MACE,CAAC,mBAAmB,YAAY,KAC7B,CAAC,mBAAmB,aAAa,KACjC,CAAC,mBAAmB,iBAAiB,GACxC;AACA,IAAAC,YAAU,EAAE;AAAA,MACV,oCAAoC,MAAM;AAAA,IAI5C;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY,mBAAmB,YAAY,IAAI,eAAe,CAAC;AAAA,IAC/D,YAAY,mBAAmB,aAAa,IAAI,gBAAgB,CAAC;AAAA,IACjE,iBAAiB,mBAAmB,iBAAiB,IAAI,oBAAoB,CAAC;AAAA,EAChF;AACF;AA+BA,IAAM,iBAAiB,CAAC,YAAoD;AAC1E,QAAM,cAAc,QAAQ;AAC5B,MAAI,OAAO,gBAAgB,YAAY,YAAY,SAAS,GAAG;AAC7D,WAAO,CAAC,WAAW;AAAA,EACrB;AACA,MAAI,MAAM,QAAQ,WAAW,KAAK,YAAY,SAAS,GAAG;AACxD,WAAO,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC;AAAA,EACjC;AACA,QAAM,WAAW,iBAAiB;AAClC,MAAI,SAAS,SAAS,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,CAAC,SAAS;AACnB;AAEA,IAAM,YAAY,CAChB,QACA,QACA,MACA,YACA,cACS;AACT,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAM,iBAAiB,UAAU,IAAI,GAAG;AACxC,QAAI,mBAAmB,UAAa,mBAAmB,YAAY;AACjE,YAAM,IAAI;AAAA,QACR,4BAA4B,IAAI,oBAAoB,GAAG,6BAC5C,cAAc,iBAAiB,UAAU;AAAA,MAEtD;AAAA,IACF;AACA,cAAU,IAAI,KAAK,UAAU;AAC7B,WAAO,GAAG,IAAI,OAAO,GAAG;AAAA,EAC1B;AACF;AAWA,IAAM,eAAe,MAAe,QAAQ,IAAI,aAAa;AAEtD,IAAM,gCAAgC,CAC3C,YACwB;AACxB,MAAI,kBAAqD;AAEzD,MAAI,sBAAyD;AAC7D,QAAM,YAAY,YAAwC;AACxD,4BAAwB,OAAO,oBAAoB;AACnD,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,sBAAsB,YAAwC;AAClE,QAAI,gBAAiB,QAAO,MAAM;AAElC,uBAAmB,YAAY;AAC7B,YAAM,UAAU,eAAe,OAAO;AACtC,YAAM,SAA4B;AAAA,QAChC,YAAY,CAAC;AAAA,QACb,YAAY,CAAC;AAAA,QACb,iBAAiB,CAAC;AAAA,MACpB;AACA,YAAM,YAAY,oBAAI,IAAoB;AAC1C,YAAM,aAAa,oBAAI,IAAoB;AAC3C,YAAM,iBAAiB,oBAAI,IAAoB;AAE/C,YAAM,gBAAgB,MAAM,QAAQ;AAAA,QAClC,QAAQ,IAAI,OAAO,YAAY;AAAA,UAC7B;AAAA,UACA,KAAK,MAAM,QAAQ,cAAc,MAAM,EAAE,MAAM,MAAM,IAAI;AAAA,QAC3D,EAAE;AAAA,MACJ;AAEA,UAAI,YAAY;AAChB,iBAAW,EAAE,QAAQ,IAAI,KAAK,eAAe;AAC3C,YAAI,CAAC,KAAK;AACR,UAAAA,YAAU,EAAE;AAAA,YACV,oCAAoC,MAAM;AAAA,UAE5C;AACA;AAAA,QACF;AACA,oBAAY;AACZ,cAAM,aAAa,yBAAyB,KAAK,MAAM;AACvD,kBAAU,OAAO,YAAY,WAAW,YAAY,OAAO,QAAQ,SAAS;AAC5E,kBAAU,OAAO,YAAY,WAAW,YAAY,QAAQ,QAAQ,UAAU;AAC9E,kBAAU,OAAO,iBAAiB,WAAW,iBAAiB,YAAY,QAAQ,cAAc;AAAA,MAClG;AAEA,UAAI,CAAC,WAAW;AACd,QAAAA,YAAU,EAAE;AAAA,UACV,wDAAwD,QAAQ,KAAK,IAAI,CAAC;AAAA,QAE5E;AACA,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,GAAG;AAEH,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,oBAAoB,YAA2C;AACnE,QAAI,CAAC,aAAa,GAAG;AACnB,YAAM,EAAE,SAAS,aAAa,IAAI,MAAM,UAAU;AAClD,aAAO;AAAA,QACL,YAAY,mBAAmB,OAAO,IAAI,UAAU,CAAC;AAAA,QACrD,iBAAiB,mBAAmB,YAAY,IAAI,eAAe,CAAC;AAAA,MACtE;AAAA,IACF;AACA,UAAM,EAAE,YAAY,gBAAgB,IAAI,MAAM,oBAAoB;AAClE,WAAO,EAAE,YAAY,gBAAgB;AAAA,EACvC;AAEA,QAAM,qBAAqB,YAA4C;AACrE,QAAI,CAAC,aAAa,GAAG;AACnB,YAAM,EAAE,UAAU,aAAa,IAAI,MAAM,UAAU;AACnD,aAAO;AAAA,QACL,YAAY,mBAAmB,QAAQ,IAAI,WAAW,CAAC;AAAA,QACvD,iBAAiB,mBAAmB,YAAY,IAAI,eAAe,CAAC;AAAA,MACtE;AAAA,IACF;AACA,UAAM,EAAE,YAAY,gBAAgB,IAAI,MAAM,oBAAoB;AAClE,WAAO,EAAE,YAAY,gBAAgB;AAAA,EACvC;AAEA,SAAO,EAAE,mBAAmB,mBAAmB;AACjD;AAOO,IAAM,kCAAkC,CAC7C,YACwB;AACxB,QAAM,WAAW,8BAA8B,OAAO;AACtD,8BAA4B,QAAQ;AACpC,SAAO;AACT;;;AC1QA;AAAA,EACE,gBAAAC;AAAA,EACA;AAAA,EACA,aAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AAGP,IAAM,8BAA8B;AAapC,IAAM,cAAc,OAClB,OACA,WACA,OACqB;AACrB,MAAI;AACJ,QAAM,UAAU,IAAI,QAAe,CAAC,YAAY;AAC9C,YAAQ,WAAW,MAAM;AACvB,MAAAD,YAAU,EAAE,KAAK,oBAAoB,KAAK,cAAc,OAAO,SAAS,CAAC,qBAAgB;AACzF,cAAQ,KAAK;AAAA,IACf,GAAG,SAAS;AAEZ,UAAM,MAAM;AAAA,EACd,CAAC;AACD,QAAM,OAAO,YAA2B;AACtC,UAAM,CAAC,KAAK,IAAI,MAAMC,UAAS,EAAE;AACjC,QAAI,MAAO,CAAAD,YAAU,EAAE,KAAK,oBAAoB,KAAK,aAAa,MAAM,OAAO,EAAE;AACjF,WAAO;AAAA,EACT,GAAG;AACH,QAAM,YAAY,MAAM,QAAQ,KAAK,CAAC,KAAK,OAAO,CAAC;AACnD,MAAI,MAAO,cAAa,KAAK;AAC7B,SAAO;AACT;AAEA,IAAM,kBAAkB,CAAC,eACvB,IAAI,QAAc,CAAC,SAAS,WAAW;AAOrC,aAAW,MAAM,CAAC,UAAU;AAAE,QAAI,MAAO,QAAO,KAAK;AAAA,QAAQ,SAAQ;AAAA,EAAG,CAAC;AAC3E,CAAC;AAEH,IAAM,gBAAgB,CAAC,aACrB,IAAI,QAAc,CAAC,SAAS,WAAW;AAKrC,OAAK,SAAS,MAAM,CAAC,UAAU;AAAE,QAAI,MAAO,QAAO,KAAK;AAAA,QAAQ,SAAQ;AAAA,EAAG,CAAC;AAC9E,CAAC;AAEH,IAAM,kBAAkB,OAAO,WAAuC;AACpE,QAAM,OAAO,KAAK;AACpB;AAiBO,IAAM,sBAAsB,OACjC,MACA,UAAuC,CAAC,MACtB;AAClB,QAAM,EAAE,YAAY,UAAU,eAAe,IAAI;AACjD,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,YAAY,QAAQ,aAAa;AAEvC,EAAAA,YAAU,EAAE,KAAK,gDAAgD,MAAM,GAAG;AAK1E,QAAM,aAAa,YAAY,cAAc,WAAW,MAAM,gBAAgB,UAAU,CAAC;AAKzF,QAAM,YAAY,sBAAsB,WAAW,YAAY;AAC7D,UAAMD,cAAa,iBAAiB,EAAE,QAAQ,UAAU,CAAC;AAAA,EAC3D,CAAC;AAID,QAAM,YAAY,wBAAwB,WAAW,MAAM,mBAAmB,CAAC;AAI/E,QAAM,YAAY,YAAY,WAAW,MAAM,cAAc,QAAQ,CAAC;AAEtE,QAAM;AAIN,QAAM,YAAY,kBAAkB,WAAW,MAAM,gBAAgB,eAAe,SAAS,CAAC;AAC9F,QAAM,YAAY,kBAAkB,WAAW,MAAM,gBAAgB,eAAe,SAAS,CAAC;AAE9F,EAAAC,YAAU,EAAE,KAAK,uCAAuC;AAC1D;;;A5BtEA,IAAM,eAAe,YAA2B;AAI9C,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,kBAAkB;AAC1D,iBAAe;AACf,QAAM,iBAAiB;AACvB,QAAM,SAAU,MAAM,OAAO;AAI7B,QAAM,OAAO,cAAc;AAC3B,SAAO,cAAc;AAIrB,UAAQ,KAAK,UAAU,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC5C,UAAQ,KAAK,WAAW,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC/C;AAMO,IAAM,yBAAyB,CACpC,YACA,IACA,MACA,aAEA,IAAI,QAAoB,CAAC,SAAS,WAAW;AAC3C,QAAM,YAAY,OAAO,SAAS,WAAW,OAAO,SAAS,MAAM,EAAE,IAAI;AAOzE,QAAM,gBAAgB,CAAC,KAAK,MAAM,EAAE;AAAA,KACjC,QAAQ,IAAI,8BAA8B,IAAI,YAAY;AAAA,EAC7D;AAEA,QAAM,YAAY,CAAC,gBAA8B;AAC/C,UAAM,UAAU,CAAC,QAAqC;AACpD,UAAI,IAAI,SAAS,cAAc;AAC7B,eAAO,GAAG;AACV;AAAA,MACF;AACA,UAAI,eAAe;AACjB,QAAAE,YAAU,EAAE;AAAA,UACV,QAAQ,OAAO,WAAW,CAAC,4BAAuB,OAAO,cAAc,CAAC,CAAC;AAAA,QAC3E;AACA,kBAAU,cAAc,CAAC;AACzB;AAAA,MACF;AAKA,MAAAA,YAAU,EAAE;AAAA,QACV,QAAQ,OAAO,WAAW,CAAC;AAAA,MAG7B;AACA,aAAO,GAAG;AAAA,IACZ;AAEA,eAAW,KAAK,SAAS,OAAO;AAChC,eAAW,OAAO,aAAa,IAAI,MAAM;AACvC,iBAAW,IAAI,SAAS,OAAO;AAC/B,YAAM,SAASC,mBAAiB;AAChC,UAAI,OAAO,QAAQ,iBAAiB,OAAO,QAAQ,SAAS;AAC1D,QAAAD,YAAU,EAAE,KAAK,+BAA+B,EAAE,IAAI,OAAO,WAAW,CAAC,GAAG;AAAA,MAC9E;AACA,iBAAW;AACX,cAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH;AAEA,YAAU,SAAS;AACrB,CAAC;AAEI,IAAM,yBAAyB,OACpC,UAAyC,CAAC,MACL;AAOrC,MAAI,QAAQ,mBAAmB;AAC7B,oCAAgC;AAAA,MAC9B,eAAe,QAAQ;AAAA,MACvB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAAA,EACH;AAKA,QAAM,gBAAgB;AAAA,IACpB,qBAAqB,QAAQ;AAAA,IAC7B,uBAAuB,QAAQ;AAAA,IAC/B,uBAAuB,QAAQ;AAAA,EACjC,CAAC;AAED,QAAM,OAAO,QAAQ,QAAQ,cAAc,KAAK,QAAQ,IAAI,eAAe;AAC3E,QAAM,KAAK,QAAQ,MAAM,QAAQ,IAAI,aAAa;AAClD,QAAM,iBAAiB,QAAQ,kBAAkB,QAAQ,IAAI,aAAa;AAM1E,sBAAoB;AAAA,IAClB;AAAA,IACA,MAAM,OAAO,SAAS,WAAW,OAAO,SAAS,MAAM,EAAE,IAAI;AAAA,EAC/D,CAAC;AAED,MAAI,gBAAgB;AAClB,UAAM,aAAa;AAAA,EACrB;AAUA,QAAM,CAAC,aAAa,IAAI,MAAME,UAAS,MAAM,cAAc,CAAC;AAC5D,MAAI,eAAe;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,EAAE,OAAO,cAAc;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,aAAyB,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7D,SAAK,kBAAkB,KAAK,KAAK,OAAO;AAAA,EAC1C,CAAC;AAOD,aAAW,GAAG,SAAS,CAAC,QAA+B;AAGrD,QAAI,IAAI,SAAS,aAAc;AAC/B,IAAAF,YAAU,EAAE,MAAM,+BAA+B,GAAG;AAAA,EACtD,CAAC;AAED,QAAM,EAAE,IAAI,UAAU,eAAe,IAAI,WAAW,YAAY;AAAA,IAC9D,mBAAmB,QAAQ;AAAA,EAC7B,CAAC;AAED,QAAM,SAAS,CAAC,aACd,uBAAuB,YAAY,IAAI,MAAM,QAAQ;AAKvD,MAAI,kBAAwC;AAC5C,QAAM,OAAO,CAAC,cAA2C,CAAC,MAAqB;AAC7E,wBAAoB,oBAAoB,EAAE,YAAY,UAAU,eAAe,GAAG,WAAW;AAC7F,WAAO;AAAA,EACT;AAQA,MAAI,CAAC,gBAAgB;AACnB,UAAM,eAAe,CAAC,WAAuC;AAC3D,YAAM,YAAY;AAChB,cAAM,KAAK,EAAE,OAAO,CAAC;AAMrB,gBAAQ,KAAK,CAAC;AAAA,MAChB,GAAG;AAAA,IACL;AACA,YAAQ,KAAK,WAAW,MAAM;AAAE,mBAAa,SAAS;AAAA,IAAG,CAAC;AAC1D,YAAQ,KAAK,UAAU,MAAM;AAAE,mBAAa,QAAQ;AAAA,IAAG,CAAC;AAAA,EAC1D;AAEA,SAAO,EAAE,YAAY,UAAU,QAAQ,MAAM,OAAO,KAAK;AAC3D;;;A6B9OA,OAAOG,WAAU;AACjB,OAAO,QAAQ;AACf,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AAsBzB,IAAM,gBAAgB;AAAA;AAAA;AAAA,EAGpB;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AACF;AAEA,IAAM,iBAAiB,OAAO,aAAoC;AAChE,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG;AAC9B,QAAM,OAAO,cAAc,QAAQ,EAAE;AACvC;AAEA,IAAM,oBAAoB,OAAO,gBAAuC;AACtE,QAAM,aAAaC,MAAK,WAAW,WAAW,IAC1C,cACAA,MAAK,KAAK,UAAU,WAAW;AAEnC,MAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAG9B;AAAA,EACF;AAEA,aAAW,eAAe,eAAe;AACvC,UAAM,aAAaA,MAAK,KAAK,YAAY,WAAW;AACpD,QAAI,CAAC,GAAG,WAAW,UAAU,EAAG;AAKhC,UAAM,kBAAkB,CAAC,YAAY,UAAU;AAC/C,eAAW,aAAa,iBAAiB;AACvC,YAAM,eAAeA,MAAK,KAAK,YAAY,SAAS,CAAC;AAAA,IACvD;AAEA,UAAM,UAAU,GAAG,YAAY,UAAU,EAAE,SAAS;AACpD,eAAW,SAAS,SAAS;AAC3B,UAAI,gBAAgB,SAAS,KAAK,EAAG;AACrC,UAAI,CAAC,MAAM,SAAS,KAAK,KAAK,CAAC,MAAM,SAAS,KAAK,EAAG;AACtD,YAAM,eAAeA,MAAK,KAAK,YAAY,KAAK,CAAC;AAAA,IACnD;AAAA,EACF;AACF;AAUA,IAAM,iCAAiC,YAA2B;AAChE,aAAW,OAAO,mBAAmB;AACnC,UAAM,YAAY,eAAe,GAAG;AACpC,QAAI,CAAC,WAAW,SAAS,EAAG;AAC5B,UAAM,wBAAwB,SAAS;AAAA,EACzC;AACF;AAEA,IAAM,0BAA0B,OAAO,cAAqC;AAC1E,MAAI;AACF,UAAM,OAAO;AAAA,EACf,QAAQ;AAAA,EAIR;AACF;AAEO,IAAM,sBAAsB,OACjC,UAAsC,CAAC,MACF;AACrC,QAAM,cAAc,QAAQ,eAAe;AAE3C,MAAI,CAAC,QAAQ,iBAAiB;AAE5B,UAAM,+BAA+B;AACrC,UAAM,kBAAkB,WAAW;AAAA,EACrC;AAMA,QAAM,SAAS;AAEf,QAAM,SAAS,MAAM,uBAAuB,OAAO;AACnD,SAAO;AACT;;;ACpIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":["getLogger","getProjectConfig","tryCatch","dispatchHook","extractTokenFromRequest","getLogger","getProjectConfig","readSession","getProjectConfig","getProjectConfig","getCsrfConfig","readSession","getCsrfConfig","readSession","getProjectConfig","getProjectConfig","redis","tryCatch","getProjectConfig","tryCatch","redis","dispatchHook","getProjectConfig","getProjectConfig","dispatchHook","getLogger","getProjectConfig","tryCatch","tryCatch","getLogger","http","getProjectConfig","getLogger","getProjectConfig","getProjectConfig","getLogger","dispatchHook","getLogger","tryCatch","getProjectConfig","getProjectConfig","resolveClientIp","tryCatch","getLogger","dispatchHook","captureException","dispatchHook","extractTokenFromRequest","getLogger","tryCatch","tryCatch","extractTokenFromRequest","getLogger","captureException","dispatchHook","captureException","getLogger","tryCatch","tryCatch","getLogger","captureException","http","getProjectConfig","getLogger","handlers","readSession","dispatchHook","extractTokenFromRequest","allowedOrigin","redis","getLogger","getProjectConfig","dispatchHook","readSession","tryCatch","tryCatch","readSession","dispatchHook","getLogger","getProjectConfig","allowedOrigin","redis","getLogger","getProjectConfig","getLogger","getProjectConfig","getLogger","getLogger","dispatchHook","getLogger","tryCatch","getLogger","getProjectConfig","tryCatch","path","path"]}
|