@luckystack/core 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sleep.ts","../src/errorTrackerRegistry.ts","../src/sentrySetup.ts","../src/tryCatch.ts","../src/serviceRoute.ts","../src/socketEvents.ts","../src/responseNormalizer.ts","../src/loggerRegistry.ts","../src/hooks/registry.ts","../src/localizedNormalizer.ts","../src/errorFormatterRegistry.ts","../src/pageRouteValidation.ts","../src/socketTypes.ts","../src/projectConfig.ts","../src/runtimeMapsRegistry.ts","../src/apiMethodMapRegistry.ts","../src/notifier.ts","../src/emailRegistry.ts","../src/deployConfigRegistry.ts","../src/servicesConfigRegistry.ts","../src/localeReloader.ts","../src/env.ts","../src/db.ts","../src/clients.ts","../src/redis.ts","../src/redisKeyFormatter.ts","../src/lease.ts","../src/socketRedisAdapter.ts","../src/bootUuid.ts","../src/synchronizedEnvHashes.ts","../src/consoleLog.ts","../src/redactedLogKeys.ts","../src/cookies.ts","../src/httpApiUtils.ts","../src/paths.ts","../src/socketMiddlewareRegistry.ts","../src/csrfConfig.ts","../src/serveAvatars.ts","../src/avatarConfig.ts","../src/processUpload.ts","../src/bindAddress.ts","../src/getParams.ts","../src/extractToken.ts","../src/extractTokenFromRequest.ts","../src/validateRequest.ts","../src/rateLimiter.ts","../src/runtimeTypeValidation.ts","../src/checkOrigin.ts","../src/offlineQueue.ts","../src/socketState.ts","../src/cancelRegistry.ts","../src/csrf.ts"],"sourcesContent":["export default function sleep(ms: number) {\n return new Promise<void>((resolve) => setTimeout(resolve, ms));\n}\n","//? Backend-agnostic error-tracker registry. Lives in @luckystack/core\r\n//? (not @luckystack/error-tracking) because framework code in core /\r\n//? api / sync needs to call it without taking a hard dep on a specific\r\n//? observability backend. The error-tracking package provides adapter\r\n//? IMPLEMENTATIONS (Sentry, Datadog, PostHog); the slot lives here.\r\n//?\r\n//? Multiple adapters can be registered at once via `registerErrorTrackers([...])` —\r\n//? every event fans out to all of them. Per-adapter errors are swallowed\r\n//? so one buggy tracker can't break the chain.\r\n\r\nexport type ErrorTrackerContext = Record<string, unknown>;\r\n\r\nexport interface ErrorTrackerUser {\r\n id?: string;\r\n email?: string;\r\n username?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\nexport interface ErrorTrackerEvent {\r\n /** When false, the adapter must not forward this event (beforeSend opt-out). */\r\n forwarded: boolean;\r\n kind: 'exception' | 'message';\r\n payload: Record<string, unknown>;\r\n}\r\n\r\n//? `SpanResult<T>` was a conditional unwrap of Promise. It's now an alias\r\n//? for `T` directly because the conditional collapsed to T in both branches\r\n//? and tsup's dts emit choked on the conditional widening. Kept as an\r\n//? exported alias so adapter authors can still annotate with `SpanResult<T>`\r\n//? for documentation intent.\r\nexport type SpanResult<T> = T;\r\n\r\nexport interface ErrorTracker {\r\n /** Human-readable identifier (logs, diagnostics). */\r\n name: string;\r\n captureException: (error: unknown, context?: ErrorTrackerContext) => void;\r\n captureMessage: (\r\n message: string,\r\n level: 'info' | 'warning' | 'error' | 'fatal',\r\n context?: ErrorTrackerContext,\r\n ) => void;\r\n setUser: (user: ErrorTrackerUser | null) => void;\r\n setContext?: (key: string, context: ErrorTrackerContext | null) => void;\r\n startSpan?: <T>(name: string, op: string, fn: () => T) => T;\r\n recordMetric?: (name: string, value: number, tags?: Record<string, string>) => void;\r\n beforeSend?: (event: ErrorTrackerEvent) => ErrorTrackerEvent | null;\r\n}\r\n\r\nlet activeTrackers: ErrorTracker[] = [];\r\n\r\nexport const registerErrorTracker = (tracker: ErrorTracker): void => {\r\n activeTrackers = [tracker];\r\n};\r\n\r\nexport const registerErrorTrackers = (trackers: ErrorTracker[]): void => {\r\n activeTrackers = [...trackers];\r\n};\r\n\r\nexport const getActiveErrorTrackers = (): ErrorTracker[] => activeTrackers;\r\n\r\nexport const captureExceptionAcrossTrackers = (\r\n error: unknown,\r\n context?: ErrorTrackerContext,\r\n): void => {\r\n for (const tracker of activeTrackers) {\r\n try {\r\n tracker.captureException(error, context);\r\n } catch {\r\n // Swallow — one buggy tracker must not break the chain.\r\n }\r\n }\r\n};\r\n\r\nexport const captureMessageAcrossTrackers = (\r\n message: string,\r\n level: 'info' | 'warning' | 'error' | 'fatal',\r\n context?: ErrorTrackerContext,\r\n): void => {\r\n for (const tracker of activeTrackers) {\r\n try {\r\n tracker.captureMessage(message, level, context);\r\n } catch {\r\n // Swallow.\r\n }\r\n }\r\n};\r\n\r\nexport const setErrorTrackerUser = (user: ErrorTrackerUser | null): void => {\r\n for (const tracker of activeTrackers) {\r\n try {\r\n tracker.setUser(user);\r\n } catch {\r\n // Swallow.\r\n }\r\n }\r\n};\r\n\r\nexport const recordMetricAcrossTrackers = (\r\n name: string,\r\n value: number,\r\n tags?: Record<string, string>,\r\n): void => {\r\n for (const tracker of activeTrackers) {\r\n if (tracker.recordMetric) {\r\n try {\r\n tracker.recordMetric(name, value, tags);\r\n } catch {\r\n // Swallow.\r\n }\r\n }\r\n }\r\n};\r\n\r\nexport const startSpanAcrossTrackers = <T>(name: string, op: string, fn: () => T): T => {\r\n //? Spans don't fan out cleanly to multiple backends — they're nested\r\n //? execution scopes. We only invoke the FIRST registered tracker's\r\n //? startSpan (others get notified via captureException paths if they\r\n //? want to instrument). When no tracker supports spans, run the fn directly.\r\n const first = activeTrackers.find((t) => t.startSpan);\r\n if (!first?.startSpan) return fn();\r\n return first.startSpan(name, op, fn);\r\n};\r\n","//? Legacy Sentry single-instance slot. Predates the multi-adapter\r\n//? ErrorTracker registry. New code should `registerErrorTracker(...)`\r\n//? from `@luckystack/error-tracking`; this file keeps backwards\r\n//? compatibility AND fans out events through any registered adapters so\r\n//? a project can mix legacy `initSharedSentry` + new `registerErrorTracker`\r\n//? during migration.\r\n//?\r\n//? The framework's own call sites (`captureException`, `captureMessage`,\r\n//? `setSentryUser`, `startSpan`) keep working unchanged — they now route\r\n//? through both the legacy slot and the new adapter list.\r\n\r\nimport {\r\n captureExceptionAcrossTrackers,\r\n captureMessageAcrossTrackers,\r\n setErrorTrackerUser,\r\n startSpanAcrossTrackers,\r\n} from './errorTrackerRegistry';\r\n\r\ninterface SentryInstance {\r\n captureException: (exception: unknown, ...args: unknown[]) => string;\r\n captureMessage: (message: string, ...args: unknown[]) => string;\r\n setUser: (user: unknown) => void;\r\n setContext: (key: string, context: unknown) => void;\r\n startInactiveSpan?: (context: unknown) => unknown;\r\n}\r\n\r\nlet sentry: SentryInstance | undefined;\r\n\r\nexport const initSharedSentry = (instance: SentryInstance): void => {\r\n sentry = instance;\r\n};\r\n\r\n//? Pass `extra` inline via Sentry's hint-shaped second argument so contexts\r\n//? don't leak across concurrent captures. Previously we called\r\n//? `sentry.setContext('additional', context)` which is process-global until\r\n//? cleared — under concurrent captures the first context could leak into the\r\n//? second's report.\r\nexport const captureException = (\r\n error: unknown,\r\n context?: Record<string, unknown>,\r\n): void => {\r\n if (sentry) {\r\n if (context) {\r\n sentry.captureException(error, { extra: context });\r\n } else {\r\n sentry.captureException(error);\r\n }\r\n }\r\n //? Multi-adapter fan-out. When no adapter is registered this is a no-op.\r\n captureExceptionAcrossTrackers(error, context);\r\n};\r\n\r\nexport const captureMessage = (\r\n message: string,\r\n level: 'info' | 'warning' | 'error' | 'fatal' = 'info',\r\n context?: Record<string, unknown>,\r\n): void => {\r\n if (sentry) {\r\n if (context) {\r\n sentry.captureMessage(message, { level, extra: context });\r\n } else {\r\n sentry.captureMessage(message, level);\r\n }\r\n }\r\n captureMessageAcrossTrackers(message, level, context);\r\n};\r\n\r\nexport const setSentryUser = (user: {\r\n id?: string;\r\n email?: string;\r\n username?: string;\r\n} | null): void => {\r\n sentry?.setUser(user);\r\n setErrorTrackerUser(user);\r\n};\r\n\r\nexport const startSpan = (name: string, op: string): unknown => {\r\n //? Multi-adapter span coordination: first registered adapter that\r\n //? supports spans wins (spans don't fan out cleanly). Legacy Sentry\r\n //? single-instance path still creates the inactive span for backwards\r\n //? compatibility on consumers that haven't migrated.\r\n if (sentry?.startInactiveSpan) {\r\n return sentry.startInactiveSpan({ name, op });\r\n }\r\n //? No-op span body is intentional — this is the legacy inactive-span shape\r\n //? consumers built against; the real span lifecycle is managed elsewhere.\r\n // eslint-disable-next-line @typescript-eslint/no-empty-function -- legacy inactive-span shape\r\n startSpanAcrossTrackers(name, op, () => {});\r\n};\r\n","import { captureException } from './sentrySetup';\n\nexport default async function tryCatch<T, P>(\n func: (values: P) => Promise<T> | T,\n params?: P,\n context?: Record<string, unknown>\n): Promise<[Error | null, T | null]> {\n try {\n const response = await func(params as P);\n return [null, response];\n } catch (error) {\n captureException(error, context);\n return [error as Error, null];\n }\n}\n","const VERSION_SEGMENT_REGEX = /^v\\d+$/;\n\nexport interface ServiceRouteParseSuccess {\n status: 'success';\n normalizedRouteName: string;\n service: string;\n routeName: string;\n}\n\nexport interface ServiceRouteParseError {\n status: 'error';\n reason: string;\n}\n\nexport type ServiceRouteParseResult = ServiceRouteParseSuccess | ServiceRouteParseError;\n\nexport interface TransportRouteParseSuccess {\n status: 'success';\n normalizedFullName: string;\n version: string;\n serviceRoute: ServiceRouteParseSuccess;\n}\n\nexport interface TransportRouteParseError {\n status: 'error';\n reason: string;\n}\n\nexport type TransportRouteParseResult = TransportRouteParseSuccess | TransportRouteParseError;\n\nconst normalizeRawRoute = (value: string): string => {\n return value.trim().replaceAll('\\\\', '/').replaceAll(/^\\/+/g, '').replaceAll(/\\/+$/g, '');\n};\n\nconst hasEmptySegments = (value: string): boolean => {\n return value.split('/').some((segment) => segment.length === 0);\n};\n\nexport const parseServiceRouteName = (value: string): ServiceRouteParseResult => {\n const normalized = normalizeRawRoute(value);\n if (!normalized) {\n return { status: 'error', reason: 'Route name cannot be empty.' };\n }\n\n if (hasEmptySegments(normalized)) {\n return { status: 'error', reason: 'Route name cannot contain empty segments.' };\n }\n\n const segments = normalized.split('/');\n if (segments.length < 2) {\n return { status: 'error', reason: 'Route name must include service and route segments.' };\n }\n\n const [service, ...routeSegments] = segments;\n const routeName = routeSegments.join('/');\n\n if (!service || !routeName) {\n return { status: 'error', reason: 'Route name must include service and route segments.' };\n }\n\n return {\n status: 'success',\n normalizedRouteName: `${service}/${routeName}`,\n service,\n routeName,\n };\n};\n\nexport const parseTransportRouteName = ({\n value,\n prefix,\n}: {\n value: string;\n prefix: 'api' | 'sync';\n}): TransportRouteParseResult => {\n const normalized = normalizeRawRoute(value);\n if (!normalized) {\n return { status: 'error', reason: 'Transport route cannot be empty.' };\n }\n\n if (hasEmptySegments(normalized)) {\n return { status: 'error', reason: 'Transport route cannot contain empty segments.' };\n }\n\n const segments = normalized.split('/');\n const routeSegments = segments[0] === prefix ? segments.slice(1) : segments;\n\n if (routeSegments.length < 3) {\n return {\n status: 'error',\n reason: `Transport route must use ${prefix}/{service}/{name}/{version} format.`,\n };\n }\n\n const version = routeSegments.at(-1);\n if (!version || !VERSION_SEGMENT_REGEX.test(version)) {\n return { status: 'error', reason: 'Route version segment must match v{number}.' };\n }\n\n const serviceRouteToken = routeSegments.slice(0, -1).join('/');\n const serviceRoute = parseServiceRouteName(serviceRouteToken);\n if (serviceRoute.status === 'error') {\n return {\n status: 'error',\n reason: serviceRoute.reason,\n };\n }\n\n return {\n status: 'success',\n normalizedFullName: `${prefix}/${serviceRoute.normalizedRouteName}/${version}`,\n version,\n serviceRoute,\n };\n};\n","const withIndex = (prefix: string, responseIndex: number | string): string => {\r\n return `${prefix}${String(responseIndex)}`;\r\n};\r\n\r\nexport const socketEventNames = {\r\n apiRequest: 'apiRequest',\r\n apiCancel: 'apiCancel',\r\n sync: 'sync',\r\n syncCancel: 'syncCancel',\r\n joinRoom: 'joinRoom',\r\n leaveRoom: 'leaveRoom',\r\n getJoinedRooms: 'getJoinedRooms',\r\n updateLocation: 'updateLocation',\r\n updateSession: 'updateSession',\r\n sessionReplaced: 'sessionReplaced',\r\n logout: 'logout',\r\n\r\n intentionalDisconnect: 'intentionalDisconnect',\r\n intentionalReconnect: 'intentionalReconnect',\r\n userAfk: 'userAfk',\r\n userBack: 'userBack',\r\n\r\n connect: 'connect',\r\n disconnect: 'disconnect',\r\n reconnectAttempt: 'reconnect_attempt',\r\n connectError: 'connect_error',\r\n\r\n apiResponsePrefix: 'apiResponse-',\r\n apiStreamPrefix: 'apiStream-',\r\n syncResponsePrefix: 'sync-',\r\n syncProgressPrefix: 'sync-progress-',\r\n joinRoomResponsePrefix: 'joinRoom-',\r\n leaveRoomResponsePrefix: 'leaveRoom-',\r\n getJoinedRoomsResponsePrefix: 'getJoinedRooms-',\r\n} as const;\r\n\r\nexport const buildApiResponseEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.apiResponsePrefix, responseIndex);\r\n};\r\n\r\nexport const buildApiStreamEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.apiStreamPrefix, responseIndex);\r\n};\r\n\r\nexport const buildSyncResponseEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.syncResponsePrefix, responseIndex);\r\n};\r\n\r\nexport const buildSyncProgressEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.syncProgressPrefix, responseIndex);\r\n};\r\n\r\nexport const buildJoinRoomResponseEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.joinRoomResponsePrefix, responseIndex);\r\n};\r\n\r\nexport const buildLeaveRoomResponseEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.leaveRoomResponsePrefix, responseIndex);\r\n};\r\n\r\nexport const buildGetJoinedRoomsResponseEventName = (responseIndex: number | string): string => {\r\n return withIndex(socketEventNames.getJoinedRoomsResponsePrefix, responseIndex);\r\n};\r\n","export interface ErrorParam {\n key: string;\n value: string | number | boolean;\n}\n\nexport interface ErrorResponseInput {\n status?: unknown;\n errorCode?: unknown;\n errorParams?: unknown;\n httpStatus?: unknown;\n}\n\nexport interface NormalizedErrorResponse {\n status: 'error';\n message: string;\n errorCode: string;\n errorParams?: ErrorParam[];\n httpStatus?: number;\n}\n\nexport const INVALID_ERROR_RESPONSE_CODE = 'error.invalidResponse';\n\nexport const isErrorParamArray = (value: unknown): value is ErrorParam[] => {\n return Array.isArray(value) && value.every((item) => {\n return (\n !!item\n && typeof item === 'object'\n && typeof (item as ErrorParam).key === 'string'\n && ['string', 'number', 'boolean'].includes(typeof (item as ErrorParam).value)\n );\n });\n};\n\nexport const normalizeErrorResponseCore = ({\n response,\n fallbackHttpStatus,\n fallbackErrorCode,\n resolveMessage,\n}: {\n response: ErrorResponseInput;\n fallbackHttpStatus?: number;\n fallbackErrorCode?: string;\n resolveMessage?: (args: { errorCode: string; errorParams?: ErrorParam[] }) => string;\n}): NormalizedErrorResponse => {\n const normalizedParams = isErrorParamArray(response.errorParams) ? response.errorParams : undefined;\n const errorCodeValue = typeof response.errorCode === 'string' ? response.errorCode.trim() : '';\n const hasErrorCode = errorCodeValue.length > 0;\n const finalErrorCode = hasErrorCode ? errorCodeValue : (fallbackErrorCode ?? INVALID_ERROR_RESPONSE_CODE);\n const providedHttpStatus = typeof response.httpStatus === 'number' ? response.httpStatus : undefined;\n\n const message = resolveMessage\n ? resolveMessage({ errorCode: finalErrorCode, errorParams: normalizedParams })\n : finalErrorCode;\n\n return {\n status: 'error',\n message,\n errorCode: finalErrorCode,\n errorParams: normalizedParams,\n httpStatus: providedHttpStatus ?? fallbackHttpStatus,\n };\n};\n\nexport const defaultHttpStatusForResponse = ({\n status,\n explicitHttpStatus,\n fallbackErrorStatus = 400,\n}: {\n status: 'success' | 'error';\n explicitHttpStatus?: number;\n fallbackErrorStatus?: number;\n}): number => {\n if (typeof explicitHttpStatus === 'number') return explicitHttpStatus;\n return status === 'success' ? 200 : fallbackErrorStatus;\n};\n","//? Pluggable logger so framework code does not have to write directly to\r\n//? stdout. The default implementation uses `console.{debug,info,warn,error}`\r\n//? directly with no trailing color-code argument — that argument was only\r\n//? meaningful when `initConsolelog()` (a dev-only monkey-patch) had been\r\n//? installed, but the default logger needs to be safe in production too.\r\n//?\r\n//? Installers route framework logs to Pino / Winston / Datadog / etc. by\r\n//? calling `registerLogger({ debug, info, warn, error })` once at boot,\r\n//? mirroring the rest of the registry pattern in this package. Dev installs\r\n//? that want colored output should call `registerLogger(createDevLogger())`.\r\n//?\r\n//? Read at call-time via `getLogger()` so registration order does not matter\r\n//? — same contract as `getProjectConfig()` and friends.\r\n\r\nexport type LoggerContext = Record<string, unknown>;\r\n\r\nexport interface Logger {\r\n debug: (message: string, context?: LoggerContext) => void;\r\n info: (message: string, context?: LoggerContext) => void;\r\n warn: (message: string, context?: LoggerContext) => void;\r\n error: (message: string, error?: unknown, context?: LoggerContext) => void;\r\n}\r\n\r\nconst defaultLogger: Logger = {\r\n debug: (message, context) => {\r\n if (context === undefined) {console.debug(message);}\r\n else {console.debug(message, context);}\r\n },\r\n info: (message, context) => {\r\n if (context === undefined) {console.info(message);}\r\n else {console.info(message, context);}\r\n },\r\n warn: (message, context) => {\r\n if (context === undefined) {console.warn(message);}\r\n else {console.warn(message, context);}\r\n },\r\n error: (message, error, context) => {\r\n if (error !== undefined && context !== undefined) console.error(message, error, context);\r\n else if (error !== undefined) console.error(message, error);\r\n else if (context === undefined) {console.error(message);}\r\n else {console.error(message, context);}\r\n },\r\n};\r\n\r\n//? Dev-mode logger factory: wraps the default behavior with the colored\r\n//? terminal output that `initConsolelog()` produces. Install via\r\n//? `registerLogger(createDevLogger())` from a dev-only entry. Note: this\r\n//? still depends on `initConsolelog()` having been called to interpret the\r\n//? trailing color string — without it, the codes will appear as literal\r\n//? text. Use the default logger when running in production or unsure.\r\nexport const createDevLogger = (): Logger => ({\r\n debug: (message, context) => {\r\n if (context === undefined) {console.log(message, 'cyan');}\r\n else {console.log(message, context, 'cyan');}\r\n },\r\n info: (message, context) => {\r\n if (context === undefined) {console.log(message);}\r\n else {console.log(message, context);}\r\n },\r\n warn: (message, context) => {\r\n if (context === undefined) {console.log(message, 'yellow');}\r\n else {console.log(message, context, 'yellow');}\r\n },\r\n error: (message, error, context) => {\r\n if (error !== undefined && context !== undefined) console.log(message, error, context, 'red');\r\n else if (error !== undefined) console.log(message, error, 'red');\r\n else if (context === undefined) {console.log(message, 'red');}\r\n else {console.log(message, context, 'red');}\r\n },\r\n});\r\n\r\nlet activeLogger: Logger = defaultLogger;\r\nlet isRegistered = false;\r\n\r\nexport const registerLogger = (logger: Logger): void => {\r\n activeLogger = logger;\r\n isRegistered = true;\r\n};\r\n\r\nexport const getLogger = (): Logger => activeLogger;\r\n\r\nexport const isLoggerRegistered = (): boolean => isRegistered;\r\n\r\n//? Test-only helper — restore the default logger between integration tests.\r\n//? Never call from production code.\r\nexport const resetLoggerForTests = (): void => {\r\n activeLogger = defaultLogger;\r\n isRegistered = false;\r\n};\r\n","import type {\r\n HookName,\r\n HookPayloads,\r\n HookHandler,\r\n HookResult,\r\n HookStopSignal,\r\n SyncHookHandler,\r\n SyncHookName,\r\n SyncHookPayloads,\r\n} from './types';\r\nimport { captureException } from '../sentrySetup';\r\nimport { getLogger } from '../loggerRegistry';\r\n\r\nexport type DispatchResult =\r\n | { stopped: false }\r\n | { stopped: true; signal: HookStopSignal };\r\n\r\n// Internal type — wider than the typed handlers but safe because register/dispatch\r\n// use the same TName key to guarantee payload shape consistency.\r\ntype AnyHandler = (payload: unknown) => Promise<HookResult> | HookResult;\r\n\r\nconst hookHandlers = new Map<string, AnyHandler[]>();\r\n\r\nexport const registerHook = <TName extends HookName>(\r\n name: TName,\r\n handler: HookHandler<HookPayloads[TName]>,\r\n): void => {\r\n const existing = hookHandlers.get(name) ?? [];\r\n existing.push(handler as AnyHandler);\r\n hookHandlers.set(name, existing);\r\n};\r\n\r\nexport const dispatchHook = async <TName extends HookName>(\r\n name: TName,\r\n payload: HookPayloads[TName],\r\n): Promise<DispatchResult> => {\r\n const handlers = hookHandlers.get(name) ?? [];\r\n\r\n for (const handler of handlers) {\r\n let result: HookResult;\r\n try {\r\n result = await handler(payload);\r\n } catch (error) {\r\n // Isolated per hook — one failing handler never interrupts the main flow,\r\n // but plugin failures must still be visible. Surface to logger + Sentry\r\n // so installers can spot bugs in their registered handlers.\r\n getLogger().error(`hook: handler for \"${name}\" threw`, error, { hook: name });\r\n captureException(error, { hook: name });\r\n continue;\r\n }\r\n\r\n if (result !== undefined) {\r\n return { stopped: true, signal: result };\r\n }\r\n }\r\n\r\n return { stopped: false };\r\n};\r\n\r\n//? Test-only: drop every registered hook handler. Used by the dev `/_test/reset`\r\n//? endpoint when integration tests need a clean slate. Never call this from\r\n//? production code paths — it would silently break framework-internal hooks\r\n//? (presence post-logout cleanup, etc.).\r\nexport const clearAllHooks = (): void => {\r\n hookHandlers.clear();\r\n syncHookHandlers.clear();\r\n};\r\n\r\n// --- Synchronous hooks ---\r\n\r\ntype AnySyncHandler = (payload: unknown) => void;\r\n\r\nconst syncHookHandlers = new Map<string, AnySyncHandler[]>();\r\n\r\nexport const registerSyncHook = <TName extends SyncHookName>(\r\n name: TName,\r\n handler: SyncHookHandler<SyncHookPayloads[TName]>,\r\n): void => {\r\n const existing = syncHookHandlers.get(name) ?? [];\r\n existing.push(handler as AnySyncHandler);\r\n syncHookHandlers.set(name, existing);\r\n};\r\n\r\nexport const dispatchSyncHook = <TName extends SyncHookName>(\r\n name: TName,\r\n payload: SyncHookPayloads[TName],\r\n): void => {\r\n const handlers = syncHookHandlers.get(name) ?? [];\r\n for (const handler of handlers) {\r\n try {\r\n handler(payload);\r\n } catch (error) {\r\n // Isolated per hook — one failing handler never interrupts the main flow.\r\n getLogger().error(`hook: sync handler for \"${name}\" threw`, error, { hook: name });\r\n captureException(error, { hook: name });\r\n }\r\n }\r\n};\r\n","import type {\r\n ErrorParam,\r\n ErrorResponseInput,\r\n NormalizedErrorResponse,\r\n} from './responseNormalizer';\r\nimport {\r\n defaultHttpStatusForResponse,\r\n normalizeErrorResponseCore,\r\n} from './responseNormalizer';\r\nimport { dispatchSyncHook } from './hooks/registry';\r\n\r\n//? Factory that turns a project-provided translate function into the full\r\n//? normalizer surface the framework packages need.\r\n//?\r\n//? Before this split, `@luckystack/api` and `@luckystack/sync` imported\r\n//? `normalizeErrorResponse` from `server/utils/responseNormalizer` via deep\r\n//? relative paths — a framework-into-project reach-in. Now the project\r\n//? registers a normalizer on boot and framework code consumes it through\r\n//? the singleton below.\r\n\r\nexport type LanguageCode = string;\r\n\r\nexport interface TranslateInput {\r\n language: LanguageCode;\r\n key: string;\r\n params?: ErrorParam[];\r\n}\r\n\r\nexport type TranslateFunction = (input: TranslateInput) => string;\r\n\r\nexport interface NormalizeErrorResponseInput {\r\n response: ErrorResponseInput;\r\n preferredLocale?: string | null;\r\n userLanguage?: string | null;\r\n fallbackHttpStatus?: number;\r\n}\r\n\r\nexport interface LocalizedNormalizer {\r\n normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;\r\n resolveErrorMessage: (input: {\r\n errorCode: string;\r\n errorParams?: ErrorParam[];\r\n preferredLocale?: string | null;\r\n userLanguage?: string | null;\r\n }) => string;\r\n extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;\r\n}\r\n\r\nexport interface CreateLocalizedNormalizerInput {\r\n translate: TranslateFunction;\r\n /**\r\n * Language the framework falls back to when the caller provides no\r\n * language hints. Defaults to 'en'.\r\n */\r\n defaultLanguage?: LanguageCode;\r\n /**\r\n * Decide whether a given string is an acceptable language code. Defaults\r\n * to accepting any non-empty short-form code (ISO 639-1 style). Projects\r\n * that only support a closed set should pass a narrower predicate.\r\n */\r\n isSupportedLanguage?: (code: string) => boolean;\r\n}\r\n\r\nconst normalizeLanguageCode = (\r\n language: string | null | undefined,\r\n isSupported: (code: string) => boolean,\r\n): LanguageCode | null => {\r\n if (!language) return null;\r\n const short = language.toLowerCase().split('-')[0];\r\n if (!short) return null;\r\n return isSupported(short) ? short : null;\r\n};\r\n\r\nexport const createLocalizedNormalizer = (\r\n input: CreateLocalizedNormalizerInput,\r\n): LocalizedNormalizer => {\r\n const defaultLanguage = input.defaultLanguage ?? 'en';\r\n const isSupported = input.isSupportedLanguage ?? (() => true);\r\n\r\n const extractLanguageFromHeader = (header?: string | string[]): LanguageCode | null => {\r\n if (!header) return null;\r\n const normalized = Array.isArray(header) ? header.join(',') : header;\r\n\r\n const candidates = normalized\r\n .split(',')\r\n .map(part => part.trim().split(';')[0])\r\n .filter(Boolean);\r\n\r\n for (const candidate of candidates) {\r\n const language = normalizeLanguageCode(candidate, isSupported);\r\n if (language) return language;\r\n }\r\n return null;\r\n };\r\n\r\n const resolveLanguage = ({\r\n preferredLocale,\r\n userLanguage,\r\n }: {\r\n preferredLocale?: string | null;\r\n userLanguage?: string | null;\r\n }): LanguageCode => {\r\n return (\r\n normalizeLanguageCode(userLanguage, isSupported)\r\n ?? normalizeLanguageCode(preferredLocale, isSupported)\r\n ?? defaultLanguage\r\n );\r\n };\r\n\r\n const resolveErrorMessage = ({\r\n errorCode,\r\n errorParams,\r\n preferredLocale,\r\n userLanguage,\r\n }: {\r\n errorCode: string;\r\n errorParams?: ErrorParam[];\r\n preferredLocale?: string | null;\r\n userLanguage?: string | null;\r\n }): string => {\r\n const language = resolveLanguage({ preferredLocale, userLanguage });\r\n return input.translate({ language, key: errorCode, params: errorParams });\r\n };\r\n\r\n const normalizeErrorResponse = ({\r\n response,\r\n preferredLocale,\r\n userLanguage,\r\n fallbackHttpStatus,\r\n }: NormalizeErrorResponseInput): NormalizedErrorResponse => {\r\n //? `preErrorNormalize` handlers may mutate `payload.response` to remap\r\n //? error codes (e.g. translate `auth.required` → custom domain code, redact\r\n //? internal codes for unauth users) before normalization.\r\n const preNormalize = { response: { ...response }, preferredLocale, userLanguage, fallbackHttpStatus };\r\n dispatchSyncHook('preErrorNormalize', preNormalize);\r\n\r\n const normalized = normalizeErrorResponseCore({\r\n response: preNormalize.response,\r\n fallbackHttpStatus: preNormalize.fallbackHttpStatus,\r\n resolveMessage: ({ errorCode, errorParams }) => resolveErrorMessage({\r\n errorCode,\r\n errorParams,\r\n preferredLocale: preNormalize.preferredLocale,\r\n userLanguage: preNormalize.userLanguage,\r\n }),\r\n });\r\n\r\n const finalized: NormalizedErrorResponse = {\r\n ...normalized,\r\n httpStatus: defaultHttpStatusForResponse({\r\n status: 'error',\r\n explicitHttpStatus: normalized.httpStatus,\r\n }),\r\n };\r\n\r\n //? `postErrorNormalize` handlers may mutate `payload.normalized` to\r\n //? post-process the localized envelope (replace messages, scrub fields).\r\n const postNormalize = { normalized: finalized, preferredLocale: preNormalize.preferredLocale, userLanguage: preNormalize.userLanguage };\r\n dispatchSyncHook('postErrorNormalize', postNormalize);\r\n\r\n return postNormalize.normalized;\r\n };\r\n\r\n return { normalizeErrorResponse, resolveErrorMessage, extractLanguageFromHeader };\r\n};\r\n\r\n//? Module-level singleton so framework packages can resolve the project's\r\n//? translate wiring without deep-relative imports. The server registers its\r\n//? normalizer on boot; tests and tooling fall back to the identity default\r\n//? (returns the errorCode key unchanged as the message).\r\nconst identityNormalizer: LocalizedNormalizer = createLocalizedNormalizer({\r\n translate: ({ key }) => key,\r\n});\r\n\r\nlet activeNormalizer: LocalizedNormalizer = identityNormalizer;\r\nlet normalizerRegistered = false;\r\n\r\nexport const registerLocalizedNormalizer = (normalizer: LocalizedNormalizer): void => {\r\n activeNormalizer = normalizer;\r\n normalizerRegistered = true;\r\n};\r\n\r\nexport const getLocalizedNormalizer = (): LocalizedNormalizer => activeNormalizer;\r\n\r\n//? Used by `verifyBootstrap` to detect the silent-degradation case where the\r\n//? identity normalizer is in use (passes errorCode through as the message,\r\n//? no i18n). Production projects should always register a real normalizer.\r\nexport const isLocalizedNormalizerRegistered = (): boolean => normalizerRegistered;\r\n\r\n//? Delegating wrappers for consumers (@luckystack/api, @luckystack/sync, etc.)\r\n//? that want import-time-safe references. The project registers its\r\n//? normalizer on boot; these functions always resolve through the singleton\r\n//? at call time, so there's no import-order fragility.\r\nexport const normalizeErrorResponse = (\r\n input: NormalizeErrorResponseInput,\r\n): NormalizedErrorResponse => activeNormalizer.normalizeErrorResponse(input);\r\n\r\nexport const resolveErrorMessage = (input: {\r\n errorCode: string;\r\n errorParams?: ErrorParam[];\r\n preferredLocale?: string | null;\r\n userLanguage?: string | null;\r\n}): string => activeNormalizer.resolveErrorMessage(input);\r\n\r\nexport const extractLanguageFromHeader = (header?: string | string[]): LanguageCode | null =>\r\n activeNormalizer.extractLanguageFromHeader(header);\r\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\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\nexport type ErrorFormatter = (\n error: { status: 'error'; errorCode?: string; message?: string; httpStatus?: number; [key: string]: unknown },\n ctx: ErrorFormatterContext,\n) => Record<string, unknown>;\n\nlet activeFormatter: 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 activeFormatter = formatter;\n};\n\n/** Read the active global formatter (or null). */\nexport const getErrorFormatter = (): ErrorFormatter | null => activeFormatter;\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 = (input: {\n response: Record<string, unknown> & { status?: string };\n routeName: string;\n transport: 'socket' | 'http';\n userId?: string | null;\n perRouteFormatter?: ErrorFormatter | undefined | null;\n}): Record<string, unknown> => {\n const { response, routeName, transport, userId, perRouteFormatter } = input;\n if (response.status !== 'error') return response;\n\n const ctx: ErrorFormatterContext = { routeName, transport, userId };\n const errorEnvelope = response as { status: 'error'; errorCode?: string; message?: string; httpStatus?: number; [key: string]: unknown };\n\n if (perRouteFormatter) {\n try {\n return perRouteFormatter(errorEnvelope, ctx);\n } catch (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 = activeFormatter;\n if (globalFormatter) {\n try {\n return globalFormatter(errorEnvelope, ctx);\n } catch (error) {\n console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);\n return response;\n }\n }\n\n return response;\n};\n","//? Pure page-route validator. Lives in @luckystack/core (no Node deps) so\n//? both the client-side router (`src/main.tsx`'s `getRoutes()`) AND the\n//? devkit-side scaffold CLI / hot-reload validator can consume it through\n//? the same single source of truth.\n//?\n//? Implements the invisible-parent folder convention: a folder starting\n//? with `privateFolderPrefix` is stripped from the URL but its CHILDREN\n//? remain routeable. A `page.tsx` placed directly inside such a folder\n//? (no non-private sibling segment) is invalid because it has no URL.\n//?\n//? Reserved framework folders (`_api`, `_sync`, `_components`, …) NEVER\n//? host a page, even nested — placing `page.tsx` anywhere under one of\n//? them is a hard error.\n\nexport interface PageRouteRules {\n /**\n * Single-character prefix that marks a folder as **invisible-parent** for\n * page routing. Default: `'_'`.\n */\n privateFolderPrefix: string;\n /**\n * Folder names that are reserved for framework-internal use and may NEVER\n * host a `page.tsx`, nested or otherwise. Scaffold + page-discovery emit\n * a hard error when a page is placed inside one.\n */\n scaffoldIgnoredFolders: readonly string[];\n}\n\nexport const DEFAULT_PAGE_ROUTE_RULES: PageRouteRules = {\n privateFolderPrefix: '_',\n scaffoldIgnoredFolders: [\n '_api',\n '_sync',\n '_function',\n '_functions',\n '_component',\n '_components',\n '_provider',\n '_providers',\n '_locale',\n '_locales',\n '_socket',\n '_sockets',\n '_shared',\n '_server',\n ],\n};\n\nexport interface PagePathValidationResult {\n /** `true` when the page file produces a routeable URL. */\n valid: boolean;\n /** Computed URL when `valid`. Always begins with `/`. `/` for root page. */\n route?: string;\n /** Human-readable explanation when `!valid`. */\n reason?: string;\n}\n\n/**\n * Validate a `page.tsx` file path relative to `src/`. Accepts both\n * forward and back slashes, with or without a leading `./` or `/`.\n * The filename **must** be `page.tsx` (or `page.jsx`); other filenames\n * return `valid: false`.\n *\n * Examples (defaults):\n * admin/page.tsx -> { valid: true, route: '/admin' }\n * _housing/renting/page.tsx -> { valid: true, route: '/renting' }\n * _housing/page.tsx -> { valid: false, reason: 'no URL segment left after stripping underscore folders' }\n * _api/something/page.tsx -> { valid: false, reason: 'page.tsx cannot live inside reserved folder _api' }\n * page.tsx -> { valid: true, route: '/' }\n * some/route.tsx -> { valid: false, reason: 'not a page file (expected page.tsx)' }\n */\nexport const validatePagePath = (\n srcRelativePath: string,\n rules: PageRouteRules = DEFAULT_PAGE_ROUTE_RULES,\n): PagePathValidationResult => {\n const normalized = srcRelativePath.replaceAll('\\\\', '/').replace(/^\\.\\//, '').replace(/^\\/+/, '');\n const segments = normalized.split('/').filter((segment) => segment.length > 0);\n\n if (segments.length === 0) {\n return { valid: false, reason: 'empty path' };\n }\n\n const filename = segments.at(-1);\n if (filename !== 'page.tsx' && filename !== 'page.jsx') {\n return { valid: false, reason: 'not a page file (expected page.tsx)' };\n }\n\n const folderSegments = segments.slice(0, -1);\n const prefix = rules.privateFolderPrefix;\n const reserved = rules.scaffoldIgnoredFolders;\n\n for (const reservedName of reserved) {\n if (folderSegments.includes(reservedName)) {\n return { valid: false, reason: `page.tsx cannot live inside reserved folder ${reservedName}` };\n }\n }\n\n const visibleSegments = folderSegments.filter((segment) => !segment.startsWith(prefix));\n\n if (folderSegments.length > 0 && visibleSegments.length === 0) {\n return { valid: false, reason: 'no URL segment left after stripping underscore folders' };\n }\n\n const route = visibleSegments.length === 0 ? '/' : `/${visibleSegments.join('/')}`;\n return { valid: true, route };\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","//? Project-level runtime configuration, consumed by framework packages via\r\n//? `getProjectConfig()`. The project's entrypoint calls\r\n//? `registerProjectConfig({...})` once at boot — before any framework code\r\n//? issues API/sync requests — mirroring the pattern we use for the localized\r\n//? normalizer.\r\n//?\r\n//? Everything here is runtime-settable. Type-level project shapes\r\n//? (`SessionLayout`, etc.) stay in the project's `config.ts` so TypeScript\r\n//? inference keeps working at call sites.\r\n//?\r\n//? Framework code reads values through `getProjectConfig()` (call-time) so\r\n//? `registerProjectConfig` can be invoked after this module is imported.\r\n//? Never read at module load — that captures whatever was registered at\r\n//? import time, which is fragile.\r\n//?\r\n//? `registerProjectConfig` accepts a deep-partial of `ProjectConfig`; missing\r\n//? fields fall back to the defaults defined here. This means a project's\r\n//? `config.ts` only has to specify the values it wants to override.\r\n\r\nexport interface LoggingConfig {\r\n devLogs: boolean;\r\n devNotifications: boolean;\r\n socketStatus: boolean;\r\n socketStartup: boolean;\r\n stream: boolean;\r\n}\r\n\r\nexport interface RateLimitingConfig {\r\n /**\r\n * Global kill-switch for all rate limiting. When `false`, every\r\n * `checkRateLimit` call short-circuits to \"allowed\" and counters are not\r\n * touched. Useful for local dev, load tests, or trusted internal tooling.\r\n * Defaults to `true`.\r\n */\r\n enabled: boolean;\r\n store: 'memory' | 'redis';\r\n redisKeyPrefix: string;\r\n defaultApiLimit: number | false;\r\n defaultIpLimit: number | false;\r\n windowMs: number;\r\n /** How often the in-memory store evicts expired entries. */\r\n cleanupIntervalMs: number;\r\n}\r\n\r\nexport interface SessionConfig {\r\n basedToken: boolean;\r\n expiryDays: number;\r\n /**\r\n * @deprecated Use `perUser: 'multiple'` instead. Kept for backwards\r\n * compatibility; when set, maps to `perUser: 'multiple'` with no concurrent\r\n * cap. The framework emits a one-shot deprecation warning at boot when\r\n * `allowMultiple` is explicitly set.\r\n */\r\n allowMultiple: boolean;\r\n /**\r\n * Whether multiple parallel logins per browser are permitted.\r\n * `'single'` (default): one active login per browser; opening a second tab\r\n * and logging in elsewhere replaces the first. `'multiple'`: each tab/window\r\n * can hold its own session independently.\r\n */\r\n perBrowser: 'single' | 'multiple';\r\n /**\r\n * Whether a single user can hold multiple active sessions across devices.\r\n * `'single'` (default): logging in on a new device kicks the previous one.\r\n * `'multiple'`: device A and device B can both be logged in concurrently.\r\n */\r\n perUser: 'single' | 'multiple';\r\n /**\r\n * Optional cap on concurrent sessions when `perUser === 'multiple'`. Null\r\n * = unlimited. When the cap is reached, the policy from `onConflict`\r\n * decides whether the new login is accepted (kicking oldest) or rejected.\r\n */\r\n maxConcurrentPerUser: number | null;\r\n /**\r\n * What to do when a session limit is exceeded.\r\n * `'revokeOld'` (default): kick the oldest session, accept the new login.\r\n * `'rejectNew'`: refuse the new login, leave existing sessions intact.\r\n */\r\n onConflict: 'revokeOld' | 'rejectNew';\r\n /**\r\n * When `onConflict === 'revokeOld'`, whether to broadcast a short UI\r\n * notification (\"logged in elsewhere\") to the kicked device before\r\n * disconnecting it. Default true.\r\n */\r\n notifyOldDeviceOnRevoke: boolean;\r\n /**\r\n * Prefix used for Redis session/activeUsers keys (e.g. `${projectName}-session:<token>`).\r\n * Falls back to `process.env.PROJECT_NAME` then this default at config build time.\r\n */\r\n projectName: string;\r\n}\r\n\r\nexport interface AppConfig {\r\n /**\r\n * Public URL of the app — used by OAuth callback redirects, transactional\r\n * email links (when `@luckystack/email` is installed), and any other\r\n * framework code that needs to render an absolute link to your app.\r\n * Empty default — consumers should set this to the public origin of their\r\n * deployment (e.g. `https://app.example.com`).\r\n */\r\n publicUrl: string;\r\n}\r\n\r\nexport interface HttpStreamConfig {\r\n /** Query-string flag clients pass to opt into SSE streaming. */\r\n queryParam: string;\r\n /** Value the query-string flag must have. */\r\n enabledValue: string;\r\n /** Initial comment line written when an SSE stream opens (keeps proxies happy). */\r\n connectedComment: string;\r\n}\r\n\r\nexport interface SecurityHeadersConfig {\r\n frameOptions: string;\r\n referrerPolicy: string;\r\n xssProtection: string;\r\n contentTypeOptions: string;\r\n}\r\n\r\nexport interface CorsConfig {\r\n allowedMethods: string;\r\n allowedHeaders: string;\r\n exposedHeaders: string;\r\n /** Send `Access-Control-Allow-Credentials: true`. */\r\n credentials: boolean;\r\n /**\r\n * Origins permitted to make cross-origin requests. The framework always\r\n * permits the `SERVER_IP:SERVER_PORT` it binds to. Set `allowLocalhost`\r\n * to true to also accept any `localhost` origin (useful for dev).\r\n * Origins are normalized (scheme + host + port) before comparison.\r\n *\r\n * Static list mode: pass a `string[]`. Origins are matched exactly.\r\n * Dynamic mode: pass a synchronous function `(origin) => boolean` for\r\n * per-tenant / per-time-window allow-listing. Async resolvers are NOT\r\n * supported here because Socket.io's CORS check is synchronous; use a\r\n * pre-computed in-memory cache that the function can read from.\r\n */\r\n allowedOrigins: string[] | ((origin: string) => boolean);\r\n /**\r\n * Accept any origin matching `localhost` (any port). Convenient for local\r\n * development; should be `false` in production. Defaults to `false` so\r\n * production deployments fail closed.\r\n */\r\n allowLocalhost: boolean;\r\n /**\r\n * Optional per-route override hook. When a `_api/*` file exports\r\n * `export const cors = { allowedOrigins, allowedHeaders, ... }` the\r\n * framework merges that on top of the global config for THAT route only.\r\n * Implemented in the api handler — this field is documentation-only.\r\n */\r\n // Note: per-route override is wired in `packages/api/src/handleHttpApiRequest.ts`.\r\n}\r\n\r\nexport interface HttpConfig {\r\n sessionCookieName: string;\r\n sessionCookieSameSite: 'Strict' | 'Lax' | 'None';\r\n sessionCookiePath: string;\r\n /** Maximum body size accepted on `/api/*` and `/sync/*` POSTs. */\r\n requestBodyMaxBytes: number;\r\n /** Path of the router boot-handshake endpoint. */\r\n healthEndpoint: string;\r\n /**\r\n * Liveness endpoint. Always returns 200 if the process is up and\r\n * responsive — does NOT check dependencies. Suitable for K8s `livenessProbe`.\r\n */\r\n liveEndpoint: string;\r\n /**\r\n * Readiness endpoint. Returns 200 only when Redis + Prisma are reachable\r\n * AND boot UUID is set. Suitable for K8s `readinessProbe`.\r\n */\r\n readyEndpoint: string;\r\n /** Path of the dev-only state-reset endpoint (gated by NODE_ENV + token). */\r\n testResetEndpoint: string;\r\n stream: HttpStreamConfig;\r\n securityHeaders: SecurityHeadersConfig;\r\n cors: CorsConfig;\r\n}\r\n\r\nexport interface PasswordPolicyConfig {\r\n /** Minimum total length. Default 8. */\r\n minLength: number;\r\n /** Maximum total length. Default 191 (DB column-friendly). */\r\n maxLength: number;\r\n /** Require at least one uppercase letter (A-Z). Default false. */\r\n requireUppercase: boolean;\r\n /** Require at least one lowercase letter (a-z). Default false. */\r\n requireLowercase: boolean;\r\n /** Require at least one digit (0-9). Default false. */\r\n requireNumber: boolean;\r\n /** Require at least one non-alphanumeric character. Default false. */\r\n requireSpecial: boolean;\r\n /**\r\n * Reject the 10k most common passwords (built-in list shipped with\r\n * `@luckystack/login`). Default true. Set to false in tests or when\r\n * onboarding from a legacy system that allowed weak passwords.\r\n */\r\n forbidCommon: boolean;\r\n /**\r\n * Optional consumer-supplied predicate. Return null when the password is\r\n * acceptable, or a string describing why it isn't (the string is used as\r\n * the i18n errorCode-style key surfaced to the client).\r\n */\r\n customValidator?: (password: string) => string | null;\r\n}\r\n\r\nexport interface AuthConfig {\r\n /** TTL for OAuth state tokens stored in Redis. */\r\n oauthStateTtlSeconds: number;\r\n /**\r\n * @deprecated Read `passwordPolicy.minLength`. Kept as a top-level field\r\n * for older consumer code; new code should use the structured policy.\r\n */\r\n passwordMinLength: number;\r\n /**\r\n * @deprecated Read `passwordPolicy.maxLength`. Kept as a top-level field\r\n * for older consumer code; new code should use the structured policy.\r\n */\r\n passwordMaxLength: number;\r\n /** Full credentials password policy. Replaces the deprecated min/max-length pair. */\r\n passwordPolicy: PasswordPolicyConfig;\r\n /** Maximum email length for credentials auth. */\r\n emailMaxLength: number;\r\n /** Maximum display-name length for credentials auth. */\r\n nameMaxLength: number;\r\n /**\r\n * bcrypt cost factor used when hashing passwords (registration, password\r\n * change, password reset). Default 10 matches industry guidance for 2026\r\n * hardware. Bump to 12 for higher-value accounts; 4 is acceptable for tests\r\n * to keep them fast.\r\n */\r\n bcryptRounds: number;\r\n /**\r\n * `'per-provider'` (default) — same email via Google and GitHub creates\r\n * two separate User rows (current behavior, no schema change).\r\n * `'unified'` — same email maps to a single User; subsequent sign-ins via\r\n * different providers link an Account row to the same User. Requires a\r\n * Prisma schema change documented in @luckystack/login's README.\r\n */\r\n providerAccountStrategy: 'per-provider' | 'unified';\r\n /**\r\n * `'framework'` — login package ships the /reset-password pages + APIs and\r\n * sends the reset email. Requires a registered email sender.\r\n * `'custom'` — login exposes only the primitives\r\n * (`createPasswordResetToken`, `consumePasswordResetToken`,\r\n * `updatePasswordHash`); the consumer wires their own UI and emails.\r\n * `'disabled'` (default) — no forgot-password feature; the link in\r\n * `LoginForm` does not render.\r\n */\r\n forgotPassword: 'framework' | 'custom' | 'disabled';\r\n /** Reset-token TTL in seconds when `forgotPassword === 'framework'`. */\r\n passwordResetTtlSeconds: number;\r\n /**\r\n * Brand/display name used in framework-mode password-reset emails (subject +\r\n * greeting + footer). Falls back to `'LuckyStack'` if unset; consumers should\r\n * override this to their own brand.\r\n */\r\n passwordResetBrand?: string;\r\n /** Email-change confirmation-token TTL in seconds. Default 3600 (1 hour). */\r\n emailChangeTtlSeconds: number;\r\n}\r\n\r\nexport interface OfflineQueueConfig {\r\n /** Hard cap on items per queue (api + sync are tracked separately). Default 200. */\r\n maxSize: number;\r\n /** Drop items older than this many ms when flushing/enqueuing. Default 1 hour. */\r\n maxAgeMs: number;\r\n /**\r\n * What to do when the queue is full:\r\n * - 'drop-oldest' (default): evict the oldest item, append the new one.\r\n * - 'drop-newest': reject the new item, keep the existing queue.\r\n * - 'reject': do not enqueue; caller must handle the failure.\r\n */\r\n dropPolicy: 'drop-oldest' | 'drop-newest' | 'reject';\r\n}\r\n\r\nexport interface SyncStreamThrottleConfig {\r\n /** Default `flushAtChars` for `createStreamThrottle({...})`. */\r\n flushAtChars: number;\r\n /** Default `flushEveryMs` for `createStreamThrottle({...})`. */\r\n flushEveryMs: number | false;\r\n /** Default `field` (payload key) for emitted chunks. */\r\n field: string;\r\n}\r\n\r\nexport interface SyncConfig {\r\n streamThrottle: SyncStreamThrottleConfig;\r\n /**\r\n * Yield to the event loop every N recipients during a broadcast fanout\r\n * (`receiver: 'all'` or large rooms). Lower = more responsive, higher\r\n * overhead. Default 100.\r\n */\r\n fanoutYieldEvery: number;\r\n /** Milliseconds to sleep when yielding. Default 1ms. */\r\n fanoutYieldMs: number;\r\n}\r\n\r\nexport interface SocketConfig {\r\n /** Maximum payload size for any single Socket.io message (bytes). */\r\n maxHttpBufferSize: number;\r\n /** ms with no pong response before the server considers the client gone. */\r\n pingTimeout: number;\r\n /** ms between pings. */\r\n pingInterval: number;\r\n}\r\n\r\nexport interface DevConfig {\r\n /** Debounce window for hot-reload triggers. */\r\n hotReloadDebounceMs: number;\r\n /** Chokidar `awaitWriteFinish.stabilityThreshold`. */\r\n watcherStabilityThresholdMs: number;\r\n /** Chokidar `awaitWriteFinish.pollInterval`. */\r\n watcherPollIntervalMs: number;\r\n /**\r\n * When true, log a warning the first time an api/sync route is invoked\r\n * without an `inputType` (typically because generated types haven't been\r\n * regenerated since the route was added). Helps catch routes that ship\r\n * with no runtime input validation. Default false (silent), set to true\r\n * in dev to surface missing types.\r\n */\r\n warnOnMissingInputType?: boolean;\r\n}\r\n\r\nexport interface PathsConfig {\r\n /** Frontend source root. */\r\n srcDir: string;\r\n /** Server-side source root. */\r\n serverDir: string;\r\n /** Shared (universal) source root. */\r\n sharedDir: string;\r\n /** Where uploaded user assets (avatars, etc.) live. */\r\n uploadsDir: string;\r\n /** Public static assets served as-is. */\r\n publicDir: string;\r\n /**\r\n * @deprecated Use `serverFunctionDirs` instead. Kept for backwards\r\n * compatibility with projects that set the singular form before the\r\n * function-injection system grew multi-directory support. When both are\r\n * present, `serverFunctionDirs` wins.\r\n */\r\n serverFunctionsDir: string;\r\n /**\r\n * Server-side function modules consumed by API/sync handlers. The\r\n * codegen and runtime walk every listed directory in order and merge the\r\n * results into one `Functions` interface / object. Duplicate keys\r\n * (same `<dirname>.<filename>.<export>` path produced by two roots)\r\n * fail the build — `shared/` is canonical, so the conflict tells the\r\n * consumer to delete the override.\r\n *\r\n * Default: `['functions', 'shared']`.\r\n *\r\n * Nested subdirectories work transparently — `functions/test/helper.ts`\r\n * with `export const foo = …` shows up as\r\n * `functions.test.helper.foo` on the injected `functions` parameter.\r\n */\r\n serverFunctionDirs: string[];\r\n /** Generated socket types output path (relative to project root). */\r\n generatedSocketTypes: string;\r\n /** Generated API input schemas output path. */\r\n generatedApiSchemas: string;\r\n /** Generated API docs JSON output path. */\r\n generatedApiDocs: string;\r\n}\r\n\r\nexport interface ProjectConfig {\r\n app: AppConfig;\r\n logging: LoggingConfig;\r\n rateLimiting: RateLimitingConfig;\r\n session: SessionConfig;\r\n http: HttpConfig;\r\n auth: AuthConfig;\r\n socket: SocketConfig;\r\n sync: SyncConfig;\r\n offlineQueue: OfflineQueueConfig;\r\n dev: DevConfig;\r\n paths: PathsConfig;\r\n defaultLanguage: string;\r\n /** Default theme for new users / fallback when no session preference exists. */\r\n defaultTheme?: 'light' | 'dark';\r\n /** Enable per-room activity broadcasting (presence). */\r\n socketActivityBroadcaster?: boolean;\r\n /** Show the floating socket-status indicator badge from `@luckystack/presence/client`. */\r\n socketStatusIndicator?: boolean;\r\n /** Enable client → server `updateLocation` syncing. */\r\n locationProviderEnabled?: boolean;\r\n /** Where to redirect the user after a successful OAuth callback. */\r\n loginRedirectUrl?: string;\r\n}\r\n\r\ntype DeepPartial<T> = {\r\n [K in keyof T]?: T[K] extends object | undefined ? DeepPartial<NonNullable<T[K]>> : T[K];\r\n};\r\n\r\nexport type ProjectConfigInput = DeepPartial<ProjectConfig>;\r\n\r\nexport const DEFAULT_PROJECT_CONFIG: ProjectConfig = {\r\n app: {\r\n publicUrl: '',\r\n },\r\n logging: {\r\n devLogs: false,\r\n devNotifications: false,\r\n socketStatus: false,\r\n socketStartup: false,\r\n stream: false,\r\n },\r\n rateLimiting: {\r\n enabled: true,\r\n store: 'memory',\r\n redisKeyPrefix: 'rate-limit',\r\n defaultApiLimit: 60,\r\n defaultIpLimit: 100,\r\n windowMs: 60_000,\r\n cleanupIntervalMs: 60_000,\r\n },\r\n session: {\r\n basedToken: false,\r\n expiryDays: 7,\r\n allowMultiple: false,\r\n perBrowser: 'single',\r\n perUser: 'single',\r\n maxConcurrentPerUser: null,\r\n onConflict: 'revokeOld',\r\n notifyOldDeviceOnRevoke: true,\r\n //? Empty default — `getProjectName()` reads `process.env.PROJECT_NAME` at\r\n //? call time and falls back to `'luckystack'`. Avoids capturing env at\r\n //? module-load before dotenv runs.\r\n projectName: '',\r\n },\r\n http: {\r\n sessionCookieName: 'token',\r\n sessionCookieSameSite: 'Strict',\r\n sessionCookiePath: '/',\r\n requestBodyMaxBytes: 1024 * 1024,\r\n healthEndpoint: '/_health',\r\n liveEndpoint: '/livez',\r\n readyEndpoint: '/readyz',\r\n testResetEndpoint: '/_test/reset',\r\n stream: {\r\n queryParam: 'stream',\r\n enabledValue: 'true',\r\n connectedComment: ': connected',\r\n },\r\n securityHeaders: {\r\n frameOptions: 'SAMEORIGIN',\r\n referrerPolicy: 'no-referrer',\r\n xssProtection: '1; mode=block',\r\n contentTypeOptions: 'nosniff',\r\n },\r\n cors: {\r\n allowedMethods: 'GET, POST, PUT, DELETE, OPTIONS',\r\n allowedHeaders: 'Content-Type, Authorization, X-Session-Based-Token, X-CSRF-Token, X-Request-Id',\r\n exposedHeaders: 'X-Session-Token, X-Request-Id',\r\n credentials: true,\r\n allowedOrigins: [],\r\n allowLocalhost: false,\r\n },\r\n },\r\n auth: {\r\n oauthStateTtlSeconds: 60 * 10,\r\n passwordMinLength: 8,\r\n passwordMaxLength: 191,\r\n passwordPolicy: {\r\n minLength: 8,\r\n maxLength: 191,\r\n requireUppercase: false,\r\n requireLowercase: false,\r\n requireNumber: false,\r\n requireSpecial: false,\r\n forbidCommon: true,\r\n },\r\n emailMaxLength: 191,\r\n nameMaxLength: 191,\r\n bcryptRounds: 10,\r\n providerAccountStrategy: 'per-provider',\r\n forgotPassword: 'disabled',\r\n passwordResetTtlSeconds: 60 * 60,\r\n emailChangeTtlSeconds: 60 * 60,\r\n },\r\n socket: {\r\n maxHttpBufferSize: 5 * 1024 * 1024,\r\n pingTimeout: 20_000,\r\n pingInterval: 25_000,\r\n },\r\n sync: {\r\n streamThrottle: {\r\n flushAtChars: 32,\r\n flushEveryMs: 50,\r\n field: 'chunk',\r\n },\r\n fanoutYieldEvery: 100,\r\n fanoutYieldMs: 1,\r\n },\r\n offlineQueue: {\r\n maxSize: 200,\r\n maxAgeMs: 60 * 60 * 1000,\r\n dropPolicy: 'drop-oldest',\r\n },\r\n dev: {\r\n hotReloadDebounceMs: 120,\r\n watcherStabilityThresholdMs: 120,\r\n watcherPollIntervalMs: 20,\r\n },\r\n paths: {\r\n srcDir: 'src',\r\n serverDir: 'server',\r\n sharedDir: 'shared',\r\n uploadsDir: 'uploads',\r\n publicDir: 'public',\r\n serverFunctionsDir: 'server/functions',\r\n serverFunctionDirs: ['functions', 'shared'],\r\n generatedSocketTypes: 'src/_sockets/apiTypes.generated.ts',\r\n generatedApiSchemas: 'src/_sockets/apiInputSchemas.generated.ts',\r\n generatedApiDocs: 'src/docs/apiDocs.generated.json',\r\n },\r\n defaultLanguage: 'en',\r\n defaultTheme: 'light',\r\n socketActivityBroadcaster: false,\r\n socketStatusIndicator: false,\r\n locationProviderEnabled: false,\r\n loginRedirectUrl: '/',\r\n};\r\n\r\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\r\n if (value === null || typeof value !== 'object') return false;\r\n const proto = Object.getPrototypeOf(value) as object | null;\r\n return proto === Object.prototype || proto === null;\r\n};\r\n\r\nconst deepMerge = <T>(base: T, override: DeepPartial<T> | undefined): T => {\r\n if (override === undefined) return base;\r\n if (!isPlainObject(base) || !isPlainObject(override)) {\r\n return (override as T) ?? base;\r\n }\r\n\r\n const result: Record<string, unknown> = { ...(base as Record<string, unknown>) };\r\n for (const [key, overrideValue] of Object.entries(override as Record<string, unknown>)) {\r\n if (overrideValue === undefined) continue;\r\n const baseValue = (base as Record<string, unknown>)[key];\r\n result[key] = isPlainObject(baseValue) && isPlainObject(overrideValue) ? deepMerge(baseValue, overrideValue as DeepPartial<unknown>) : overrideValue;\r\n }\r\n return result as T;\r\n};\r\n\r\nlet activeConfig: ProjectConfig = DEFAULT_PROJECT_CONFIG;\r\nlet isRegistered = false;\r\n\r\nexport const registerProjectConfig = (config: ProjectConfigInput): void => {\r\n activeConfig = deepMerge(DEFAULT_PROJECT_CONFIG, config);\r\n isRegistered = true;\r\n};\r\n\r\nexport const getProjectConfig = (): ProjectConfig => activeConfig;\r\n\r\n//? Resolve the project namespace at call time. Single source of truth used\r\n//? for Redis key prefixes (`<projectName>-session:`, `-activeUsers:`,\r\n//? `-pwreset:`, `-oauth-state:`, etc.) across session.ts, logout.ts,\r\n//? rateLimiter.ts, passwordReset.ts, login.ts, testResetRoute.ts.\r\n//?\r\n//? Resolution order (first non-empty wins):\r\n//? 1. `projectConfig.session.projectName` (if a consumer set it explicitly)\r\n//? 2. `process.env.PROJECT_NAME` (read at call time — works after dotenv)\r\n//? 3. literal `'luckystack'` as the absolute fallback\r\nexport const getProjectName = (): string => {\r\n const fromConfig = activeConfig.session.projectName;\r\n if (fromConfig && fromConfig.length > 0) return fromConfig;\r\n const fromEnv = process.env.PROJECT_NAME;\r\n if (fromEnv && fromEnv.length > 0) return fromEnv;\r\n return 'luckystack';\r\n};\r\n\r\n//? Guard for code paths that MUST run after registration (e.g. the server's\r\n//? startup sequence). Log once, never throw — framework packages should\r\n//? still do something reasonable when called in a test or CLI context that\r\n//? never registered.\r\nexport const isProjectConfigRegistered = (): boolean => isRegistered;\r\n","//? DI surface for generated runtime route maps. The project's\r\n//? `server/prod/runtimeMaps.ts` knows how to dynamically import per-preset\r\n//? `generatedApis.<preset>.ts` files and merge devkit overrides — framework\r\n//? packages don't. They ask core for the current maps at request time, and\r\n//? core delegates to whichever provider was registered on boot.\r\n//?\r\n//? Registration is a side effect of the project importing its own runtimeMaps\r\n//? module. Default (unregistered) returns empty maps so framework code can\r\n//? boot in tests without crashing.\r\n\r\ntype RuntimeMapRecord = Record<string, unknown>;\r\n\r\nexport interface RuntimeApiMapsResult {\r\n apisObject: RuntimeMapRecord;\r\n functionsObject: RuntimeMapRecord;\r\n}\r\n\r\nexport interface RuntimeSyncMapsResult {\r\n syncObject: RuntimeMapRecord;\r\n functionsObject: RuntimeMapRecord;\r\n}\r\n\r\nexport interface RuntimeMapsProvider {\r\n getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;\r\n getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;\r\n}\r\n\r\nconst emptyApi: RuntimeApiMapsResult = { apisObject: {}, functionsObject: {} };\r\nconst emptySync: RuntimeSyncMapsResult = { syncObject: {}, functionsObject: {} };\r\n\r\nlet activeProvider: RuntimeMapsProvider = {\r\n getRuntimeApiMaps: () => Promise.resolve(emptyApi),\r\n getRuntimeSyncMaps: () => Promise.resolve(emptySync),\r\n};\r\nlet providerRegistered = false;\r\n\r\nexport const registerRuntimeMapsProvider = (provider: RuntimeMapsProvider): void => {\r\n activeProvider = provider;\r\n providerRegistered = true;\r\n};\r\n\r\n//? Used by `verifyBootstrap` to detect the production fail-mode where no\r\n//? provider got registered (typically because the project forgot to import\r\n//? its `server/prod/runtimeMaps.ts` side-effect). Without this check, every\r\n//? api/sync request silently returns `api.notFound` / `sync.notFound`.\r\nexport const isRuntimeMapsProviderRegistered = (): boolean => providerRegistered;\r\n\r\nexport const getRuntimeApiMaps = async (): Promise<RuntimeApiMapsResult> =>\r\n activeProvider.getRuntimeApiMaps();\r\n\r\nexport const getRuntimeSyncMaps = async (): Promise<RuntimeSyncMapsResult> =>\r\n activeProvider.getRuntimeSyncMaps();\r\n","//? Generated `apiMethodMap` registry. Replaces the prefix-heuristic\r\n//? in `isGetMethod` (apiRequest.ts) so abort-controller selection uses\r\n//? the actual HTTP method declared on the handler instead of guessing\r\n//? from the name prefix.\r\n//?\r\n//? Wiring: the project's `src/_sockets/socketInitializer.ts` (or any boot\r\n//? entry that imports `apiTypes.generated.ts`) calls\r\n//? `registerApiMethodMap(apiMethodMap)` once at module load. Until then,\r\n//? lookups return `undefined` and `isGetMethod` falls back to the\r\n//? prefix heuristic (preserving previous behavior for transitional code).\r\n\r\nexport type HttpMethodLiteral = 'GET' | 'POST' | 'PUT' | 'DELETE';\r\n\r\nexport type ApiMethodMap = Record<string, Record<string, Record<string, HttpMethodLiteral>>>;\r\n\r\nlet activeMap: ApiMethodMap | null = null;\r\n\r\nexport const registerApiMethodMap = (map: ApiMethodMap): void => {\r\n activeMap = map;\r\n};\r\n\r\nexport const getRegisteredApiMethod = (\r\n pagePath: string,\r\n apiName: string,\r\n version: string,\r\n): HttpMethodLiteral | undefined => {\r\n if (!activeMap) return undefined;\r\n //? The map shape is `Record<page, Record<name, Record<version, method>>>`;\r\n //? TS treats each indexed access as defined (no `noUncheckedIndexedAccess`),\r\n //? so the optional chains look redundant. They're not — a missing key returns\r\n //? undefined at runtime, which is the contract this function is documenting.\r\n /* eslint-disable @typescript-eslint/no-unnecessary-condition -- runtime-defensive index access */\r\n return activeMap[pagePath]?.[apiName]?.[version];\r\n /* eslint-enable @typescript-eslint/no-unnecessary-condition */\r\n};\r\n\r\nexport const isApiMethodMapRegistered = (): boolean => activeMap !== null;\r\n","//? Client-side notifier DI. Framework packages (apiRequest, syncRequest,\r\n//? offline queue flush) emit toasts on transport errors. Default is silent\r\n//? (no-op) so tests and SSR contexts don't need a toaster installed.\r\n//?\r\n//? The project's entry point registers a notifier backed by its UI toolkit\r\n//? (sonner + i18n) via `registerNotifier({ error, success, ... })`.\r\n\r\nexport interface NotifyParam {\r\n key: string;\r\n value: string | number | boolean;\r\n}\r\n\r\nexport interface NotifyInput {\r\n key: string;\r\n params?: NotifyParam[];\r\n}\r\n\r\nexport interface Notifier {\r\n success: (input: NotifyInput) => void;\r\n error: (input: NotifyInput) => void;\r\n info: (input: NotifyInput) => void;\r\n warning: (input: NotifyInput) => void;\r\n}\r\n\r\nconst noopNotifier: Notifier = {\r\n success: () => { /* no-op */ },\r\n error: () => { /* no-op */ },\r\n info: () => { /* no-op */ },\r\n warning: () => { /* no-op */ },\r\n};\r\n\r\nlet activeNotifier: Notifier = noopNotifier;\r\n\r\nexport const registerNotifier = (notifier: Notifier): void => {\r\n activeNotifier = notifier;\r\n};\r\n\r\nexport const getNotifier = (): Notifier => activeNotifier;\r\n\r\n//? Delegating wrapper matching the project's existing `notify` shape so the\r\n//? switchover inside framework packages is a one-line import change.\r\nexport const notify: Notifier = {\r\n success: (input) => { activeNotifier.success(input); },\r\n error: (input) => { activeNotifier.error(input); },\r\n info: (input) => { activeNotifier.info(input); },\r\n warning: (input) => { activeNotifier.warning(input); },\r\n};\r\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\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\nexport type EmailResult =\n | { ok: true; id: string }\n | { ok: false; reason: string; cause?: unknown };\n\nexport interface EmailSender {\n /** Adapter identifier for logs + diagnostics (\"console\", \"resend\", \"smtp\", etc.). */\n name: string;\n send: (message: EmailMessage) => 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","//? Deploy-time topology registered by the project at boot. Mirrors the\r\n//? `projectConfig` / `runtimeMapsRegistry` / `notifier` pattern: framework\r\n//? packages read deploy data through `getDeployConfig()` so they don't\r\n//? `import '../../../deploy.config'`.\r\n//?\r\n//? Only the fields core actually consumes are declared here. The project's\r\n//? richer `DeployConfig` (environments, bindings, routing, etc.) stays in\r\n//? `deploy.config.ts` and is consumed directly by the router package, which\r\n//? is project-glue (tier-B) and intentionally stays project-coupled.\r\n//?\r\n//? Project entrypoint registers via the direct file path\r\n//? (`./packages/core/src/deployConfigRegistry`) — same Vite-bundle rule we\r\n//? already use for projectConfig: importing the core barrel from a\r\n//? client-bundled file pulls server-only modules (Redis, paths, etc.).\r\n\r\nexport interface DeployResourceShape {\r\n type: 'redis' | 'mongo';\r\n urlEnvKey: string;\r\n synchronizedEnvKeys?: string[];\r\n}\r\n\r\nexport interface DeployEnvironmentShape {\r\n redis: string;\r\n mongo: string;\r\n fallback?: string;\r\n bindings: Record<string, string>;\r\n}\r\n\r\nexport interface DeployRoutingShape {\r\n onMissingService?: 'hard-error' | 'proxy-fallback';\r\n missingServiceErrorCode?: string;\r\n enableUnhealthyFallback?: boolean;\r\n strictBootHandshake?: boolean;\r\n /** Default port the router listens on when ROUTER_PORT env var is unset (default 4000). */\r\n defaultRouterPort?: number;\r\n /** Default ms between health polls when not specified per-call (default 5000). */\r\n defaultHealthPollMs?: number;\r\n /** ms before the fallback `/_health` probe times out (default 3000). */\r\n healthProbeTimeoutMs?: number;\r\n /** Seconds the boot-UUID Redis key lives for (default 3600 = 1h). */\r\n bootKeyTtlSeconds?: number;\r\n}\r\n\r\nexport interface DeployDevelopmentShape {\r\n enableFallbackRouting?: boolean;\r\n healthPollMs?: number;\r\n switchNewTrafficToLocalWhenHealthy?: boolean;\r\n}\r\n\r\nexport interface DeployConfigShape {\r\n resources: Record<string, DeployResourceShape>;\r\n environments?: Record<string, DeployEnvironmentShape>;\r\n routing?: DeployRoutingShape;\r\n development?: DeployDevelopmentShape;\r\n}\r\n\r\nconst DEFAULT_DEPLOY_CONFIG: DeployConfigShape = {\r\n resources: {},\r\n};\r\n\r\nlet activeConfig: DeployConfigShape = DEFAULT_DEPLOY_CONFIG;\r\nlet registered = false;\r\n\r\nexport const registerDeployConfig = (config: DeployConfigShape): DeployConfigShape => {\r\n activeConfig = config;\r\n registered = true;\r\n return activeConfig;\r\n};\r\n\r\nexport const getDeployConfig = (): DeployConfigShape => activeConfig;\r\n\r\nexport const isDeployConfigRegistered = (): boolean => registered;\r\n","//? Services config registry. The router (and presetLoader) consume the\n//? consumer's services topology — which services exist, which presets bundle\n//? them together — through this registry instead of a relative\n//? `../../../services.config` import.\n//?\n//? The consumer's `services.config.ts` registers its config at module import\n//? (side-effect pattern, identical to `deploy.config.ts:151`). Router code\n//? calls `getServicesConfig()` at request time.\n\nexport interface ServiceDefinition {\n /** 'root' -> reserved for the system service. Otherwise a folder name under srcDir. */\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- 'root' is documented as the reserved value; the union surfaces it in autocomplete\n source: 'root' | string;\n}\n\nexport interface PresetDefinition {\n description?: string;\n /** Services that are bundled together into one backend artifact. */\n services: string[];\n}\n\nexport interface ServicesConfigShape {\n services: Record<string, ServiceDefinition>;\n presets: Record<string, PresetDefinition>;\n}\n\nlet activeServicesConfig: ServicesConfigShape | null = null;\n\nexport const registerServicesConfig = (config: ServicesConfigShape): ServicesConfigShape => {\n activeServicesConfig = config;\n return activeServicesConfig;\n};\n\nexport const getServicesConfig = (): ServicesConfigShape => {\n if (!activeServicesConfig) {\n throw new Error(\n '@luckystack/core: services config has not been registered. Call ' +\n '`registerServicesConfig({...})` from your `services.config.ts` (which should be ' +\n 'imported as a side-effect during boot).'\n );\n }\n return activeServicesConfig;\n};\n\nexport const isServicesConfigRegistered = (): boolean => activeServicesConfig !== null;\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\nexport type LocaleReloader = () => void | Promise<void>;\n\nlet activeReloader: LocaleReloader | null = null;\n\nexport const registerLocaleReloader = (reloader: LocaleReloader): LocaleReloader => {\n activeReloader = reloader;\n return reloader;\n};\n\nexport const getLocaleReloader = (): LocaleReloader | null => activeReloader;\n","import { config as loadDotenv } from 'dotenv';\r\nimport { z } from 'zod';\r\n\r\nconst EnvSchema = z.object({\r\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\r\n SERVER_IP: z.string().min(1).default('127.0.0.1'),\r\n SERVER_PORT: z.string().regex(/^\\d+$/).default('80'),\r\n SECURE: z.enum(['true', 'false']).default('false'),\r\n REDIS_HOST: z.string().min(1).default('127.0.0.1'),\r\n REDIS_PORT: z.string().regex(/^\\d+$/).default('6379'),\r\n PROJECT_NAME: z.string().min(1).default('luckystack'),\r\n}).loose();\r\n\r\nexport type RuntimeEnv = z.infer<typeof EnvSchema>;\r\n\r\nlet cachedEnv: RuntimeEnv | null = null;\r\n\r\n//? Single source of truth for which env files the framework loads, in order\r\n//? (\"later overrides earlier\"). Default `['.env', '.env.local']`. Override at\r\n//? runtime with the AMBIENT env var `LUCKYSTACK_ENV_FILES` (comma-separated) — it\r\n//? must be a real environment variable, not a key inside one of the .env files\r\n//? (those are only read AFTER this list is resolved).\r\nexport const DEFAULT_ENV_FILES = ['.env', '.env.local'];\r\n\r\nexport const getEnvFiles = (): string[] => {\r\n const override = process.env.LUCKYSTACK_ENV_FILES;\r\n if (override) {\r\n const list = override.split(',').map((entry) => entry.trim()).filter(Boolean);\r\n if (list.length > 0) return list;\r\n }\r\n return DEFAULT_ENV_FILES;\r\n};\r\n\r\nexport const loadEnvFiles = (): void => {\r\n //? First file is non-override (a real ambient env var wins); each later file\r\n //? overrides earlier ones, matching the historical `.env` -> `.env.local` order.\r\n for (const [index, file] of getEnvFiles().entries()) {\r\n loadDotenv({ path: file, override: index > 0 });\r\n }\r\n};\r\n\r\nconst applyResolvedDefaultsToProcessEnv = (resolvedEnv: RuntimeEnv) => {\r\n process.env.NODE_ENV = process.env.NODE_ENV ?? resolvedEnv.NODE_ENV;\r\n process.env.SERVER_IP = process.env.SERVER_IP ?? resolvedEnv.SERVER_IP;\r\n process.env.SERVER_PORT = process.env.SERVER_PORT ?? resolvedEnv.SERVER_PORT;\r\n process.env.SECURE = process.env.SECURE ?? resolvedEnv.SECURE;\r\n process.env.REDIS_HOST = process.env.REDIS_HOST ?? resolvedEnv.REDIS_HOST;\r\n process.env.REDIS_PORT = process.env.REDIS_PORT ?? resolvedEnv.REDIS_PORT;\r\n process.env.PROJECT_NAME = process.env.PROJECT_NAME ?? resolvedEnv.PROJECT_NAME;\r\n};\r\n\r\nexport const bootstrapEnv = (): RuntimeEnv => {\r\n if (cachedEnv) {\r\n return cachedEnv;\r\n }\r\n\r\n loadEnvFiles();\r\n\r\n const parsed = EnvSchema.safeParse(process.env);\r\n if (!parsed.success) {\r\n const details = parsed.error.issues\r\n .map((issue) => `${issue.path.join('.') || 'env'}: ${issue.message}`)\r\n .join('; ');\r\n\r\n throw new Error(`Invalid environment configuration: ${details}`);\r\n }\r\n\r\n cachedEnv = parsed.data;\r\n applyResolvedDefaultsToProcessEnv(cachedEnv);\r\n return cachedEnv;\r\n};\r\n\r\nexport const env = bootstrapEnv();\r\nexport const isProduction = env.NODE_ENV === 'production';\r\n","import './env';\r\nimport { PrismaClient } from '@prisma/client';\r\nimport { getPrismaClient, setDefaultPrismaResolver } from './clients';\r\n\r\ndeclare global {\r\n // eslint-disable-next-line no-var\r\n var __luckystackPrisma: PrismaClient | undefined;\r\n}\r\n\r\n//? Lazy default: only constructed when the proxy is first accessed AND no\r\n//? consumer has registered their own Prisma client. Avoids paying the\r\n//? PrismaClient construction cost in code paths that swap it out at boot.\r\nconst buildDefaultPrismaClient = (): PrismaClient => {\r\n globalThis.__luckystackPrisma ??= new PrismaClient();\r\n return globalThis.__luckystackPrisma;\r\n};\r\n\r\nsetDefaultPrismaResolver(buildDefaultPrismaClient);\r\n\r\nif (process.env.NODE_ENV !== 'production') {\r\n // Eager-init in dev so HMR sees a single Prisma instance across reloads.\r\n buildDefaultPrismaClient();\r\n}\r\n\r\n//? Proxy forwards every read/call to whichever client is active *at access\r\n//? time*, so a `registerPrismaClient(...)` performed after this module is\r\n//? imported still wins. The empty-object Proxy target needs an `as` cast\r\n//? because the target is never read directly — every access is intercepted.\r\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Proxy target placeholder\r\nexport const prisma = new Proxy({} as PrismaClient, {\r\n //? Bind methods to the real client so a CAPTURED method keeps its `this`.\r\n //? Without binding, a detached call (`const q = prisma.$queryRaw; q(...)`)\r\n //? runs with `this === undefined` and Prisma's internal `this`-access throws\r\n //? — exactly how the `/readyz` prisma ping silently failed (it captures\r\n //? `$runCommandRaw` / `$queryRaw` into a local before calling, so the ping\r\n //? always threw → `prisma: false` → 503, even with the DB reachable).\r\n //? Mirrors the `redis` proxy. Non-function reads (model delegates like\r\n //? `prisma.user`) pass through untouched, so their methods bind to the\r\n //? delegate as before.\r\n get: (_target, prop, receiver) => {\r\n const real = getPrismaClient() as object;\r\n const value: unknown = Reflect.get(real, prop, receiver);\r\n if (typeof value !== 'function') return value;\r\n const fn = value as (...args: unknown[]) => unknown;\r\n return fn.bind(real);\r\n },\r\n has: (_target, prop) => Reflect.has(getPrismaClient(), prop),\r\n ownKeys: () => Reflect.ownKeys(getPrismaClient()),\r\n getOwnPropertyDescriptor: (_target, prop) =>\r\n Reflect.getOwnPropertyDescriptor(getPrismaClient(), prop),\r\n});\r\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","/* eslint-disable @typescript-eslint/no-misused-promises, @typescript-eslint/require-await */\r\n\r\nimport Redis from 'ioredis';\r\nimport type { Redis as RedisClient } from 'ioredis';\r\nimport { env } from './env';\r\nimport { getRedisClient, setDefaultRedisResolver } from './clients';\r\nimport { getLogger } from './loggerRegistry';\r\nimport { applyStrayKeyPrefix } from './redisKeyFormatter';\r\n\r\nlet cachedDefault: RedisClient | null = null;\r\n\r\n//? Stop reconnecting after this many consecutive failures so a permanently\r\n//? unreachable / misconfigured Redis (bad creds, wrong host) doesn't hammer\r\n//? forever. With the capped backoff below this is ~1 minute of attempts —\r\n//? long enough to ride out a transient blip, short enough to surface a real\r\n//? outage. A process manager / the dev supervisor restarts the process, which\r\n//? re-resolves a corrected `.env`. Raise it for longer outage tolerance.\r\nconst MAX_REDIS_RECONNECT_ATTEMPTS = 50;\r\n\r\nconst buildDefaultRedisClient = (): RedisClient => {\r\n if (cachedDefault) return cachedDefault;\r\n\r\n cachedDefault = new Redis({\r\n host: env.REDIS_HOST,\r\n port: Number.parseInt(env.REDIS_PORT, 10),\r\n ...(process.env.REDIS_USER && { username: process.env.REDIS_USER }),\r\n ...(process.env.REDIS_PASSWORD && { password: process.env.REDIS_PASSWORD }),\r\n\r\n retryStrategy(times) {\r\n if (times > MAX_REDIS_RECONNECT_ATTEMPTS) {\r\n getLogger().error(\r\n `Redis unreachable after ${String(MAX_REDIS_RECONNECT_ATTEMPTS)} reconnect attempts; giving up. Check REDIS_HOST / REDIS_PORT / credentials and that Redis is reachable.`,\r\n );\r\n return null;\r\n }\r\n return Math.min(times * 50, 2000);\r\n },\r\n });\r\n\r\n //? `ready` (not `connect`): `connect` fires on TCP connect BEFORE the AUTH\r\n //? handshake, so logging there reports success even when credentials are\r\n //? wrong. `ready` fires only after AUTH succeeds.\r\n cachedDefault.on('ready', async () => {\r\n getLogger().info('Connected to Redis');\r\n });\r\n\r\n cachedDefault.on('error', (err) => {\r\n getLogger().error('Error connecting to Redis', err);\r\n });\r\n\r\n return cachedDefault;\r\n};\r\n\r\nsetDefaultRedisResolver(buildDefaultRedisClient);\r\n\r\n//? Single source of truth for Redis connection params. Used by\r\n//? `@luckystack/router`'s bootHandshake (which opens its own short-lived\r\n//? connections for cross-env probes) so a config rename touches only this\r\n//? file. Reads env at call time so dotenv timing doesn't capture a stale\r\n//? value.\r\nexport interface RedisConnectionOptions {\r\n host: string;\r\n port: number;\r\n username?: string;\r\n password?: string;\r\n}\r\n\r\nexport const getRedisConnectionOptions = (): RedisConnectionOptions => ({\r\n host: env.REDIS_HOST,\r\n port: Number.parseInt(env.REDIS_PORT, 10),\r\n ...(process.env.REDIS_USER ? { username: process.env.REDIS_USER } : {}),\r\n ...(process.env.REDIS_PASSWORD ? { password: process.env.REDIS_PASSWORD } : {}),\r\n});\r\n\r\n//? Single-key commands whose FIRST argument is the key. The proxy runs that\r\n//? argument through `applyStrayKeyPrefix` as a best-effort namespace net for\r\n//? raw app keys; already-namespaced keys (anything containing `:`, which is\r\n//? every framework key) pass through untouched, so framework behavior is\r\n//? unchanged. Variadic / non-arg0-key commands (`del`, `scan`, `eval`,\r\n//? `multi`, `mget`, ...) are deliberately excluded — their key positions can't\r\n//? be inferred safely; those call sites use `formatKey()` explicitly.\r\nconst STRAY_PREFIX_COMMANDS = new Set<string>([\r\n 'get', 'set', 'setex', 'psetex', 'setnx', 'getset', 'getdel', 'append', 'strlen',\r\n 'expire', 'pexpire', 'expireat', 'pexpireat', 'persist', 'ttl', 'pttl', 'type',\r\n 'incr', 'incrby', 'incrbyfloat', 'decr', 'decrby',\r\n 'sadd', 'srem', 'smembers', 'scard', 'sismember', 'spop', 'srandmember',\r\n 'hget', 'hset', 'hdel', 'hgetall', 'hkeys', 'hvals', 'hexists', 'hincrby', 'hmget', 'hmset',\r\n 'lpush', 'rpush', 'lpop', 'rpop', 'llen', 'lrange', 'lrem',\r\n 'zadd', 'zrem', 'zscore', 'zrange', 'zrank', 'zcard',\r\n 'getbit', 'setbit', 'bitcount',\r\n]);\r\n\r\n//? Proxy: same pattern as `prisma`. Defers resolution until call time so\r\n//? `registerRedisClient(...)` can still win after this module is loaded.\r\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Proxy target placeholder\r\nconst redisProxy = new Proxy({} as RedisClient, {\r\n get: (_target, prop, receiver) => {\r\n const real = getRedisClient() as object;\r\n const value: unknown = Reflect.get(real, prop, receiver);\r\n if (typeof value !== 'function') return value;\r\n const fn = value as (...args: unknown[]) => unknown;\r\n if (typeof prop === 'string' && STRAY_PREFIX_COMMANDS.has(prop.toLowerCase())) {\r\n return (...args: unknown[]): unknown => {\r\n if (args.length > 0 && typeof args[0] === 'string') {\r\n args[0] = applyStrayKeyPrefix(args[0]);\r\n }\r\n return fn.apply(real, args);\r\n };\r\n }\r\n return fn.bind(real);\r\n },\r\n has: (_target, prop) => Reflect.has(getRedisClient(), prop),\r\n});\r\n\r\nexport const redis = redisProxy;\r\nexport default redisProxy;\r\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';\r\nimport { createAdapter } from '@socket.io/redis-adapter';\r\nimport { redis } from './redis';\r\nimport { getLogger } from './loggerRegistry';\r\n\r\n//? Cross-instance Socket.io pub/sub. Without this, room broadcasts only reach\r\n//? clients connected to the same process. The router can route a client to\r\n//? service instance A, but a sync event fired from instance B never reaches\r\n//? them unless both instances share a pub/sub channel.\r\n//?\r\n//? Uses `redis.duplicate()` rather than the main handle because ioredis in\r\n//? subscribe mode blocks non-pub/sub commands on that connection.\r\nexport const attachSocketRedisAdapter = (io: SocketIOServer): void => {\r\n const pubClient = redis.duplicate();\r\n const subClient = redis.duplicate();\r\n\r\n pubClient.on('error', (err) => {\r\n getLogger().error('[socket-redis-adapter] pub client error', err);\r\n });\r\n subClient.on('error', (err) => {\r\n getLogger().error('[socket-redis-adapter] sub client error', err);\r\n });\r\n\r\n io.adapter(createAdapter(pubClient, subClient));\r\n};\r\n","import { randomUUID } from 'node:crypto';\r\nimport { redis } from './redis';\r\nimport { getDeployConfig } from './deployConfigRegistry';\r\n\r\n//? Writes a fresh UUID to `luckystack:boot:<envKey>` on startup. Any router\r\n//? in another env that's truly sharing this Redis can read the same key.\r\n//? Router boot handshake cross-checks against the /_health endpoint to catch\r\n//? the \"two Redis URLs that both respond\" failure mode.\r\n\r\n//? Single source of truth — also consumed by `@luckystack/router`'s\r\n//? `bootHandshake.ts` so the prefix can never drift between writer and reader.\r\nexport const BOOT_KEY_PREFIX = 'luckystack:boot:';\r\nconst DEFAULT_BOOT_KEY_TTL_SECONDS = 3600;\r\n\r\nconst getBootKeyTtlSeconds = (): number =>\r\n getDeployConfig().routing?.bootKeyTtlSeconds ?? DEFAULT_BOOT_KEY_TTL_SECONDS;\r\n\r\nexport const resolveEnvKey = (): string => {\r\n return process.env.LUCKYSTACK_ENV ?? process.env.NODE_ENV ?? 'development';\r\n};\r\n\r\nexport const writeBootUuid = async (envKey?: string): Promise<string> => {\r\n const key = envKey ?? resolveEnvKey();\r\n const uuid = randomUUID();\r\n await redis.set(`${BOOT_KEY_PREFIX}${key}`, uuid, 'EX', getBootKeyTtlSeconds());\r\n return uuid;\r\n};\r\n\r\nexport const readBootUuid = async (envKey?: string): Promise<string | null> => {\r\n const key = envKey ?? resolveEnvKey();\r\n return redis.get(`${BOOT_KEY_PREFIX}${key}`);\r\n};\r\n","import { createHash } from 'node:crypto';\r\nimport { getDeployConfig } from './deployConfigRegistry';\r\n\r\n\r\n//? Cross-env consistency check: when env A declares `fallback: B`, any env\r\n//? vars listed under `synchronizedEnvKeys` on their shared resource must hold\r\n//? identical values on both deployments — otherwise sessions encrypted by\r\n//? one side can't be decrypted by the other.\r\n//?\r\n//? Secrets never leave the deployment in the clear. `/_health` returns SHA-256\r\n//? hashes of each synchronized value; the router compares hashes across envs.\r\n//? If hashes match, values match. If they don't, the deployments are out of\r\n//? sync and the router either warns or refuses to start (per `strictBootHandshake`).\r\n\r\nconst hashValue = (value: string): string => {\r\n return createHash('sha256').update(value).digest('hex');\r\n};\r\n\r\nexport const collectSynchronizedEnvKeys = (): string[] => {\r\n const keys = new Set<string>();\r\n for (const resource of Object.values(getDeployConfig().resources)) {\r\n for (const key of resource.synchronizedEnvKeys ?? []) {\r\n keys.add(key);\r\n }\r\n }\r\n return [...keys].toSorted();\r\n};\r\n\r\nexport const computeSynchronizedEnvHashes = (): Record<string, string | null> => {\r\n const keys = collectSynchronizedEnvKeys();\r\n const out: Record<string, string | null> = {};\r\n for (const key of keys) {\r\n const value = process.env[key];\r\n out[key] = value === undefined ? null : hashValue(value);\r\n }\r\n return out;\r\n};\r\n\r\n//? Router-side helper: hash a single value with the same algorithm the\r\n//? backend's /_health uses. Kept separate from the backend-only collector\r\n//? so the router can import it without loading the core barrel (which\r\n//? opens a Redis connection).\r\nexport const hashSynchronizedValue = (value: string): string => hashValue(value);\r\n\r\n\r\n\r\nexport {resolveEnvKey} from './bootUuid';","const COLORS: Record<string, string> = {\r\n black: \"\\u001B[30m\",\r\n red: \"\\u001B[31m\",\r\n green: \"\\u001B[32m\",\r\n yellow: \"\\u001B[33m\",\r\n blue: \"\\u001B[34m\",\r\n magenta: \"\\u001B[35m\",\r\n cyan: \"\\u001B[36m\",\r\n white: \"\\u001B[37m\",\r\n reset: \"\\u001B[0m\",\r\n};\r\n\r\n//? Strip both POSIX `/` and Windows `\\` separators so the file:line tag is\r\n//? consistent across platforms. Drop the column suffix too — `:42:7` becomes\r\n//? `:42`. If the regex finds no separator at all, fall back to the bare\r\n//? string (avoids leaking the full filesystem path).\r\nconst FILE_NAME_REGEX = /[\\\\/]([^\\\\/]+)$/;\r\n\r\nconst extractFrameLabel = (frame: string): string => {\r\n let lineInfo = frame.slice(frame.indexOf(\"(\") + 1, frame.lastIndexOf(\")\"));\r\n if (lineInfo === \"\") lineInfo = frame;\r\n const match = FILE_NAME_REGEX.exec(lineInfo);\r\n const fileName = match?.[1] ?? lineInfo;\r\n return fileName.replace(/:\\d+(?::\\d+)?$/, \"\");\r\n};\r\n\r\nexport const initConsolelog = () => {\r\n const originalLog = console.log;\r\n console.log = (...args: unknown[]) => {\r\n const stackLines = new Error('console.log trace').stack?.split(\"\\n\") ?? [];\r\n //? Search for the first frame outside this module so wrapper functions\r\n //? (e.g. `getLogger().info` → `console.log`) don't pin the label to the\r\n //? wrapper. Frame 0 is the Error itself; skip it.\r\n const frame = stackLines.slice(1).find((line) => {\r\n const trimmed = line.trim();\r\n return trimmed.length > 0 && !trimmed.includes('consoleLog.ts') && !trimmed.includes('loggerRegistry.ts');\r\n })?.trim();\r\n\r\n if (!frame) { originalLog(...args); return; }\r\n\r\n const extractedInfo = extractFrameLabel(frame);\r\n\r\n // find color keyword and remove it from args\r\n let colorCode = COLORS.white;\r\n for (const key of Object.keys(COLORS)) {\r\n const index = args.indexOf(key);\r\n if (index !== -1) {\r\n colorCode = COLORS[key];\r\n args.splice(index, 1);\r\n break;\r\n }\r\n }\r\n\r\n // handle object vs text\r\n if (typeof args[0] === \"object\") {\r\n originalLog(`${colorCode}${extractedInfo}${COLORS.reset}`);\r\n originalLog(...args);\r\n } else {\r\n originalLog(`${colorCode}${extractedInfo} -- ${args.join(\" \")}${COLORS.reset}`);\r\n }\r\n };\r\n}","//? Registry of log keys that get masked when the framework prints request /\n//? response payloads. Feature packages register their domain-specific\n//? sensitive keys at boot (login → `password`, email → `apiKey`, etc.) so a\n//? consumer of the package never has to remember to extend a static list in\n//? core to keep their own data out of logs.\n//?\n//? Lookups are case-insensitive. Keys are merged with the framework defaults;\n//? `registerRedactedLogKeys` is additive — duplicate calls are idempotent.\n\nconst DEFAULT_REDACTED_LOG_KEYS: readonly string[] = [\n 'password',\n 'confirmpassword',\n 'token',\n 'newtoken',\n 'authorization',\n 'cookie',\n 'set-cookie',\n];\n\nconst redactedKeys = new Set<string>(DEFAULT_REDACTED_LOG_KEYS.map((key) => key.toLowerCase()));\n\nexport const registerRedactedLogKeys = (keys: readonly string[]): void => {\n for (const key of keys) {\n redactedKeys.add(key.toLowerCase());\n }\n};\n\nexport const getRedactedLogKeys = (): readonly string[] => [...redactedKeys];\n\nexport const isRedactedLogKey = (key: string): boolean => {\n return redactedKeys.has(key.toLowerCase());\n};\n\n//? Test-only helper — restore the default seed set between integration tests.\n//? Never call from production code; framework packages register their own\n//? keys at boot and clearing would re-expose them.\nexport const resetRedactedLogKeysForTests = (): void => {\n redactedKeys.clear();\n for (const key of DEFAULT_REDACTED_LOG_KEYS) {\n redactedKeys.add(key.toLowerCase());\n }\n};\n","const escapeRegExp = (value: string): string => {\r\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`);\r\n};\r\n\r\nexport const getCookieValue = (cookieHeader: string | undefined, cookieName: string): string | null => {\r\n if (!cookieHeader || !cookieName) {\r\n return null;\r\n }\r\n\r\n const safeCookieName = escapeRegExp(cookieName);\r\n const cookieRegex = new RegExp(String.raw`(?:^|;\\s*)${safeCookieName}=([^;]*)`);\r\n const match = cookieHeader.match(cookieRegex);\r\n const rawValue = match?.[1];\r\n\r\n if (!rawValue) {\r\n return null;\r\n }\r\n\r\n try {\r\n return decodeURIComponent(rawValue);\r\n } catch {\r\n return rawValue;\r\n }\r\n};\r\n\r\nexport const hasCookie = (cookieHeader: string | undefined, cookieName: string): boolean => {\r\n return getCookieValue(cookieHeader, cookieName) !== null;\r\n};\r\n","/**\r\n * HTTP API Utilities\r\n * \r\n * Helpers for HTTP API request handling including:\r\n * - Smart HTTP method detection based on API name\r\n * - Method validation\r\n */\r\n\r\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';\r\n\r\n/**\r\n * Infer the HTTP method from an API name based on common naming conventions.\r\n * \r\n * - Names starting with \"get\" → GET\r\n * - Names starting with \"delete\" or \"remove\" → DELETE\r\n * - Names starting with \"update\" or \"edit\" → PUT\r\n * - Everything else → POST (create operations, actions)\r\n * \r\n * @example\r\n * ```typescript\r\n * inferHttpMethod('getUserData') // → 'GET'\r\n * inferHttpMethod('deleteAccount') // → 'DELETE'\r\n * inferHttpMethod('updateProfile') // → 'PUT'\r\n * inferHttpMethod('createUser') // → 'POST'\r\n * inferHttpMethod('sendEmail') // → 'POST'\r\n * ```\r\n */\r\nexport const inferHttpMethod = (apiName: string): HttpMethod => {\r\n // Extract the method name from the full path (e.g., \"api/examples/getUserData\" → \"getUserData\")\r\n const nameParts = apiName.split('/');\r\n const lastPart = nameParts.at(-1)?.toLowerCase() ?? '';\r\n const methodName = /^v\\d+$/.test(lastPart)\r\n ? (nameParts.at(-2) ?? '').toLowerCase()\r\n : lastPart;\r\n\r\n if (methodName.startsWith('get') || methodName.startsWith('fetch') || methodName.startsWith('list')) {\r\n return 'GET';\r\n }\r\n\r\n if (methodName.startsWith('delete') || methodName.startsWith('remove')) {\r\n return 'DELETE';\r\n }\r\n\r\n if (methodName.startsWith('update') || methodName.startsWith('edit') || methodName.startsWith('patch')) {\r\n return 'PUT';\r\n }\r\n\r\n // Default: POST for create operations and general actions\r\n return 'POST';\r\n};\r\n\r\n/**\r\n * Get the effective HTTP method for an API.\r\n * Uses explicit export if available, otherwise infers from name.\r\n */\r\nexport const getEffectiveHttpMethod = (\r\n apiConfig: { httpMethod?: HttpMethod },\r\n apiName: string\r\n): HttpMethod => {\r\n return apiConfig.httpMethod ?? inferHttpMethod(apiName);\r\n};\r\n\r\n/**\r\n * Check if an HTTP method is valid for an API.\r\n * \r\n * @param requestMethod - The method from the HTTP request\r\n * @param allowedMethod - The method the API accepts\r\n * @returns true if the method matches or if it's a preflight OPTIONS request\r\n */\r\nexport const isMethodAllowed = (\r\n requestMethod: string,\r\n allowedMethod: HttpMethod\r\n): boolean => {\r\n return requestMethod === allowedMethod || requestMethod === 'OPTIONS';\r\n};\r\n","/* eslint-disable @typescript-eslint/no-unnecessary-condition */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { getProjectConfig } from './projectConfig';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\nconst isWorkspaceRoot = (candidate: string): boolean => {\r\n\treturn (\r\n\t\tfs.existsSync(path.join(candidate, 'package.json'))\r\n\t\t&& (\r\n\t\t\tfs.existsSync(path.join(candidate, 'tsconfig.json'))\r\n\t\t\t|| fs.existsSync(path.join(candidate, 'tsconfig.server.json'))\r\n\t\t)\r\n\t);\r\n};\r\n\r\nconst findWorkspaceRoot = (startDir: string): string | null => {\r\n\tlet current = path.resolve(startDir);\r\n\r\n\twhile (true) {\r\n\t\tif (isWorkspaceRoot(current)) {\r\n\t\t\treturn current;\r\n\t\t}\r\n\r\n\t\tconst parent = path.dirname(current);\r\n\t\tif (parent === current) return null;\r\n\t\tcurrent = parent;\r\n\t}\r\n};\r\n\r\nexport const ROOT_DIR =\r\n\tfindWorkspaceRoot(process.cwd())\r\n\t?? findWorkspaceRoot(__dirname)\r\n\t?? process.cwd();\r\n\r\nconst resolveAgainstRoot = (relative: string): string =>\r\n\tpath.isAbsolute(relative) ? relative : path.join(ROOT_DIR, relative);\r\n\r\n//? Lazy getters: read from the registered ProjectConfig at *call time* so a\r\n//? consumer's `registerProjectConfig({ paths: { srcDir: 'app/src' } })` wins\r\n//? even if it ran after this module was imported.\r\nexport const getSrcDir = (): string => resolveAgainstRoot(getProjectConfig().paths.srcDir);\r\nexport const getServerDir = (): string => resolveAgainstRoot(getProjectConfig().paths.serverDir);\r\nexport const getSharedDir = (): string => resolveAgainstRoot(getProjectConfig().paths.sharedDir);\r\nexport const getUploadsDir = (): string => resolveAgainstRoot(getProjectConfig().paths.uploadsDir);\r\nexport const getPublicDir = (): string => resolveAgainstRoot(getProjectConfig().paths.publicDir);\r\n//? Returns the list of directories the function-injection system walks\r\n//? at codegen + runtime. Default `['functions', 'shared']`. Consumers can\r\n//? override via `registerProjectConfig({ paths: { serverFunctionDirs: [...] } })`.\r\n//?\r\n//? Backwards-compat: if a consumer still sets the deprecated singular\r\n//? `serverFunctionsDir: string`, we honor it as a single-entry list. Once\r\n//? both are set, `serverFunctionDirs` wins.\r\nexport const getServerFunctionDirs = (): string[] => {\r\n\tconst paths = getProjectConfig().paths;\r\n\tconst arrayConfig = paths.serverFunctionDirs;\r\n\tif (Array.isArray(arrayConfig) && arrayConfig.length > 0) {\r\n\t\treturn arrayConfig.map((p) => resolveAgainstRoot(p));\r\n\t}\r\n\t// eslint-disable-next-line @typescript-eslint/no-deprecated -- BC shim: the legacy singular field is still honored when set\r\n\tconst legacySingular = paths.serverFunctionsDir;\r\n\tif (typeof legacySingular === 'string' && legacySingular.length > 0) {\r\n\t\treturn [resolveAgainstRoot(legacySingular)];\r\n\t}\r\n\treturn [];\r\n};\r\n\r\n/**\r\n * @deprecated Use `getServerFunctionDirs()` instead. Returns the first\r\n * directory from the array, or `'functions'` resolved against the workspace\r\n * root when no directories are configured.\r\n */\r\nexport const getServerFunctionsDir = (): string => {\r\n\tconst dirs = getServerFunctionDirs();\r\n\treturn dirs[0] ?? resolveAgainstRoot('functions');\r\n};\r\n\r\nexport const getGeneratedSocketTypesPath = (): string =>\r\n\tresolveAgainstRoot(getProjectConfig().paths.generatedSocketTypes);\r\nexport const getGeneratedApiSchemasPath = (): string =>\r\n\tresolveAgainstRoot(getProjectConfig().paths.generatedApiSchemas);\r\nexport const getGeneratedApiDocsPath = (): string =>\r\n\tresolveAgainstRoot(getProjectConfig().paths.generatedApiDocs);\r\n\r\n//? Backwards-compatible constants resolved against the framework defaults at\r\n//? module load. New code should use the getters above. These will continue to\r\n//? work for projects that don't override `paths` in `registerProjectConfig`.\r\n//? @deprecated use `getSrcDir()` etc. instead.\r\nexport const SRC_DIR = path.join(ROOT_DIR, 'src');\r\n/** @deprecated use `getServerDir()` instead. */\r\nexport const SERVER_DIR = path.join(ROOT_DIR, 'server');\r\n/** @deprecated use `getSharedDir()` instead. */\r\nexport const SHARED_DIR = path.join(ROOT_DIR, 'shared');\r\n\r\n/** @deprecated use `getUploadsDir()` instead. */\r\nexport const UPLOADS_DIR = path.join(ROOT_DIR, 'uploads');\r\n/** @deprecated use `getPublicDir()` instead. */\r\nexport const PUBLIC_DIR = path.join(ROOT_DIR, 'public');\r\n/** @deprecated use `getServerFunctionsDir()` instead. */\r\n// eslint-disable-next-line @typescript-eslint/no-deprecated -- BC export: depends on the deprecated SERVER_DIR\r\nexport const SERVER_FUNCTIONS_DIR = path.join(SERVER_DIR, 'functions');\r\n\r\n/** @deprecated use `getGeneratedSocketTypesPath()` instead. */\r\nexport const GENERATED_SOCKET_TYPES_PATH = path.join(SRC_DIR, '_sockets', 'apiTypes.generated.ts');\r\n/** @deprecated use `getGeneratedApiSchemasPath()` instead. */\r\nexport const GENERATED_API_SCHEMAS_PATH = path.join(SRC_DIR, '_sockets', 'apiInputSchemas.generated.ts');\r\n/** @deprecated use `getGeneratedApiDocsPath()` instead. */\r\nexport const GENERATED_API_DOCS_PATH = path.join(SRC_DIR, 'docs', 'apiDocs.generated.json');\r\n\r\nexport const TSCONFIG_ALIAS_FILES = ['tsconfig.server.json', 'tsconfig.client.json'];\r\n\r\nexport const resolveFromRoot = (...segments: string[]): string => path.join(ROOT_DIR, ...segments);\r\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","//? CSRF configuration registry. The framework's CSRF middleware\r\n//? (`packages/server/src/httpRoutes/csrfMiddleware.ts`) and the\r\n//? client-side fetch wrapper (`packages/core/src/csrf.ts`) both consult\r\n//? `getCsrfConfig()` so consumers can change the header name, cookie\r\n//? name, token length, or cookie options without forking.\r\n//?\r\n//? The default values match the framework's pre-registry hardcodes\r\n//? (header `x-csrf-token`, 32-byte hex token minted at session create) so\r\n//? consumers who never call `registerCsrfConfig` get the historical\r\n//? behaviour unchanged.\r\n\r\nexport interface CsrfCookieOptions {\r\n sameSite?: 'strict' | 'lax' | 'none';\r\n secure?: boolean;\r\n /**\r\n * MUST be false for CSRF cookies — the client needs to read the value\r\n * with JS to attach it as a request header. The framework reads the\r\n * value from the session record (Redis-side) rather than the cookie,\r\n * so this flag is documentation/intent only.\r\n */\r\n httpOnly?: boolean;\r\n path?: string;\r\n maxAgeMs?: number;\r\n}\r\n\r\nexport interface CsrfConfig {\r\n /** Cookie name used to deliver the CSRF token to the browser. */\r\n cookieName: string;\r\n /** Request-header name the client attaches on state-changing requests. */\r\n headerName: string;\r\n /** Length in bytes of the random token minted by the session layer. */\r\n tokenLength: number;\r\n cookieOptions: CsrfCookieOptions;\r\n}\r\n\r\nexport const DEFAULT_CSRF_CONFIG: CsrfConfig = {\r\n cookieName: 'csrf-token',\r\n headerName: 'x-csrf-token',\r\n tokenLength: 32,\r\n cookieOptions: {\r\n sameSite: 'lax',\r\n secure: true,\r\n httpOnly: false,\r\n path: '/',\r\n maxAgeMs: 24 * 60 * 60 * 1000,\r\n },\r\n};\r\n\r\nlet active: CsrfConfig = DEFAULT_CSRF_CONFIG;\r\n\r\n/**\r\n * Override one or more CSRF settings. Merges shallowly with the current\r\n * config; `cookieOptions` is deep-merged so a partial override of (say)\r\n * `sameSite` does not clobber `path` / `maxAgeMs`. Last-write-wins.\r\n */\r\nexport const registerCsrfConfig = (input: Partial<CsrfConfig>): void => {\r\n active = {\r\n ...active,\r\n ...input,\r\n cookieOptions: { ...active.cookieOptions, ...input.cookieOptions },\r\n };\r\n};\r\n\r\n/** Read the active config at call time (never at module load). */\r\nexport const getCsrfConfig = (): CsrfConfig => active;\r\n\r\n/** Test-only — restore defaults between scenarios. */\r\nexport const resetCsrfConfigForTests = (): void => {\r\n active = DEFAULT_CSRF_CONFIG;\r\n};\r\n","import path from \"node:path\";\r\nimport { access } from 'node:fs/promises';\r\nimport fs from \"node:fs\";\r\nimport { ServerResponse } from \"node:http\";\r\nimport { getUploadsDir } from './paths';\r\nimport { getAvatarConfig } from './avatarConfig';\r\nimport { getLogger } from './loggerRegistry';\r\nimport tryCatch from './tryCatch';\r\n\r\n//? Belt-and-suspenders: `path.basename` already strips traversal segments,\r\n//? but explicitly allowlist the fileId character set so any future regression\r\n//? in the upstream routing (e.g. URL-decoded embedded null) can't slip\r\n//? through to the filesystem layer.\r\nconst FILE_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;\r\n\r\nexport const serveAvatar = async ({\r\n routePath,\r\n res,\r\n}: {\r\n routePath: string;\r\n res: ServerResponse;\r\n}) => {\r\n const uploadsFolder = getUploadsDir();\r\n const fileId = path.basename(routePath, path.extname(routePath));\r\n if (!fileId || !FILE_ID_REGEX.test(fileId)) {\r\n res.writeHead(404, { 'Content-Type': 'text/plain' });\r\n res.end('File not found');\r\n return;\r\n }\r\n\r\n const { formats, cacheControl } = getAvatarConfig();\r\n\r\n //? Try each configured format in order; first existing file wins. This\r\n //? lets an installer add `png` or `jpg` for backwards compatibility while\r\n //? still preferring `webp` for new uploads.\r\n for (const { extension, contentType } of formats) {\r\n const filePath = path.join(uploadsFolder, `${fileId}.${extension}`);\r\n const [accessError] = await tryCatch(() => access(filePath));\r\n if (accessError) continue;\r\n\r\n res.writeHead(200, {\r\n 'Content-Type': contentType,\r\n 'Cache-Control': cacheControl,\r\n });\r\n fs.createReadStream(filePath).pipe(res);\r\n return;\r\n }\r\n\r\n getLogger().debug('avatar: file not found', { routePath, fileId });\r\n res.writeHead(404, { 'Content-Type': 'text/plain' });\r\n res.end('File not found');\r\n};\r\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\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\ntype DeepPartial<T> = {\n [K in keyof T]?: T[K] extends object | undefined ? DeepPartial<NonNullable<T[K]>> : T[K];\n};\n\nexport type AvatarConfigInput = DeepPartial<AvatarConfig>;\n\nlet activeConfig: AvatarConfig = DEFAULT_AVATAR_CONFIG;\n\nexport const registerAvatarConfig = (config: AvatarConfigInput): void => {\n activeConfig = {\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 getAvatarConfig = (): AvatarConfig => activeConfig;\n","//? Framework-side upload helper. Wraps the `onUploadStart` / `onUploadComplete`\r\n//? hook dispatch around an installer-provided encode/save callback so consumer\r\n//? upload routes don't have to plumb the hooks themselves.\r\n//?\r\n//? Why a callback (not a built-in encoder): @luckystack/core stays free of\r\n//? heavy native deps (sharp, ffmpeg, etc.). Consumers wire whichever encoder\r\n//? fits their use case and the framework just brackets the call with the\r\n//? hook contract.\r\n\r\nimport { dispatchHook } from './hooks/registry';\r\nimport tryCatch from './tryCatch';\r\n\r\nexport interface ProcessUploadInput {\r\n /** User the upload is associated with. */\r\n userId: string;\r\n /** Content type from the source (e.g. parsed from a data URL prefix). */\r\n contentType: string;\r\n /** Raw upload bytes — used for `sizeBytes` and passed to the encoder. */\r\n buffer: Buffer;\r\n /** Identifier for the upload category. Defaults to `'avatar'`. */\r\n uploadKind?: string;\r\n /** Final file name written to disk (used in the `onUploadComplete` payload). */\r\n fileName: string;\r\n /**\r\n * Project-supplied encode + write step. Receives the raw buffer, does\r\n * whatever encoding/resizing the installer wants (sharp, ffmpeg, raw fs\r\n * write, S3 upload, etc.), and returns the final byte size on disk —\r\n * pass through `buffer.byteLength` if you don't have a meaningful\r\n * post-encode size.\r\n */\r\n encodeAndSave: (buffer: Buffer) => Promise<number>;\r\n}\r\n\r\nexport type ProcessUploadResult =\r\n | { status: 'success'; sizeBytes: number }\r\n | { status: 'rejected'; errorCode: string }\r\n | { status: 'error'; reason: string; cause?: unknown };\r\n\r\n/**\r\n * Run a project-side upload through the framework's hook contract.\r\n *\r\n * Order of operations:\r\n * 1. Dispatch `onUploadStart` — handlers may return a stop signal to reject\r\n * the upload (content moderation, virus scanner, quota check).\r\n * 2. Run the project-supplied `encodeAndSave` callback (wrapped in tryCatch).\r\n * 3. Dispatch `onUploadComplete` with the final `sizeBytes`.\r\n *\r\n * Returns one of three statuses so the caller can map to its own response\r\n * envelope without inspecting hook internals.\r\n */\r\nexport const processUpload = async (input: ProcessUploadInput): Promise<ProcessUploadResult> => {\r\n const uploadKind = input.uploadKind ?? 'avatar';\r\n\r\n const startSignal = await dispatchHook('onUploadStart', {\r\n userId: input.userId,\r\n contentType: input.contentType,\r\n sizeBytes: input.buffer.byteLength,\r\n uploadKind,\r\n });\r\n if (startSignal.stopped) {\r\n return { status: 'rejected', errorCode: startSignal.signal.errorCode };\r\n }\r\n\r\n const [error, finalSize] = await tryCatch(() => input.encodeAndSave(input.buffer));\r\n if (error) {\r\n return { status: 'error', reason: error.message || 'encode-failed', cause: error };\r\n }\r\n\r\n const sizeBytes = finalSize ?? input.buffer.byteLength;\r\n await dispatchHook('onUploadComplete', {\r\n userId: input.userId,\r\n fileName: input.fileName,\r\n sizeBytes,\r\n uploadKind,\r\n });\r\n\r\n return { status: 'success', sizeBytes };\r\n};\r\n","//? Server bind-address registry. The `createLuckyStackServer` bootstrap\r\n//? populates this with the actual listen `ip`/`port` so framework code that\r\n//? needs the bind address (e.g. `checkOrigin` building the same-origin\r\n//? entry) doesn't drift from `SERVER_IP`/`SERVER_PORT` env vars when the\r\n//? consumer used the `options.ip`/`options.port` arguments instead.\r\n//?\r\n//? Resolution at call time:\r\n//? 1. `registerBindAddress(...)` value (if the server has booted)\r\n//? 2. `process.env.SERVER_IP` / `process.env.SERVER_PORT` (legacy)\r\n//? 3. `'127.0.0.1'` / `''` as the absolute fallback\r\n\r\ninterface BindAddress {\r\n ip: string;\r\n port: number;\r\n}\r\n\r\nlet registered: BindAddress | null = null;\r\n\r\nexport const registerBindAddress = (address: BindAddress): void => {\r\n registered = address;\r\n};\r\n\r\nexport const getBindAddress = (): { ip: string; port: string } => {\r\n if (registered) {\r\n return { ip: registered.ip, port: String(registered.port) };\r\n }\r\n return {\r\n ip: process.env.SERVER_IP ?? '127.0.0.1',\r\n port: process.env.SERVER_PORT ?? '',\r\n };\r\n};\r\n","/* eslint-disable unicorn/no-abusive-eslint-disable */\r\n/* eslint-disable */\r\nimport { IncomingMessage, ServerResponse } from \"node:http\";\r\nimport tryCatch from \"./tryCatch\";\r\nimport { getProjectConfig } from \"./projectConfig\";\r\n\r\ninterface getParamsType {\r\n method: string;\r\n req: IncomingMessage;\r\n res: ServerResponse;\r\n queryString?: string;\r\n}\r\n\r\nexport default async function getParams({ method, req, res: _res, queryString }: getParamsType): Promise<Record<string, unknown> | null> {\r\n\r\n if (method == \"GET\") {\r\n //? if get request we return the query string as an object\r\n return Object.fromEntries(new URLSearchParams(queryString || '')) as Record<string, string>;\r\n }\r\n\r\n //? if a POST, PUT or DELETE method we return the body as an object\r\n return new Promise((resolve, reject) => {\r\n const contentType = req.headers['content-type'] || '';\r\n const contentLengthHeader = req.headers['content-length'];\r\n const declaredLength = typeof contentLengthHeader === 'string' ? Number(contentLengthHeader) : Number.NaN;\r\n const maxBodyBytes = getProjectConfig().http.requestBodyMaxBytes;\r\n\r\n if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {\r\n _res.setHeader('Content-Type', 'application/json');\r\n _res.writeHead(413);\r\n _res.end(JSON.stringify({\r\n status: 'error',\r\n httpStatus: 413,\r\n message: 'api.payloadTooLarge',\r\n errorCode: 'api.payloadTooLarge',\r\n }));\r\n resolve(null); return;\r\n }\r\n\r\n //? we store the passed data chunks in a string\r\n let body = '';\r\n let bodySize = 0;\r\n req.on('data', (chunk) => {\r\n bodySize += chunk.length;\r\n if (bodySize > maxBodyBytes) {\r\n _res.setHeader('Content-Type', 'application/json');\r\n _res.writeHead(413);\r\n _res.end(JSON.stringify({\r\n status: 'error',\r\n httpStatus: 413,\r\n message: 'api.payloadTooLarge',\r\n errorCode: 'api.payloadTooLarge',\r\n }));\r\n req.destroy();\r\n resolve(null); return;\r\n }\r\n\r\n body += chunk.toString();\r\n });\r\n\r\n req.on('end', async () => {\r\n //? here we parse the data depending on the content type\r\n //? if the content type is application/x-www-form-urlencoded we parse the data as a URLSearchParams object\r\n if (contentType.startsWith('application/x-www-form-urlencoded')) {\r\n const parseData = () => {\r\n const data = new URLSearchParams(body);\r\n return Object.fromEntries(data);\r\n }\r\n const [, response] = await tryCatch(parseData)\r\n if (response) {\r\n resolve(response); return;\r\n }\r\n\r\n _res.setHeader('Content-Type', 'application/json');\r\n _res.writeHead(400);\r\n _res.end(JSON.stringify({\r\n status: 'error',\r\n httpStatus: 400,\r\n message: 'api.invalidRequestFormat',\r\n errorCode: 'api.invalidRequestFormat',\r\n }));\r\n resolve(null); return;\r\n }\r\n\r\n //? if the content type is application/json we parse the data as a JSON object\r\n if (contentType.startsWith('application/json')) {\r\n const parseData = () => {\r\n return JSON.parse(body || '{}');\r\n }\r\n const [, response] = await tryCatch(parseData)\r\n\r\n //? Reject array/scalar/null bodies — only plain objects are accepted.\r\n //? An attacker sending `[1,2,3]` or `42` could surface odd shapes in\r\n //? handlers that didn't expect them.\r\n if (response && typeof response === 'object' && !Array.isArray(response)) {\r\n resolve(response as Record<string, unknown>); return;\r\n }\r\n\r\n _res.setHeader('Content-Type', 'application/json');\r\n _res.writeHead(400);\r\n _res.end(JSON.stringify({\r\n status: 'error',\r\n httpStatus: 400,\r\n message: 'api.invalidRequestFormat',\r\n errorCode: 'api.invalidRequestFormat',\r\n }));\r\n resolve(null); return;\r\n }\r\n\r\n //? Unknown content-type: don't attempt to interpret. Empty content-type\r\n //? bodies in particular were previously parsed as `{ body }` which let\r\n //? handlers mistake raw text for a typed payload.\r\n _res.setHeader('Content-Type', 'application/json');\r\n _res.writeHead(415);\r\n _res.end(JSON.stringify({\r\n status: 'error',\r\n httpStatus: 415,\r\n message: 'api.unsupportedMediaType',\r\n errorCode: 'api.unsupportedMediaType',\r\n }));\r\n resolve(null);\r\n })\r\n\r\n req.on('error', (error) => {\r\n reject(error);\r\n })\r\n // }\r\n\r\n });\r\n}\r\n","/* eslint-disable unicorn/no-abusive-eslint-disable */\r\n/* eslint-disable */\r\nimport { Socket } from 'socket.io';\r\nimport { getProjectConfig } from './projectConfig';\r\nimport { getCookieValue } from './cookies';\r\n\r\n/**\r\n * Extract the authentication token from a Socket.io connection.\r\n *\r\n * Checks `config.sessionBasedToken` to determine where to look:\r\n * - `true`: Token is in `socket.handshake.auth.token` (sessionStorage on client)\r\n * - `false`: Token is in cookies via `socket.handshake.headers.cookie`\r\n *\r\n * @param socket - The Socket.io socket instance\r\n * @returns The token string or null if not found\r\n */\r\n//? `Pick<Socket, 'handshake'>` (not `Socket`) so this also accepts a\r\n//? `RemoteSocket` from `io.fetchSockets()` — the cross-instance fan-out path\r\n//? only has RemoteSockets, which expose `handshake` but not the full Socket API.\r\nexport const extractTokenFromSocket = (socket: Pick<Socket, 'handshake'>): string | null => {\r\n const cookie = socket.handshake.headers.cookie;\r\n const sessionToken = typeof socket.handshake.auth?.token === 'string'\r\n ? socket.handshake.auth.token\r\n : null;\r\n const cookieToken = getCookieValue(cookie, getProjectConfig().http.sessionCookieName);\r\n\r\n // Session-based token (stored in sessionStorage on client)\r\n if (getProjectConfig().session.basedToken) {\r\n return sessionToken ?? cookieToken;\r\n }\r\n\r\n // Cookie-based token\r\n return cookieToken ?? sessionToken;\r\n};\r\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 const authHeader = req.headers.authorization;\n const bearerToken = authHeader?.startsWith('Bearer ')\n ? authHeader.slice(7)\n : null;\n\n const cookieToken = getCookieValue(req.headers.cookie, getProjectConfig().http.sessionCookieName);\n\n // Prefer the configured mode, but fall back to the other transport.\n if (getProjectConfig().session.basedToken) {\n return bearerToken ?? cookieToken;\n }\n\n return cookieToken ?? bearerToken;\n};\n","/**\r\n * Shared validation utilities for API and Sync request handlers.\r\n * Extracted to avoid code duplication between handleApiRequest and handleSyncRequest.\r\n */\r\n\r\nimport type { AuthProps, BaseSessionLayout } from './sessionTypes';\r\n\r\n/**\r\n * Check if a value is falsy (false, 0, 0n, '', null, undefined, or NaN)\r\n */\r\nexport const isFalsy = (value: unknown): boolean => {\r\n return (\r\n value === false ||\r\n value === 0 ||\r\n value === 0n ||\r\n value === '' ||\r\n value === null ||\r\n value === undefined ||\r\n (typeof value === 'number' && Number.isNaN(value))\r\n );\r\n};\r\n\r\nexport interface ValidationResult {\r\n status: 'success' | 'error';\r\n errorCode?: string;\r\n errorParams?: { key: string; value: string | number | boolean; }[];\r\n httpStatus?: number;\r\n}\r\n\r\n/**\r\n * Validate a request against authentication requirements.\r\n *\r\n * Multiple constraints on a single `additional` entry are AND'd: every\r\n * specified field (`nullish`, `type`, `value`, `mustBeFalsy`) must pass\r\n * for that entry to validate. Each entry across the array is also AND'd:\r\n * the request must pass every entry to succeed.\r\n *\r\n * Constraint semantics:\r\n * - `nullish: true` — `val` must be `null` or `undefined`.\r\n * - `nullish: false` — `val` must NOT be `null` or `undefined`.\r\n * - `type: 'string'` — `typeof val === 'string'` (skipped when val is null/undefined).\r\n * - `value: <x>` — strict equality `val === x`. Omit the field entirely if you\r\n * don't want this check; setting `value: undefined`\r\n * explicitly means \"val must be undefined\".\r\n * - `mustBeFalsy: true` — `isFalsy(val)`.\r\n * - `mustBeFalsy: false` — `!isFalsy(val)`.\r\n */\r\nexport const validateRequest = ({\r\n auth,\r\n user\r\n}: {\r\n auth: AuthProps;\r\n user: BaseSessionLayout;\r\n}): ValidationResult => {\r\n\r\n if (!auth.additional) {\r\n return { status: 'success' };\r\n }\r\n\r\n const forbid = (key: string): ValidationResult => ({\r\n status: 'error',\r\n errorCode: 'auth.forbidden',\r\n errorParams: [{ key: 'key', value: key }],\r\n httpStatus: 403,\r\n });\r\n\r\n for (const condition of auth.additional) {\r\n // Key must exist in user session — that's a setup error, not a runtime auth fail.\r\n if (!(condition.key in user)) {\r\n return {\r\n status: 'error',\r\n errorCode: 'auth.invalidCondition',\r\n errorParams: [{ key: 'key', value: condition.key }],\r\n httpStatus: 500,\r\n };\r\n }\r\n\r\n const val = user[condition.key];\r\n const isNullish = val === null || val === undefined;\r\n\r\n if (typeof condition.nullish === 'boolean' && condition.nullish !== isNullish) {\r\n return forbid(condition.key);\r\n }\r\n\r\n if (condition.type && !isNullish && typeof val !== condition.type) {\r\n return forbid(condition.key);\r\n }\r\n\r\n //? Use `'value' in condition` so an explicit `value: undefined` still\r\n //? participates (caller saying \"val must be undefined\"), distinct from\r\n //? the omitted case (no exact-value constraint).\r\n if ('value' in condition && val !== condition.value) {\r\n return forbid(condition.key);\r\n }\r\n\r\n if (typeof condition.mustBeFalsy === 'boolean' && condition.mustBeFalsy !== isFalsy(val)) {\r\n return forbid(condition.key);\r\n }\r\n }\r\n\r\n return { status: 'success' };\r\n};\r\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 tryCatch from './tryCatch';\nimport { redis } from './redis';\nimport { formatKey } from './redisKeyFormatter';\nimport { getLogger } from './loggerRegistry';\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\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\nlet redisFallbackLogged = false;\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): number => {\n if (!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 windowMs = 60_000,\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 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 windowMs = 60_000,\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 logRedisFallback = (): void => {\n if (redisFallbackLogged) return;\n redisFallbackLogged = true;\n getLogger().warn('[RateLimiter] Redis mode unavailable, falling back to in-memory mode');\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 }\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 }\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\nlet activeStrategy: RateLimitStrategy = defaultRateLimitStrategy;\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 activeStrategy = strategy;\n getLogger().debug(`[RateLimiter] active strategy → ${strategy.name}`);\n};\n\n/** Read the currently-active strategy (defaults to the built-in). */\nexport const getRateLimitStrategy = (): RateLimitStrategy => activeStrategy;\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 activeStrategy.check(params);\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 activeStrategy.getStatus(key, limit);\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> => activeStrategy.clear(key);\n\n/**\n * Clear all rate limits.\n * Useful for testing or server restart.\n */\nexport const clearAllRateLimits = async (): Promise<void> => activeStrategy.clearAll();\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`).\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\nscheduleCleanup();\n","type ValidationResult =\r\n | { status: 'success' }\r\n | { status: 'error'; message: string };\r\n\r\n interface ParsedObjectField {\r\n key: string;\r\n optional: boolean;\r\n type: string;\r\n}\r\n\r\ninterface ParsedObjectIndexSignature {\r\n keyName: string;\r\n keyType: string;\r\n type: string;\r\n}\r\n\r\nconst splitTopLevel = (value: string, splitter: '|' | '&'): string[] => {\r\n const items: string[] = [];\r\n let depthParen = 0;\r\n let depthBrace = 0;\r\n let depthBracket = 0;\r\n let token = '';\r\n\r\n for (const char of value) {\r\n if (char === '(') depthParen += 1;\r\n if (char === ')') depthParen -= 1;\r\n if (char === '{') depthBrace += 1;\r\n if (char === '}') depthBrace -= 1;\r\n if (char === '[') depthBracket += 1;\r\n if (char === ']') depthBracket -= 1;\r\n\r\n if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {\r\n items.push(token.trim());\r\n token = '';\r\n continue;\r\n }\r\n\r\n token += char;\r\n }\r\n\r\n if (token.trim()) items.push(token.trim());\r\n return items;\r\n};\r\n\r\nconst parseObjectFields = (typeText: string): {\r\n fields: ParsedObjectField[];\r\n indexSignatures: ParsedObjectIndexSignature[];\r\n} => {\r\n const clean = typeText.trim();\r\n if (!clean.startsWith('{') || !clean.endsWith('}')) {\r\n return { fields: [], indexSignatures: [] };\r\n }\r\n\r\n const inner = clean.slice(1, -1);\r\n const fields: ParsedObjectField[] = [];\r\n const indexSignatures: ParsedObjectIndexSignature[] = [];\r\n\r\n let part = '';\r\n let depth = 0;\r\n for (const char of inner) {\r\n if (char === '{' || char === '[' || char === '(' || char === '<') depth += 1;\r\n if (char === '}' || char === ']' || char === ')' || char === '>') depth -= 1;\r\n\r\n if (char === ';' && depth === 0) {\r\n const trimmed = part.trim();\r\n if (trimmed) {\r\n const fieldMatch = /^(\"|')?[A-Za-z_][A-Za-z0-9_]*(\"|')?(\\?)?\\s*:\\s*([\\s\\S]+)$/.exec(trimmed);\r\n if (fieldMatch) {\r\n const keyMatch = /^(\"|')?[A-Za-z_][A-Za-z0-9_]*(\"|')?/.exec(trimmed);\r\n const rawKey = keyMatch?.[0] ?? '';\r\n fields.push({\r\n key: rawKey.replaceAll(/^['\"]|['\"]$/g, ''),\r\n optional: Boolean(fieldMatch[3]),\r\n type: (fieldMatch[4] ?? '').trim(),\r\n });\r\n } else {\r\n const indexMatch = /^\\[\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*([^\\]]+)\\]\\s*:\\s*([\\s\\S]+)$/.exec(trimmed);\r\n if (indexMatch) {\r\n indexSignatures.push({\r\n keyName: (indexMatch[1] ?? '').trim(),\r\n keyType: (indexMatch[2] ?? '').trim(),\r\n type: (indexMatch[3] ?? '').trim(),\r\n });\r\n }\r\n }\r\n }\r\n part = '';\r\n continue;\r\n }\r\n\r\n part += char;\r\n }\r\n\r\n const final = part.trim();\r\n if (final) {\r\n const fieldMatch = /^(\"|')?[A-Za-z_][A-Za-z0-9_]*(\"|')?(\\?)?\\s*:\\s*([\\s\\S]+)$/.exec(final);\r\n if (fieldMatch) {\r\n const keyMatch = /^(\"|')?[A-Za-z_][A-Za-z0-9_]*(\"|')?/.exec(final);\r\n const rawKey = keyMatch?.[0] ?? '';\r\n fields.push({\r\n key: rawKey.replaceAll(/^['\"]|['\"]$/g, ''),\r\n optional: Boolean(fieldMatch[3]),\r\n type: (fieldMatch[4] ?? '').trim(),\r\n });\r\n } else {\r\n const indexMatch = /^\\[\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*([^\\]]+)\\]\\s*:\\s*([\\s\\S]+)$/.exec(final);\r\n if (indexMatch) {\r\n indexSignatures.push({\r\n keyName: (indexMatch[1] ?? '').trim(),\r\n keyType: (indexMatch[2] ?? '').trim(),\r\n type: (indexMatch[3] ?? '').trim(),\r\n });\r\n }\r\n }\r\n }\r\n\r\n return { fields, indexSignatures };\r\n};\r\n\r\nconst matchesIndexKeyType = ({ keyType, key }: { keyType: string; key: string }): boolean => {\r\n const normalized = keyType.trim();\r\n if (normalized === 'string') return true;\r\n if (normalized === 'number') return /^-?\\d+(\\.\\d+)?$/.test(key);\r\n if (normalized.includes('|')) {\r\n const parts = splitTopLevel(normalized, '|').map((part) => part.trim());\r\n return parts.some((part) => matchesIndexKeyType({ keyType: part, key }));\r\n }\r\n if ((normalized.startsWith(\"'\") && normalized.endsWith(\"'\")) || (normalized.startsWith('\"') && normalized.endsWith('\"'))) {\r\n return key === normalized.slice(1, -1);\r\n }\r\n return false;\r\n};\r\n\r\nconst isPrimitiveMatch = (type: string, value: unknown): boolean => {\r\n if (type === 'string') return typeof value === 'string';\r\n if (type === 'number') return typeof value === 'number' && Number.isFinite(value);\r\n if (type === 'boolean') return typeof value === 'boolean';\r\n if (type === 'true') return value === true;\r\n if (type === 'false') return value === false;\r\n if (type === 'null') return value === null;\r\n if (type === 'undefined') return value === undefined;\r\n if (type === 'Date') return typeof value === 'string' || value instanceof Date;\r\n return false;\r\n};\r\n\r\nconst isPrimitiveType = (type: string): boolean => {\r\n return ['string', 'number', 'boolean', 'true', 'false', 'null', 'undefined', 'Date'].includes(type);\r\n};\r\n\r\nconst validateType = (typeText: string, value: unknown, path: string): ValidationResult => {\r\n const type = typeText.trim();\r\n\r\n if (type.startsWith('__RUNTIME_UNRESOLVED__::')) {\r\n const unresolvedMessage = type.replace('__RUNTIME_UNRESOLVED__::', '').trim();\r\n return { status: 'error', message: `${path}: ${unresolvedMessage}` };\r\n }\r\n\r\n if (type.startsWith('(') && type.endsWith(')')) {\r\n return validateType(type.slice(1, -1), value, path);\r\n }\r\n\r\n if (type.includes('|')) {\r\n const unionParts = splitTopLevel(type, '|').filter(Boolean);\r\n if (unionParts.length > 1) {\r\n for (const unionType of unionParts) {\r\n const result = validateType(unionType, value, path);\r\n if (result.status === 'success') return result;\r\n }\r\n return { status: 'error', message: `${path} does not match union type ${type}` };\r\n }\r\n }\r\n\r\n if (type.includes('&')) {\r\n const intersectionParts = splitTopLevel(type, '&').filter(Boolean);\r\n if (intersectionParts.length > 1) {\r\n for (const intersectionType of intersectionParts) {\r\n const result = validateType(intersectionType, value, path);\r\n if (result.status === 'error') return result;\r\n }\r\n return { status: 'success' };\r\n }\r\n }\r\n\r\n if (type.endsWith('[]')) {\r\n if (!Array.isArray(value)) {\r\n return { status: 'error', message: `${path} should be an array` };\r\n }\r\n const itemType = type.slice(0, -2).trim();\r\n if (itemType === type) {\r\n return { status: 'success' };\r\n }\r\n for (const [index, element] of value.entries()) {\r\n const result = validateType(itemType, element, `${path}[${String(index)}]`);\r\n if (result.status === 'error') return result;\r\n }\r\n return { status: 'success' };\r\n }\r\n\r\n if ((type.startsWith(\"'\") && type.endsWith(\"'\")) || (type.startsWith('\"') && type.endsWith('\"'))) {\r\n const literal = type.slice(1, -1);\r\n return value === literal\r\n ? { status: 'success' }\r\n : { status: 'error', message: `${path} should equal ${literal}` };\r\n }\r\n\r\n if (isPrimitiveMatch(type, value)) {\r\n return { status: 'success' };\r\n }\r\n\r\n if (isPrimitiveType(type)) {\r\n const expectedType = type === 'Date' ? 'Date (ISO string or Date)' : type;\r\n return { status: 'error', message: `${path} should be ${expectedType}` };\r\n }\r\n\r\n if (type === 'any' || type === 'unknown') {\r\n return { status: 'success' };\r\n }\r\n\r\n if (/^Record<.+>$/.test(type)) {\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n return { status: 'success' };\r\n }\r\n return { status: 'error', message: `${path} should be an object` };\r\n }\r\n\r\n if (type.startsWith('{') && type.endsWith('}')) {\r\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\r\n return { status: 'error', message: `${path} should be an object` };\r\n }\r\n\r\n const { fields, indexSignatures } = parseObjectFields(type);\r\n const input = value as Record<string, unknown>;\r\n\r\n for (const field of fields) {\r\n const fieldValue = input[field.key];\r\n if (fieldValue === undefined) {\r\n if (field.optional) continue;\r\n return { status: 'error', message: `${path}.${field.key} is required` };\r\n }\r\n\r\n const result = validateType(field.type, fieldValue, `${path}.${field.key}`);\r\n if (result.status === 'error') return result;\r\n }\r\n\r\n const allowedKeys = new Set(fields.map((field) => field.key));\r\n for (const key of Object.keys(input)) {\r\n if (allowedKeys.has(key)) {\r\n continue;\r\n }\r\n\r\n if (indexSignatures.length === 0) {\r\n return { status: 'error', message: `${path}.${key} is not allowed` };\r\n }\r\n\r\n const indexValue = input[key];\r\n let matched = false;\r\n for (const indexSignature of indexSignatures) {\r\n if (!matchesIndexKeyType({ keyType: indexSignature.keyType, key })) {\r\n continue;\r\n }\r\n\r\n const indexResult = validateType(indexSignature.type, indexValue, `${path}.${key}`);\r\n if (indexResult.status === 'success') {\r\n matched = true;\r\n break;\r\n }\r\n }\r\n\r\n if (!matched) {\r\n return { status: 'error', message: `${path}.${key} is not allowed` };\r\n }\r\n }\r\n\r\n return { status: 'success' };\r\n }\r\n\r\n if (/^[A-Za-z_][A-Za-z0-9_]*(?:<.+>)?$/.test(type)) {\r\n if (/^[A-Za-z_][A-Za-z0-9_]*<.+>$/.test(type)) {\r\n return { status: 'error', message: `${path}: unresolved utility ${type}` };\r\n }\r\n\r\n return { status: 'error', message: `${path}: unresolved type ${type}` };\r\n }\r\n\r\n return { status: 'success' };\r\n};\r\n\r\nexport const validateInputByType = async ({\r\n typeText,\r\n value,\r\n rootKey,\r\n filePath,\r\n}: {\r\n typeText?: string;\r\n value: unknown;\r\n rootKey: string;\r\n filePath?: string;\r\n}): Promise<ValidationResult> => {\r\n if (!typeText || typeText.trim() === '' || typeText.trim() === 'any') {\r\n return { status: 'success' };\r\n }\r\n\r\n // Runtime type expansion relies on TypeScript internals and is intended for development.\r\n // In production we skip this expensive validation to avoid loading dev-only compiler code.\r\n if (process.env.NODE_ENV === 'production') {\r\n return { status: 'success' };\r\n }\r\n\r\n // Dev-only: the resolver uses TypeScript's compiler API for deep type\r\n // expansion. `@luckystack/devkit` is marked external in the prod esbuild\r\n // bundle (see scripts/bundleServer.mjs), so this branch compiles in prod but\r\n // cannot run (import would fail). That is fine — the `NODE_ENV !== 'production'`\r\n // guard above means the import is never reached in prod.\r\n //\r\n // Indirect module ID (string variable, not literal) so tsc doesn't try to\r\n // type-resolve devkit at build time. Devkit depends on core, so a literal\r\n // type-resolved import would be a build-time circular dep.\r\n const devkitModuleId = '@luckystack/devkit';\r\n const devkit = (await import(devkitModuleId)) as DevkitTypeResolverModule;\r\n const resolvedType = devkit.resolveRuntimeTypeText({ typeText, filePath });\r\n if (resolvedType.status === 'error') {\r\n return { status: 'error', message: `${rootKey}: ${resolvedType.message}` };\r\n }\r\n\r\n return validateType(resolvedType.typeText, value, rootKey);\r\n};\r\n\r\ninterface DevkitTypeResolverModule {\r\n resolveRuntimeTypeText: (params: { typeText: string; filePath?: string }) =>\r\n | { status: 'success'; typeText: string }\r\n | { status: 'error'; message: string };\r\n}\r\n","/* eslint-disable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/restrict-template-expressions */\r\n\r\nimport { getProjectConfig } from './projectConfig';\r\nimport { getLogger } from './loggerRegistry';\r\nimport { dispatchHook } from './hooks/registry';\r\nimport { getBindAddress } from './bindAddress';\r\n\r\nconst normalizeOrigin = ({ value, secure }: { value: string; secure: boolean }): string => {\r\n const trimmedValue = value.trim().toLowerCase();\r\n if (!trimmedValue) { return ''; }\r\n\r\n const withProtocol = trimmedValue.startsWith('http://') || trimmedValue.startsWith('https://')\r\n ? trimmedValue\r\n : `http${secure ? 's' : ''}://${trimmedValue}`;\r\n\r\n // Keep only scheme + host[:port] so paths, query params, and fragments don't affect allowlist checks.\r\n const extractedOrigin = (/^(https?:\\/\\/[^/?#]+)/.exec(withProtocol))?.[1] || '';\r\n if (!extractedOrigin) { return ''; }\r\n\r\n return extractedOrigin\r\n // Treat explicit :80 as equivalent to implicit default http port.\r\n .replace(/^http:\\/\\/(.+):80$/i, 'http://$1')\r\n // Treat explicit :443 as equivalent to implicit default https port.\r\n .replace(/^https:\\/\\/(.+):443$/i, 'https://$1');\r\n};\r\n\r\nconst isLocalhostOrigin = (normalized: string): boolean => {\r\n return /^https?:\\/\\/localhost(:\\d+)?$/i.test(normalized);\r\n};\r\n\r\nconst allowedOrigin = (origin: string): boolean => {\r\n const secure = process.env.SECURE === 'true';\r\n const cors = getProjectConfig().http.cors;\r\n\r\n //? Project-supplied origins live in `ProjectConfig.http.cors.allowedOrigins`.\r\n //? Two modes: static array OR synchronous resolver function. The resolver\r\n //? form lets consumers do per-tenant / per-time-window allow-listing\r\n //? without restarting; it must be sync because Socket.io's CORS check is sync.\r\n const configured = cors.allowedOrigins;\r\n\r\n //? Bind address comes from the registry (populated by `createLuckyStackServer`\r\n //? from `options.ip`/`options.port`). Falls back to `SERVER_IP`/`SERVER_PORT`\r\n //? env vars for legacy boots that don't go through the helper. Empty `port`\r\n //? produces an unmatchable `host:` entry which is fine — same-origin requests\r\n //? are typically allowed via `cors.allowLocalhost` or the configured\r\n //? `allowedOrigins` list anyway.\r\n const { ip: bindIp, port: bindPort } = getBindAddress();\r\n const location = bindPort\r\n ? `http${secure ? 's' : ''}://${bindIp}:${bindPort}`\r\n : `http${secure ? 's' : ''}://${bindIp}`;\r\n const normalizedOrigin = normalizeOrigin({ value: origin, secure });\r\n\r\n if (cors.allowLocalhost && normalizedOrigin && isLocalhostOrigin(normalizedOrigin)) {\r\n return true;\r\n }\r\n\r\n //? Resolver-function mode: defer entirely to the consumer's logic. The\r\n //? framework still keeps the same-origin bind address always-allowed.\r\n if (typeof configured === 'function') {\r\n if (normalizedOrigin === location) return true;\r\n if (configured(origin)) return true;\r\n // Fall through to the rejection path below.\r\n } else {\r\n const normalizedAllowedOrigins = new Set(\r\n [location, ...configured]\r\n .map((value) => normalizeOrigin({ value, secure }))\r\n .filter(Boolean)\r\n );\r\n\r\n if (normalizedOrigin && normalizedAllowedOrigins.has(normalizedOrigin)) {\r\n return true;\r\n }\r\n }\r\n\r\n //? Gated behind `devLogs` to avoid amplifying CORS-rejection traffic into\r\n //? production logs (an attacker could spam invalid origins otherwise). The\r\n //? structured `corsRejected` hook is the durable signal for production —\r\n //? subscribe via `registerHook('corsRejected', ...)` for audit/alerting.\r\n const debugAllowedOrigins = typeof configured === 'function'\r\n ? ['<dynamic-resolver>', location]\r\n : [location, ...configured]\r\n .map((value) => normalizeOrigin({ value, secure }))\r\n .filter(Boolean);\r\n\r\n if (getProjectConfig().logging.devLogs) {\r\n getLogger().warn('cors: origin not allowed', {\r\n origin,\r\n normalizedOrigin,\r\n allowedOrigins: debugAllowedOrigins,\r\n allowLocalhost: cors.allowLocalhost,\r\n });\r\n }\r\n void dispatchHook('corsRejected', {\r\n origin,\r\n normalizedOrigin,\r\n allowedOrigins: debugAllowedOrigins,\r\n allowLocalhost: cors.allowLocalhost,\r\n });\r\n return false;\r\n};\r\n\r\nexport default allowedOrigin;\r\n","import { Socket } from \"socket.io-client\";\r\nimport { getProjectConfig } from \"./projectConfig\";\r\nimport { getLogger } from \"./loggerRegistry\";\r\n\r\ninterface ApiQueueItem {\r\n id: string;\r\n key: string;\r\n run: (socketInstance: Socket) => void;\r\n createdAt: number;\r\n /**\r\n * Per-request override of `projectConfig.offlineQueue.dropPolicy`. See\r\n * `SyncQueueItem.dropPolicy` for details.\r\n */\r\n dropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\r\n}\r\n\r\ninterface SyncQueueItem {\r\n id: string;\r\n key: string;\r\n run: (socketInstance: Socket) => void;\r\n createdAt: number;\r\n /**\r\n * Per-request override of `projectConfig.offlineQueue.dropPolicy`. Lets a\r\n * specific sync (\"editor cursor move\") pick `'drop-oldest'` while the app\r\n * default stays `'reject'` for safer sends. When omitted, falls back to\r\n * the global config.\r\n */\r\n dropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\r\n}\r\n\r\nconst apiQueue: ApiQueueItem[] = [];\r\nconst syncQueue: SyncQueueItem[] = [];\r\n\r\n//? Independent flush guards so a long api flush doesn't no-op a concurrent\r\n//? sync flush (or vice versa).\r\nlet isFlushingApi = false;\r\nlet isFlushingSync = false;\r\n\r\nexport const isOnline = () => {\r\n if (typeof navigator === \"undefined\") return true;\r\n return navigator.onLine;\r\n};\r\n\r\nconst evictExpired = (queue: { createdAt: number }[]): void => {\r\n const maxAgeMs = getProjectConfig().offlineQueue.maxAgeMs;\r\n if (maxAgeMs <= 0) return;\r\n const cutoff = Date.now() - maxAgeMs;\r\n for (let i = queue.length - 1; i >= 0; i -= 1) {\r\n const item = queue[i];\r\n if (item && item.createdAt < cutoff) {\r\n queue.splice(i, 1);\r\n }\r\n }\r\n};\r\n\r\nconst enqueueWithPolicy = <T extends { createdAt: number; dropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject' }>(\r\n queue: T[],\r\n item: T,\r\n label: string,\r\n): boolean => {\r\n evictExpired(queue);\r\n const { maxSize, dropPolicy: globalPolicy } = getProjectConfig().offlineQueue;\r\n //? Per-request override wins over the global policy. Lets specific\r\n //? syncs pick 'drop-oldest' for liveness while the app default stays\r\n //? 'reject' for safer sends.\r\n const effectivePolicy = item.dropPolicy ?? globalPolicy;\r\n if (queue.length < maxSize) {\r\n queue.push(item);\r\n return true;\r\n }\r\n if (effectivePolicy === 'drop-oldest') {\r\n queue.shift();\r\n queue.push(item);\r\n getLogger().debug(`offlineQueue:${label} full — dropped oldest item (policy=${effectivePolicy})`);\r\n return true;\r\n }\r\n if (effectivePolicy === 'drop-newest') {\r\n getLogger().debug(`offlineQueue:${label} full — dropped newest item (policy=${effectivePolicy})`);\r\n return false;\r\n }\r\n getLogger().warn(`offlineQueue:${label} full — rejecting enqueue (policy=${effectivePolicy})`);\r\n return false;\r\n};\r\n\r\nexport const enqueueApiRequest = (item: ApiQueueItem): boolean =>\r\n enqueueWithPolicy(apiQueue, item, 'api');\r\n\r\nexport const enqueueSyncRequest = (item: SyncQueueItem): boolean =>\r\n enqueueWithPolicy(syncQueue, item, 'sync');\r\n\r\nexport const removeApiQueueItem = (id: string) => {\r\n const index = apiQueue.findIndex((item) => item.id === id);\r\n if (index !== -1) {\r\n apiQueue.splice(index, 1);\r\n }\r\n};\r\n\r\nexport const removeSyncQueueItem = (id: string) => {\r\n const index = syncQueue.findIndex((item) => item.id === id);\r\n if (index !== -1) {\r\n syncQueue.splice(index, 1);\r\n }\r\n};\r\n\r\nexport const removeApiQueueItemsByKey = (key: string) => {\r\n for (let i = apiQueue.length - 1; i >= 0; i -= 1) {\r\n if (apiQueue[i]?.key === key) {\r\n apiQueue.splice(i, 1);\r\n }\r\n }\r\n};\r\n\r\nexport const flushApiQueue = (canRun: () => boolean, socketInstance: Socket) => {\r\n if (isFlushingApi) return;\r\n evictExpired(apiQueue);\r\n if (apiQueue.length === 0) return;\r\n if (!canRun()) return;\r\n\r\n isFlushingApi = true;\r\n while (apiQueue.length > 0) {\r\n if (!canRun()) break;\r\n const item = apiQueue.shift();\r\n item?.run(socketInstance);\r\n }\r\n isFlushingApi = false;\r\n};\r\n\r\nexport const flushSyncQueue = (canRun: () => boolean, socketInstance: Socket) => {\r\n if (isFlushingSync) return;\r\n evictExpired(syncQueue);\r\n if (syncQueue.length === 0) return;\r\n if (!canRun()) return;\r\n\r\n isFlushingSync = true;\r\n while (syncQueue.length > 0) {\r\n if (!canRun()) break;\r\n const item = syncQueue.shift();\r\n item?.run(socketInstance);\r\n }\r\n isFlushingSync = false;\r\n};\r\n\r\nexport const getApiQueueSize = () => apiQueue.length;\r\nexport const getSyncQueueSize = () => syncQueue.length;\r\n","import type { Socket } from 'socket.io-client';\n\n// Shared, mutable socket-client state. Single source of truth for the socket\n// instance, consumed by transport helpers in @luckystack/core (apiRequest)\n// and @luckystack/sync (syncRequest), and written by the project's React\n// hook wiring in `src/_sockets/socketInitializer.ts` via `setSocket(io(...))`.\n//\n// The `export let socket` form gives importers a live binding — they always\n// see the current value after `setSocket` replaces it.\n\n// eslint-disable-next-line import-x/no-mutable-exports\nexport let socket: Socket | null = null;\n\nexport const setSocket = (next: Socket | null): void => {\n socket = next;\n};\n\nlet responseIndex = 0;\nconst WAIT_FOR_SOCKET_INTERVAL_MS = 10;\nconst WAIT_FOR_SOCKET_MAX_ITERATIONS = 500;\n\nexport const incrementResponseIndex = (): number => {\n responseIndex = responseIndex + 1;\n return responseIndex;\n};\n\nexport const waitForSocket = async (): Promise<Socket | null> => {\n let i = 0;\n while (!socket) {\n await new Promise((resolve) => setTimeout(resolve, WAIT_FOR_SOCKET_INTERVAL_MS));\n i++;\n if (i > WAIT_FOR_SOCKET_MAX_ITERATIONS) {\n return null;\n }\n }\n return socket;\n};\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","//? Client-side CSRF helper. Lazily fetches and caches the session's CSRF\r\n//? token from `/auth/csrf`, then attaches it as `x-csrf-token` on\r\n//? subsequent HTTP fetches in cookie mode.\r\n//?\r\n//? Token-mode sessions are CSRF-immune because cross-origin POSTs do not\r\n//? auto-attach the sessionStorage value; the cache simply stays empty in\r\n//? that mode.\r\n\r\nimport { getCsrfConfig } from './csrfConfig';\r\nimport { getProjectConfig } from './projectConfig';\r\nimport tryCatch from './tryCatch';\r\n\r\nlet cachedToken: string | null = null;\r\nlet inflightFetch: Promise<string | null> | null = null;\r\n\r\nconst resolveBackendUrl = (): string => {\r\n // The browser's location is the most reliable backend URL when the app\r\n // is served same-origin. Server-side callers should not invoke this\r\n // helper — they have direct access to the session.\r\n //? Both checks here look redundant to TS (globals are always typed as set)\r\n //? but at runtime in Node `globalThis.window` is undefined; the defensive\r\n //? guard is what keeps SSR from crashing.\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- SSR runtime guard\r\n if (globalThis.window !== undefined && globalThis.location?.origin) {\r\n return globalThis.location.origin;\r\n }\r\n return '';\r\n};\r\n\r\nconst fetchCsrfToken = async (): Promise<string | null> => {\r\n const base = resolveBackendUrl();\r\n const [error, token] = await tryCatch<string | null, undefined>(async () => {\r\n const response = await fetch(`${base}/auth/csrf`, {\r\n method: 'GET',\r\n credentials: 'include',\r\n });\r\n if (!response.ok) return null;\r\n const body = (await response.json()) as { csrfToken?: string | null };\r\n return body.csrfToken ?? null;\r\n });\r\n if (error) return null;\r\n return token ?? null;\r\n};\r\n\r\n/**\r\n * Fetch (or return the cached) CSRF token for the active session. Returns\r\n * null in token mode or when no session exists. Callers should attach the\r\n * returned value as the `X-CSRF-Token` request header.\r\n */\r\nexport const getCsrfToken = async (): Promise<string | null> => {\r\n //? Token-mode sessions: skip entirely. The server doesn't enforce CSRF\r\n //? in token mode, and there's no value to attach.\r\n if (getProjectConfig().session.basedToken) {\r\n return null;\r\n }\r\n\r\n if (cachedToken) return cachedToken;\r\n if (inflightFetch) return inflightFetch;\r\n\r\n inflightFetch = fetchCsrfToken().then((token) => {\r\n cachedToken = token;\r\n inflightFetch = null;\r\n return token;\r\n });\r\n\r\n return inflightFetch;\r\n};\r\n\r\n/** Drop the cached token. Call this on logout or when a 403 csrfMismatch is seen. */\r\nexport const clearCsrfToken = (): void => {\r\n cachedToken = null;\r\n inflightFetch = null;\r\n};\r\n\r\nconst STATE_CHANGING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\r\n\r\nconst isCsrfMismatchResponse = async (response: Response): Promise<boolean> => {\r\n if (response.status !== 403) return false;\r\n const [error, isMismatch] = await tryCatch<boolean, undefined>(async () => {\r\n const cloned = response.clone();\r\n const body = (await cloned.json()) as { errorCode?: string };\r\n return body.errorCode === 'auth.csrfMismatch';\r\n });\r\n if (error) return false;\r\n return isMismatch ?? false;\r\n};\r\n\r\n/**\r\n * Drop-in replacement for `fetch` that automatically attaches the framework's\r\n * `X-CSRF-Token` and `X-Request-Id` headers to state-changing requests in\r\n * cookie mode. Use this for any project-side HTTP call that hits a\r\n * LuckyStack `/api/*`, `/sync/*`, or `/auth/api/*` endpoint.\r\n *\r\n * Behavior:\r\n * - GET / OPTIONS / HEAD: passes through without CSRF (read-only).\r\n * - Token mode: passes through without CSRF (immune by design).\r\n * - Cookie mode + state-changing method: lazily fetches `/auth/csrf` once,\r\n * caches, attaches as `x-csrf-token`. On a 403 `auth.csrfMismatch`\r\n * response, clears the cache and retries once.\r\n * - `credentials: 'include'` is forced unless the caller overrode it.\r\n *\r\n * @example\r\n * import { httpFetch } from '@luckystack/core/client';\r\n * const res = await httpFetch('/api/system/myRoute/v1', {\r\n * method: 'POST',\r\n * body: JSON.stringify({ ... }),\r\n * });\r\n */\r\nexport const httpFetch: typeof fetch = async (input, init = {}) => {\r\n const method = (init.method ?? 'GET').toUpperCase();\r\n const headers = new Headers(init.headers);\r\n if (!headers.has('Content-Type') && init.body && typeof init.body === 'string') {\r\n headers.set('Content-Type', 'application/json');\r\n }\r\n\r\n const csrfHeaderName = getCsrfConfig().headerName;\r\n const send = async (csrfToken: string | null): Promise<Response> => {\r\n const finalHeaders = new Headers(headers);\r\n if (csrfToken && STATE_CHANGING.has(method) && !finalHeaders.has(csrfHeaderName)) {\r\n finalHeaders.set(csrfHeaderName, csrfToken);\r\n }\r\n return fetch(input, {\r\n ...init,\r\n method,\r\n headers: finalHeaders,\r\n credentials: init.credentials ?? 'include',\r\n });\r\n };\r\n\r\n if (!STATE_CHANGING.has(method) || getProjectConfig().session.basedToken) {\r\n return send(null);\r\n }\r\n\r\n const token = await getCsrfToken();\r\n let response = await send(token);\r\n\r\n //? On csrfMismatch, the cached token is stale (session rotated). Clear and\r\n //? retry once with a fresh fetch — covers the case where the user logged\r\n //? back in while a tab was idle.\r\n if (await isCsrfMismatchResponse(response)) {\r\n clearCsrfToken();\r\n const refreshed = await getCsrfToken();\r\n if (refreshed) {\r\n response = await send(refreshed);\r\n }\r\n }\r\n\r\n return response;\r\n};\r\n"],"mappings":"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","names":["responseIndex","extractLanguageFromHeader","resolveErrorMessage","normalizeErrorResponse","formatter","isRegistered","activeConfig","path","fs","activeConfig","path","fs","registered","socket","path","responseIndex"]}
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/deployConfigRegistry.ts","../src/servicesConfigRegistry.ts","../src/localeReloader.ts","../src/env.ts","../src/db.ts","../src/clients.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 '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\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 /** Why the room name is being formatted — so a formatter can branch if needed. */\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 /** Local file path the adapter reads (when it supports path-based sends). */\n path?: string;\n /** Remote URL the adapter fetches (when it supports href-based sends). */\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 headers?: Record<string, string>;\n}\n\nexport type EmailResult =\n | { ok: true; id: string }\n | { ok: false; reason: string; cause?: unknown };\n\nexport interface EmailSender {\n /** Adapter identifier for logs + diagnostics (\"console\", \"resend\", \"smtp\", etc.). */\n name: string;\n send: (message: EmailMessage) => 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","//? Deploy-time topology registered by the project at boot. Mirrors the\n//? `projectConfig` / `runtimeMapsRegistry` / `notifier` pattern: framework\n//? packages read deploy data through `getDeployConfig()` so they don't\n//? `import '../../../deploy.config'`.\n//?\n//? Only the fields core actually consumes are declared here. The project's\n//? richer `DeployConfig` (environments, bindings, routing, etc.) stays in\n//? `deploy.config.ts` and is consumed directly by the router package, which\n//? is project-glue (tier-B) and intentionally stays project-coupled.\n//?\n//? Project entrypoint registers via the direct file path\n//? (`./packages/core/src/deployConfigRegistry`) — same Vite-bundle rule we\n//? already use for projectConfig: importing the core barrel from a\n//? client-bundled file pulls server-only modules (Redis, paths, etc.).\n\nexport interface DeployResourceShape {\n type: 'redis' | 'mongo';\n urlEnvKey: string;\n synchronizedEnvKeys?: string[];\n}\n\nexport interface DeployEnvironmentShape {\n redis: string;\n mongo: string;\n fallback?: string;\n bindings: Record<string, string>;\n}\n\nexport interface DeployRoutingShape {\n onMissingService?: 'hard-error' | 'proxy-fallback';\n missingServiceErrorCode?: string;\n enableUnhealthyFallback?: boolean;\n strictBootHandshake?: boolean;\n /** Default port the router listens on when ROUTER_PORT env var is unset (default 4000). */\n defaultRouterPort?: number;\n /** Default ms between health polls when not specified per-call (default 5000). */\n defaultHealthPollMs?: number;\n /** ms before the fallback `/_health` probe times out (default 3000). */\n healthProbeTimeoutMs?: number;\n /** Seconds the boot-UUID Redis key lives for (default 3600 = 1h). */\n bootKeyTtlSeconds?: number;\n /**\n * ms before the router gives up proxying to an upstream backend (SEC-30 /\n * router-DeployRoutingShape). Guards against slow-loris upstreams holding a\n * router worker open. The router reads this when building its proxy request.\n * DEFAULT undefined → router falls back to its own built-in default.\n */\n upstreamTimeoutMs?: number;\n /**\n * Name of the service that terminates websocket (Socket.io) traffic. The\n * router pins WS upgrade requests to this service (they cannot be load-balanced\n * round-robin without sticky sessions). DEFAULT undefined → router uses its\n * `system` convention.\n */\n websocketService?: string;\n /**\n * Path the router exposes for its OWN health/liveness (distinct from the\n * backend `/_health` boot-handshake endpoint). DEFAULT undefined → router\n * uses its built-in default.\n */\n routerHealthPath?: string;\n /**\n * Max request body size (bytes) the router will buffer/forward before\n * rejecting with 413. DEFAULT undefined → router uses its built-in default.\n */\n maxRequestBodyBytes?: number;\n}\n\nexport interface DeployDevelopmentShape {\n enableFallbackRouting?: boolean;\n healthPollMs?: number;\n switchNewTrafficToLocalWhenHealthy?: boolean;\n}\n\nexport interface DeployConfigShape {\n resources: Record<string, DeployResourceShape>;\n environments?: Record<string, DeployEnvironmentShape>;\n routing?: DeployRoutingShape;\n development?: DeployDevelopmentShape;\n}\n\nimport { createRegistry } from './createRegistry';\n\nconst DEFAULT_DEPLOY_CONFIG: DeployConfigShape = {\n resources: {},\n};\n\nconst registry = createRegistry<DeployConfigShape>(DEFAULT_DEPLOY_CONFIG);\n\nexport const registerDeployConfig = (config: DeployConfigShape): DeployConfigShape =>\n registry.register(config);\n\nexport const getDeployConfig = (): DeployConfigShape => registry.get();\n\nexport const isDeployConfigRegistered = (): boolean => registry.isRegistered();\n","//? Services config registry. The router (and presetLoader) consume the\n//? consumer's services topology — which services exist, which presets bundle\n//? them together — through this registry instead of a relative\n//? `../../../services.config` import.\n//?\n//? The consumer's `services.config.ts` registers its config at module import\n//? (side-effect pattern, identical to `deploy.config.ts:151`). Router code\n//? calls `getServicesConfig()` at request time.\n\nexport interface ServiceDefinition {\n /** 'root' -> reserved for the system service. Otherwise a folder name under srcDir. */\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- 'root' is documented as the reserved value; the union surfaces it in autocomplete\n source: 'root' | string;\n}\n\nexport interface PresetDefinition {\n description?: string;\n /** Services that are bundled together into one backend artifact. */\n services: string[];\n}\n\nexport interface ServicesConfigShape {\n services: Record<string, ServiceDefinition>;\n presets: Record<string, PresetDefinition>;\n}\n\nimport { createRegistry } from './createRegistry';\n\nconst registry = createRegistry<ServicesConfigShape>(\n //? Unregistered sentinel — `get` never returns this because `resolveDefault`\n //? throws first. Typed as the real shape so the slot stays well-typed.\n { services: {}, presets: {} },\n {\n resolveDefault: (): ServicesConfigShape => {\n throw new Error(\n '@luckystack/core: services config has not been registered. Call ' +\n '`registerServicesConfig({...})` from your `services.config.ts` (which should be ' +\n 'imported as a side-effect during boot).'\n );\n },\n },\n);\n\nexport const registerServicesConfig = (config: ServicesConfigShape): ServicesConfigShape =>\n registry.register(config);\n\nexport const getServicesConfig = (): ServicesConfigShape => registry.get();\n\nexport const isServicesConfigRegistered = (): boolean => registry.isRegistered();\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","import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { config as loadDotenv, parse as parseDotenv } from 'dotenv';\nimport { z } from 'zod';\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 SERVER_PORT: z.string().regex(/^\\d+$/).default('80'),\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\nexport const loadEnvFiles = (): void => {\n const files = getEnvFiles();\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.SERVER_PORT = process.env.SERVER_PORT ?? resolvedEnv.SERVER_PORT;\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 const parsed = EnvSchema.safeParse(process.env);\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","import './env';\nimport { PrismaClient } from '@prisma/client';\nimport { getPrismaClient, setDefaultPrismaResolver } from './clients';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __luckystackPrisma: PrismaClient | undefined;\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 globalThis.__luckystackPrisma ??= new PrismaClient();\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","/* 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 } from './clients';\nimport { getLogger } from './loggerRegistry';\nimport { applyStrayKeyPrefix } from './redisKeyFormatter';\n\nlet cachedDefault: 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\nconst buildDefaultRedisClient = (): RedisClient => {\n if (cachedDefault) return cachedDefault;\n\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 cachedDefault = new Redis({\n host: env.REDIS_HOST,\n port: Number.parseInt(env.REDIS_PORT, 10),\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 cachedDefault.on('ready', async () => {\n getLogger().info('Connected to Redis');\n });\n\n cachedDefault.on('error', (err) => {\n getLogger().error('Error connecting to Redis', err);\n });\n\n return cachedDefault;\n};\n\nsetDefaultRedisResolver(buildDefaultRedisClient);\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: env.REDIS_HOST,\n port: Number.parseInt(env.REDIS_PORT, 10),\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';\n\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\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","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","//? 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 from `SERVER_IP`/`SERVER_PORT` env vars when the\n//? consumer used the `options.ip`/`options.port` arguments instead.\n//?\n//? Resolution at call time:\n//? 1. `registerBindAddress(...)` value (if the server has booted)\n//? 2. `process.env.SERVER_IP` / `process.env.SERVER_PORT` (legacy)\n//? 3. `'127.0.0.1'` / `''` as the absolute fallback\n\ninterface BindAddress {\n ip: string;\n port: number;\n}\n\nlet registered: BindAddress | null = null;\n\nexport const registerBindAddress = (address: BindAddress): void => {\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 port: process.env.SERVER_PORT ?? '',\n };\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.\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\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. Rare in\n //? a correctly-configured deployment, so a plain warn is not spammy.\n getLogger().warn('rate-limit: client IP unresolved — keyed into the shared \"unknown\" bucket', { trustProxy });\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\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 */\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';\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 the oldest entry before inserting a new key to keep the\n //? store bounded. `Map` preserves insertion order so the first iterator key\n //? is the oldest. Only evict when we are at the cap AND the incoming key is\n //? genuinely new (i.e. not a refresh of an existing key checked above).\n if (rateLimitStore.size >= MAX_MEMORY_STORE_SIZE) {\n const oldest = rateLimitStore.keys().next().value;\n if (oldest !== undefined) rateLimitStore.delete(oldest);\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 (process.env.NODE_ENV === 'production') {\n getLogger().warn('[RateLimiter] clearAllRateLimits() called in production — ignored. Use clearRateLimit(key) for scoped resets.');\n return;\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).\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';\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 for (const char of inner) {\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 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 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\" (e.g. a parenthesised type `(string)` where the\n //? outer `()` was already stripped) must still recurse so the inner type\n //? is validated rather than falling through to the fail-closed terminal.\n if (unionParts.length === 1) {\n return validateType(unionParts[0] ?? type, 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 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 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 (process.env.NODE_ENV === 'production') {\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`). Falls back to `SERVER_IP`/`SERVER_PORT`\n //? env vars for legacy boots that don't go through the helper. 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 if (getProjectConfig().logging.devLogs) {\n getLogger().warn('cors: origin not allowed', {\n origin,\n normalizedOrigin,\n allowedOrigins: debugAllowedOrigins,\n allowLocalhost: cors.allowLocalhost,\n });\n }\n void dispatchHook('corsRejected', {\n origin,\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 === 0) return null;\n const first = txResult[0];\n if (!first) return null;\n const [getErr, value] = first;\n if (getErr) 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","registry","registry","path","path","path","fs","registry","path","fs","socket","createHash","createHash","path","getProjectConfig","createHash","randomBytes","createHash","randomBytes"]}