@luckystack/core 0.8.4 → 0.8.6
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 +6 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/escapeHtml.ts","../src/peerDeps.ts","../src/roomNameFormatterRegistry.ts","../src/hooks/registry.ts","../src/localizedNormalizer.ts","../src/errorFormatterRegistry.ts","../src/socketTypes.ts","../src/runtimeMapsRegistry.ts","../src/emailRegistry.ts","../src/localeReloader.ts","../src/errorTrackerIdentity.ts","../src/env.ts","../src/db.ts","../src/clients.ts","../src/dbHealthCheck.ts","../src/redis.ts","../src/redisKeyFormatter.ts","../src/lease.ts","../src/socketRedisAdapter.ts","../src/bootUuid.ts","../src/synchronizedEnvHashes.ts","../src/consoleLog.ts","../src/cookies.ts","../src/paths.ts","../src/socketMiddlewareRegistry.ts","../src/serveAvatars.ts","../src/avatarConfig.ts","../src/processUpload.ts","../src/bindAddress.ts","../src/getParams.ts","../src/extractToken.ts","../src/extractTokenFromRequest.ts","../src/resolveClientIp.ts","../src/sessionProviderRegistry.ts","../src/validateRequest.ts","../src/rateLimiter.ts","../src/runtimeTypeValidation.ts","../src/checkOrigin.ts","../src/cancelRegistry.ts","../src/oneTimeToken.ts"],"sourcesContent":["//? Canonical HTML-escaping helper. Several packages (docs-ui's rendered HTML,\n//? email's layout renderer) each carried their own copy with the same five\n//? entity mappings — this is the single source of truth they migrate to so\n//? the escaping convention can't drift between surfaces.\n//?\n//? Escapes the five characters that are unsafe in HTML text/attribute\n//? contexts: `&`, `<`, `>`, `\"`, `'`. `&` is replaced first so the ampersands\n//? introduced by the other replacements are not double-escaped.\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/**\n * Escape `&`, `<`, `>`, `\"`, `'` for safe interpolation into HTML text nodes\n * and quoted attribute values. Does NOT protect against CSS/JS/URL-context\n * injection — use a dedicated sanitizer for those contexts.\n */\nexport const escapeHtml = (str: string): string =>\n str.replaceAll(/[&<>\"']/g, (c) => HTML_ESCAPE_MAP[c] ?? c);\n","//? Shared optional-peer-dependency guards. The framework's optional adapters\n//? (`@luckystack/email`'s Resend/SMTP senders, `@luckystack/error-tracking`'s\n//? Sentry/PostHog/Datadog adapters) each hand-rolled the same boot-time guard:\n//? `try { localRequire.resolve(pkg) } catch { throw new Error(hint) }`, then a\n//? synchronous `require(pkg)` once present. This centralises both halves so\n//? the message convention and resolution semantics stay identical everywhere.\n//?\n//? CommonJS `require` resolution is relative to the MODULE that owns the\n//? `require` function. A guard called from `@luckystack/email`'s adapter must\n//? resolve `resend` from the consumer's `node_modules`, not core's — so both\n//? helpers accept an optional `requireFn` that callers build with\n//? `createRequire(import.meta.url)` at their own location. When omitted, the\n//? helpers fall back to a `require` based on this module (sufficient inside a\n//? flat monorepo / hoisted install).\n\nimport { createRequire } from 'node:module';\n\n/** Minimal shape of a Node `require` with the `.resolve` member these guards use. */\nexport interface PeerRequire {\n (id: string): unknown;\n resolve: (id: string) => string;\n}\n\n//? `NodeRequire` (the return of `createRequire`) is structurally a superset of\n//? `PeerRequire` — its call signature + `.resolve` satisfy our minimal shape —\n//? so it assigns directly with no cast.\nconst defaultRequire: PeerRequire = createRequire(import.meta.url);\n\n/**\n * Assert that an optional peer package is installed, throwing a consistent,\n * actionable error when it is not. Does NOT load the module — use `loadPeer`\n * for that. Pass `requireFn` (a `createRequire(import.meta.url)` from the\n * calling adapter) so resolution happens from the consumer's perspective.\n *\n * `hint` is appended to the standard \"package X is not installed\" sentence —\n * e.g. `'Run `npm install resend`, or pick a different EmailSender adapter.'`.\n */\nexport const ensurePeerDepInstalled = (\n packageName: string,\n hint: string,\n requireFn: PeerRequire = defaultRequire,\n): void => {\n try {\n requireFn.resolve(packageName);\n } catch {\n throw new Error(\n `The \\`${packageName}\\` package is not installed but a feature that needs it was used. ${hint}`,\n );\n }\n};\n\n/**\n * Synchronously load an optional peer package, asserting it is installed\n * first (so a missing dependency fails with the actionable `hint` rather than\n * a raw `MODULE_NOT_FOUND`). Returns the module typed as `T`. Pass `requireFn`\n * (built with `createRequire(import.meta.url)` at the call site) so resolution\n * + load happen relative to the calling module.\n */\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is a caller-supplied return-shape annotation (typed boundary helper, like a checked cast); single use is intentional\nexport const loadPeer = <T>(\n packageName: string,\n hint: string,\n requireFn: PeerRequire = defaultRequire,\n): T => {\n ensurePeerDepInstalled(packageName, hint, requireFn);\n return requireFn(packageName) as T;\n};\n","//? Optional room-name formatter seam (presence socket-room-formatter handoff).\n//? Socket.io room names are global within an io instance; in a multi-tenant or\n//? multi-feature deployment two features can collide on the same raw room code.\n//? A consumer registers a formatter to namespace room names (e.g. prefix with a\n//? workspace id) at the single point every framework join/leave/broadcast site\n//? routes through — mirroring `registerRedisKeyFormatter` for Redis keys.\n//?\n//? The DEFAULT is identity (return the raw room name unchanged), so a missing\n//? registration keeps today's behavior byte-for-byte. The formatter MUST be a\n//? pure, synchronous function (it runs on the socket hot path) and MUST be\n//? deterministic so join and leave produce the same physical room.\n\nimport { createRegistry } from './createRegistry';\n\nexport interface RoomNameFormatterContext {\n /**\n * Which room FAMILY the name is being formatted for — a formatter may branch on\n * this to isolate genuinely-separate families, but MUST NOT use it to give the\n * same raw room code different physical names across a single family's\n * operations. A content room is joined, left, membership-checked, broadcast to,\n * and streamed to under the SINGLE canonical `'broadcast'` purpose (M5); the\n * framework never varies a content room's physical name by operation, because a\n * socket physically IN `f(R,'join')` could never receive a fanout targeting\n * `f(R,'broadcast')`. `'presence'` is the one genuinely-separate family (its own\n * rooms). `'join'`/`'leave'` remain in the union for backward-compat but the\n * framework no longer emits them for room-NAME formatting — treat them, if ever\n * seen, identically to `'broadcast'`.\n */\n purpose: 'join' | 'leave' | 'broadcast' | 'presence';\n /** Session user id when known (join/leave by an authenticated socket), else null. */\n userId?: string | null;\n}\n\nexport type RoomNameFormatter = (rawRoomName: string, ctx: RoomNameFormatterContext) => string;\n\nconst identityFormatter: RoomNameFormatter = (rawRoomName) => rawRoomName;\n\nconst registry = createRegistry<RoomNameFormatter>(identityFormatter);\n\n/** Override how every framework room name is namespaced (multi-tenant prefixing). */\nexport const registerRoomNameFormatter = (formatter: RoomNameFormatter): void => {\n registry.register(formatter);\n};\n\n/** Read the active formatter (defaults to identity). */\nexport const getRoomNameFormatter = (): RoomNameFormatter => registry.get();\n\n/** Apply the active formatter — the single helper framework room-sites call. */\nexport const formatRoomName = (rawRoomName: string, ctx: RoomNameFormatterContext): string =>\n registry.get()(rawRoomName, ctx);\n\n/** The built-in identity formatter (exported for tests / explicit reset). */\nexport const defaultRoomNameFormatter = identityFormatter;\n","import type {\n HookName,\n HookPayloads,\n HookHandler,\n HookResult,\n HookStopSignal,\n SyncHookHandler,\n SyncHookName,\n SyncHookPayloads,\n} from './types';\nimport { captureException } from '../sentrySetup';\nimport { getLogger } from '../loggerRegistry';\n\nexport type DispatchResult =\n | { stopped: false }\n | { stopped: true; signal: HookStopSignal };\n\n// Internal type — wider than the typed handlers but safe because register/dispatch\n// use the same TName key to guarantee payload shape consistency.\ntype AnyHandler = (payload: unknown) => Promise<HookResult> | HookResult;\n\nconst hookHandlers = new Map<string, AnyHandler[]>();\n\nexport const registerHook = <TName extends HookName>(\n name: TName,\n handler: HookHandler<HookPayloads[TName]>,\n): (() => void) => {\n const existing = hookHandlers.get(name) ?? [];\n const wrapped = handler as AnyHandler;\n existing.push(wrapped);\n hookHandlers.set(name, existing);\n //? Return an unsubscribe that splices THIS exact handler (matching\n //? `onClientHook`'s symmetry). Lets conditional plugins / per-tenant toggles /\n //? hot-reload detach a single handler without `clearAllHooks` nuking the\n //? framework-internal handlers too.\n return () => {\n const handlers = hookHandlers.get(name);\n if (!handlers) return;\n const index = handlers.indexOf(wrapped);\n if (index !== -1) handlers.splice(index, 1);\n };\n};\n\nexport const dispatchHook = async <TName extends HookName>(\n name: TName,\n payload: HookPayloads[TName],\n): Promise<DispatchResult> => {\n const handlers = hookHandlers.get(name) ?? [];\n\n for (const handler of handlers) {\n let result: HookResult;\n try {\n result = await handler(payload);\n } catch (error) {\n // Isolated per hook — one failing handler never interrupts the main flow,\n // but plugin failures must still be visible. Surface to logger + Sentry\n // so installers can spot bugs in their registered handlers.\n //? Wrap the reporter calls themselves (CORE-N10): if the logger or error\n //? tracker throws (misconfigured adapter, serialisation error), we must\n //? not propagate out of `dispatchHook` — that would surface as an\n //? unhandled rejection on the caller and undo the isolation guarantee.\n try { getLogger().error(`hook: handler for \"${name}\" threw`, error, { hook: name }); } catch { /* luckystack-allow: reporter must not break hook isolation */ }\n try { captureException(error, { hook: name }); } catch { /* luckystack-allow */ }\n continue;\n }\n\n if (result !== undefined) {\n return { stopped: true, signal: result };\n }\n }\n\n return { stopped: false };\n};\n\n//? Test-only: drop every registered hook handler. Used by the dev `/_test/reset`\n//? endpoint when integration tests need a clean slate. Never call this from\n//? production code paths — it would silently break framework-internal hooks\n//? (presence post-logout cleanup, etc.).\nexport const clearAllHooks = (): void => {\n hookHandlers.clear();\n syncHookHandlers.clear();\n};\n\n// --- Synchronous hooks ---\n\ntype AnySyncHandler = (payload: unknown) => void;\n\nconst syncHookHandlers = new Map<string, AnySyncHandler[]>();\n\nexport const registerSyncHook = <TName extends SyncHookName>(\n name: TName,\n handler: SyncHookHandler<SyncHookPayloads[TName]>,\n): (() => void) => {\n const existing = syncHookHandlers.get(name) ?? [];\n const wrapped = handler as AnySyncHandler;\n existing.push(wrapped);\n syncHookHandlers.set(name, existing);\n //? Returns an unsubscribe that splices THIS exact handler. See `registerHook`.\n return () => {\n const handlers = syncHookHandlers.get(name);\n if (!handlers) return;\n const index = handlers.indexOf(wrapped);\n if (index !== -1) handlers.splice(index, 1);\n };\n};\n\nexport const dispatchSyncHook = <TName extends SyncHookName>(\n name: TName,\n payload: SyncHookPayloads[TName],\n): void => {\n const handlers = syncHookHandlers.get(name) ?? [];\n for (const handler of handlers) {\n try {\n handler(payload);\n } catch (error) {\n // Isolated per hook — one failing handler never interrupts the main flow.\n //? Same guard as the async hook path (CORE-N10).\n try { getLogger().error(`hook: sync handler for \"${name}\" threw`, error, { hook: name }); } catch { /* luckystack-allow */ }\n try { captureException(error, { hook: name }); } catch { /* luckystack-allow */ }\n }\n }\n};\n","import type {\n ErrorParam,\n ErrorResponseInput,\n NormalizedErrorResponse,\n} from './responseNormalizer';\nimport {\n defaultHttpStatusForResponse,\n normalizeErrorResponseCore,\n} from './responseNormalizer';\nimport { dispatchSyncHook } from './hooks/registry';\n\n//? Factory that turns a project-provided translate function into the full\n//? normalizer surface the framework packages need.\n//?\n//? Before this split, `@luckystack/api` and `@luckystack/sync` imported\n//? `normalizeErrorResponse` from `server/utils/responseNormalizer` via deep\n//? relative paths — a framework-into-project reach-in. Now the project\n//? registers a normalizer on boot and framework code consumes it through\n//? the singleton below.\n\nexport type LanguageCode = string;\n\nexport interface TranslateInput {\n language: LanguageCode;\n key: string;\n params?: ErrorParam[];\n}\n\nexport type TranslateFunction = (input: TranslateInput) => string;\n\nexport interface NormalizeErrorResponseInput {\n response: ErrorResponseInput;\n preferredLocale?: string | null;\n userLanguage?: string | null;\n fallbackHttpStatus?: number;\n}\n\nexport interface LocalizedNormalizer {\n normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;\n resolveErrorMessage: (input: {\n errorCode: string;\n errorParams?: ErrorParam[];\n preferredLocale?: string | null;\n userLanguage?: string | null;\n }) => string;\n extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;\n}\n\nexport interface CreateLocalizedNormalizerInput {\n translate: TranslateFunction;\n /**\n * Language the framework falls back to when the caller provides no\n * language hints. Defaults to 'en'.\n */\n defaultLanguage?: LanguageCode;\n /**\n * Decide whether a given string is an acceptable language code. Defaults\n * to accepting any non-empty short-form code (ISO 639-1 style). Projects\n * that only support a closed set should pass a narrower predicate.\n */\n isSupportedLanguage?: (code: string) => boolean;\n}\n\nconst normalizeLanguageCode = (\n language: string | null | undefined,\n isSupported: (code: string) => boolean,\n): LanguageCode | null => {\n if (!language) return null;\n const short = language.toLowerCase().split('-')[0];\n if (!short) return null;\n return isSupported(short) ? short : null;\n};\n\nexport const createLocalizedNormalizer = (\n input: CreateLocalizedNormalizerInput,\n): LocalizedNormalizer => {\n const defaultLanguage = input.defaultLanguage ?? 'en';\n const isSupported = input.isSupportedLanguage ?? (() => true);\n\n const extractLanguageFromHeader = (header?: string | string[]): LanguageCode | null => {\n if (!header) return null;\n const normalized = Array.isArray(header) ? header.join(',') : header;\n\n const candidates = normalized\n .split(',')\n .map(part => part.trim().split(';')[0])\n .filter(Boolean);\n\n for (const candidate of candidates) {\n const language = normalizeLanguageCode(candidate, isSupported);\n if (language) return language;\n }\n return null;\n };\n\n const resolveLanguage = ({\n preferredLocale,\n userLanguage,\n }: {\n preferredLocale?: string | null;\n userLanguage?: string | null;\n }): LanguageCode => {\n return (\n normalizeLanguageCode(userLanguage, isSupported)\n ?? normalizeLanguageCode(preferredLocale, isSupported)\n ?? defaultLanguage\n );\n };\n\n const resolveErrorMessage = ({\n errorCode,\n errorParams,\n preferredLocale,\n userLanguage,\n }: {\n errorCode: string;\n errorParams?: ErrorParam[];\n preferredLocale?: string | null;\n userLanguage?: string | null;\n }): string => {\n const language = resolveLanguage({ preferredLocale, userLanguage });\n return input.translate({ language, key: errorCode, params: errorParams });\n };\n\n const normalizeErrorResponse = ({\n response,\n preferredLocale,\n userLanguage,\n fallbackHttpStatus,\n }: NormalizeErrorResponseInput): NormalizedErrorResponse => {\n //? `preErrorNormalize` handlers may mutate `payload.response` to remap\n //? error codes (e.g. translate `auth.required` → custom domain code, redact\n //? internal codes for unauth users) before normalization.\n const preNormalize = { response: { ...response }, preferredLocale, userLanguage, fallbackHttpStatus };\n dispatchSyncHook('preErrorNormalize', preNormalize);\n\n const normalized = normalizeErrorResponseCore({\n response: preNormalize.response,\n fallbackHttpStatus: preNormalize.fallbackHttpStatus,\n resolveMessage: ({ errorCode, errorParams }) => resolveErrorMessage({\n errorCode,\n errorParams,\n preferredLocale: preNormalize.preferredLocale,\n userLanguage: preNormalize.userLanguage,\n }),\n });\n\n const finalized: NormalizedErrorResponse = {\n ...normalized,\n httpStatus: defaultHttpStatusForResponse({\n status: 'error',\n explicitHttpStatus: normalized.httpStatus,\n }),\n };\n\n //? `postErrorNormalize` handlers may mutate `payload.normalized` to\n //? post-process the localized envelope (replace messages, scrub fields).\n const postNormalize = { normalized: finalized, preferredLocale: preNormalize.preferredLocale, userLanguage: preNormalize.userLanguage };\n dispatchSyncHook('postErrorNormalize', postNormalize);\n\n return postNormalize.normalized;\n };\n\n return { normalizeErrorResponse, resolveErrorMessage, extractLanguageFromHeader };\n};\n\n//? Module-level singleton so framework packages can resolve the project's\n//? translate wiring without deep-relative imports. The server registers its\n//? normalizer on boot; tests and tooling fall back to the identity default\n//? (returns the errorCode key unchanged as the message).\nconst identityNormalizer: LocalizedNormalizer = createLocalizedNormalizer({\n translate: ({ key }) => key,\n});\n\nlet activeNormalizer: LocalizedNormalizer = identityNormalizer;\nlet normalizerRegistered = false;\n\nexport const registerLocalizedNormalizer = (normalizer: LocalizedNormalizer): void => {\n activeNormalizer = normalizer;\n normalizerRegistered = true;\n};\n\nexport const getLocalizedNormalizer = (): LocalizedNormalizer => activeNormalizer;\n\n//? Used by `verifyBootstrap` to detect the silent-degradation case where the\n//? identity normalizer is in use (passes errorCode through as the message,\n//? no i18n). Production projects should always register a real normalizer.\nexport const isLocalizedNormalizerRegistered = (): boolean => normalizerRegistered;\n\n//? Delegating wrappers for consumers (@luckystack/api, @luckystack/sync, etc.)\n//? that want import-time-safe references. The project registers its\n//? normalizer on boot; these functions always resolve through the singleton\n//? at call time, so there's no import-order fragility.\nexport const normalizeErrorResponse = (\n input: NormalizeErrorResponseInput,\n): NormalizedErrorResponse => activeNormalizer.normalizeErrorResponse(input);\n\nexport const resolveErrorMessage = (input: {\n errorCode: string;\n errorParams?: ErrorParam[];\n preferredLocale?: string | null;\n userLanguage?: string | null;\n}): string => activeNormalizer.resolveErrorMessage(input);\n\nexport const extractLanguageFromHeader = (header?: string | string[]): LanguageCode | null =>\n activeNormalizer.extractLanguageFromHeader(header);\n","//? Error-response formatter registry — lives in core so transport handlers\n//? in @luckystack/api and @luckystack/sync can dispatch the per-route /\n//? global formatter without depending back up on @luckystack/server (which\n//? would create a dependency cycle).\n//?\n//? The framework normalizes errors via `normalizeErrorResponse` (i18n +\n//? envelope shape). Consumers can register a global formatter to add\n//? fields (e.g. supportTicketId, correlationId, deprecated alias keys for\n//? legacy clients), or override per-endpoint by exporting `errorFormatter`\n//? from an `_api/*.ts` or `_sync/*.ts` file — the api/sync handlers read\n//? that export off the runtime apiEntry/syncEntry and apply it through\n//? `applyErrorFormatter` below.\n//?\n//? Resolution order at error time:\n//? 1. Per-endpoint `errorFormatter` export (when the route file declares one).\n//? 2. Global formatter from `registerErrorFormatter(...)`.\n//? 3. Framework default `normalizeErrorResponse`.\n//?\n//? @luckystack/server re-exports `registerErrorFormatter` + types from\n//? here for backward compatibility — `import { registerErrorFormatter }\n//? from '@luckystack/server'` continues to work.\n\nimport { createRegistry } from './createRegistry';\nimport tryCatchSync from './tryCatchSync';\n\nexport interface ErrorFormatterContext {\n /** Resolved route name (`api/billing/getInvoice/v1`, `sync/...`). */\n routeName: string;\n /** Transport that triggered the error. */\n transport: 'socket' | 'http';\n /** Session id when available. */\n userId?: string | null;\n}\n\n/** The normalized error envelope handed to every formatter. */\nexport interface FormatterErrorEnvelope {\n status: 'error';\n errorCode?: string;\n message?: string;\n httpStatus?: number;\n [key: string]: unknown;\n}\n\nexport type ErrorFormatter = (\n error: FormatterErrorEnvelope,\n ctx: ErrorFormatterContext,\n) => Record<string, unknown>;\n\n/**\n * Minimal structural contract every formatter-eligible envelope satisfies:\n * a discriminant `status` plus an open string-index so formatters can read /\n * add arbitrary fields. Generic helpers below constrain to this so transport\n * handlers no longer need `as unknown as` double-casts at the formatter\n * boundary — see `applyErrorFormatter` / `buildFormattedError`.\n */\nexport interface FormatterEnvelope { status?: string;[key: string]: unknown }\n\nconst registry = createRegistry<ErrorFormatter | null>(null);\n\n/**\n * Register a global error-response formatter. Receives the normalized\n * error envelope + context; return a (possibly extended) object that the\n * framework will emit. Pass `null` to unregister.\n *\n * For per-endpoint overrides, export `errorFormatter` from your\n * `_api/foo_v1.ts` file directly — the api/sync handler imports + invokes\n * it through `applyErrorFormatter`.\n */\nexport const registerErrorFormatter = (formatter: ErrorFormatter | null): void => {\n registry.register(formatter);\n};\n\n/** Read the active global formatter (or null). */\nexport const getErrorFormatter = (): ErrorFormatter | null => registry.get();\n\n/**\n * Apply per-route → global → identity formatter chain to a normalized\n * error envelope. Returns the envelope unchanged when `status !== 'error'`\n * so it's safe to wrap every emit call. Errors thrown inside a formatter\n * are caught and logged; the un-formatted envelope is returned in that\n * case to keep the error path crash-resistant.\n */\nexport const applyErrorFormatter = <T extends FormatterEnvelope>(input: {\n response: T;\n routeName: string;\n transport: 'socket' | 'http';\n userId?: string | null;\n perRouteFormatter?: ErrorFormatter | undefined | null;\n}): T => {\n const { response, routeName, transport, userId, perRouteFormatter } = input;\n if (response.status !== 'error') return response;\n\n const ctx: ErrorFormatterContext = { routeName, transport, userId };\n //? After the `status !== 'error'` guard, `response` is an error envelope.\n //? `T` and `FormatterErrorEnvelope` share the open `[key: string]: unknown`\n //? index + `status` discriminant, so this is a single, direct narrowing\n //? assertion (plain `as`, NOT a `as unknown as` double-cast). This is the one\n //? documented envelope→formatter boundary.\n const errorEnvelope = response as FormatterErrorEnvelope;\n\n if (perRouteFormatter) {\n const [error, formatted] = tryCatchSync(() => perRouteFormatter(errorEnvelope, ctx));\n //? A formatter may add/rename fields, so its output is the wider\n //? `Record<string, unknown>`. We return it as `T` because the framework\n //? contract guarantees a formatter receives and returns the SAME logical\n //? error envelope for a given route — the discriminant + required keys are\n //? preserved. This is the single, documented formatter→envelope boundary.\n if (!error && formatted) return formatted as T;\n if (error) {\n console.error(`[errorFormatter] per-route formatter for ${routeName} threw:`, error);\n //? Fall through to global / identity so the error path stays resilient.\n }\n }\n\n const globalFormatter = registry.get();\n if (globalFormatter) {\n const [error, formatted] = tryCatchSync(() => globalFormatter(errorEnvelope, ctx));\n if (!error && formatted) return formatted as T;\n console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);\n return response;\n }\n\n return response;\n};\n","import type { Server as SocketIOServer } from 'socket.io';\n\n//? Shared wire-protocol types for the socket-based API and sync transports.\n//? These used to live in `server/sockets/socket.ts` — moved to core so\n//? framework packages (@luckystack/api, @luckystack/sync) can stop\n//? deep-relative-importing into the project's server directory.\n\nexport interface apiMessage {\n name: string;\n data: object;\n responseIndex: number;\n}\n\nexport interface syncMessage {\n name: string;\n data: object;\n cb: string;\n receiver: string;\n responseIndex?: number;\n ignoreSelf?: boolean;\n}\n\n//? Module-level slot for the running Socket.io server instance. The project\n//? calls `setIoInstance(io)` right after constructing the server; framework\n//? packages call `getIoInstance()` when they need to broadcast.\nlet _ioInstance: SocketIOServer | null = null;\n\nexport const setIoInstance = (io: SocketIOServer | null): void => {\n _ioInstance = io;\n};\n\nexport const getIoInstance = (): SocketIOServer | null => _ioInstance;\n","//? DI surface for generated runtime route maps. The project's\n//? `server/prod/runtimeMaps.ts` knows how to dynamically import per-preset\n//? `generatedApis.<preset>.ts` files and merge devkit overrides — framework\n//? packages don't. They ask core for the current maps at request time, and\n//? core delegates to whichever provider was registered on boot.\n//?\n//? Registration is a side effect of the project importing its own runtimeMaps\n//? module. Default (unregistered) returns empty maps so framework code can\n//? boot in tests without crashing.\n\nimport { createRegistry } from './createRegistry';\n\ntype RuntimeMapRecord = Record<string, unknown>;\n\nexport interface RuntimeApiMapsResult {\n apisObject: RuntimeMapRecord;\n functionsObject: RuntimeMapRecord;\n}\n\nexport interface RuntimeSyncMapsResult {\n syncObject: RuntimeMapRecord;\n functionsObject: RuntimeMapRecord;\n}\n\nexport interface RuntimeMapsProvider {\n getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;\n getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;\n}\n\nconst emptyApi: RuntimeApiMapsResult = { apisObject: {}, functionsObject: {} };\nconst emptySync: RuntimeSyncMapsResult = { syncObject: {}, functionsObject: {} };\n\nconst registry = createRegistry<RuntimeMapsProvider>({\n getRuntimeApiMaps: () => Promise.resolve(emptyApi),\n getRuntimeSyncMaps: () => Promise.resolve(emptySync),\n});\n\nexport const registerRuntimeMapsProvider = (provider: RuntimeMapsProvider): void => {\n registry.register(provider);\n};\n\n//? Used by `verifyBootstrap` to detect the production fail-mode where no\n//? provider got registered (typically because the project forgot to import\n//? its `server/prod/runtimeMaps.ts` side-effect). Without this check, every\n//? api/sync request silently returns `api.notFound` / `sync.notFound`.\nexport const isRuntimeMapsProviderRegistered = (): boolean => registry.isRegistered();\n\nexport const getRuntimeApiMaps = async (): Promise<RuntimeApiMapsResult> =>\n registry.get().getRuntimeApiMaps();\n\nexport const getRuntimeSyncMaps = async (): Promise<RuntimeSyncMapsResult> =>\n registry.get().getRuntimeSyncMaps();\n","//? Email-sender registry. Two registration modes:\n//?\n//? 1. `registerEmailSender(sender)` — single global sender (legacy, kept\n//? for backwards compatibility with consumers who haven't migrated to\n//? the multi-adapter shape).\n//? 2. `registerEmailSenders({ default, transactional, marketing, ... })` —\n//? multiple named senders. Convention: framework code (login's\n//? password-reset, account flows) routes to the `'transactional'` slot\n//? when registered, otherwise falls back to `'default'` or the legacy\n//? single sender.\n//?\n//? Types live here (not in `@luckystack/email`) so framework packages can\n//? type-check against them without depending on the email package — keeping\n//? the email package optional.\n\n/**\n * A single email attachment (email F2). Adapters (Resend, SMTP/Nodemailer, SES)\n * thread these into their provider payload. `content` is the raw bytes\n * (`Buffer`/`Uint8Array`) or a base64 string; provide a `path`/`href` instead\n * for adapters that fetch externally. Shapes are intentionally permissive so a\n * single attachment object works across providers — each adapter maps the\n * fields it supports.\n */\nexport interface EmailAttachment {\n /** File name shown to the recipient (e.g. `invoice.pdf`). */\n filename: string;\n /** Inline bytes or base64 string. Mutually exclusive with `path`/`href`. */\n content?: Buffer | Uint8Array | string;\n /**\n * Local file path the adapter reads (when it supports path-based sends).\n *\n * @security Trusted-input field — do NOT derive from client-supplied data\n * without validation. Framework-level path-traversal or safe-directory checks\n * are NOT applied automatically; callers must reject paths that escape the\n * intended directory (e.g. containing `..` or absolute paths outside a known\n * root) before passing them here.\n */\n path?: string;\n /**\n * Remote URL the adapter fetches (when it supports href-based sends).\n *\n * @security Trusted-input field — do NOT derive from client-supplied data\n * without validation. Only `https:` and `http:` URLs are safe for\n * framework-internal use; arbitrary schemes (e.g. `file:`, `data:`) may be\n * forwarded to nodemailer/Resend without restriction. Validate against an\n * allowlist before passing here.\n */\n href?: string;\n /** MIME type (e.g. `application/pdf`). Adapter may infer from `filename` if omitted. */\n contentType?: string;\n /** `Content-ID` for inline/embedded images referenced by `cid:` in the HTML. */\n cid?: string;\n}\n\nexport interface EmailMessage {\n to: string | string[];\n subject: string;\n html: string;\n text?: string;\n from?: string;\n replyTo?: string;\n cc?: string | string[];\n bcc?: string | string[];\n /**\n * File attachments (email F2). Owned by core (not `@luckystack/email`) so the\n * type is the single source of truth across the framework and every adapter;\n * adapters thread these into their provider's attachment payload. Optional —\n * a message without attachments behaves exactly as before.\n */\n attachments?: EmailAttachment[];\n /**\n * Custom message headers (email F2) — e.g. `X-Entity-Ref-ID`,\n * `List-Unsubscribe`, idempotency keys. Adapters merge these over the headers\n * they set themselves (adapter-reserved headers win to avoid breaking\n * delivery). Optional.\n *\n * @security CR and LF characters in header keys or values enable SMTP header\n * injection. `sendEmail` strips `\\r` and `\\n` from every key and value before\n * passing them to the adapter (EMAIL-O7). Do NOT derive keys or values from\n * unsanitized client input.\n */\n headers?: Record<string, string>;\n}\n\nexport type EmailDeliveryOutcome = 'not-sent' | 'unknown';\n\nexport type EmailResult =\n | { ok: true; id: string }\n | {\n ok: false;\n reason: string;\n cause?: unknown;\n /**\n * `unknown` means the caller stopped waiting after dispatch began; retry\n * only with the same idempotency key. Omitted on legacy/provider failures\n * whose delivery semantics are adapter-specific.\n */\n deliveryOutcome?: EmailDeliveryOutcome;\n };\n\nexport interface EmailSendContext {\n /** Cooperative cancellation. Custom adapters should abort provider I/O when supported. */\n signal: AbortSignal;\n /** Stable caller-provided key reused across retries of the same logical email. */\n idempotencyKey?: string;\n}\n\nexport interface EmailSender {\n /** Adapter identifier for logs + diagnostics (\"console\", \"resend\", \"smtp\", etc.). */\n name: string;\n /**\n * The optional context preserves source compatibility with pre-0.7 adapters.\n * New adapters should honor `signal` and provider-native idempotency where available.\n */\n send: (message: EmailMessage, context?: EmailSendContext) => Promise<EmailResult>;\n}\n\n/**\n * Registry keyed by adapter-slot name. Reserved slots used by the\n * framework:\n *\n * - `'default'` — fallback when no specific slot matches.\n * - `'transactional'` — login password-reset, account-confirmation,\n * invoice-receipt, security-critical emails.\n * - `'marketing'` — newsletters, announcements; consumers MAY route\n * bulk sends here.\n * - `'diagnostics'` — diagnostic-test endpoints (e.g. playground).\n *\n * Custom slot names ('billing', 'support', etc.) are allowed; consumers\n * resolve them explicitly via `sendEmail({ adapter: 'billing', ... })`.\n */\nexport type EmailSenderRegistry = Partial<Record<string, EmailSender>>;\n\nlet activeSender: EmailSender | null = null;\nlet registry: EmailSenderRegistry = {};\n\n/**\n * Register a single global email sender. Kept for backwards compatibility.\n * For new code, prefer `registerEmailSenders({ ... })` which lets the\n * framework route transactional vs. marketing through different adapters.\n */\nexport const registerEmailSender = (sender: EmailSender): void => {\n activeSender = sender;\n //? Mirror into the registry so `getEmailSenderByName('default')` finds\n //? the legacy registration too. Consumers calling both APIs in the same\n //? boot get last-write-wins (multi-API call beats single, or vice versa,\n //? depending on order).\n registry = { ...registry, default: sender };\n};\n\n/**\n * Register multiple named email senders. Replaces the entire registry\n * (last-write-wins). Use this when your app routes different message\n * types through different adapters — e.g. Resend for marketing and SMTP\n * for transactional. The framework picks adapter slots by convention\n * (see `EmailSenderRegistry`); consumer code can pick explicitly via\n * `sendEmail({ adapter: 'foo', ... })`.\n */\nexport const registerEmailSenders = (senders: EmailSenderRegistry): void => {\n registry = { ...senders };\n if (senders.default) {\n activeSender = senders.default;\n }\n};\n\n/** Read the legacy single sender (or the `default` slot of the registry). */\nexport const getEmailSender = (): EmailSender | null => activeSender ?? registry.default ?? null;\n\n/**\n * Read a specific named sender. Falls back to the legacy single sender\n * when the requested slot is absent. Returns null when neither exists.\n * Pass the convention-based slots (`'transactional'`, `'marketing'`,\n * `'diagnostics'`, `'default'`) or any custom slot you registered.\n */\nexport const getEmailSenderByName = (name: string): EmailSender | null => {\n if (registry[name]) return registry[name];\n if (name === 'default' && activeSender) return activeSender;\n return null;\n};\n\n/** Read every registered slot name. */\nexport const listEmailSenderNames = (): string[] => Object.keys(registry);\n\nexport const isEmailSenderRegistered = (): boolean =>\n activeSender !== null || Object.keys(registry).length > 0;\n","//? Locale reloader registry. Lets `@luckystack/devkit`'s hot-reload watcher\n//? trigger a translations reload without taking a relative-path dependency\n//? on the consumer's project tree (the previous design did\n//? `import { reloadLocaleTranslations } from \"../../../server/utils/responseNormalizer\"`,\n//? which only resolved while devkit lived inside this monorepo).\n//?\n//? The consumer's `responseNormalizer.ts` (or whatever owns its translation\n//? loading) calls `registerLocaleReloader(fn)` at boot. The watcher calls\n//? `getLocaleReloader()?.()` whenever a `_locales/*.json` file changes. If\n//? no reloader is registered, the watcher is a no-op for that event.\n\nimport { createRegistry } from './createRegistry';\n\nexport type LocaleReloader = () => void | Promise<void>;\n\nconst registry = createRegistry<LocaleReloader | null>(null);\n\nexport const registerLocaleReloader = (reloader: LocaleReloader): LocaleReloader => {\n registry.register(reloader);\n return reloader;\n};\n\nexport const getLocaleReloader = (): LocaleReloader | null => registry.get();\n","//? SERVER-ONLY error-tracker identity scope. Keep `node:async_hooks` isolated in\n//? this module: the browser-safe `sentrySetup`/`tryCatchClient` path needs the\n//? tracker fan-out registry, but it never needs per-request server identity.\n//? Mixing both concerns in errorTrackerRegistry.ts let tsup coalesce this import\n//? into a shared chunk reached by @luckystack/core/client (VA-02).\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport interface ErrorTrackerUser {\n id?: string;\n email?: string;\n username?: string;\n [key: string]: unknown;\n}\n\n//? ET-02: each request receives a mutable box so identity can be filled after\n//? session lookup while remaining isolated from concurrent request scopes.\ninterface IdentityBox {\n user: ErrorTrackerUser | null;\n}\n\nconst identityStore = new AsyncLocalStorage<IdentityBox>();\n\n/** Open an isolated request identity scope before the first async boundary. */\nexport const runWithErrorTrackerIdentityScope = <T>(fn: () => T): T =>\n identityStore.run({ user: null }, fn);\n\n/** Open an isolated scope with a user that is already known. */\nexport const runWithErrorTrackerIdentity = <T>(\n user: ErrorTrackerUser | null,\n fn: () => T,\n): T => identityStore.run({ user }, fn);\n\n/** Set the user inside the active request scope; no-op outside a scope. */\nexport const setCurrentErrorTrackerIdentity = (user: ErrorTrackerUser | null): void => {\n const box = identityStore.getStore();\n if (box) box.user = user;\n};\n\n/** Read the current request identity, or null outside a scope. */\nexport const getCurrentErrorTrackerIdentity = (): ErrorTrackerUser | null =>\n identityStore.getStore()?.user ?? null;\n","//? @adr 0045\nimport { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { config as loadDotenv, parse as parseDotenv } from 'dotenv';\nimport { z } from 'zod';\n\nimport tryCatchSync from './tryCatchSync';\n\nconst EnvSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n SERVER_IP: z.string().min(1).default('127.0.0.1'),\n SECURE: z.enum(['true', 'false']).default('false'),\n REDIS_HOST: z.string().min(1).default('127.0.0.1'),\n REDIS_PORT: z.string().regex(/^\\d+$/).default('6379'),\n PROJECT_NAME: z.string().min(1).default('luckystack'),\n}).loose();\n\nexport type RuntimeEnv = z.infer<typeof EnvSchema>;\n\nlet cachedEnv: RuntimeEnv | null = null;\n\n//? Single source of truth for which env files the framework loads, in order\n//? (\"later overrides earlier\"). Default `['.env', '.env.local']`. Override at\n//? runtime with the AMBIENT env var `LUCKYSTACK_ENV_FILES` (comma-separated) — it\n//? must be a real environment variable, not a key inside one of the .env files\n//? (those are only read AFTER this list is resolved).\nexport const DEFAULT_ENV_FILES = ['.env', '.env.local'];\n\nexport const getEnvFiles = (): string[] => {\n const override = process.env.LUCKYSTACK_ENV_FILES;\n if (override) {\n const list = override.split(',').map((entry) => entry.trim()).filter(Boolean);\n if (list.length > 0) return list;\n }\n return DEFAULT_ENV_FILES;\n};\n\n//? Opt-in diagnostic (set `LUCKYSTACK_ENV_DEBUG=1`). The layered-env model is\n//? `.env` (shared) -> `.env.local` (local overrides): a later file's value wins,\n//? and a key REMOVED from the later file silently falls back to the earlier\n//? file's value (it is never \"unset\"). That's correct dotenv precedence but a\n//? common footgun — e.g. removing an OAuth key from `.env.local` while a copy\n//? lingers in `.env` keeps the provider enabled. When the flag is on we parse\n//? each file purely (`dotenv.parse`, no `process.env` mutation) and report every\n//? key defined in more than one loaded file, naming the file whose value wins.\nconst reportDuplicateEnvKeys = (files: string[]): void => {\n const seenIn = new Map<string, string[]>();\n for (const file of files) {\n const filePath = path.resolve(process.cwd(), file);\n if (!existsSync(filePath)) continue;\n const parsed = parseDotenv(readFileSync(filePath, 'utf8'));\n for (const key of Object.keys(parsed)) {\n const list = seenIn.get(key) ?? [];\n list.push(file);\n seenIn.set(key, list);\n }\n }\n for (const [key, sources] of seenIn) {\n if (sources.length > 1) {\n const winner = sources.at(-1);\n // eslint-disable-next-line no-console -- runs before the logger registry is wired\n console.warn(`[env] \"${key}\" is defined in ${sources.join(' + ')}; \"${winner}\" wins. Removing it from only one file lets the other file's value resurface.`);\n }\n }\n};\n\n//? Bun auto-loads .env files BEFORE any user code runs; Node does not. That\n//? silently breaks two guarantees of `loadEnvFiles` below:\n//? 1. `LUCKYSTACK_ENV_FILES` is contractually an AMBIENT-only override (see\n//? `getEnvFiles`) — under Bun a key inside `.env` is already in\n//? `process.env` by the time we read it, so it hijacks the file list.\n//? 2. Bun also loads `.env.<mode>` / `.env.<mode>.local`, which this framework\n//? never loads. Because the first file is `override: false`, those values\n//? silently outrank `.env`.\n//? The cure is a `bunfig.toml` with `env = false` (Bun >= 1.3.3), shipped at the\n//? repo root and in the scaffold template. Bun exposes NO way to read that\n//? setting back at runtime (`Bun.config` / `Bun.bunfig` are undefined), so we\n//? detect the SYMPTOM instead: a file we are about to load whose every key is\n//? already in `process.env` with a byte-identical value can only mean the\n//? runtime preloaded it.\nconst isBunRuntime = (): boolean => 'Bun' in globalThis;\n\n//? Mirrors Bun's own mode selection (`BUN_ENV` -> `NODE_ENV`, exact-match\n//? 'production'/'test', anything else -> development) so the warning can NAME\n//? the extra files Bun loaded behind the framework's back.\nconst getBunModeEnvFiles = (): string[] => {\n const mode = process.env.BUN_ENV ?? process.env.NODE_ENV;\n const suffix = mode === 'production' || mode === 'test' ? mode : 'development';\n return [`.env.${suffix}`, `.env.${suffix}.local`];\n};\n\nconst fileExists = (file: string): boolean => existsSync(path.resolve(process.cwd(), file));\n\n//? True when every key the file defines is ALREADY in `process.env` with the\n//? same value. Requires value equality (not mere presence) so a deployment that\n//? legitimately sets a few ambient overrides is not mistaken for auto-load.\nconst isEnvFilePreloaded = (file: string): boolean => {\n const filePath = path.resolve(process.cwd(), file);\n if (!existsSync(filePath)) return false;\n const [error, parsed] = tryCatchSync(() => parseDotenv(readFileSync(filePath, 'utf8')));\n if (error || !parsed) return false;\n const keys = Object.keys(parsed);\n if (keys.length === 0) return false;\n return keys.every((key) => process.env[key] === parsed[key]);\n};\n\n//? Checked at most once, and only BEFORE the first load — after `loadEnvFiles`\n//? has run, our own dotenv writes would make every file look \"preloaded\".\nlet hasCheckedBunEnvAutoload = false;\n\n//? Warns rather than throws on purpose: `bun install` ignores `env = false`\n//? (oven-sh/bun#31450, still open), so a postinstall boot on Bun would be\n//? unfixably fatal. A wrong env value is a config bug, not a security boundary.\nconst warnOnBunEnvAutoload = (files: string[]): void => {\n if (hasCheckedBunEnvAutoload || !isBunRuntime()) return;\n hasCheckedBunEnvAutoload = true;\n\n //? Bun's mode files are the PUREST signal: the framework never loads them, so\n //? one already applied to `process.env` can only have come from Bun. They also\n //? cover the case that hides `.env` from this check — a mode file overriding a\n //? `.env` key means `.env` itself is no longer byte-identical to the env.\n const modeFiles = getBunModeEnvFiles();\n const preloaded = [...files, ...modeFiles].filter((file) => isEnvFilePreloaded(file));\n if (preloaded.length === 0) return;\n\n const shadowed = modeFiles.filter((file) => fileExists(file));\n const shadowNote = shadowed.length > 0\n ? ` Bun also loaded ${shadowed.join(' + ')}, which LuckyStack never loads — those values outrank ${files[0]}.`\n : '';\n\n // eslint-disable-next-line no-console -- runs before the logger registry is wired\n console.warn(`[env] Bun appears to have auto-loaded ${preloaded.join(' + ')} before LuckyStack did, so Bun's precedence wins over the framework's own layering.${shadowNote} LUCKYSTACK_ENV_FILES is also no longer ambient-only: a value set inside a .env file now hijacks the file list. Add \\`env = false\\` to bunfig.toml (Bun >= 1.3.3) to restore Node semantics.`);\n};\n\nexport const loadEnvFiles = (): void => {\n const files = getEnvFiles();\n warnOnBunEnvAutoload(files);\n //? First file is non-override (a real ambient env var wins); each later file\n //? overrides earlier ones, matching the historical `.env` -> `.env.local` order.\n for (const [index, file] of files.entries()) {\n loadDotenv({ path: file, override: index > 0 });\n }\n if (process.env.LUCKYSTACK_ENV_DEBUG) {\n reportDuplicateEnvKeys(files);\n }\n};\n\nconst applyResolvedDefaultsToProcessEnv = (resolvedEnv: RuntimeEnv) => {\n process.env.NODE_ENV = process.env.NODE_ENV ?? resolvedEnv.NODE_ENV;\n process.env.SERVER_IP = process.env.SERVER_IP ?? resolvedEnv.SERVER_IP;\n process.env.SECURE = process.env.SECURE ?? resolvedEnv.SECURE;\n process.env.REDIS_HOST = process.env.REDIS_HOST ?? resolvedEnv.REDIS_HOST;\n process.env.REDIS_PORT = process.env.REDIS_PORT ?? resolvedEnv.REDIS_PORT;\n process.env.PROJECT_NAME = process.env.PROJECT_NAME ?? resolvedEnv.PROJECT_NAME;\n};\n\nexport const bootstrapEnv = (): RuntimeEnv => {\n if (cachedEnv) {\n return cachedEnv;\n }\n\n loadEnvFiles();\n\n //? Zod's `.loose()` preserves consumer-defined keys, but the removed legacy\n //? backend-port key must not survive in the validated snapshot. Server port\n //? resolution is exclusively options/argv/config.ports.ts.\n const envInput = { ...process.env };\n Reflect.deleteProperty(envInput, 'SERVER_PORT');\n const parsed = EnvSchema.safeParse(envInput);\n if (!parsed.success) {\n const details = parsed.error.issues\n .map((issue) => `${issue.path.join('.') || 'env'}: ${issue.message}`)\n .join('; ');\n\n throw new Error(`Invalid environment configuration: ${details}`);\n }\n\n cachedEnv = parsed.data;\n applyResolvedDefaultsToProcessEnv(cachedEnv);\n return cachedEnv;\n};\n\n//? DD-CORE-D2 — eager-load decision (back-compat, kept for 0.x):\n//? `env` is evaluated at MODULE IMPORT time. This is intentional for the 0.x\n//? public API: every file that does `import { env } from '@luckystack/core'`\n//? gets the same frozen, Zod-validated snapshot, and the dotenv layering runs\n//? exactly once at the package boundary regardless of import order.\n//?\n//? Migration path to lazy consumption (0.2+ recommendation):\n//? Use `getEnv()` instead of `env` at call sites where you need to read env\n//? values inside a function body rather than at module scope. `getEnv()` is\n//? identical to `env` (it returns the same cached singleton after the first\n//? call) but it avoids capturing a stale snapshot when `bootstrapEnv` is\n//? called before dotenv files are fully loaded — a common footgun in test\n//? runners that reset `process.env` between cases. New framework code and\n//? consumer code should prefer `getEnv()` at call-time over the `env`\n//? top-level binding.\nexport const env = bootstrapEnv();\nexport const isProduction = env.NODE_ENV === 'production';\n\n//? Lazy accessor that returns the same cached singleton as `env` but defers\n//? the bootstrap to first call. Prefer this over the top-level `env` binding\n//? in functions, class constructors, and anywhere `bootstrapEnv` must not run\n//? at module evaluation time (e.g. unit tests that reset `process.env`).\nexport const getEnv = (): RuntimeEnv => bootstrapEnv();\n\n/** Application runtime mode. Keep separate from `resolveEnvKey()`, which is a\n * deploy-topology identity and may be `staging`, `dockerSplit`, etc. */\nexport const resolveRuntimeMode = (): RuntimeEnv['NODE_ENV'] => {\n const mode = process.env.NODE_ENV;\n return mode === 'production' || mode === 'test' || mode === 'development'\n ? mode\n : getEnv().NODE_ENV;\n};\nexport const isProductionRuntime = (): boolean => resolveRuntimeMode() === 'production';\nexport const isTestRuntime = (): boolean => resolveRuntimeMode() === 'test';\n","import './env';\nimport { createRequire } from 'node:module';\nimport type { PrismaClient } from '@prisma/client';\nimport { getPrismaClient, setDefaultPrismaResolver } from './clients';\nimport tryCatchSync from './tryCatchSync';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __luckystackPrisma: PrismaClient | undefined;\n}\n\n//? '@prisma/client' deliberately left the static-import position (ADR 0020,\n//? the ORM-choice decision): with `orm: 'none'` (or a non-Prisma ORM) the\n//? package is legitimately absent, and an unguarded value-import crashed boot\n//? with a bare ERR_MODULE_NOT_FOUND before any framework error handling\n//? existed. The type-only import above is erased at runtime; the client is\n//? require()d lazily on first default-resolver access instead.\nconst requireFromCore = createRequire(import.meta.url);\n\n/**\n * Is a generated `@prisma/client` present in this install? Used by the\n * default resolver's error path and by `/readyz` to decide whether a missing\n * DB ping means \"broken\" or \"this project has no Prisma by design\".\n */\nexport const isPrismaClientResolvable = (): boolean => {\n const [error] = tryCatchSync(() => requireFromCore.resolve('@prisma/client'));\n return !error;\n};\n\ninterface PrismaClientModuleShape {\n PrismaClient?: new () => PrismaClient;\n}\n\n//? Lazy default: only constructed when the proxy is first accessed AND no\n//? consumer has registered their own Prisma client. Avoids paying the\n//? PrismaClient construction cost in code paths that swap it out at boot.\nconst buildDefaultPrismaClient = (): PrismaClient => {\n if (!globalThis.__luckystackPrisma) {\n const [loadError, loaded] = tryCatchSync(\n () => requireFromCore('@prisma/client') as PrismaClientModuleShape,\n );\n const PrismaClientCtor = loaded?.PrismaClient;\n if (loadError || typeof PrismaClientCtor !== 'function') {\n //? THE `orm: 'none'` error seam (ADR 0020): every DB access without a\n //? registered client lands here with an actionable message instead of a\n //? module-resolution stack trace.\n throw new Error(\n \"[luckystack] no database client is available: '@prisma/client' is not installed/generated \" +\n 'and no client was registered. Register your own database client in ' +\n '`luckystack/core/clients.ts` via registerPrismaClient(...) (any client whose surface your ' +\n 'handlers use) — and swap the login UserAdapter via registerUserAdapter(...) if auth is on — ' +\n 'or install Prisma: `npm i @prisma/client prisma` + `npx prisma generate`. ' +\n \"Projects scaffolded with orm: 'none' are expected to fill in that file.\",\n );\n }\n globalThis.__luckystackPrisma = new PrismaClientCtor();\n }\n return globalThis.__luckystackPrisma;\n};\n\nsetDefaultPrismaResolver(buildDefaultPrismaClient);\n\n//? No eager dev init: the `globalThis.__luckystackPrisma` cache already\n//? guarantees a single Prisma instance across HMR reloads on first proxy\n//? access, so constructing it at import time only burdens tools/CLIs/tests that\n//? type-import core but never touch the DB (the package's \"no import-time side\n//? effects\" doctrine).\n\n//? Proxy forwards every read/call to whichever client is active *at access\n//? time*, so a `registerPrismaClient(...)` performed after this module is\n//? imported still wins. The empty-object Proxy target needs an `as` cast\n//? because the target is never read directly — every access is intercepted.\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Proxy target placeholder\nexport const prisma = new Proxy({} as PrismaClient, {\n //? Bind methods to the real client so a CAPTURED method keeps its `this`.\n //? Without binding, a detached call (`const q = prisma.$queryRaw; q(...)`)\n //? runs with `this === undefined` and Prisma's internal `this`-access throws\n //? — exactly how the `/readyz` prisma ping silently failed (it captures\n //? `$runCommandRaw` / `$queryRaw` into a local before calling, so the ping\n //? always threw → `prisma: false` → 503, even with the DB reachable).\n //? Mirrors the `redis` proxy. Non-function reads (model delegates like\n //? `prisma.user`) pass through untouched, so their methods bind to the\n //? delegate as before.\n get: (_target, prop, _receiver) => {\n const real = getPrismaClient() as object;\n //? Pass `real` (not the Proxy) as the receiver so that getter-backed\n //? private fields (Prisma uses #-private slots internally) look up their\n //? slot on the real target instead of on the Proxy, which has no private\n //? slots and would throw TypeError (CORE-N9).\n const value: unknown = Reflect.get(real, prop, real);\n if (typeof value !== 'function') return value;\n const fn = value as (...args: unknown[]) => unknown;\n return fn.bind(real);\n },\n has: (_target, prop) => Reflect.has(getPrismaClient(), prop),\n ownKeys: () => Reflect.ownKeys(getPrismaClient()),\n getOwnPropertyDescriptor: (_target, prop) =>\n Reflect.getOwnPropertyDescriptor(getPrismaClient(), prop),\n});\n","//? Prisma + Redis client registries. Lets consumers swap the framework's\n//? default clients (TLS, Accelerate, custom logger, sentinel, ...) without\n//? touching framework code.\n//?\n//? Keyed by slot: the reserved `'default'` slot backs every framework\n//? internal (sessions, rate-limit, presence) and the `prisma`/`redis`\n//? proxies. Additional slots are opt-in and only reachable via\n//? `getPrismaClientFor(key)` / `getRedisClientFor(key)`, so a consumer can run\n//? graded credentials side by side — e.g. a read-only client on `'ro'` and a\n//? read-write client on `'rw'`, or one client per tenant.\n//?\n//? `db.ts` and `redis.ts` re-export `prisma` and `redis` as Proxy objects that\n//? forward every operation to the currently registered default client (or a\n//? lazy default if nothing was registered). All existing call sites that do\n//? `import { prisma } from '@luckystack/core'` or `import { redis } from '@luckystack/core'`\n//? keep working unchanged — they resolve the `'default'` slot.\n\nimport type { PrismaClient } from '@prisma/client';\nimport type { Redis as RedisClient } from 'ioredis';\n\n//? Reserved slot consumed by every framework internal and the proxies. A\n//? no-key registration / lookup targets this slot, preserving the original\n//? single-client behavior.\nexport const DEFAULT_CLIENT_KEY = 'default';\n\nconst prismaClients = new Map<string, PrismaClient>();\nconst redisClients = new Map<string, RedisClient>();\n\nexport const registerPrismaClient = (client: PrismaClient, key = DEFAULT_CLIENT_KEY): PrismaClient => {\n prismaClients.set(key, client);\n return client;\n};\n\nexport const registerRedisClient = (client: RedisClient, key = DEFAULT_CLIENT_KEY): RedisClient => {\n redisClients.set(key, client);\n return client;\n};\n\n//? Boot guards check the DEFAULT slot only — a keyed-only registration does\n//? not count as \"the framework has a client\", because internals use default.\nexport const isPrismaClientRegistered = (): boolean => prismaClients.has(DEFAULT_CLIENT_KEY);\nexport const isRedisClientRegistered = (): boolean => redisClients.has(DEFAULT_CLIENT_KEY);\n\n//? Diagnostic: which slots have an explicitly-registered client.\nexport const getPrismaClientKeys = (): string[] => [...prismaClients.keys()];\nexport const getRedisClientKeys = (): string[] => [...redisClients.keys()];\n\n//? `db.ts` and `redis.ts` set these accessors at module load with the lazy\n//? defaults. Splitting registration from default construction keeps a hard\n//? type dependency on `@prisma/client` / `ioredis` out of this file.\nlet defaultPrismaResolver: (() => PrismaClient) | null = null;\nlet defaultRedisResolver: (() => RedisClient) | null = null;\n\nexport const setDefaultPrismaResolver = (resolver: () => PrismaClient): void => {\n defaultPrismaResolver = resolver;\n};\n\nexport const setDefaultRedisResolver = (resolver: () => RedisClient): void => {\n defaultRedisResolver = resolver;\n};\n\n//? Resolve a specific slot. The `'default'` slot falls back to the lazy\n//? default resolver when nothing was registered; any other slot throws when\n//? unregistered — a keyed lookup must NEVER silently hand back the privileged\n//? default client, as that would defeat the point of graded credentials.\nexport const getPrismaClientFor = (key = DEFAULT_CLIENT_KEY): PrismaClient => {\n const client = prismaClients.get(key);\n if (client) return client;\n if (key === DEFAULT_CLIENT_KEY && defaultPrismaResolver) return defaultPrismaResolver();\n throw new Error(\n key === DEFAULT_CLIENT_KEY\n ? 'No Prisma client available. Either register one via registerPrismaClient(), ' +\n 'or import \"@luckystack/core\" from a context where the default resolver was set.'\n : `No Prisma client registered for slot \"${key}\". Register one via registerPrismaClient(client, \"${key}\").`\n );\n};\n\nexport const getRedisClientFor = (key = DEFAULT_CLIENT_KEY): RedisClient => {\n const client = redisClients.get(key);\n if (client) return client;\n if (key === DEFAULT_CLIENT_KEY && defaultRedisResolver) return defaultRedisResolver();\n throw new Error(\n key === DEFAULT_CLIENT_KEY\n ? 'No Redis client available. Either register one via registerRedisClient(), ' +\n 'or import \"@luckystack/core\" from a context where the default resolver was set.'\n : `No Redis client registered for slot \"${key}\". Register one via registerRedisClient(client, \"${key}\").`\n );\n};\n\nexport const getPrismaClient = (): PrismaClient => getPrismaClientFor(DEFAULT_CLIENT_KEY);\nexport const getRedisClient = (): RedisClient => getRedisClientFor(DEFAULT_CLIENT_KEY);\n\n//? Test-only: drop every registered slot. The default resolvers (set once at\n//? module load by db.ts / redis.ts) stay in place.\nexport const resetClientsForTests = (): void => {\n prismaClients.clear();\n redisClients.clear();\n};\n","//? Pluggable database readiness probe (ADR 0020, the ORM-choice decision).\n//? `/readyz` used to hard-wire a Prisma ping, which made a deliberately\n//? Prisma-less project (orm: 'none', or a custom ORM behind\n//? `registerPrismaClient`) permanently report 503. Now:\n//?\n//? - a consumer with a custom data layer registers its own probe from a\n//? `luckystack/core/*.ts` overlay file:\n//? registerDbHealthCheck(async () => { await db.execute('select 1'); return true; });\n//? - with no registration, `/readyz` falls back to the built-in Prisma ping\n//? when `@prisma/client` is resolvable, and reports the check as\n//? 'skipped' when it is not (a DB-less project must be able to go ready).\n\nexport type DbHealthResult = boolean | 'skipped';\nexport type DbHealthCheck = () => Promise<DbHealthResult> | DbHealthResult;\n\nlet activeCheck: DbHealthCheck | null = null;\n\nexport const registerDbHealthCheck = (check: DbHealthCheck): void => {\n activeCheck = check;\n};\n\nexport const getDbHealthCheck = (): DbHealthCheck | null => activeCheck;\n\nexport const isDbHealthCheckRegistered = (): boolean => activeCheck !== null;\n\nexport const resetDbHealthCheckForTests = (): void => {\n activeCheck = null;\n};\n","/* eslint-disable @typescript-eslint/no-misused-promises, @typescript-eslint/require-await */\n\nimport Redis from 'ioredis';\nimport type { Redis as RedisClient } from 'ioredis';\nimport { env } from './env';\nimport { getRedisClient, setDefaultRedisResolver, registerRedisClient, isRedisClientRegistered } from './clients';\nimport { getLogger } from './loggerRegistry';\nimport { applyStrayKeyPrefix } from './redisKeyFormatter';\nimport { registerSecretsResolvedListener } from './secretsResolved';\n\nlet cachedDefault: RedisClient | null = null;\n//? Identity of the default-slot client last installed by THIS module. Secret\n//? rotation may replace only this framework-owned registration; a different\n//? registered identity belongs to the consumer (cluster/sentinel/TLS/custom\n//? retry policy) and must keep precedence across automatic refreshes.\nlet frameworkRegisteredDefault: RedisClient | null = null;\n\n//? Stop reconnecting after this many consecutive failures so a permanently\n//? unreachable / misconfigured Redis (bad creds, wrong host) doesn't hammer\n//? forever. With the capped backoff below the default 50 is ~1 minute of\n//? attempts — long enough to ride out a transient blip, short enough to surface\n//? a real outage. Managed-Redis maintenance windows can exceed a minute, so\n//? both the attempt cap and the backoff ceiling are overridable via env\n//? (read at client-build time, so dotenv timing doesn't capture a stale value):\n//? LUCKYSTACK_REDIS_MAX_RECONNECTS — attempt cap (default 50)\n//? LUCKYSTACK_REDIS_MAX_BACKOFF_MS — per-attempt backoff ceiling (default 2000)\nconst DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS = 50;\nconst DEFAULT_MAX_REDIS_BACKOFF_MS = 2000;\n\nconst readPositiveIntEnv = (name: string, fallback: number): number => {\n const raw = process.env[name];\n if (!raw) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n};\n\n//? Every connection field is read from `process.env` at CALL time, never from\n//? the frozen `env` snapshot. A @luckystack/secret-manager POINTER is replaced\n//? in `process.env` at resolve time, which happens AFTER core's module-eval\n//? bootstrap froze `env` — so the snapshot can still hold the pointer. This\n//? applies to host/port exactly as it does to the credentials: `REDIS_HOST` is\n//? a legal pointer target too, and a pointer passes the Zod `min(1)` check, so\n//? the failure is a silent connect to a host literally named after the pointer.\n//? `getEnv()` is NOT an alternative — `bootstrapEnv` returns a cached singleton,\n//? so it hands back the same stale snapshot.\n//?\n//? The `env.*` fallbacks only guard a caller that cleared `process.env`:\n//? `env.ts` mirrors the resolved Zod defaults for the ordinary runtime keys\n//? back into `process.env`; runtime ports are owned separately by\n//? config.ports.ts plus the typed positional-override registry. An EMPTY string must fall\n//? back to the validated snapshot rather than reach\n//? ioredis as a blank host/port — hence the explicit emptiness check (`??`\n//? would let `''` win, which is why this is not a nullish-coalescing site).\nconst readEnvOrSnapshot = (raw: string | undefined, fallback: string): string =>\n raw === undefined || raw === '' ? fallback : raw;\n\nconst readRedisHost = (): string => readEnvOrSnapshot(process.env.REDIS_HOST, env.REDIS_HOST);\nconst readRedisPort = (): number => Number.parseInt(readEnvOrSnapshot(process.env.REDIS_PORT, env.REDIS_PORT), 10);\n\n//? Build a fresh ioredis client from the CURRENT env (read at call time so\n//? dotenv / secret-manager timing doesn't capture a stale value).\nconst constructRedisClient = (): RedisClient => {\n const maxReconnectAttempts = readPositiveIntEnv('LUCKYSTACK_REDIS_MAX_RECONNECTS', DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS);\n const maxBackoffMs = readPositiveIntEnv('LUCKYSTACK_REDIS_MAX_BACKOFF_MS', DEFAULT_MAX_REDIS_BACKOFF_MS);\n\n const client = new Redis({\n host: readRedisHost(),\n port: readRedisPort(),\n ...(process.env.REDIS_USER && { username: process.env.REDIS_USER }),\n ...(process.env.REDIS_PASSWORD && { password: process.env.REDIS_PASSWORD }),\n\n retryStrategy(times) {\n if (times > maxReconnectAttempts) {\n getLogger().error(\n `Redis unreachable after ${String(maxReconnectAttempts)} reconnect attempts; giving up. Check REDIS_HOST / REDIS_PORT / credentials and that Redis is reachable. Raise LUCKYSTACK_REDIS_MAX_RECONNECTS for longer outage tolerance.`,\n );\n return null;\n }\n return Math.min(times * 50, maxBackoffMs);\n },\n });\n\n //? `ready` (not `connect`): `connect` fires on TCP connect BEFORE the AUTH\n //? handshake, so logging there reports success even when credentials are\n //? wrong. `ready` fires only after AUTH succeeds.\n client.on('ready', async () => {\n getLogger().info('Connected to Redis');\n });\n\n client.on('error', (err) => {\n //? CORE-1: a very common, cryptic failure is an auth error whose password\n //? is still a @luckystack/secret-manager POINTER (e.g. `REDIS_PASSWORD_V1`)\n //? because the default client was constructed — and cached — during an\n //? early import-scan, BEFORE secret-manager init resolved the pointer. Turn\n //? the raw `WRONGPASS` into an actionable message instead.\n const message = String((err as { message?: string } | undefined)?.message ?? err);\n const password = process.env.REDIS_PASSWORD ?? '';\n if (/WRONGPASS|NOAUTH|invalid username-password/i.test(message) && /_V\\d+$/i.test(password)) {\n getLogger().error(\n `Redis auth failed and REDIS_PASSWORD (\"${password}\") looks like an UNRESOLVED secret-manager pointer. ` +\n '`@luckystack/secret-manager` now rebuilds this client automatically after it resolves secrets, so this usually ' +\n 'means REDIS_PASSWORD is NOT in the secret-manager `envNames` allowlist (unset resolves nothing) — add it. ' +\n 'If you resolve secrets by other means, call `rebuildDefaultRedisClient()` (or ' +\n '`registerRedisClient(new Redis(getRedisConnectionOptions()))`) after resolution. ' +\n 'See docs/luckystack/ARCHITECTURE_SECRET_MANAGER.md.',\n err,\n );\n return;\n }\n getLogger().error('Error connecting to Redis', err);\n });\n\n return client;\n};\n\nconst buildDefaultRedisClient = (): RedisClient => {\n if (cachedDefault) return cachedDefault;\n cachedDefault = constructRedisClient();\n return cachedDefault;\n};\n\n//? Drop (and disconnect) the cached default Redis client so the NEXT resolve\n//? rebuilds it. Kept for consumers that resolve secrets by other means, but note\n//? it is INSUFFICIENT on its own for the secret-manager pointer case: it only\n//? nulls `cachedDefault` and DEFERS the rebuild, so a stale value can resurface.\n//? Prefer `rebuildDefaultRedisClient()` (eager replace) — the framework uses it.\nexport const resetDefaultRedisClient = (): void => {\n if (!cachedDefault) return;\n const previous = cachedDefault;\n cachedDefault = null;\n try {\n previous.disconnect();\n } catch {\n //? Best-effort — a client that never connected can throw on disconnect.\n }\n};\n\n//? EAGER REPLACE (the secret-manager pointer fix): build a fresh default client\n//? from the CURRENT (resolved) env and REGISTER it into the default slot so it\n//? wins over the lazy resolver. This is what `resetDefaultRedisClient()` alone\n//? could not do — a plain reset defers the rebuild (a stale/pointer value can\n//? resurface by the time it's lazily rebuilt) and never overrides a client that\n//? was already registered. Capturing the resolved secret NOW, into a registered\n//? client, is immune to that. Disconnects the previous default first so rotation\n//? doesn't leak a connection. The framework calls this from the secrets-resolved\n//? hook below (fired by `@luckystack/secret-manager` after every resolve).\nexport const rebuildDefaultRedisClient = (): RedisClient => {\n const previousRegistered = isRedisClientRegistered() ? getRedisClient() : null;\n resetDefaultRedisClient();\n const client = constructRedisClient();\n registerRedisClient(client);\n frameworkRegisteredDefault = client;\n if (previousRegistered && previousRegistered !== client) {\n try {\n previousRegistered.disconnect();\n } catch {\n //? Best-effort — a client that never connected can throw on disconnect.\n }\n }\n return client;\n};\n\nsetDefaultRedisResolver(buildDefaultRedisClient);\n\n//? CORE-1 (decoupled hook): when a secret resolver reports that secrets were\n//? (re)resolved — `@luckystack/secret-manager` fires this automatically after\n//? every resolve (via the global-symbol channel in `secretsResolved.ts`) —\n//? EAGERLY REBUILD + REGISTER the default client from the now-resolved env if a\n//? `REDIS_` credential changed. Registering (not resetting) is what lets a project\n//? run Redis auth via a secret-manager POINTER with zero consumer code: the\n//? resolved secret is captured immediately into a registered client that wins over\n//? the resolver. `undefined` (caller doesn't know which keys changed) rebuilds\n//? defensively.\nregisterSecretsResolvedListener((changedKeys) => {\n if (changedKeys !== undefined && !changedKeys.some((key) => /^REDIS_/i.test(key))) return;\n\n const registered = isRedisClientRegistered() ? getRedisClient() : null;\n if (registered && registered !== frameworkRegisteredDefault) {\n //? Consumer-owned clients win by registry contract. The generic\n //? secrets-resolved channel still notifies consumer listeners, so their\n //? own rotation hook can construct the right cluster/sentinel/TLS client.\n return;\n }\n rebuildDefaultRedisClient();\n});\n\n//? Single source of truth for Redis connection params. Used by\n//? `@luckystack/router`'s bootHandshake (which opens its own short-lived\n//? connections for cross-env probes) so a config rename touches only this\n//? file. Reads env at call time so dotenv timing doesn't capture a stale\n//? value.\nexport interface RedisConnectionOptions {\n host: string;\n port: number;\n username?: string;\n password?: string;\n}\n\nexport const getRedisConnectionOptions = (): RedisConnectionOptions => ({\n host: readRedisHost(),\n port: readRedisPort(),\n ...(process.env.REDIS_USER ? { username: process.env.REDIS_USER } : {}),\n ...(process.env.REDIS_PASSWORD ? { password: process.env.REDIS_PASSWORD } : {}),\n});\n\n//? Single-key commands whose FIRST argument is the key. The proxy runs that\n//? argument through `applyStrayKeyPrefix` as a best-effort namespace net for\n//? raw app keys; already-namespaced keys (anything containing `:`, which is\n//? every framework key) pass through untouched, so framework behavior is\n//? unchanged. Variadic / non-arg0-key commands (`del`, `scan`, `eval`,\n//? `multi`, `mget`, ...) are deliberately excluded — their key positions can't\n//? be inferred safely; those call sites use `formatKey()` explicitly.\nconst STRAY_PREFIX_COMMANDS = new Set<string>([\n 'get', 'set', 'setex', 'psetex', 'setnx', 'getset', 'getdel', 'append', 'strlen',\n 'expire', 'pexpire', 'expireat', 'pexpireat', 'persist', 'ttl', 'pttl', 'type',\n 'incr', 'incrby', 'incrbyfloat', 'decr', 'decrby',\n 'sadd', 'srem', 'smembers', 'scard', 'sismember', 'spop', 'srandmember',\n 'hget', 'hset', 'hdel', 'hgetall', 'hkeys', 'hvals', 'hexists', 'hincrby', 'hmget', 'hmset',\n 'lpush', 'rpush', 'lpop', 'rpop', 'llen', 'lrange', 'lrem',\n 'zadd', 'zrem', 'zscore', 'zrange', 'zrank', 'zcard',\n 'getbit', 'setbit', 'bitcount',\n]);\n\n//? Variadic commands whose EVERY string argument is a key (`del('a','b')`,\n//? `mget('a','b')`). The single-key `STRAY_PREFIX_COMMANDS` set only prefixes\n//? arg0, which left these asymmetric: `redis.set('flag', v)` wrote\n//? `<project>:flag` while `redis.del('flag')` targeted the unprefixed `flag`\n//? and silently no-op'd. Prefixing every string arg here closes that footgun\n//? for revocation-style stray keys. (`eval`/`scan`/`multi` stay excluded —\n//? their key positions can't be inferred and framework call sites already use\n//? `formatKey()` there.)\nconst ALL_ARGS_ARE_KEYS_COMMANDS = new Set<string>([\n 'del', 'unlink', 'exists', 'touch', 'mget',\n]);\n\n//? Extend the set of single-key, arg0-is-key commands the `redis` proxy runs\n//? through `applyStrayKeyPrefix`. Additive — the built-in set above is always\n//? retained — so a consumer using a Redis command the framework's default\n//? list doesn't cover (a newer/module command whose first argument is the\n//? key) can opt it into the multi-tenant stray-prefix net without forking.\n//? Command names are matched case-insensitively, mirroring the proxy lookup.\n//? Variadic / non-arg0-key commands must NOT be registered here — their key\n//? positions can't be inferred safely; use `formatKey()` explicitly instead.\nexport const registerStrayPrefixCommand = (...commands: string[]): void => {\n for (const command of commands) {\n STRAY_PREFIX_COMMANDS.add(command.toLowerCase());\n }\n};\n\n//? Proxy: same pattern as `prisma`. Defers resolution until call time so\n//? `registerRedisClient(...)` can still win after this module is loaded.\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Proxy target placeholder\nconst redisProxy = new Proxy({} as RedisClient, {\n get: (_target, prop, _receiver) => {\n const real = getRedisClient() as object;\n //? Pass `real` as receiver (CORE-N9) — see the same fix in `db.ts`.\n const value: unknown = Reflect.get(real, prop, real);\n if (typeof value !== 'function') return value;\n const fn = value as (...args: unknown[]) => unknown;\n const command = typeof prop === 'string' ? prop.toLowerCase() : '';\n if (command && ALL_ARGS_ARE_KEYS_COMMANDS.has(command)) {\n return (...args: unknown[]): unknown => {\n const prefixed = args.map((arg) => (typeof arg === 'string' ? applyStrayKeyPrefix(arg) : arg));\n return fn.apply(real, prefixed);\n };\n }\n if (command && STRAY_PREFIX_COMMANDS.has(command)) {\n return (...args: unknown[]): unknown => {\n if (args.length > 0 && typeof args[0] === 'string') {\n args[0] = applyStrayKeyPrefix(args[0]);\n }\n return fn.apply(real, args);\n };\n }\n return fn.bind(real);\n },\n has: (_target, prop) => Reflect.has(getRedisClient(), prop),\n});\n\nexport const redis = redisProxy;\nexport default redisProxy;\n","//? Redis key formatter — the single, registerable authority for how every\n//? framework Redis key is namespaced. Replaces the per-call-site\n//? `${getProjectName()}-<area>:<id>` convention with one injectable hook, so a\n//? multi-tenant consumer can prefix keys per tenant (e.g. read a tenant id\n//? from AsyncLocalStorage) from ONE place instead of patching ~9 call sites.\n//?\n//? Contract: `formatKey(namespace, suffix)` builds a key. The DEFAULT\n//? formatter reproduces the framework's historical key bytes EXACTLY, so\n//? upgrading without registering a custom formatter requires NO key migration.\n//? A namespace beginning with a separator (`-` or `:`) is appended verbatim —\n//? this is how the framework preserves its legacy shapes (`-session`,\n//? `:rate-limit`); any other namespace is colon-joined for clean app keys\n//? (`formatKey('rag', id)` -> `<project>:rag:<id>`).\n//?\n//? Custom formatters MUST keep the `<namespace-root>:<suffix>` join, otherwise\n//? the framework's session/rate-limit `SCAN` enumeration (which derives its\n//? match pattern from `formatKey(namespace, '')`) cannot find the keys.\n\nimport { getProjectName } from './projectConfig';\n\nexport type RedisKeyFormatter = (namespace: string, suffix: string) => string;\n\nexport const defaultRedisKeyFormatter: RedisKeyFormatter = (namespace, suffix) => {\n const root = /^[-:]/.test(namespace) ? `${getProjectName()}${namespace}` : `${getProjectName()}:${namespace}`;\n return suffix === '' ? root : `${root}:${suffix}`;\n};\n\nlet formatter: RedisKeyFormatter | null = null;\n\n//? Register a custom key formatter (call once at boot, before the first Redis\n//? key is built). Last-write-wins.\nexport const registerRedisKeyFormatter = (fn: RedisKeyFormatter): void => {\n formatter = fn;\n};\n\nexport const getRedisKeyFormatter = (): RedisKeyFormatter => formatter ?? defaultRedisKeyFormatter;\n\n//? Test-only — restore the default formatter between scenarios.\nexport const resetRedisKeyFormatterForTests = (): void => {\n formatter = null;\n};\n\n//? Build a namespaced Redis key through the active formatter. Suffix is\n//? optional so callers can produce just the namespace root (used to derive\n//? SCAN match patterns: `${formatKey('-session', '')}:*`).\nexport const formatKey = (namespace: string, suffix = ''): string =>\n getRedisKeyFormatter()(namespace, suffix);\n\n//? Best-effort prefix for stray, un-namespaced keys passed to the `redis`\n//? proxy's single-key commands. A key that already contains a `:` (every\n//? framework key, plus any app key the caller chose to namespace) passes\n//? through unchanged, so this can never double-prefix or corrupt a real key.\n//? Project-level only — per-tenant scoping is the formatter's job, not the\n//? net's (the net has no request context).\nexport const applyStrayKeyPrefix = (key: string): string =>\n key.includes(':') ? key : `${getProjectName()}:${key}`;\n","//? Redis lease primitive — a minimal single-writer / leader-ownership building\n//? block (SET NX PX + owner-checked Lua release/renew). Lets a multi-instance\n//? deployment elect ONE owner for a host-bound resource (orchestrator, serial\n//? indexer, cron) without building fencing from scratch. Redis is already a\n//? hard dependency.\n//?\n//? Scope: this is a single-Redis, best-effort lock (NOT Redlock / distributed\n//? consensus). A client whose event loop stalls past the TTL can have its\n//? lease expire and be handed to another owner — size the TTL accordingly and\n//? renew well before expiry. For strict correctness across a Redis failover,\n//? use a dedicated coordinator (etcd/Zookeeper). The lease is a PRIMITIVE; the\n//? leader-election RUNTIME (the renew loop, what to do on loss) is app code.\n\nimport { randomBytes } from 'node:crypto';\nimport { redis } from './redis';\nimport tryCatch from './tryCatch';\nimport { formatKey } from './redisKeyFormatter';\n\nconst leaseKey = (name: string): string => formatKey('lease', name);\n\n//? Owner-checked compare-and-pexpire: only the current holder may extend.\nconst RENEW_SCRIPT = `\nif redis.call('get', KEYS[1]) == ARGV[1] then\n return redis.call('pexpire', KEYS[1], ARGV[2])\nelse\n return 0\nend\n`;\n\n//? Owner-checked compare-and-delete: releasing someone else's lease is a no-op,\n//? so a stale holder can't free a lease another owner has already taken.\nconst RELEASE_SCRIPT = `\nif redis.call('get', KEYS[1]) == ARGV[1] then\n return redis.call('del', KEYS[1])\nelse\n return 0\nend\n`;\n\n/**\n * Acquire an exclusive lease named `name` for `ttlMs` milliseconds. Returns an\n * opaque owner token on success, or `null` when another owner already holds it.\n * `SET key token PX ttl NX` is atomic, so concurrent acquirers race safely.\n * Keep the returned token to `renewLease` / `releaseLease`.\n */\nexport const acquireLease = async (name: string, ttlMs: number): Promise<string | null> => {\n const token = randomBytes(16).toString('hex');\n const [error, result] = await tryCatch(async () =>\n redis.set(leaseKey(name), token, 'PX', ttlMs, 'NX'),\n );\n if (error) return null;\n return result === 'OK' ? token : null;\n};\n\n/**\n * Extend a held lease by `ttlMs` (sliding ownership). Returns true when the\n * supplied `token` still owns the lease and the TTL was applied, false\n * otherwise (lost ownership, expired, or Redis error).\n */\nexport const renewLease = async (name: string, token: string, ttlMs: number): Promise<boolean> => {\n const [error, result] = await tryCatch(async () =>\n redis.eval(RENEW_SCRIPT, 1, leaseKey(name), token, String(ttlMs)),\n );\n if (error) return false;\n return result === 1;\n};\n\n/**\n * Release a held lease. Owner-checked: only succeeds (returns true) when\n * `token` is the current holder. Returns false on mismatch or Redis error.\n */\nexport const releaseLease = async (name: string, token: string): Promise<boolean> => {\n const [error, result] = await tryCatch(async () =>\n redis.eval(RELEASE_SCRIPT, 1, leaseKey(name), token),\n );\n if (error) return false;\n return result === 1;\n};\n","import type { Server as SocketIOServer } from 'socket.io';\nimport type { Redis as RedisClient } from 'ioredis';\nimport { createAdapter } from '@socket.io/redis-adapter';\nimport type { RedisAdapterOptions } from '@socket.io/redis-adapter';\nimport { redis } from './redis';\nimport { getLogger } from './loggerRegistry';\n\nexport interface AttachSocketRedisAdapterOptions {\n /**\n * Options forwarded to `@socket.io/redis-adapter`'s `createAdapter`\n * (e.g. `key` prefix, `requestsTimeout`, `publishOnSpecificResponseChannel`).\n */\n adapterOptions?: Partial<RedisAdapterOptions>;\n /**\n * Supply a pre-built pub client instead of `redis.duplicate()`. Use this for\n * a dedicated adapter connection, custom auth, or a sentinel/cluster handle.\n * When omitted the framework duplicates the default `redis` handle.\n */\n pubClient?: RedisClient;\n /** Pre-built sub client (see `pubClient`). */\n subClient?: RedisClient;\n}\n\n//? Cross-instance Socket.io pub/sub. Without this, room broadcasts only reach\n//? clients connected to the same process. The router can route a client to\n//? service instance A, but a sync event fired from instance B never reaches\n//? them unless both instances share a pub/sub channel.\n//?\n//? Uses `redis.duplicate()` rather than the main handle because ioredis in\n//? subscribe mode blocks non-pub/sub commands on that connection. Pass\n//? `pubClient`/`subClient` to override (e.g. a separate adapter connection),\n//? and `adapterOptions` to tune the underlying `createAdapter`.\nexport const attachSocketRedisAdapter = (\n io: SocketIOServer,\n options: AttachSocketRedisAdapterOptions = {},\n): void => {\n const pubClient = options.pubClient ?? redis.duplicate();\n const subClient = options.subClient ?? redis.duplicate();\n\n pubClient.on('error', (err) => {\n getLogger().error('[socket-redis-adapter] pub client error', err);\n });\n subClient.on('error', (err) => {\n getLogger().error('[socket-redis-adapter] sub client error', err);\n });\n\n io.adapter(createAdapter(pubClient, subClient, options.adapterOptions));\n};\n","import { randomUUID } from 'node:crypto';\nimport { redis } from './redis';\nimport { getDeployConfig } from './deployConfigRegistry';\nimport { getLogger } from './loggerRegistry';\n\n//? @adr 0036\n//? Writes a fresh UUID to `luckystack:boot:<envKey>` on startup. Any router\n//? in another env that's truly sharing this Redis can read the same key.\n//? Router boot handshake cross-checks against the /_health endpoint to catch\n//? the \"two Redis URLs that both respond\" failure mode.\n\n//? Single source of truth — also consumed by `@luckystack/router`'s\n//? `bootHandshake.ts` so the prefix can never drift between writer and reader.\nexport const BOOT_KEY_PREFIX = 'luckystack:boot:';\nconst DEFAULT_BOOT_KEY_TTL_SECONDS = 3600;\n\nconst getBootKeyTtlSeconds = (): number =>\n getDeployConfig().routing?.bootKeyTtlSeconds ?? DEFAULT_BOOT_KEY_TTL_SECONDS;\n\nconst getBootUuidRefreshIntervalMs = (): number =>\n Math.max(100, Math.floor((getBootKeyTtlSeconds() * 1000) / 3));\n\nexport const resolveEnvKey = (): string => {\n return process.env.LUCKYSTACK_ENV ?? process.env.NODE_ENV ?? 'development';\n};\n\nexport const writeBootUuid = async (envKey?: string): Promise<string> => {\n const key = envKey ?? resolveEnvKey();\n const uuid = randomUUID();\n await redis.set(`${BOOT_KEY_PREFIX}${key}`, uuid, 'EX', getBootKeyTtlSeconds());\n return uuid;\n};\n\nexport const readBootUuid = async (envKey?: string): Promise<string | null> => {\n const key = envKey ?? resolveEnvKey();\n return redis.get(`${BOOT_KEY_PREFIX}${key}`);\n};\n\n/**\n * Extends the current environment boot UUID without rotating its value.\n *\n * Every healthy backend may renew the environment-level key, so multi-instance\n * deployments do not depend on whichever process happened to write it last. If\n * Redis recovered after the key expired, the first refresher writes a new UUID.\n */\nexport const refreshBootUuid = async (envKey?: string): Promise<void> => {\n const key = envKey ?? resolveEnvKey();\n const refreshed = await redis.expire(`${BOOT_KEY_PREFIX}${key}`, getBootKeyTtlSeconds());\n if (refreshed === 0) await writeBootUuid(key);\n};\n\nexport interface BootUuidHeartbeat {\n stop: () => void;\n}\n\n/**\n * Keeps the Redis boot UUID alive for as long as this backend is running.\n *\n * The self-rescheduling timeout waits for each Redis operation to settle before\n * scheduling another, so an outage cannot accumulate overlapping refreshes.\n * The timer is unref'd and therefore never keeps an otherwise-finished process\n * alive. Failures are logged and retried on the next interval.\n */\nexport const startBootUuidHeartbeat = (envKey?: string): BootUuidHeartbeat => {\n const key = envKey ?? resolveEnvKey();\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const schedule = (): void => {\n if (stopped) return;\n timer = setTimeout(() => {\n timer = null;\n void refreshBootUuid(key).then(\n () => { schedule(); },\n (error: unknown) => {\n getLogger().error(\n `[boot-uuid] failed to refresh ${BOOT_KEY_PREFIX}${key}; readiness may degrade if the TTL expires`,\n error instanceof Error ? error : new Error(String(error)),\n );\n schedule();\n },\n );\n }, getBootUuidRefreshIntervalMs());\n timer.unref();\n };\n\n schedule();\n\n return {\n stop: () => {\n stopped = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n },\n };\n};\n","import { createHash, createHmac } from 'node:crypto';\nimport { getDeployConfig } from './deployConfigRegistry';\nimport { getProjectConfig } from './projectConfig';\nimport type { HealthHashConfig } from './projectConfig';\n\n\n//? Cross-env consistency check: when env A declares `fallback: B`, any env\n//? vars listed under `synchronizedEnvKeys` on their shared resource must hold\n//? identical values on both deployments — otherwise sessions encrypted by\n//? one side can't be decrypted by the other.\n//?\n//? Secrets never leave the deployment in the clear. `/_health` returns hashes\n//? of each synchronized value; the router compares hashes across envs. If\n//? hashes match, values match. If they don't, the deployments are out of sync\n//? and the router either warns or refuses to start (per `strictBootHandshake`).\n//?\n//? SEC-13: the historical hash was an UNSALTED `sha256(value)` — unauthenticated\n//? `/_health` then exposed a stable, dictionary-attackable fingerprint of each\n//? synchronized secret. `http.healthHash` now lets a deployment salt/HMAC the\n//? value; BOTH the backend (`/_health`) and the router (compare) MUST use the\n//? same `{ mode, salt }`, so {@link resolveHealthHashConfig} +\n//? {@link hashSynchronizedValueWith} are exported for the router to reuse. The\n//? 0.2.0 DEFAULT is `{ mode: 'hmac', salt: '@bootUuid' }` — the fingerprint is\n//? HMAC-keyed on the per-boot UUID, so both sides MUST resolve the salt against\n//? the SAME boot UUID (the backend's own; the router reads it from /_health).\n\nconst plainHash = (value: string): string =>\n createHash('sha256').update(value).digest('hex');\n\n/**\n * Hash a synchronized value under an EXPLICIT `{ mode, salt }` (SEC-13). The\n * shared primitive the router calls with the same config the backend used, so\n * the cross-env compare still holds:\n * - `'plain'` → `sha256(value)` (today's behavior; `salt` ignored).\n * - `'salted'` → `sha256(salt + value)`.\n * - `'hmac'` → `HMAC-SHA256(key=salt, value)`.\n * A `'salted'`/`'hmac'` mode with an empty resolved salt falls back to `'plain'`\n * (and is the caller's responsibility to avoid) rather than silently hashing\n * with an empty key.\n */\nexport const hashSynchronizedValueWith = (\n config: Pick<HealthHashConfig, 'mode'> & { salt: string },\n value: string,\n): string => {\n const { mode, salt } = config;\n if (mode === 'plain' || salt.length === 0) return plainHash(value);\n if (mode === 'salted') return createHash('sha256').update(salt).update(value).digest('hex');\n return createHmac('sha256', salt).update(value).digest('hex');\n};\n\n/**\n * Resolve the effective health-hash config from `http.healthHash`, expanding the\n * `'@bootUuid'` salt sentinel to the supplied boot UUID. Honoured for BOTH\n * `'salted'` and `'hmac'` (the 0.2.0 default is `hmac` + `'@bootUuid'`, so the\n * per-boot UUID becomes the HMAC key — the synchronized-env fingerprint rotates\n * every restart instead of being a stable, dictionary-attackable `sha256`). The\n * server passes its current boot UUID; the router, comparing, passes the same\n * value it read from the backend's handshake. When the sentinel is in play but\n * no boot UUID is available the config collapses to `'plain'` so the cross-env\n * compare never silently diverges.\n */\nexport const resolveHealthHashConfig = (\n bootUuid?: string | null,\n): { mode: HealthHashConfig['mode']; salt: string } => {\n const { mode, salt } = getProjectConfig().http.healthHash;\n if (salt === '@bootUuid') {\n if ((mode === 'salted' || mode === 'hmac') && bootUuid && bootUuid.length > 0) {\n return { mode, salt: bootUuid };\n }\n return { mode: 'plain', salt: '' };\n }\n return { mode, salt };\n};\n\n/**\n * Safe-to-expose descriptor of the resolved health-hash config. The router needs\n * to hash with the SAME `{ mode, salt }` the backend used, but a static `salt`\n * (mode `'salted'`/`'hmac'` with a non-sentinel salt) is a SECRET and must never\n * leave the backend. So `/_health` exposes only the `mode` + whether the salt is\n * the `'@bootUuid'` sentinel (the bootUuid itself is already in the response).\n * From this the router can reproduce the hash for `'plain'` and `'@bootUuid'`\n * configs, and KNOWS it cannot reproduce a static-salt config (so it skips the\n * compare instead of reporting a false drift).\n */\nexport interface HealthHashDescriptor {\n mode: HealthHashConfig['mode'];\n bootUuidSalt: boolean;\n}\n\n/** Describe the active health-hash config for the `/_health` payload (no secret salt). */\nexport const describeHealthHashConfig = (): HealthHashDescriptor => {\n const { mode, salt } = getProjectConfig().http.healthHash;\n return { mode, bootUuidSalt: salt === '@bootUuid' };\n};\n\n/**\n * Reproduce the backend's resolved `{ mode, salt }` from its `/_health`\n * descriptor + the shared boot UUID. Returns `null` when the backend uses a\n * STATIC salt the router cannot see — the caller must then skip the compare\n * rather than hash with a different (default) config and report a false drift.\n */\nexport const resolveHealthHashConfigFromDescriptor = (\n descriptor: HealthHashDescriptor,\n bootUuid: string,\n): { mode: HealthHashConfig['mode']; salt: string } | null => {\n if (descriptor.mode === 'plain') return { mode: 'plain', salt: '' };\n if (descriptor.bootUuidSalt) return { mode: descriptor.mode, salt: bootUuid };\n return null;\n};\n\nexport const collectSynchronizedEnvKeys = (): string[] => {\n const keys = new Set<string>();\n for (const resource of Object.values(getDeployConfig().resources)) {\n for (const key of resource.synchronizedEnvKeys ?? []) {\n keys.add(key);\n }\n }\n return [...keys].toSorted();\n};\n\n/**\n * Compute the `/_health` hash map for every synchronized env var. Reads\n * `http.healthHash` (call-time) so a consumer's salt/HMAC opt-in applies;\n * `bootUuid` is consulted only when the salt is the `'@bootUuid'` sentinel.\n * The 0.2.0 DEFAULT (`{ mode: 'hmac', salt: '@bootUuid' }`) HMAC-keys the output\n * on `bootUuid`; pass `mode: 'plain'` to restore the historical unsalted output.\n */\nexport const computeSynchronizedEnvHashes = (\n bootUuid?: string | null,\n): Record<string, string | null> => {\n const keys = collectSynchronizedEnvKeys();\n const config = resolveHealthHashConfig(bootUuid);\n const out: Record<string, string | null> = {};\n for (const key of keys) {\n const value = process.env[key];\n out[key] = value === undefined ? null : hashSynchronizedValueWith(config, value);\n }\n return out;\n};\n\n//? Router-side helper: hash a single value with the same algorithm the backend's\n//? /_health uses. Kept as a named helper distinct from the backend-only collector.\n//? Honours the active `http.healthHash` config (call-time) so router + backend\n//? stay in lockstep; pass the backend's boot UUID when the salt is `'@bootUuid'`.\nexport const hashSynchronizedValue = (value: string, bootUuid?: string | null): string =>\n hashSynchronizedValueWith(resolveHealthHashConfig(bootUuid), value);","const COLORS: Record<string, string> = {\n black: \"\\u001B[30m\",\n red: \"\\u001B[31m\",\n green: \"\\u001B[32m\",\n yellow: \"\\u001B[33m\",\n blue: \"\\u001B[34m\",\n magenta: \"\\u001B[35m\",\n cyan: \"\\u001B[36m\",\n white: \"\\u001B[37m\",\n reset: \"\\u001B[0m\",\n};\n\n//? Strip both POSIX `/` and Windows `\\` separators so the file:line tag is\n//? consistent across platforms. Drop the column suffix too — `:42:7` becomes\n//? `:42`. If the regex finds no separator at all, fall back to the bare\n//? string (avoids leaking the full filesystem path).\nconst FILE_NAME_REGEX = /[\\\\/]([^\\\\/]+)$/;\n\nconst extractFrameLabel = (frame: string): string => {\n let lineInfo = frame.slice(frame.indexOf(\"(\") + 1, frame.lastIndexOf(\")\"));\n if (lineInfo === \"\") lineInfo = frame;\n const match = FILE_NAME_REGEX.exec(lineInfo);\n const fileName = match?.[1] ?? lineInfo;\n return fileName.replace(/:\\d+(?::\\d+)?$/, \"\");\n};\n\nexport const initConsolelog = () => {\n const originalLog = console.log;\n console.log = (...args: unknown[]) => {\n const stackLines = new Error('console.log trace').stack?.split(\"\\n\") ?? [];\n //? Search for the first frame outside this module so wrapper functions\n //? (e.g. `getLogger().info` → `console.log`) don't pin the label to the\n //? wrapper. Frame 0 is the Error itself; skip it.\n const frame = stackLines.slice(1).find((line) => {\n const trimmed = line.trim();\n return trimmed.length > 0 && !trimmed.includes('consoleLog.ts') && !trimmed.includes('loggerRegistry.ts');\n })?.trim();\n\n if (!frame) { originalLog(...args); return; }\n\n const extractedInfo = extractFrameLabel(frame);\n\n //? Color keyword strip — ONLY the trailing argument (the documented\n //? contract: `console.log('hello', 'red')`). Scanning all args meant a\n //? legitimate value of `\"red\"` anywhere in the call was silently dropped.\n let colorCode = COLORS.white;\n const lastArg = args.at(-1);\n if (typeof lastArg === 'string' && Object.prototype.hasOwnProperty.call(COLORS, lastArg)) {\n colorCode = COLORS[lastArg];\n args.pop();\n }\n\n // handle object vs text\n if (typeof args[0] === \"object\") {\n originalLog(`${colorCode}${extractedInfo}${COLORS.reset}`);\n originalLog(...args);\n } else {\n originalLog(`${colorCode}${extractedInfo} -- ${args.join(\" \")}${COLORS.reset}`);\n }\n };\n}","const escapeRegExp = (value: string): string => {\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`);\n};\n\nexport const getCookieValue = (cookieHeader: string | undefined, cookieName: string): string | null => {\n if (!cookieHeader || !cookieName) {\n return null;\n }\n\n const safeCookieName = escapeRegExp(cookieName);\n const cookieRegex = new RegExp(String.raw`(?:^|;\\s*)${safeCookieName}=([^;]*)`);\n const match = cookieHeader.match(cookieRegex);\n const rawValue = match?.[1];\n\n if (!rawValue) {\n return null;\n }\n\n try {\n return decodeURIComponent(rawValue);\n } catch {\n //? CORE-O21: returning the raw bytes on decode failure is by-design.\n //? A lone `%` or malformed percent-sequence is unusual but not grounds to\n //? treat the cookie as absent — the caller receives the exact bytes the\n //? browser sent and can decide whether to accept or reject them.\n return rawValue;\n }\n};\n\nexport const hasCookie = (cookieHeader: string | undefined, cookieName: string): boolean => {\n return getCookieValue(cookieHeader, cookieName) !== null;\n};\n\n/** Attributes a cookie-builder must honour, after applying any name prefix. */\nexport interface CookiePrefixConstraints {\n /** Effective cookie name (prefix prepended when applicable). */\n name: string;\n /** Whether `Secure` must be set (forced true by either prefix). */\n secure: boolean;\n /** Whether `Path=/` is forced (true under `__Host-`). */\n forcePathRoot: boolean;\n /** Whether a `Domain` attribute is FORBIDDEN (true under `__Host-`). */\n forbidDomain: boolean;\n}\n\n/**\n * Compute the attribute constraints a `__Host-` / `__Secure-` cookie-name prefix\n * imposes (CORE-10/39), so the server's session + CSRF cookie builders apply the\n * browser-enforced rules consistently:\n *\n * - `'__Host-'` → name prefixed, `Secure` forced, `Path=/` forced, `Domain` forbidden.\n * - `'__Secure-'` → name prefixed, `Secure` forced.\n * - `undefined` → name unchanged, no forced attributes (today's behavior).\n *\n * Pure + deterministic — the builder still owns serialization; this only\n * resolves the constraints so server and any other cookie-site can't drift.\n * `secureOverride` lets the caller force `Secure` independent of a prefix\n * (mirrors `http.sessionCookieSecure`).\n */\nexport const applyCookiePrefixConstraints = (\n baseName: string,\n prefix: '__Host-' | '__Secure-' | undefined,\n secureOverride?: boolean,\n): CookiePrefixConstraints => {\n if (prefix === '__Host-') {\n return { name: `__Host-${baseName}`, secure: true, forcePathRoot: true, forbidDomain: true };\n }\n if (prefix === '__Secure-') {\n return { name: `__Secure-${baseName}`, secure: true, forcePathRoot: false, forbidDomain: false };\n }\n return { name: baseName, secure: secureOverride ?? false, forcePathRoot: false, forbidDomain: false };\n};\n","/* eslint-disable @typescript-eslint/no-unnecessary-condition */\n\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { getProjectConfig } from './projectConfig';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst isWorkspaceRoot = (candidate: string): boolean => {\n\treturn (\n\t\tfs.existsSync(path.join(candidate, 'package.json'))\n\t\t&& (\n\t\t\tfs.existsSync(path.join(candidate, 'tsconfig.json'))\n\t\t\t|| fs.existsSync(path.join(candidate, 'tsconfig.server.json'))\n\t\t)\n\t);\n};\n\nconst findWorkspaceRoot = (startDir: string): string | null => {\n\tlet current = path.resolve(startDir);\n\n\twhile (true) {\n\t\tif (isWorkspaceRoot(current)) {\n\t\t\treturn current;\n\t\t}\n\n\t\tconst parent = path.dirname(current);\n\t\tif (parent === current) return null;\n\t\tcurrent = parent;\n\t}\n};\n\nexport const ROOT_DIR =\n\tfindWorkspaceRoot(process.cwd())\n\t?? findWorkspaceRoot(__dirname)\n\t?? process.cwd();\n\nconst resolveAgainstRoot = (relative: string): string =>\n\tpath.isAbsolute(relative) ? relative : path.join(ROOT_DIR, relative);\n\n//? Lazy getters: read from the registered ProjectConfig at *call time* so a\n//? consumer's `registerProjectConfig({ paths: { srcDir: 'app/src' } })` wins\n//? even if it ran after this module was imported.\nexport const getSrcDir = (): string => resolveAgainstRoot(getProjectConfig().paths.srcDir);\nexport const getServerDir = (): string => resolveAgainstRoot(getProjectConfig().paths.serverDir);\nexport const getSharedDir = (): string => resolveAgainstRoot(getProjectConfig().paths.sharedDir);\nexport const getUploadsDir = (): string => resolveAgainstRoot(getProjectConfig().paths.uploadsDir);\nexport const getPublicDir = (): string => resolveAgainstRoot(getProjectConfig().paths.publicDir);\n//? Returns the list of directories the function-injection system walks\n//? at codegen + runtime. Default `['functions', 'shared']`. Consumers can\n//? override via `registerProjectConfig({ paths: { serverFunctionDirs: [...] } })`.\n//?\n//? Backwards-compat: if a consumer still sets the deprecated singular\n//? `serverFunctionsDir: string`, we honor it as a single-entry list. Once\n//? both are set, `serverFunctionDirs` wins.\nexport const getServerFunctionDirs = (): string[] => {\n\tconst paths = getProjectConfig().paths;\n\tconst arrayConfig = paths.serverFunctionDirs;\n\tif (Array.isArray(arrayConfig) && arrayConfig.length > 0) {\n\t\treturn arrayConfig.map((p) => resolveAgainstRoot(p));\n\t}\n\t// eslint-disable-next-line @typescript-eslint/no-deprecated -- BC shim: the legacy singular field is still honored when set\n\tconst legacySingular = paths.serverFunctionsDir;\n\tif (typeof legacySingular === 'string' && legacySingular.length > 0) {\n\t\treturn [resolveAgainstRoot(legacySingular)];\n\t}\n\treturn [];\n};\n\n/**\n * @deprecated Use `getServerFunctionDirs()` instead. Returns the first\n * directory from the array, or `'functions'` resolved against the workspace\n * root when no directories are configured.\n */\nexport const getServerFunctionsDir = (): string => {\n\tconst dirs = getServerFunctionDirs();\n\treturn dirs[0] ?? resolveAgainstRoot('functions');\n};\n\nexport const getGeneratedSocketTypesPath = (): string =>\n\tresolveAgainstRoot(getProjectConfig().paths.generatedSocketTypes);\nexport const getGeneratedApiSchemasPath = (): string =>\n\tresolveAgainstRoot(getProjectConfig().paths.generatedApiSchemas);\nexport const getGeneratedApiDocsPath = (): string =>\n\tresolveAgainstRoot(getProjectConfig().paths.generatedApiDocs);\n\n//? Backwards-compatible constants resolved against the framework defaults at\n//? module load. New code should use the getters above. These will continue to\n//? work for projects that don't override `paths` in `registerProjectConfig`.\n//? @deprecated use `getSrcDir()` etc. instead.\nexport const SRC_DIR = path.join(ROOT_DIR, 'src');\n/** @deprecated use `getServerDir()` instead. */\nexport const SERVER_DIR = path.join(ROOT_DIR, 'server');\n/** @deprecated use `getSharedDir()` instead. */\nexport const SHARED_DIR = path.join(ROOT_DIR, 'shared');\n\n/** @deprecated use `getUploadsDir()` instead. */\nexport const UPLOADS_DIR = path.join(ROOT_DIR, 'uploads');\n/** @deprecated use `getPublicDir()` instead. */\nexport const PUBLIC_DIR = path.join(ROOT_DIR, 'public');\n/** @deprecated use `getServerFunctionsDir()` instead. */\n// eslint-disable-next-line @typescript-eslint/no-deprecated -- BC export: depends on the deprecated SERVER_DIR\nexport const SERVER_FUNCTIONS_DIR = path.join(SERVER_DIR, 'functions');\n\n/** @deprecated use `getGeneratedSocketTypesPath()` instead. */\nexport const GENERATED_SOCKET_TYPES_PATH = path.join(SRC_DIR, '_sockets', 'apiTypes.generated.ts');\n/** @deprecated use `getGeneratedApiSchemasPath()` instead. */\nexport const GENERATED_API_SCHEMAS_PATH = path.join(SRC_DIR, '_sockets', 'apiInputSchemas.generated.ts');\n/** @deprecated use `getGeneratedApiDocsPath()` instead. */\nexport const GENERATED_API_DOCS_PATH = path.join(SRC_DIR, 'docs', 'apiDocs.generated.json');\n\nexport const TSCONFIG_ALIAS_FILES = ['tsconfig.server.json', 'tsconfig.client.json'];\n\nexport const resolveFromRoot = (...segments: string[]): string => path.join(ROOT_DIR, ...segments);\n","//? Socket.io middleware registry. Lets consumers wedge their own\n//? `io.use(...)` middlewares into the framework's socket bootstrap without\n//? forking `loadSocket.ts`. Registration order is preservation order —\n//? `applySocketMiddlewares(io)` calls each middleware in the order it was\n//? registered, which is the same contract Socket.io's native `io.use(...)`\n//? chain provides.\n//?\n//? Use cases: per-connection auth gates (JWT verification beyond the\n//? framework's session cookie), rate-limit shedding before handlers run,\n//? observability tags (Datadog span context, OpenTelemetry context),\n//? license-key checks, allow-list IP filters.\n\nimport type { Socket, Server as SocketIOServer } from 'socket.io';\n\nexport type SocketMiddleware = (socket: Socket, next: (err?: Error) => void) => void;\n\nconst middlewares: SocketMiddleware[] = [];\n\n/**\n * Register a Socket.io middleware that will be wired via `io.use(...)` when\n * the framework boots the socket server. Multiple middlewares can be\n * registered — they run in registration order before any `connect`\n * handler fires.\n */\nexport const registerSocketMiddleware = (mw: SocketMiddleware): void => {\n middlewares.push(mw);\n};\n\n/** Read the registered middleware list (read-only view). */\nexport const getSocketMiddlewares = (): readonly SocketMiddleware[] => middlewares;\n\n/** Test-only / hot-reload helper. Drops every registered middleware. */\nexport const clearSocketMiddlewares = (): void => {\n middlewares.length = 0;\n};\n\n/**\n * Wire every registered middleware into the running Socket.io server.\n * Called by `@luckystack/server`'s `loadSocket` after the `SocketIOServer`\n * is constructed and before any `connect` handler is attached, so custom\n * middlewares run before framework handlers see the socket.\n */\nexport const applySocketMiddlewares = (io: SocketIOServer): void => {\n for (const mw of middlewares) {\n io.use(mw);\n }\n};\n","import path from \"node:path\";\nimport { access } from 'node:fs/promises';\nimport fs from \"node:fs\";\nimport stream from \"node:stream\";\nimport { ServerResponse } from \"node:http\";\nimport { getUploadsDir } from './paths';\nimport { getAvatarConfig } from './avatarConfig';\nimport { getLogger } from './loggerRegistry';\nimport tryCatch from './tryCatch';\nimport { dispatchHook } from './hooks/registry';\n\n//? Belt-and-suspenders: `path.basename` already strips traversal segments,\n//? but explicitly allowlist the fileId character set so any future regression\n//? in the upstream routing (e.g. URL-decoded embedded null) can't slip\n//? through to the filesystem layer.\nconst FILE_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;\n\nexport const serveAvatar = async ({\n routePath,\n res,\n}: {\n routePath: string;\n res: ServerResponse;\n}) => {\n const uploadsFolder = getUploadsDir();\n const fileId = path.basename(routePath, path.extname(routePath));\n if (!fileId || !FILE_ID_REGEX.test(fileId)) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('File not found');\n return;\n }\n\n //? Access-control / audit seam: a `preAvatarServe` handler may veto the read\n //? (stop signal). We answer 404 — not 403 — so a private avatar's existence\n //? isn't disclosed to an unauthorized caller.\n const preDispatch = await dispatchHook('preAvatarServe', { routePath, fileId });\n if (preDispatch.stopped) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('File not found');\n return;\n }\n\n const { formats, cacheControl } = getAvatarConfig();\n\n //? Try each configured format in order; first existing file wins. This\n //? lets an installer add `png` or `jpg` for backwards compatibility while\n //? still preferring `webp` for new uploads.\n for (const { extension, contentType } of formats) {\n const filePath = path.join(uploadsFolder, `${fileId}.${extension}`);\n const [accessError] = await tryCatch(() => access(filePath));\n if (accessError) continue;\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Cache-Control': cacheControl,\n });\n //? Pipe with an error sink: once headers are flushed a read error can no\n //? longer be turned into a 5xx, so `pipeline` just tears the response down\n //? (destroys both streams) instead of letting the unhandled stream 'error'\n //? crash the worker. No double-write — we never write a body after this.\n stream.pipeline(fs.createReadStream(filePath), res, (pipelineError) => {\n if (pipelineError) {\n getLogger().debug('avatar: stream pipeline failed', { routePath, fileId, error: pipelineError });\n }\n });\n void dispatchHook('postAvatarServe', { routePath, fileId, extension, contentType });\n return;\n }\n\n getLogger().debug('avatar: file not found', { routePath, fileId });\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('File not found');\n};\n","//? Configurable avatar serving so installers don't have to fork\n//? `serveAvatar` to change the file format, cache headers, or storage path.\n//? Defaults match the framework's prior hardcoded behavior (.webp, 24h cache).\n\nimport { type DeepPartial } from './configUtils';\nimport { createRegistry } from './createRegistry';\n\nexport interface AvatarConfig {\n /**\n * File format(s) the framework will look for on disk, in order. The first\n * one that exists wins. Each entry is `{ extension, contentType }` so the\n * `Content-Type` response header matches what the file actually is.\n * Default: `[{ extension: 'webp', contentType: 'image/webp' }]`.\n */\n formats: { extension: string; contentType: string }[];\n /**\n * `Cache-Control` value sent with the avatar response.\n * Default: `'public, max-age=86400'` (24 hours).\n */\n cacheControl: string;\n}\n\nexport const DEFAULT_AVATAR_CONFIG: AvatarConfig = {\n formats: [{ extension: 'webp', contentType: 'image/webp' }],\n cacheControl: 'public, max-age=86400',\n};\n\nexport type AvatarConfigInput = DeepPartial<AvatarConfig>;\n\nconst registry = createRegistry<AvatarConfig, AvatarConfigInput>(DEFAULT_AVATAR_CONFIG, {\n //? Bespoke merge (not a generic deep-merge): an empty `formats` array falls\n //? back to the default formats rather than wiping serving. Built fresh from\n //? the defaults each call, matching the historical behaviour.\n transform: (config) => ({\n formats: config.formats?.length ? (config.formats as AvatarConfig['formats']) : DEFAULT_AVATAR_CONFIG.formats,\n cacheControl: config.cacheControl ?? DEFAULT_AVATAR_CONFIG.cacheControl,\n }),\n});\n\nexport const registerAvatarConfig = (config: AvatarConfigInput): void => {\n registry.register(config);\n};\n\nexport const getAvatarConfig = (): AvatarConfig => registry.get();\n","//? Framework-side upload helper. Wraps the `onUploadStart` / `onUploadComplete`\n//? hook dispatch around an installer-provided encode/save callback so consumer\n//? upload routes don't have to plumb the hooks themselves.\n//?\n//? Why a callback (not a built-in encoder): @luckystack/core stays free of\n//? heavy native deps (sharp, ffmpeg, etc.). Consumers wire whichever encoder\n//? fits their use case and the framework just brackets the call with the\n//? hook contract.\n\nimport { dispatchHook } from './hooks/registry';\nimport tryCatch from './tryCatch';\n\nexport interface ProcessUploadInput {\n /** User the upload is associated with. */\n userId: string;\n /** Content type from the source (e.g. parsed from a data URL prefix). */\n contentType: string;\n /** Raw upload bytes — used for `sizeBytes` and passed to the encoder. */\n buffer: Buffer;\n /** Identifier for the upload category. Defaults to `'avatar'`. */\n uploadKind?: string;\n /** Final file name written to disk (used in the `onUploadComplete` payload). */\n fileName: string;\n /**\n * Project-supplied encode + write step. Receives the raw buffer, does\n * whatever encoding/resizing the installer wants (sharp, ffmpeg, raw fs\n * write, S3 upload, etc.), and returns the final byte size on disk —\n * pass through `buffer.byteLength` if you don't have a meaningful\n * post-encode size.\n */\n encodeAndSave: (buffer: Buffer) => Promise<number>;\n}\n\nexport type ProcessUploadResult =\n | { status: 'success'; sizeBytes: number }\n | { status: 'rejected'; errorCode: string }\n | { status: 'error'; reason: string; cause?: unknown };\n\n/**\n * Run a project-side upload through the framework's hook contract.\n *\n * Order of operations:\n * 1. Dispatch `onUploadStart` — handlers may return a stop signal to reject\n * the upload (content moderation, virus scanner, quota check).\n * 2. Run the project-supplied `encodeAndSave` callback (wrapped in tryCatch).\n * 3. Dispatch `onUploadComplete` with the final `sizeBytes`.\n *\n * Returns one of three statuses so the caller can map to its own response\n * envelope without inspecting hook internals.\n */\nexport const processUpload = async (input: ProcessUploadInput): Promise<ProcessUploadResult> => {\n const uploadKind = input.uploadKind ?? 'avatar';\n\n const startSignal = await dispatchHook('onUploadStart', {\n userId: input.userId,\n contentType: input.contentType,\n sizeBytes: input.buffer.byteLength,\n uploadKind,\n });\n if (startSignal.stopped) {\n return { status: 'rejected', errorCode: startSignal.signal.errorCode };\n }\n\n const [error, finalSize] = await tryCatch(() => input.encodeAndSave(input.buffer));\n if (error) {\n return { status: 'error', reason: error.message || 'encode-failed', cause: error };\n }\n\n const sizeBytes = finalSize ?? input.buffer.byteLength;\n await dispatchHook('onUploadComplete', {\n userId: input.userId,\n fileName: input.fileName,\n sizeBytes,\n uploadKind,\n });\n\n return { status: 'success', sizeBytes };\n};\n","//? @adr 0031\n//? Server bind-address registry. The `createLuckyStackServer` bootstrap\n//? populates this with the actual listen `ip`/`port` so framework code that\n//? needs the bind address (e.g. `checkOrigin` building the same-origin\n//? entry) doesn't drift when the consumer used the `options.ip`/`options.port`\n//? arguments instead.\n//?\n//? `registerBindAddress` stores the intended address before `listen`;\n//? `registerBoundAddress` then records the address reported by node:http. Keeping\n//? both values lets readers see reality while OAuth rewrites only a stale direct\n//? callback that still names the intended pre-hop port — never an explicit local\n//? router/reverse-proxy ingress.\n//?\n//? Resolution at call time:\n//? 1. registered current value (intended before listen, bound after success)\n//? 2. `process.env.SERVER_IP` for the bind address\n//? 3. `'127.0.0.1'` / port `'80'` as the generic pre-bootstrap fallback\n\nimport { isProductionRuntime } from './env';\n\ninterface BindAddress {\n ip: string;\n port: number;\n}\n\ninterface IntendedBindAddress extends BindAddress {\n configuredPort?: number;\n}\n\nconst DEFAULT_SERVER_PORT = '80';\n\nlet intended: BindAddress | null = null;\nlet configuredPort: number | null = null;\nlet registered: BindAddress | null = null;\n\nexport const registerBindAddress = (address: IntendedBindAddress): void => {\n intended = { ip: address.ip, port: address.port };\n configuredPort = address.configuredPort ?? null;\n registered = intended;\n};\n\nexport const registerBoundAddress = (address: BindAddress): void => {\n intended ??= address;\n registered = address;\n};\n\nexport const getBindAddress = (): { ip: string; port: string } => {\n if (registered) {\n return { ip: registered.ip, port: String(registered.port) };\n }\n return {\n ip: process.env.SERVER_IP ?? '127.0.0.1',\n //? A real server registers its intended/defaultPort before OAuth or CORS\n //? reads this value. Port 80 only remains the generic pre-bootstrap fallback.\n port: DEFAULT_SERVER_PORT,\n };\n};\n\nconst defaultPortForProtocol = (protocol: string): string => (protocol === 'https:' ? '443' : '80');\n\n//? Make an OAuth callback URL reflect the port the server ACTUALLY bound.\n//?\n//? In dev the backend may auto-increment off its intended port (`:80` busy →\n//? `:81`), but `oauthCallbackBase` is frozen at the intended port the moment\n//? `config.ts` runs at module load. A frozen `redirect_uri` then points the OAuth\n//? round-trip at a dead port. This rewrites ONLY a loopback callback that still\n//? names the intended pre-listen port. An explicit localhost router/reverse proxy\n//? on another port remains authoritative. `localhost`, `127.0.0.1`, and IPv6\n//? `[::1]` use the same loopback policy as CORS.\n//?\n//? SAFETY — authorize and token-exchange must send a BYTE-IDENTICAL `redirect_uri`\n//? (OAuth requires it). Both derive from this helper, and `getBindAddress()` is\n//? constant for the process lifetime, so the two calls agree. It is NOT\n//? request-derived (never attacker-influenced), so it needs no per-state storage.\n//?\n//? Prod is a no-op: no hop, and the public callback domain has no localhost port\n//? to chase. A non-localhost base (remote dev backend) is returned untouched.\n//? Default ports collapse to the empty string so the emitted URL stays\n//? byte-stable with how a provider redirect_uri is normally registered\n//? (`http://localhost/...`, not `http://localhost:80/...`).\nexport const resolveDevCallbackUrl = (callbackUrl: string): string => {\n if (isProductionRuntime()) return callbackUrl;\n\n let url: URL;\n try {\n url = new URL(callbackUrl);\n } catch {\n return callbackUrl;\n }\n\n if (!['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) return callbackUrl;\n\n const boundPort = getBindAddress().port;\n const intendedPort = intended ? String(intended.port) : '';\n if (!boundPort || !intendedPort) return callbackUrl;\n\n const currentPort = url.port || defaultPortForProtocol(url.protocol);\n const matchesIntendedPort = currentPort === intendedPort;\n const matchesConfiguredPort = configuredPort !== null && currentPort === String(configuredPort);\n if (currentPort === boundPort || (!matchesIntendedPort && !matchesConfiguredPort)) return callbackUrl;\n\n url.port = boundPort === defaultPortForProtocol(url.protocol) ? '' : boundPort;\n return url.toString();\n};\n","import { IncomingMessage, ServerResponse } from \"node:http\";\nimport tryCatch from \"./tryCatch\";\nimport { getProjectConfig } from \"./projectConfig\";\n\ninterface getParamsType {\n method: string;\n req: IncomingMessage;\n res: ServerResponse;\n queryString?: string;\n}\n\n//? Shared error-response shape for the body parser's 413/400/415 branches.\n//? Writes a JSON error envelope and resolves the outer promise with `null`\n//? (the parser's \"no usable body\" signal). Extracted so the duplicated\n//? \"set header + writeHead + end(JSON) + resolve(null)\" blocks live once.\nconst writeJsonErrorAndResolve = (\n res: ServerResponse,\n status: number,\n errorCode: string,\n resolve: (value: Record<string, unknown> | null) => void,\n): void => {\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(status);\n res.end(JSON.stringify({\n status: 'error',\n httpStatus: status,\n message: errorCode,\n errorCode,\n }));\n resolve(null);\n};\n\nexport default async function getParams({ method, req, res: _res, queryString }: getParamsType): Promise<Record<string, unknown> | null> {\n\n if (method === \"GET\") {\n //? if get request we return the query string as an object\n return Object.fromEntries(new URLSearchParams(queryString ?? ''));\n }\n\n //? if a POST, PUT or DELETE method we return the body as an object\n return new Promise((resolve) => {\n const contentType = req.headers['content-type'] ?? '';\n const contentLengthHeader = req.headers['content-length'];\n const declaredLength = typeof contentLengthHeader === 'string' ? Number(contentLengthHeader) : Number.NaN;\n const maxBodyBytes = getProjectConfig().http.requestBodyMaxBytes;\n\n if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {\n writeJsonErrorAndResolve(_res, 413, 'api.payloadTooLarge', resolve);\n //? Destroy the socket immediately after writing the 413 so the client\n //? can't park a slow body and tie up the connection (marginal slow-loris\n //? vector). The response was already flushed by `writeJsonErrorAndResolve`\n //? so destroying here only stops incoming data, not the outbound 413.\n req.destroy();\n return;\n }\n\n //? Collect raw Buffer chunks and decode once at `end` — concatenating\n //? `chunk.toString()` per chunk corrupts multi-byte UTF-8 sequences that\n //? straddle a TCP chunk boundary.\n const chunks: Buffer[] = [];\n let bodySize = 0;\n req.on('data', (chunk: Buffer) => {\n //? Guard against already-responded: Node can deliver buffered 'data' events\n //? even after `req.destroy()` is called (the event was already queued). A\n //? second call into `writeJsonErrorAndResolve` on an ended response throws\n //? ERR_HTTP_HEADERS_SENT. The `resolve` idempotency is fine; the response\n //? write is not.\n if (_res.headersSent) return;\n\n bodySize += chunk.length;\n if (bodySize > maxBodyBytes) {\n //? Write the 413 BEFORE tearing down the socket. `req` and `res` share one\n //? TCP socket, so destroying `req` first kills the connection before the\n //? 413 body is flushed and the client gets an empty/RST response instead.\n //? `writeJsonErrorAndResolve` sets `res.writableEnded`, which short-circuits\n //? the caller, so the destroy only needs to stop further inbound bytes.\n writeJsonErrorAndResolve(_res, 413, 'api.payloadTooLarge', resolve);\n req.destroy();\n return;\n }\n\n chunks.push(chunk);\n });\n\n req.on('end', () => { void (async () => {\n const body = Buffer.concat(chunks).toString('utf8');\n\n //? here we parse the data depending on the content type\n //? if the content type is application/x-www-form-urlencoded we parse the data as a URLSearchParams object\n if (contentType.startsWith('application/x-www-form-urlencoded')) {\n const parseData = () => {\n const data = new URLSearchParams(body);\n return Object.fromEntries(data);\n };\n const [, response] = await tryCatch(parseData);\n if (response) {\n resolve(response); return;\n }\n\n writeJsonErrorAndResolve(_res, 400, 'api.invalidRequestFormat', resolve);\n return;\n }\n\n //? if the content type is application/json we parse the data as a JSON object\n if (contentType.startsWith('application/json')) {\n const parseData = (): unknown => {\n return JSON.parse(body || '{}');\n };\n const [, response] = await tryCatch(parseData);\n\n //? Reject array/scalar/null bodies — only plain objects are accepted.\n //? An attacker sending `[1,2,3]` or `42` could surface odd shapes in\n //? handlers that didn't expect them.\n if (response && typeof response === 'object' && !Array.isArray(response)) {\n resolve(response as Record<string, unknown>); return;\n }\n\n writeJsonErrorAndResolve(_res, 400, 'api.invalidRequestFormat', resolve);\n return;\n }\n\n //? Unknown content-type: don't attempt to interpret. Empty content-type\n //? bodies in particular were previously parsed as `{ body }` which let\n //? handlers mistake raw text for a typed payload.\n writeJsonErrorAndResolve(_res, 415, 'api.unsupportedMediaType', resolve);\n })(); });\n\n //? Request-stream error (e.g. a client RST mid-body on POST/PUT/DELETE).\n //? Mirror the SSE `markClosed` pattern: a broken request stream must NOT\n //? reject — a rejected promise here is voided by the http handler\n //? (`void handleHttpRequest(...)`) and becomes an unhandled rejection that\n //? crashes the worker. The connection is already gone, so we can't write a\n //? 4xx; resolve with `null` (the parser's \"no usable body\" signal) exactly\n //? like the 413/400/415 branches, letting the handler short-circuit\n //? cleanly. `resolve` after a prior settle is a no-op, so racing with\n //? `end`/the 413 path is safe.\n req.on('error', () => {\n resolve(null);\n });\n });\n}\n","import { Socket } from 'socket.io';\nimport { getProjectConfig } from './projectConfig';\nimport { getCookieValue } from './cookies';\n\n/**\n * Extract the authentication token from a Socket.io connection.\n *\n * Checks `config.sessionBasedToken` to determine where to look:\n * - `true`: Token is in `socket.handshake.auth.token` (sessionStorage on client)\n * - `false`: Token is in cookies via `socket.handshake.headers.cookie`\n *\n * @param socket - The Socket.io socket instance\n * @returns The token string or null if not found\n */\n//? `Pick<Socket, 'handshake'>` (not `Socket`) so this also accepts a\n//? `RemoteSocket` from `io.fetchSockets()` — the cross-instance fan-out path\n//? only has RemoteSockets, which expose `handshake` but not the full Socket API.\nexport const extractTokenFromSocket = (socket: Pick<Socket, 'handshake'>): string | null => {\n const config = getProjectConfig();\n const cookie = socket.handshake.headers.cookie;\n //? socket.io always initializes `handshake.auth` to at least `{}` (RemoteSockets included).\n const sessionToken = typeof socket.handshake.auth.token === 'string'\n ? socket.handshake.auth.token\n : null;\n const cookieToken = getCookieValue(cookie, config.http.sessionCookieName);\n\n // Session-based token (stored in sessionStorage on client)\n if (config.session.basedToken) {\n return sessionToken ?? cookieToken;\n }\n\n // Cookie-mode (CORE-O10): only accept the handshake-auth token fallback when\n // explicitly opted in, otherwise a stolen token supplied via\n // `handshake.auth.token` would defeat the cookie/CSRF model.\n if (config.http.acceptBearerInCookieMode) {\n return cookieToken ?? sessionToken;\n }\n\n return cookieToken;\n};\n","import { IncomingMessage } from 'node:http';\nimport { getProjectConfig } from './projectConfig';\nimport { getCookieValue } from './cookies';\n\n/**\n * Extract the authentication token from an HTTP request.\n *\n * Supports tokens from:\n * - `Authorization: Bearer <token>` header\n * - the configured session cookie\n *\n * @param req - The HTTP incoming message\n * @returns The token string or null if not found\n */\nexport const extractTokenFromRequest = (req: IncomingMessage): string | null => {\n // Node types `authorization` as `string`, but duplicate headers can arrive as\n // an array at runtime via the IncomingHttpHeaders index signature. Treat the\n // value as unknown and narrow with typeof guards so a single string is used.\n const rawAuthHeader: unknown = req.headers.authorization;\n const authHeader = typeof rawAuthHeader === 'string'\n ? rawAuthHeader\n : (Array.isArray(rawAuthHeader) && typeof rawAuthHeader[0] === 'string' ? rawAuthHeader[0] : undefined);\n const bearerToken = authHeader?.startsWith('Bearer ')\n ? authHeader.slice(7)\n : null;\n\n const config = getProjectConfig();\n const cookieToken = getCookieValue(req.headers.cookie, config.http.sessionCookieName);\n\n // Token-mode: bearer is canonical, cookie is the fallback.\n if (config.session.basedToken) {\n return bearerToken ?? cookieToken;\n }\n\n // Cookie-mode (CORE-O10): only accept the bearer fallback when explicitly\n // opted in, otherwise a stolen token replayed via `Authorization: Bearer`\n // would defeat the cookie/CSRF model.\n if (config.http.acceptBearerInCookieMode) {\n return cookieToken ?? bearerToken;\n }\n\n return cookieToken;\n};\n","import { createHash } from 'node:crypto';\nimport type { IncomingHttpHeaders } from 'node:http';\n\nimport { getLogger } from './loggerRegistry';\n\n/**\n * Stable sentinel returned when no usable client address could be resolved.\n * Kept as a single exported constant so every rate-limit key-site funnels\n * missing addresses into one deterministic bucket instead of ad-hoc literals.\n */\nexport const UNKNOWN_CLIENT_IP = 'unknown';\n\nexport interface ResolveClientIpParams {\n /**\n * The raw transport peer address — `socket.handshake.address` (Socket.io)\n * or `req.socket.remoteAddress` (raw HTTP). May be `undefined`/`null` when\n * the connection has already been torn down.\n */\n rawAddress: string | null | undefined;\n /** Request / handshake headers. Only consulted when `trustProxy` is true. */\n headers: IncomingHttpHeaders;\n /**\n * Whether a known reverse proxy sits in front of this server. DEFAULT false.\n * When false the raw peer address is returned verbatim (only IPv4-mapped\n * IPv6 canonicalization is applied), preserving historical behaviour. Only\n * enable when a trusted proxy populates `X-Forwarded-For` / `X-Real-IP`,\n * otherwise a client can spoof its own IP via those headers.\n */\n trustProxy?: boolean;\n /**\n * Number of proxy hops to skip from the RIGHT of `X-Forwarded-For` when\n * `trustProxy` is on (CORE-O3). The rightmost entries are appended by your own\n * trusted proxies; the resolved client IP is the entry that many hops in from\n * the end. The leftmost hop is client-controlled and is never trusted. DEFAULT\n * 1 (the immediate upstream proxy). Clamped to the list length so an over-large\n * count falls back to the leftmost trusted hop rather than the spoofable entry.\n */\n trustedProxyHopCount?: number;\n}\n\n/**\n * Canonicalize an address so variant spellings of the same host collapse into\n * one rate-limit bucket (M-7):\n * - strip the IPv4-mapped IPv6 prefix (`::ffff:1.2.3.4` -> `1.2.3.4`);\n * - drop an IPv6 zone id (`fe80::1%eth0` -> `fe80::1`);\n * - lowercase IPv6 (case-insensitive hex) so `2001:DB8::1` == `2001:db8::1`.\n *\n * IPv4 + hostnames are returned untouched. NOTE: this is the lightweight,\n * dependency-free normalization — it does NOT expand/compress IPv6 (`2001:db8::1`\n * vs `2001:0db8:0:0:0:0:0:1`); use a dedicated `ip-address` parser if that\n * edge-case bucket-spreading matters for your threat model.\n */\nconst canonicalizeIp = (value: string): string => {\n let ip = value.startsWith('::ffff:') ? value.slice('::ffff:'.length) : value;\n const zoneIdx = ip.indexOf('%');\n if (zoneIdx !== -1) ip = ip.slice(0, zoneIdx);\n return ip.includes(':') ? ip.toLowerCase() : ip;\n};\n\n/**\n * Normalize a single header value (string | string[] | undefined) to its\n * first non-empty string, trimmed. `X-Forwarded-For` may legitimately arrive\n * as a comma-joined list (\"client, proxy1, proxy2\") OR as a repeated header\n * (string[]); both are handled.\n */\nconst firstHeaderValue = (raw: string | string[] | undefined): string | undefined => {\n if (raw === undefined) return undefined;\n const single = Array.isArray(raw) ? raw[0] : raw;\n if (typeof single !== 'string') return undefined;\n const trimmed = single.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\n/**\n * Flatten an `X-Forwarded-For` header into its ordered list of non-empty,\n * trimmed hops. Handles both the comma-joined list form (\"client, proxy1\") and\n * the repeated-header (string[]) form, and a mix of the two. Order is preserved:\n * leftmost (client-controlled) first, rightmost (closest trusted proxy) last.\n */\nconst forwardedForHops = (raw: string | string[] | undefined): string[] => {\n if (raw === undefined) return [];\n const parts = Array.isArray(raw) ? raw : [raw];\n return parts\n .flatMap((part) => (typeof part === 'string' ? part.split(',') : []))\n .map((hop) => hop.trim())\n .filter((hop) => hop.length > 0);\n};\n\n/**\n * Resolve the real client IP for per-IP rate-limit keying.\n *\n * - `trustProxy === false` (default): returns the raw peer address with only\n * IPv4-mapped IPv6 canonicalization applied; falls back to\n * {@link UNKNOWN_CLIENT_IP} when the raw address is missing. This preserves\n * the framework's historical behaviour (`address ?? 'unknown'`) byte-for-byte\n * for non-mapped addresses.\n * - `trustProxy === true`: skips `trustedProxyHopCount` hops from the RIGHT of\n * `X-Forwarded-For` (the rightmost entries are appended by YOUR trusted\n * proxies), then `X-Real-IP`, and only then the raw peer address.\n *\n * CORE-O3: the leftmost `X-Forwarded-For` hop is CLIENT-CONTROLLED and is never\n * trusted (it enables per-IP rate-limit evasion + audit-IP spoofing). With the\n * default `trustedProxyHopCount: 1` and a single trusted proxy, the resolved IP\n * is the immediate upstream peer (the rightmost real hop).\n */\nconst resolveRaw = ({ rawAddress, headers, trustProxy, trustedProxyHopCount }: ResolveClientIpParams & { trustProxy: boolean; trustedProxyHopCount: number }): string => {\n const rawFallback = rawAddress && rawAddress.length > 0\n ? canonicalizeIp(rawAddress)\n : UNKNOWN_CLIENT_IP;\n\n if (!trustProxy) return rawFallback;\n\n const hops = forwardedForHops(headers['x-forwarded-for']);\n if (hops.length > 0) {\n //? Count from the RIGHT — the rightmost entry is the one your own trusted\n //? proxy appended. Clamp so an over-large count lands on the leftmost\n //? available trusted hop instead of underflowing past the start. A configured\n //? value < 1 (e.g. 0) is also clamped UP to 1 — the leftmost, client-controlled\n //? hop is never selectable by design (CORE-O3), so `0` cannot mean \"trust the\n //? whole XFF\". Document this on `http.trustedProxyHopCount`.\n const skip = Math.max(1, Math.floor(trustedProxyHopCount));\n const index = Math.max(0, hops.length - skip);\n const chosen = hops[index];\n if (chosen && chosen.length > 0) return canonicalizeIp(chosen);\n }\n\n const realIp = firstHeaderValue(headers['x-real-ip']);\n if (realIp) return canonicalizeIp(realIp);\n\n return rawFallback;\n};\n\n/**\n * Whether a resolved client IP is a loopback / unresolved address — used by the\n * api/sync transports to honour `rateLimiting.skipLoopbackInDev` (api F5/F11).\n * Matches IPv4 loopback (`127.0.0.0/8`), IPv6 loopback (`::1`), the IPv4-mapped\n * form (`::ffff:127.0.0.1`, already canonicalized away by {@link resolveClientIp}),\n * and the {@link UNKNOWN_CLIENT_IP} sentinel. Pass an already-resolved IP.\n */\nexport const isLoopbackIp = (ip: string): boolean => {\n if (ip === UNKNOWN_CLIENT_IP) return true;\n const canonical = ip.startsWith('::ffff:') ? ip.slice('::ffff:'.length) : ip;\n if (canonical === '::1') return true;\n return /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(canonical);\n};\n\nconst TOKEN_HASH_LENGTH = 32;\n\n/**\n * Derive the deterministic, non-reversible bucket component for a session\n * token. The same token always yields the same value (bucket identity is\n * preserved) while the raw token never appears in the returned string.\n * Used by api AND sync rate-limit key builders — lives in core so both\n * packages can share one implementation without a cross-package dependency.\n */\nexport const deriveTokenBucketId = (token: string): string =>\n createHash('sha256').update(token).digest('hex').slice(0, TOKEN_HASH_LENGTH);\n\n//? Re-armable cooldown latch for the UNKNOWN_CLIENT_IP warning so a\n//? misconfigured deployment (e.g. `trustProxy: false` behind a reverse proxy)\n//? does not flood the log sink — one log entry per 60-second window. The first\n//? occurrence is always emitted, then suppressed until the window resets.\nconst UNKNOWN_IP_WARN_COOLDOWN_MS = 60_000;\nlet unknownIpLastWarnedAt = 0;\n\nexport const resolveClientIp = ({ rawAddress, headers, trustProxy = false, trustedProxyHopCount = 1 }: ResolveClientIpParams): string => {\n const resolved = resolveRaw({ rawAddress, headers, trustProxy, trustedProxyHopCount });\n if (resolved === UNKNOWN_CLIENT_IP) {\n //? M-8 — surface the shared fallback bucket. A burst of these usually means\n //? a reverse proxy is in front but `trustProxy` is off (every request then\n //? collapses here), or the connection was torn down before keying.\n //? Rate-suppressed to one log entry per 60 s to avoid log-sink flooding.\n const now = Date.now();\n if (now - unknownIpLastWarnedAt >= UNKNOWN_IP_WARN_COOLDOWN_MS) {\n unknownIpLastWarnedAt = now;\n getLogger().warn('rate-limit: client IP unresolved — keyed into the shared \"unknown\" bucket', { trustProxy });\n }\n }\n return resolved;\n};\n","//? Session-provider registry — the decoupling seam that lets `@luckystack/login`\n//? be an OPTIONAL package. Historically api/sync/presence/server imported\n//? `getSession`/`saveSession`/`deleteSession`/`logout` straight from\n//? `@luckystack/login`, which made login a hard transitive dependency of the\n//? whole stack. Now login REGISTERS its session implementation here at boot and\n//? everyone else reads sessions through the null-safe accessors below.\n//?\n//? When login is absent (no provider registered) the app is by definition\n//? UNAUTHENTICATED: reads return `null`, writes are no-ops, logout is a no-op.\n//? Callers already handle a null session (they treat it as \"not logged in\"), so\n//? auth-gated routes reject and public routes keep working.\n//?\n//? Resolved at call time (never captured at module load) so registration order\n//? does not matter — same contract as `getProjectConfig()` / `getLogger()`.\n\nimport type { Socket } from 'socket.io';\nimport type { BaseSessionLayout } from './sessionTypes';\n\nexport interface SessionSaveResult {\n ok: boolean;\n errorCode?: string;\n}\n\nexport interface SessionLogoutInput {\n token: string | null;\n socket?: Socket;\n userId?: string | null;\n skipSessionDelete?: boolean;\n}\n\nexport interface SessionProvider {\n getSession: (token: string | null) => Promise<BaseSessionLayout | null>;\n saveSession: (\n token: string,\n data: BaseSessionLayout,\n newUser?: boolean,\n options?: { supersedeToken?: string },\n ) => Promise<SessionSaveResult>;\n deleteSession: (token: string, options?: { skipSocketLogout?: boolean }) => Promise<boolean>;\n logout: (input: SessionLogoutInput) => Promise<void>;\n}\n\nlet provider: SessionProvider | null = null;\n\n//? Called once at boot by `@luckystack/login` (side-effect in its index).\nexport const registerSessionProvider = (next: SessionProvider): void => {\n provider = next;\n};\n\nexport const getRegisteredSessionProvider = (): SessionProvider | null => provider;\n\nexport const isSessionProviderRegistered = (): boolean => provider !== null;\n\n//? Test-only — drop the registered provider between scenarios.\nexport const resetSessionProviderForTests = (): void => {\n provider = null;\n};\n\n//? Null-safe accessors. Use these everywhere outside `@luckystack/login`.\nexport const readSession = async (token: string | null): Promise<BaseSessionLayout | null> =>\n provider ? provider.getSession(token) : null;\n\n/**\n * Persist a session via the registered provider, or no-op if no login provider\n * is registered (unauthenticated-app case).\n *\n * **WARNING**: when no session provider is registered, this returns `{ ok: true }`\n * as a no-op — the session is NOT actually persisted anywhere. Callers that\n * check `result.ok` before considering a user logged in will incorrectly\n * proceed as if the session was saved. This is intentional for apps that do not\n * use `@luckystack/login`, but on a partial install (login package misconfigured\n * or not yet registered) the failure is silent rather than loud.\n * Use `isSessionProviderRegistered()` at boot to verify the provider is present.\n */\nexport const writeSession = async (\n token: string,\n data: BaseSessionLayout,\n newUser?: boolean,\n options?: { supersedeToken?: string },\n): Promise<SessionSaveResult> =>\n provider ? provider.saveSession(token, data, newUser, options) : { ok: true };\n\nexport const removeSession = async (\n token: string,\n options?: { skipSocketLogout?: boolean },\n): Promise<boolean> => (provider ? provider.deleteSession(token, options) : false);\n\nexport const performLogout = async (input: SessionLogoutInput): Promise<void> => {\n if (provider) await provider.logout(input);\n};\n","/**\n * Shared validation utilities for API and Sync request handlers.\n * Extracted to avoid code duplication between handleApiRequest and handleSyncRequest.\n */\n\nimport type { AuthProps, BaseSessionLayout } from './sessionTypes';\n\n/**\n * Check if a value is falsy (false, 0, 0n, '', null, undefined, or NaN)\n */\nexport const isFalsy = (value: unknown): boolean => {\n return (\n value === false ||\n value === 0 ||\n value === 0n ||\n value === '' ||\n value === null ||\n value === undefined ||\n (typeof value === 'number' && Number.isNaN(value))\n );\n};\n\nexport interface ValidationResult {\n status: 'success' | 'error';\n errorCode?: string;\n errorParams?: { key: string; value: string | number | boolean; }[];\n httpStatus?: number;\n}\n\n/**\n * Validate a request against authentication requirements.\n *\n * Multiple constraints on a single `additional` entry are AND'd: every\n * specified field (`nullish`, `type`, `value`, `mustBeFalsy`) must pass\n * for that entry to validate. Each entry across the array is also AND'd:\n * the request must pass every entry to succeed.\n *\n * Constraint semantics:\n * - `nullish: true` — `val` must be `null` or `undefined`.\n * - `nullish: false` — `val` must NOT be `null` or `undefined`.\n * - `type: 'string'` — `typeof val === 'string'` (skipped when val is null/undefined).\n * - `value: <x>` — strict equality `val === x`. Omit the field entirely if you\n * don't want this check; setting `value: undefined`\n * explicitly means \"val must be undefined\".\n * - `mustBeFalsy: true` — `isFalsy(val)`.\n * - `mustBeFalsy: false` — `!isFalsy(val)`.\n *\n * **SECURITY CONTRACT**: this function does NOT check `auth.login`. Login\n * enforcement is the responsibility of the surrounding API/sync handler — it\n * must verify the session exists before calling `validateRequest`. Callers\n * that omit the login check will allow unauthenticated requests through the\n * `additional[]` predicates even when `auth.login: true` is declared, because\n * `validateRequest` short-circuits to success when `additional` is empty.\n * The expected call pattern is:\n * 1. Check session exists (enforce `auth.login`).\n * 2. Call `validateRequest({ auth, user })` to enforce `auth.additional[]`.\n */\nexport const validateRequest = ({\n auth,\n user\n}: {\n auth: AuthProps;\n //? Null-safe (api F4 / SYNC-02-root): unauth / additional-only routes used to\n //? pass a non-null assertion (`user!`) here even when no session existed,\n //? which crashed once `additional[]` was present (the `condition.key in user`\n //? check throws on null/undefined). `user` is now optional; when it is\n //? absent we forbid any `additional[]` predicate (an unauthenticated request\n //? can't satisfy a session-field constraint) instead of throwing. A route with\n //? no `additional` still succeeds regardless of `user` (login itself is\n //? enforced by the surrounding handler, not here).\n user?: BaseSessionLayout | null;\n}): ValidationResult => {\n\n //? No predicates to evaluate — succeed regardless of `user`. This MUST treat\n //? an empty array the same as an absent one: the prod/generated auth map\n //? normalizes every `auth` to include `additional: []`, so a non-empty-array\n //? guard alone would forbid every PUBLIC route (login:false) for anonymous\n //? callers once the null-user branch below runs.\n if (!auth.additional || auth.additional.length === 0) {\n return { status: 'success' };\n }\n\n const forbid = (key: string): ValidationResult => ({\n status: 'error',\n errorCode: 'auth.forbidden',\n errorParams: [{ key: 'key', value: key }],\n httpStatus: 403,\n });\n\n //? No session but the route declares `additional[]` predicates: an\n //? unauthenticated request cannot satisfy any session-field constraint, so\n //? forbid the first one rather than dereferencing a null `user`.\n if (user === null || user === undefined) {\n return forbid(auth.additional[0]?.key ?? '');\n }\n\n for (const condition of auth.additional) {\n // Key must exist in user session — that's a setup error, not a runtime auth fail.\n if (!(condition.key in user)) {\n return {\n status: 'error',\n errorCode: 'auth.invalidCondition',\n errorParams: [{ key: 'key', value: condition.key }],\n httpStatus: 500,\n };\n }\n\n const val = user[condition.key];\n const isNullish = val === null || val === undefined;\n\n if (typeof condition.nullish === 'boolean' && condition.nullish !== isNullish) {\n return forbid(condition.key);\n }\n\n if (condition.type && !isNullish && typeof val !== condition.type) {\n return forbid(condition.key);\n }\n\n //? Use `'value' in condition` so an explicit `value: undefined` still\n //? participates (caller saying \"val must be undefined\"), distinct from\n //? the omitted case (no exact-value constraint).\n if ('value' in condition && val !== condition.value) {\n return forbid(condition.key);\n }\n\n if (typeof condition.mustBeFalsy === 'boolean' && condition.mustBeFalsy !== isFalsy(val)) {\n return forbid(condition.key);\n }\n }\n\n return { status: 'success' };\n};\n","/**\n * Rate Limiter\n *\n * The framework's rate-limit dispatch is pluggable via\n * `registerRateLimitStrategy()`. The built-in `defaultRateLimitStrategy`\n * implements in-memory counters with optional Redis backing for\n * multi-instance deployments. Consumers can plug in alternative strategies\n * (token-bucket, sliding-window, per-user tiers, edge-KV-backed, etc.) by\n * implementing the `RateLimitStrategy` interface.\n */\n\nimport { getProjectConfig } from './projectConfig';\nimport { createRegistry } from './createRegistry';\nimport tryCatch from './tryCatch';\nimport { redis } from './redis';\nimport { formatKey } from './redisKeyFormatter';\nimport { getLogger } from './loggerRegistry';\nimport { isProductionRuntime } from './env';\n\nexport interface CheckRateLimitParams {\n /** Unique key for rate limiting (e.g., \"user:123:api:getData\") */\n key: string;\n /** Maximum requests allowed in window */\n limit: number;\n /** Time window in milliseconds (default: 60_000 = 1 minute) */\n windowMs?: number;\n}\n\nexport interface RateLimitResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in current window */\n remaining: number;\n /** Seconds until rate limit resets */\n resetIn: number;\n}\n\n/**\n * Pluggable rate-limit backend. The framework calls these methods through\n * the active strategy slot. Default implementation is memory + Redis.\n * Replace via `registerRateLimitStrategy(...)` at boot.\n */\nexport interface RateLimitStrategy {\n /** Human-readable identifier (used in logs/diagnostics). */\n name: string;\n /** Increment the counter for `key` and return whether the request is allowed. */\n check(params: CheckRateLimitParams): Promise<RateLimitResult>;\n /** Read current status without incrementing — useful for response headers. */\n getStatus(key: string, limit: number): Promise<RateLimitResult>;\n /** Reset the counter for a single key. */\n clear(key: string): Promise<void>;\n /** Reset all counters under this strategy's namespace. */\n clearAll(): Promise<void>;\n}\n\ninterface RateLimitEntry {\n count: number;\n resetAt: number;\n}\n\n//? CORE-O7: cap the in-memory store so a key-varying flood (e.g. path-parameter\n//? spray) can't grow the Map to OOM between cleanup ticks. When the cap is hit,\n//? the oldest entry is evicted (Map preserves insertion order; the first key is\n//? the oldest). Evicting the oldest is the least-harm choice: the victim can\n//? retry (now capped to 1) while the attacker's own keys are the new ones,\n//? making them the hardest to evict. In memory mode this is a single-instance\n//? soft limit; for multi-instance deployments use Redis mode.\nconst MAX_MEMORY_STORE_SIZE = 50_000;\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\n//? Resolved at call time so `registerProjectConfig` can run after this\n//? module is imported. If the project never registers, the defaults from\n//? projectConfig.ts take effect (memory store, 'rate-limit' prefix).\nconst getRedisPrefix = (): string =>\n formatKey(`:${getProjectConfig().rateLimiting.redisKeyPrefix}`, '');\n\nconst isRedisMode = (): boolean => getProjectConfig().rateLimiting.store === 'redis';\nconst isRateLimitingEnabled = (): boolean => getProjectConfig().rateLimiting.enabled;\n\nconst buildAllowedResult = (limit: number): RateLimitResult => ({\n allowed: true,\n remaining: Math.max(0, limit),\n resetIn: 0,\n});\n\n//? Re-armable warning latch: log at most once per `REDIS_FALLBACK_LOG_COOLDOWN_MS`\n//? so a recurring Redis degradation stays visible (the old one-shot latch hid\n//? every fallback after the first, so a flapping Redis looked healthy in logs).\nconst REDIS_FALLBACK_LOG_COOLDOWN_MS = 60_000;\nlet redisFallbackLastLoggedAt = 0;\n\nconst RATE_LIMIT_INCREMENT_SCRIPT = `\nlocal current = redis.call('INCR', KEYS[1])\nif current == 1 then\n redis.call('PEXPIRE', KEYS[1], ARGV[1])\nend\nlocal ttl = redis.call('PTTL', KEYS[1])\nreturn { current, ttl }\n`;\n\nconst normalizeWindowMs = (windowMs: number | undefined): number => {\n if (windowMs === undefined || !Number.isFinite(windowMs) || windowMs <= 0) {\n return getProjectConfig().rateLimiting.windowMs;\n }\n return windowMs;\n};\n\nconst getRedisRateLimitKey = (key: string): string => `${getRedisPrefix()}:${key}`;\n\nconst checkRateLimitInMemory = ({\n key,\n limit,\n //? No default here — `normalizeWindowMs` reads `projectConfig.rateLimiting.windowMs`\n //? when `windowMs` is undefined or non-finite, so a hardcoded `60_000` would\n //? shadow the configured value (CORE-N6).\n windowMs,\n}: CheckRateLimitParams): RateLimitResult => {\n const safeWindowMs = normalizeWindowMs(windowMs);\n const now = Date.now();\n const entry = rateLimitStore.get(key);\n\n if (!entry || entry.resetAt < now) {\n //? CORE-O7: evict before inserting a new key to keep the store bounded.\n //? Prefer evicting an EXPIRED entry (rate limit already reset, no security\n //? cost) over evicting the oldest ACTIVE entry (which could let a rate-limited\n //? key bypass the limit by resetting its counter). Only when no expired entry\n //? exists do we fall back to the insertion-order oldest (least-harm choice).\n if (rateLimitStore.size >= MAX_MEMORY_STORE_SIZE) {\n let evictKey: string | undefined;\n for (const [k, e] of rateLimitStore) {\n if (e.resetAt < now) { evictKey = k; break; }\n }\n evictKey ??= rateLimitStore.keys().next().value;\n if (evictKey !== undefined) rateLimitStore.delete(evictKey);\n }\n rateLimitStore.set(key, { count: 1, resetAt: now + safeWindowMs });\n return { allowed: true, remaining: limit - 1, resetIn: Math.ceil(safeWindowMs / 1000) };\n }\n\n entry.count += 1;\n return {\n allowed: entry.count <= limit,\n remaining: Math.max(0, limit - entry.count),\n resetIn: Math.ceil((entry.resetAt - now) / 1000),\n };\n};\n\nconst checkRateLimitInRedis = async ({\n key,\n limit,\n //? No default — same reasoning as `checkRateLimitInMemory` (CORE-N6).\n windowMs,\n}: CheckRateLimitParams): Promise<RateLimitResult | null> => {\n const safeWindowMs = normalizeWindowMs(windowMs);\n const redisKey = getRedisRateLimitKey(key);\n\n const [evalError, evalResponse] = await tryCatch(\n async () => await redis.eval(RATE_LIMIT_INCREMENT_SCRIPT, 1, redisKey, String(safeWindowMs)),\n );\n\n if (evalError || !Array.isArray(evalResponse) || evalResponse.length < 2) {\n return null;\n }\n\n const requestCount = Number(evalResponse[0]);\n const ttlMs = Number(evalResponse[1]);\n const safeCount = Number.isFinite(requestCount) ? requestCount : 1;\n const safeTtlMs = Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : safeWindowMs;\n\n return {\n allowed: safeCount <= limit,\n remaining: Math.max(0, limit - safeCount),\n resetIn: Math.ceil(safeTtlMs / 1000),\n };\n};\n\nconst getRateLimitStatusInMemory = (key: string, limit: number): RateLimitResult => {\n const now = Date.now();\n const entry = rateLimitStore.get(key);\n\n if (!entry || entry.resetAt < now) {\n return { allowed: true, remaining: limit, resetIn: 0 };\n }\n\n return {\n allowed: entry.count < limit,\n remaining: Math.max(0, limit - entry.count),\n resetIn: Math.ceil((entry.resetAt - now) / 1000),\n };\n};\n\nconst getRateLimitStatusInRedis = async (key: string, limit: number): Promise<RateLimitResult | null> => {\n const redisKey = getRedisRateLimitKey(key);\n const [txError, txResponse] = await tryCatch(\n async () => await redis.multi().get(redisKey).pttl(redisKey).exec(),\n );\n\n if (txError || !txResponse || txResponse.length < 2) return null;\n\n const getResult = txResponse[0];\n const ttlResult = txResponse[1];\n if (!getResult || !ttlResult || getResult[0] || ttlResult[0]) return null;\n\n const currentCount = Number(getResult[1] ?? 0);\n const ttlMs = Number(ttlResult[1] ?? 0);\n\n if (currentCount <= 0 || ttlMs <= 0) {\n return { allowed: true, remaining: limit, resetIn: 0 };\n }\n\n return {\n allowed: currentCount < limit,\n remaining: Math.max(0, limit - currentCount),\n resetIn: Math.ceil(ttlMs / 1000),\n };\n};\n\nconst getOnStoreError = (): 'memory' | 'deny' =>\n getProjectConfig().rateLimiting.onStoreError;\n\nconst logRedisFallback = (): void => {\n const now = Date.now();\n if (now - redisFallbackLastLoggedAt < REDIS_FALLBACK_LOG_COOLDOWN_MS) return;\n redisFallbackLastLoggedAt = now;\n getLogger().warn(\n getOnStoreError() === 'deny'\n ? '[RateLimiter] Redis mode unavailable — denying request (onStoreError=deny)'\n : '[RateLimiter] Redis mode unavailable, falling back to in-memory mode (onStoreError=memory)',\n );\n};\n\n//? When `onStoreError: 'deny'` we fail closed: a Redis outage must NOT silently\n//? relax the limit. Returns a not-allowed result with the full window as the\n//? reset hint so callers surface a sane `Retry-After`.\nconst buildDeniedResult = (): RateLimitResult => ({\n allowed: false,\n remaining: 0,\n resetIn: Math.ceil(getProjectConfig().rateLimiting.windowMs / 1000),\n});\n\n/**\n * Built-in strategy: in-memory counters with optional Redis backing for\n * multi-instance deployments. Mode is controlled by\n * `projectConfig.rateLimiting.store` (`'memory'` or `'redis'`). When `redis`\n * mode fails (e.g. Redis disconnect), the strategy silently degrades to\n * in-memory and logs a one-shot warning.\n */\nexport const defaultRateLimitStrategy: RateLimitStrategy = {\n name: 'default-memory-redis',\n\n async check(params) {\n if (!isRateLimitingEnabled()) return buildAllowedResult(params.limit);\n\n if (isRedisMode()) {\n const redisResult = await checkRateLimitInRedis(params);\n if (redisResult) return redisResult;\n logRedisFallback();\n if (getOnStoreError() === 'deny') return buildDeniedResult();\n }\n\n return checkRateLimitInMemory(params);\n },\n\n async getStatus(key, limit) {\n if (!isRateLimitingEnabled()) return buildAllowedResult(limit);\n\n if (isRedisMode()) {\n const redisResult = await getRateLimitStatusInRedis(key, limit);\n if (redisResult) return redisResult;\n logRedisFallback();\n if (getOnStoreError() === 'deny') return buildDeniedResult();\n }\n\n return getRateLimitStatusInMemory(key, limit);\n },\n\n async clear(key) {\n if (isRedisMode()) {\n await tryCatch(async () => await redis.del(getRedisRateLimitKey(key)));\n }\n rateLimitStore.delete(key);\n },\n\n async clearAll() {\n if (isRedisMode()) {\n let cursor = '0';\n do {\n const [scanError, scanResponse] = await tryCatch(\n async () => await redis.scan(cursor, 'MATCH', `${getRedisPrefix()}:*`, 'COUNT', 100),\n );\n\n if (scanError || !scanResponse) break;\n\n cursor = scanResponse[0];\n const keys = scanResponse[1];\n\n if (keys.length > 0) {\n await tryCatch(async () => await redis.del(...keys));\n }\n } while (cursor !== '0');\n }\n\n rateLimitStore.clear();\n },\n};\n\nconst strategyRegistry = createRegistry<RateLimitStrategy>(defaultRateLimitStrategy, {\n onRegister: (strategy) => {\n getLogger().debug(`[RateLimiter] active strategy → ${strategy.name}`);\n },\n});\n\n/**\n * Replace the active rate-limit backend. Pass a strategy that implements\n * `RateLimitStrategy`. Common use cases:\n *\n * - Token-bucket / sliding-window algorithms for smoother burst handling.\n * - Per-user tier limits (premium accounts get higher caps).\n * - Cloudflare-Workers-KV or upstash-rest-backed counters for edge.\n * - No-op strategy for test environments.\n *\n * Call this once at boot (typically from `luckystack/server/index.ts`)\n * BEFORE the first request lands. Subsequent calls overwrite the previous\n * registration (last-write-wins).\n */\nexport const registerRateLimitStrategy = (strategy: RateLimitStrategy): void => {\n strategyRegistry.register(strategy);\n};\n\n/** Read the currently-active strategy (defaults to the built-in). */\nexport const getRateLimitStrategy = (): RateLimitStrategy => strategyRegistry.get();\n\n/**\n * Check if a request is allowed under rate limiting rules.\n * Increments the counter for the key if allowed.\n */\nexport const checkRateLimit = async (params: CheckRateLimitParams): Promise<RateLimitResult> => {\n ensureCleanupScheduled();\n return strategyRegistry.get().check(params);\n};\n\n/**\n * Get current rate limit status without incrementing counter.\n * Useful for rate limit headers in responses.\n */\nexport const getRateLimitStatus = async (key: string, limit: number): Promise<RateLimitResult> => {\n ensureCleanupScheduled();\n return strategyRegistry.get().getStatus(key, limit);\n};\n\n/**\n * Clear rate limit for a specific key.\n * Useful for admin overrides or testing.\n */\nexport const clearRateLimit = async (key: string): Promise<void> => strategyRegistry.get().clear(key);\n\n/**\n * Clear all rate limits.\n * Intended for testing or dev-mode server reset only.\n * CORE-O8: throws in production to prevent accidental global counter wipe across\n * all tenants. If you need a production-safe reset, pass a scoped key to\n * `clearRateLimit` or implement a tenant-namespaced strategy.\n */\nexport const clearAllRateLimits = async (): Promise<void> => {\n if (isProductionRuntime()) {\n //? Throw (not warn-and-return) so callers that catch the rejection know the\n //? operation was skipped. A silent `return` makes integration test scaffolding\n //? proceed with stale counters, producing flaky results (CORE-O8 logic-gap fix).\n throw new Error('[RateLimiter] clearAllRateLimits() is not allowed in production. Use clearRateLimit(key) for scoped resets.');\n }\n return strategyRegistry.get().clearAll();\n};\n\n//? Cleanup expired entries from the in-memory store on a configurable\n//? interval. Only relevant for the default strategy; custom strategies\n//? handle their own cleanup. Restart-aware: we schedule recursively so the\n//? interval picks up `projectConfig.rateLimiting.cleanupIntervalMs` whenever\n//? it changes (e.g. after `registerProjectConfig`).\n//?\n//? Lazy-started on the first `checkRateLimit`/`getRateLimitStatus` call rather\n//? than at module load, so any tool/CLI/test that merely type-imports core does\n//? NOT inherit a recurring timer it never asked for (the package's \"no import-\n//? time side effects\" doctrine).\n//? NOTE: `cleanupStarted` is never reset, even when a custom strategy is\n//? registered via `registerRateLimitStrategy()`. The cleanup timer runs on the\n//? built-in `rateLimitStore` only — it is harmless but wasteful when a custom\n//? strategy takes over (the store stays empty). If your custom strategy needs\n//? its own cleanup, schedule it in the strategy's own `clearAll` / a separate\n//? boot hook. The one-shot `onStoreError='deny'-in-memory-mode` warning (below)\n//? will also NOT fire for a strategy registered after the first request; call\n//? `registerRateLimitStrategy` before the first request to ensure the warning\n//? check fires with the intended strategy in place.\nlet cleanupStarted = false;\nconst scheduleCleanup = (): void => {\n const intervalMs = getProjectConfig().rateLimiting.cleanupIntervalMs;\n const timer = setTimeout(() => {\n const now = Date.now();\n let cleaned = 0;\n for (const [key, entry] of rateLimitStore) {\n if (entry.resetAt < now) {\n rateLimitStore.delete(key);\n cleaned++;\n }\n }\n if (cleaned > 0) {\n getLogger().debug(`[RateLimiter] Cleaned ${String(cleaned)} expired entries`);\n }\n scheduleCleanup();\n }, intervalMs);\n timer.unref();\n};\n\nconst ensureCleanupScheduled = (): void => {\n if (cleanupStarted) return;\n cleanupStarted = true;\n //? CORE-O6: warn once at the first request if `onStoreError:'deny'` is set\n //? in non-Redis mode. In memory mode there is no \"store error\" path to honor,\n //? so the setting is a silent no-op — log it so operators aren't surprised.\n if (!isRedisMode() && getOnStoreError() === 'deny') {\n getLogger().warn(\n '[RateLimiter] onStoreError=\"deny\" has no effect in memory mode (only Redis mode has a fallback path). Switch to store:\"redis\" or remove the setting.',\n );\n }\n scheduleCleanup();\n};\n","import tryCatchSync from './tryCatchSync';\nimport { isProductionRuntime } from './env';\n\ntype ValidationResult =\n | { status: 'success' }\n | { status: 'error'; message: string };\n\n interface ParsedObjectField {\n key: string;\n optional: boolean;\n type: string;\n}\n\ninterface ParsedObjectIndexSignature {\n keyName: string;\n keyType: string;\n type: string;\n}\n\nconst splitTopLevel = (value: string, splitter: '|' | '&'): string[] => {\n const items: string[] = [];\n let depthParen = 0;\n let depthBrace = 0;\n let depthBracket = 0;\n //? Track whether we are inside a string literal (single or double quote) so\n //? a `|` or `&` character appearing in `\"a|b\"` is not treated as a splitter.\n //? Escaped quotes (`\\\"`, `\\'`) extend the literal rather than ending it.\n let inString: '\"' | \"'\" | null = null;\n let token = '';\n\n //? Iterate as code-unit pairs so we can peek-and-skip the character after\n //? a backslash inside a string literal. We work on the spread array of\n //? code-points so that multi-byte characters are treated as single tokens.\n //? Code-point iteration is intentional (multi-byte chars as single tokens); type\n //? strings are ASCII syntax so emoji decomposition is a non-issue here.\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- intentional code-point split of an ASCII type string\n const codePoints = [...value];\n let i = 0;\n while (i < codePoints.length) {\n const char = codePoints[i] ?? '';\n i += 1;\n\n //? Handle escape sequences inside string literals: consume the next\n //? code-point as part of the current literal without toggling inString.\n if (inString && char === '\\\\') {\n token += char;\n const next = codePoints[i] ?? '';\n token += next;\n i += 1;\n continue;\n }\n\n //? Toggle string-literal tracking on opening/closing quote.\n if (char === '\"' || char === \"'\") {\n if (inString === char) {\n inString = null;\n } else {\n inString ??= char;\n }\n token += char;\n continue;\n }\n\n //? Only track nesting depth and split when we are not inside a string.\n if (!inString) {\n if (char === '(') depthParen += 1;\n if (char === ')') depthParen -= 1;\n if (char === '{') depthBrace += 1;\n if (char === '}') depthBrace -= 1;\n if (char === '[') depthBracket += 1;\n if (char === ']') depthBracket -= 1;\n\n if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {\n items.push(token.trim());\n token = '';\n continue;\n }\n }\n\n token += char;\n }\n\n if (token.trim()) items.push(token.trim());\n return items;\n};\n\n//? Match a quoted key that may contain any characters (including hyphens,\n//? dots, spaces) — TypeScript allows `'foo-bar'?: string` as a valid field.\n//? Group 1 = quote char, group 2 = raw key text, group 3 = optional `?`.\nconst QUOTED_FIELD_RE = /^(\"|')((?:[^\\\\]|\\\\.)*?)\\1(\\?)?\\s*:\\s*([\\s\\S]+)$/;\n//? Plain identifier key: `myField?: string`.\nconst PLAIN_FIELD_RE = /^([A-Za-z_][A-Za-z0-9_]*)(\\?)?\\s*:\\s*([\\s\\S]+)$/;\nconst INDEX_SIG_RE = /^\\[\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*([^\\]]+)\\]\\s*:\\s*([\\s\\S]+)$/;\n\n//? Parse one semicolon-separated token from a TS object-type body.\n//? Supports quoted keys like `'foo-bar'?: string` in addition to plain\n//? identifiers — previously those were silently dropped (CORE-N3).\nconst parseFieldOrIndex = (\n trimmed: string,\n fields: ParsedObjectField[],\n indexSignatures: ParsedObjectIndexSignature[],\n): void => {\n const quotedMatch = QUOTED_FIELD_RE.exec(trimmed);\n if (quotedMatch) {\n fields.push({\n key: (quotedMatch[2] ?? '').replaceAll(String.raw`\\'`, \"'\").replaceAll(String.raw`\\\"`, '\"'),\n optional: Boolean(quotedMatch[3]),\n type: (quotedMatch[4] ?? '').trim(),\n });\n return;\n }\n const plainMatch = PLAIN_FIELD_RE.exec(trimmed);\n if (plainMatch) {\n fields.push({\n key: plainMatch[1] ?? '',\n optional: Boolean(plainMatch[2]),\n type: (plainMatch[3] ?? '').trim(),\n });\n return;\n }\n const indexMatch = INDEX_SIG_RE.exec(trimmed);\n if (indexMatch) {\n indexSignatures.push({\n keyName: (indexMatch[1] ?? '').trim(),\n keyType: (indexMatch[2] ?? '').trim(),\n type: (indexMatch[3] ?? '').trim(),\n });\n }\n};\n\nconst parseObjectFields = (typeText: string): {\n fields: ParsedObjectField[];\n indexSignatures: ParsedObjectIndexSignature[];\n} => {\n const clean = typeText.trim();\n if (!clean.startsWith('{') || !clean.endsWith('}')) {\n return { fields: [], indexSignatures: [] };\n }\n\n const inner = clean.slice(1, -1);\n const fields: ParsedObjectField[] = [];\n const indexSignatures: ParsedObjectIndexSignature[] = [];\n\n let part = '';\n let depth = 0;\n //? Track string literals (single and double quote) so a `;`, `{`, or `}`\n //? character appearing inside a quoted type value (e.g. `name: 'a{b}'`) is not\n //? treated as a field delimiter or nesting marker. Same logic as `splitTopLevel`.\n let inString: '\"' | \"'\" | null = null;\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- intentional code-point split of an ASCII type string\n const codePoints = [...inner];\n let i = 0;\n while (i < codePoints.length) {\n const char = codePoints[i] ?? '';\n i += 1;\n\n if (inString && char === '\\\\') {\n part += char;\n const next = codePoints[i] ?? '';\n part += next;\n i += 1;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n if (inString === char) {\n inString = null;\n } else {\n inString ??= char;\n }\n part += char;\n continue;\n }\n\n if (!inString) {\n //? Nesting depth tracks `{}[]()<>` to find field boundaries at `;`.\n //? Supported field value types: primitives, object literals, arrays, unions,\n //? intersections, Record<K,V>, and generics like Array<T> / Promise<T>.\n //? NOT supported: conditional types (`T extends U ? A : B`), mapped types,\n //? template-literal types, and function types (`() => void`). Those fall\n //? through to the fail-closed terminal branch of `validateType` which\n //? rejects with an \"unvalidatable type\" message rather than silently passing.\n //? NOTE: `<` and `>` are used for generic delimiters (Array<T>, Record<K,V>)\n //? but also appear as comparison operators in conditional types. For the\n //? supported type set (no conditionals) this depth tracking is sufficient.\n if (char === '{' || char === '[' || char === '(' || char === '<') depth += 1;\n if (char === '}' || char === ']' || char === ')' || char === '>') depth -= 1;\n\n if (char === ';' && depth === 0) {\n const trimmed = part.trim();\n if (trimmed) {\n parseFieldOrIndex(trimmed, fields, indexSignatures);\n }\n part = '';\n continue;\n }\n }\n\n part += char;\n }\n\n const final = part.trim();\n if (final) {\n parseFieldOrIndex(final, fields, indexSignatures);\n }\n\n return { fields, indexSignatures };\n};\n\nconst matchesIndexKeyType = ({ keyType, key }: { keyType: string; key: string }): boolean => {\n const normalized = keyType.trim();\n if (normalized === 'string') return true;\n if (normalized === 'number') return /^-?\\d+(\\.\\d+)?$/.test(key);\n if (normalized.includes('|')) {\n const parts = splitTopLevel(normalized, '|').map((part) => part.trim());\n return parts.some((part) => matchesIndexKeyType({ keyType: part, key }));\n }\n if ((normalized.startsWith(\"'\") && normalized.endsWith(\"'\")) || (normalized.startsWith('\"') && normalized.endsWith('\"'))) {\n return key === normalized.slice(1, -1);\n }\n return false;\n};\n\nconst isPrimitiveMatch = (type: string, value: unknown): boolean => {\n if (type === 'string') return typeof value === 'string';\n if (type === 'number') return typeof value === 'number' && Number.isFinite(value);\n if (type === 'boolean') return typeof value === 'boolean';\n if (type === 'true') return value === true;\n if (type === 'false') return value === false;\n if (type === 'null') return value === null;\n if (type === 'undefined') return value === undefined;\n //? Accept a Date instance directly, or a string that parses to a valid date.\n //? Rejecting non-parseable strings prevents `new Date(x).toISOString()` from\n //? returning \"Invalid Date\" when a handler trusts the validator's pass.\n if (type === 'Date') return value instanceof Date || (typeof value === 'string' && !Number.isNaN(Date.parse(value)));\n return false;\n};\n\nconst parseFiniteNumericLiteral = (type: string): number | null => {\n const normalized = type.replaceAll('_', '');\n const decimal = /^-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?$/i;\n const nonDecimal = /^-?0(?:x[\\da-f]+|b[01]+|o[0-7]+)$/i;\n if (!decimal.test(normalized) && !nonDecimal.test(normalized)) return null;\n\n const negative = normalized.startsWith('-');\n const unsigned = negative ? normalized.slice(1) : normalized;\n const parsed = Number(unsigned) * (negative ? -1 : 1);\n return Number.isFinite(parsed) ? parsed : null;\n};\n\nconst isPrimitiveType = (type: string): boolean => {\n return ['string', 'number', 'boolean', 'true', 'false', 'null', 'undefined', 'Date'].includes(type);\n};\n\n//? Prototype-pollution guard: own-keys that must never be accepted as object /\n//? Record entries on the input boundary, regardless of the declared value type.\n//? An attacker smuggling `{\"__proto__\": {...}}` into a `Record<string, …>` (or\n//? an index-signature object) could otherwise reach a handler that spreads or\n//? deep-assigns the payload.\nconst PROTO_POLLUTION_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n//? Split a `Record<K, V>` type text into its key and value type at the TOP-LEVEL\n//? comma (so a nested `Record<string, { a: number }>` or a union value keeps its\n//? own commas). Returns null when the head/value can't be isolated.\nconst parseRecordParts = (type: string): { keyType: string; valueType: string } | null => {\n const inner = type.slice('Record<'.length, -1);\n let depth = 0;\n for (let index = 0; index < inner.length; index += 1) {\n const char = inner[index];\n if (char === '<' || char === '{' || char === '[' || char === '(') depth += 1;\n else if (char === '>' || char === '}' || char === ']' || char === ')') depth -= 1;\n else if (char === ',' && depth === 0) {\n return {\n keyType: inner.slice(0, index).trim(),\n valueType: inner.slice(index + 1).trim(),\n };\n }\n }\n return null;\n};\n\n//? Recursion-depth ceiling for `validateType`. The TYPE text is build-time\n//? fixed, but the VALUE is attacker-controlled: a deeply-nested array/object\n//? payload (within `requestBodyMaxBytes`) drives recursion proportional to its\n//? nesting and could blow the stack before the handler runs (cheap DoS). 64 is\n//? far deeper than any realistic typed payload while still bounding the stack.\nconst MAX_VALIDATION_DEPTH = 64;\n\nconst validateType = (typeText: string, value: unknown, path: string, depth = 0): ValidationResult => {\n if (depth > MAX_VALIDATION_DEPTH) {\n return { status: 'error', message: `${path}: input nesting exceeds the maximum depth of ${String(MAX_VALIDATION_DEPTH)}` };\n }\n\n const type = typeText.trim();\n\n if (type.startsWith('__RUNTIME_UNRESOLVED__::')) {\n const unresolvedMessage = type.replace('__RUNTIME_UNRESOLVED__::', '').trim();\n return { status: 'error', message: `${path}: ${unresolvedMessage}` };\n }\n\n if (type.startsWith('(') && type.endsWith(')')) {\n return validateType(type.slice(1, -1), value, path, depth + 1);\n }\n\n if (type.includes('|')) {\n const unionParts = splitTopLevel(type, '|').filter(Boolean);\n if (unionParts.length > 1) {\n for (const unionType of unionParts) {\n const result = validateType(unionType, value, path, depth + 1);\n if (result.status === 'success') return result;\n }\n return { status: 'error', message: `${path} does not match union type ${type}` };\n }\n //? A single-member \"union\" recurses ONLY when `splitTopLevel` actually\n //? isolated a DIFFERENT sub-type (e.g. a leading/trailing `|` was trimmed, or\n //? a parenthesised `(string)` inner remains). If the single part is the WHOLE\n //? type unchanged, there is NO top-level `|` at all — the `|` lives INSIDE a\n //? nested `{...}` / `[...]` / `<...>` (e.g. `{ theme?: 'a' | 'b' }`). Recursing\n //? on the identical string here would loop, incrementing `depth` on every pass\n //? until `MAX_VALIDATION_DEPTH` false-positives with a bogus \"input nesting\n //? exceeds the maximum depth\" on a perfectly shallow value. So fall through to\n //? the structural (object/array/primitive) handling below instead.\n const singleMember = unionParts[0] ?? type;\n if (unionParts.length === 1 && singleMember !== type) {\n return validateType(singleMember, value, path, depth + 1);\n }\n }\n\n if (type.includes('&')) {\n const intersectionParts = splitTopLevel(type, '&').filter(Boolean);\n if (intersectionParts.length > 1) {\n for (const intersectionType of intersectionParts) {\n const result = validateType(intersectionType, value, path, depth + 1);\n if (result.status === 'error') return result;\n }\n return { status: 'success' };\n }\n //? Same as the union case — a single-member intersection should recurse.\n if (intersectionParts.length === 1) {\n return validateType(intersectionParts[0] ?? type, value, path, depth + 1);\n }\n }\n\n if (type.endsWith('[]')) {\n if (!Array.isArray(value)) {\n return { status: 'error', message: `${path} should be an array` };\n }\n const itemType = type.slice(0, -2).trim();\n if (itemType === '' || itemType === type) {\n //? FAIL CLOSED: the element type was unsplittable from the `[]` suffix\n //? (defensive — should be unreachable). Don't pass an array of arbitrary\n //? elements unvalidated; surface it so the route author models it.\n return { status: 'error', message: `${path}: unvalidatable array element type for ${type}` };\n }\n for (const [index, element] of value.entries()) {\n const result = validateType(itemType, element, `${path}[${String(index)}]`, depth + 1);\n if (result.status === 'error') return result;\n }\n return { status: 'success' };\n }\n\n if ((type.startsWith(\"'\") && type.endsWith(\"'\")) || (type.startsWith('\"') && type.endsWith('\"'))) {\n const literal = type.slice(1, -1);\n return value === literal\n ? { status: 'success' }\n : { status: 'error', message: `${path} should equal ${literal}` };\n }\n\n const numericLiteral = parseFiniteNumericLiteral(type);\n if (numericLiteral !== null) {\n return typeof value === 'number' && value === numericLiteral\n ? { status: 'success' }\n : { status: 'error', message: `${path} should equal ${type}` };\n }\n\n if (isPrimitiveMatch(type, value)) {\n return { status: 'success' };\n }\n\n if (isPrimitiveType(type)) {\n const expectedType = type === 'Date' ? 'Date (ISO string or Date)' : type;\n return { status: 'error', message: `${path} should be ${expectedType}` };\n }\n\n //? `any` and `unknown` typed fields pass validation unconditionally.\n //? SECURITY NOTE: a route input field typed `any` or `unknown` accepts ANY\n //? value from the caller without structural checking. In practice this occurs\n //? in legacy routes, utility handlers, or pass-through relay fields.\n //? To tighten: narrow the field type explicitly in `ApiParams.data`, or set\n //? `validation: 'relaxed'` / `{ input: 'skip' }` on the route as a conscious\n //? opt-out (which short-circuits before this validator runs rather than silently\n //? admitting arbitrary values here).\n if (type === 'any' || type === 'unknown') {\n return { status: 'success' };\n }\n\n if (/^Record<.+>$/.test(type)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return { status: 'error', message: `${path} should be an object` };\n }\n\n const record = value as Record<string, unknown>;\n const recordParts = parseRecordParts(type);\n\n //? CORE-N4: if the Record<K,V> type string can't be parsed we fail closed\n //? rather than silently skipping value validation. An unrecognized generic\n //? shape must not pass arbitrary values unchecked.\n if (!recordParts) {\n return { status: 'error', message: `${path}: unvalidatable Record type ${type} — model it explicitly` };\n }\n\n for (const key of Object.keys(record)) {\n //? Reject prototype-polluting own-keys unconditionally.\n if (PROTO_POLLUTION_KEYS.has(key)) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n //? Validate the key against K (only the cheap string/number/literal forms\n //? `matchesIndexKeyType` understands; an unrecognized K leaves the key\n //? unconstrained, same as an index signature).\n if (\n (recordParts.keyType === 'number' || recordParts.keyType.includes('|') ||\n recordParts.keyType.startsWith(\"'\") || recordParts.keyType.startsWith('\"')) &&\n !matchesIndexKeyType({ keyType: recordParts.keyType, key })\n ) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n //? Validate each value against V — previously skipped entirely, so a\n //? `Record<string, number>` accepted `{ a: '<script>' }`.\n const valueResult = validateType(recordParts.valueType, record[key], `${path}.${key}`, depth + 1);\n if (valueResult.status === 'error') return valueResult;\n }\n\n return { status: 'success' };\n }\n\n if (type.startsWith('{') && type.endsWith('}')) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return { status: 'error', message: `${path} should be an object` };\n }\n\n const { fields, indexSignatures } = parseObjectFields(type);\n const input = value as Record<string, unknown>;\n\n for (const field of fields) {\n const fieldValue = input[field.key];\n if (fieldValue === undefined) {\n if (field.optional) continue;\n return { status: 'error', message: `${path}.${field.key} is required` };\n }\n\n const result = validateType(field.type, fieldValue, `${path}.${field.key}`, depth + 1);\n if (result.status === 'error') return result;\n }\n\n const allowedKeys = new Set(fields.map((field) => field.key));\n for (const key of Object.keys(input)) {\n if (allowedKeys.has(key)) {\n continue;\n }\n\n //? Reject prototype-polluting own-keys unconditionally — even when an\n //? index signature (`[k: string]: …`) would otherwise admit them.\n if (PROTO_POLLUTION_KEYS.has(key)) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n if (indexSignatures.length === 0) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n const indexValue = input[key];\n let matched = false;\n for (const indexSignature of indexSignatures) {\n if (!matchesIndexKeyType({ keyType: indexSignature.keyType, key })) {\n continue;\n }\n\n const indexResult = validateType(indexSignature.type, indexValue, `${path}.${key}`, depth + 1);\n if (indexResult.status === 'success') {\n matched = true;\n break;\n }\n }\n\n if (!matched) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n }\n\n return { status: 'success' };\n }\n\n if (/^[A-Za-z_][A-Za-z0-9_]*(?:<.+>)?$/.test(type)) {\n if (/^[A-Za-z_][A-Za-z0-9_]*<.+>$/.test(type)) {\n return { status: 'error', message: `${path}: unresolved utility ${type}` };\n }\n\n return { status: 'error', message: `${path}: unresolved type ${type}` };\n }\n\n //? FAIL CLOSED. This terminal branch is reached only by a type string none of\n //? the recognizers above could structurally validate (tuples `[a, b]`,\n //? function types `() => void`, mapped/conditional/template-literal types, or\n //? an unresolved alias the generator left behind). Previously it returned\n //? `{ status: 'success' }`, silently passing ANY value for such a route — the\n //? only structural input gate became a no-op exactly where the type is\n //? unknown, enabling type-confusion / operator-smuggling. Rejecting here means\n //? an unmodelable route surfaces loudly: model the input explicitly, or set\n //? `validation: 'relaxed'` / `{ input: 'skip' }` on the route to opt out\n //? consciously (which short-circuits before this validator runs).\n return { status: 'error', message: `${path}: unvalidatable type ${type} — model it explicitly or set validation: 'relaxed' on the route` };\n};\n\n//? FAIL CLOSED on a parser throw. The hand-rolled `validateType` walks\n//? attacker-controlled values; an unexpected shape that makes it throw must map\n//? to a validation ERROR (rejected request), NEVER propagate up to the\n//? api/sync handler as an uncaught throw — that would surface as a 500 and, on\n//? some paths, bypass the input gate entirely (fail-open-to-crash).\nconst safeValidateType = (typeText: string, value: unknown, path: string): ValidationResult => {\n const [error, result] = tryCatchSync(() => validateType(typeText, value, path));\n if (error || !result) {\n return { status: 'error', message: `${path}: input validation failed` };\n }\n return result;\n};\n\nexport const validateInputByType = async ({\n typeText,\n value,\n rootKey,\n filePath,\n}: {\n typeText?: string;\n value: unknown;\n rootKey: string;\n filePath?: string;\n}): Promise<ValidationResult> => {\n if (!typeText || typeText.trim() === '' || typeText.trim() === 'any') {\n return { status: 'success' };\n }\n\n //? CORE-01: production input-validation wiring. The legacy code returned\n //? `{ status: 'success' }` unconditionally in production, making the only\n //? structural input gate a no-op in prod (test/prod divergence + operator-\n //? injection exposure for handlers that trust `data` shape).\n //?\n //? The fix splits the two costs that used to be lumped together:\n //? 1. The DEEP type RESOLVER (`@luckystack/devkit`, TypeScript compiler API)\n //? — dev-only, expensive, and `external` in the prod bundle. Still skipped\n //? in prod (it would fail to import there anyway).\n //? 2. The structural VALIDATOR (`validateType`) — a pure, dependency-free\n //? walk over the ALREADY-RESOLVED generated type text. This is cheap and\n //? now runs in production too.\n //?\n //? In prod the generated `apiTypes` type text is already fully resolved at\n //? build time (the generator ran the devkit resolver before emitting), so we\n //? validate it directly without re-resolving. `validation.runtimeMode: 'off'`\n //? is the loud, documented opt-out that restores the old prod no-op.\n if (isProductionRuntime()) {\n //? Read the mode lazily (call-time) so `registerProjectConfig` can run after\n //? this module is imported. Indirect import avoids pulling projectConfig into\n //? the dev resolver path needlessly — but it's a cheap same-package import.\n const { getProjectConfig } = await import('./projectConfig');\n if (getProjectConfig().validation.runtimeMode === 'off') {\n return { status: 'success' };\n }\n //? Validate the pre-resolved generated type text directly. If the generator\n //? left an unresolved marker (`__RUNTIME_UNRESOLVED__::`), `validateType`\n //? surfaces it as an error rather than silently passing — that's the\n //? intended loud signal to regenerate artifacts before shipping.\n return safeValidateType(typeText, value, rootKey);\n }\n\n // Dev-only: the resolver uses TypeScript's compiler API for deep type\n // expansion. `@luckystack/devkit` is marked external in the prod esbuild\n // bundle (see scripts/bundleServer.mjs), so this branch compiles in prod but\n // cannot run (import would fail). That is fine — the production branch above\n // never reaches the devkit import.\n //\n // Indirect module ID (string variable, not literal) so tsc doesn't try to\n // type-resolve devkit at build time. Devkit depends on core, so a literal\n // type-resolved import would be a build-time circular dep.\n const devkitModuleId = '@luckystack/devkit';\n const devkit = (await import(devkitModuleId)) as DevkitTypeResolverModule;\n const resolvedType = devkit.resolveRuntimeTypeText({ typeText, filePath });\n if (resolvedType.status === 'error') {\n return { status: 'error', message: `${rootKey}: ${resolvedType.message}` };\n }\n\n return safeValidateType(resolvedType.typeText, value, rootKey);\n};\n\ninterface DevkitTypeResolverModule {\n resolveRuntimeTypeText: (params: { typeText: string; filePath?: string }) =>\n | { status: 'success'; typeText: string }\n | { status: 'error'; message: string };\n}\n","/* eslint-disable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/restrict-template-expressions */\n\nimport { getProjectConfig } from './projectConfig';\nimport { getLogger } from './loggerRegistry';\nimport { dispatchHook } from './hooks/registry';\nimport { getBindAddress } from './bindAddress';\n\nexport const normalizeOrigin = ({ value, secure }: { value: string; secure: boolean }): string => {\n const trimmedValue = value.trim().toLowerCase();\n if (!trimmedValue) { return ''; }\n\n const withProtocol = trimmedValue.startsWith('http://') || trimmedValue.startsWith('https://')\n ? trimmedValue\n : `http${secure ? 's' : ''}://${trimmedValue}`;\n\n // Keep only scheme + host[:port] so paths, query params, and fragments don't affect allowlist checks.\n const extractedOrigin = (/^(https?:\\/\\/[^/?#]+)/.exec(withProtocol))?.[1] || '';\n if (!extractedOrigin) { return ''; }\n\n return extractedOrigin\n // Treat explicit :80 as equivalent to implicit default http port.\n .replace(/^http:\\/\\/(.+):80$/i, 'http://$1')\n // Treat explicit :443 as equivalent to implicit default https port.\n .replace(/^https:\\/\\/(.+):443$/i, 'https://$1');\n};\n\nconst isLocalhostOrigin = (normalized: string): boolean => {\n //? `allowLocalhost` covers every loopback spelling a dev frontend uses —\n //? `localhost`, the IPv4 `127.0.0.1`, and the IPv6 `[::1]` — not just the\n //? literal hostname (a Vite dev server on `http://127.0.0.1:5173` was\n //? previously rejected even with the flag on).\n return /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/i.test(normalized);\n};\n\nconst allowedOrigin = (origin: string): boolean => {\n const secure = process.env.SECURE === 'true';\n const cors = getProjectConfig().http.cors;\n\n //? Project-supplied origins live in `ProjectConfig.http.cors.allowedOrigins`.\n //? Two modes: static array OR synchronous resolver function. The resolver\n //? form lets consumers do per-tenant / per-time-window allow-listing\n //? without restarting; it must be sync because Socket.io's CORS check is sync.\n const configured = cors.allowedOrigins;\n\n //? Bind address comes from the registry (populated by `createLuckyStackServer`\n //? from `options.ip`/`options.port`). Before bootstrap it falls back to\n //? `SERVER_IP` plus the generic port 80. Empty `port`\n //? produces an unmatchable `host:` entry which is fine — same-origin requests\n //? are typically allowed via `cors.allowLocalhost` or the configured\n //? `allowedOrigins` list anyway.\n const { ip: bindIp, port: bindPort } = getBindAddress();\n const rawLocation = bindPort\n ? `http${secure ? 's' : ''}://${bindIp}:${bindPort}`\n : `http${secure ? 's' : ''}://${bindIp}`;\n //? Normalize the bind address ONCE and reuse it in both branches. Comparing a\n //? normalized origin against the raw `location` made the same-origin fast-path\n //? miss for default ports (:80/:443, which the normalizer strips) — it then\n //? fell through to the resolver (still safe, but the shortcut didn't fire).\n const location = normalizeOrigin({ value: rawLocation, secure });\n const normalizedOrigin = normalizeOrigin({ value: origin, secure });\n\n if (cors.allowLocalhost && normalizedOrigin && isLocalhostOrigin(normalizedOrigin)) {\n return true;\n }\n\n //? Resolver-function mode: defer entirely to the consumer's logic. The\n //? framework keeps the normalized same-origin bind address always-allowed.\n //? Pass the NORMALIZED origin so that a naïve `startsWith`-style resolver\n //? cannot be bypassed by appending a path, port alias, or mixed casing —\n //? the same normalization the static-array branch applies (CORE-O16).\n if (typeof configured === 'function') {\n if (normalizedOrigin && normalizedOrigin === location) return true;\n if (configured(normalizedOrigin)) return true;\n // Fall through to the rejection path below.\n } else {\n const normalizedAllowedOrigins = new Set(\n [location, ...configured.map((value) => normalizeOrigin({ value, secure }))]\n .filter(Boolean)\n );\n\n if (normalizedOrigin && normalizedAllowedOrigins.has(normalizedOrigin)) {\n return true;\n }\n }\n\n //? Gated behind `devLogs` to avoid amplifying CORS-rejection traffic into\n //? production logs (an attacker could spam invalid origins otherwise). The\n //? structured `corsRejected` hook is the durable signal for production —\n //? subscribe via `registerHook('corsRejected', ...)` for audit/alerting.\n const debugAllowedOrigins = typeof configured === 'function'\n ? ['<dynamic-resolver>', location]\n : [location, ...configured]\n .map((value) => normalizeOrigin({ value, secure }))\n .filter(Boolean);\n\n //? Sanitize the caller-supplied origin before including it in any log or hook\n //? payload: cap length and strip non-printable characters so an attacker cannot\n //? inject arbitrary bytes into log sinks or external alerting hooks.\n const safeOrigin = origin.slice(0, 512).replaceAll(/[^ -~]/g, '');\n\n if (getProjectConfig().logging.devLogs) {\n getLogger().warn('cors: origin not allowed', {\n origin: safeOrigin,\n normalizedOrigin,\n allowedOrigins: debugAllowedOrigins,\n allowLocalhost: cors.allowLocalhost,\n });\n }\n void dispatchHook('corsRejected', {\n origin: safeOrigin,\n normalizedOrigin,\n allowedOrigins: debugAllowedOrigins,\n allowLocalhost: cors.allowLocalhost,\n });\n return false;\n};\n\nexport default allowedOrigin;\n","//? Cross-handler in-flight-request registry. Maps a stable key\n//? (`socketId:cb` for sync, `socketId:responseIndex` for api) to the\n//? `AbortController` driving the request's handler chain. The `apiCancel`\n//? / `syncCancel` socket event handlers look up the controller and call\n//? `.abort()` so the in-flight handler can stop emitting new chunks and\n//? exit early. Also walked on socket `disconnect` to abort everything\n//? still in flight for that socket.\n//?\n//? Lives in `@luckystack/core` (not `@luckystack/api` or `@luckystack/sync`)\n//? because the cancel-event listener is registered in `@luckystack/server`'s\n//? `loadSocket.ts`, which sits below both handler packages in the dep graph.\n//?\n//? Stored as plain Map (not WeakMap) because lookup is by string key and\n//? entries are explicitly removed at request end + on `disconnect`. Memory\n//? leak risk is bounded — handlers always call `unregister*` in their\n//? cleanup paths and disconnect drains everything for the closing socket.\n\nconst syncControllers = new Map<string, AbortController>();\nconst apiControllers = new Map<string, AbortController>();\n\nconst buildKey = (socketId: string, identifier: string | number): string =>\n `${socketId}:${String(identifier)}`;\n\nexport const registerSyncAbortController = (\n socketId: string,\n cb: string,\n controller: AbortController,\n): string => {\n const key = buildKey(socketId, cb);\n syncControllers.set(key, controller);\n return key;\n};\n\nexport const unregisterSyncAbortController = (key: string): void => {\n syncControllers.delete(key);\n};\n\nexport const abortSyncByCb = (socketId: string, cb: string): boolean => {\n const key = buildKey(socketId, cb);\n const controller = syncControllers.get(key);\n if (!controller) return false;\n controller.abort();\n syncControllers.delete(key);\n return true;\n};\n\nexport const registerApiAbortController = (\n socketId: string,\n responseIndex: number | string,\n controller: AbortController,\n): string => {\n const key = buildKey(socketId, responseIndex);\n apiControllers.set(key, controller);\n return key;\n};\n\nexport const unregisterApiAbortController = (key: string): void => {\n apiControllers.delete(key);\n};\n\nexport const abortApiByResponseIndex = (\n socketId: string,\n responseIndex: number | string,\n): boolean => {\n const key = buildKey(socketId, responseIndex);\n const controller = apiControllers.get(key);\n if (!controller) return false;\n controller.abort();\n apiControllers.delete(key);\n return true;\n};\n\n//? Drain everything in-flight for a socket — called on `disconnect`.\n//? Linear scan over both registries; disconnect is rare relative to\n//? regular request volume, and the registries are bounded by per-socket\n//? in-flight request count (typically <10).\nexport const abortAllForSocket = (socketId: string): void => {\n const prefix = `${socketId}:`;\n for (const [key, controller] of syncControllers) {\n if (key.startsWith(prefix)) {\n controller.abort();\n syncControllers.delete(key);\n }\n }\n for (const [key, controller] of apiControllers) {\n if (key.startsWith(prefix)) {\n controller.abort();\n apiControllers.delete(key);\n }\n }\n};\n","//? One-time-token primitive (CORE-OTT). A namespaced, single-use, Redis-backed\n//? token suitable for password-reset and email-change confirmation links — the\n//? two flows `@luckystack/login` previously hand-rolled with the RAW token as\n//? the Redis key.\n//?\n//? SECURITY — hashing at rest. The raw token is NEVER stored. We persist\n//? `sha256(token)` (hex) as the Redis key, so a dump / read of the Redis\n//? keyspace (a backup leak, an over-broad `KEYS` from another service, an\n//? attacker with read-only Redis) does NOT yield usable tokens: an attacker\n//? would have to brute-force a 256-bit preimage to mint a redemption. The raw\n//? token is returned to the caller exactly once (to embed in the emailed URL)\n//? and otherwise only ever exists in transit.\n//?\n//? CONSUME is atomic. Redemption is a single `MULTI` `GET`+`DEL`, so a token\n//? can be redeemed AT MOST once even under concurrent clicks — the second\n//? caller sees an empty `GET` and is rejected. A wrong / expired / malformed\n//? token also fails closed (returns null).\n//?\n//? The key namespace is routed through `formatKey(namespace, hash)` so the\n//? multi-tenant key formatter (and project-name prefixing) applies uniformly,\n//? identical to every other framework key-site.\n\nimport { createHash, randomBytes } from 'node:crypto';\n\nimport { formatKey } from './redisKeyFormatter';\n// eslint-disable-next-line import-x/no-named-as-default -- redis.ts ships both a default and a `redis` named export; the default is the proxy, matching every other framework key-site\nimport redis from './redis';\nimport tryCatchSync from './tryCatchSync';\n\n//? 32 random bytes -> 64-char hex token (same entropy as the legacy\n//? login reset/email-change tokens, so existing URL shapes are unchanged).\nconst TOKEN_BYTES = 32;\n\n/** sha256(token) hex — the value actually used as the Redis key suffix. */\nconst hashToken = (token: string): string =>\n createHash('sha256').update(token).digest('hex');\n\n/** Build the namespaced Redis key for a token's HASH (never the raw token). */\nconst tokenKey = (namespace: string, token: string): string =>\n formatKey(namespace, hashToken(token));\n\n/**\n * Handle returned by {@link issueOneTimeToken}. `token` is the RAW token to hand\n * to the caller (embed in the emailed URL); call `store()` to persist its hash\n * to Redis with the configured TTL. Persisting is a separate step so the caller\n * can run veto logic (hooks, validation) between minting and committing without\n * leaving an orphan Redis key.\n */\nexport interface OneTimeTokenHandle {\n /** The raw, single-use token. Returned exactly once; never stored verbatim. */\n token: string;\n /** Persist `sha256(token)` -> `payload` in Redis with the issue TTL. */\n store(): Promise<void>;\n}\n\n/**\n * Mint a one-time token in `namespace` carrying `payload`, redeemable for `ttl`\n * seconds. Returns the raw token plus a `store()` committer. The payload is\n * serialised to a string (objects via JSON); on redemption it is returned as a\n * string and the caller parses it (or use {@link consumeOneTimeTokenJson}).\n *\n * @param namespace Key namespace (e.g. `'-pwreset'`, `'-email-change'`) — routed\n * through `formatKey` so the active key formatter / project prefix applies.\n * @param ttlSeconds Time-to-live in seconds for the stored hash.\n * @param payload Value bound to the token. A string is stored verbatim; any\n * other value is `JSON.stringify`-d.\n */\nexport const issueOneTimeToken = (\n namespace: string,\n ttlSeconds: number,\n payload: string | Record<string, unknown>,\n): OneTimeTokenHandle => {\n const token = randomBytes(TOKEN_BYTES).toString('hex');\n const value = typeof payload === 'string' ? payload : JSON.stringify(payload);\n return {\n token,\n store: async (): Promise<void> => {\n await redis.set(tokenKey(namespace, token), value, 'EX', ttlSeconds);\n },\n };\n};\n\n/**\n * Atomically validate + consume a one-time token from `namespace`. Returns the\n * stored payload STRING on success (and deletes the entry, so it can never be\n * reused), or `null` when the token is missing / expired / malformed / already\n * consumed. A single `MULTI` `GET`+`DEL` guarantees at-most-once redemption.\n */\nexport const consumeOneTimeToken = async (\n namespace: string,\n token: string,\n): Promise<string | null> => {\n if (!token || typeof token !== 'string') return null;\n const key = tokenKey(namespace, token);\n const txResult = await redis.multi().get(key).del(key).exec();\n if (!txResult || txResult.length < 2) return null;\n const first = txResult[0];\n if (!first) return null;\n const [getErr, value] = first;\n if (getErr) return null;\n //? Verify the DEL also succeeded (txResult[1][0] is the per-command error slot).\n //? If DEL failed the key was not consumed — return null (fail-closed) so the\n //? token is not considered redeemed and cannot be replayed.\n const second = txResult[1];\n if (!second || second[0]) return null;\n return typeof value === 'string' && value.length > 0 ? value : null;\n};\n\n/**\n * Convenience wrapper over {@link consumeOneTimeToken} that JSON-parses the\n * stored payload into `T`. Returns `null` on miss OR on a malformed JSON\n * payload. Use when the token was issued with an object payload.\n */\nexport const consumeOneTimeTokenJson = async <T>(\n namespace: string,\n token: string,\n): Promise<T | null> => {\n const raw = await consumeOneTimeToken(namespace, token);\n if (raw === null) return null;\n const [parseErr, parsed] = tryCatchSync(() => JSON.parse(raw) as T);\n if (parseErr) return null;\n return parsed;\n};\n\n//? Exported for tests + any consumer that needs to derive the stored key shape\n//? (e.g. an admin tool listing/expiring outstanding tokens) WITHOUT possessing\n//? the raw token is impossible by design — this helper requires the raw token,\n//? matching the at-rest guarantee.\nexport const oneTimeTokenKey = tokenKey;\n"],"mappings":"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","names":["formatter","extractLanguageFromHeader","resolveErrorMessage","normalizeErrorResponse","registry","formatter","registry","provider","registry","registry","createRequire","createRequire","registered","path","path","path","fs","registry","path","fs","socket","createHash","createHash","path","getProjectConfig","createHash","randomBytes","createHash","randomBytes"]}
|
|
1
|
+
{"version":3,"sources":["../src/escapeHtml.ts","../src/peerDeps.ts","../src/testFileConvention.ts","../src/roomNameFormatterRegistry.ts","../src/hooks/registry.ts","../src/localizedNormalizer.ts","../src/errorFormatterRegistry.ts","../src/socketTypes.ts","../src/runtimeMapsRegistry.ts","../src/emailRegistry.ts","../src/localeReloader.ts","../src/errorTrackerIdentity.ts","../src/env.ts","../src/db.ts","../src/clients.ts","../src/dbHealthCheck.ts","../src/redis.ts","../src/redisKeyFormatter.ts","../src/lease.ts","../src/socketRedisAdapter.ts","../src/bootUuid.ts","../src/synchronizedEnvHashes.ts","../src/consoleLog.ts","../src/cookies.ts","../src/paths.ts","../src/socketMiddlewareRegistry.ts","../src/serveAvatars.ts","../src/avatarConfig.ts","../src/processUpload.ts","../src/bindAddress.ts","../src/getParams.ts","../src/extractToken.ts","../src/extractTokenFromRequest.ts","../src/resolveClientIp.ts","../src/sessionProviderRegistry.ts","../src/validateRequest.ts","../src/rateLimiter.ts","../src/runtimeTypeValidation.ts","../src/checkOrigin.ts","../src/cancelRegistry.ts","../src/oneTimeToken.ts"],"sourcesContent":["//? Canonical HTML-escaping helper. Several packages (docs-ui's rendered HTML,\n//? email's layout renderer) each carried their own copy with the same five\n//? entity mappings — this is the single source of truth they migrate to so\n//? the escaping convention can't drift between surfaces.\n//?\n//? Escapes the five characters that are unsafe in HTML text/attribute\n//? contexts: `&`, `<`, `>`, `\"`, `'`. `&` is replaced first so the ampersands\n//? introduced by the other replacements are not double-escaped.\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/**\n * Escape `&`, `<`, `>`, `\"`, `'` for safe interpolation into HTML text nodes\n * and quoted attribute values. Does NOT protect against CSS/JS/URL-context\n * injection — use a dedicated sanitizer for those contexts.\n */\nexport const escapeHtml = (str: string): string =>\n str.replaceAll(/[&<>\"']/g, (c) => HTML_ESCAPE_MAP[c] ?? c);\n","//? Shared optional-peer-dependency guards. The framework's optional adapters\n//? (`@luckystack/email`'s Resend/SMTP senders, `@luckystack/error-tracking`'s\n//? Sentry/PostHog/Datadog adapters) each hand-rolled the same boot-time guard:\n//? `try { localRequire.resolve(pkg) } catch { throw new Error(hint) }`, then a\n//? synchronous `require(pkg)` once present. This centralises both halves so\n//? the message convention and resolution semantics stay identical everywhere.\n//?\n//? CommonJS `require` resolution is relative to the MODULE that owns the\n//? `require` function. A guard called from `@luckystack/email`'s adapter must\n//? resolve `resend` from the consumer's `node_modules`, not core's — so both\n//? helpers accept an optional `requireFn` that callers build with\n//? `createRequire(import.meta.url)` at their own location. When omitted, the\n//? helpers fall back to a `require` based on this module (sufficient inside a\n//? flat monorepo / hoisted install).\n\nimport { createRequire } from 'node:module';\n\n/** Minimal shape of a Node `require` with the `.resolve` member these guards use. */\nexport interface PeerRequire {\n (id: string): unknown;\n resolve: (id: string) => string;\n}\n\n//? `NodeRequire` (the return of `createRequire`) is structurally a superset of\n//? `PeerRequire` — its call signature + `.resolve` satisfy our minimal shape —\n//? so it assigns directly with no cast.\nconst defaultRequire: PeerRequire = createRequire(import.meta.url);\n\n/**\n * Assert that an optional peer package is installed, throwing a consistent,\n * actionable error when it is not. Does NOT load the module — use `loadPeer`\n * for that. Pass `requireFn` (a `createRequire(import.meta.url)` from the\n * calling adapter) so resolution happens from the consumer's perspective.\n *\n * `hint` is appended to the standard \"package X is not installed\" sentence —\n * e.g. `'Run `npm install resend`, or pick a different EmailSender adapter.'`.\n */\nexport const ensurePeerDepInstalled = (\n packageName: string,\n hint: string,\n requireFn: PeerRequire = defaultRequire,\n): void => {\n try {\n requireFn.resolve(packageName);\n } catch {\n throw new Error(\n `The \\`${packageName}\\` package is not installed but a feature that needs it was used. ${hint}`,\n );\n }\n};\n\n/**\n * Synchronously load an optional peer package, asserting it is installed\n * first (so a missing dependency fails with the actionable `hint` rather than\n * a raw `MODULE_NOT_FOUND`). Returns the module typed as `T`. Pass `requireFn`\n * (built with `createRequire(import.meta.url)` at the call site) so resolution\n * + load happen relative to the calling module.\n */\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is a caller-supplied return-shape annotation (typed boundary helper, like a checked cast); single use is intentional\nexport const loadPeer = <T>(\n packageName: string,\n hint: string,\n requireFn: PeerRequire = defaultRequire,\n): T => {\n ensurePeerDepInstalled(packageName, hint, requireFn);\n return requireFn(packageName) as T;\n};\n","//? Which files are TEST files, for every framework surface that discovers\n//? files on disk and then IMPORTS them.\n//?\n//? Route discovery never needed this: `_api/getUser_v1.tests.ts` fails the\n//? `_v<N>.ts` filename regex, so it is excluded for free. The surfaces that\n//? import \"every `.ts` in this folder\" have no such natural filter, and there\n//? are three of them — the overlay loader, the overlay bundler, and server\n//? function injection. A test file sitting in one of those folders is imported\n//? at boot and baked into the production bundle; if it only performs side\n//? effects (registering a stub adapter, say) it silently alters production\n//? behaviour rather than failing loudly.\n//?\n//? Deliberately broader than devkit's route-only `isRouteTestFile` (`.tests.ts`):\n//? consumers write `.test.ts` and `.spec.ts` too, and the point here is to\n//? exclude anything a test runner would pick up.\n\n/** Matches `<name>.test|tests|spec.<ts|tsx|js|jsx|mts|cts|mjs|cjs>`. */\nexport const TEST_FILE_PATTERN = /\\.(?:tests?|spec)\\.(?:[cm]?[jt]sx?)$/i;\n\n/** Directory names that conventionally hold tests and never runtime modules. */\nexport const TEST_DIRECTORY_NAMES = new Set(['__tests__', '__mocks__']);\n\n/**\n * True when the given file name (or path — only the last segment is examined)\n * is a test file by the conventions above.\n */\nexport const isTestFile = (fileNameOrPath: string): boolean => {\n const fileName = fileNameOrPath.split(/[/\\\\]/).pop() ?? fileNameOrPath;\n return TEST_FILE_PATTERN.test(fileName);\n};\n\n/** True when a directory name conventionally holds tests rather than runtime modules. */\nexport const isTestDirectory = (directoryName: string): boolean =>\n TEST_DIRECTORY_NAMES.has(directoryName);\n","//? Optional room-name formatter seam (presence socket-room-formatter handoff).\n//? Socket.io room names are global within an io instance; in a multi-tenant or\n//? multi-feature deployment two features can collide on the same raw room code.\n//? A consumer registers a formatter to namespace room names (e.g. prefix with a\n//? workspace id) at the single point every framework join/leave/broadcast site\n//? routes through — mirroring `registerRedisKeyFormatter` for Redis keys.\n//?\n//? The DEFAULT is identity (return the raw room name unchanged), so a missing\n//? registration keeps today's behavior byte-for-byte. The formatter MUST be a\n//? pure, synchronous function (it runs on the socket hot path) and MUST be\n//? deterministic so join and leave produce the same physical room.\n\nimport { createRegistry } from './createRegistry';\n\nexport interface RoomNameFormatterContext {\n /**\n * Which room FAMILY the name is being formatted for — a formatter may branch on\n * this to isolate genuinely-separate families, but MUST NOT use it to give the\n * same raw room code different physical names across a single family's\n * operations. A content room is joined, left, membership-checked, broadcast to,\n * and streamed to under the SINGLE canonical `'broadcast'` purpose (M5); the\n * framework never varies a content room's physical name by operation, because a\n * socket physically IN `f(R,'join')` could never receive a fanout targeting\n * `f(R,'broadcast')`. `'presence'` is the one genuinely-separate family (its own\n * rooms). `'join'`/`'leave'` remain in the union for backward-compat but the\n * framework no longer emits them for room-NAME formatting — treat them, if ever\n * seen, identically to `'broadcast'`.\n */\n purpose: 'join' | 'leave' | 'broadcast' | 'presence';\n /** Session user id when known (join/leave by an authenticated socket), else null. */\n userId?: string | null;\n}\n\nexport type RoomNameFormatter = (rawRoomName: string, ctx: RoomNameFormatterContext) => string;\n\nconst identityFormatter: RoomNameFormatter = (rawRoomName) => rawRoomName;\n\nconst registry = createRegistry<RoomNameFormatter>(identityFormatter);\n\n/** Override how every framework room name is namespaced (multi-tenant prefixing). */\nexport const registerRoomNameFormatter = (formatter: RoomNameFormatter): void => {\n registry.register(formatter);\n};\n\n/** Read the active formatter (defaults to identity). */\nexport const getRoomNameFormatter = (): RoomNameFormatter => registry.get();\n\n/** Apply the active formatter — the single helper framework room-sites call. */\nexport const formatRoomName = (rawRoomName: string, ctx: RoomNameFormatterContext): string =>\n registry.get()(rawRoomName, ctx);\n\n/** The built-in identity formatter (exported for tests / explicit reset). */\nexport const defaultRoomNameFormatter = identityFormatter;\n","import type {\n HookName,\n HookPayloads,\n HookHandler,\n HookResult,\n HookStopSignal,\n SyncHookHandler,\n SyncHookName,\n SyncHookPayloads,\n} from './types';\nimport { captureException } from '../sentrySetup';\nimport { getLogger } from '../loggerRegistry';\n\nexport type DispatchResult =\n | { stopped: false }\n | { stopped: true; signal: HookStopSignal };\n\n// Internal type — wider than the typed handlers but safe because register/dispatch\n// use the same TName key to guarantee payload shape consistency.\ntype AnyHandler = (payload: unknown) => Promise<HookResult> | HookResult;\n\nconst hookHandlers = new Map<string, AnyHandler[]>();\n\nexport const registerHook = <TName extends HookName>(\n name: TName,\n handler: HookHandler<HookPayloads[TName]>,\n): (() => void) => {\n const existing = hookHandlers.get(name) ?? [];\n const wrapped = handler as AnyHandler;\n existing.push(wrapped);\n hookHandlers.set(name, existing);\n //? Return an unsubscribe that splices THIS exact handler (matching\n //? `onClientHook`'s symmetry). Lets conditional plugins / per-tenant toggles /\n //? hot-reload detach a single handler without `clearAllHooks` nuking the\n //? framework-internal handlers too.\n return () => {\n const handlers = hookHandlers.get(name);\n if (!handlers) return;\n const index = handlers.indexOf(wrapped);\n if (index !== -1) handlers.splice(index, 1);\n };\n};\n\nexport const dispatchHook = async <TName extends HookName>(\n name: TName,\n payload: HookPayloads[TName],\n): Promise<DispatchResult> => {\n const handlers = hookHandlers.get(name) ?? [];\n\n for (const handler of handlers) {\n let result: HookResult;\n try {\n result = await handler(payload);\n } catch (error) {\n // Isolated per hook — one failing handler never interrupts the main flow,\n // but plugin failures must still be visible. Surface to logger + Sentry\n // so installers can spot bugs in their registered handlers.\n //? Wrap the reporter calls themselves (CORE-N10): if the logger or error\n //? tracker throws (misconfigured adapter, serialisation error), we must\n //? not propagate out of `dispatchHook` — that would surface as an\n //? unhandled rejection on the caller and undo the isolation guarantee.\n try { getLogger().error(`hook: handler for \"${name}\" threw`, error, { hook: name }); } catch { /* luckystack-allow: reporter must not break hook isolation */ }\n try { captureException(error, { hook: name }); } catch { /* luckystack-allow */ }\n continue;\n }\n\n if (result !== undefined) {\n return { stopped: true, signal: result };\n }\n }\n\n return { stopped: false };\n};\n\n//? Test-only: drop every registered hook handler. Used by the dev `/_test/reset`\n//? endpoint when integration tests need a clean slate. Never call this from\n//? production code paths — it would silently break framework-internal hooks\n//? (presence post-logout cleanup, etc.).\nexport const clearAllHooks = (): void => {\n hookHandlers.clear();\n syncHookHandlers.clear();\n};\n\n// --- Synchronous hooks ---\n\ntype AnySyncHandler = (payload: unknown) => void;\n\nconst syncHookHandlers = new Map<string, AnySyncHandler[]>();\n\nexport const registerSyncHook = <TName extends SyncHookName>(\n name: TName,\n handler: SyncHookHandler<SyncHookPayloads[TName]>,\n): (() => void) => {\n const existing = syncHookHandlers.get(name) ?? [];\n const wrapped = handler as AnySyncHandler;\n existing.push(wrapped);\n syncHookHandlers.set(name, existing);\n //? Returns an unsubscribe that splices THIS exact handler. See `registerHook`.\n return () => {\n const handlers = syncHookHandlers.get(name);\n if (!handlers) return;\n const index = handlers.indexOf(wrapped);\n if (index !== -1) handlers.splice(index, 1);\n };\n};\n\nexport const dispatchSyncHook = <TName extends SyncHookName>(\n name: TName,\n payload: SyncHookPayloads[TName],\n): void => {\n const handlers = syncHookHandlers.get(name) ?? [];\n for (const handler of handlers) {\n try {\n handler(payload);\n } catch (error) {\n // Isolated per hook — one failing handler never interrupts the main flow.\n //? Same guard as the async hook path (CORE-N10).\n try { getLogger().error(`hook: sync handler for \"${name}\" threw`, error, { hook: name }); } catch { /* luckystack-allow */ }\n try { captureException(error, { hook: name }); } catch { /* luckystack-allow */ }\n }\n }\n};\n","import type {\n ErrorParam,\n ErrorResponseInput,\n NormalizedErrorResponse,\n} from './responseNormalizer';\nimport {\n defaultHttpStatusForResponse,\n normalizeErrorResponseCore,\n} from './responseNormalizer';\nimport { dispatchSyncHook } from './hooks/registry';\n\n//? Factory that turns a project-provided translate function into the full\n//? normalizer surface the framework packages need.\n//?\n//? Before this split, `@luckystack/api` and `@luckystack/sync` imported\n//? `normalizeErrorResponse` from `server/utils/responseNormalizer` via deep\n//? relative paths — a framework-into-project reach-in. Now the project\n//? registers a normalizer on boot and framework code consumes it through\n//? the singleton below.\n\nexport type LanguageCode = string;\n\nexport interface TranslateInput {\n language: LanguageCode;\n key: string;\n params?: ErrorParam[];\n}\n\nexport type TranslateFunction = (input: TranslateInput) => string;\n\nexport interface NormalizeErrorResponseInput {\n response: ErrorResponseInput;\n preferredLocale?: string | null;\n userLanguage?: string | null;\n fallbackHttpStatus?: number;\n}\n\nexport interface LocalizedNormalizer {\n normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;\n resolveErrorMessage: (input: {\n errorCode: string;\n errorParams?: ErrorParam[];\n preferredLocale?: string | null;\n userLanguage?: string | null;\n }) => string;\n extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;\n}\n\nexport interface CreateLocalizedNormalizerInput {\n translate: TranslateFunction;\n /**\n * Language the framework falls back to when the caller provides no\n * language hints. Defaults to 'en'.\n */\n defaultLanguage?: LanguageCode;\n /**\n * Decide whether a given string is an acceptable language code. Defaults\n * to accepting any non-empty short-form code (ISO 639-1 style). Projects\n * that only support a closed set should pass a narrower predicate.\n */\n isSupportedLanguage?: (code: string) => boolean;\n}\n\nconst normalizeLanguageCode = (\n language: string | null | undefined,\n isSupported: (code: string) => boolean,\n): LanguageCode | null => {\n if (!language) return null;\n const short = language.toLowerCase().split('-')[0];\n if (!short) return null;\n return isSupported(short) ? short : null;\n};\n\nexport const createLocalizedNormalizer = (\n input: CreateLocalizedNormalizerInput,\n): LocalizedNormalizer => {\n const defaultLanguage = input.defaultLanguage ?? 'en';\n const isSupported = input.isSupportedLanguage ?? (() => true);\n\n const extractLanguageFromHeader = (header?: string | string[]): LanguageCode | null => {\n if (!header) return null;\n const normalized = Array.isArray(header) ? header.join(',') : header;\n\n const candidates = normalized\n .split(',')\n .map(part => part.trim().split(';')[0])\n .filter(Boolean);\n\n for (const candidate of candidates) {\n const language = normalizeLanguageCode(candidate, isSupported);\n if (language) return language;\n }\n return null;\n };\n\n const resolveLanguage = ({\n preferredLocale,\n userLanguage,\n }: {\n preferredLocale?: string | null;\n userLanguage?: string | null;\n }): LanguageCode => {\n return (\n normalizeLanguageCode(userLanguage, isSupported)\n ?? normalizeLanguageCode(preferredLocale, isSupported)\n ?? defaultLanguage\n );\n };\n\n const resolveErrorMessage = ({\n errorCode,\n errorParams,\n preferredLocale,\n userLanguage,\n }: {\n errorCode: string;\n errorParams?: ErrorParam[];\n preferredLocale?: string | null;\n userLanguage?: string | null;\n }): string => {\n const language = resolveLanguage({ preferredLocale, userLanguage });\n return input.translate({ language, key: errorCode, params: errorParams });\n };\n\n const normalizeErrorResponse = ({\n response,\n preferredLocale,\n userLanguage,\n fallbackHttpStatus,\n }: NormalizeErrorResponseInput): NormalizedErrorResponse => {\n //? `preErrorNormalize` handlers may mutate `payload.response` to remap\n //? error codes (e.g. translate `auth.required` → custom domain code, redact\n //? internal codes for unauth users) before normalization.\n const preNormalize = { response: { ...response }, preferredLocale, userLanguage, fallbackHttpStatus };\n dispatchSyncHook('preErrorNormalize', preNormalize);\n\n const normalized = normalizeErrorResponseCore({\n response: preNormalize.response,\n fallbackHttpStatus: preNormalize.fallbackHttpStatus,\n resolveMessage: ({ errorCode, errorParams }) => resolveErrorMessage({\n errorCode,\n errorParams,\n preferredLocale: preNormalize.preferredLocale,\n userLanguage: preNormalize.userLanguage,\n }),\n });\n\n const finalized: NormalizedErrorResponse = {\n ...normalized,\n httpStatus: defaultHttpStatusForResponse({\n status: 'error',\n explicitHttpStatus: normalized.httpStatus,\n }),\n };\n\n //? `postErrorNormalize` handlers may mutate `payload.normalized` to\n //? post-process the localized envelope (replace messages, scrub fields).\n const postNormalize = { normalized: finalized, preferredLocale: preNormalize.preferredLocale, userLanguage: preNormalize.userLanguage };\n dispatchSyncHook('postErrorNormalize', postNormalize);\n\n return postNormalize.normalized;\n };\n\n return { normalizeErrorResponse, resolveErrorMessage, extractLanguageFromHeader };\n};\n\n//? Module-level singleton so framework packages can resolve the project's\n//? translate wiring without deep-relative imports. The server registers its\n//? normalizer on boot; tests and tooling fall back to the identity default\n//? (returns the errorCode key unchanged as the message).\nconst identityNormalizer: LocalizedNormalizer = createLocalizedNormalizer({\n translate: ({ key }) => key,\n});\n\nlet activeNormalizer: LocalizedNormalizer = identityNormalizer;\nlet normalizerRegistered = false;\n\nexport const registerLocalizedNormalizer = (normalizer: LocalizedNormalizer): void => {\n activeNormalizer = normalizer;\n normalizerRegistered = true;\n};\n\nexport const getLocalizedNormalizer = (): LocalizedNormalizer => activeNormalizer;\n\n//? Used by `verifyBootstrap` to detect the silent-degradation case where the\n//? identity normalizer is in use (passes errorCode through as the message,\n//? no i18n). Production projects should always register a real normalizer.\nexport const isLocalizedNormalizerRegistered = (): boolean => normalizerRegistered;\n\n//? Delegating wrappers for consumers (@luckystack/api, @luckystack/sync, etc.)\n//? that want import-time-safe references. The project registers its\n//? normalizer on boot; these functions always resolve through the singleton\n//? at call time, so there's no import-order fragility.\nexport const normalizeErrorResponse = (\n input: NormalizeErrorResponseInput,\n): NormalizedErrorResponse => activeNormalizer.normalizeErrorResponse(input);\n\nexport const resolveErrorMessage = (input: {\n errorCode: string;\n errorParams?: ErrorParam[];\n preferredLocale?: string | null;\n userLanguage?: string | null;\n}): string => activeNormalizer.resolveErrorMessage(input);\n\nexport const extractLanguageFromHeader = (header?: string | string[]): LanguageCode | null =>\n activeNormalizer.extractLanguageFromHeader(header);\n","//? Error-response formatter registry — lives in core so transport handlers\n//? in @luckystack/api and @luckystack/sync can dispatch the per-route /\n//? global formatter without depending back up on @luckystack/server (which\n//? would create a dependency cycle).\n//?\n//? The framework normalizes errors via `normalizeErrorResponse` (i18n +\n//? envelope shape). Consumers can register a global formatter to add\n//? fields (e.g. supportTicketId, correlationId, deprecated alias keys for\n//? legacy clients), or override per-endpoint by exporting `errorFormatter`\n//? from an `_api/*.ts` or `_sync/*.ts` file — the api/sync handlers read\n//? that export off the runtime apiEntry/syncEntry and apply it through\n//? `applyErrorFormatter` below.\n//?\n//? Resolution order at error time:\n//? 1. Per-endpoint `errorFormatter` export (when the route file declares one).\n//? 2. Global formatter from `registerErrorFormatter(...)`.\n//? 3. Framework default `normalizeErrorResponse`.\n//?\n//? @luckystack/server re-exports `registerErrorFormatter` + types from\n//? here for backward compatibility — `import { registerErrorFormatter }\n//? from '@luckystack/server'` continues to work.\n\nimport { createRegistry } from './createRegistry';\nimport tryCatchSync from './tryCatchSync';\n\nexport interface ErrorFormatterContext {\n /** Resolved route name (`api/billing/getInvoice/v1`, `sync/...`). */\n routeName: string;\n /** Transport that triggered the error. */\n transport: 'socket' | 'http';\n /** Session id when available. */\n userId?: string | null;\n}\n\n/** The normalized error envelope handed to every formatter. */\nexport interface FormatterErrorEnvelope {\n status: 'error';\n errorCode?: string;\n message?: string;\n httpStatus?: number;\n [key: string]: unknown;\n}\n\nexport type ErrorFormatter = (\n error: FormatterErrorEnvelope,\n ctx: ErrorFormatterContext,\n) => Record<string, unknown>;\n\n/**\n * Minimal structural contract every formatter-eligible envelope satisfies:\n * a discriminant `status` plus an open string-index so formatters can read /\n * add arbitrary fields. Generic helpers below constrain to this so transport\n * handlers no longer need `as unknown as` double-casts at the formatter\n * boundary — see `applyErrorFormatter` / `buildFormattedError`.\n */\nexport interface FormatterEnvelope { status?: string;[key: string]: unknown }\n\nconst registry = createRegistry<ErrorFormatter | null>(null);\n\n/**\n * Register a global error-response formatter. Receives the normalized\n * error envelope + context; return a (possibly extended) object that the\n * framework will emit. Pass `null` to unregister.\n *\n * For per-endpoint overrides, export `errorFormatter` from your\n * `_api/foo_v1.ts` file directly — the api/sync handler imports + invokes\n * it through `applyErrorFormatter`.\n */\nexport const registerErrorFormatter = (formatter: ErrorFormatter | null): void => {\n registry.register(formatter);\n};\n\n/** Read the active global formatter (or null). */\nexport const getErrorFormatter = (): ErrorFormatter | null => registry.get();\n\n/**\n * Apply per-route → global → identity formatter chain to a normalized\n * error envelope. Returns the envelope unchanged when `status !== 'error'`\n * so it's safe to wrap every emit call. Errors thrown inside a formatter\n * are caught and logged; the un-formatted envelope is returned in that\n * case to keep the error path crash-resistant.\n */\nexport const applyErrorFormatter = <T extends FormatterEnvelope>(input: {\n response: T;\n routeName: string;\n transport: 'socket' | 'http';\n userId?: string | null;\n perRouteFormatter?: ErrorFormatter | undefined | null;\n}): T => {\n const { response, routeName, transport, userId, perRouteFormatter } = input;\n if (response.status !== 'error') return response;\n\n const ctx: ErrorFormatterContext = { routeName, transport, userId };\n //? After the `status !== 'error'` guard, `response` is an error envelope.\n //? `T` and `FormatterErrorEnvelope` share the open `[key: string]: unknown`\n //? index + `status` discriminant, so this is a single, direct narrowing\n //? assertion (plain `as`, NOT a `as unknown as` double-cast). This is the one\n //? documented envelope→formatter boundary.\n const errorEnvelope = response as FormatterErrorEnvelope;\n\n if (perRouteFormatter) {\n const [error, formatted] = tryCatchSync(() => perRouteFormatter(errorEnvelope, ctx));\n //? A formatter may add/rename fields, so its output is the wider\n //? `Record<string, unknown>`. We return it as `T` because the framework\n //? contract guarantees a formatter receives and returns the SAME logical\n //? error envelope for a given route — the discriminant + required keys are\n //? preserved. This is the single, documented formatter→envelope boundary.\n if (!error && formatted) return formatted as T;\n if (error) {\n console.error(`[errorFormatter] per-route formatter for ${routeName} threw:`, error);\n //? Fall through to global / identity so the error path stays resilient.\n }\n }\n\n const globalFormatter = registry.get();\n if (globalFormatter) {\n const [error, formatted] = tryCatchSync(() => globalFormatter(errorEnvelope, ctx));\n if (!error && formatted) return formatted as T;\n console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);\n return response;\n }\n\n return response;\n};\n","import type { Server as SocketIOServer } from 'socket.io';\n\n//? Shared wire-protocol types for the socket-based API and sync transports.\n//? These used to live in `server/sockets/socket.ts` — moved to core so\n//? framework packages (@luckystack/api, @luckystack/sync) can stop\n//? deep-relative-importing into the project's server directory.\n\nexport interface apiMessage {\n name: string;\n data: object;\n responseIndex: number;\n}\n\nexport interface syncMessage {\n name: string;\n data: object;\n cb: string;\n receiver: string;\n responseIndex?: number;\n ignoreSelf?: boolean;\n}\n\n//? Module-level slot for the running Socket.io server instance. The project\n//? calls `setIoInstance(io)` right after constructing the server; framework\n//? packages call `getIoInstance()` when they need to broadcast.\nlet _ioInstance: SocketIOServer | null = null;\n\nexport const setIoInstance = (io: SocketIOServer | null): void => {\n _ioInstance = io;\n};\n\nexport const getIoInstance = (): SocketIOServer | null => _ioInstance;\n","//? DI surface for generated runtime route maps. The project's\n//? `server/prod/runtimeMaps.ts` knows how to dynamically import per-preset\n//? `generatedApis.<preset>.ts` files and merge devkit overrides — framework\n//? packages don't. They ask core for the current maps at request time, and\n//? core delegates to whichever provider was registered on boot.\n//?\n//? Registration is a side effect of the project importing its own runtimeMaps\n//? module. Default (unregistered) returns empty maps so framework code can\n//? boot in tests without crashing.\n\nimport { createRegistry } from './createRegistry';\n\ntype RuntimeMapRecord = Record<string, unknown>;\n\nexport interface RuntimeApiMapsResult {\n apisObject: RuntimeMapRecord;\n functionsObject: RuntimeMapRecord;\n}\n\nexport interface RuntimeSyncMapsResult {\n syncObject: RuntimeMapRecord;\n functionsObject: RuntimeMapRecord;\n}\n\nexport interface RuntimeMapsProvider {\n getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;\n getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;\n}\n\nconst emptyApi: RuntimeApiMapsResult = { apisObject: {}, functionsObject: {} };\nconst emptySync: RuntimeSyncMapsResult = { syncObject: {}, functionsObject: {} };\n\nconst registry = createRegistry<RuntimeMapsProvider>({\n getRuntimeApiMaps: () => Promise.resolve(emptyApi),\n getRuntimeSyncMaps: () => Promise.resolve(emptySync),\n});\n\nexport const registerRuntimeMapsProvider = (provider: RuntimeMapsProvider): void => {\n registry.register(provider);\n};\n\n//? Used by `verifyBootstrap` to detect the production fail-mode where no\n//? provider got registered (typically because the project forgot to import\n//? its `server/prod/runtimeMaps.ts` side-effect). Without this check, every\n//? api/sync request silently returns `api.notFound` / `sync.notFound`.\nexport const isRuntimeMapsProviderRegistered = (): boolean => registry.isRegistered();\n\nexport const getRuntimeApiMaps = async (): Promise<RuntimeApiMapsResult> =>\n registry.get().getRuntimeApiMaps();\n\nexport const getRuntimeSyncMaps = async (): Promise<RuntimeSyncMapsResult> =>\n registry.get().getRuntimeSyncMaps();\n","//? Email-sender registry. Two registration modes:\n//?\n//? 1. `registerEmailSender(sender)` — single global sender (legacy, kept\n//? for backwards compatibility with consumers who haven't migrated to\n//? the multi-adapter shape).\n//? 2. `registerEmailSenders({ default, transactional, marketing, ... })` —\n//? multiple named senders. Convention: framework code (login's\n//? password-reset, account flows) routes to the `'transactional'` slot\n//? when registered, otherwise falls back to `'default'` or the legacy\n//? single sender.\n//?\n//? Types live here (not in `@luckystack/email`) so framework packages can\n//? type-check against them without depending on the email package — keeping\n//? the email package optional.\n\n/**\n * A single email attachment (email F2). Adapters (Resend, SMTP/Nodemailer, SES)\n * thread these into their provider payload. `content` is the raw bytes\n * (`Buffer`/`Uint8Array`) or a base64 string; provide a `path`/`href` instead\n * for adapters that fetch externally. Shapes are intentionally permissive so a\n * single attachment object works across providers — each adapter maps the\n * fields it supports.\n */\nexport interface EmailAttachment {\n /** File name shown to the recipient (e.g. `invoice.pdf`). */\n filename: string;\n /** Inline bytes or base64 string. Mutually exclusive with `path`/`href`. */\n content?: Buffer | Uint8Array | string;\n /**\n * Local file path the adapter reads (when it supports path-based sends).\n *\n * @security Trusted-input field — do NOT derive from client-supplied data\n * without validation. Framework-level path-traversal or safe-directory checks\n * are NOT applied automatically; callers must reject paths that escape the\n * intended directory (e.g. containing `..` or absolute paths outside a known\n * root) before passing them here.\n */\n path?: string;\n /**\n * Remote URL the adapter fetches (when it supports href-based sends).\n *\n * @security Trusted-input field — do NOT derive from client-supplied data\n * without validation. Only `https:` and `http:` URLs are safe for\n * framework-internal use; arbitrary schemes (e.g. `file:`, `data:`) may be\n * forwarded to nodemailer/Resend without restriction. Validate against an\n * allowlist before passing here.\n */\n href?: string;\n /** MIME type (e.g. `application/pdf`). Adapter may infer from `filename` if omitted. */\n contentType?: string;\n /** `Content-ID` for inline/embedded images referenced by `cid:` in the HTML. */\n cid?: string;\n}\n\nexport interface EmailMessage {\n to: string | string[];\n subject: string;\n html: string;\n text?: string;\n from?: string;\n replyTo?: string;\n cc?: string | string[];\n bcc?: string | string[];\n /**\n * File attachments (email F2). Owned by core (not `@luckystack/email`) so the\n * type is the single source of truth across the framework and every adapter;\n * adapters thread these into their provider's attachment payload. Optional —\n * a message without attachments behaves exactly as before.\n */\n attachments?: EmailAttachment[];\n /**\n * Custom message headers (email F2) — e.g. `X-Entity-Ref-ID`,\n * `List-Unsubscribe`, idempotency keys. Adapters merge these over the headers\n * they set themselves (adapter-reserved headers win to avoid breaking\n * delivery). Optional.\n *\n * @security CR and LF characters in header keys or values enable SMTP header\n * injection. `sendEmail` strips `\\r` and `\\n` from every key and value before\n * passing them to the adapter (EMAIL-O7). Do NOT derive keys or values from\n * unsanitized client input.\n */\n headers?: Record<string, string>;\n}\n\nexport type EmailDeliveryOutcome = 'not-sent' | 'unknown';\n\nexport type EmailResult =\n | { ok: true; id: string }\n | {\n ok: false;\n reason: string;\n cause?: unknown;\n /**\n * `unknown` means the caller stopped waiting after dispatch began; retry\n * only with the same idempotency key. Omitted on legacy/provider failures\n * whose delivery semantics are adapter-specific.\n */\n deliveryOutcome?: EmailDeliveryOutcome;\n };\n\nexport interface EmailSendContext {\n /** Cooperative cancellation. Custom adapters should abort provider I/O when supported. */\n signal: AbortSignal;\n /** Stable caller-provided key reused across retries of the same logical email. */\n idempotencyKey?: string;\n}\n\nexport interface EmailSender {\n /** Adapter identifier for logs + diagnostics (\"console\", \"resend\", \"smtp\", etc.). */\n name: string;\n /**\n * The optional context preserves source compatibility with pre-0.7 adapters.\n * New adapters should honor `signal` and provider-native idempotency where available.\n */\n send: (message: EmailMessage, context?: EmailSendContext) => Promise<EmailResult>;\n}\n\n/**\n * Registry keyed by adapter-slot name. Reserved slots used by the\n * framework:\n *\n * - `'default'` — fallback when no specific slot matches.\n * - `'transactional'` — login password-reset, account-confirmation,\n * invoice-receipt, security-critical emails.\n * - `'marketing'` — newsletters, announcements; consumers MAY route\n * bulk sends here.\n * - `'diagnostics'` — diagnostic-test endpoints (e.g. playground).\n *\n * Custom slot names ('billing', 'support', etc.) are allowed; consumers\n * resolve them explicitly via `sendEmail({ adapter: 'billing', ... })`.\n */\nexport type EmailSenderRegistry = Partial<Record<string, EmailSender>>;\n\nlet activeSender: EmailSender | null = null;\nlet registry: EmailSenderRegistry = {};\n\n/**\n * Register a single global email sender. Kept for backwards compatibility.\n * For new code, prefer `registerEmailSenders({ ... })` which lets the\n * framework route transactional vs. marketing through different adapters.\n */\nexport const registerEmailSender = (sender: EmailSender): void => {\n activeSender = sender;\n //? Mirror into the registry so `getEmailSenderByName('default')` finds\n //? the legacy registration too. Consumers calling both APIs in the same\n //? boot get last-write-wins (multi-API call beats single, or vice versa,\n //? depending on order).\n registry = { ...registry, default: sender };\n};\n\n/**\n * Register multiple named email senders. Replaces the entire registry\n * (last-write-wins). Use this when your app routes different message\n * types through different adapters — e.g. Resend for marketing and SMTP\n * for transactional. The framework picks adapter slots by convention\n * (see `EmailSenderRegistry`); consumer code can pick explicitly via\n * `sendEmail({ adapter: 'foo', ... })`.\n */\nexport const registerEmailSenders = (senders: EmailSenderRegistry): void => {\n registry = { ...senders };\n if (senders.default) {\n activeSender = senders.default;\n }\n};\n\n/** Read the legacy single sender (or the `default` slot of the registry). */\nexport const getEmailSender = (): EmailSender | null => activeSender ?? registry.default ?? null;\n\n/**\n * Read a specific named sender. Falls back to the legacy single sender\n * when the requested slot is absent. Returns null when neither exists.\n * Pass the convention-based slots (`'transactional'`, `'marketing'`,\n * `'diagnostics'`, `'default'`) or any custom slot you registered.\n */\nexport const getEmailSenderByName = (name: string): EmailSender | null => {\n if (registry[name]) return registry[name];\n if (name === 'default' && activeSender) return activeSender;\n return null;\n};\n\n/** Read every registered slot name. */\nexport const listEmailSenderNames = (): string[] => Object.keys(registry);\n\nexport const isEmailSenderRegistered = (): boolean =>\n activeSender !== null || Object.keys(registry).length > 0;\n","//? Locale reloader registry. Lets `@luckystack/devkit`'s hot-reload watcher\n//? trigger a translations reload without taking a relative-path dependency\n//? on the consumer's project tree (the previous design did\n//? `import { reloadLocaleTranslations } from \"../../../server/utils/responseNormalizer\"`,\n//? which only resolved while devkit lived inside this monorepo).\n//?\n//? The consumer's `responseNormalizer.ts` (or whatever owns its translation\n//? loading) calls `registerLocaleReloader(fn)` at boot. The watcher calls\n//? `getLocaleReloader()?.()` whenever a `_locales/*.json` file changes. If\n//? no reloader is registered, the watcher is a no-op for that event.\n\nimport { createRegistry } from './createRegistry';\n\nexport type LocaleReloader = () => void | Promise<void>;\n\nconst registry = createRegistry<LocaleReloader | null>(null);\n\nexport const registerLocaleReloader = (reloader: LocaleReloader): LocaleReloader => {\n registry.register(reloader);\n return reloader;\n};\n\nexport const getLocaleReloader = (): LocaleReloader | null => registry.get();\n","//? SERVER-ONLY error-tracker identity scope. Keep `node:async_hooks` isolated in\n//? this module: the browser-safe `sentrySetup`/`tryCatchClient` path needs the\n//? tracker fan-out registry, but it never needs per-request server identity.\n//? Mixing both concerns in errorTrackerRegistry.ts let tsup coalesce this import\n//? into a shared chunk reached by @luckystack/core/client (VA-02).\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport interface ErrorTrackerUser {\n id?: string;\n email?: string;\n username?: string;\n [key: string]: unknown;\n}\n\n//? ET-02: each request receives a mutable box so identity can be filled after\n//? session lookup while remaining isolated from concurrent request scopes.\ninterface IdentityBox {\n user: ErrorTrackerUser | null;\n}\n\nconst identityStore = new AsyncLocalStorage<IdentityBox>();\n\n/** Open an isolated request identity scope before the first async boundary. */\nexport const runWithErrorTrackerIdentityScope = <T>(fn: () => T): T =>\n identityStore.run({ user: null }, fn);\n\n/** Open an isolated scope with a user that is already known. */\nexport const runWithErrorTrackerIdentity = <T>(\n user: ErrorTrackerUser | null,\n fn: () => T,\n): T => identityStore.run({ user }, fn);\n\n/** Set the user inside the active request scope; no-op outside a scope. */\nexport const setCurrentErrorTrackerIdentity = (user: ErrorTrackerUser | null): void => {\n const box = identityStore.getStore();\n if (box) box.user = user;\n};\n\n/** Read the current request identity, or null outside a scope. */\nexport const getCurrentErrorTrackerIdentity = (): ErrorTrackerUser | null =>\n identityStore.getStore()?.user ?? null;\n","//? @adr 0045\nimport { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { config as loadDotenv, parse as parseDotenv } from 'dotenv';\nimport { z } from 'zod';\n\nimport tryCatchSync from './tryCatchSync';\n\nconst EnvSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n SERVER_IP: z.string().min(1).default('127.0.0.1'),\n SECURE: z.enum(['true', 'false']).default('false'),\n REDIS_HOST: z.string().min(1).default('127.0.0.1'),\n REDIS_PORT: z.string().regex(/^\\d+$/).default('6379'),\n PROJECT_NAME: z.string().min(1).default('luckystack'),\n}).loose();\n\nexport type RuntimeEnv = z.infer<typeof EnvSchema>;\n\nlet cachedEnv: RuntimeEnv | null = null;\n\n//? Single source of truth for which env files the framework loads, in order\n//? (\"later overrides earlier\"). Default `['.env', '.env.local']`. Override at\n//? runtime with the AMBIENT env var `LUCKYSTACK_ENV_FILES` (comma-separated) — it\n//? must be a real environment variable, not a key inside one of the .env files\n//? (those are only read AFTER this list is resolved).\nexport const DEFAULT_ENV_FILES = ['.env', '.env.local'];\n\nexport const getEnvFiles = (): string[] => {\n const override = process.env.LUCKYSTACK_ENV_FILES;\n if (override) {\n const list = override.split(',').map((entry) => entry.trim()).filter(Boolean);\n if (list.length > 0) return list;\n }\n return DEFAULT_ENV_FILES;\n};\n\n//? Opt-in diagnostic (set `LUCKYSTACK_ENV_DEBUG=1`). The layered-env model is\n//? `.env` (shared) -> `.env.local` (local overrides): a later file's value wins,\n//? and a key REMOVED from the later file silently falls back to the earlier\n//? file's value (it is never \"unset\"). That's correct dotenv precedence but a\n//? common footgun — e.g. removing an OAuth key from `.env.local` while a copy\n//? lingers in `.env` keeps the provider enabled. When the flag is on we parse\n//? each file purely (`dotenv.parse`, no `process.env` mutation) and report every\n//? key defined in more than one loaded file, naming the file whose value wins.\nconst reportDuplicateEnvKeys = (files: string[]): void => {\n const seenIn = new Map<string, string[]>();\n for (const file of files) {\n const filePath = path.resolve(process.cwd(), file);\n if (!existsSync(filePath)) continue;\n const parsed = parseDotenv(readFileSync(filePath, 'utf8'));\n for (const key of Object.keys(parsed)) {\n const list = seenIn.get(key) ?? [];\n list.push(file);\n seenIn.set(key, list);\n }\n }\n for (const [key, sources] of seenIn) {\n if (sources.length > 1) {\n const winner = sources.at(-1);\n // eslint-disable-next-line no-console -- runs before the logger registry is wired\n console.warn(`[env] \"${key}\" is defined in ${sources.join(' + ')}; \"${winner}\" wins. Removing it from only one file lets the other file's value resurface.`);\n }\n }\n};\n\n//? Bun auto-loads .env files BEFORE any user code runs; Node does not. That\n//? silently breaks two guarantees of `loadEnvFiles` below:\n//? 1. `LUCKYSTACK_ENV_FILES` is contractually an AMBIENT-only override (see\n//? `getEnvFiles`) — under Bun a key inside `.env` is already in\n//? `process.env` by the time we read it, so it hijacks the file list.\n//? 2. Bun also loads `.env.<mode>` / `.env.<mode>.local`, which this framework\n//? never loads. Because the first file is `override: false`, those values\n//? silently outrank `.env`.\n//? The cure is a `bunfig.toml` with `env = false` (Bun >= 1.3.3), shipped at the\n//? repo root and in the scaffold template. Bun exposes NO way to read that\n//? setting back at runtime (`Bun.config` / `Bun.bunfig` are undefined), so we\n//? detect the SYMPTOM instead: a file we are about to load whose every key is\n//? already in `process.env` with a byte-identical value can only mean the\n//? runtime preloaded it.\nconst isBunRuntime = (): boolean => 'Bun' in globalThis;\n\n//? Mirrors Bun's own mode selection (`BUN_ENV` -> `NODE_ENV`, exact-match\n//? 'production'/'test', anything else -> development) so the warning can NAME\n//? the extra files Bun loaded behind the framework's back.\nconst getBunModeEnvFiles = (): string[] => {\n const mode = process.env.BUN_ENV ?? process.env.NODE_ENV;\n const suffix = mode === 'production' || mode === 'test' ? mode : 'development';\n return [`.env.${suffix}`, `.env.${suffix}.local`];\n};\n\nconst fileExists = (file: string): boolean => existsSync(path.resolve(process.cwd(), file));\n\n//? True when every key the file defines is ALREADY in `process.env` with the\n//? same value. Requires value equality (not mere presence) so a deployment that\n//? legitimately sets a few ambient overrides is not mistaken for auto-load.\nconst isEnvFilePreloaded = (file: string): boolean => {\n const filePath = path.resolve(process.cwd(), file);\n if (!existsSync(filePath)) return false;\n const [error, parsed] = tryCatchSync(() => parseDotenv(readFileSync(filePath, 'utf8')));\n if (error || !parsed) return false;\n const keys = Object.keys(parsed);\n if (keys.length === 0) return false;\n return keys.every((key) => process.env[key] === parsed[key]);\n};\n\n//? Checked at most once, and only BEFORE the first load — after `loadEnvFiles`\n//? has run, our own dotenv writes would make every file look \"preloaded\".\nlet hasCheckedBunEnvAutoload = false;\n\n//? Warns rather than throws on purpose: `bun install` ignores `env = false`\n//? (oven-sh/bun#31450, still open), so a postinstall boot on Bun would be\n//? unfixably fatal. A wrong env value is a config bug, not a security boundary.\nconst warnOnBunEnvAutoload = (files: string[]): void => {\n if (hasCheckedBunEnvAutoload || !isBunRuntime()) return;\n hasCheckedBunEnvAutoload = true;\n\n //? Bun's mode files are the PUREST signal: the framework never loads them, so\n //? one already applied to `process.env` can only have come from Bun. They also\n //? cover the case that hides `.env` from this check — a mode file overriding a\n //? `.env` key means `.env` itself is no longer byte-identical to the env.\n const modeFiles = getBunModeEnvFiles();\n const preloaded = [...files, ...modeFiles].filter((file) => isEnvFilePreloaded(file));\n if (preloaded.length === 0) return;\n\n const shadowed = modeFiles.filter((file) => fileExists(file));\n const shadowNote = shadowed.length > 0\n ? ` Bun also loaded ${shadowed.join(' + ')}, which LuckyStack never loads — those values outrank ${files[0]}.`\n : '';\n\n // eslint-disable-next-line no-console -- runs before the logger registry is wired\n console.warn(`[env] Bun appears to have auto-loaded ${preloaded.join(' + ')} before LuckyStack did, so Bun's precedence wins over the framework's own layering.${shadowNote} LUCKYSTACK_ENV_FILES is also no longer ambient-only: a value set inside a .env file now hijacks the file list. Add \\`env = false\\` to bunfig.toml (Bun >= 1.3.3) to restore Node semantics.`);\n};\n\nexport const loadEnvFiles = (): void => {\n const files = getEnvFiles();\n warnOnBunEnvAutoload(files);\n //? First file is non-override (a real ambient env var wins); each later file\n //? overrides earlier ones, matching the historical `.env` -> `.env.local` order.\n for (const [index, file] of files.entries()) {\n loadDotenv({ path: file, override: index > 0 });\n }\n if (process.env.LUCKYSTACK_ENV_DEBUG) {\n reportDuplicateEnvKeys(files);\n }\n};\n\nconst applyResolvedDefaultsToProcessEnv = (resolvedEnv: RuntimeEnv) => {\n process.env.NODE_ENV = process.env.NODE_ENV ?? resolvedEnv.NODE_ENV;\n process.env.SERVER_IP = process.env.SERVER_IP ?? resolvedEnv.SERVER_IP;\n process.env.SECURE = process.env.SECURE ?? resolvedEnv.SECURE;\n process.env.REDIS_HOST = process.env.REDIS_HOST ?? resolvedEnv.REDIS_HOST;\n process.env.REDIS_PORT = process.env.REDIS_PORT ?? resolvedEnv.REDIS_PORT;\n process.env.PROJECT_NAME = process.env.PROJECT_NAME ?? resolvedEnv.PROJECT_NAME;\n};\n\nexport const bootstrapEnv = (): RuntimeEnv => {\n if (cachedEnv) {\n return cachedEnv;\n }\n\n loadEnvFiles();\n\n //? Zod's `.loose()` preserves consumer-defined keys, but the removed legacy\n //? backend-port key must not survive in the validated snapshot. Server port\n //? resolution is exclusively options/argv/config.ports.ts.\n const envInput = { ...process.env };\n Reflect.deleteProperty(envInput, 'SERVER_PORT');\n const parsed = EnvSchema.safeParse(envInput);\n if (!parsed.success) {\n const details = parsed.error.issues\n .map((issue) => `${issue.path.join('.') || 'env'}: ${issue.message}`)\n .join('; ');\n\n throw new Error(`Invalid environment configuration: ${details}`);\n }\n\n cachedEnv = parsed.data;\n applyResolvedDefaultsToProcessEnv(cachedEnv);\n return cachedEnv;\n};\n\n//? DD-CORE-D2 — eager-load decision (back-compat, kept for 0.x):\n//? `env` is evaluated at MODULE IMPORT time. This is intentional for the 0.x\n//? public API: every file that does `import { env } from '@luckystack/core'`\n//? gets the same frozen, Zod-validated snapshot, and the dotenv layering runs\n//? exactly once at the package boundary regardless of import order.\n//?\n//? Migration path to lazy consumption (0.2+ recommendation):\n//? Use `getEnv()` instead of `env` at call sites where you need to read env\n//? values inside a function body rather than at module scope. `getEnv()` is\n//? identical to `env` (it returns the same cached singleton after the first\n//? call) but it avoids capturing a stale snapshot when `bootstrapEnv` is\n//? called before dotenv files are fully loaded — a common footgun in test\n//? runners that reset `process.env` between cases. New framework code and\n//? consumer code should prefer `getEnv()` at call-time over the `env`\n//? top-level binding.\nexport const env = bootstrapEnv();\nexport const isProduction = env.NODE_ENV === 'production';\n\n//? Lazy accessor that returns the same cached singleton as `env` but defers\n//? the bootstrap to first call. Prefer this over the top-level `env` binding\n//? in functions, class constructors, and anywhere `bootstrapEnv` must not run\n//? at module evaluation time (e.g. unit tests that reset `process.env`).\nexport const getEnv = (): RuntimeEnv => bootstrapEnv();\n\n/** Application runtime mode. Keep separate from `resolveEnvKey()`, which is a\n * deploy-topology identity and may be `staging`, `dockerSplit`, etc. */\nexport const resolveRuntimeMode = (): RuntimeEnv['NODE_ENV'] => {\n const mode = process.env.NODE_ENV;\n return mode === 'production' || mode === 'test' || mode === 'development'\n ? mode\n : getEnv().NODE_ENV;\n};\nexport const isProductionRuntime = (): boolean => resolveRuntimeMode() === 'production';\nexport const isTestRuntime = (): boolean => resolveRuntimeMode() === 'test';\n","import './env';\nimport { createRequire } from 'node:module';\nimport type { PrismaClient } from '@prisma/client';\nimport { getPrismaClient, setDefaultPrismaResolver } from './clients';\nimport tryCatchSync from './tryCatchSync';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __luckystackPrisma: PrismaClient | undefined;\n}\n\n//? '@prisma/client' deliberately left the static-import position (ADR 0020,\n//? the ORM-choice decision): with `orm: 'none'` (or a non-Prisma ORM) the\n//? package is legitimately absent, and an unguarded value-import crashed boot\n//? with a bare ERR_MODULE_NOT_FOUND before any framework error handling\n//? existed. The type-only import above is erased at runtime; the client is\n//? require()d lazily on first default-resolver access instead.\nconst requireFromCore = createRequire(import.meta.url);\n\n/**\n * Is a generated `@prisma/client` present in this install? Used by the\n * default resolver's error path and by `/readyz` to decide whether a missing\n * DB ping means \"broken\" or \"this project has no Prisma by design\".\n */\nexport const isPrismaClientResolvable = (): boolean => {\n const [error] = tryCatchSync(() => requireFromCore.resolve('@prisma/client'));\n return !error;\n};\n\ninterface PrismaClientModuleShape {\n PrismaClient?: new () => PrismaClient;\n}\n\n//? Lazy default: only constructed when the proxy is first accessed AND no\n//? consumer has registered their own Prisma client. Avoids paying the\n//? PrismaClient construction cost in code paths that swap it out at boot.\nconst buildDefaultPrismaClient = (): PrismaClient => {\n if (!globalThis.__luckystackPrisma) {\n const [loadError, loaded] = tryCatchSync(\n () => requireFromCore('@prisma/client') as PrismaClientModuleShape,\n );\n const PrismaClientCtor = loaded?.PrismaClient;\n if (loadError || typeof PrismaClientCtor !== 'function') {\n //? THE `orm: 'none'` error seam (ADR 0020): every DB access without a\n //? registered client lands here with an actionable message instead of a\n //? module-resolution stack trace.\n throw new Error(\n \"[luckystack] no database client is available: '@prisma/client' is not installed/generated \" +\n 'and no client was registered. Register your own database client in ' +\n '`luckystack/core/clients.ts` via registerPrismaClient(...) (any client whose surface your ' +\n 'handlers use) — and swap the login UserAdapter via registerUserAdapter(...) if auth is on — ' +\n 'or install Prisma: `npm i @prisma/client prisma` + `npx prisma generate`. ' +\n \"Projects scaffolded with orm: 'none' are expected to fill in that file.\",\n );\n }\n globalThis.__luckystackPrisma = new PrismaClientCtor();\n }\n return globalThis.__luckystackPrisma;\n};\n\nsetDefaultPrismaResolver(buildDefaultPrismaClient);\n\n//? No eager dev init: the `globalThis.__luckystackPrisma` cache already\n//? guarantees a single Prisma instance across HMR reloads on first proxy\n//? access, so constructing it at import time only burdens tools/CLIs/tests that\n//? type-import core but never touch the DB (the package's \"no import-time side\n//? effects\" doctrine).\n\n//? Proxy forwards every read/call to whichever client is active *at access\n//? time*, so a `registerPrismaClient(...)` performed after this module is\n//? imported still wins. The empty-object Proxy target needs an `as` cast\n//? because the target is never read directly — every access is intercepted.\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Proxy target placeholder\nexport const prisma = new Proxy({} as PrismaClient, {\n //? Bind methods to the real client so a CAPTURED method keeps its `this`.\n //? Without binding, a detached call (`const q = prisma.$queryRaw; q(...)`)\n //? runs with `this === undefined` and Prisma's internal `this`-access throws\n //? — exactly how the `/readyz` prisma ping silently failed (it captures\n //? `$runCommandRaw` / `$queryRaw` into a local before calling, so the ping\n //? always threw → `prisma: false` → 503, even with the DB reachable).\n //? Mirrors the `redis` proxy. Non-function reads (model delegates like\n //? `prisma.user`) pass through untouched, so their methods bind to the\n //? delegate as before.\n get: (_target, prop, _receiver) => {\n const real = getPrismaClient() as object;\n //? Pass `real` (not the Proxy) as the receiver so that getter-backed\n //? private fields (Prisma uses #-private slots internally) look up their\n //? slot on the real target instead of on the Proxy, which has no private\n //? slots and would throw TypeError (CORE-N9).\n const value: unknown = Reflect.get(real, prop, real);\n if (typeof value !== 'function') return value;\n const fn = value as (...args: unknown[]) => unknown;\n return fn.bind(real);\n },\n has: (_target, prop) => Reflect.has(getPrismaClient(), prop),\n ownKeys: () => Reflect.ownKeys(getPrismaClient()),\n getOwnPropertyDescriptor: (_target, prop) =>\n Reflect.getOwnPropertyDescriptor(getPrismaClient(), prop),\n});\n","//? Prisma + Redis client registries. Lets consumers swap the framework's\n//? default clients (TLS, Accelerate, custom logger, sentinel, ...) without\n//? touching framework code.\n//?\n//? Keyed by slot: the reserved `'default'` slot backs every framework\n//? internal (sessions, rate-limit, presence) and the `prisma`/`redis`\n//? proxies. Additional slots are opt-in and only reachable via\n//? `getPrismaClientFor(key)` / `getRedisClientFor(key)`, so a consumer can run\n//? graded credentials side by side — e.g. a read-only client on `'ro'` and a\n//? read-write client on `'rw'`, or one client per tenant.\n//?\n//? `db.ts` and `redis.ts` re-export `prisma` and `redis` as Proxy objects that\n//? forward every operation to the currently registered default client (or a\n//? lazy default if nothing was registered). All existing call sites that do\n//? `import { prisma } from '@luckystack/core'` or `import { redis } from '@luckystack/core'`\n//? keep working unchanged — they resolve the `'default'` slot.\n\nimport type { PrismaClient } from '@prisma/client';\nimport type { Redis as RedisClient } from 'ioredis';\n\n//? Reserved slot consumed by every framework internal and the proxies. A\n//? no-key registration / lookup targets this slot, preserving the original\n//? single-client behavior.\nexport const DEFAULT_CLIENT_KEY = 'default';\n\nconst prismaClients = new Map<string, PrismaClient>();\nconst redisClients = new Map<string, RedisClient>();\n\nexport const registerPrismaClient = (client: PrismaClient, key = DEFAULT_CLIENT_KEY): PrismaClient => {\n prismaClients.set(key, client);\n return client;\n};\n\nexport const registerRedisClient = (client: RedisClient, key = DEFAULT_CLIENT_KEY): RedisClient => {\n redisClients.set(key, client);\n return client;\n};\n\n//? Boot guards check the DEFAULT slot only — a keyed-only registration does\n//? not count as \"the framework has a client\", because internals use default.\nexport const isPrismaClientRegistered = (): boolean => prismaClients.has(DEFAULT_CLIENT_KEY);\nexport const isRedisClientRegistered = (): boolean => redisClients.has(DEFAULT_CLIENT_KEY);\n\n//? Diagnostic: which slots have an explicitly-registered client.\nexport const getPrismaClientKeys = (): string[] => [...prismaClients.keys()];\nexport const getRedisClientKeys = (): string[] => [...redisClients.keys()];\n\n//? `db.ts` and `redis.ts` set these accessors at module load with the lazy\n//? defaults. Splitting registration from default construction keeps a hard\n//? type dependency on `@prisma/client` / `ioredis` out of this file.\nlet defaultPrismaResolver: (() => PrismaClient) | null = null;\nlet defaultRedisResolver: (() => RedisClient) | null = null;\n\nexport const setDefaultPrismaResolver = (resolver: () => PrismaClient): void => {\n defaultPrismaResolver = resolver;\n};\n\nexport const setDefaultRedisResolver = (resolver: () => RedisClient): void => {\n defaultRedisResolver = resolver;\n};\n\n//? Resolve a specific slot. The `'default'` slot falls back to the lazy\n//? default resolver when nothing was registered; any other slot throws when\n//? unregistered — a keyed lookup must NEVER silently hand back the privileged\n//? default client, as that would defeat the point of graded credentials.\nexport const getPrismaClientFor = (key = DEFAULT_CLIENT_KEY): PrismaClient => {\n const client = prismaClients.get(key);\n if (client) return client;\n if (key === DEFAULT_CLIENT_KEY && defaultPrismaResolver) return defaultPrismaResolver();\n throw new Error(\n key === DEFAULT_CLIENT_KEY\n ? 'No Prisma client available. Either register one via registerPrismaClient(), ' +\n 'or import \"@luckystack/core\" from a context where the default resolver was set.'\n : `No Prisma client registered for slot \"${key}\". Register one via registerPrismaClient(client, \"${key}\").`\n );\n};\n\nexport const getRedisClientFor = (key = DEFAULT_CLIENT_KEY): RedisClient => {\n const client = redisClients.get(key);\n if (client) return client;\n if (key === DEFAULT_CLIENT_KEY && defaultRedisResolver) return defaultRedisResolver();\n throw new Error(\n key === DEFAULT_CLIENT_KEY\n ? 'No Redis client available. Either register one via registerRedisClient(), ' +\n 'or import \"@luckystack/core\" from a context where the default resolver was set.'\n : `No Redis client registered for slot \"${key}\". Register one via registerRedisClient(client, \"${key}\").`\n );\n};\n\nexport const getPrismaClient = (): PrismaClient => getPrismaClientFor(DEFAULT_CLIENT_KEY);\nexport const getRedisClient = (): RedisClient => getRedisClientFor(DEFAULT_CLIENT_KEY);\n\n//? Test-only: drop every registered slot. The default resolvers (set once at\n//? module load by db.ts / redis.ts) stay in place.\nexport const resetClientsForTests = (): void => {\n prismaClients.clear();\n redisClients.clear();\n};\n","//? Pluggable database readiness probe (ADR 0020, the ORM-choice decision).\n//? `/readyz` used to hard-wire a Prisma ping, which made a deliberately\n//? Prisma-less project (orm: 'none', or a custom ORM behind\n//? `registerPrismaClient`) permanently report 503. Now:\n//?\n//? - a consumer with a custom data layer registers its own probe from a\n//? `luckystack/core/*.ts` overlay file:\n//? registerDbHealthCheck(async () => { await db.execute('select 1'); return true; });\n//? - with no registration, `/readyz` falls back to the built-in Prisma ping\n//? when `@prisma/client` is resolvable, and reports the check as\n//? 'skipped' when it is not (a DB-less project must be able to go ready).\n\nexport type DbHealthResult = boolean | 'skipped';\nexport type DbHealthCheck = () => Promise<DbHealthResult> | DbHealthResult;\n\nlet activeCheck: DbHealthCheck | null = null;\n\nexport const registerDbHealthCheck = (check: DbHealthCheck): void => {\n activeCheck = check;\n};\n\nexport const getDbHealthCheck = (): DbHealthCheck | null => activeCheck;\n\nexport const isDbHealthCheckRegistered = (): boolean => activeCheck !== null;\n\nexport const resetDbHealthCheckForTests = (): void => {\n activeCheck = null;\n};\n","/* eslint-disable @typescript-eslint/no-misused-promises, @typescript-eslint/require-await */\n\nimport Redis from 'ioredis';\nimport type { Redis as RedisClient } from 'ioredis';\nimport { env } from './env';\nimport { getRedisClient, setDefaultRedisResolver, registerRedisClient, isRedisClientRegistered } from './clients';\nimport { getLogger } from './loggerRegistry';\nimport { applyStrayKeyPrefix } from './redisKeyFormatter';\nimport { registerSecretsResolvedListener } from './secretsResolved';\n\nlet cachedDefault: RedisClient | null = null;\n//? Identity of the default-slot client last installed by THIS module. Secret\n//? rotation may replace only this framework-owned registration; a different\n//? registered identity belongs to the consumer (cluster/sentinel/TLS/custom\n//? retry policy) and must keep precedence across automatic refreshes.\nlet frameworkRegisteredDefault: RedisClient | null = null;\n\n//? Stop reconnecting after this many consecutive failures so a permanently\n//? unreachable / misconfigured Redis (bad creds, wrong host) doesn't hammer\n//? forever. With the capped backoff below the default 50 is ~1 minute of\n//? attempts — long enough to ride out a transient blip, short enough to surface\n//? a real outage. Managed-Redis maintenance windows can exceed a minute, so\n//? both the attempt cap and the backoff ceiling are overridable via env\n//? (read at client-build time, so dotenv timing doesn't capture a stale value):\n//? LUCKYSTACK_REDIS_MAX_RECONNECTS — attempt cap (default 50)\n//? LUCKYSTACK_REDIS_MAX_BACKOFF_MS — per-attempt backoff ceiling (default 2000)\nconst DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS = 50;\nconst DEFAULT_MAX_REDIS_BACKOFF_MS = 2000;\n\nconst readPositiveIntEnv = (name: string, fallback: number): number => {\n const raw = process.env[name];\n if (!raw) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n};\n\n//? Every connection field is read from `process.env` at CALL time, never from\n//? the frozen `env` snapshot. A @luckystack/secret-manager POINTER is replaced\n//? in `process.env` at resolve time, which happens AFTER core's module-eval\n//? bootstrap froze `env` — so the snapshot can still hold the pointer. This\n//? applies to host/port exactly as it does to the credentials: `REDIS_HOST` is\n//? a legal pointer target too, and a pointer passes the Zod `min(1)` check, so\n//? the failure is a silent connect to a host literally named after the pointer.\n//? `getEnv()` is NOT an alternative — `bootstrapEnv` returns a cached singleton,\n//? so it hands back the same stale snapshot.\n//?\n//? The `env.*` fallbacks only guard a caller that cleared `process.env`:\n//? `env.ts` mirrors the resolved Zod defaults for the ordinary runtime keys\n//? back into `process.env`; runtime ports are owned separately by\n//? config.ports.ts plus the typed positional-override registry. An EMPTY string must fall\n//? back to the validated snapshot rather than reach\n//? ioredis as a blank host/port — hence the explicit emptiness check (`??`\n//? would let `''` win, which is why this is not a nullish-coalescing site).\nconst readEnvOrSnapshot = (raw: string | undefined, fallback: string): string =>\n raw === undefined || raw === '' ? fallback : raw;\n\nconst readRedisHost = (): string => readEnvOrSnapshot(process.env.REDIS_HOST, env.REDIS_HOST);\nconst readRedisPort = (): number => Number.parseInt(readEnvOrSnapshot(process.env.REDIS_PORT, env.REDIS_PORT), 10);\n\n//? Build a fresh ioredis client from the CURRENT env (read at call time so\n//? dotenv / secret-manager timing doesn't capture a stale value).\nconst constructRedisClient = (): RedisClient => {\n const maxReconnectAttempts = readPositiveIntEnv('LUCKYSTACK_REDIS_MAX_RECONNECTS', DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS);\n const maxBackoffMs = readPositiveIntEnv('LUCKYSTACK_REDIS_MAX_BACKOFF_MS', DEFAULT_MAX_REDIS_BACKOFF_MS);\n\n const client = new Redis({\n host: readRedisHost(),\n port: readRedisPort(),\n ...(process.env.REDIS_USER && { username: process.env.REDIS_USER }),\n ...(process.env.REDIS_PASSWORD && { password: process.env.REDIS_PASSWORD }),\n\n retryStrategy(times) {\n if (times > maxReconnectAttempts) {\n getLogger().error(\n `Redis unreachable after ${String(maxReconnectAttempts)} reconnect attempts; giving up. Check REDIS_HOST / REDIS_PORT / credentials and that Redis is reachable. Raise LUCKYSTACK_REDIS_MAX_RECONNECTS for longer outage tolerance.`,\n );\n return null;\n }\n return Math.min(times * 50, maxBackoffMs);\n },\n });\n\n //? `ready` (not `connect`): `connect` fires on TCP connect BEFORE the AUTH\n //? handshake, so logging there reports success even when credentials are\n //? wrong. `ready` fires only after AUTH succeeds.\n client.on('ready', async () => {\n getLogger().info('Connected to Redis');\n });\n\n client.on('error', (err) => {\n //? CORE-1: a very common, cryptic failure is an auth error whose password\n //? is still a @luckystack/secret-manager POINTER (e.g. `REDIS_PASSWORD_V1`)\n //? because the default client was constructed — and cached — during an\n //? early import-scan, BEFORE secret-manager init resolved the pointer. Turn\n //? the raw `WRONGPASS` into an actionable message instead.\n const message = String((err as { message?: string } | undefined)?.message ?? err);\n const password = process.env.REDIS_PASSWORD ?? '';\n if (/WRONGPASS|NOAUTH|invalid username-password/i.test(message) && /_V\\d+$/i.test(password)) {\n getLogger().error(\n `Redis auth failed and REDIS_PASSWORD (\"${password}\") looks like an UNRESOLVED secret-manager pointer. ` +\n '`@luckystack/secret-manager` now rebuilds this client automatically after it resolves secrets, so this usually ' +\n 'means REDIS_PASSWORD is NOT in the secret-manager `envNames` allowlist (unset resolves nothing) — add it. ' +\n 'If you resolve secrets by other means, call `rebuildDefaultRedisClient()` (or ' +\n '`registerRedisClient(new Redis(getRedisConnectionOptions()))`) after resolution. ' +\n 'See docs/luckystack/ARCHITECTURE_SECRET_MANAGER.md.',\n err,\n );\n return;\n }\n getLogger().error('Error connecting to Redis', err);\n });\n\n return client;\n};\n\nconst buildDefaultRedisClient = (): RedisClient => {\n if (cachedDefault) return cachedDefault;\n cachedDefault = constructRedisClient();\n return cachedDefault;\n};\n\n//? Drop (and disconnect) the cached default Redis client so the NEXT resolve\n//? rebuilds it. Kept for consumers that resolve secrets by other means, but note\n//? it is INSUFFICIENT on its own for the secret-manager pointer case: it only\n//? nulls `cachedDefault` and DEFERS the rebuild, so a stale value can resurface.\n//? Prefer `rebuildDefaultRedisClient()` (eager replace) — the framework uses it.\nexport const resetDefaultRedisClient = (): void => {\n if (!cachedDefault) return;\n const previous = cachedDefault;\n cachedDefault = null;\n try {\n previous.disconnect();\n } catch {\n //? Best-effort — a client that never connected can throw on disconnect.\n }\n};\n\n//? EAGER REPLACE (the secret-manager pointer fix): build a fresh default client\n//? from the CURRENT (resolved) env and REGISTER it into the default slot so it\n//? wins over the lazy resolver. This is what `resetDefaultRedisClient()` alone\n//? could not do — a plain reset defers the rebuild (a stale/pointer value can\n//? resurface by the time it's lazily rebuilt) and never overrides a client that\n//? was already registered. Capturing the resolved secret NOW, into a registered\n//? client, is immune to that. Disconnects the previous default first so rotation\n//? doesn't leak a connection. The framework calls this from the secrets-resolved\n//? hook below (fired by `@luckystack/secret-manager` after every resolve).\nexport const rebuildDefaultRedisClient = (): RedisClient => {\n const previousRegistered = isRedisClientRegistered() ? getRedisClient() : null;\n resetDefaultRedisClient();\n const client = constructRedisClient();\n registerRedisClient(client);\n frameworkRegisteredDefault = client;\n if (previousRegistered && previousRegistered !== client) {\n try {\n previousRegistered.disconnect();\n } catch {\n //? Best-effort — a client that never connected can throw on disconnect.\n }\n }\n return client;\n};\n\nsetDefaultRedisResolver(buildDefaultRedisClient);\n\n//? CORE-1 (decoupled hook): when a secret resolver reports that secrets were\n//? (re)resolved — `@luckystack/secret-manager` fires this automatically after\n//? every resolve (via the global-symbol channel in `secretsResolved.ts`) —\n//? EAGERLY REBUILD + REGISTER the default client from the now-resolved env if a\n//? `REDIS_` credential changed. Registering (not resetting) is what lets a project\n//? run Redis auth via a secret-manager POINTER with zero consumer code: the\n//? resolved secret is captured immediately into a registered client that wins over\n//? the resolver. `undefined` (caller doesn't know which keys changed) rebuilds\n//? defensively.\nregisterSecretsResolvedListener((changedKeys) => {\n if (changedKeys !== undefined && !changedKeys.some((key) => /^REDIS_/i.test(key))) return;\n\n const registered = isRedisClientRegistered() ? getRedisClient() : null;\n if (registered && registered !== frameworkRegisteredDefault) {\n //? Consumer-owned clients win by registry contract. The generic\n //? secrets-resolved channel still notifies consumer listeners, so their\n //? own rotation hook can construct the right cluster/sentinel/TLS client.\n return;\n }\n rebuildDefaultRedisClient();\n});\n\n//? Single source of truth for Redis connection params. Used by\n//? `@luckystack/router`'s bootHandshake (which opens its own short-lived\n//? connections for cross-env probes) so a config rename touches only this\n//? file. Reads env at call time so dotenv timing doesn't capture a stale\n//? value.\nexport interface RedisConnectionOptions {\n host: string;\n port: number;\n username?: string;\n password?: string;\n}\n\nexport const getRedisConnectionOptions = (): RedisConnectionOptions => ({\n host: readRedisHost(),\n port: readRedisPort(),\n ...(process.env.REDIS_USER ? { username: process.env.REDIS_USER } : {}),\n ...(process.env.REDIS_PASSWORD ? { password: process.env.REDIS_PASSWORD } : {}),\n});\n\n//? Single-key commands whose FIRST argument is the key. The proxy runs that\n//? argument through `applyStrayKeyPrefix` as a best-effort namespace net for\n//? raw app keys; already-namespaced keys (anything containing `:`, which is\n//? every framework key) pass through untouched, so framework behavior is\n//? unchanged. Variadic / non-arg0-key commands (`del`, `scan`, `eval`,\n//? `multi`, `mget`, ...) are deliberately excluded — their key positions can't\n//? be inferred safely; those call sites use `formatKey()` explicitly.\nconst STRAY_PREFIX_COMMANDS = new Set<string>([\n 'get', 'set', 'setex', 'psetex', 'setnx', 'getset', 'getdel', 'append', 'strlen',\n 'expire', 'pexpire', 'expireat', 'pexpireat', 'persist', 'ttl', 'pttl', 'type',\n 'incr', 'incrby', 'incrbyfloat', 'decr', 'decrby',\n 'sadd', 'srem', 'smembers', 'scard', 'sismember', 'spop', 'srandmember',\n 'hget', 'hset', 'hdel', 'hgetall', 'hkeys', 'hvals', 'hexists', 'hincrby', 'hmget', 'hmset',\n 'lpush', 'rpush', 'lpop', 'rpop', 'llen', 'lrange', 'lrem',\n 'zadd', 'zrem', 'zscore', 'zrange', 'zrank', 'zcard',\n 'getbit', 'setbit', 'bitcount',\n]);\n\n//? Variadic commands whose EVERY string argument is a key (`del('a','b')`,\n//? `mget('a','b')`). The single-key `STRAY_PREFIX_COMMANDS` set only prefixes\n//? arg0, which left these asymmetric: `redis.set('flag', v)` wrote\n//? `<project>:flag` while `redis.del('flag')` targeted the unprefixed `flag`\n//? and silently no-op'd. Prefixing every string arg here closes that footgun\n//? for revocation-style stray keys. (`eval`/`scan`/`multi` stay excluded —\n//? their key positions can't be inferred and framework call sites already use\n//? `formatKey()` there.)\nconst ALL_ARGS_ARE_KEYS_COMMANDS = new Set<string>([\n 'del', 'unlink', 'exists', 'touch', 'mget',\n]);\n\n//? Extend the set of single-key, arg0-is-key commands the `redis` proxy runs\n//? through `applyStrayKeyPrefix`. Additive — the built-in set above is always\n//? retained — so a consumer using a Redis command the framework's default\n//? list doesn't cover (a newer/module command whose first argument is the\n//? key) can opt it into the multi-tenant stray-prefix net without forking.\n//? Command names are matched case-insensitively, mirroring the proxy lookup.\n//? Variadic / non-arg0-key commands must NOT be registered here — their key\n//? positions can't be inferred safely; use `formatKey()` explicitly instead.\nexport const registerStrayPrefixCommand = (...commands: string[]): void => {\n for (const command of commands) {\n STRAY_PREFIX_COMMANDS.add(command.toLowerCase());\n }\n};\n\n//? Proxy: same pattern as `prisma`. Defers resolution until call time so\n//? `registerRedisClient(...)` can still win after this module is loaded.\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Proxy target placeholder\nconst redisProxy = new Proxy({} as RedisClient, {\n get: (_target, prop, _receiver) => {\n const real = getRedisClient() as object;\n //? Pass `real` as receiver (CORE-N9) — see the same fix in `db.ts`.\n const value: unknown = Reflect.get(real, prop, real);\n if (typeof value !== 'function') return value;\n const fn = value as (...args: unknown[]) => unknown;\n const command = typeof prop === 'string' ? prop.toLowerCase() : '';\n if (command && ALL_ARGS_ARE_KEYS_COMMANDS.has(command)) {\n return (...args: unknown[]): unknown => {\n const prefixed = args.map((arg) => (typeof arg === 'string' ? applyStrayKeyPrefix(arg) : arg));\n return fn.apply(real, prefixed);\n };\n }\n if (command && STRAY_PREFIX_COMMANDS.has(command)) {\n return (...args: unknown[]): unknown => {\n if (args.length > 0 && typeof args[0] === 'string') {\n args[0] = applyStrayKeyPrefix(args[0]);\n }\n return fn.apply(real, args);\n };\n }\n return fn.bind(real);\n },\n has: (_target, prop) => Reflect.has(getRedisClient(), prop),\n});\n\nexport const redis = redisProxy;\nexport default redisProxy;\n","//? Redis key formatter — the single, registerable authority for how every\n//? framework Redis key is namespaced. Replaces the per-call-site\n//? `${getProjectName()}-<area>:<id>` convention with one injectable hook, so a\n//? multi-tenant consumer can prefix keys per tenant (e.g. read a tenant id\n//? from AsyncLocalStorage) from ONE place instead of patching ~9 call sites.\n//?\n//? Contract: `formatKey(namespace, suffix)` builds a key. The DEFAULT\n//? formatter reproduces the framework's historical key bytes EXACTLY, so\n//? upgrading without registering a custom formatter requires NO key migration.\n//? A namespace beginning with a separator (`-` or `:`) is appended verbatim —\n//? this is how the framework preserves its legacy shapes (`-session`,\n//? `:rate-limit`); any other namespace is colon-joined for clean app keys\n//? (`formatKey('rag', id)` -> `<project>:rag:<id>`).\n//?\n//? Custom formatters MUST keep the `<namespace-root>:<suffix>` join, otherwise\n//? the framework's session/rate-limit `SCAN` enumeration (which derives its\n//? match pattern from `formatKey(namespace, '')`) cannot find the keys.\n\nimport { getProjectName } from './projectConfig';\n\nexport type RedisKeyFormatter = (namespace: string, suffix: string) => string;\n\nexport const defaultRedisKeyFormatter: RedisKeyFormatter = (namespace, suffix) => {\n const root = /^[-:]/.test(namespace) ? `${getProjectName()}${namespace}` : `${getProjectName()}:${namespace}`;\n return suffix === '' ? root : `${root}:${suffix}`;\n};\n\nlet formatter: RedisKeyFormatter | null = null;\n\n//? Register a custom key formatter (call once at boot, before the first Redis\n//? key is built). Last-write-wins.\nexport const registerRedisKeyFormatter = (fn: RedisKeyFormatter): void => {\n formatter = fn;\n};\n\nexport const getRedisKeyFormatter = (): RedisKeyFormatter => formatter ?? defaultRedisKeyFormatter;\n\n//? Test-only — restore the default formatter between scenarios.\nexport const resetRedisKeyFormatterForTests = (): void => {\n formatter = null;\n};\n\n//? Build a namespaced Redis key through the active formatter. Suffix is\n//? optional so callers can produce just the namespace root (used to derive\n//? SCAN match patterns: `${formatKey('-session', '')}:*`).\nexport const formatKey = (namespace: string, suffix = ''): string =>\n getRedisKeyFormatter()(namespace, suffix);\n\n//? Best-effort prefix for stray, un-namespaced keys passed to the `redis`\n//? proxy's single-key commands. A key that already contains a `:` (every\n//? framework key, plus any app key the caller chose to namespace) passes\n//? through unchanged, so this can never double-prefix or corrupt a real key.\n//? Project-level only — per-tenant scoping is the formatter's job, not the\n//? net's (the net has no request context).\nexport const applyStrayKeyPrefix = (key: string): string =>\n key.includes(':') ? key : `${getProjectName()}:${key}`;\n","//? Redis lease primitive — a minimal single-writer / leader-ownership building\n//? block (SET NX PX + owner-checked Lua release/renew). Lets a multi-instance\n//? deployment elect ONE owner for a host-bound resource (orchestrator, serial\n//? indexer, cron) without building fencing from scratch. Redis is already a\n//? hard dependency.\n//?\n//? Scope: this is a single-Redis, best-effort lock (NOT Redlock / distributed\n//? consensus). A client whose event loop stalls past the TTL can have its\n//? lease expire and be handed to another owner — size the TTL accordingly and\n//? renew well before expiry. For strict correctness across a Redis failover,\n//? use a dedicated coordinator (etcd/Zookeeper). The lease is a PRIMITIVE; the\n//? leader-election RUNTIME (the renew loop, what to do on loss) is app code.\n\nimport { randomBytes } from 'node:crypto';\nimport { redis } from './redis';\nimport tryCatch from './tryCatch';\nimport { formatKey } from './redisKeyFormatter';\n\nconst leaseKey = (name: string): string => formatKey('lease', name);\n\n//? Owner-checked compare-and-pexpire: only the current holder may extend.\nconst RENEW_SCRIPT = `\nif redis.call('get', KEYS[1]) == ARGV[1] then\n return redis.call('pexpire', KEYS[1], ARGV[2])\nelse\n return 0\nend\n`;\n\n//? Owner-checked compare-and-delete: releasing someone else's lease is a no-op,\n//? so a stale holder can't free a lease another owner has already taken.\nconst RELEASE_SCRIPT = `\nif redis.call('get', KEYS[1]) == ARGV[1] then\n return redis.call('del', KEYS[1])\nelse\n return 0\nend\n`;\n\n/**\n * Acquire an exclusive lease named `name` for `ttlMs` milliseconds. Returns an\n * opaque owner token on success, or `null` when another owner already holds it.\n * `SET key token PX ttl NX` is atomic, so concurrent acquirers race safely.\n * Keep the returned token to `renewLease` / `releaseLease`.\n */\nexport const acquireLease = async (name: string, ttlMs: number): Promise<string | null> => {\n const token = randomBytes(16).toString('hex');\n const [error, result] = await tryCatch(async () =>\n redis.set(leaseKey(name), token, 'PX', ttlMs, 'NX'),\n );\n if (error) return null;\n return result === 'OK' ? token : null;\n};\n\n/**\n * Extend a held lease by `ttlMs` (sliding ownership). Returns true when the\n * supplied `token` still owns the lease and the TTL was applied, false\n * otherwise (lost ownership, expired, or Redis error).\n */\nexport const renewLease = async (name: string, token: string, ttlMs: number): Promise<boolean> => {\n const [error, result] = await tryCatch(async () =>\n redis.eval(RENEW_SCRIPT, 1, leaseKey(name), token, String(ttlMs)),\n );\n if (error) return false;\n return result === 1;\n};\n\n/**\n * Release a held lease. Owner-checked: only succeeds (returns true) when\n * `token` is the current holder. Returns false on mismatch or Redis error.\n */\nexport const releaseLease = async (name: string, token: string): Promise<boolean> => {\n const [error, result] = await tryCatch(async () =>\n redis.eval(RELEASE_SCRIPT, 1, leaseKey(name), token),\n );\n if (error) return false;\n return result === 1;\n};\n","import type { Server as SocketIOServer } from 'socket.io';\nimport type { Redis as RedisClient } from 'ioredis';\nimport { createAdapter } from '@socket.io/redis-adapter';\nimport type { RedisAdapterOptions } from '@socket.io/redis-adapter';\nimport { redis } from './redis';\nimport { getLogger } from './loggerRegistry';\n\nexport interface AttachSocketRedisAdapterOptions {\n /**\n * Options forwarded to `@socket.io/redis-adapter`'s `createAdapter`\n * (e.g. `key` prefix, `requestsTimeout`, `publishOnSpecificResponseChannel`).\n */\n adapterOptions?: Partial<RedisAdapterOptions>;\n /**\n * Supply a pre-built pub client instead of `redis.duplicate()`. Use this for\n * a dedicated adapter connection, custom auth, or a sentinel/cluster handle.\n * When omitted the framework duplicates the default `redis` handle.\n */\n pubClient?: RedisClient;\n /** Pre-built sub client (see `pubClient`). */\n subClient?: RedisClient;\n}\n\n//? Cross-instance Socket.io pub/sub. Without this, room broadcasts only reach\n//? clients connected to the same process. The router can route a client to\n//? service instance A, but a sync event fired from instance B never reaches\n//? them unless both instances share a pub/sub channel.\n//?\n//? Uses `redis.duplicate()` rather than the main handle because ioredis in\n//? subscribe mode blocks non-pub/sub commands on that connection. Pass\n//? `pubClient`/`subClient` to override (e.g. a separate adapter connection),\n//? and `adapterOptions` to tune the underlying `createAdapter`.\nexport const attachSocketRedisAdapter = (\n io: SocketIOServer,\n options: AttachSocketRedisAdapterOptions = {},\n): void => {\n const pubClient = options.pubClient ?? redis.duplicate();\n const subClient = options.subClient ?? redis.duplicate();\n\n pubClient.on('error', (err) => {\n getLogger().error('[socket-redis-adapter] pub client error', err);\n });\n subClient.on('error', (err) => {\n getLogger().error('[socket-redis-adapter] sub client error', err);\n });\n\n io.adapter(createAdapter(pubClient, subClient, options.adapterOptions));\n};\n","import { randomUUID } from 'node:crypto';\nimport { redis } from './redis';\nimport { getDeployConfig } from './deployConfigRegistry';\nimport { getLogger } from './loggerRegistry';\n\n//? @adr 0036\n//? Writes a fresh UUID to `luckystack:boot:<envKey>` on startup. Any router\n//? in another env that's truly sharing this Redis can read the same key.\n//? Router boot handshake cross-checks against the /_health endpoint to catch\n//? the \"two Redis URLs that both respond\" failure mode.\n\n//? Single source of truth — also consumed by `@luckystack/router`'s\n//? `bootHandshake.ts` so the prefix can never drift between writer and reader.\nexport const BOOT_KEY_PREFIX = 'luckystack:boot:';\nconst DEFAULT_BOOT_KEY_TTL_SECONDS = 3600;\n\nconst getBootKeyTtlSeconds = (): number =>\n getDeployConfig().routing?.bootKeyTtlSeconds ?? DEFAULT_BOOT_KEY_TTL_SECONDS;\n\nconst getBootUuidRefreshIntervalMs = (): number =>\n Math.max(100, Math.floor((getBootKeyTtlSeconds() * 1000) / 3));\n\nexport const resolveEnvKey = (): string => {\n return process.env.LUCKYSTACK_ENV ?? process.env.NODE_ENV ?? 'development';\n};\n\nexport const writeBootUuid = async (envKey?: string): Promise<string> => {\n const key = envKey ?? resolveEnvKey();\n const uuid = randomUUID();\n await redis.set(`${BOOT_KEY_PREFIX}${key}`, uuid, 'EX', getBootKeyTtlSeconds());\n return uuid;\n};\n\nexport const readBootUuid = async (envKey?: string): Promise<string | null> => {\n const key = envKey ?? resolveEnvKey();\n return redis.get(`${BOOT_KEY_PREFIX}${key}`);\n};\n\n/**\n * Extends the current environment boot UUID without rotating its value.\n *\n * Every healthy backend may renew the environment-level key, so multi-instance\n * deployments do not depend on whichever process happened to write it last. If\n * Redis recovered after the key expired, the first refresher writes a new UUID.\n */\nexport const refreshBootUuid = async (envKey?: string): Promise<void> => {\n const key = envKey ?? resolveEnvKey();\n const refreshed = await redis.expire(`${BOOT_KEY_PREFIX}${key}`, getBootKeyTtlSeconds());\n if (refreshed === 0) await writeBootUuid(key);\n};\n\nexport interface BootUuidHeartbeat {\n stop: () => void;\n}\n\n/**\n * Keeps the Redis boot UUID alive for as long as this backend is running.\n *\n * The self-rescheduling timeout waits for each Redis operation to settle before\n * scheduling another, so an outage cannot accumulate overlapping refreshes.\n * The timer is unref'd and therefore never keeps an otherwise-finished process\n * alive. Failures are logged and retried on the next interval.\n */\nexport const startBootUuidHeartbeat = (envKey?: string): BootUuidHeartbeat => {\n const key = envKey ?? resolveEnvKey();\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const schedule = (): void => {\n if (stopped) return;\n timer = setTimeout(() => {\n timer = null;\n void refreshBootUuid(key).then(\n () => { schedule(); },\n (error: unknown) => {\n getLogger().error(\n `[boot-uuid] failed to refresh ${BOOT_KEY_PREFIX}${key}; readiness may degrade if the TTL expires`,\n error instanceof Error ? error : new Error(String(error)),\n );\n schedule();\n },\n );\n }, getBootUuidRefreshIntervalMs());\n timer.unref();\n };\n\n schedule();\n\n return {\n stop: () => {\n stopped = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n },\n };\n};\n","import { createHash, createHmac } from 'node:crypto';\nimport { getDeployConfig } from './deployConfigRegistry';\nimport { getProjectConfig } from './projectConfig';\nimport type { HealthHashConfig } from './projectConfig';\n\n\n//? Cross-env consistency check: when env A declares `fallback: B`, any env\n//? vars listed under `synchronizedEnvKeys` on their shared resource must hold\n//? identical values on both deployments — otherwise sessions encrypted by\n//? one side can't be decrypted by the other.\n//?\n//? Secrets never leave the deployment in the clear. `/_health` returns hashes\n//? of each synchronized value; the router compares hashes across envs. If\n//? hashes match, values match. If they don't, the deployments are out of sync\n//? and the router either warns or refuses to start (per `strictBootHandshake`).\n//?\n//? SEC-13: the historical hash was an UNSALTED `sha256(value)` — unauthenticated\n//? `/_health` then exposed a stable, dictionary-attackable fingerprint of each\n//? synchronized secret. `http.healthHash` now lets a deployment salt/HMAC the\n//? value; BOTH the backend (`/_health`) and the router (compare) MUST use the\n//? same `{ mode, salt }`, so {@link resolveHealthHashConfig} +\n//? {@link hashSynchronizedValueWith} are exported for the router to reuse. The\n//? 0.2.0 DEFAULT is `{ mode: 'hmac', salt: '@bootUuid' }` — the fingerprint is\n//? HMAC-keyed on the per-boot UUID, so both sides MUST resolve the salt against\n//? the SAME boot UUID (the backend's own; the router reads it from /_health).\n\nconst plainHash = (value: string): string =>\n createHash('sha256').update(value).digest('hex');\n\n/**\n * Hash a synchronized value under an EXPLICIT `{ mode, salt }` (SEC-13). The\n * shared primitive the router calls with the same config the backend used, so\n * the cross-env compare still holds:\n * - `'plain'` → `sha256(value)` (today's behavior; `salt` ignored).\n * - `'salted'` → `sha256(salt + value)`.\n * - `'hmac'` → `HMAC-SHA256(key=salt, value)`.\n * A `'salted'`/`'hmac'` mode with an empty resolved salt falls back to `'plain'`\n * (and is the caller's responsibility to avoid) rather than silently hashing\n * with an empty key.\n */\nexport const hashSynchronizedValueWith = (\n config: Pick<HealthHashConfig, 'mode'> & { salt: string },\n value: string,\n): string => {\n const { mode, salt } = config;\n if (mode === 'plain' || salt.length === 0) return plainHash(value);\n if (mode === 'salted') return createHash('sha256').update(salt).update(value).digest('hex');\n return createHmac('sha256', salt).update(value).digest('hex');\n};\n\n/**\n * Resolve the effective health-hash config from `http.healthHash`, expanding the\n * `'@bootUuid'` salt sentinel to the supplied boot UUID. Honoured for BOTH\n * `'salted'` and `'hmac'` (the 0.2.0 default is `hmac` + `'@bootUuid'`, so the\n * per-boot UUID becomes the HMAC key — the synchronized-env fingerprint rotates\n * every restart instead of being a stable, dictionary-attackable `sha256`). The\n * server passes its current boot UUID; the router, comparing, passes the same\n * value it read from the backend's handshake. When the sentinel is in play but\n * no boot UUID is available the config collapses to `'plain'` so the cross-env\n * compare never silently diverges.\n */\nexport const resolveHealthHashConfig = (\n bootUuid?: string | null,\n): { mode: HealthHashConfig['mode']; salt: string } => {\n const { mode, salt } = getProjectConfig().http.healthHash;\n if (salt === '@bootUuid') {\n if ((mode === 'salted' || mode === 'hmac') && bootUuid && bootUuid.length > 0) {\n return { mode, salt: bootUuid };\n }\n return { mode: 'plain', salt: '' };\n }\n return { mode, salt };\n};\n\n/**\n * Safe-to-expose descriptor of the resolved health-hash config. The router needs\n * to hash with the SAME `{ mode, salt }` the backend used, but a static `salt`\n * (mode `'salted'`/`'hmac'` with a non-sentinel salt) is a SECRET and must never\n * leave the backend. So `/_health` exposes only the `mode` + whether the salt is\n * the `'@bootUuid'` sentinel (the bootUuid itself is already in the response).\n * From this the router can reproduce the hash for `'plain'` and `'@bootUuid'`\n * configs, and KNOWS it cannot reproduce a static-salt config (so it skips the\n * compare instead of reporting a false drift).\n */\nexport interface HealthHashDescriptor {\n mode: HealthHashConfig['mode'];\n bootUuidSalt: boolean;\n}\n\n/** Describe the active health-hash config for the `/_health` payload (no secret salt). */\nexport const describeHealthHashConfig = (): HealthHashDescriptor => {\n const { mode, salt } = getProjectConfig().http.healthHash;\n return { mode, bootUuidSalt: salt === '@bootUuid' };\n};\n\n/**\n * Reproduce the backend's resolved `{ mode, salt }` from its `/_health`\n * descriptor + the shared boot UUID. Returns `null` when the backend uses a\n * STATIC salt the router cannot see — the caller must then skip the compare\n * rather than hash with a different (default) config and report a false drift.\n */\nexport const resolveHealthHashConfigFromDescriptor = (\n descriptor: HealthHashDescriptor,\n bootUuid: string,\n): { mode: HealthHashConfig['mode']; salt: string } | null => {\n if (descriptor.mode === 'plain') return { mode: 'plain', salt: '' };\n if (descriptor.bootUuidSalt) return { mode: descriptor.mode, salt: bootUuid };\n return null;\n};\n\nexport const collectSynchronizedEnvKeys = (): string[] => {\n const keys = new Set<string>();\n for (const resource of Object.values(getDeployConfig().resources)) {\n for (const key of resource.synchronizedEnvKeys ?? []) {\n keys.add(key);\n }\n }\n return [...keys].toSorted();\n};\n\n/**\n * Compute the `/_health` hash map for every synchronized env var. Reads\n * `http.healthHash` (call-time) so a consumer's salt/HMAC opt-in applies;\n * `bootUuid` is consulted only when the salt is the `'@bootUuid'` sentinel.\n * The 0.2.0 DEFAULT (`{ mode: 'hmac', salt: '@bootUuid' }`) HMAC-keys the output\n * on `bootUuid`; pass `mode: 'plain'` to restore the historical unsalted output.\n */\nexport const computeSynchronizedEnvHashes = (\n bootUuid?: string | null,\n): Record<string, string | null> => {\n const keys = collectSynchronizedEnvKeys();\n const config = resolveHealthHashConfig(bootUuid);\n const out: Record<string, string | null> = {};\n for (const key of keys) {\n const value = process.env[key];\n out[key] = value === undefined ? null : hashSynchronizedValueWith(config, value);\n }\n return out;\n};\n\n//? Router-side helper: hash a single value with the same algorithm the backend's\n//? /_health uses. Kept as a named helper distinct from the backend-only collector.\n//? Honours the active `http.healthHash` config (call-time) so router + backend\n//? stay in lockstep; pass the backend's boot UUID when the salt is `'@bootUuid'`.\nexport const hashSynchronizedValue = (value: string, bootUuid?: string | null): string =>\n hashSynchronizedValueWith(resolveHealthHashConfig(bootUuid), value);","const COLORS: Record<string, string> = {\n black: \"\\u001B[30m\",\n red: \"\\u001B[31m\",\n green: \"\\u001B[32m\",\n yellow: \"\\u001B[33m\",\n blue: \"\\u001B[34m\",\n magenta: \"\\u001B[35m\",\n cyan: \"\\u001B[36m\",\n white: \"\\u001B[37m\",\n reset: \"\\u001B[0m\",\n};\n\n//? Strip both POSIX `/` and Windows `\\` separators so the file:line tag is\n//? consistent across platforms. Drop the column suffix too — `:42:7` becomes\n//? `:42`. If the regex finds no separator at all, fall back to the bare\n//? string (avoids leaking the full filesystem path).\nconst FILE_NAME_REGEX = /[\\\\/]([^\\\\/]+)$/;\n\nconst extractFrameLabel = (frame: string): string => {\n let lineInfo = frame.slice(frame.indexOf(\"(\") + 1, frame.lastIndexOf(\")\"));\n if (lineInfo === \"\") lineInfo = frame;\n const match = FILE_NAME_REGEX.exec(lineInfo);\n const fileName = match?.[1] ?? lineInfo;\n return fileName.replace(/:\\d+(?::\\d+)?$/, \"\");\n};\n\nexport const initConsolelog = () => {\n const originalLog = console.log;\n console.log = (...args: unknown[]) => {\n const stackLines = new Error('console.log trace').stack?.split(\"\\n\") ?? [];\n //? Search for the first frame outside this module so wrapper functions\n //? (e.g. `getLogger().info` → `console.log`) don't pin the label to the\n //? wrapper. Frame 0 is the Error itself; skip it.\n const frame = stackLines.slice(1).find((line) => {\n const trimmed = line.trim();\n return trimmed.length > 0 && !trimmed.includes('consoleLog.ts') && !trimmed.includes('loggerRegistry.ts');\n })?.trim();\n\n if (!frame) { originalLog(...args); return; }\n\n const extractedInfo = extractFrameLabel(frame);\n\n //? Color keyword strip — ONLY the trailing argument (the documented\n //? contract: `console.log('hello', 'red')`). Scanning all args meant a\n //? legitimate value of `\"red\"` anywhere in the call was silently dropped.\n let colorCode = COLORS.white;\n const lastArg = args.at(-1);\n if (typeof lastArg === 'string' && Object.prototype.hasOwnProperty.call(COLORS, lastArg)) {\n colorCode = COLORS[lastArg];\n args.pop();\n }\n\n // handle object vs text\n if (typeof args[0] === \"object\") {\n originalLog(`${colorCode}${extractedInfo}${COLORS.reset}`);\n originalLog(...args);\n } else {\n originalLog(`${colorCode}${extractedInfo} -- ${args.join(\" \")}${COLORS.reset}`);\n }\n };\n}","const escapeRegExp = (value: string): string => {\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`);\n};\n\nexport const getCookieValue = (cookieHeader: string | undefined, cookieName: string): string | null => {\n if (!cookieHeader || !cookieName) {\n return null;\n }\n\n const safeCookieName = escapeRegExp(cookieName);\n const cookieRegex = new RegExp(String.raw`(?:^|;\\s*)${safeCookieName}=([^;]*)`);\n const match = cookieHeader.match(cookieRegex);\n const rawValue = match?.[1];\n\n if (!rawValue) {\n return null;\n }\n\n try {\n return decodeURIComponent(rawValue);\n } catch {\n //? CORE-O21: returning the raw bytes on decode failure is by-design.\n //? A lone `%` or malformed percent-sequence is unusual but not grounds to\n //? treat the cookie as absent — the caller receives the exact bytes the\n //? browser sent and can decide whether to accept or reject them.\n return rawValue;\n }\n};\n\nexport const hasCookie = (cookieHeader: string | undefined, cookieName: string): boolean => {\n return getCookieValue(cookieHeader, cookieName) !== null;\n};\n\n/** Attributes a cookie-builder must honour, after applying any name prefix. */\nexport interface CookiePrefixConstraints {\n /** Effective cookie name (prefix prepended when applicable). */\n name: string;\n /** Whether `Secure` must be set (forced true by either prefix). */\n secure: boolean;\n /** Whether `Path=/` is forced (true under `__Host-`). */\n forcePathRoot: boolean;\n /** Whether a `Domain` attribute is FORBIDDEN (true under `__Host-`). */\n forbidDomain: boolean;\n}\n\n/**\n * Compute the attribute constraints a `__Host-` / `__Secure-` cookie-name prefix\n * imposes (CORE-10/39), so the server's session + CSRF cookie builders apply the\n * browser-enforced rules consistently:\n *\n * - `'__Host-'` → name prefixed, `Secure` forced, `Path=/` forced, `Domain` forbidden.\n * - `'__Secure-'` → name prefixed, `Secure` forced.\n * - `undefined` → name unchanged, no forced attributes (today's behavior).\n *\n * Pure + deterministic — the builder still owns serialization; this only\n * resolves the constraints so server and any other cookie-site can't drift.\n * `secureOverride` lets the caller force `Secure` independent of a prefix\n * (mirrors `http.sessionCookieSecure`).\n */\nexport const applyCookiePrefixConstraints = (\n baseName: string,\n prefix: '__Host-' | '__Secure-' | undefined,\n secureOverride?: boolean,\n): CookiePrefixConstraints => {\n if (prefix === '__Host-') {\n return { name: `__Host-${baseName}`, secure: true, forcePathRoot: true, forbidDomain: true };\n }\n if (prefix === '__Secure-') {\n return { name: `__Secure-${baseName}`, secure: true, forcePathRoot: false, forbidDomain: false };\n }\n return { name: baseName, secure: secureOverride ?? false, forcePathRoot: false, forbidDomain: false };\n};\n","/* eslint-disable @typescript-eslint/no-unnecessary-condition */\n\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { getProjectConfig } from './projectConfig';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst isWorkspaceRoot = (candidate: string): boolean => {\n\treturn (\n\t\tfs.existsSync(path.join(candidate, 'package.json'))\n\t\t&& (\n\t\t\tfs.existsSync(path.join(candidate, 'tsconfig.json'))\n\t\t\t|| fs.existsSync(path.join(candidate, 'tsconfig.server.json'))\n\t\t)\n\t);\n};\n\nconst findWorkspaceRoot = (startDir: string): string | null => {\n\tlet current = path.resolve(startDir);\n\n\twhile (true) {\n\t\tif (isWorkspaceRoot(current)) {\n\t\t\treturn current;\n\t\t}\n\n\t\tconst parent = path.dirname(current);\n\t\tif (parent === current) return null;\n\t\tcurrent = parent;\n\t}\n};\n\nexport const ROOT_DIR =\n\tfindWorkspaceRoot(process.cwd())\n\t?? findWorkspaceRoot(__dirname)\n\t?? process.cwd();\n\nconst resolveAgainstRoot = (relative: string): string =>\n\tpath.isAbsolute(relative) ? relative : path.join(ROOT_DIR, relative);\n\n//? Lazy getters: read from the registered ProjectConfig at *call time* so a\n//? consumer's `registerProjectConfig({ paths: { srcDir: 'app/src' } })` wins\n//? even if it ran after this module was imported.\nexport const getSrcDir = (): string => resolveAgainstRoot(getProjectConfig().paths.srcDir);\nexport const getServerDir = (): string => resolveAgainstRoot(getProjectConfig().paths.serverDir);\nexport const getSharedDir = (): string => resolveAgainstRoot(getProjectConfig().paths.sharedDir);\nexport const getUploadsDir = (): string => resolveAgainstRoot(getProjectConfig().paths.uploadsDir);\nexport const getPublicDir = (): string => resolveAgainstRoot(getProjectConfig().paths.publicDir);\n//? Returns the list of directories the function-injection system walks\n//? at codegen + runtime. Default `['functions', 'shared']`. Consumers can\n//? override via `registerProjectConfig({ paths: { serverFunctionDirs: [...] } })`.\n//?\n//? Backwards-compat: if a consumer still sets the deprecated singular\n//? `serverFunctionsDir: string`, we honor it as a single-entry list. Once\n//? both are set, `serverFunctionDirs` wins.\nexport const getServerFunctionDirs = (): string[] => {\n\tconst paths = getProjectConfig().paths;\n\tconst arrayConfig = paths.serverFunctionDirs;\n\tif (Array.isArray(arrayConfig) && arrayConfig.length > 0) {\n\t\treturn arrayConfig.map((p) => resolveAgainstRoot(p));\n\t}\n\t// eslint-disable-next-line @typescript-eslint/no-deprecated -- BC shim: the legacy singular field is still honored when set\n\tconst legacySingular = paths.serverFunctionsDir;\n\tif (typeof legacySingular === 'string' && legacySingular.length > 0) {\n\t\treturn [resolveAgainstRoot(legacySingular)];\n\t}\n\treturn [];\n};\n\n/**\n * @deprecated Use `getServerFunctionDirs()` instead. Returns the first\n * directory from the array, or `'functions'` resolved against the workspace\n * root when no directories are configured.\n */\nexport const getServerFunctionsDir = (): string => {\n\tconst dirs = getServerFunctionDirs();\n\treturn dirs[0] ?? resolveAgainstRoot('functions');\n};\n\nexport const getGeneratedSocketTypesPath = (): string =>\n\tresolveAgainstRoot(getProjectConfig().paths.generatedSocketTypes);\nexport const getGeneratedApiSchemasPath = (): string =>\n\tresolveAgainstRoot(getProjectConfig().paths.generatedApiSchemas);\nexport const getGeneratedApiDocsPath = (): string =>\n\tresolveAgainstRoot(getProjectConfig().paths.generatedApiDocs);\n\n//? Backwards-compatible constants resolved against the framework defaults at\n//? module load. New code should use the getters above. These will continue to\n//? work for projects that don't override `paths` in `registerProjectConfig`.\n//? @deprecated use `getSrcDir()` etc. instead.\nexport const SRC_DIR = path.join(ROOT_DIR, 'src');\n/** @deprecated use `getServerDir()` instead. */\nexport const SERVER_DIR = path.join(ROOT_DIR, 'server');\n/** @deprecated use `getSharedDir()` instead. */\nexport const SHARED_DIR = path.join(ROOT_DIR, 'shared');\n\n/** @deprecated use `getUploadsDir()` instead. */\nexport const UPLOADS_DIR = path.join(ROOT_DIR, 'uploads');\n/** @deprecated use `getPublicDir()` instead. */\nexport const PUBLIC_DIR = path.join(ROOT_DIR, 'public');\n/** @deprecated use `getServerFunctionsDir()` instead. */\n// eslint-disable-next-line @typescript-eslint/no-deprecated -- BC export: depends on the deprecated SERVER_DIR\nexport const SERVER_FUNCTIONS_DIR = path.join(SERVER_DIR, 'functions');\n\n/** @deprecated use `getGeneratedSocketTypesPath()` instead. */\nexport const GENERATED_SOCKET_TYPES_PATH = path.join(SRC_DIR, '_sockets', 'apiTypes.generated.ts');\n/** @deprecated use `getGeneratedApiSchemasPath()` instead. */\nexport const GENERATED_API_SCHEMAS_PATH = path.join(SRC_DIR, '_sockets', 'apiInputSchemas.generated.ts');\n/** @deprecated use `getGeneratedApiDocsPath()` instead. */\nexport const GENERATED_API_DOCS_PATH = path.join(SRC_DIR, 'docs', 'apiDocs.generated.json');\n\nexport const TSCONFIG_ALIAS_FILES = ['tsconfig.server.json', 'tsconfig.client.json'];\n\nexport const resolveFromRoot = (...segments: string[]): string => path.join(ROOT_DIR, ...segments);\n","//? Socket.io middleware registry. Lets consumers wedge their own\n//? `io.use(...)` middlewares into the framework's socket bootstrap without\n//? forking `loadSocket.ts`. Registration order is preservation order —\n//? `applySocketMiddlewares(io)` calls each middleware in the order it was\n//? registered, which is the same contract Socket.io's native `io.use(...)`\n//? chain provides.\n//?\n//? Use cases: per-connection auth gates (JWT verification beyond the\n//? framework's session cookie), rate-limit shedding before handlers run,\n//? observability tags (Datadog span context, OpenTelemetry context),\n//? license-key checks, allow-list IP filters.\n\nimport type { Socket, Server as SocketIOServer } from 'socket.io';\n\nexport type SocketMiddleware = (socket: Socket, next: (err?: Error) => void) => void;\n\nconst middlewares: SocketMiddleware[] = [];\n\n/**\n * Register a Socket.io middleware that will be wired via `io.use(...)` when\n * the framework boots the socket server. Multiple middlewares can be\n * registered — they run in registration order before any `connect`\n * handler fires.\n */\nexport const registerSocketMiddleware = (mw: SocketMiddleware): void => {\n middlewares.push(mw);\n};\n\n/** Read the registered middleware list (read-only view). */\nexport const getSocketMiddlewares = (): readonly SocketMiddleware[] => middlewares;\n\n/** Test-only / hot-reload helper. Drops every registered middleware. */\nexport const clearSocketMiddlewares = (): void => {\n middlewares.length = 0;\n};\n\n/**\n * Wire every registered middleware into the running Socket.io server.\n * Called by `@luckystack/server`'s `loadSocket` after the `SocketIOServer`\n * is constructed and before any `connect` handler is attached, so custom\n * middlewares run before framework handlers see the socket.\n */\nexport const applySocketMiddlewares = (io: SocketIOServer): void => {\n for (const mw of middlewares) {\n io.use(mw);\n }\n};\n","import path from \"node:path\";\nimport { access } from 'node:fs/promises';\nimport fs from \"node:fs\";\nimport stream from \"node:stream\";\nimport { ServerResponse } from \"node:http\";\nimport { getUploadsDir } from './paths';\nimport { getAvatarConfig } from './avatarConfig';\nimport { getLogger } from './loggerRegistry';\nimport tryCatch from './tryCatch';\nimport { dispatchHook } from './hooks/registry';\n\n//? Belt-and-suspenders: `path.basename` already strips traversal segments,\n//? but explicitly allowlist the fileId character set so any future regression\n//? in the upstream routing (e.g. URL-decoded embedded null) can't slip\n//? through to the filesystem layer.\nconst FILE_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;\n\nexport const serveAvatar = async ({\n routePath,\n res,\n}: {\n routePath: string;\n res: ServerResponse;\n}) => {\n const uploadsFolder = getUploadsDir();\n const fileId = path.basename(routePath, path.extname(routePath));\n if (!fileId || !FILE_ID_REGEX.test(fileId)) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('File not found');\n return;\n }\n\n //? Access-control / audit seam: a `preAvatarServe` handler may veto the read\n //? (stop signal). We answer 404 — not 403 — so a private avatar's existence\n //? isn't disclosed to an unauthorized caller.\n const preDispatch = await dispatchHook('preAvatarServe', { routePath, fileId });\n if (preDispatch.stopped) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('File not found');\n return;\n }\n\n const { formats, cacheControl } = getAvatarConfig();\n\n //? Try each configured format in order; first existing file wins. This\n //? lets an installer add `png` or `jpg` for backwards compatibility while\n //? still preferring `webp` for new uploads.\n for (const { extension, contentType } of formats) {\n const filePath = path.join(uploadsFolder, `${fileId}.${extension}`);\n const [accessError] = await tryCatch(() => access(filePath));\n if (accessError) continue;\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Cache-Control': cacheControl,\n });\n //? Pipe with an error sink: once headers are flushed a read error can no\n //? longer be turned into a 5xx, so `pipeline` just tears the response down\n //? (destroys both streams) instead of letting the unhandled stream 'error'\n //? crash the worker. No double-write — we never write a body after this.\n stream.pipeline(fs.createReadStream(filePath), res, (pipelineError) => {\n if (pipelineError) {\n getLogger().debug('avatar: stream pipeline failed', { routePath, fileId, error: pipelineError });\n }\n });\n void dispatchHook('postAvatarServe', { routePath, fileId, extension, contentType });\n return;\n }\n\n getLogger().debug('avatar: file not found', { routePath, fileId });\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('File not found');\n};\n","//? Configurable avatar serving so installers don't have to fork\n//? `serveAvatar` to change the file format, cache headers, or storage path.\n//? Defaults match the framework's prior hardcoded behavior (.webp, 24h cache).\n\nimport { type DeepPartial } from './configUtils';\nimport { createRegistry } from './createRegistry';\n\nexport interface AvatarConfig {\n /**\n * File format(s) the framework will look for on disk, in order. The first\n * one that exists wins. Each entry is `{ extension, contentType }` so the\n * `Content-Type` response header matches what the file actually is.\n * Default: `[{ extension: 'webp', contentType: 'image/webp' }]`.\n */\n formats: { extension: string; contentType: string }[];\n /**\n * `Cache-Control` value sent with the avatar response.\n * Default: `'public, max-age=86400'` (24 hours).\n */\n cacheControl: string;\n}\n\nexport const DEFAULT_AVATAR_CONFIG: AvatarConfig = {\n formats: [{ extension: 'webp', contentType: 'image/webp' }],\n cacheControl: 'public, max-age=86400',\n};\n\nexport type AvatarConfigInput = DeepPartial<AvatarConfig>;\n\nconst registry = createRegistry<AvatarConfig, AvatarConfigInput>(DEFAULT_AVATAR_CONFIG, {\n //? Bespoke merge (not a generic deep-merge): an empty `formats` array falls\n //? back to the default formats rather than wiping serving. Built fresh from\n //? the defaults each call, matching the historical behaviour.\n transform: (config) => ({\n formats: config.formats?.length ? (config.formats as AvatarConfig['formats']) : DEFAULT_AVATAR_CONFIG.formats,\n cacheControl: config.cacheControl ?? DEFAULT_AVATAR_CONFIG.cacheControl,\n }),\n});\n\nexport const registerAvatarConfig = (config: AvatarConfigInput): void => {\n registry.register(config);\n};\n\nexport const getAvatarConfig = (): AvatarConfig => registry.get();\n","//? Framework-side upload helper. Wraps the `onUploadStart` / `onUploadComplete`\n//? hook dispatch around an installer-provided encode/save callback so consumer\n//? upload routes don't have to plumb the hooks themselves.\n//?\n//? Why a callback (not a built-in encoder): @luckystack/core stays free of\n//? heavy native deps (sharp, ffmpeg, etc.). Consumers wire whichever encoder\n//? fits their use case and the framework just brackets the call with the\n//? hook contract.\n\nimport { dispatchHook } from './hooks/registry';\nimport tryCatch from './tryCatch';\n\nexport interface ProcessUploadInput {\n /** User the upload is associated with. */\n userId: string;\n /** Content type from the source (e.g. parsed from a data URL prefix). */\n contentType: string;\n /** Raw upload bytes — used for `sizeBytes` and passed to the encoder. */\n buffer: Buffer;\n /** Identifier for the upload category. Defaults to `'avatar'`. */\n uploadKind?: string;\n /** Final file name written to disk (used in the `onUploadComplete` payload). */\n fileName: string;\n /**\n * Project-supplied encode + write step. Receives the raw buffer, does\n * whatever encoding/resizing the installer wants (sharp, ffmpeg, raw fs\n * write, S3 upload, etc.), and returns the final byte size on disk —\n * pass through `buffer.byteLength` if you don't have a meaningful\n * post-encode size.\n */\n encodeAndSave: (buffer: Buffer) => Promise<number>;\n}\n\nexport type ProcessUploadResult =\n | { status: 'success'; sizeBytes: number }\n | { status: 'rejected'; errorCode: string }\n | { status: 'error'; reason: string; cause?: unknown };\n\n/**\n * Run a project-side upload through the framework's hook contract.\n *\n * Order of operations:\n * 1. Dispatch `onUploadStart` — handlers may return a stop signal to reject\n * the upload (content moderation, virus scanner, quota check).\n * 2. Run the project-supplied `encodeAndSave` callback (wrapped in tryCatch).\n * 3. Dispatch `onUploadComplete` with the final `sizeBytes`.\n *\n * Returns one of three statuses so the caller can map to its own response\n * envelope without inspecting hook internals.\n */\nexport const processUpload = async (input: ProcessUploadInput): Promise<ProcessUploadResult> => {\n const uploadKind = input.uploadKind ?? 'avatar';\n\n const startSignal = await dispatchHook('onUploadStart', {\n userId: input.userId,\n contentType: input.contentType,\n sizeBytes: input.buffer.byteLength,\n uploadKind,\n });\n if (startSignal.stopped) {\n return { status: 'rejected', errorCode: startSignal.signal.errorCode };\n }\n\n const [error, finalSize] = await tryCatch(() => input.encodeAndSave(input.buffer));\n if (error) {\n return { status: 'error', reason: error.message || 'encode-failed', cause: error };\n }\n\n const sizeBytes = finalSize ?? input.buffer.byteLength;\n await dispatchHook('onUploadComplete', {\n userId: input.userId,\n fileName: input.fileName,\n sizeBytes,\n uploadKind,\n });\n\n return { status: 'success', sizeBytes };\n};\n","//? @adr 0031\n//? Server bind-address registry. The `createLuckyStackServer` bootstrap\n//? populates this with the actual listen `ip`/`port` so framework code that\n//? needs the bind address (e.g. `checkOrigin` building the same-origin\n//? entry) doesn't drift when the consumer used the `options.ip`/`options.port`\n//? arguments instead.\n//?\n//? `registerBindAddress` stores the intended address before `listen`;\n//? `registerBoundAddress` then records the address reported by node:http. Keeping\n//? both values lets readers see reality while OAuth rewrites only a stale direct\n//? callback that still names the intended pre-hop port — never an explicit local\n//? router/reverse-proxy ingress.\n//?\n//? Resolution at call time:\n//? 1. registered current value (intended before listen, bound after success)\n//? 2. `process.env.SERVER_IP` for the bind address\n//? 3. `'127.0.0.1'` / port `'80'` as the generic pre-bootstrap fallback\n\nimport { isProductionRuntime } from './env';\n\ninterface BindAddress {\n ip: string;\n port: number;\n}\n\ninterface IntendedBindAddress extends BindAddress {\n configuredPort?: number;\n}\n\nconst DEFAULT_SERVER_PORT = '80';\n\nlet intended: BindAddress | null = null;\nlet configuredPort: number | null = null;\nlet registered: BindAddress | null = null;\n\nexport const registerBindAddress = (address: IntendedBindAddress): void => {\n intended = { ip: address.ip, port: address.port };\n configuredPort = address.configuredPort ?? null;\n registered = intended;\n};\n\nexport const registerBoundAddress = (address: BindAddress): void => {\n intended ??= address;\n registered = address;\n};\n\nexport const getBindAddress = (): { ip: string; port: string } => {\n if (registered) {\n return { ip: registered.ip, port: String(registered.port) };\n }\n return {\n ip: process.env.SERVER_IP ?? '127.0.0.1',\n //? A real server registers its intended/defaultPort before OAuth or CORS\n //? reads this value. Port 80 only remains the generic pre-bootstrap fallback.\n port: DEFAULT_SERVER_PORT,\n };\n};\n\nconst defaultPortForProtocol = (protocol: string): string => (protocol === 'https:' ? '443' : '80');\n\n//? Make an OAuth callback URL reflect the port the server ACTUALLY bound.\n//?\n//? In dev the backend may auto-increment off its intended port (`:80` busy →\n//? `:81`), but `oauthCallbackBase` is frozen at the intended port the moment\n//? `config.ts` runs at module load. A frozen `redirect_uri` then points the OAuth\n//? round-trip at a dead port. This rewrites ONLY a loopback callback that still\n//? names the intended pre-listen port. An explicit localhost router/reverse proxy\n//? on another port remains authoritative. `localhost`, `127.0.0.1`, and IPv6\n//? `[::1]` use the same loopback policy as CORS.\n//?\n//? SAFETY — authorize and token-exchange must send a BYTE-IDENTICAL `redirect_uri`\n//? (OAuth requires it). Both derive from this helper, and `getBindAddress()` is\n//? constant for the process lifetime, so the two calls agree. It is NOT\n//? request-derived (never attacker-influenced), so it needs no per-state storage.\n//?\n//? Prod is a no-op: no hop, and the public callback domain has no localhost port\n//? to chase. A non-localhost base (remote dev backend) is returned untouched.\n//? Default ports collapse to the empty string so the emitted URL stays\n//? byte-stable with how a provider redirect_uri is normally registered\n//? (`http://localhost/...`, not `http://localhost:80/...`).\nexport const resolveDevCallbackUrl = (callbackUrl: string): string => {\n if (isProductionRuntime()) return callbackUrl;\n\n let url: URL;\n try {\n url = new URL(callbackUrl);\n } catch {\n return callbackUrl;\n }\n\n if (!['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) return callbackUrl;\n\n const boundPort = getBindAddress().port;\n const intendedPort = intended ? String(intended.port) : '';\n if (!boundPort || !intendedPort) return callbackUrl;\n\n const currentPort = url.port || defaultPortForProtocol(url.protocol);\n const matchesIntendedPort = currentPort === intendedPort;\n const matchesConfiguredPort = configuredPort !== null && currentPort === String(configuredPort);\n if (currentPort === boundPort || (!matchesIntendedPort && !matchesConfiguredPort)) return callbackUrl;\n\n url.port = boundPort === defaultPortForProtocol(url.protocol) ? '' : boundPort;\n return url.toString();\n};\n","import { IncomingMessage, ServerResponse } from \"node:http\";\nimport tryCatch from \"./tryCatch\";\nimport { getProjectConfig } from \"./projectConfig\";\n\ninterface getParamsType {\n method: string;\n req: IncomingMessage;\n res: ServerResponse;\n queryString?: string;\n}\n\n//? Shared error-response shape for the body parser's 413/400/415 branches.\n//? Writes a JSON error envelope and resolves the outer promise with `null`\n//? (the parser's \"no usable body\" signal). Extracted so the duplicated\n//? \"set header + writeHead + end(JSON) + resolve(null)\" blocks live once.\nconst writeJsonErrorAndResolve = (\n res: ServerResponse,\n status: number,\n errorCode: string,\n resolve: (value: Record<string, unknown> | null) => void,\n): void => {\n res.setHeader('Content-Type', 'application/json');\n res.writeHead(status);\n res.end(JSON.stringify({\n status: 'error',\n httpStatus: status,\n message: errorCode,\n errorCode,\n }));\n resolve(null);\n};\n\nexport default async function getParams({ method, req, res: _res, queryString }: getParamsType): Promise<Record<string, unknown> | null> {\n\n if (method === \"GET\") {\n //? if get request we return the query string as an object\n return Object.fromEntries(new URLSearchParams(queryString ?? ''));\n }\n\n //? if a POST, PUT or DELETE method we return the body as an object\n return new Promise((resolve) => {\n const contentType = req.headers['content-type'] ?? '';\n const contentLengthHeader = req.headers['content-length'];\n const declaredLength = typeof contentLengthHeader === 'string' ? Number(contentLengthHeader) : Number.NaN;\n const maxBodyBytes = getProjectConfig().http.requestBodyMaxBytes;\n\n if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {\n writeJsonErrorAndResolve(_res, 413, 'api.payloadTooLarge', resolve);\n //? Destroy the socket immediately after writing the 413 so the client\n //? can't park a slow body and tie up the connection (marginal slow-loris\n //? vector). The response was already flushed by `writeJsonErrorAndResolve`\n //? so destroying here only stops incoming data, not the outbound 413.\n req.destroy();\n return;\n }\n\n //? Collect raw Buffer chunks and decode once at `end` — concatenating\n //? `chunk.toString()` per chunk corrupts multi-byte UTF-8 sequences that\n //? straddle a TCP chunk boundary.\n const chunks: Buffer[] = [];\n let bodySize = 0;\n req.on('data', (chunk: Buffer) => {\n //? Guard against already-responded: Node can deliver buffered 'data' events\n //? even after `req.destroy()` is called (the event was already queued). A\n //? second call into `writeJsonErrorAndResolve` on an ended response throws\n //? ERR_HTTP_HEADERS_SENT. The `resolve` idempotency is fine; the response\n //? write is not.\n if (_res.headersSent) return;\n\n bodySize += chunk.length;\n if (bodySize > maxBodyBytes) {\n //? Write the 413 BEFORE tearing down the socket. `req` and `res` share one\n //? TCP socket, so destroying `req` first kills the connection before the\n //? 413 body is flushed and the client gets an empty/RST response instead.\n //? `writeJsonErrorAndResolve` sets `res.writableEnded`, which short-circuits\n //? the caller, so the destroy only needs to stop further inbound bytes.\n writeJsonErrorAndResolve(_res, 413, 'api.payloadTooLarge', resolve);\n req.destroy();\n return;\n }\n\n chunks.push(chunk);\n });\n\n req.on('end', () => { void (async () => {\n const body = Buffer.concat(chunks).toString('utf8');\n\n //? here we parse the data depending on the content type\n //? if the content type is application/x-www-form-urlencoded we parse the data as a URLSearchParams object\n if (contentType.startsWith('application/x-www-form-urlencoded')) {\n const parseData = () => {\n const data = new URLSearchParams(body);\n return Object.fromEntries(data);\n };\n const [, response] = await tryCatch(parseData);\n if (response) {\n resolve(response); return;\n }\n\n writeJsonErrorAndResolve(_res, 400, 'api.invalidRequestFormat', resolve);\n return;\n }\n\n //? if the content type is application/json we parse the data as a JSON object\n if (contentType.startsWith('application/json')) {\n const parseData = (): unknown => {\n return JSON.parse(body || '{}');\n };\n const [, response] = await tryCatch(parseData);\n\n //? Reject array/scalar/null bodies — only plain objects are accepted.\n //? An attacker sending `[1,2,3]` or `42` could surface odd shapes in\n //? handlers that didn't expect them.\n if (response && typeof response === 'object' && !Array.isArray(response)) {\n resolve(response as Record<string, unknown>); return;\n }\n\n writeJsonErrorAndResolve(_res, 400, 'api.invalidRequestFormat', resolve);\n return;\n }\n\n //? Unknown content-type: don't attempt to interpret. Empty content-type\n //? bodies in particular were previously parsed as `{ body }` which let\n //? handlers mistake raw text for a typed payload.\n writeJsonErrorAndResolve(_res, 415, 'api.unsupportedMediaType', resolve);\n })(); });\n\n //? Request-stream error (e.g. a client RST mid-body on POST/PUT/DELETE).\n //? Mirror the SSE `markClosed` pattern: a broken request stream must NOT\n //? reject — a rejected promise here is voided by the http handler\n //? (`void handleHttpRequest(...)`) and becomes an unhandled rejection that\n //? crashes the worker. The connection is already gone, so we can't write a\n //? 4xx; resolve with `null` (the parser's \"no usable body\" signal) exactly\n //? like the 413/400/415 branches, letting the handler short-circuit\n //? cleanly. `resolve` after a prior settle is a no-op, so racing with\n //? `end`/the 413 path is safe.\n req.on('error', () => {\n resolve(null);\n });\n });\n}\n","import { Socket } from 'socket.io';\nimport { getProjectConfig } from './projectConfig';\nimport { getCookieValue } from './cookies';\n\n/**\n * Extract the authentication token from a Socket.io connection.\n *\n * Checks `config.sessionBasedToken` to determine where to look:\n * - `true`: Token is in `socket.handshake.auth.token` (sessionStorage on client)\n * - `false`: Token is in cookies via `socket.handshake.headers.cookie`\n *\n * @param socket - The Socket.io socket instance\n * @returns The token string or null if not found\n */\n//? `Pick<Socket, 'handshake'>` (not `Socket`) so this also accepts a\n//? `RemoteSocket` from `io.fetchSockets()` — the cross-instance fan-out path\n//? only has RemoteSockets, which expose `handshake` but not the full Socket API.\nexport const extractTokenFromSocket = (socket: Pick<Socket, 'handshake'>): string | null => {\n const config = getProjectConfig();\n const cookie = socket.handshake.headers.cookie;\n //? socket.io always initializes `handshake.auth` to at least `{}` (RemoteSockets included).\n const sessionToken = typeof socket.handshake.auth.token === 'string'\n ? socket.handshake.auth.token\n : null;\n const cookieToken = getCookieValue(cookie, config.http.sessionCookieName);\n\n // Session-based token (stored in sessionStorage on client)\n if (config.session.basedToken) {\n return sessionToken ?? cookieToken;\n }\n\n // Cookie-mode (CORE-O10): only accept the handshake-auth token fallback when\n // explicitly opted in, otherwise a stolen token supplied via\n // `handshake.auth.token` would defeat the cookie/CSRF model.\n if (config.http.acceptBearerInCookieMode) {\n return cookieToken ?? sessionToken;\n }\n\n return cookieToken;\n};\n","import { IncomingMessage } from 'node:http';\nimport { getProjectConfig } from './projectConfig';\nimport { getCookieValue } from './cookies';\n\n/**\n * Extract the authentication token from an HTTP request.\n *\n * Supports tokens from:\n * - `Authorization: Bearer <token>` header\n * - the configured session cookie\n *\n * @param req - The HTTP incoming message\n * @returns The token string or null if not found\n */\nexport const extractTokenFromRequest = (req: IncomingMessage): string | null => {\n // Node types `authorization` as `string`, but duplicate headers can arrive as\n // an array at runtime via the IncomingHttpHeaders index signature. Treat the\n // value as unknown and narrow with typeof guards so a single string is used.\n const rawAuthHeader: unknown = req.headers.authorization;\n const authHeader = typeof rawAuthHeader === 'string'\n ? rawAuthHeader\n : (Array.isArray(rawAuthHeader) && typeof rawAuthHeader[0] === 'string' ? rawAuthHeader[0] : undefined);\n const bearerToken = authHeader?.startsWith('Bearer ')\n ? authHeader.slice(7)\n : null;\n\n const config = getProjectConfig();\n const cookieToken = getCookieValue(req.headers.cookie, config.http.sessionCookieName);\n\n // Token-mode: bearer is canonical, cookie is the fallback.\n if (config.session.basedToken) {\n return bearerToken ?? cookieToken;\n }\n\n // Cookie-mode (CORE-O10): only accept the bearer fallback when explicitly\n // opted in, otherwise a stolen token replayed via `Authorization: Bearer`\n // would defeat the cookie/CSRF model.\n if (config.http.acceptBearerInCookieMode) {\n return cookieToken ?? bearerToken;\n }\n\n return cookieToken;\n};\n","import { createHash } from 'node:crypto';\nimport type { IncomingHttpHeaders } from 'node:http';\n\nimport { getLogger } from './loggerRegistry';\n\n/**\n * Stable sentinel returned when no usable client address could be resolved.\n * Kept as a single exported constant so every rate-limit key-site funnels\n * missing addresses into one deterministic bucket instead of ad-hoc literals.\n */\nexport const UNKNOWN_CLIENT_IP = 'unknown';\n\nexport interface ResolveClientIpParams {\n /**\n * The raw transport peer address — `socket.handshake.address` (Socket.io)\n * or `req.socket.remoteAddress` (raw HTTP). May be `undefined`/`null` when\n * the connection has already been torn down.\n */\n rawAddress: string | null | undefined;\n /** Request / handshake headers. Only consulted when `trustProxy` is true. */\n headers: IncomingHttpHeaders;\n /**\n * Whether a known reverse proxy sits in front of this server. DEFAULT false.\n * When false the raw peer address is returned verbatim (only IPv4-mapped\n * IPv6 canonicalization is applied), preserving historical behaviour. Only\n * enable when a trusted proxy populates `X-Forwarded-For` / `X-Real-IP`,\n * otherwise a client can spoof its own IP via those headers.\n */\n trustProxy?: boolean;\n /**\n * Number of proxy hops to skip from the RIGHT of `X-Forwarded-For` when\n * `trustProxy` is on (CORE-O3). The rightmost entries are appended by your own\n * trusted proxies; the resolved client IP is the entry that many hops in from\n * the end. The leftmost hop is client-controlled and is never trusted. DEFAULT\n * 1 (the immediate upstream proxy). Clamped to the list length so an over-large\n * count falls back to the leftmost trusted hop rather than the spoofable entry.\n */\n trustedProxyHopCount?: number;\n}\n\n/**\n * Canonicalize an address so variant spellings of the same host collapse into\n * one rate-limit bucket (M-7):\n * - strip the IPv4-mapped IPv6 prefix (`::ffff:1.2.3.4` -> `1.2.3.4`);\n * - drop an IPv6 zone id (`fe80::1%eth0` -> `fe80::1`);\n * - lowercase IPv6 (case-insensitive hex) so `2001:DB8::1` == `2001:db8::1`.\n *\n * IPv4 + hostnames are returned untouched. NOTE: this is the lightweight,\n * dependency-free normalization — it does NOT expand/compress IPv6 (`2001:db8::1`\n * vs `2001:0db8:0:0:0:0:0:1`); use a dedicated `ip-address` parser if that\n * edge-case bucket-spreading matters for your threat model.\n */\nconst canonicalizeIp = (value: string): string => {\n let ip = value.startsWith('::ffff:') ? value.slice('::ffff:'.length) : value;\n const zoneIdx = ip.indexOf('%');\n if (zoneIdx !== -1) ip = ip.slice(0, zoneIdx);\n return ip.includes(':') ? ip.toLowerCase() : ip;\n};\n\n/**\n * Normalize a single header value (string | string[] | undefined) to its\n * first non-empty string, trimmed. `X-Forwarded-For` may legitimately arrive\n * as a comma-joined list (\"client, proxy1, proxy2\") OR as a repeated header\n * (string[]); both are handled.\n */\nconst firstHeaderValue = (raw: string | string[] | undefined): string | undefined => {\n if (raw === undefined) return undefined;\n const single = Array.isArray(raw) ? raw[0] : raw;\n if (typeof single !== 'string') return undefined;\n const trimmed = single.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\n/**\n * Flatten an `X-Forwarded-For` header into its ordered list of non-empty,\n * trimmed hops. Handles both the comma-joined list form (\"client, proxy1\") and\n * the repeated-header (string[]) form, and a mix of the two. Order is preserved:\n * leftmost (client-controlled) first, rightmost (closest trusted proxy) last.\n */\nconst forwardedForHops = (raw: string | string[] | undefined): string[] => {\n if (raw === undefined) return [];\n const parts = Array.isArray(raw) ? raw : [raw];\n return parts\n .flatMap((part) => (typeof part === 'string' ? part.split(',') : []))\n .map((hop) => hop.trim())\n .filter((hop) => hop.length > 0);\n};\n\n/**\n * Resolve the real client IP for per-IP rate-limit keying.\n *\n * - `trustProxy === false` (default): returns the raw peer address with only\n * IPv4-mapped IPv6 canonicalization applied; falls back to\n * {@link UNKNOWN_CLIENT_IP} when the raw address is missing. This preserves\n * the framework's historical behaviour (`address ?? 'unknown'`) byte-for-byte\n * for non-mapped addresses.\n * - `trustProxy === true`: skips `trustedProxyHopCount` hops from the RIGHT of\n * `X-Forwarded-For` (the rightmost entries are appended by YOUR trusted\n * proxies), then `X-Real-IP`, and only then the raw peer address.\n *\n * CORE-O3: the leftmost `X-Forwarded-For` hop is CLIENT-CONTROLLED and is never\n * trusted (it enables per-IP rate-limit evasion + audit-IP spoofing). With the\n * default `trustedProxyHopCount: 1` and a single trusted proxy, the resolved IP\n * is the immediate upstream peer (the rightmost real hop).\n */\nconst resolveRaw = ({ rawAddress, headers, trustProxy, trustedProxyHopCount }: ResolveClientIpParams & { trustProxy: boolean; trustedProxyHopCount: number }): string => {\n const rawFallback = rawAddress && rawAddress.length > 0\n ? canonicalizeIp(rawAddress)\n : UNKNOWN_CLIENT_IP;\n\n if (!trustProxy) return rawFallback;\n\n const hops = forwardedForHops(headers['x-forwarded-for']);\n if (hops.length > 0) {\n //? Count from the RIGHT — the rightmost entry is the one your own trusted\n //? proxy appended. Clamp so an over-large count lands on the leftmost\n //? available trusted hop instead of underflowing past the start. A configured\n //? value < 1 (e.g. 0) is also clamped UP to 1 — the leftmost, client-controlled\n //? hop is never selectable by design (CORE-O3), so `0` cannot mean \"trust the\n //? whole XFF\". Document this on `http.trustedProxyHopCount`.\n const skip = Math.max(1, Math.floor(trustedProxyHopCount));\n const index = Math.max(0, hops.length - skip);\n const chosen = hops[index];\n if (chosen && chosen.length > 0) return canonicalizeIp(chosen);\n }\n\n const realIp = firstHeaderValue(headers['x-real-ip']);\n if (realIp) return canonicalizeIp(realIp);\n\n return rawFallback;\n};\n\n/**\n * Whether a resolved client IP is a loopback / unresolved address — used by the\n * api/sync transports to honour `rateLimiting.skipLoopbackInDev` (api F5/F11).\n * Matches IPv4 loopback (`127.0.0.0/8`), IPv6 loopback (`::1`), the IPv4-mapped\n * form (`::ffff:127.0.0.1`, already canonicalized away by {@link resolveClientIp}),\n * and the {@link UNKNOWN_CLIENT_IP} sentinel. Pass an already-resolved IP.\n */\nexport const isLoopbackIp = (ip: string): boolean => {\n if (ip === UNKNOWN_CLIENT_IP) return true;\n const canonical = ip.startsWith('::ffff:') ? ip.slice('::ffff:'.length) : ip;\n if (canonical === '::1') return true;\n return /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(canonical);\n};\n\nconst TOKEN_HASH_LENGTH = 32;\n\n/**\n * Derive the deterministic, non-reversible bucket component for a session\n * token. The same token always yields the same value (bucket identity is\n * preserved) while the raw token never appears in the returned string.\n * Used by api AND sync rate-limit key builders — lives in core so both\n * packages can share one implementation without a cross-package dependency.\n */\nexport const deriveTokenBucketId = (token: string): string =>\n createHash('sha256').update(token).digest('hex').slice(0, TOKEN_HASH_LENGTH);\n\n//? Re-armable cooldown latch for the UNKNOWN_CLIENT_IP warning so a\n//? misconfigured deployment (e.g. `trustProxy: false` behind a reverse proxy)\n//? does not flood the log sink — one log entry per 60-second window. The first\n//? occurrence is always emitted, then suppressed until the window resets.\nconst UNKNOWN_IP_WARN_COOLDOWN_MS = 60_000;\nlet unknownIpLastWarnedAt = 0;\n\nexport const resolveClientIp = ({ rawAddress, headers, trustProxy = false, trustedProxyHopCount = 1 }: ResolveClientIpParams): string => {\n const resolved = resolveRaw({ rawAddress, headers, trustProxy, trustedProxyHopCount });\n if (resolved === UNKNOWN_CLIENT_IP) {\n //? M-8 — surface the shared fallback bucket. A burst of these usually means\n //? a reverse proxy is in front but `trustProxy` is off (every request then\n //? collapses here), or the connection was torn down before keying.\n //? Rate-suppressed to one log entry per 60 s to avoid log-sink flooding.\n const now = Date.now();\n if (now - unknownIpLastWarnedAt >= UNKNOWN_IP_WARN_COOLDOWN_MS) {\n unknownIpLastWarnedAt = now;\n getLogger().warn('rate-limit: client IP unresolved — keyed into the shared \"unknown\" bucket', { trustProxy });\n }\n }\n return resolved;\n};\n","//? Session-provider registry — the decoupling seam that lets `@luckystack/login`\n//? be an OPTIONAL package. Historically api/sync/presence/server imported\n//? `getSession`/`saveSession`/`deleteSession`/`logout` straight from\n//? `@luckystack/login`, which made login a hard transitive dependency of the\n//? whole stack. Now login REGISTERS its session implementation here at boot and\n//? everyone else reads sessions through the null-safe accessors below.\n//?\n//? When login is absent (no provider registered) the app is by definition\n//? UNAUTHENTICATED: reads return `null`, writes are no-ops, logout is a no-op.\n//? Callers already handle a null session (they treat it as \"not logged in\"), so\n//? auth-gated routes reject and public routes keep working.\n//?\n//? Resolved at call time (never captured at module load) so registration order\n//? does not matter — same contract as `getProjectConfig()` / `getLogger()`.\n\nimport type { Socket } from 'socket.io';\nimport type { BaseSessionLayout } from './sessionTypes';\n\nexport interface SessionSaveResult {\n ok: boolean;\n errorCode?: string;\n}\n\nexport interface SessionLogoutInput {\n token: string | null;\n socket?: Socket;\n userId?: string | null;\n skipSessionDelete?: boolean;\n}\n\nexport interface SessionProvider {\n getSession: (token: string | null) => Promise<BaseSessionLayout | null>;\n saveSession: (\n token: string,\n data: BaseSessionLayout,\n newUser?: boolean,\n options?: { supersedeToken?: string },\n ) => Promise<SessionSaveResult>;\n deleteSession: (token: string, options?: { skipSocketLogout?: boolean }) => Promise<boolean>;\n logout: (input: SessionLogoutInput) => Promise<void>;\n}\n\nlet provider: SessionProvider | null = null;\n\n//? Called once at boot by `@luckystack/login` (side-effect in its index).\nexport const registerSessionProvider = (next: SessionProvider): void => {\n provider = next;\n};\n\nexport const getRegisteredSessionProvider = (): SessionProvider | null => provider;\n\nexport const isSessionProviderRegistered = (): boolean => provider !== null;\n\n//? Test-only — drop the registered provider between scenarios.\nexport const resetSessionProviderForTests = (): void => {\n provider = null;\n};\n\n//? Null-safe accessors. Use these everywhere outside `@luckystack/login`.\nexport const readSession = async (token: string | null): Promise<BaseSessionLayout | null> =>\n provider ? provider.getSession(token) : null;\n\n/**\n * Persist a session via the registered provider, or no-op if no login provider\n * is registered (unauthenticated-app case).\n *\n * **WARNING**: when no session provider is registered, this returns `{ ok: true }`\n * as a no-op — the session is NOT actually persisted anywhere. Callers that\n * check `result.ok` before considering a user logged in will incorrectly\n * proceed as if the session was saved. This is intentional for apps that do not\n * use `@luckystack/login`, but on a partial install (login package misconfigured\n * or not yet registered) the failure is silent rather than loud.\n * Use `isSessionProviderRegistered()` at boot to verify the provider is present.\n */\nexport const writeSession = async (\n token: string,\n data: BaseSessionLayout,\n newUser?: boolean,\n options?: { supersedeToken?: string },\n): Promise<SessionSaveResult> =>\n provider ? provider.saveSession(token, data, newUser, options) : { ok: true };\n\nexport const removeSession = async (\n token: string,\n options?: { skipSocketLogout?: boolean },\n): Promise<boolean> => (provider ? provider.deleteSession(token, options) : false);\n\nexport const performLogout = async (input: SessionLogoutInput): Promise<void> => {\n if (provider) await provider.logout(input);\n};\n","/**\n * Shared validation utilities for API and Sync request handlers.\n * Extracted to avoid code duplication between handleApiRequest and handleSyncRequest.\n */\n\nimport type { AuthProps, BaseSessionLayout } from './sessionTypes';\n\n/**\n * Check if a value is falsy (false, 0, 0n, '', null, undefined, or NaN)\n */\nexport const isFalsy = (value: unknown): boolean => {\n return (\n value === false ||\n value === 0 ||\n value === 0n ||\n value === '' ||\n value === null ||\n value === undefined ||\n (typeof value === 'number' && Number.isNaN(value))\n );\n};\n\nexport interface ValidationResult {\n status: 'success' | 'error';\n errorCode?: string;\n errorParams?: { key: string; value: string | number | boolean; }[];\n httpStatus?: number;\n}\n\n/**\n * Validate a request against authentication requirements.\n *\n * Multiple constraints on a single `additional` entry are AND'd: every\n * specified field (`nullish`, `type`, `value`, `mustBeFalsy`) must pass\n * for that entry to validate. Each entry across the array is also AND'd:\n * the request must pass every entry to succeed.\n *\n * Constraint semantics:\n * - `nullish: true` — `val` must be `null` or `undefined`.\n * - `nullish: false` — `val` must NOT be `null` or `undefined`.\n * - `type: 'string'` — `typeof val === 'string'` (skipped when val is null/undefined).\n * - `value: <x>` — strict equality `val === x`. Omit the field entirely if you\n * don't want this check; setting `value: undefined`\n * explicitly means \"val must be undefined\".\n * - `mustBeFalsy: true` — `isFalsy(val)`.\n * - `mustBeFalsy: false` — `!isFalsy(val)`.\n *\n * **SECURITY CONTRACT**: this function does NOT check `auth.login`. Login\n * enforcement is the responsibility of the surrounding API/sync handler — it\n * must verify the session exists before calling `validateRequest`. Callers\n * that omit the login check will allow unauthenticated requests through the\n * `additional[]` predicates even when `auth.login: true` is declared, because\n * `validateRequest` short-circuits to success when `additional` is empty.\n * The expected call pattern is:\n * 1. Check session exists (enforce `auth.login`).\n * 2. Call `validateRequest({ auth, user })` to enforce `auth.additional[]`.\n */\nexport const validateRequest = ({\n auth,\n user\n}: {\n auth: AuthProps;\n //? Null-safe (api F4 / SYNC-02-root): unauth / additional-only routes used to\n //? pass a non-null assertion (`user!`) here even when no session existed,\n //? which crashed once `additional[]` was present (the `condition.key in user`\n //? check throws on null/undefined). `user` is now optional; when it is\n //? absent we forbid any `additional[]` predicate (an unauthenticated request\n //? can't satisfy a session-field constraint) instead of throwing. A route with\n //? no `additional` still succeeds regardless of `user` (login itself is\n //? enforced by the surrounding handler, not here).\n user?: BaseSessionLayout | null;\n}): ValidationResult => {\n\n //? No predicates to evaluate — succeed regardless of `user`. This MUST treat\n //? an empty array the same as an absent one: the prod/generated auth map\n //? normalizes every `auth` to include `additional: []`, so a non-empty-array\n //? guard alone would forbid every PUBLIC route (login:false) for anonymous\n //? callers once the null-user branch below runs.\n if (!auth.additional || auth.additional.length === 0) {\n return { status: 'success' };\n }\n\n const forbid = (key: string): ValidationResult => ({\n status: 'error',\n errorCode: 'auth.forbidden',\n errorParams: [{ key: 'key', value: key }],\n httpStatus: 403,\n });\n\n //? No session but the route declares `additional[]` predicates: an\n //? unauthenticated request cannot satisfy any session-field constraint, so\n //? forbid the first one rather than dereferencing a null `user`.\n if (user === null || user === undefined) {\n return forbid(auth.additional[0]?.key ?? '');\n }\n\n for (const condition of auth.additional) {\n // Key must exist in user session — that's a setup error, not a runtime auth fail.\n if (!(condition.key in user)) {\n return {\n status: 'error',\n errorCode: 'auth.invalidCondition',\n errorParams: [{ key: 'key', value: condition.key }],\n httpStatus: 500,\n };\n }\n\n const val = user[condition.key];\n const isNullish = val === null || val === undefined;\n\n if (typeof condition.nullish === 'boolean' && condition.nullish !== isNullish) {\n return forbid(condition.key);\n }\n\n if (condition.type && !isNullish && typeof val !== condition.type) {\n return forbid(condition.key);\n }\n\n //? Use `'value' in condition` so an explicit `value: undefined` still\n //? participates (caller saying \"val must be undefined\"), distinct from\n //? the omitted case (no exact-value constraint).\n if ('value' in condition && val !== condition.value) {\n return forbid(condition.key);\n }\n\n if (typeof condition.mustBeFalsy === 'boolean' && condition.mustBeFalsy !== isFalsy(val)) {\n return forbid(condition.key);\n }\n }\n\n return { status: 'success' };\n};\n","/**\n * Rate Limiter\n *\n * The framework's rate-limit dispatch is pluggable via\n * `registerRateLimitStrategy()`. The built-in `defaultRateLimitStrategy`\n * implements in-memory counters with optional Redis backing for\n * multi-instance deployments. Consumers can plug in alternative strategies\n * (token-bucket, sliding-window, per-user tiers, edge-KV-backed, etc.) by\n * implementing the `RateLimitStrategy` interface.\n */\n\nimport { getProjectConfig } from './projectConfig';\nimport { createRegistry } from './createRegistry';\nimport tryCatch from './tryCatch';\nimport { redis } from './redis';\nimport { formatKey } from './redisKeyFormatter';\nimport { getLogger } from './loggerRegistry';\nimport { isProductionRuntime } from './env';\n\nexport interface CheckRateLimitParams {\n /** Unique key for rate limiting (e.g., \"user:123:api:getData\") */\n key: string;\n /** Maximum requests allowed in window */\n limit: number;\n /** Time window in milliseconds (default: 60_000 = 1 minute) */\n windowMs?: number;\n}\n\nexport interface RateLimitResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in current window */\n remaining: number;\n /** Seconds until rate limit resets */\n resetIn: number;\n}\n\n/**\n * Pluggable rate-limit backend. The framework calls these methods through\n * the active strategy slot. Default implementation is memory + Redis.\n * Replace via `registerRateLimitStrategy(...)` at boot.\n */\nexport interface RateLimitStrategy {\n /** Human-readable identifier (used in logs/diagnostics). */\n name: string;\n /** Increment the counter for `key` and return whether the request is allowed. */\n check(params: CheckRateLimitParams): Promise<RateLimitResult>;\n /** Read current status without incrementing — useful for response headers. */\n getStatus(key: string, limit: number): Promise<RateLimitResult>;\n /** Reset the counter for a single key. */\n clear(key: string): Promise<void>;\n /** Reset all counters under this strategy's namespace. */\n clearAll(): Promise<void>;\n}\n\ninterface RateLimitEntry {\n count: number;\n resetAt: number;\n}\n\n//? CORE-O7: cap the in-memory store so a key-varying flood (e.g. path-parameter\n//? spray) can't grow the Map to OOM between cleanup ticks. When the cap is hit,\n//? the oldest entry is evicted (Map preserves insertion order; the first key is\n//? the oldest). Evicting the oldest is the least-harm choice: the victim can\n//? retry (now capped to 1) while the attacker's own keys are the new ones,\n//? making them the hardest to evict. In memory mode this is a single-instance\n//? soft limit; for multi-instance deployments use Redis mode.\nconst MAX_MEMORY_STORE_SIZE = 50_000;\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\n//? Resolved at call time so `registerProjectConfig` can run after this\n//? module is imported. If the project never registers, the defaults from\n//? projectConfig.ts take effect (memory store, 'rate-limit' prefix).\nconst getRedisPrefix = (): string =>\n formatKey(`:${getProjectConfig().rateLimiting.redisKeyPrefix}`, '');\n\nconst isRedisMode = (): boolean => getProjectConfig().rateLimiting.store === 'redis';\nconst isRateLimitingEnabled = (): boolean => getProjectConfig().rateLimiting.enabled;\n\nconst buildAllowedResult = (limit: number): RateLimitResult => ({\n allowed: true,\n remaining: Math.max(0, limit),\n resetIn: 0,\n});\n\n//? Re-armable warning latch: log at most once per `REDIS_FALLBACK_LOG_COOLDOWN_MS`\n//? so a recurring Redis degradation stays visible (the old one-shot latch hid\n//? every fallback after the first, so a flapping Redis looked healthy in logs).\nconst REDIS_FALLBACK_LOG_COOLDOWN_MS = 60_000;\nlet redisFallbackLastLoggedAt = 0;\n\nconst RATE_LIMIT_INCREMENT_SCRIPT = `\nlocal current = redis.call('INCR', KEYS[1])\nif current == 1 then\n redis.call('PEXPIRE', KEYS[1], ARGV[1])\nend\nlocal ttl = redis.call('PTTL', KEYS[1])\nreturn { current, ttl }\n`;\n\nconst normalizeWindowMs = (windowMs: number | undefined): number => {\n if (windowMs === undefined || !Number.isFinite(windowMs) || windowMs <= 0) {\n return getProjectConfig().rateLimiting.windowMs;\n }\n return windowMs;\n};\n\nconst getRedisRateLimitKey = (key: string): string => `${getRedisPrefix()}:${key}`;\n\nconst checkRateLimitInMemory = ({\n key,\n limit,\n //? No default here — `normalizeWindowMs` reads `projectConfig.rateLimiting.windowMs`\n //? when `windowMs` is undefined or non-finite, so a hardcoded `60_000` would\n //? shadow the configured value (CORE-N6).\n windowMs,\n}: CheckRateLimitParams): RateLimitResult => {\n const safeWindowMs = normalizeWindowMs(windowMs);\n const now = Date.now();\n const entry = rateLimitStore.get(key);\n\n if (!entry || entry.resetAt < now) {\n //? CORE-O7: evict before inserting a new key to keep the store bounded.\n //? Prefer evicting an EXPIRED entry (rate limit already reset, no security\n //? cost) over evicting the oldest ACTIVE entry (which could let a rate-limited\n //? key bypass the limit by resetting its counter). Only when no expired entry\n //? exists do we fall back to the insertion-order oldest (least-harm choice).\n if (rateLimitStore.size >= MAX_MEMORY_STORE_SIZE) {\n let evictKey: string | undefined;\n for (const [k, e] of rateLimitStore) {\n if (e.resetAt < now) { evictKey = k; break; }\n }\n evictKey ??= rateLimitStore.keys().next().value;\n if (evictKey !== undefined) rateLimitStore.delete(evictKey);\n }\n rateLimitStore.set(key, { count: 1, resetAt: now + safeWindowMs });\n return { allowed: true, remaining: limit - 1, resetIn: Math.ceil(safeWindowMs / 1000) };\n }\n\n entry.count += 1;\n return {\n allowed: entry.count <= limit,\n remaining: Math.max(0, limit - entry.count),\n resetIn: Math.ceil((entry.resetAt - now) / 1000),\n };\n};\n\nconst checkRateLimitInRedis = async ({\n key,\n limit,\n //? No default — same reasoning as `checkRateLimitInMemory` (CORE-N6).\n windowMs,\n}: CheckRateLimitParams): Promise<RateLimitResult | null> => {\n const safeWindowMs = normalizeWindowMs(windowMs);\n const redisKey = getRedisRateLimitKey(key);\n\n const [evalError, evalResponse] = await tryCatch(\n async () => await redis.eval(RATE_LIMIT_INCREMENT_SCRIPT, 1, redisKey, String(safeWindowMs)),\n );\n\n if (evalError || !Array.isArray(evalResponse) || evalResponse.length < 2) {\n return null;\n }\n\n const requestCount = Number(evalResponse[0]);\n const ttlMs = Number(evalResponse[1]);\n const safeCount = Number.isFinite(requestCount) ? requestCount : 1;\n const safeTtlMs = Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : safeWindowMs;\n\n return {\n allowed: safeCount <= limit,\n remaining: Math.max(0, limit - safeCount),\n resetIn: Math.ceil(safeTtlMs / 1000),\n };\n};\n\nconst getRateLimitStatusInMemory = (key: string, limit: number): RateLimitResult => {\n const now = Date.now();\n const entry = rateLimitStore.get(key);\n\n if (!entry || entry.resetAt < now) {\n return { allowed: true, remaining: limit, resetIn: 0 };\n }\n\n return {\n allowed: entry.count < limit,\n remaining: Math.max(0, limit - entry.count),\n resetIn: Math.ceil((entry.resetAt - now) / 1000),\n };\n};\n\nconst getRateLimitStatusInRedis = async (key: string, limit: number): Promise<RateLimitResult | null> => {\n const redisKey = getRedisRateLimitKey(key);\n const [txError, txResponse] = await tryCatch(\n async () => await redis.multi().get(redisKey).pttl(redisKey).exec(),\n );\n\n if (txError || !txResponse || txResponse.length < 2) return null;\n\n const getResult = txResponse[0];\n const ttlResult = txResponse[1];\n if (!getResult || !ttlResult || getResult[0] || ttlResult[0]) return null;\n\n const currentCount = Number(getResult[1] ?? 0);\n const ttlMs = Number(ttlResult[1] ?? 0);\n\n if (currentCount <= 0 || ttlMs <= 0) {\n return { allowed: true, remaining: limit, resetIn: 0 };\n }\n\n return {\n allowed: currentCount < limit,\n remaining: Math.max(0, limit - currentCount),\n resetIn: Math.ceil(ttlMs / 1000),\n };\n};\n\nconst getOnStoreError = (): 'memory' | 'deny' =>\n getProjectConfig().rateLimiting.onStoreError;\n\nconst logRedisFallback = (): void => {\n const now = Date.now();\n if (now - redisFallbackLastLoggedAt < REDIS_FALLBACK_LOG_COOLDOWN_MS) return;\n redisFallbackLastLoggedAt = now;\n getLogger().warn(\n getOnStoreError() === 'deny'\n ? '[RateLimiter] Redis mode unavailable — denying request (onStoreError=deny)'\n : '[RateLimiter] Redis mode unavailable, falling back to in-memory mode (onStoreError=memory)',\n );\n};\n\n//? When `onStoreError: 'deny'` we fail closed: a Redis outage must NOT silently\n//? relax the limit. Returns a not-allowed result with the full window as the\n//? reset hint so callers surface a sane `Retry-After`.\nconst buildDeniedResult = (): RateLimitResult => ({\n allowed: false,\n remaining: 0,\n resetIn: Math.ceil(getProjectConfig().rateLimiting.windowMs / 1000),\n});\n\n/**\n * Built-in strategy: in-memory counters with optional Redis backing for\n * multi-instance deployments. Mode is controlled by\n * `projectConfig.rateLimiting.store` (`'memory'` or `'redis'`). When `redis`\n * mode fails (e.g. Redis disconnect), the strategy silently degrades to\n * in-memory and logs a one-shot warning.\n */\nexport const defaultRateLimitStrategy: RateLimitStrategy = {\n name: 'default-memory-redis',\n\n async check(params) {\n if (!isRateLimitingEnabled()) return buildAllowedResult(params.limit);\n\n if (isRedisMode()) {\n const redisResult = await checkRateLimitInRedis(params);\n if (redisResult) return redisResult;\n logRedisFallback();\n if (getOnStoreError() === 'deny') return buildDeniedResult();\n }\n\n return checkRateLimitInMemory(params);\n },\n\n async getStatus(key, limit) {\n if (!isRateLimitingEnabled()) return buildAllowedResult(limit);\n\n if (isRedisMode()) {\n const redisResult = await getRateLimitStatusInRedis(key, limit);\n if (redisResult) return redisResult;\n logRedisFallback();\n if (getOnStoreError() === 'deny') return buildDeniedResult();\n }\n\n return getRateLimitStatusInMemory(key, limit);\n },\n\n async clear(key) {\n if (isRedisMode()) {\n await tryCatch(async () => await redis.del(getRedisRateLimitKey(key)));\n }\n rateLimitStore.delete(key);\n },\n\n async clearAll() {\n if (isRedisMode()) {\n let cursor = '0';\n do {\n const [scanError, scanResponse] = await tryCatch(\n async () => await redis.scan(cursor, 'MATCH', `${getRedisPrefix()}:*`, 'COUNT', 100),\n );\n\n if (scanError || !scanResponse) break;\n\n cursor = scanResponse[0];\n const keys = scanResponse[1];\n\n if (keys.length > 0) {\n await tryCatch(async () => await redis.del(...keys));\n }\n } while (cursor !== '0');\n }\n\n rateLimitStore.clear();\n },\n};\n\nconst strategyRegistry = createRegistry<RateLimitStrategy>(defaultRateLimitStrategy, {\n onRegister: (strategy) => {\n getLogger().debug(`[RateLimiter] active strategy → ${strategy.name}`);\n },\n});\n\n/**\n * Replace the active rate-limit backend. Pass a strategy that implements\n * `RateLimitStrategy`. Common use cases:\n *\n * - Token-bucket / sliding-window algorithms for smoother burst handling.\n * - Per-user tier limits (premium accounts get higher caps).\n * - Cloudflare-Workers-KV or upstash-rest-backed counters for edge.\n * - No-op strategy for test environments.\n *\n * Call this once at boot (typically from `luckystack/server/index.ts`)\n * BEFORE the first request lands. Subsequent calls overwrite the previous\n * registration (last-write-wins).\n */\nexport const registerRateLimitStrategy = (strategy: RateLimitStrategy): void => {\n strategyRegistry.register(strategy);\n};\n\n/** Read the currently-active strategy (defaults to the built-in). */\nexport const getRateLimitStrategy = (): RateLimitStrategy => strategyRegistry.get();\n\n/**\n * Check if a request is allowed under rate limiting rules.\n * Increments the counter for the key if allowed.\n */\nexport const checkRateLimit = async (params: CheckRateLimitParams): Promise<RateLimitResult> => {\n ensureCleanupScheduled();\n return strategyRegistry.get().check(params);\n};\n\n/**\n * Get current rate limit status without incrementing counter.\n * Useful for rate limit headers in responses.\n */\nexport const getRateLimitStatus = async (key: string, limit: number): Promise<RateLimitResult> => {\n ensureCleanupScheduled();\n return strategyRegistry.get().getStatus(key, limit);\n};\n\n/**\n * Clear rate limit for a specific key.\n * Useful for admin overrides or testing.\n */\nexport const clearRateLimit = async (key: string): Promise<void> => strategyRegistry.get().clear(key);\n\n/**\n * Clear all rate limits.\n * Intended for testing or dev-mode server reset only.\n * CORE-O8: throws in production to prevent accidental global counter wipe across\n * all tenants. If you need a production-safe reset, pass a scoped key to\n * `clearRateLimit` or implement a tenant-namespaced strategy.\n */\nexport const clearAllRateLimits = async (): Promise<void> => {\n if (isProductionRuntime()) {\n //? Throw (not warn-and-return) so callers that catch the rejection know the\n //? operation was skipped. A silent `return` makes integration test scaffolding\n //? proceed with stale counters, producing flaky results (CORE-O8 logic-gap fix).\n throw new Error('[RateLimiter] clearAllRateLimits() is not allowed in production. Use clearRateLimit(key) for scoped resets.');\n }\n return strategyRegistry.get().clearAll();\n};\n\n//? Cleanup expired entries from the in-memory store on a configurable\n//? interval. Only relevant for the default strategy; custom strategies\n//? handle their own cleanup. Restart-aware: we schedule recursively so the\n//? interval picks up `projectConfig.rateLimiting.cleanupIntervalMs` whenever\n//? it changes (e.g. after `registerProjectConfig`).\n//?\n//? Lazy-started on the first `checkRateLimit`/`getRateLimitStatus` call rather\n//? than at module load, so any tool/CLI/test that merely type-imports core does\n//? NOT inherit a recurring timer it never asked for (the package's \"no import-\n//? time side effects\" doctrine).\n//? NOTE: `cleanupStarted` is never reset, even when a custom strategy is\n//? registered via `registerRateLimitStrategy()`. The cleanup timer runs on the\n//? built-in `rateLimitStore` only — it is harmless but wasteful when a custom\n//? strategy takes over (the store stays empty). If your custom strategy needs\n//? its own cleanup, schedule it in the strategy's own `clearAll` / a separate\n//? boot hook. The one-shot `onStoreError='deny'-in-memory-mode` warning (below)\n//? will also NOT fire for a strategy registered after the first request; call\n//? `registerRateLimitStrategy` before the first request to ensure the warning\n//? check fires with the intended strategy in place.\nlet cleanupStarted = false;\nconst scheduleCleanup = (): void => {\n const intervalMs = getProjectConfig().rateLimiting.cleanupIntervalMs;\n const timer = setTimeout(() => {\n const now = Date.now();\n let cleaned = 0;\n for (const [key, entry] of rateLimitStore) {\n if (entry.resetAt < now) {\n rateLimitStore.delete(key);\n cleaned++;\n }\n }\n if (cleaned > 0) {\n getLogger().debug(`[RateLimiter] Cleaned ${String(cleaned)} expired entries`);\n }\n scheduleCleanup();\n }, intervalMs);\n timer.unref();\n};\n\nconst ensureCleanupScheduled = (): void => {\n if (cleanupStarted) return;\n cleanupStarted = true;\n //? CORE-O6: warn once at the first request if `onStoreError:'deny'` is set\n //? in non-Redis mode. In memory mode there is no \"store error\" path to honor,\n //? so the setting is a silent no-op — log it so operators aren't surprised.\n if (!isRedisMode() && getOnStoreError() === 'deny') {\n getLogger().warn(\n '[RateLimiter] onStoreError=\"deny\" has no effect in memory mode (only Redis mode has a fallback path). Switch to store:\"redis\" or remove the setting.',\n );\n }\n scheduleCleanup();\n};\n","import tryCatchSync from './tryCatchSync';\nimport { isProductionRuntime } from './env';\n\ntype ValidationResult =\n | { status: 'success' }\n | { status: 'error'; message: string };\n\n interface ParsedObjectField {\n key: string;\n optional: boolean;\n type: string;\n}\n\ninterface ParsedObjectIndexSignature {\n keyName: string;\n keyType: string;\n type: string;\n}\n\nconst splitTopLevel = (value: string, splitter: '|' | '&'): string[] => {\n const items: string[] = [];\n let depthParen = 0;\n let depthBrace = 0;\n let depthBracket = 0;\n //? Track whether we are inside a string literal (single or double quote) so\n //? a `|` or `&` character appearing in `\"a|b\"` is not treated as a splitter.\n //? Escaped quotes (`\\\"`, `\\'`) extend the literal rather than ending it.\n let inString: '\"' | \"'\" | null = null;\n let token = '';\n\n //? Iterate as code-unit pairs so we can peek-and-skip the character after\n //? a backslash inside a string literal. We work on the spread array of\n //? code-points so that multi-byte characters are treated as single tokens.\n //? Code-point iteration is intentional (multi-byte chars as single tokens); type\n //? strings are ASCII syntax so emoji decomposition is a non-issue here.\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- intentional code-point split of an ASCII type string\n const codePoints = [...value];\n let i = 0;\n while (i < codePoints.length) {\n const char = codePoints[i] ?? '';\n i += 1;\n\n //? Handle escape sequences inside string literals: consume the next\n //? code-point as part of the current literal without toggling inString.\n if (inString && char === '\\\\') {\n token += char;\n const next = codePoints[i] ?? '';\n token += next;\n i += 1;\n continue;\n }\n\n //? Toggle string-literal tracking on opening/closing quote.\n if (char === '\"' || char === \"'\") {\n if (inString === char) {\n inString = null;\n } else {\n inString ??= char;\n }\n token += char;\n continue;\n }\n\n //? Only track nesting depth and split when we are not inside a string.\n if (!inString) {\n if (char === '(') depthParen += 1;\n if (char === ')') depthParen -= 1;\n if (char === '{') depthBrace += 1;\n if (char === '}') depthBrace -= 1;\n if (char === '[') depthBracket += 1;\n if (char === ']') depthBracket -= 1;\n\n if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {\n items.push(token.trim());\n token = '';\n continue;\n }\n }\n\n token += char;\n }\n\n if (token.trim()) items.push(token.trim());\n return items;\n};\n\n//? Match a quoted key that may contain any characters (including hyphens,\n//? dots, spaces) — TypeScript allows `'foo-bar'?: string` as a valid field.\n//? Group 1 = quote char, group 2 = raw key text, group 3 = optional `?`.\nconst QUOTED_FIELD_RE = /^(\"|')((?:[^\\\\]|\\\\.)*?)\\1(\\?)?\\s*:\\s*([\\s\\S]+)$/;\n//? Plain identifier key: `myField?: string`.\nconst PLAIN_FIELD_RE = /^([A-Za-z_][A-Za-z0-9_]*)(\\?)?\\s*:\\s*([\\s\\S]+)$/;\nconst INDEX_SIG_RE = /^\\[\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*([^\\]]+)\\]\\s*:\\s*([\\s\\S]+)$/;\n\n//? Parse one semicolon-separated token from a TS object-type body.\n//? Supports quoted keys like `'foo-bar'?: string` in addition to plain\n//? identifiers — previously those were silently dropped (CORE-N3).\nconst parseFieldOrIndex = (\n trimmed: string,\n fields: ParsedObjectField[],\n indexSignatures: ParsedObjectIndexSignature[],\n): void => {\n const quotedMatch = QUOTED_FIELD_RE.exec(trimmed);\n if (quotedMatch) {\n fields.push({\n key: (quotedMatch[2] ?? '').replaceAll(String.raw`\\'`, \"'\").replaceAll(String.raw`\\\"`, '\"'),\n optional: Boolean(quotedMatch[3]),\n type: (quotedMatch[4] ?? '').trim(),\n });\n return;\n }\n const plainMatch = PLAIN_FIELD_RE.exec(trimmed);\n if (plainMatch) {\n fields.push({\n key: plainMatch[1] ?? '',\n optional: Boolean(plainMatch[2]),\n type: (plainMatch[3] ?? '').trim(),\n });\n return;\n }\n const indexMatch = INDEX_SIG_RE.exec(trimmed);\n if (indexMatch) {\n indexSignatures.push({\n keyName: (indexMatch[1] ?? '').trim(),\n keyType: (indexMatch[2] ?? '').trim(),\n type: (indexMatch[3] ?? '').trim(),\n });\n }\n};\n\nconst parseObjectFields = (typeText: string): {\n fields: ParsedObjectField[];\n indexSignatures: ParsedObjectIndexSignature[];\n} => {\n const clean = typeText.trim();\n if (!clean.startsWith('{') || !clean.endsWith('}')) {\n return { fields: [], indexSignatures: [] };\n }\n\n const inner = clean.slice(1, -1);\n const fields: ParsedObjectField[] = [];\n const indexSignatures: ParsedObjectIndexSignature[] = [];\n\n let part = '';\n let depth = 0;\n //? Track string literals (single and double quote) so a `;`, `{`, or `}`\n //? character appearing inside a quoted type value (e.g. `name: 'a{b}'`) is not\n //? treated as a field delimiter or nesting marker. Same logic as `splitTopLevel`.\n let inString: '\"' | \"'\" | null = null;\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- intentional code-point split of an ASCII type string\n const codePoints = [...inner];\n let i = 0;\n while (i < codePoints.length) {\n const char = codePoints[i] ?? '';\n i += 1;\n\n if (inString && char === '\\\\') {\n part += char;\n const next = codePoints[i] ?? '';\n part += next;\n i += 1;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n if (inString === char) {\n inString = null;\n } else {\n inString ??= char;\n }\n part += char;\n continue;\n }\n\n if (!inString) {\n //? Nesting depth tracks `{}[]()<>` to find field boundaries at `;`.\n //? Supported field value types: primitives, object literals, arrays, unions,\n //? intersections, Record<K,V>, and generics like Array<T> / Promise<T>.\n //? NOT supported: conditional types (`T extends U ? A : B`), mapped types,\n //? template-literal types, and function types (`() => void`). Those fall\n //? through to the fail-closed terminal branch of `validateType` which\n //? rejects with an \"unvalidatable type\" message rather than silently passing.\n //? NOTE: `<` and `>` are used for generic delimiters (Array<T>, Record<K,V>)\n //? but also appear as comparison operators in conditional types. For the\n //? supported type set (no conditionals) this depth tracking is sufficient.\n if (char === '{' || char === '[' || char === '(' || char === '<') depth += 1;\n if (char === '}' || char === ']' || char === ')' || char === '>') depth -= 1;\n\n if (char === ';' && depth === 0) {\n const trimmed = part.trim();\n if (trimmed) {\n parseFieldOrIndex(trimmed, fields, indexSignatures);\n }\n part = '';\n continue;\n }\n }\n\n part += char;\n }\n\n const final = part.trim();\n if (final) {\n parseFieldOrIndex(final, fields, indexSignatures);\n }\n\n return { fields, indexSignatures };\n};\n\nconst matchesIndexKeyType = ({ keyType, key }: { keyType: string; key: string }): boolean => {\n const normalized = keyType.trim();\n if (normalized === 'string') return true;\n if (normalized === 'number') return /^-?\\d+(\\.\\d+)?$/.test(key);\n if (normalized.includes('|')) {\n const parts = splitTopLevel(normalized, '|').map((part) => part.trim());\n return parts.some((part) => matchesIndexKeyType({ keyType: part, key }));\n }\n if ((normalized.startsWith(\"'\") && normalized.endsWith(\"'\")) || (normalized.startsWith('\"') && normalized.endsWith('\"'))) {\n return key === normalized.slice(1, -1);\n }\n return false;\n};\n\nconst isPrimitiveMatch = (type: string, value: unknown): boolean => {\n if (type === 'string') return typeof value === 'string';\n if (type === 'number') return typeof value === 'number' && Number.isFinite(value);\n if (type === 'boolean') return typeof value === 'boolean';\n if (type === 'true') return value === true;\n if (type === 'false') return value === false;\n if (type === 'null') return value === null;\n if (type === 'undefined') return value === undefined;\n //? Accept a Date instance directly, or a string that parses to a valid date.\n //? Rejecting non-parseable strings prevents `new Date(x).toISOString()` from\n //? returning \"Invalid Date\" when a handler trusts the validator's pass.\n if (type === 'Date') return value instanceof Date || (typeof value === 'string' && !Number.isNaN(Date.parse(value)));\n return false;\n};\n\nconst parseFiniteNumericLiteral = (type: string): number | null => {\n const normalized = type.replaceAll('_', '');\n const decimal = /^-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?$/i;\n const nonDecimal = /^-?0(?:x[\\da-f]+|b[01]+|o[0-7]+)$/i;\n if (!decimal.test(normalized) && !nonDecimal.test(normalized)) return null;\n\n const negative = normalized.startsWith('-');\n const unsigned = negative ? normalized.slice(1) : normalized;\n const parsed = Number(unsigned) * (negative ? -1 : 1);\n return Number.isFinite(parsed) ? parsed : null;\n};\n\nconst isPrimitiveType = (type: string): boolean => {\n return ['string', 'number', 'boolean', 'true', 'false', 'null', 'undefined', 'Date'].includes(type);\n};\n\n//? Prototype-pollution guard: own-keys that must never be accepted as object /\n//? Record entries on the input boundary, regardless of the declared value type.\n//? An attacker smuggling `{\"__proto__\": {...}}` into a `Record<string, …>` (or\n//? an index-signature object) could otherwise reach a handler that spreads or\n//? deep-assigns the payload.\nconst PROTO_POLLUTION_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n//? Split a `Record<K, V>` type text into its key and value type at the TOP-LEVEL\n//? comma (so a nested `Record<string, { a: number }>` or a union value keeps its\n//? own commas). Returns null when the head/value can't be isolated.\nconst parseRecordParts = (type: string): { keyType: string; valueType: string } | null => {\n const inner = type.slice('Record<'.length, -1);\n let depth = 0;\n for (let index = 0; index < inner.length; index += 1) {\n const char = inner[index];\n if (char === '<' || char === '{' || char === '[' || char === '(') depth += 1;\n else if (char === '>' || char === '}' || char === ']' || char === ')') depth -= 1;\n else if (char === ',' && depth === 0) {\n return {\n keyType: inner.slice(0, index).trim(),\n valueType: inner.slice(index + 1).trim(),\n };\n }\n }\n return null;\n};\n\n//? Recursion-depth ceiling for `validateType`. The TYPE text is build-time\n//? fixed, but the VALUE is attacker-controlled: a deeply-nested array/object\n//? payload (within `requestBodyMaxBytes`) drives recursion proportional to its\n//? nesting and could blow the stack before the handler runs (cheap DoS). 64 is\n//? far deeper than any realistic typed payload while still bounding the stack.\nconst MAX_VALIDATION_DEPTH = 64;\n\nconst validateType = (typeText: string, value: unknown, path: string, depth = 0): ValidationResult => {\n if (depth > MAX_VALIDATION_DEPTH) {\n return { status: 'error', message: `${path}: input nesting exceeds the maximum depth of ${String(MAX_VALIDATION_DEPTH)}` };\n }\n\n const type = typeText.trim();\n\n if (type.startsWith('__RUNTIME_UNRESOLVED__::')) {\n const unresolvedMessage = type.replace('__RUNTIME_UNRESOLVED__::', '').trim();\n return { status: 'error', message: `${path}: ${unresolvedMessage}` };\n }\n\n if (type.startsWith('(') && type.endsWith(')')) {\n return validateType(type.slice(1, -1), value, path, depth + 1);\n }\n\n if (type.includes('|')) {\n const unionParts = splitTopLevel(type, '|').filter(Boolean);\n if (unionParts.length > 1) {\n for (const unionType of unionParts) {\n const result = validateType(unionType, value, path, depth + 1);\n if (result.status === 'success') return result;\n }\n return { status: 'error', message: `${path} does not match union type ${type}` };\n }\n //? A single-member \"union\" recurses ONLY when `splitTopLevel` actually\n //? isolated a DIFFERENT sub-type (e.g. a leading/trailing `|` was trimmed, or\n //? a parenthesised `(string)` inner remains). If the single part is the WHOLE\n //? type unchanged, there is NO top-level `|` at all — the `|` lives INSIDE a\n //? nested `{...}` / `[...]` / `<...>` (e.g. `{ theme?: 'a' | 'b' }`). Recursing\n //? on the identical string here would loop, incrementing `depth` on every pass\n //? until `MAX_VALIDATION_DEPTH` false-positives with a bogus \"input nesting\n //? exceeds the maximum depth\" on a perfectly shallow value. So fall through to\n //? the structural (object/array/primitive) handling below instead.\n const singleMember = unionParts[0] ?? type;\n if (unionParts.length === 1 && singleMember !== type) {\n return validateType(singleMember, value, path, depth + 1);\n }\n }\n\n if (type.includes('&')) {\n const intersectionParts = splitTopLevel(type, '&').filter(Boolean);\n if (intersectionParts.length > 1) {\n for (const intersectionType of intersectionParts) {\n const result = validateType(intersectionType, value, path, depth + 1);\n if (result.status === 'error') return result;\n }\n return { status: 'success' };\n }\n //? Same as the union case — a single-member intersection should recurse.\n if (intersectionParts.length === 1) {\n return validateType(intersectionParts[0] ?? type, value, path, depth + 1);\n }\n }\n\n if (type.endsWith('[]')) {\n if (!Array.isArray(value)) {\n return { status: 'error', message: `${path} should be an array` };\n }\n const itemType = type.slice(0, -2).trim();\n if (itemType === '' || itemType === type) {\n //? FAIL CLOSED: the element type was unsplittable from the `[]` suffix\n //? (defensive — should be unreachable). Don't pass an array of arbitrary\n //? elements unvalidated; surface it so the route author models it.\n return { status: 'error', message: `${path}: unvalidatable array element type for ${type}` };\n }\n for (const [index, element] of value.entries()) {\n const result = validateType(itemType, element, `${path}[${String(index)}]`, depth + 1);\n if (result.status === 'error') return result;\n }\n return { status: 'success' };\n }\n\n if ((type.startsWith(\"'\") && type.endsWith(\"'\")) || (type.startsWith('\"') && type.endsWith('\"'))) {\n const literal = type.slice(1, -1);\n return value === literal\n ? { status: 'success' }\n : { status: 'error', message: `${path} should equal ${literal}` };\n }\n\n const numericLiteral = parseFiniteNumericLiteral(type);\n if (numericLiteral !== null) {\n return typeof value === 'number' && value === numericLiteral\n ? { status: 'success' }\n : { status: 'error', message: `${path} should equal ${type}` };\n }\n\n if (isPrimitiveMatch(type, value)) {\n return { status: 'success' };\n }\n\n if (isPrimitiveType(type)) {\n const expectedType = type === 'Date' ? 'Date (ISO string or Date)' : type;\n return { status: 'error', message: `${path} should be ${expectedType}` };\n }\n\n //? `any` and `unknown` typed fields pass validation unconditionally.\n //? SECURITY NOTE: a route input field typed `any` or `unknown` accepts ANY\n //? value from the caller without structural checking. In practice this occurs\n //? in legacy routes, utility handlers, or pass-through relay fields.\n //? To tighten: narrow the field type explicitly in `ApiParams.data`, or set\n //? `validation: 'relaxed'` / `{ input: 'skip' }` on the route as a conscious\n //? opt-out (which short-circuits before this validator runs rather than silently\n //? admitting arbitrary values here).\n if (type === 'any' || type === 'unknown') {\n return { status: 'success' };\n }\n\n if (/^Record<.+>$/.test(type)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return { status: 'error', message: `${path} should be an object` };\n }\n\n const record = value as Record<string, unknown>;\n const recordParts = parseRecordParts(type);\n\n //? CORE-N4: if the Record<K,V> type string can't be parsed we fail closed\n //? rather than silently skipping value validation. An unrecognized generic\n //? shape must not pass arbitrary values unchecked.\n if (!recordParts) {\n return { status: 'error', message: `${path}: unvalidatable Record type ${type} — model it explicitly` };\n }\n\n for (const key of Object.keys(record)) {\n //? Reject prototype-polluting own-keys unconditionally.\n if (PROTO_POLLUTION_KEYS.has(key)) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n //? Validate the key against K (only the cheap string/number/literal forms\n //? `matchesIndexKeyType` understands; an unrecognized K leaves the key\n //? unconstrained, same as an index signature).\n if (\n (recordParts.keyType === 'number' || recordParts.keyType.includes('|') ||\n recordParts.keyType.startsWith(\"'\") || recordParts.keyType.startsWith('\"')) &&\n !matchesIndexKeyType({ keyType: recordParts.keyType, key })\n ) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n //? Validate each value against V — previously skipped entirely, so a\n //? `Record<string, number>` accepted `{ a: '<script>' }`.\n const valueResult = validateType(recordParts.valueType, record[key], `${path}.${key}`, depth + 1);\n if (valueResult.status === 'error') return valueResult;\n }\n\n return { status: 'success' };\n }\n\n if (type.startsWith('{') && type.endsWith('}')) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return { status: 'error', message: `${path} should be an object` };\n }\n\n const { fields, indexSignatures } = parseObjectFields(type);\n const input = value as Record<string, unknown>;\n\n for (const field of fields) {\n const fieldValue = input[field.key];\n if (fieldValue === undefined) {\n if (field.optional) continue;\n return { status: 'error', message: `${path}.${field.key} is required` };\n }\n\n const result = validateType(field.type, fieldValue, `${path}.${field.key}`, depth + 1);\n if (result.status === 'error') return result;\n }\n\n const allowedKeys = new Set(fields.map((field) => field.key));\n for (const key of Object.keys(input)) {\n if (allowedKeys.has(key)) {\n continue;\n }\n\n //? Reject prototype-polluting own-keys unconditionally — even when an\n //? index signature (`[k: string]: …`) would otherwise admit them.\n if (PROTO_POLLUTION_KEYS.has(key)) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n if (indexSignatures.length === 0) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n\n const indexValue = input[key];\n let matched = false;\n for (const indexSignature of indexSignatures) {\n if (!matchesIndexKeyType({ keyType: indexSignature.keyType, key })) {\n continue;\n }\n\n const indexResult = validateType(indexSignature.type, indexValue, `${path}.${key}`, depth + 1);\n if (indexResult.status === 'success') {\n matched = true;\n break;\n }\n }\n\n if (!matched) {\n return { status: 'error', message: `${path}.${key} is not allowed` };\n }\n }\n\n return { status: 'success' };\n }\n\n if (/^[A-Za-z_][A-Za-z0-9_]*(?:<.+>)?$/.test(type)) {\n if (/^[A-Za-z_][A-Za-z0-9_]*<.+>$/.test(type)) {\n return { status: 'error', message: `${path}: unresolved utility ${type}` };\n }\n\n return { status: 'error', message: `${path}: unresolved type ${type}` };\n }\n\n //? FAIL CLOSED. This terminal branch is reached only by a type string none of\n //? the recognizers above could structurally validate (tuples `[a, b]`,\n //? function types `() => void`, mapped/conditional/template-literal types, or\n //? an unresolved alias the generator left behind). Previously it returned\n //? `{ status: 'success' }`, silently passing ANY value for such a route — the\n //? only structural input gate became a no-op exactly where the type is\n //? unknown, enabling type-confusion / operator-smuggling. Rejecting here means\n //? an unmodelable route surfaces loudly: model the input explicitly, or set\n //? `validation: 'relaxed'` / `{ input: 'skip' }` on the route to opt out\n //? consciously (which short-circuits before this validator runs).\n return { status: 'error', message: `${path}: unvalidatable type ${type} — model it explicitly or set validation: 'relaxed' on the route` };\n};\n\n//? FAIL CLOSED on a parser throw. The hand-rolled `validateType` walks\n//? attacker-controlled values; an unexpected shape that makes it throw must map\n//? to a validation ERROR (rejected request), NEVER propagate up to the\n//? api/sync handler as an uncaught throw — that would surface as a 500 and, on\n//? some paths, bypass the input gate entirely (fail-open-to-crash).\nconst safeValidateType = (typeText: string, value: unknown, path: string): ValidationResult => {\n const [error, result] = tryCatchSync(() => validateType(typeText, value, path));\n if (error || !result) {\n return { status: 'error', message: `${path}: input validation failed` };\n }\n return result;\n};\n\nexport const validateInputByType = async ({\n typeText,\n value,\n rootKey,\n filePath,\n}: {\n typeText?: string;\n value: unknown;\n rootKey: string;\n filePath?: string;\n}): Promise<ValidationResult> => {\n if (!typeText || typeText.trim() === '' || typeText.trim() === 'any') {\n return { status: 'success' };\n }\n\n //? CORE-01: production input-validation wiring. The legacy code returned\n //? `{ status: 'success' }` unconditionally in production, making the only\n //? structural input gate a no-op in prod (test/prod divergence + operator-\n //? injection exposure for handlers that trust `data` shape).\n //?\n //? The fix splits the two costs that used to be lumped together:\n //? 1. The DEEP type RESOLVER (`@luckystack/devkit`, TypeScript compiler API)\n //? — dev-only, expensive, and `external` in the prod bundle. Still skipped\n //? in prod (it would fail to import there anyway).\n //? 2. The structural VALIDATOR (`validateType`) — a pure, dependency-free\n //? walk over the ALREADY-RESOLVED generated type text. This is cheap and\n //? now runs in production too.\n //?\n //? In prod the generated `apiTypes` type text is already fully resolved at\n //? build time (the generator ran the devkit resolver before emitting), so we\n //? validate it directly without re-resolving. `validation.runtimeMode: 'off'`\n //? is the loud, documented opt-out that restores the old prod no-op.\n if (isProductionRuntime()) {\n //? Read the mode lazily (call-time) so `registerProjectConfig` can run after\n //? this module is imported. Indirect import avoids pulling projectConfig into\n //? the dev resolver path needlessly — but it's a cheap same-package import.\n const { getProjectConfig } = await import('./projectConfig');\n if (getProjectConfig().validation.runtimeMode === 'off') {\n return { status: 'success' };\n }\n //? Validate the pre-resolved generated type text directly. If the generator\n //? left an unresolved marker (`__RUNTIME_UNRESOLVED__::`), `validateType`\n //? surfaces it as an error rather than silently passing — that's the\n //? intended loud signal to regenerate artifacts before shipping.\n return safeValidateType(typeText, value, rootKey);\n }\n\n // Dev-only: the resolver uses TypeScript's compiler API for deep type\n // expansion. `@luckystack/devkit` is marked external in the prod esbuild\n // bundle (see scripts/bundleServer.mjs), so this branch compiles in prod but\n // cannot run (import would fail). That is fine — the production branch above\n // never reaches the devkit import.\n //\n // Indirect module ID (string variable, not literal) so tsc doesn't try to\n // type-resolve devkit at build time. Devkit depends on core, so a literal\n // type-resolved import would be a build-time circular dep.\n const devkitModuleId = '@luckystack/devkit';\n const devkit = (await import(devkitModuleId)) as DevkitTypeResolverModule;\n const resolvedType = devkit.resolveRuntimeTypeText({ typeText, filePath });\n if (resolvedType.status === 'error') {\n return { status: 'error', message: `${rootKey}: ${resolvedType.message}` };\n }\n\n return safeValidateType(resolvedType.typeText, value, rootKey);\n};\n\ninterface DevkitTypeResolverModule {\n resolveRuntimeTypeText: (params: { typeText: string; filePath?: string }) =>\n | { status: 'success'; typeText: string }\n | { status: 'error'; message: string };\n}\n","/* eslint-disable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/restrict-template-expressions */\n\nimport { getProjectConfig } from './projectConfig';\nimport { getLogger } from './loggerRegistry';\nimport { dispatchHook } from './hooks/registry';\nimport { getBindAddress } from './bindAddress';\n\nexport const normalizeOrigin = ({ value, secure }: { value: string; secure: boolean }): string => {\n const trimmedValue = value.trim().toLowerCase();\n if (!trimmedValue) { return ''; }\n\n const withProtocol = trimmedValue.startsWith('http://') || trimmedValue.startsWith('https://')\n ? trimmedValue\n : `http${secure ? 's' : ''}://${trimmedValue}`;\n\n // Keep only scheme + host[:port] so paths, query params, and fragments don't affect allowlist checks.\n const extractedOrigin = (/^(https?:\\/\\/[^/?#]+)/.exec(withProtocol))?.[1] || '';\n if (!extractedOrigin) { return ''; }\n\n return extractedOrigin\n // Treat explicit :80 as equivalent to implicit default http port.\n .replace(/^http:\\/\\/(.+):80$/i, 'http://$1')\n // Treat explicit :443 as equivalent to implicit default https port.\n .replace(/^https:\\/\\/(.+):443$/i, 'https://$1');\n};\n\nconst isLocalhostOrigin = (normalized: string): boolean => {\n //? `allowLocalhost` covers every loopback spelling a dev frontend uses —\n //? `localhost`, the IPv4 `127.0.0.1`, and the IPv6 `[::1]` — not just the\n //? literal hostname (a Vite dev server on `http://127.0.0.1:5173` was\n //? previously rejected even with the flag on).\n return /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/i.test(normalized);\n};\n\nconst allowedOrigin = (origin: string): boolean => {\n const secure = process.env.SECURE === 'true';\n const cors = getProjectConfig().http.cors;\n\n //? Project-supplied origins live in `ProjectConfig.http.cors.allowedOrigins`.\n //? Two modes: static array OR synchronous resolver function. The resolver\n //? form lets consumers do per-tenant / per-time-window allow-listing\n //? without restarting; it must be sync because Socket.io's CORS check is sync.\n const configured = cors.allowedOrigins;\n\n //? Bind address comes from the registry (populated by `createLuckyStackServer`\n //? from `options.ip`/`options.port`). Before bootstrap it falls back to\n //? `SERVER_IP` plus the generic port 80. Empty `port`\n //? produces an unmatchable `host:` entry which is fine — same-origin requests\n //? are typically allowed via `cors.allowLocalhost` or the configured\n //? `allowedOrigins` list anyway.\n const { ip: bindIp, port: bindPort } = getBindAddress();\n const rawLocation = bindPort\n ? `http${secure ? 's' : ''}://${bindIp}:${bindPort}`\n : `http${secure ? 's' : ''}://${bindIp}`;\n //? Normalize the bind address ONCE and reuse it in both branches. Comparing a\n //? normalized origin against the raw `location` made the same-origin fast-path\n //? miss for default ports (:80/:443, which the normalizer strips) — it then\n //? fell through to the resolver (still safe, but the shortcut didn't fire).\n const location = normalizeOrigin({ value: rawLocation, secure });\n const normalizedOrigin = normalizeOrigin({ value: origin, secure });\n\n if (cors.allowLocalhost && normalizedOrigin && isLocalhostOrigin(normalizedOrigin)) {\n return true;\n }\n\n //? Resolver-function mode: defer entirely to the consumer's logic. The\n //? framework keeps the normalized same-origin bind address always-allowed.\n //? Pass the NORMALIZED origin so that a naïve `startsWith`-style resolver\n //? cannot be bypassed by appending a path, port alias, or mixed casing —\n //? the same normalization the static-array branch applies (CORE-O16).\n if (typeof configured === 'function') {\n if (normalizedOrigin && normalizedOrigin === location) return true;\n if (configured(normalizedOrigin)) return true;\n // Fall through to the rejection path below.\n } else {\n const normalizedAllowedOrigins = new Set(\n [location, ...configured.map((value) => normalizeOrigin({ value, secure }))]\n .filter(Boolean)\n );\n\n if (normalizedOrigin && normalizedAllowedOrigins.has(normalizedOrigin)) {\n return true;\n }\n }\n\n //? Gated behind `devLogs` to avoid amplifying CORS-rejection traffic into\n //? production logs (an attacker could spam invalid origins otherwise). The\n //? structured `corsRejected` hook is the durable signal for production —\n //? subscribe via `registerHook('corsRejected', ...)` for audit/alerting.\n const debugAllowedOrigins = typeof configured === 'function'\n ? ['<dynamic-resolver>', location]\n : [location, ...configured]\n .map((value) => normalizeOrigin({ value, secure }))\n .filter(Boolean);\n\n //? Sanitize the caller-supplied origin before including it in any log or hook\n //? payload: cap length and strip non-printable characters so an attacker cannot\n //? inject arbitrary bytes into log sinks or external alerting hooks.\n const safeOrigin = origin.slice(0, 512).replaceAll(/[^ -~]/g, '');\n\n if (getProjectConfig().logging.devLogs) {\n getLogger().warn('cors: origin not allowed', {\n origin: safeOrigin,\n normalizedOrigin,\n allowedOrigins: debugAllowedOrigins,\n allowLocalhost: cors.allowLocalhost,\n });\n }\n void dispatchHook('corsRejected', {\n origin: safeOrigin,\n normalizedOrigin,\n allowedOrigins: debugAllowedOrigins,\n allowLocalhost: cors.allowLocalhost,\n });\n return false;\n};\n\nexport default allowedOrigin;\n","//? Cross-handler in-flight-request registry. Maps a stable key\n//? (`socketId:cb` for sync, `socketId:responseIndex` for api) to the\n//? `AbortController` driving the request's handler chain. The `apiCancel`\n//? / `syncCancel` socket event handlers look up the controller and call\n//? `.abort()` so the in-flight handler can stop emitting new chunks and\n//? exit early. Also walked on socket `disconnect` to abort everything\n//? still in flight for that socket.\n//?\n//? Lives in `@luckystack/core` (not `@luckystack/api` or `@luckystack/sync`)\n//? because the cancel-event listener is registered in `@luckystack/server`'s\n//? `loadSocket.ts`, which sits below both handler packages in the dep graph.\n//?\n//? Stored as plain Map (not WeakMap) because lookup is by string key and\n//? entries are explicitly removed at request end + on `disconnect`. Memory\n//? leak risk is bounded — handlers always call `unregister*` in their\n//? cleanup paths and disconnect drains everything for the closing socket.\n\nconst syncControllers = new Map<string, AbortController>();\nconst apiControllers = new Map<string, AbortController>();\n\nconst buildKey = (socketId: string, identifier: string | number): string =>\n `${socketId}:${String(identifier)}`;\n\nexport const registerSyncAbortController = (\n socketId: string,\n cb: string,\n controller: AbortController,\n): string => {\n const key = buildKey(socketId, cb);\n syncControllers.set(key, controller);\n return key;\n};\n\nexport const unregisterSyncAbortController = (key: string): void => {\n syncControllers.delete(key);\n};\n\nexport const abortSyncByCb = (socketId: string, cb: string): boolean => {\n const key = buildKey(socketId, cb);\n const controller = syncControllers.get(key);\n if (!controller) return false;\n controller.abort();\n syncControllers.delete(key);\n return true;\n};\n\nexport const registerApiAbortController = (\n socketId: string,\n responseIndex: number | string,\n controller: AbortController,\n): string => {\n const key = buildKey(socketId, responseIndex);\n apiControllers.set(key, controller);\n return key;\n};\n\nexport const unregisterApiAbortController = (key: string): void => {\n apiControllers.delete(key);\n};\n\nexport const abortApiByResponseIndex = (\n socketId: string,\n responseIndex: number | string,\n): boolean => {\n const key = buildKey(socketId, responseIndex);\n const controller = apiControllers.get(key);\n if (!controller) return false;\n controller.abort();\n apiControllers.delete(key);\n return true;\n};\n\n//? Drain everything in-flight for a socket — called on `disconnect`.\n//? Linear scan over both registries; disconnect is rare relative to\n//? regular request volume, and the registries are bounded by per-socket\n//? in-flight request count (typically <10).\nexport const abortAllForSocket = (socketId: string): void => {\n const prefix = `${socketId}:`;\n for (const [key, controller] of syncControllers) {\n if (key.startsWith(prefix)) {\n controller.abort();\n syncControllers.delete(key);\n }\n }\n for (const [key, controller] of apiControllers) {\n if (key.startsWith(prefix)) {\n controller.abort();\n apiControllers.delete(key);\n }\n }\n};\n","//? One-time-token primitive (CORE-OTT). A namespaced, single-use, Redis-backed\n//? token suitable for password-reset and email-change confirmation links — the\n//? two flows `@luckystack/login` previously hand-rolled with the RAW token as\n//? the Redis key.\n//?\n//? SECURITY — hashing at rest. The raw token is NEVER stored. We persist\n//? `sha256(token)` (hex) as the Redis key, so a dump / read of the Redis\n//? keyspace (a backup leak, an over-broad `KEYS` from another service, an\n//? attacker with read-only Redis) does NOT yield usable tokens: an attacker\n//? would have to brute-force a 256-bit preimage to mint a redemption. The raw\n//? token is returned to the caller exactly once (to embed in the emailed URL)\n//? and otherwise only ever exists in transit.\n//?\n//? CONSUME is atomic. Redemption is a single `MULTI` `GET`+`DEL`, so a token\n//? can be redeemed AT MOST once even under concurrent clicks — the second\n//? caller sees an empty `GET` and is rejected. A wrong / expired / malformed\n//? token also fails closed (returns null).\n//?\n//? The key namespace is routed through `formatKey(namespace, hash)` so the\n//? multi-tenant key formatter (and project-name prefixing) applies uniformly,\n//? identical to every other framework key-site.\n\nimport { createHash, randomBytes } from 'node:crypto';\n\nimport { formatKey } from './redisKeyFormatter';\n// eslint-disable-next-line import-x/no-named-as-default -- redis.ts ships both a default and a `redis` named export; the default is the proxy, matching every other framework key-site\nimport redis from './redis';\nimport tryCatchSync from './tryCatchSync';\n\n//? 32 random bytes -> 64-char hex token (same entropy as the legacy\n//? login reset/email-change tokens, so existing URL shapes are unchanged).\nconst TOKEN_BYTES = 32;\n\n/** sha256(token) hex — the value actually used as the Redis key suffix. */\nconst hashToken = (token: string): string =>\n createHash('sha256').update(token).digest('hex');\n\n/** Build the namespaced Redis key for a token's HASH (never the raw token). */\nconst tokenKey = (namespace: string, token: string): string =>\n formatKey(namespace, hashToken(token));\n\n/**\n * Handle returned by {@link issueOneTimeToken}. `token` is the RAW token to hand\n * to the caller (embed in the emailed URL); call `store()` to persist its hash\n * to Redis with the configured TTL. Persisting is a separate step so the caller\n * can run veto logic (hooks, validation) between minting and committing without\n * leaving an orphan Redis key.\n */\nexport interface OneTimeTokenHandle {\n /** The raw, single-use token. Returned exactly once; never stored verbatim. */\n token: string;\n /** Persist `sha256(token)` -> `payload` in Redis with the issue TTL. */\n store(): Promise<void>;\n}\n\n/**\n * Mint a one-time token in `namespace` carrying `payload`, redeemable for `ttl`\n * seconds. Returns the raw token plus a `store()` committer. The payload is\n * serialised to a string (objects via JSON); on redemption it is returned as a\n * string and the caller parses it (or use {@link consumeOneTimeTokenJson}).\n *\n * @param namespace Key namespace (e.g. `'-pwreset'`, `'-email-change'`) — routed\n * through `formatKey` so the active key formatter / project prefix applies.\n * @param ttlSeconds Time-to-live in seconds for the stored hash.\n * @param payload Value bound to the token. A string is stored verbatim; any\n * other value is `JSON.stringify`-d.\n */\nexport const issueOneTimeToken = (\n namespace: string,\n ttlSeconds: number,\n payload: string | Record<string, unknown>,\n): OneTimeTokenHandle => {\n const token = randomBytes(TOKEN_BYTES).toString('hex');\n const value = typeof payload === 'string' ? payload : JSON.stringify(payload);\n return {\n token,\n store: async (): Promise<void> => {\n await redis.set(tokenKey(namespace, token), value, 'EX', ttlSeconds);\n },\n };\n};\n\n/**\n * Atomically validate + consume a one-time token from `namespace`. Returns the\n * stored payload STRING on success (and deletes the entry, so it can never be\n * reused), or `null` when the token is missing / expired / malformed / already\n * consumed. A single `MULTI` `GET`+`DEL` guarantees at-most-once redemption.\n */\nexport const consumeOneTimeToken = async (\n namespace: string,\n token: string,\n): Promise<string | null> => {\n if (!token || typeof token !== 'string') return null;\n const key = tokenKey(namespace, token);\n const txResult = await redis.multi().get(key).del(key).exec();\n if (!txResult || txResult.length < 2) return null;\n const first = txResult[0];\n if (!first) return null;\n const [getErr, value] = first;\n if (getErr) return null;\n //? Verify the DEL also succeeded (txResult[1][0] is the per-command error slot).\n //? If DEL failed the key was not consumed — return null (fail-closed) so the\n //? token is not considered redeemed and cannot be replayed.\n const second = txResult[1];\n if (!second || second[0]) return null;\n return typeof value === 'string' && value.length > 0 ? value : null;\n};\n\n/**\n * Convenience wrapper over {@link consumeOneTimeToken} that JSON-parses the\n * stored payload into `T`. Returns `null` on miss OR on a malformed JSON\n * payload. Use when the token was issued with an object payload.\n */\nexport const consumeOneTimeTokenJson = async <T>(\n namespace: string,\n token: string,\n): Promise<T | null> => {\n const raw = await consumeOneTimeToken(namespace, token);\n if (raw === null) return null;\n const [parseErr, parsed] = tryCatchSync(() => JSON.parse(raw) as T);\n if (parseErr) return null;\n return parsed;\n};\n\n//? Exported for tests + any consumer that needs to derive the stored key shape\n//? (e.g. an admin tool listing/expiring outstanding tokens) WITHOUT possessing\n//? the raw token is impossible by design — this helper requires the raw token,\n//? matching the at-rest guarantee.\nexport const oneTimeTokenKey = tokenKey;\n"],"mappings":"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","names":["formatter","extractLanguageFromHeader","resolveErrorMessage","normalizeErrorResponse","registry","formatter","registry","provider","registry","registry","createRequire","createRequire","registered","path","path","path","fs","registry","path","fs","socket","createHash","createHash","path","getProjectConfig","createHash","randomBytes","createHash","randomBytes"]}
|