@luckystack/core 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -14
- package/CLAUDE.md +0 -1
- package/dist/client.js.map +1 -1
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/docs/app-bootstrap.md +0 -1
- package/package.json +1 -1
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) {\r\n return new Promise<void>((resolve) => setTimeout(resolve, ms));\r\n}\r\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';\r\n\r\nexport default async function tryCatch<T, P>(\r\n func: (values: P) => Promise<T> | T,\r\n params?: P,\r\n context?: Record<string, unknown>\r\n): Promise<[Error | null, T | null]> {\r\n try {\r\n const response = await func(params as P);\r\n return [null, response];\r\n } catch (error) {\r\n captureException(error, context);\r\n return [error as Error, null];\r\n }\r\n}\r\n","const VERSION_SEGMENT_REGEX = /^v\\d+$/;\r\n\r\nexport interface ServiceRouteParseSuccess {\r\n status: 'success';\r\n normalizedRouteName: string;\r\n service: string;\r\n routeName: string;\r\n}\r\n\r\nexport interface ServiceRouteParseError {\r\n status: 'error';\r\n reason: string;\r\n}\r\n\r\nexport type ServiceRouteParseResult = ServiceRouteParseSuccess | ServiceRouteParseError;\r\n\r\nexport interface TransportRouteParseSuccess {\r\n status: 'success';\r\n normalizedFullName: string;\r\n version: string;\r\n serviceRoute: ServiceRouteParseSuccess;\r\n}\r\n\r\nexport interface TransportRouteParseError {\r\n status: 'error';\r\n reason: string;\r\n}\r\n\r\nexport type TransportRouteParseResult = TransportRouteParseSuccess | TransportRouteParseError;\r\n\r\nconst normalizeRawRoute = (value: string): string => {\r\n return value.trim().replaceAll('\\\\', '/').replaceAll(/^\\/+/g, '').replaceAll(/\\/+$/g, '');\r\n};\r\n\r\nconst hasEmptySegments = (value: string): boolean => {\r\n return value.split('/').some((segment) => segment.length === 0);\r\n};\r\n\r\nexport const parseServiceRouteName = (value: string): ServiceRouteParseResult => {\r\n const normalized = normalizeRawRoute(value);\r\n if (!normalized) {\r\n return { status: 'error', reason: 'Route name cannot be empty.' };\r\n }\r\n\r\n if (hasEmptySegments(normalized)) {\r\n return { status: 'error', reason: 'Route name cannot contain empty segments.' };\r\n }\r\n\r\n const segments = normalized.split('/');\r\n if (segments.length < 2) {\r\n return { status: 'error', reason: 'Route name must include service and route segments.' };\r\n }\r\n\r\n const [service, ...routeSegments] = segments;\r\n const routeName = routeSegments.join('/');\r\n\r\n if (!service || !routeName) {\r\n return { status: 'error', reason: 'Route name must include service and route segments.' };\r\n }\r\n\r\n return {\r\n status: 'success',\r\n normalizedRouteName: `${service}/${routeName}`,\r\n service,\r\n routeName,\r\n };\r\n};\r\n\r\nexport const parseTransportRouteName = ({\r\n value,\r\n prefix,\r\n}: {\r\n value: string;\r\n prefix: 'api' | 'sync';\r\n}): TransportRouteParseResult => {\r\n const normalized = normalizeRawRoute(value);\r\n if (!normalized) {\r\n return { status: 'error', reason: 'Transport route cannot be empty.' };\r\n }\r\n\r\n if (hasEmptySegments(normalized)) {\r\n return { status: 'error', reason: 'Transport route cannot contain empty segments.' };\r\n }\r\n\r\n const segments = normalized.split('/');\r\n const routeSegments = segments[0] === prefix ? segments.slice(1) : segments;\r\n\r\n if (routeSegments.length < 3) {\r\n return {\r\n status: 'error',\r\n reason: `Transport route must use ${prefix}/{service}/{name}/{version} format.`,\r\n };\r\n }\r\n\r\n const version = routeSegments.at(-1);\r\n if (!version || !VERSION_SEGMENT_REGEX.test(version)) {\r\n return { status: 'error', reason: 'Route version segment must match v{number}.' };\r\n }\r\n\r\n const serviceRouteToken = routeSegments.slice(0, -1).join('/');\r\n const serviceRoute = parseServiceRouteName(serviceRouteToken);\r\n if (serviceRoute.status === 'error') {\r\n return {\r\n status: 'error',\r\n reason: serviceRoute.reason,\r\n };\r\n }\r\n\r\n return {\r\n status: 'success',\r\n normalizedFullName: `${prefix}/${serviceRoute.normalizedRouteName}/${version}`,\r\n version,\r\n serviceRoute,\r\n };\r\n};\r\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 {\r\n key: string;\r\n value: string | number | boolean;\r\n}\r\n\r\nexport interface ErrorResponseInput {\r\n status?: unknown;\r\n errorCode?: unknown;\r\n errorParams?: unknown;\r\n httpStatus?: unknown;\r\n}\r\n\r\nexport interface NormalizedErrorResponse {\r\n status: 'error';\r\n message: string;\r\n errorCode: string;\r\n errorParams?: ErrorParam[];\r\n httpStatus?: number;\r\n}\r\n\r\nexport const INVALID_ERROR_RESPONSE_CODE = 'error.invalidResponse';\r\n\r\nexport const isErrorParamArray = (value: unknown): value is ErrorParam[] => {\r\n return Array.isArray(value) && value.every((item) => {\r\n return (\r\n !!item\r\n && typeof item === 'object'\r\n && typeof (item as ErrorParam).key === 'string'\r\n && ['string', 'number', 'boolean'].includes(typeof (item as ErrorParam).value)\r\n );\r\n });\r\n};\r\n\r\nexport const normalizeErrorResponseCore = ({\r\n response,\r\n fallbackHttpStatus,\r\n fallbackErrorCode,\r\n resolveMessage,\r\n}: {\r\n response: ErrorResponseInput;\r\n fallbackHttpStatus?: number;\r\n fallbackErrorCode?: string;\r\n resolveMessage?: (args: { errorCode: string; errorParams?: ErrorParam[] }) => string;\r\n}): NormalizedErrorResponse => {\r\n const normalizedParams = isErrorParamArray(response.errorParams) ? response.errorParams : undefined;\r\n const errorCodeValue = typeof response.errorCode === 'string' ? response.errorCode.trim() : '';\r\n const hasErrorCode = errorCodeValue.length > 0;\r\n const finalErrorCode = hasErrorCode ? errorCodeValue : (fallbackErrorCode ?? INVALID_ERROR_RESPONSE_CODE);\r\n const providedHttpStatus = typeof response.httpStatus === 'number' ? response.httpStatus : undefined;\r\n\r\n const message = resolveMessage\r\n ? resolveMessage({ errorCode: finalErrorCode, errorParams: normalizedParams })\r\n : finalErrorCode;\r\n\r\n return {\r\n status: 'error',\r\n message,\r\n errorCode: finalErrorCode,\r\n errorParams: normalizedParams,\r\n httpStatus: providedHttpStatus ?? fallbackHttpStatus,\r\n };\r\n};\r\n\r\nexport const defaultHttpStatusForResponse = ({\r\n status,\r\n explicitHttpStatus,\r\n fallbackErrorStatus = 400,\r\n}: {\r\n status: 'success' | 'error';\r\n explicitHttpStatus?: number;\r\n fallbackErrorStatus?: number;\r\n}): number => {\r\n if (typeof explicitHttpStatus === 'number') return explicitHttpStatus;\r\n return status === 'success' ? 200 : fallbackErrorStatus;\r\n};\r\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\r\n//? in @luckystack/api and @luckystack/sync can dispatch the per-route /\r\n//? global formatter without depending back up on @luckystack/server (which\r\n//? would create a dependency cycle).\r\n//?\r\n//? The framework normalizes errors via `normalizeErrorResponse` (i18n +\r\n//? envelope shape). Consumers can register a global formatter to add\r\n//? fields (e.g. supportTicketId, correlationId, deprecated alias keys for\r\n//? legacy clients), or override per-endpoint by exporting `errorFormatter`\r\n//? from an `_api/*.ts` or `_sync/*.ts` file — the api/sync handlers read\r\n//? that export off the runtime apiEntry/syncEntry and apply it through\r\n//? `applyErrorFormatter` below.\r\n//?\r\n//? Resolution order at error time:\r\n//? 1. Per-endpoint `errorFormatter` export (when the route file declares one).\r\n//? 2. Global formatter from `registerErrorFormatter(...)`.\r\n//? 3. Framework default `normalizeErrorResponse`.\r\n//?\r\n//? @luckystack/server re-exports `registerErrorFormatter` + types from\r\n//? here for backward compatibility — `import { registerErrorFormatter }\r\n//? from '@luckystack/server'` continues to work.\r\n\r\nexport interface ErrorFormatterContext {\r\n /** Resolved route name (`api/billing/getInvoice/v1`, `sync/...`). */\r\n routeName: string;\r\n /** Transport that triggered the error. */\r\n transport: 'socket' | 'http';\r\n /** Session id when available. */\r\n userId?: string | null;\r\n}\r\n\r\nexport type ErrorFormatter = (\r\n error: { status: 'error'; errorCode?: string; message?: string; httpStatus?: number; [key: string]: unknown },\r\n ctx: ErrorFormatterContext,\r\n) => Record<string, unknown>;\r\n\r\nlet activeFormatter: ErrorFormatter | null = null;\r\n\r\n/**\r\n * Register a global error-response formatter. Receives the normalized\r\n * error envelope + context; return a (possibly extended) object that the\r\n * framework will emit. Pass `null` to unregister.\r\n *\r\n * For per-endpoint overrides, export `errorFormatter` from your\r\n * `_api/foo_v1.ts` file directly — the api/sync handler imports + invokes\r\n * it through `applyErrorFormatter`.\r\n */\r\nexport const registerErrorFormatter = (formatter: ErrorFormatter | null): void => {\r\n activeFormatter = formatter;\r\n};\r\n\r\n/** Read the active global formatter (or null). */\r\nexport const getErrorFormatter = (): ErrorFormatter | null => activeFormatter;\r\n\r\n/**\r\n * Apply per-route → global → identity formatter chain to a normalized\r\n * error envelope. Returns the envelope unchanged when `status !== 'error'`\r\n * so it's safe to wrap every emit call. Errors thrown inside a formatter\r\n * are caught and logged; the un-formatted envelope is returned in that\r\n * case to keep the error path crash-resistant.\r\n */\r\nexport const applyErrorFormatter = (input: {\r\n response: Record<string, unknown> & { status?: string };\r\n routeName: string;\r\n transport: 'socket' | 'http';\r\n userId?: string | null;\r\n perRouteFormatter?: ErrorFormatter | undefined | null;\r\n}): Record<string, unknown> => {\r\n const { response, routeName, transport, userId, perRouteFormatter } = input;\r\n if (response.status !== 'error') return response;\r\n\r\n const ctx: ErrorFormatterContext = { routeName, transport, userId };\r\n const errorEnvelope = response as { status: 'error'; errorCode?: string; message?: string; httpStatus?: number; [key: string]: unknown };\r\n\r\n if (perRouteFormatter) {\r\n try {\r\n return perRouteFormatter(errorEnvelope, ctx);\r\n } catch (error) {\r\n console.error(`[errorFormatter] per-route formatter for ${routeName} threw:`, error);\r\n //? Fall through to global / identity so the error path stays resilient.\r\n }\r\n }\r\n\r\n const globalFormatter = activeFormatter;\r\n if (globalFormatter) {\r\n try {\r\n return globalFormatter(errorEnvelope, ctx);\r\n } catch (error) {\r\n console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);\r\n return response;\r\n }\r\n }\r\n\r\n return response;\r\n};\r\n","//? Pure page-route validator. Lives in @luckystack/core (no Node deps) so\r\n//? both the client-side router (`src/main.tsx`'s `getRoutes()`) AND the\r\n//? devkit-side scaffold CLI / hot-reload validator can consume it through\r\n//? the same single source of truth.\r\n//?\r\n//? Implements the invisible-parent folder convention: a folder starting\r\n//? with `privateFolderPrefix` is stripped from the URL but its CHILDREN\r\n//? remain routeable. A `page.tsx` placed directly inside such a folder\r\n//? (no non-private sibling segment) is invalid because it has no URL.\r\n//?\r\n//? Reserved framework folders (`_api`, `_sync`, `_components`, …) NEVER\r\n//? host a page, even nested — placing `page.tsx` anywhere under one of\r\n//? them is a hard error.\r\n\r\nexport interface PageRouteRules {\r\n /**\r\n * Single-character prefix that marks a folder as **invisible-parent** for\r\n * page routing. Default: `'_'`.\r\n */\r\n privateFolderPrefix: string;\r\n /**\r\n * Folder names that are reserved for framework-internal use and may NEVER\r\n * host a `page.tsx`, nested or otherwise. Scaffold + page-discovery emit\r\n * a hard error when a page is placed inside one.\r\n */\r\n scaffoldIgnoredFolders: readonly string[];\r\n}\r\n\r\nexport const DEFAULT_PAGE_ROUTE_RULES: PageRouteRules = {\r\n privateFolderPrefix: '_',\r\n scaffoldIgnoredFolders: [\r\n '_api',\r\n '_sync',\r\n '_function',\r\n '_functions',\r\n '_component',\r\n '_components',\r\n '_provider',\r\n '_providers',\r\n '_locale',\r\n '_locales',\r\n '_socket',\r\n '_sockets',\r\n '_shared',\r\n '_server',\r\n ],\r\n};\r\n\r\nexport interface PagePathValidationResult {\r\n /** `true` when the page file produces a routeable URL. */\r\n valid: boolean;\r\n /** Computed URL when `valid`. Always begins with `/`. `/` for root page. */\r\n route?: string;\r\n /** Human-readable explanation when `!valid`. */\r\n reason?: string;\r\n}\r\n\r\n/**\r\n * Validate a `page.tsx` file path relative to `src/`. Accepts both\r\n * forward and back slashes, with or without a leading `./` or `/`.\r\n * The filename **must** be `page.tsx` (or `page.jsx`); other filenames\r\n * return `valid: false`.\r\n *\r\n * Examples (defaults):\r\n * admin/page.tsx -> { valid: true, route: '/admin' }\r\n * _housing/renting/page.tsx -> { valid: true, route: '/renting' }\r\n * _housing/page.tsx -> { valid: false, reason: 'no URL segment left after stripping underscore folders' }\r\n * _api/something/page.tsx -> { valid: false, reason: 'page.tsx cannot live inside reserved folder _api' }\r\n * page.tsx -> { valid: true, route: '/' }\r\n * some/route.tsx -> { valid: false, reason: 'not a page file (expected page.tsx)' }\r\n */\r\nexport const validatePagePath = (\r\n srcRelativePath: string,\r\n rules: PageRouteRules = DEFAULT_PAGE_ROUTE_RULES,\r\n): PagePathValidationResult => {\r\n const normalized = srcRelativePath.replaceAll('\\\\', '/').replace(/^\\.\\//, '').replace(/^\\/+/, '');\r\n const segments = normalized.split('/').filter((segment) => segment.length > 0);\r\n\r\n if (segments.length === 0) {\r\n return { valid: false, reason: 'empty path' };\r\n }\r\n\r\n const filename = segments.at(-1);\r\n if (filename !== 'page.tsx' && filename !== 'page.jsx') {\r\n return { valid: false, reason: 'not a page file (expected page.tsx)' };\r\n }\r\n\r\n const folderSegments = segments.slice(0, -1);\r\n const prefix = rules.privateFolderPrefix;\r\n const reserved = rules.scaffoldIgnoredFolders;\r\n\r\n for (const reservedName of reserved) {\r\n if (folderSegments.includes(reservedName)) {\r\n return { valid: false, reason: `page.tsx cannot live inside reserved folder ${reservedName}` };\r\n }\r\n }\r\n\r\n const visibleSegments = folderSegments.filter((segment) => !segment.startsWith(prefix));\r\n\r\n if (folderSegments.length > 0 && visibleSegments.length === 0) {\r\n return { valid: false, reason: 'no URL segment left after stripping underscore folders' };\r\n }\r\n\r\n const route = visibleSegments.length === 0 ? '/' : `/${visibleSegments.join('/')}`;\r\n return { valid: true, route };\r\n};\r\n","import type { Server as SocketIOServer } from 'socket.io';\r\n\r\n//? Shared wire-protocol types for the socket-based API and sync transports.\r\n//? These used to live in `server/sockets/socket.ts` — moved to core so\r\n//? framework packages (@luckystack/api, @luckystack/sync) can stop\r\n//? deep-relative-importing into the project's server directory.\r\n\r\nexport interface apiMessage {\r\n name: string;\r\n data: object;\r\n responseIndex: number;\r\n}\r\n\r\nexport interface syncMessage {\r\n name: string;\r\n data: object;\r\n cb: string;\r\n receiver: string;\r\n responseIndex?: number;\r\n ignoreSelf?: boolean;\r\n}\r\n\r\n//? Module-level slot for the running Socket.io server instance. The project\r\n//? calls `setIoInstance(io)` right after constructing the server; framework\r\n//? packages call `getIoInstance()` when they need to broadcast.\r\nlet _ioInstance: SocketIOServer | null = null;\r\n\r\nexport const setIoInstance = (io: SocketIOServer | null): void => {\r\n _ioInstance = io;\r\n};\r\n\r\nexport const getIoInstance = (): SocketIOServer | null => _ioInstance;\r\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:\r\n//?\r\n//? 1. `registerEmailSender(sender)` — single global sender (legacy, kept\r\n//? for backwards compatibility with consumers who haven't migrated to\r\n//? the multi-adapter shape).\r\n//? 2. `registerEmailSenders({ default, transactional, marketing, ... })` —\r\n//? multiple named senders. Convention: framework code (login's\r\n//? password-reset, account flows) routes to the `'transactional'` slot\r\n//? when registered, otherwise falls back to `'default'` or the legacy\r\n//? single sender.\r\n//?\r\n//? Types live here (not in `@luckystack/email`) so framework packages can\r\n//? type-check against them without depending on the email package — keeping\r\n//? the email package optional.\r\n\r\nexport interface EmailMessage {\r\n to: string | string[];\r\n subject: string;\r\n html: string;\r\n text?: string;\r\n from?: string;\r\n replyTo?: string;\r\n cc?: string | string[];\r\n bcc?: string | string[];\r\n}\r\n\r\nexport type EmailResult =\r\n | { ok: true; id: string }\r\n | { ok: false; reason: string; cause?: unknown };\r\n\r\nexport interface EmailSender {\r\n /** Adapter identifier for logs + diagnostics (\"console\", \"resend\", \"smtp\", etc.). */\r\n name: string;\r\n send: (message: EmailMessage) => Promise<EmailResult>;\r\n}\r\n\r\n/**\r\n * Registry keyed by adapter-slot name. Reserved slots used by the\r\n * framework:\r\n *\r\n * - `'default'` — fallback when no specific slot matches.\r\n * - `'transactional'` — login password-reset, account-confirmation,\r\n * invoice-receipt, security-critical emails.\r\n * - `'marketing'` — newsletters, announcements; consumers MAY route\r\n * bulk sends here.\r\n * - `'diagnostics'` — diagnostic-test endpoints (e.g. playground).\r\n *\r\n * Custom slot names ('billing', 'support', etc.) are allowed; consumers\r\n * resolve them explicitly via `sendEmail({ adapter: 'billing', ... })`.\r\n */\r\nexport type EmailSenderRegistry = Partial<Record<string, EmailSender>>;\r\n\r\nlet activeSender: EmailSender | null = null;\r\nlet registry: EmailSenderRegistry = {};\r\n\r\n/**\r\n * Register a single global email sender. Kept for backwards compatibility.\r\n * For new code, prefer `registerEmailSenders({ ... })` which lets the\r\n * framework route transactional vs. marketing through different adapters.\r\n */\r\nexport const registerEmailSender = (sender: EmailSender): void => {\r\n activeSender = sender;\r\n //? Mirror into the registry so `getEmailSenderByName('default')` finds\r\n //? the legacy registration too. Consumers calling both APIs in the same\r\n //? boot get last-write-wins (multi-API call beats single, or vice versa,\r\n //? depending on order).\r\n registry = { ...registry, default: sender };\r\n};\r\n\r\n/**\r\n * Register multiple named email senders. Replaces the entire registry\r\n * (last-write-wins). Use this when your app routes different message\r\n * types through different adapters — e.g. Resend for marketing and SMTP\r\n * for transactional. The framework picks adapter slots by convention\r\n * (see `EmailSenderRegistry`); consumer code can pick explicitly via\r\n * `sendEmail({ adapter: 'foo', ... })`.\r\n */\r\nexport const registerEmailSenders = (senders: EmailSenderRegistry): void => {\r\n registry = { ...senders };\r\n if (senders.default) {\r\n activeSender = senders.default;\r\n }\r\n};\r\n\r\n/** Read the legacy single sender (or the `default` slot of the registry). */\r\nexport const getEmailSender = (): EmailSender | null => activeSender ?? registry.default ?? null;\r\n\r\n/**\r\n * Read a specific named sender. Falls back to the legacy single sender\r\n * when the requested slot is absent. Returns null when neither exists.\r\n * Pass the convention-based slots (`'transactional'`, `'marketing'`,\r\n * `'diagnostics'`, `'default'`) or any custom slot you registered.\r\n */\r\nexport const getEmailSenderByName = (name: string): EmailSender | null => {\r\n if (registry[name]) return registry[name];\r\n if (name === 'default' && activeSender) return activeSender;\r\n return null;\r\n};\r\n\r\n/** Read every registered slot name. */\r\nexport const listEmailSenderNames = (): string[] => Object.keys(registry);\r\n\r\nexport const isEmailSenderRegistered = (): boolean =>\r\n activeSender !== null || Object.keys(registry).length > 0;\r\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\r\n//? consumer's services topology — which services exist, which presets bundle\r\n//? them together — through this registry instead of a relative\r\n//? `../../../services.config` import.\r\n//?\r\n//? The consumer's `services.config.ts` registers its config at module import\r\n//? (side-effect pattern, identical to `deploy.config.ts:151`). Router code\r\n//? calls `getServicesConfig()` at request time.\r\n\r\nexport interface ServiceDefinition {\r\n /** 'root' -> reserved for the system service. Otherwise a folder name under srcDir. */\r\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- 'root' is documented as the reserved value; the union surfaces it in autocomplete\r\n source: 'root' | string;\r\n}\r\n\r\nexport interface PresetDefinition {\r\n description?: string;\r\n /** Services that are bundled together into one backend artifact. */\r\n services: string[];\r\n}\r\n\r\nexport interface ServicesConfigShape {\r\n services: Record<string, ServiceDefinition>;\r\n presets: Record<string, PresetDefinition>;\r\n}\r\n\r\nlet activeServicesConfig: ServicesConfigShape | null = null;\r\n\r\nexport const registerServicesConfig = (config: ServicesConfigShape): ServicesConfigShape => {\r\n activeServicesConfig = config;\r\n return activeServicesConfig;\r\n};\r\n\r\nexport const getServicesConfig = (): ServicesConfigShape => {\r\n if (!activeServicesConfig) {\r\n throw new Error(\r\n '@luckystack/core: services config has not been registered. Call ' +\r\n '`registerServicesConfig({...})` from your `services.config.ts` (which should be ' +\r\n 'imported as a side-effect during boot).'\r\n );\r\n }\r\n return activeServicesConfig;\r\n};\r\n\r\nexport const isServicesConfigRegistered = (): boolean => activeServicesConfig !== null;\r\n","//? Locale reloader registry. Lets `@luckystack/devkit`'s hot-reload watcher\r\n//? trigger a translations reload without taking a relative-path dependency\r\n//? on the consumer's project tree (the previous design did\r\n//? `import { reloadLocaleTranslations } from \"../../../server/utils/responseNormalizer\"`,\r\n//? which only resolved while devkit lived inside this monorepo).\r\n//?\r\n//? The consumer's `responseNormalizer.ts` (or whatever owns its translation\r\n//? loading) calls `registerLocaleReloader(fn)` at boot. The watcher calls\r\n//? `getLocaleReloader()?.()` whenever a `_locales/*.json` file changes. If\r\n//? no reloader is registered, the watcher is a no-op for that event.\r\n\r\nexport type LocaleReloader = () => void | Promise<void>;\r\n\r\nlet activeReloader: LocaleReloader | null = null;\r\n\r\nexport const registerLocaleReloader = (reloader: LocaleReloader): LocaleReloader => {\r\n activeReloader = reloader;\r\n return reloader;\r\n};\r\n\r\nexport const getLocaleReloader = (): LocaleReloader | null => activeReloader;\r\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 DNS: z.string().default(''),\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.DNS = process.env.DNS ?? resolvedEnv.DNS;\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\r\n//? default clients (TLS, Accelerate, custom logger, sentinel, ...) without\r\n//? touching framework code.\r\n//?\r\n//? Keyed by slot: the reserved `'default'` slot backs every framework\r\n//? internal (sessions, rate-limit, presence) and the `prisma`/`redis`\r\n//? proxies. Additional slots are opt-in and only reachable via\r\n//? `getPrismaClientFor(key)` / `getRedisClientFor(key)`, so a consumer can run\r\n//? graded credentials side by side — e.g. a read-only client on `'ro'` and a\r\n//? read-write client on `'rw'`, or one client per tenant.\r\n//?\r\n//? `db.ts` and `redis.ts` re-export `prisma` and `redis` as Proxy objects that\r\n//? forward every operation to the currently registered default client (or a\r\n//? lazy default if nothing was registered). All existing call sites that do\r\n//? `import { prisma } from '@luckystack/core'` or `import { redis } from '@luckystack/core'`\r\n//? keep working unchanged — they resolve the `'default'` slot.\r\n\r\nimport type { PrismaClient } from '@prisma/client';\r\nimport type { Redis as RedisClient } from 'ioredis';\r\n\r\n//? Reserved slot consumed by every framework internal and the proxies. A\r\n//? no-key registration / lookup targets this slot, preserving the original\r\n//? single-client behavior.\r\nexport const DEFAULT_CLIENT_KEY = 'default';\r\n\r\nconst prismaClients = new Map<string, PrismaClient>();\r\nconst redisClients = new Map<string, RedisClient>();\r\n\r\nexport const registerPrismaClient = (client: PrismaClient, key = DEFAULT_CLIENT_KEY): PrismaClient => {\r\n prismaClients.set(key, client);\r\n return client;\r\n};\r\n\r\nexport const registerRedisClient = (client: RedisClient, key = DEFAULT_CLIENT_KEY): RedisClient => {\r\n redisClients.set(key, client);\r\n return client;\r\n};\r\n\r\n//? Boot guards check the DEFAULT slot only — a keyed-only registration does\r\n//? not count as \"the framework has a client\", because internals use default.\r\nexport const isPrismaClientRegistered = (): boolean => prismaClients.has(DEFAULT_CLIENT_KEY);\r\nexport const isRedisClientRegistered = (): boolean => redisClients.has(DEFAULT_CLIENT_KEY);\r\n\r\n//? Diagnostic: which slots have an explicitly-registered client.\r\nexport const getPrismaClientKeys = (): string[] => [...prismaClients.keys()];\r\nexport const getRedisClientKeys = (): string[] => [...redisClients.keys()];\r\n\r\n//? `db.ts` and `redis.ts` set these accessors at module load with the lazy\r\n//? defaults. Splitting registration from default construction keeps a hard\r\n//? type dependency on `@prisma/client` / `ioredis` out of this file.\r\nlet defaultPrismaResolver: (() => PrismaClient) | null = null;\r\nlet defaultRedisResolver: (() => RedisClient) | null = null;\r\n\r\nexport const setDefaultPrismaResolver = (resolver: () => PrismaClient): void => {\r\n defaultPrismaResolver = resolver;\r\n};\r\n\r\nexport const setDefaultRedisResolver = (resolver: () => RedisClient): void => {\r\n defaultRedisResolver = resolver;\r\n};\r\n\r\n//? Resolve a specific slot. The `'default'` slot falls back to the lazy\r\n//? default resolver when nothing was registered; any other slot throws when\r\n//? unregistered — a keyed lookup must NEVER silently hand back the privileged\r\n//? default client, as that would defeat the point of graded credentials.\r\nexport const getPrismaClientFor = (key = DEFAULT_CLIENT_KEY): PrismaClient => {\r\n const client = prismaClients.get(key);\r\n if (client) return client;\r\n if (key === DEFAULT_CLIENT_KEY && defaultPrismaResolver) return defaultPrismaResolver();\r\n throw new Error(\r\n key === DEFAULT_CLIENT_KEY\r\n ? 'No Prisma client available. Either register one via registerPrismaClient(), ' +\r\n 'or import \"@luckystack/core\" from a context where the default resolver was set.'\r\n : `No Prisma client registered for slot \"${key}\". Register one via registerPrismaClient(client, \"${key}\").`\r\n );\r\n};\r\n\r\nexport const getRedisClientFor = (key = DEFAULT_CLIENT_KEY): RedisClient => {\r\n const client = redisClients.get(key);\r\n if (client) return client;\r\n if (key === DEFAULT_CLIENT_KEY && defaultRedisResolver) return defaultRedisResolver();\r\n throw new Error(\r\n key === DEFAULT_CLIENT_KEY\r\n ? 'No Redis client available. Either register one via registerRedisClient(), ' +\r\n 'or import \"@luckystack/core\" from a context where the default resolver was set.'\r\n : `No Redis client registered for slot \"${key}\". Register one via registerRedisClient(client, \"${key}\").`\r\n );\r\n};\r\n\r\nexport const getPrismaClient = (): PrismaClient => getPrismaClientFor(DEFAULT_CLIENT_KEY);\r\nexport const getRedisClient = (): RedisClient => getRedisClientFor(DEFAULT_CLIENT_KEY);\r\n\r\n//? Test-only: drop every registered slot. The default resolvers (set once at\r\n//? module load by db.ts / redis.ts) stay in place.\r\nexport const resetClientsForTests = (): void => {\r\n prismaClients.clear();\r\n redisClients.clear();\r\n};\r\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\r\n//? framework Redis key is namespaced. Replaces the per-call-site\r\n//? `${getProjectName()}-<area>:<id>` convention with one injectable hook, so a\r\n//? multi-tenant consumer can prefix keys per tenant (e.g. read a tenant id\r\n//? from AsyncLocalStorage) from ONE place instead of patching ~9 call sites.\r\n//?\r\n//? Contract: `formatKey(namespace, suffix)` builds a key. The DEFAULT\r\n//? formatter reproduces the framework's historical key bytes EXACTLY, so\r\n//? upgrading without registering a custom formatter requires NO key migration.\r\n//? A namespace beginning with a separator (`-` or `:`) is appended verbatim —\r\n//? this is how the framework preserves its legacy shapes (`-session`,\r\n//? `:rate-limit`); any other namespace is colon-joined for clean app keys\r\n//? (`formatKey('rag', id)` -> `<project>:rag:<id>`).\r\n//?\r\n//? Custom formatters MUST keep the `<namespace-root>:<suffix>` join, otherwise\r\n//? the framework's session/rate-limit `SCAN` enumeration (which derives its\r\n//? match pattern from `formatKey(namespace, '')`) cannot find the keys.\r\n\r\nimport { getProjectName } from './projectConfig';\r\n\r\nexport type RedisKeyFormatter = (namespace: string, suffix: string) => string;\r\n\r\nexport const defaultRedisKeyFormatter: RedisKeyFormatter = (namespace, suffix) => {\r\n const root = /^[-:]/.test(namespace) ? `${getProjectName()}${namespace}` : `${getProjectName()}:${namespace}`;\r\n return suffix === '' ? root : `${root}:${suffix}`;\r\n};\r\n\r\nlet formatter: RedisKeyFormatter | null = null;\r\n\r\n//? Register a custom key formatter (call once at boot, before the first Redis\r\n//? key is built). Last-write-wins.\r\nexport const registerRedisKeyFormatter = (fn: RedisKeyFormatter): void => {\r\n formatter = fn;\r\n};\r\n\r\nexport const getRedisKeyFormatter = (): RedisKeyFormatter => formatter ?? defaultRedisKeyFormatter;\r\n\r\n//? Test-only — restore the default formatter between scenarios.\r\nexport const resetRedisKeyFormatterForTests = (): void => {\r\n formatter = null;\r\n};\r\n\r\n//? Build a namespaced Redis key through the active formatter. Suffix is\r\n//? optional so callers can produce just the namespace root (used to derive\r\n//? SCAN match patterns: `${formatKey('-session', '')}:*`).\r\nexport const formatKey = (namespace: string, suffix = ''): string =>\r\n getRedisKeyFormatter()(namespace, suffix);\r\n\r\n//? Best-effort prefix for stray, un-namespaced keys passed to the `redis`\r\n//? proxy's single-key commands. A key that already contains a `:` (every\r\n//? framework key, plus any app key the caller chose to namespace) passes\r\n//? through unchanged, so this can never double-prefix or corrupt a real key.\r\n//? Project-level only — per-tenant scoping is the formatter's job, not the\r\n//? net's (the net has no request context).\r\nexport const applyStrayKeyPrefix = (key: string): string =>\r\n key.includes(':') ? key : `${getProjectName()}:${key}`;\r\n","//? Redis lease primitive — a minimal single-writer / leader-ownership building\r\n//? block (SET NX PX + owner-checked Lua release/renew). Lets a multi-instance\r\n//? deployment elect ONE owner for a host-bound resource (orchestrator, serial\r\n//? indexer, cron) without building fencing from scratch. Redis is already a\r\n//? hard dependency.\r\n//?\r\n//? Scope: this is a single-Redis, best-effort lock (NOT Redlock / distributed\r\n//? consensus). A client whose event loop stalls past the TTL can have its\r\n//? lease expire and be handed to another owner — size the TTL accordingly and\r\n//? renew well before expiry. For strict correctness across a Redis failover,\r\n//? use a dedicated coordinator (etcd/Zookeeper). The lease is a PRIMITIVE; the\r\n//? leader-election RUNTIME (the renew loop, what to do on loss) is app code.\r\n\r\nimport { randomBytes } from 'node:crypto';\r\nimport { redis } from './redis';\r\nimport tryCatch from './tryCatch';\r\nimport { formatKey } from './redisKeyFormatter';\r\n\r\nconst leaseKey = (name: string): string => formatKey('lease', name);\r\n\r\n//? Owner-checked compare-and-pexpire: only the current holder may extend.\r\nconst RENEW_SCRIPT = `\r\nif redis.call('get', KEYS[1]) == ARGV[1] then\r\n return redis.call('pexpire', KEYS[1], ARGV[2])\r\nelse\r\n return 0\r\nend\r\n`;\r\n\r\n//? Owner-checked compare-and-delete: releasing someone else's lease is a no-op,\r\n//? so a stale holder can't free a lease another owner has already taken.\r\nconst RELEASE_SCRIPT = `\r\nif redis.call('get', KEYS[1]) == ARGV[1] then\r\n return redis.call('del', KEYS[1])\r\nelse\r\n return 0\r\nend\r\n`;\r\n\r\n/**\r\n * Acquire an exclusive lease named `name` for `ttlMs` milliseconds. Returns an\r\n * opaque owner token on success, or `null` when another owner already holds it.\r\n * `SET key token PX ttl NX` is atomic, so concurrent acquirers race safely.\r\n * Keep the returned token to `renewLease` / `releaseLease`.\r\n */\r\nexport const acquireLease = async (name: string, ttlMs: number): Promise<string | null> => {\r\n const token = randomBytes(16).toString('hex');\r\n const [error, result] = await tryCatch(async () =>\r\n redis.set(leaseKey(name), token, 'PX', ttlMs, 'NX'),\r\n );\r\n if (error) return null;\r\n return result === 'OK' ? token : null;\r\n};\r\n\r\n/**\r\n * Extend a held lease by `ttlMs` (sliding ownership). Returns true when the\r\n * supplied `token` still owns the lease and the TTL was applied, false\r\n * otherwise (lost ownership, expired, or Redis error).\r\n */\r\nexport const renewLease = async (name: string, token: string, ttlMs: number): Promise<boolean> => {\r\n const [error, result] = await tryCatch(async () =>\r\n redis.eval(RENEW_SCRIPT, 1, leaseKey(name), token, String(ttlMs)),\r\n );\r\n if (error) return false;\r\n return result === 1;\r\n};\r\n\r\n/**\r\n * Release a held lease. Owner-checked: only succeeds (returns true) when\r\n * `token` is the current holder. Returns false on mismatch or Redis error.\r\n */\r\nexport const releaseLease = async (name: string, token: string): Promise<boolean> => {\r\n const [error, result] = await tryCatch(async () =>\r\n redis.eval(RELEASE_SCRIPT, 1, leaseKey(name), token),\r\n );\r\n if (error) return false;\r\n return result === 1;\r\n};\r\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 /\r\n//? response payloads. Feature packages register their domain-specific\r\n//? sensitive keys at boot (login → `password`, email → `apiKey`, etc.) so a\r\n//? consumer of the package never has to remember to extend a static list in\r\n//? core to keep their own data out of logs.\r\n//?\r\n//? Lookups are case-insensitive. Keys are merged with the framework defaults;\r\n//? `registerRedactedLogKeys` is additive — duplicate calls are idempotent.\r\n\r\nconst DEFAULT_REDACTED_LOG_KEYS: readonly string[] = [\r\n 'password',\r\n 'confirmpassword',\r\n 'token',\r\n 'newtoken',\r\n 'authorization',\r\n 'cookie',\r\n 'set-cookie',\r\n];\r\n\r\nconst redactedKeys = new Set<string>(DEFAULT_REDACTED_LOG_KEYS.map((key) => key.toLowerCase()));\r\n\r\nexport const registerRedactedLogKeys = (keys: readonly string[]): void => {\r\n for (const key of keys) {\r\n redactedKeys.add(key.toLowerCase());\r\n }\r\n};\r\n\r\nexport const getRedactedLogKeys = (): readonly string[] => [...redactedKeys];\r\n\r\nexport const isRedactedLogKey = (key: string): boolean => {\r\n return redactedKeys.has(key.toLowerCase());\r\n};\r\n\r\n//? Test-only helper — restore the default seed set between integration tests.\r\n//? Never call from production code; framework packages register their own\r\n//? keys at boot and clearing would re-expose them.\r\nexport const resetRedactedLogKeysForTests = (): void => {\r\n redactedKeys.clear();\r\n for (const key of DEFAULT_REDACTED_LOG_KEYS) {\r\n redactedKeys.add(key.toLowerCase());\r\n }\r\n};\r\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\r\n//? `io.use(...)` middlewares into the framework's socket bootstrap without\r\n//? forking `loadSocket.ts`. Registration order is preservation order —\r\n//? `applySocketMiddlewares(io)` calls each middleware in the order it was\r\n//? registered, which is the same contract Socket.io's native `io.use(...)`\r\n//? chain provides.\r\n//?\r\n//? Use cases: per-connection auth gates (JWT verification beyond the\r\n//? framework's session cookie), rate-limit shedding before handlers run,\r\n//? observability tags (Datadog span context, OpenTelemetry context),\r\n//? license-key checks, allow-list IP filters.\r\n\r\nimport type { Socket, Server as SocketIOServer } from 'socket.io';\r\n\r\nexport type SocketMiddleware = (socket: Socket, next: (err?: Error) => void) => void;\r\n\r\nconst middlewares: SocketMiddleware[] = [];\r\n\r\n/**\r\n * Register a Socket.io middleware that will be wired via `io.use(...)` when\r\n * the framework boots the socket server. Multiple middlewares can be\r\n * registered — they run in registration order before any `connect`\r\n * handler fires.\r\n */\r\nexport const registerSocketMiddleware = (mw: SocketMiddleware): void => {\r\n middlewares.push(mw);\r\n};\r\n\r\n/** Read the registered middleware list (read-only view). */\r\nexport const getSocketMiddlewares = (): readonly SocketMiddleware[] => middlewares;\r\n\r\n/** Test-only / hot-reload helper. Drops every registered middleware. */\r\nexport const clearSocketMiddlewares = (): void => {\r\n middlewares.length = 0;\r\n};\r\n\r\n/**\r\n * Wire every registered middleware into the running Socket.io server.\r\n * Called by `@luckystack/server`'s `loadSocket` after the `SocketIOServer`\r\n * is constructed and before any `connect` handler is attached, so custom\r\n * middlewares run before framework handlers see the socket.\r\n */\r\nexport const applySocketMiddlewares = (io: SocketIOServer): void => {\r\n for (const mw of middlewares) {\r\n io.use(mw);\r\n }\r\n};\r\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\r\n//? `serveAvatar` to change the file format, cache headers, or storage path.\r\n//? Defaults match the framework's prior hardcoded behavior (.webp, 24h cache).\r\n\r\nexport interface AvatarConfig {\r\n /**\r\n * File format(s) the framework will look for on disk, in order. The first\r\n * one that exists wins. Each entry is `{ extension, contentType }` so the\r\n * `Content-Type` response header matches what the file actually is.\r\n * Default: `[{ extension: 'webp', contentType: 'image/webp' }]`.\r\n */\r\n formats: { extension: string; contentType: string }[];\r\n /**\r\n * `Cache-Control` value sent with the avatar response.\r\n * Default: `'public, max-age=86400'` (24 hours).\r\n */\r\n cacheControl: string;\r\n}\r\n\r\nexport const DEFAULT_AVATAR_CONFIG: AvatarConfig = {\r\n formats: [{ extension: 'webp', contentType: 'image/webp' }],\r\n cacheControl: 'public, max-age=86400',\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 AvatarConfigInput = DeepPartial<AvatarConfig>;\r\n\r\nlet activeConfig: AvatarConfig = DEFAULT_AVATAR_CONFIG;\r\n\r\nexport const registerAvatarConfig = (config: AvatarConfigInput): void => {\r\n activeConfig = {\r\n formats: config.formats?.length ? (config.formats as AvatarConfig['formats']) : DEFAULT_AVATAR_CONFIG.formats,\r\n cacheControl: config.cacheControl ?? DEFAULT_AVATAR_CONFIG.cacheControl,\r\n };\r\n};\r\n\r\nexport const getAvatarConfig = (): AvatarConfig => activeConfig;\r\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","/* 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';\r\nimport { getProjectConfig } from './projectConfig';\r\nimport { getCookieValue } from './cookies';\r\n\r\n/**\r\n * Extract the authentication token from an HTTP request.\r\n *\r\n * Supports tokens from:\r\n * - `Authorization: Bearer <token>` header\r\n * - the configured session cookie\r\n *\r\n * @param req - The HTTP incoming message\r\n * @returns The token string or null if not found\r\n */\r\nexport const extractTokenFromRequest = (req: IncomingMessage): string | null => {\r\n const authHeader = req.headers.authorization;\r\n const bearerToken = authHeader?.startsWith('Bearer ')\r\n ? authHeader.slice(7)\r\n : null;\r\n\r\n const cookieToken = getCookieValue(req.headers.cookie, getProjectConfig().http.sessionCookieName);\r\n\r\n // Prefer the configured mode, but fall back to the other transport.\r\n if (getProjectConfig().session.basedToken) {\r\n return bearerToken ?? cookieToken;\r\n }\r\n\r\n return cookieToken ?? bearerToken;\r\n};\r\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","/**\r\n * Rate Limiter\r\n *\r\n * The framework's rate-limit dispatch is pluggable via\r\n * `registerRateLimitStrategy()`. The built-in `defaultRateLimitStrategy`\r\n * implements in-memory counters with optional Redis backing for\r\n * multi-instance deployments. Consumers can plug in alternative strategies\r\n * (token-bucket, sliding-window, per-user tiers, edge-KV-backed, etc.) by\r\n * implementing the `RateLimitStrategy` interface.\r\n */\r\n\r\nimport { getProjectConfig } from './projectConfig';\r\nimport tryCatch from './tryCatch';\r\nimport { redis } from './redis';\r\nimport { formatKey } from './redisKeyFormatter';\r\nimport { getLogger } from './loggerRegistry';\r\n\r\nexport interface CheckRateLimitParams {\r\n /** Unique key for rate limiting (e.g., \"user:123:api:getData\") */\r\n key: string;\r\n /** Maximum requests allowed in window */\r\n limit: number;\r\n /** Time window in milliseconds (default: 60_000 = 1 minute) */\r\n windowMs?: number;\r\n}\r\n\r\nexport interface RateLimitResult {\r\n /** Whether the request is allowed */\r\n allowed: boolean;\r\n /** Remaining requests in current window */\r\n remaining: number;\r\n /** Seconds until rate limit resets */\r\n resetIn: number;\r\n}\r\n\r\n/**\r\n * Pluggable rate-limit backend. The framework calls these methods through\r\n * the active strategy slot. Default implementation is memory + Redis.\r\n * Replace via `registerRateLimitStrategy(...)` at boot.\r\n */\r\nexport interface RateLimitStrategy {\r\n /** Human-readable identifier (used in logs/diagnostics). */\r\n name: string;\r\n /** Increment the counter for `key` and return whether the request is allowed. */\r\n check(params: CheckRateLimitParams): Promise<RateLimitResult>;\r\n /** Read current status without incrementing — useful for response headers. */\r\n getStatus(key: string, limit: number): Promise<RateLimitResult>;\r\n /** Reset the counter for a single key. */\r\n clear(key: string): Promise<void>;\r\n /** Reset all counters under this strategy's namespace. */\r\n clearAll(): Promise<void>;\r\n}\r\n\r\ninterface RateLimitEntry {\r\n count: number;\r\n resetAt: number;\r\n}\r\n\r\nconst rateLimitStore = new Map<string, RateLimitEntry>();\r\n\r\n//? Resolved at call time so `registerProjectConfig` can run after this\r\n//? module is imported. If the project never registers, the defaults from\r\n//? projectConfig.ts take effect (memory store, 'rate-limit' prefix).\r\nconst getRedisPrefix = (): string =>\r\n formatKey(`:${getProjectConfig().rateLimiting.redisKeyPrefix}`, '');\r\n\r\nconst isRedisMode = (): boolean => getProjectConfig().rateLimiting.store === 'redis';\r\nconst isRateLimitingEnabled = (): boolean => getProjectConfig().rateLimiting.enabled;\r\n\r\nconst buildAllowedResult = (limit: number): RateLimitResult => ({\r\n allowed: true,\r\n remaining: Math.max(0, limit),\r\n resetIn: 0,\r\n});\r\n\r\nlet redisFallbackLogged = false;\r\n\r\nconst RATE_LIMIT_INCREMENT_SCRIPT = `\r\nlocal current = redis.call('INCR', KEYS[1])\r\nif current == 1 then\r\n redis.call('PEXPIRE', KEYS[1], ARGV[1])\r\nend\r\nlocal ttl = redis.call('PTTL', KEYS[1])\r\nreturn { current, ttl }\r\n`;\r\n\r\nconst normalizeWindowMs = (windowMs: number): number => {\r\n if (!Number.isFinite(windowMs) || windowMs <= 0) {\r\n return getProjectConfig().rateLimiting.windowMs;\r\n }\r\n return windowMs;\r\n};\r\n\r\nconst getRedisRateLimitKey = (key: string): string => `${getRedisPrefix()}:${key}`;\r\n\r\nconst checkRateLimitInMemory = ({\r\n key,\r\n limit,\r\n windowMs = 60_000,\r\n}: CheckRateLimitParams): RateLimitResult => {\r\n const safeWindowMs = normalizeWindowMs(windowMs);\r\n const now = Date.now();\r\n const entry = rateLimitStore.get(key);\r\n\r\n if (!entry || entry.resetAt < now) {\r\n rateLimitStore.set(key, { count: 1, resetAt: now + safeWindowMs });\r\n return { allowed: true, remaining: limit - 1, resetIn: Math.ceil(safeWindowMs / 1000) };\r\n }\r\n\r\n entry.count += 1;\r\n return {\r\n allowed: entry.count <= limit,\r\n remaining: Math.max(0, limit - entry.count),\r\n resetIn: Math.ceil((entry.resetAt - now) / 1000),\r\n };\r\n};\r\n\r\nconst checkRateLimitInRedis = async ({\r\n key,\r\n limit,\r\n windowMs = 60_000,\r\n}: CheckRateLimitParams): Promise<RateLimitResult | null> => {\r\n const safeWindowMs = normalizeWindowMs(windowMs);\r\n const redisKey = getRedisRateLimitKey(key);\r\n\r\n const [evalError, evalResponse] = await tryCatch(\r\n async () => await redis.eval(RATE_LIMIT_INCREMENT_SCRIPT, 1, redisKey, String(safeWindowMs)),\r\n );\r\n\r\n if (evalError || !Array.isArray(evalResponse) || evalResponse.length < 2) {\r\n return null;\r\n }\r\n\r\n const requestCount = Number(evalResponse[0]);\r\n const ttlMs = Number(evalResponse[1]);\r\n const safeCount = Number.isFinite(requestCount) ? requestCount : 1;\r\n const safeTtlMs = Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : safeWindowMs;\r\n\r\n return {\r\n allowed: safeCount <= limit,\r\n remaining: Math.max(0, limit - safeCount),\r\n resetIn: Math.ceil(safeTtlMs / 1000),\r\n };\r\n};\r\n\r\nconst getRateLimitStatusInMemory = (key: string, limit: number): RateLimitResult => {\r\n const now = Date.now();\r\n const entry = rateLimitStore.get(key);\r\n\r\n if (!entry || entry.resetAt < now) {\r\n return { allowed: true, remaining: limit, resetIn: 0 };\r\n }\r\n\r\n return {\r\n allowed: entry.count < limit,\r\n remaining: Math.max(0, limit - entry.count),\r\n resetIn: Math.ceil((entry.resetAt - now) / 1000),\r\n };\r\n};\r\n\r\nconst getRateLimitStatusInRedis = async (key: string, limit: number): Promise<RateLimitResult | null> => {\r\n const redisKey = getRedisRateLimitKey(key);\r\n const [txError, txResponse] = await tryCatch(\r\n async () => await redis.multi().get(redisKey).pttl(redisKey).exec(),\r\n );\r\n\r\n if (txError || !txResponse || txResponse.length < 2) return null;\r\n\r\n const getResult = txResponse[0];\r\n const ttlResult = txResponse[1];\r\n if (!getResult || !ttlResult || getResult[0] || ttlResult[0]) return null;\r\n\r\n const currentCount = Number(getResult[1] ?? 0);\r\n const ttlMs = Number(ttlResult[1] ?? 0);\r\n\r\n if (currentCount <= 0 || ttlMs <= 0) {\r\n return { allowed: true, remaining: limit, resetIn: 0 };\r\n }\r\n\r\n return {\r\n allowed: currentCount < limit,\r\n remaining: Math.max(0, limit - currentCount),\r\n resetIn: Math.ceil(ttlMs / 1000),\r\n };\r\n};\r\n\r\nconst logRedisFallback = (): void => {\r\n if (redisFallbackLogged) return;\r\n redisFallbackLogged = true;\r\n getLogger().warn('[RateLimiter] Redis mode unavailable, falling back to in-memory mode');\r\n};\r\n\r\n/**\r\n * Built-in strategy: in-memory counters with optional Redis backing for\r\n * multi-instance deployments. Mode is controlled by\r\n * `projectConfig.rateLimiting.store` (`'memory'` or `'redis'`). When `redis`\r\n * mode fails (e.g. Redis disconnect), the strategy silently degrades to\r\n * in-memory and logs a one-shot warning.\r\n */\r\nexport const defaultRateLimitStrategy: RateLimitStrategy = {\r\n name: 'default-memory-redis',\r\n\r\n async check(params) {\r\n if (!isRateLimitingEnabled()) return buildAllowedResult(params.limit);\r\n\r\n if (isRedisMode()) {\r\n const redisResult = await checkRateLimitInRedis(params);\r\n if (redisResult) return redisResult;\r\n logRedisFallback();\r\n }\r\n\r\n return checkRateLimitInMemory(params);\r\n },\r\n\r\n async getStatus(key, limit) {\r\n if (!isRateLimitingEnabled()) return buildAllowedResult(limit);\r\n\r\n if (isRedisMode()) {\r\n const redisResult = await getRateLimitStatusInRedis(key, limit);\r\n if (redisResult) return redisResult;\r\n logRedisFallback();\r\n }\r\n\r\n return getRateLimitStatusInMemory(key, limit);\r\n },\r\n\r\n async clear(key) {\r\n if (isRedisMode()) {\r\n await tryCatch(async () => await redis.del(getRedisRateLimitKey(key)));\r\n }\r\n rateLimitStore.delete(key);\r\n },\r\n\r\n async clearAll() {\r\n if (isRedisMode()) {\r\n let cursor = '0';\r\n do {\r\n const [scanError, scanResponse] = await tryCatch(\r\n async () => await redis.scan(cursor, 'MATCH', `${getRedisPrefix()}:*`, 'COUNT', 100),\r\n );\r\n\r\n if (scanError || !scanResponse) break;\r\n\r\n cursor = scanResponse[0];\r\n const keys = scanResponse[1];\r\n\r\n if (keys.length > 0) {\r\n await tryCatch(async () => await redis.del(...keys));\r\n }\r\n } while (cursor !== '0');\r\n }\r\n\r\n rateLimitStore.clear();\r\n },\r\n};\r\n\r\nlet activeStrategy: RateLimitStrategy = defaultRateLimitStrategy;\r\n\r\n/**\r\n * Replace the active rate-limit backend. Pass a strategy that implements\r\n * `RateLimitStrategy`. Common use cases:\r\n *\r\n * - Token-bucket / sliding-window algorithms for smoother burst handling.\r\n * - Per-user tier limits (premium accounts get higher caps).\r\n * - Cloudflare-Workers-KV or upstash-rest-backed counters for edge.\r\n * - No-op strategy for test environments.\r\n *\r\n * Call this once at boot (typically from `luckystack/server/index.ts`)\r\n * BEFORE the first request lands. Subsequent calls overwrite the previous\r\n * registration (last-write-wins).\r\n */\r\nexport const registerRateLimitStrategy = (strategy: RateLimitStrategy): void => {\r\n activeStrategy = strategy;\r\n getLogger().debug(`[RateLimiter] active strategy → ${strategy.name}`);\r\n};\r\n\r\n/** Read the currently-active strategy (defaults to the built-in). */\r\nexport const getRateLimitStrategy = (): RateLimitStrategy => activeStrategy;\r\n\r\n/**\r\n * Check if a request is allowed under rate limiting rules.\r\n * Increments the counter for the key if allowed.\r\n */\r\nexport const checkRateLimit = async (params: CheckRateLimitParams): Promise<RateLimitResult> =>\r\n activeStrategy.check(params);\r\n\r\n/**\r\n * Get current rate limit status without incrementing counter.\r\n * Useful for rate limit headers in responses.\r\n */\r\nexport const getRateLimitStatus = async (key: string, limit: number): Promise<RateLimitResult> =>\r\n activeStrategy.getStatus(key, limit);\r\n\r\n/**\r\n * Clear rate limit for a specific key.\r\n * Useful for admin overrides or testing.\r\n */\r\nexport const clearRateLimit = async (key: string): Promise<void> => activeStrategy.clear(key);\r\n\r\n/**\r\n * Clear all rate limits.\r\n * Useful for testing or server restart.\r\n */\r\nexport const clearAllRateLimits = async (): Promise<void> => activeStrategy.clearAll();\r\n\r\n//? Cleanup expired entries from the in-memory store on a configurable\r\n//? interval. Only relevant for the default strategy; custom strategies\r\n//? handle their own cleanup. Restart-aware: we schedule recursively so the\r\n//? interval picks up `projectConfig.rateLimiting.cleanupIntervalMs` whenever\r\n//? it changes (e.g. after `registerProjectConfig`).\r\nconst scheduleCleanup = (): void => {\r\n const intervalMs = getProjectConfig().rateLimiting.cleanupIntervalMs;\r\n const timer = setTimeout(() => {\r\n const now = Date.now();\r\n let cleaned = 0;\r\n for (const [key, entry] of rateLimitStore) {\r\n if (entry.resetAt < now) {\r\n rateLimitStore.delete(key);\r\n cleaned++;\r\n }\r\n }\r\n if (cleaned > 0) {\r\n getLogger().debug(`[RateLimiter] Cleaned ${String(cleaned)} expired entries`);\r\n }\r\n scheduleCleanup();\r\n }, intervalMs);\r\n timer.unref();\r\n};\r\n\r\nscheduleCleanup();\r\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';\r\n\r\n// Shared, mutable socket-client state. Single source of truth for the socket\r\n// instance, consumed by transport helpers in @luckystack/core (apiRequest)\r\n// and @luckystack/sync (syncRequest), and written by the project's React\r\n// hook wiring in `src/_sockets/socketInitializer.ts` via `setSocket(io(...))`.\r\n//\r\n// The `export let socket` form gives importers a live binding — they always\r\n// see the current value after `setSocket` replaces it.\r\n\r\n// eslint-disable-next-line import-x/no-mutable-exports\r\nexport let socket: Socket | null = null;\r\n\r\nexport const setSocket = (next: Socket | null): void => {\r\n socket = next;\r\n};\r\n\r\nlet responseIndex = 0;\r\nconst WAIT_FOR_SOCKET_INTERVAL_MS = 10;\r\nconst WAIT_FOR_SOCKET_MAX_ITERATIONS = 500;\r\n\r\nexport const incrementResponseIndex = (): number => {\r\n responseIndex = responseIndex + 1;\r\n return responseIndex;\r\n};\r\n\r\nexport const waitForSocket = async (): Promise<Socket | null> => {\r\n let i = 0;\r\n while (!socket) {\r\n await new Promise((resolve) => setTimeout(resolve, WAIT_FOR_SOCKET_INTERVAL_MS));\r\n i++;\r\n if (i > WAIT_FOR_SOCKET_MAX_ITERATIONS) {\r\n return null;\r\n }\r\n }\r\n return socket;\r\n};\r\n","//? Cross-handler in-flight-request registry. Maps a stable key\r\n//? (`socketId:cb` for sync, `socketId:responseIndex` for api) to the\r\n//? `AbortController` driving the request's handler chain. The `apiCancel`\r\n//? / `syncCancel` socket event handlers look up the controller and call\r\n//? `.abort()` so the in-flight handler can stop emitting new chunks and\r\n//? exit early. Also walked on socket `disconnect` to abort everything\r\n//? still in flight for that socket.\r\n//?\r\n//? Lives in `@luckystack/core` (not `@luckystack/api` or `@luckystack/sync`)\r\n//? because the cancel-event listener is registered in `@luckystack/server`'s\r\n//? `loadSocket.ts`, which sits below both handler packages in the dep graph.\r\n//?\r\n//? Stored as plain Map (not WeakMap) because lookup is by string key and\r\n//? entries are explicitly removed at request end + on `disconnect`. Memory\r\n//? leak risk is bounded — handlers always call `unregister*` in their\r\n//? cleanup paths and disconnect drains everything for the closing socket.\r\n\r\nconst syncControllers = new Map<string, AbortController>();\r\nconst apiControllers = new Map<string, AbortController>();\r\n\r\nconst buildKey = (socketId: string, identifier: string | number): string =>\r\n `${socketId}:${String(identifier)}`;\r\n\r\nexport const registerSyncAbortController = (\r\n socketId: string,\r\n cb: string,\r\n controller: AbortController,\r\n): string => {\r\n const key = buildKey(socketId, cb);\r\n syncControllers.set(key, controller);\r\n return key;\r\n};\r\n\r\nexport const unregisterSyncAbortController = (key: string): void => {\r\n syncControllers.delete(key);\r\n};\r\n\r\nexport const abortSyncByCb = (socketId: string, cb: string): boolean => {\r\n const key = buildKey(socketId, cb);\r\n const controller = syncControllers.get(key);\r\n if (!controller) return false;\r\n controller.abort();\r\n syncControllers.delete(key);\r\n return true;\r\n};\r\n\r\nexport const registerApiAbortController = (\r\n socketId: string,\r\n responseIndex: number | string,\r\n controller: AbortController,\r\n): string => {\r\n const key = buildKey(socketId, responseIndex);\r\n apiControllers.set(key, controller);\r\n return key;\r\n};\r\n\r\nexport const unregisterApiAbortController = (key: string): void => {\r\n apiControllers.delete(key);\r\n};\r\n\r\nexport const abortApiByResponseIndex = (\r\n socketId: string,\r\n responseIndex: number | string,\r\n): boolean => {\r\n const key = buildKey(socketId, responseIndex);\r\n const controller = apiControllers.get(key);\r\n if (!controller) return false;\r\n controller.abort();\r\n apiControllers.delete(key);\r\n return true;\r\n};\r\n\r\n//? Drain everything in-flight for a socket — called on `disconnect`.\r\n//? Linear scan over both registries; disconnect is rare relative to\r\n//? regular request volume, and the registries are bounded by per-socket\r\n//? in-flight request count (typically <10).\r\nexport const abortAllForSocket = (socketId: string): void => {\r\n const prefix = `${socketId}:`;\r\n for (const [key, controller] of syncControllers) {\r\n if (key.startsWith(prefix)) {\r\n controller.abort();\r\n syncControllers.delete(key);\r\n }\r\n }\r\n for (const [key, controller] of apiControllers) {\r\n if (key.startsWith(prefix)) {\r\n controller.abort();\r\n apiControllers.delete(key);\r\n }\r\n }\r\n};\r\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/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"]}
|