@luckystack/server 0.1.7 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.5]
11
+
12
+ ### Changed
13
+
14
+ - **OAuth post-login redirect no longer reads the `DNS` env var.** `authCallbackRoute`
15
+ now redirects to `projectConfig.app.publicUrl` (the public origin where users
16
+ browse) after a callback, instead of `process.env.DNS || app.publicUrl`. The
17
+ callback is handled on the backend origin but must send the browser back to the
18
+ public origin. Set `app.publicUrl` in your `config.ts` (the scaffold does this).
19
+
20
+ ### Fixed
21
+
22
+ - **CSRF no longer blocks credentials login/register when a session cookie already
23
+ exists.** `POST /auth/api/credentials` is the session bootstrap, so requiring a
24
+ pre-existing session's CSRF token to authenticate is circular and broke
25
+ legitimate same-site re-login/register (403 `auth.csrfMismatch`). That endpoint
26
+ is now exempt from CSRF enforcement. This removes no real protection: the session
27
+ cookie is `SameSite=Strict`, so a cross-site POST never carries it and the guard
28
+ wouldn't have fired anyway. All other `/auth/api/*`, `/api/*`, and `/sync/*`
29
+ state-changing routes remain protected.
30
+
10
31
  ## [0.1.0]
11
32
 
12
33
  ### Added
package/dist/index.js CHANGED
@@ -53,7 +53,8 @@ var enforceCsrfOnStateChangingRequest = async ({
53
53
  const isCookieMode = !config.session.basedToken;
54
54
  const isStateChanging = req.method !== "GET" && req.method !== "OPTIONS";
55
55
  const isCallbackPath = routePath.startsWith("/auth/callback");
56
- const looksLikeFrameworkRoute = routePath.startsWith("/api/") || routePath.startsWith("/sync/") || routePath.startsWith("/auth/api/");
56
+ const isAuthBootstrap = routePath === "/auth/api/credentials";
57
+ const looksLikeFrameworkRoute = routePath.startsWith("/api/") || routePath.startsWith("/sync/") || routePath.startsWith("/auth/api/") && !isAuthBootstrap;
57
58
  if (!(isCookieMode && isStateChanging && looksLikeFrameworkRoute && !isCallbackPath && token)) {
58
59
  return false;
59
60
  }
@@ -385,7 +386,7 @@ var handleAuthCallbackRoute = async ({
385
386
  const config = getProjectConfig6();
386
387
  const sessionCookieName = config.http.sessionCookieName;
387
388
  const shouldLogDev = config.logging.devLogs;
388
- const baseLocation = process.env.DNS || config.app.publicUrl || "/";
389
+ const baseLocation = config.app.publicUrl || "/";
389
390
  const callbackResult = await loginCallback(routePath, req, res, {
390
391
  defaultRedirectUrl: baseLocation
391
392
  });
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 const looksLikeFrameworkRoute =\r\n routePath.startsWith('/api/')\r\n || routePath.startsWith('/sync/')\r\n || routePath.startsWith('/auth/api/');\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\r\n //? URL. Prefer `projectConfig.app.publicUrl` (the public origin also used\r\n //? in transactional email links). The legacy `DNS` env var is kept as an\r\n //? override-on-top so existing deployments don't break, but new installs\r\n //? should set `app.publicUrl`.\r\n //? `process.env.DNS` is coerced to '' by the env layer when unset, so `??`\r\n //? would treat empty as set and shadow `app.publicUrl`. Use `||` so an empty\r\n //? DNS falls through to `app.publicUrl`, then `/` as last resort.\r\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- see comment above\r\n const baseLocation = (process.env.DNS || 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/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":";AAAA,OAAO,UAAyC;AAChD,SAAS,qBAAqB,eAAe,aAAAA,YAAW,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,OACK;AACP,SAAS,cAAAC,mBAAkB;;;ACP3B,SAAS,kBAAkB,wBAAwB;AAEnD,IAAM,uBAAuB;AAE7B,IAAM,gBAAgB,CAAC,QAAyB;AAC9C,MAAI,iBAAiB,GAAG,EAAG,QAAO;AAClC,SAAO,IAAI,YAAY,MAAM,iBAAiB,EAAE,KAAK,kBAAkB,YAAY;AACrF;AAEO,IAAM,iBAAiB,CAAC,UAA4B;AACzD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,UAAU,eAAe,KAAK,CAAC;AAE3E,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACzE,QAAI,GAAG,IAAI,cAAc,GAAG,IAAI,uBAAuB,eAAe,GAAG;AAAA,EAC3E;AACA,SAAO;AACT;;;ACLA,IAAI,oBAAmD;AAUhD,IAAM,0BAA0B,CAAC,YAAiD;AACvF,sBAAoB;AACtB;AAGO,IAAM,4BAA4B,MAAqC;;;AC/B9E,SAAS,cAAc,eAAe,oBAAAC,yBAAwB;AAC9D,SAAS,kBAAkB;AAIpB,IAAM,oCAAoC,OAAO;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMwB;AACtB,QAAM,SAASA,kBAAiB;AAChC,QAAM,eAAe,CAAC,OAAO,QAAQ;AACrC,QAAM,kBAAkB,IAAI,WAAW,SAAS,IAAI,WAAW;AAC/D,QAAM,iBAAiB,UAAU,WAAW,gBAAgB;AAO5D,QAAM,kBAAkB,cAAc;AACtC,QAAM,0BACJ,UAAU,WAAW,OAAO,KACzB,UAAU,WAAW,QAAQ,KAC5B,UAAU,WAAW,YAAY,KAAK,CAAC;AAE7C,MAAI,EAAE,gBAAgB,mBAAmB,2BAA2B,CAAC,kBAAkB,QAAQ;AAC7F,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM,WAAW,KAAK;AAC1C,MAAI,CAAC,aAAa,GAAI,QAAO;AAI7B,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;AAC/D,MAAI,YAAY,aAAa,YAAY,UAAW,QAAO;AAE3D,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;;;AClEA,SAAS,cAAAC,mBAAkB;AAGpB,IAAM,kBAAoC,OAAO,EAAE,KAAK,WAAW,MAAM,MAAM;AACpF,MAAI,cAAc,aAAc,QAAO;AAEvC,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,MAAMA,YAAW,KAAK;AAC1C,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;;;ACxBO,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,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;AAEhE,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;AAEjE,QAAM,WAAW,MAAM,aAAa;AACpC,QAAM,qBAAqB,6BAA6B;AACxD,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,EACF,CAAC,CAAC;AACF,SAAO;AACT;;;AC1FA;AAAA,EACE;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA,SAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AAGA,IAAM,uBAAyC,OAAO,EAAE,KAAK,KAAK,UAAU,MAAM;AACvF,MAAI,cAAcF,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,MAAI,CAAC,iBAAiB,IAAI,QAAQ,oBAAoB,MAAM,eAAe;AACzE,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,MAAME,UAAS,YAAY;AAClD,UAAI,SAAS;AACb,UAAI,QAAQ;AACZ,SAAG;AACD,cAAM,CAAC,MAAM,IAAI,IAAI,MAAMD,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;AAC1B,QAAM,OAAO,UAAU,IAAI,QAAQ,QAAQ,WAAW;AACtD,QAAM,cAAc,IAAI,SAAS,QAAQ,IAAI,IACzC,IAAI,IAAI,QAAQ,IAAI,EAAE,aAAa,IAAI,SAAS,KAAK,KACrD;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;;;ACnFA,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,gBAAAE;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,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;AAE/C,QAAM,SAASA,kBAAiB;AAChC,QAAM,oBAAoB,OAAO,KAAK;AACtC,QAAM,eAAe,OAAO,QAAQ;AAEpC,QAAM,eAAe,UAAU,MAAM,GAAG,EAAE,CAAC;AAC3C,QAAM,WAAW,kBAAkB,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY;AACxE,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,oBAAoB,QAAQ,GAAG;AACjC,UAAM,aAAa,MAAM,iBAAiB,SAAS,IAAI;AACvD,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,UAAU;AAE3C,QAAI,UAAU,KAAK;AAAA,MACjB,UAAU,GAAG,SAAS,gBAAgB,cAAc,QAAQ,iBAAiB,WAAW,UAAU,KAAK,mDAAmD,KAAK;AAAA,IACjK,CAAC;AACD,QAAI,IAAI;AACR,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO;AAC5B,MAAI,aAAa,oBAAoB,SAAS,aAAa,kBAAkB,GAAG;AAC9E,UAAM,cAAc,IAAI,OAAO,iBAAiB;AAChD,UAAM,EAAE,SAAS,QAAQ,IAAI,MAAM,eAAe;AAAA,MAChD,KAAK,MAAM,WAAW;AAAA,MACtB,OAAO,aAAa;AAAA,MACpB,UAAU,aAAa;AAAA,IACzB,CAAC;AAED,QAAI,CAAC,SAAS;AACZ,WAAKD,cAAa,qBAAqB;AAAA,QACrC,OAAO;AAAA,QACP,KAAK,MAAM,WAAW;AAAA,QACtB,OAAO,aAAa;AAAA,QACpB,UAAU,aAAa;AAAA,QACvB,OAAO,aAAa,kBAAkB;AAAA,QACtC,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;AAEA,QAAM,SAAU,MAAM,qBAAqB,MAAM;AAOjD,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;AACnB,QAAI,MAAO,OAAM,cAAc,KAAK;AAEpC,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;;;ACrIA,SAAS,qBAAAE,0BAAyB;AAS3B,IAAM,2BAA6C,CAAC,EAAE,KAAK,KAAK,UAAU,MAAM;AACrF,MAAI,cAAc,qBAAqB,IAAI,WAAW,MAAO,QAAO,QAAQ,QAAQ,KAAK;AAEzF,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,IAAI,KAAK,UAAU,EAAE,WAAWA,mBAAkB,EAAE,IAAI,CAAC,aAAa,SAAS,IAAI,EAAE,CAAC,CAAC;AAC3F,SAAO,QAAQ,QAAQ,IAAI;AAC7B;;;ACfA,SAAS,aAAAC,YAAW,oBAAAC,yBAAwB;AAC5C,SAAS,iBAAAC,gBAAe,qBAAqB;AAGtC,IAAM,0BAA4C,OAAO;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,CAAC,UAAU,WAAW,gBAAgB,EAAG,QAAO;AAEpD,QAAM,SAASD,kBAAiB;AAChC,QAAM,oBAAoB,OAAO,KAAK;AACtC,QAAM,eAAe,OAAO,QAAQ;AASpC,QAAM,eAAe,OAAO,IAAI,aAAa;AAC7C,QAAM,iBAAiB,MAAM,cAAc,WAAW,KAAK,KAAK;AAAA,IAC9D,oBAAoB;AAAA,EACtB,CAAC;AAED,MAAI,CAAC,gBAAgB;AACnB,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,cAAc;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,MAAO,OAAMC,eAAc,KAAK;AAEpC,MAAI,aAAc,CAAAF,WAAU,EAAE,MAAM,iDAAiD;AAErF,QAAM,EAAE,OAAO,UAAU,YAAY,IAAI;AACzC,MAAI,OAAO,QAAQ,YAAY;AAC7B,UAAM,YAAY,YAAY,SAAS,GAAG,IAAI,MAAM;AACpD,QAAI,UAAU,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,SAAS,QAAQ,GAAG,CAAC;AAAA,EAChF,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;;;ACjDA;AAAA,EACE;AAAA,EACA,gBAAAG;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;AACvB,MAAI,MAAM,UAAU,KAAK;AAAA,CAAI;AAC7B,MAAI,MAAM,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM;AAC/C;;;AD3CO,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;AACnB,QAAI,GAAG,SAAS,MAAM;AACpB,qBAAe;AAAA,IACjB,CAAC;AAAA,EACH;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;AAE5C,UAAM,SAAS,MAAM,qBAAqB;AAAA,MACxC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa,IAAI,OAAO,iBAAiB;AAAA,MACzC,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;;;AEpHA;AAAA,EACE,oBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,aAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AACP,SAAS,6BAAuD;AAWhE,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;AAE5C,QAAM,gBAAgB,oBAAoB,EAAE,cAAc,IAAI,QAAQ,QAAQ,YAAY,CAAC;AAC3F,MAAI,eAAe;AACnB,MAAI,eAAe;AACjB,oBAAgB,GAAG;AACnB,QAAI,GAAG,SAAS,MAAM;AACpB,qBAAe;AAAA,IACjB,CAAC;AAAA,EACH;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;AAEjD,UAAM,SAAS,MAAM,sBAAsB;AAAA,MACzC,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,IAAI,OAAO,iBAAiB;AAAA,MACzC,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;;;ACtJA,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;AAOhC,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,UAAU,SAAS,UAAU,GAAG;AAClC,QAAI,CAAC,QAAQ,WAAW;AACtB,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AACnB,aAAO;AAAA,IACT;AACA,UAAM,YAAY,UAAU,MAAM,UAAU,QAAQ,UAAU,CAAC;AAC/D,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;;;ACpDA,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;;;AlBJxE,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,kBAAiB,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;AACX,QAAI;AACF,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,SAAS,OAAO;AACd,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;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,YAAW,KAAK;AAC7C,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,kBAAiB;AAChC,QAAM,eAAe,OAAO,QAAQ;AACpC,QAAM,oBAAoB,OAAO,KAAK;AACtC,QAAM,uBAAuB;AAAA,IAC3B,OAAO,QAAQ;AAAA,IACf,QAAQ,IAAI,WAAW;AAAA,IACvB,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;;;AmB1SA,SAAS,UAAU,sBAAsB;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,EACA;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,YAAAC;AAAA,OAIK;AACP,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,cAAAC,aAAY,mBAAmB;AACxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,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,QAAMD,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;AAMO,IAAM,aAAa,CAAC,YAAwB,UAA6B,CAAC,MAAsB;AACrG,QAAM,SAASF,kBAAiB;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;AAC5B,YAAI,CAAC,UAAUF,eAAc,MAAM,GAAG;AACpC,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;AAIzB,2BAAyB,EAAE;AAE3B,MAAI,wBAAwB;AAC1B,IAAAC,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,OAAO;AACT,WAAK,gBAAgB,EAAE,OAAO,GAAG,CAAC;AAAA,IACpC;AAEA,SAAKE,cAAa,mBAAmB;AAAA,MACnC,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,IAAI,OAAO,UAAU;AAAA,IACvB,CAAC;AAED,WAAO,GAAG,iBAAiB,YAAY,CAAC,QAAoB;AAC1D,WAAK,iBAAiB,EAAE,KAAK,QAAQ,MAAM,CAAC;AAAA,IAC9C,CAAC;AAED,WAAO,GAAG,iBAAiB,MAAM,CAAC,QAAqB;AACrD,WAAK,kBAAkB,EAAE,KAAK,QAAQ,MAAM,CAAC;AAAA,IAC/C,CAAC;AAOD,WAAO,GAAG,iBAAiB,YAAY,CAAC,SAA0B;AAChE,YAAM,KAAK,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AACnD,UAAI,CAAC,GAAI;AACT,oBAAc,OAAO,IAAI,EAAE;AAAA,IAC7B,CAAC;AACD,WAAO,GAAG,iBAAiB,WAAW,CAAC,SAA8C;AACnF,YAAM,gBAAgB,KAAK;AAC3B,UAAI,OAAO,kBAAkB,YAAY,OAAO,kBAAkB,SAAU;AAC5E,8BAAwB,OAAO,IAAI,aAAa;AAAA,IAClD,CAAC;AAED,WAAO,GAAG,iBAAiB,UAAU,CAAC,SAAqD;AACzF,YAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AACnE,YAAM,gBAAgB,KAAK;AAC3B,UAAI,OAAO,kBAAkB,SAAU;AAEvC,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,+BAA+B,aAAa,GAAG,uBAAuB;AAAA,UAChF,UAAU,EAAE,QAAQ,SAAS,WAAW,gBAAgB;AAAA,UACxD;AAAA,QACF,CAAC,CAAC;AACF;AAAA,MACF;AACA,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,+BAA+B,aAAa,GAAG,uBAAuB;AAAA,UAChF,UAAU,EAAE,QAAQ,SAAS,WAAW,eAAe;AAAA,UACvD;AAAA,QACF,CAAC,CAAC;AACF;AAAA,MACF;AAEA,WAAK,gBAAgB,OAAO,YAAY;AACtC,cAAM,UAAU,MAAME,YAAW,KAAK;AACtC,YAAI,CAAC,SAAS;AACZ,iBAAO,KAAK,+BAA+B,aAAa,GAAG,uBAAuB;AAAA,YAChF,UAAU,EAAE,QAAQ,SAAS,WAAW,mBAAmB;AAAA,YAC3D;AAAA,UACF,CAAC,CAAC;AACF;AAAA,QACF;AAGA,cAAM,YAAY,MAAMF,cAAa,eAAe,EAAE,OAAO,MAAM,MAAM,CAAC;AAC1E,YAAI,UAAU,SAAS;AACrB,iBAAO,KAAK,+BAA+B,aAAa,GAAG,uBAAuB;AAAA,YAChF,UAAU;AAAA,cACR,QAAQ;AAAA,cACR,WAAW,UAAU,OAAO,aAAa;AAAA,YAC3C;AAAA,YACA;AAAA,YACA,cAAc,QAAQ;AAAA,UACxB,CAAC,CAAC;AACF;AAAA,QACF;AAEA,cAAM,oBAAoB,oBAAoB,OAAO;AACrD,cAAM,gBAAgB,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,mBAAmB,KAAK,CAAC,CAAC;AAEhE,cAAM,OAAO,KAAK,KAAK;AACvB,cAAM,mBAAmB,wBAAwB,OAAO;AACxD,cAAM,YAAY,OAAO,EAAE,GAAG,kBAAkB,WAAW,cAAc,CAAC;AAC1E,cAAM,eAAe,sBAAsB,QAAQ,KAAK;AACxD,eAAO,KAAK,+BAA+B,aAAa,GAAG,EAAE,OAAO,aAAa,CAAC;AAClF,YAAI,cAAc;AAChB,UAAAF,WAAU,EAAE,MAAM,UAAU,OAAO,EAAE,iBAAiB,KAAK,EAAE;AAAA,QAC/D;AAEA,aAAKE,cAAa,gBAAgB,EAAE,OAAO,MAAM,OAAO,UAAU,aAAa,CAAC;AAAA,MAClF,CAAC;AAAA,IACH,CAAC;AAED,WAAO,GAAG,iBAAiB,WAAW,CAAC,SAAqD;AAC1F,YAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AACnE,YAAM,gBAAgB,KAAK;AAC3B,UAAI,OAAO,kBAAkB,SAAU;AAEvC,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,gCAAgC,aAAa,GAAG,uBAAuB;AAAA,UACjF,UAAU,EAAE,QAAQ,SAAS,WAAW,gBAAgB;AAAA,UACxD;AAAA,QACF,CAAC,CAAC;AACF;AAAA,MACF;AACA,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,gCAAgC,aAAa,GAAG,uBAAuB;AAAA,UACjF,UAAU,EAAE,QAAQ,SAAS,WAAW,eAAe;AAAA,UACvD;AAAA,QACF,CAAC,CAAC;AACF;AAAA,MACF;AAEA,WAAK,gBAAgB,OAAO,YAAY;AACtC,cAAM,UAAU,MAAME,YAAW,KAAK;AACtC,YAAI,CAAC,SAAS;AACZ,iBAAO,KAAK,gCAAgC,aAAa,GAAG,uBAAuB;AAAA,YACjF,UAAU,EAAE,QAAQ,SAAS,WAAW,mBAAmB;AAAA,YAC3D;AAAA,UACF,CAAC,CAAC;AACF;AAAA,QACF;AAEA,cAAM,YAAY,MAAMF,cAAa,gBAAgB,EAAE,OAAO,MAAM,MAAM,CAAC;AAC3E,YAAI,UAAU,SAAS;AACrB,iBAAO,KAAK,gCAAgC,aAAa,GAAG,uBAAuB;AAAA,YACjF,UAAU;AAAA,cACR,QAAQ;AAAA,cACR,WAAW,UAAU,OAAO,aAAa;AAAA,YAC3C;AAAA,YACA;AAAA,YACA,cAAc,QAAQ;AAAA,UACxB,CAAC,CAAC;AACF;AAAA,QACF;AAEA,cAAM,oBAAoB,oBAAoB,OAAO;AACrD,cAAM,gBAAgB,kBAAkB,OAAO,CAAC,aAAa,aAAa,KAAK;AAE/E,cAAM,OAAO,MAAM,KAAK;AACxB,cAAM,mBAAmB,wBAAwB,OAAO;AACxD,cAAM,YAAY,OAAO,EAAE,GAAG,kBAAkB,WAAW,cAAc,CAAC;AAE1E,cAAM,eAAe,sBAAsB,QAAQ,KAAK;AACxD,eAAO,KAAK,gCAAgC,aAAa,GAAG,EAAE,OAAO,aAAa,CAAC;AACnF,YAAI,cAAc;AAChB,UAAAF,WAAU,EAAE,MAAM,UAAU,OAAO,EAAE,eAAe,KAAK,EAAE;AAAA,QAC7D;AAEA,aAAKE,cAAa,iBAAiB,EAAE,OAAO,MAAM,OAAO,UAAU,aAAa,CAAC;AAAA,MACnF,CAAC;AAAA,IACH,CAAC;AAED,WAAO,GAAG,iBAAiB,gBAAgB,CAAC,SAAqC;AAC/E,YAAM,gBAAgB,KAAK;AAC3B,UAAI,OAAO,kBAAkB,SAAU;AAEvC,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,qCAAqC,aAAa,GAAG;AAAA,UAC/D,GAAG,uBAAuB;AAAA,YACxB,UAAU,EAAE,QAAQ,SAAS,WAAW,gBAAgB;AAAA,YACxD;AAAA,UACF,CAAC;AAAA,UACD,OAAO,CAAC;AAAA,QACV,CAAC;AACD;AAAA,MACF;AAEA,aAAO,KAAK,qCAAqC,aAAa,GAAG;AAAA,QAC/D,OAAO,sBAAsB,QAAQ,KAAK;AAAA,MAC5C,CAAC;AAAA,IACH,CAAC;AAED,WAAO,GAAG,iBAAiB,YAAY,CAAC,WAAmB;AAKzD,wBAAkB,OAAO,EAAE;AAC3B,WAAKA,cAAa,sBAAsB,EAAE,UAAU,OAAO,IAAI,OAAO,OAAO,CAAC;AAE9E,UAAI,8BAA8B,OAAO;AACvC,aAAK,oBAAoB,EAAE,OAAO,QAAQ,OAAO,CAAC;AAAA,MACpD,OAAO;AACL,YAAI,CAAC,MAAO;AACZ,YAAI,cAAc;AAChB,UAAAF,WAAU,EAAE,MAAM,qBAAqB,EAAE,OAAO,CAAC;AAAA,QACnD;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL,iBAAiB;AAAA,MACjB,CAAC,gBAA6E;AAC5E,YAAI,CAAC,MAAO;AACZ,YAAI,CAAC,wBAAyB;AAC9B,YAAI,cAAc;AAChB,UAAAA,WAAU,EAAE,MAAM,qBAAqB,EAAE,UAAU,YAAY,SAAS,CAAC;AAAA,QAC3E;AAEA,aAAK,gBAAgB,OAAO,YAAY;AACtC,cAAI,eAAyC;AAC7C,cAAI,4BAA4B;AAC9B,2BAAe,MAAM,gBAAgB,EAAE,OAAO,QAAQ,SAAS,YAAY,SAAS,CAAC;AAAA,UACvF;AAEA,gBAAM,OAAO,gBAAiB,MAAMI,YAAW,KAAK;AACpD,cAAI,CAAC,KAAM;AAEX,gBAAM,eAAe;AACrB,gBAAM,cAAc,aAAa;AACjC,uBAAa,WAAW;AACxB,gBAAM,YAAY,OAAO,IAAI;AAE7B,eAAKF,cAAa,oBAAoB,EAAE,OAAO,aAAa,YAAY,CAAC;AAAA,QAC3E,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,8BAA8B,OAAO;AACvC,8BAAwB,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC3C;AAEA,QAAI,OAAO;AAUT,YAAM,YAAY;AAChB,cAAM,CAAC,aAAa,KAAK,IAAI,MAAMC,UAAS,YAAY;AACtD,gBAAM,OAAO,KAAK,KAAK;AACvB,gBAAM,UAAU,MAAMC,YAAW,KAAK;AACtC,gBAAM,YAAY,UAAU,oBAAoB,OAAO,IAAI,CAAC;AAC5D,qBAAW,YAAY,WAAW;AAChC,kBAAM,OAAO,KAAK,QAAQ;AAAA,UAC5B;AACA,iBAAO;AAAA,QACT,CAAC;AACD,YAAI,aAAa;AACf,UAAAJ,WAAU,EAAE,KAAK,iCAAiC,OAAO,EAAE,IAAI,EAAE,OAAO,YAAY,QAAQ,CAAC;AAC7F;AAAA,QACF;AACA,YAAI,cAAc;AAChB,UAAAA,WAAU,EAAE,MAAM,UAAU,OAAO,EAAE,uBAAuB,SAAS,CAAC,GAAG,KAAK,IAAI,KAAK,QAAQ,EAAE;AAAA,QACnG;AAAA,MACF,GAAG;AAAA,IACL;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AC7XA;AAAA,EACE,aAAAK;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAoBA,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,EAAE,mBAAAC,mBAAkB,IAAI,MAAM,OAAO,mBAAmB;AAC9D,QAAIA,mBAAkB,EAAE,UAAU,GAAG;AAGnC,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,MAAAD,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;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;;;AC/FA;AAAA,EACE;AAAA,OAIK;;;ACHP,IAAI,gBAA0B,CAAC;AAC/B,IAAI,aAA4B;AAChC,IAAI,SAAS;AAEb,IAAM,eAAe;AAEd,IAAM,kBAAkB,CAAC,SAAqC;AACnE,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,IAAI;AAAA,MACR,yDAAyD,KAAK,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,IAElF;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,SAAS,IACxC,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC,IACpE,CAAC;AAEL,MAAI,OAAsB;AAC1B,QAAM,UAAU,KAAK,CAAC;AACtB,MAAI,YAAY,QAAW;AACzB,QAAI,CAAC,aAAa,KAAK,OAAO,GAAG;AAC/B,YAAM,IAAI;AAAA,QACR,0DAA0D,OAAO;AAAA,MAEnE;AAAA,IACF;AACA,WAAO,OAAO,SAAS,SAAS,EAAE;AAAA,EACpC;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;AAEO,IAAM,kBAAkB,MAAY;AACzC,MAAI,OAAQ;AACZ,WAAS;AAET,QAAM,SAAS,gBAAgB,QAAQ,KAAK,MAAM,CAAC,CAAC;AACpD,kBAAgB,OAAO;AACvB,eAAa,OAAO;AAOpB,MAAI,eAAe,MAAM;AACvB,YAAQ,IAAI,cAAc,OAAO,UAAU;AAAA,EAC7C;AACF;AAEO,IAAM,mBAAmB,MAAgB;AACzC,IAAM,gBAAgB,MAAqB;;;ADhClD,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,gBAA4C;AAC5E,QAAM,eAAe,eAAe,OAAO,gBAAgB,WACtD,cACD,CAAC;AAEL,QAAM,eAAe,aAAa;AAClC,QAAM,gBAAgB,aAAa;AACnC,QAAM,oBAAoB,aAAa;AAEvC,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;AA0BA,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,kBAAQ;AAAA,YACN,oCAAoC,MAAM;AAAA,UAE5C;AACA;AAAA,QACF;AACA,oBAAY;AACZ,cAAM,aAAa,yBAAyB,GAAG;AAC/C,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,gBAAQ;AAAA,UACN,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;;;AtBxLO,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;AAIlB,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,kBAAkB;AAC1D,mBAAe;AACf,UAAM,iBAAiB;AACvB,UAAM,SAAU,MAAM,OAAO;AAI7B,UAAM,OAAO,cAAc;AAC3B,WAAO,cAAc;AAIrB,YAAQ,KAAK,UAAU,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC5C,YAAQ,KAAK,WAAW,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,EAC/C;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;AAED,QAAM,WAAW,WAAW,YAAY;AAAA,IACtC,mBAAmB,QAAQ;AAAA,EAC7B,CAAC;AAED,QAAM,SAAS,CAAC,aACd,IAAI,QAAoB,CAAC,YAAY;AACnC,UAAM,YAAY,OAAO,SAAS,WAAW,OAAO,SAAS,MAAM,EAAE,IAAI;AACzE,eAAW,OAAO,WAAW,IAAI,MAAM;AACrC,YAAM,SAASC,mBAAiB;AAChC,UAAI,OAAO,QAAQ,iBAAiB,OAAO,QAAQ,SAAS;AAC1D,QAAAC,WAAU,EAAE,KAAK,+BAA+B,EAAE,IAAI,OAAO,IAAI,CAAC,GAAG;AAAA,MACvE;AACA,iBAAW;AACX,cAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AAEH,SAAO,EAAE,YAAY,UAAU,OAAO;AACxC;;;AwBtIA,OAAOC,WAAU;AACjB,OAAO,QAAQ;AACf,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AAqBzB,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;AAEO,IAAM,sBAAsB,OACjC,UAAsC,CAAC,MACF;AACrC,QAAM,cAAc,QAAQ,eAAe;AAE3C,MAAI,CAAC,QAAQ,iBAAiB;AAC5B,UAAM,kBAAkB,WAAW;AAAA,EACrC;AAEA,QAAM,SAAS,MAAM,uBAAuB,OAAO;AACnD,SAAO;AACT;;;ACjGA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;","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"]}
@@ -147,7 +147,7 @@ A handler returns `true` (or simply ends `res`) to short-circuit dispatch; retur
147
147
  **Behavior:**
148
148
 
149
149
  - Matches `routePath` starting with `/auth/callback`.
150
- - Computes `baseLocation = process.env.DNS || projectConfig.app.publicUrl || '/'` (the `||` is intentional empty `DNS` falls through; `??` would shadow `app.publicUrl`).
150
+ - Computes `baseLocation = projectConfig.app.publicUrl || '/'` (the public origin where users browse; the OAuth callback is handled on the backend origin but redirects the browser back here). `||` so an empty `publicUrl` falls through to `/`.
151
151
  - Delegates to `loginCallback(routePath, req, res, { defaultRedirectUrl })` from `@luckystack/login`.
152
152
  - On failure: `401 'Login failed'`.
153
153
  - On success: delete existing session, then redirect (`302`). With `projectConfig.session.basedToken: true` the token rides as `?token=...` in the query; otherwise it's emitted as a session cookie.
@@ -296,7 +296,7 @@ Lifecycle in `handleApiRoute` / `handleSyncRoute`:
296
296
  | config | `projectConfig.http.sessionCookieName` | Cookie name written by `/auth/api` / `/auth/callback`. |
297
297
  | config | `projectConfig.session.basedToken` | Cookie vs header token transport. |
298
298
  | config | `projectConfig.rateLimiting.defaultApiLimit` / `windowMs` | Credentials login rate limit. |
299
- | env | `DNS` | Legacy public origin override for callback redirect. |
299
+ | config | `projectConfig.app.publicUrl` | Public origin the OAuth callback redirects the browser back to. |
300
300
  | env | `TEST_RESET_TOKEN` | Consumed by `/_test/reset` — see `security-defaults.md`. |
301
301
 
302
302
  ## Related
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckystack/server",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -56,19 +56,19 @@
56
56
  "test": "vitest run"
57
57
  },
58
58
  "dependencies": {
59
- "@luckystack/api": "^0.1.7",
60
- "@luckystack/core": "^0.1.7",
61
- "@luckystack/login": "^0.1.7",
62
- "@luckystack/presence": "^0.1.7",
63
- "@luckystack/sync": "^0.1.7"
59
+ "@luckystack/api": "^0.1.8",
60
+ "@luckystack/core": "^0.1.8",
61
+ "@luckystack/login": "^0.1.8",
62
+ "@luckystack/presence": "^0.1.8",
63
+ "@luckystack/sync": "^0.1.8"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@prisma/client": "^6.19.0",
67
67
  "socket.io": "^4.8.0",
68
- "@luckystack/devkit": "^0.1.7",
69
- "@luckystack/docs-ui": "^0.1.7",
70
- "@luckystack/email": "^0.1.7",
71
- "@luckystack/error-tracking": "^0.1.7"
68
+ "@luckystack/devkit": "^0.1.8",
69
+ "@luckystack/docs-ui": "^0.1.8",
70
+ "@luckystack/email": "^0.1.8",
71
+ "@luckystack/error-tracking": "^0.1.8"
72
72
  },
73
73
  "peerDependenciesMeta": {
74
74
  "@luckystack/devkit": {