@digilogiclabs/platform-core 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/index.ts","../src/auth/keycloak.ts","../src/auth/nextjs-keycloak.ts","../src/auth/api-security.ts","../src/auth/schemas.ts","../src/security.ts","../src/auth/feature-flags.ts","../src/auth/rate-limiter.ts","../src/auth/rate-limit-store-redis.ts","../src/auth/audit.ts","../src/api.ts","../src/auth/nextjs-api.ts","../src/env.ts"],"sourcesContent":["/**\n * Auth Module\n *\n * Keycloak OIDC helpers, API security types, validation schemas,\n * feature flags, rate limiting, audit logging, and essential utilities\n * shared across all apps.\n *\n * This sub-path export (`@digilogiclabs/platform-core/auth`) is lightweight —\n * it does NOT pull in database, storage, email, or payment adapters.\n * Use it in next.config.ts, middleware, and API routes without bloating bundles.\n */\n\nexport * from \"./keycloak\";\nexport * from \"./nextjs-keycloak\";\nexport * from \"./api-security\";\nexport * from \"./schemas\";\nexport * from \"./feature-flags\";\nexport * from \"./rate-limiter\";\nexport * from \"./rate-limit-store-redis\";\nexport * from \"./audit\";\nexport * from \"./nextjs-api\";\n\n// Re-export lightweight utilities from sibling modules so apps don't need\n// the main barrel import (which pulls in all adapters including stripe, pg, etc.)\nexport {\n // Security utilities\n constantTimeEqual,\n escapeHtml,\n containsUrls,\n containsHtml,\n stripHtml,\n sanitizeApiError,\n getCorrelationId,\n} from \"../security\";\n\nexport {\n // API error handling\n classifyError,\n ApiError,\n isApiError,\n CommonApiErrors,\n ApiErrorCode,\n buildPagination,\n} from \"../api\";\nexport type {\n ApiErrorCodeType,\n ApiSuccessResponse,\n ApiPaginatedResponse,\n} from \"../api\";\n\nexport {\n // Environment helpers\n getRequiredEnv,\n getOptionalEnv,\n getBoolEnv,\n getIntEnv,\n validateEnvVars,\n checkEnvVars,\n getEnvSummary,\n} from \"../env\";\nexport type { EnvValidationConfig, EnvValidationResult } from \"../env\";\n","/**\n * Keycloak Authentication Utilities\n *\n * Framework-agnostic helpers for working with Keycloak OIDC tokens.\n * Used by Next.js apps (Auth.js middleware + API routes) and .NET services\n * share the same role model — these helpers ensure consistent behavior.\n *\n * Edge-runtime compatible: uses atob() for JWT decoding, not Buffer.\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Keycloak provider configuration for Auth.js / NextAuth.\n * Everything needed to configure the Keycloak OIDC provider.\n */\nexport interface KeycloakConfig {\n /** Keycloak issuer URL (e.g. https://auth.example.com/realms/my-realm) */\n issuer: string;\n /** OAuth client ID registered in Keycloak */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n}\n\n/**\n * Token set returned by Keycloak after authentication or refresh.\n */\nexport interface KeycloakTokenSet {\n accessToken: string;\n refreshToken?: string;\n idToken?: string;\n /** Expiry as epoch milliseconds */\n expiresAt: number;\n /** Parsed realm roles (filtered, no Keycloak defaults) */\n roles: string[];\n}\n\n/**\n * Result of a token refresh attempt.\n */\nexport type TokenRefreshResult =\n | { ok: true; tokens: KeycloakTokenSet }\n | { ok: false; error: string };\n\n// ═══════════════════════════════════════════════════════════════\n// ROLE MANAGEMENT\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Default Keycloak roles that should be filtered out when checking\n * application-level roles. These are always present and not meaningful\n * for authorization decisions.\n */\nexport const KEYCLOAK_DEFAULT_ROLES = [\n \"offline_access\",\n \"uma_authorization\",\n] as const;\n\n/**\n * Parse realm roles from a Keycloak JWT access token.\n *\n * Supports two token formats:\n * - `realm_roles` (flat array) — Custom client mapper configuration\n * - `realm_access.roles` (nested) — Keycloak default format\n *\n * Uses atob() for Edge runtime compatibility (not Buffer.from).\n * Filters out Keycloak default roles automatically.\n *\n * @param accessToken - Raw JWT string from Keycloak\n * @param additionalDefaultRoles - Extra role names to filter (e.g. realm-specific defaults)\n * @returns Array of meaningful role names\n *\n * @example\n * ```typescript\n * const roles = parseKeycloakRoles(account.access_token)\n * // ['admin', 'editor']\n *\n * // With realm-specific defaults\n * const roles = parseKeycloakRoles(token, ['default-roles-my-realm'])\n * ```\n */\nexport function parseKeycloakRoles(\n accessToken: string | undefined | null,\n additionalDefaultRoles: string[] = [],\n): string[] {\n if (!accessToken) return [];\n\n try {\n const parts = accessToken.split(\".\");\n if (parts.length !== 3) return [];\n\n // Decode JWT payload — use atob for Edge runtime compatibility\n const payload = parts[1]!;\n const decoded = JSON.parse(atob(payload));\n\n // Try flat realm_roles first (custom mapper), then nested (Keycloak default)\n const realmRoles: unknown =\n decoded.realm_roles ?? decoded.realm_access?.roles;\n if (!Array.isArray(realmRoles)) return [];\n\n // Filter out Keycloak defaults and any realm-specific defaults\n const filterSet = new Set<string>([\n ...KEYCLOAK_DEFAULT_ROLES,\n ...additionalDefaultRoles,\n ]);\n\n return realmRoles.filter(\n (role): role is string =>\n typeof role === \"string\" && !filterSet.has(role),\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Check if a user has a specific role.\n *\n * @example\n * ```typescript\n * if (hasRole(session.user.roles, 'admin')) {\n * // grant access\n * }\n * ```\n */\nexport function hasRole(\n roles: string[] | undefined | null,\n role: string,\n): boolean {\n return roles?.includes(role) ?? false;\n}\n\n/**\n * Check if a user has ANY of the specified roles (OR logic).\n *\n * @example\n * ```typescript\n * if (hasAnyRole(session.user.roles, ['admin', 'editor'])) {\n * // grant access\n * }\n * ```\n */\nexport function hasAnyRole(\n roles: string[] | undefined | null,\n requiredRoles: string[],\n): boolean {\n if (!roles || roles.length === 0) return false;\n return requiredRoles.some((role) => roles.includes(role));\n}\n\n/**\n * Check if a user has ALL of the specified roles (AND logic).\n *\n * @example\n * ```typescript\n * if (hasAllRoles(session.user.roles, ['admin', 'billing'])) {\n * // grant access to billing admin\n * }\n * ```\n */\nexport function hasAllRoles(\n roles: string[] | undefined | null,\n requiredRoles: string[],\n): boolean {\n if (!roles || roles.length === 0) return false;\n return requiredRoles.every((role) => roles.includes(role));\n}\n\n// ═══════════════════════════════════════════════════════════════\n// TOKEN REFRESH\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Check if a token needs refreshing.\n *\n * @param expiresAt - Token expiry as epoch milliseconds\n * @param bufferMs - Refresh this many ms before actual expiry (default: 60s)\n * @returns true if the token should be refreshed\n */\nexport function isTokenExpired(\n expiresAt: number | undefined | null,\n bufferMs = 60_000,\n): boolean {\n if (!expiresAt) return true;\n return Date.now() >= expiresAt - bufferMs;\n}\n\n/**\n * Build the URLSearchParams for a Keycloak token refresh request.\n *\n * This is the body for POST to `{issuer}/protocol/openid-connect/token`.\n * Framework-agnostic — use with fetch(), axios, or any HTTP client.\n *\n * @example\n * ```typescript\n * const params = buildTokenRefreshParams(config, refreshToken)\n * const response = await fetch(`${config.issuer}/protocol/openid-connect/token`, {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n * body: params,\n * })\n * ```\n */\nexport function buildTokenRefreshParams(\n config: KeycloakConfig,\n refreshToken: string,\n): URLSearchParams {\n return new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: config.clientId,\n client_secret: config.clientSecret,\n refresh_token: refreshToken,\n });\n}\n\n/**\n * Get the Keycloak token endpoint URL for a given issuer.\n */\nexport function getTokenEndpoint(issuer: string): string {\n // Remove trailing slash if present\n const base = issuer.endsWith(\"/\") ? issuer.slice(0, -1) : issuer;\n return `${base}/protocol/openid-connect/token`;\n}\n\n/**\n * Get the Keycloak end session (logout) endpoint URL.\n */\nexport function getEndSessionEndpoint(issuer: string): string {\n const base = issuer.endsWith(\"/\") ? issuer.slice(0, -1) : issuer;\n return `${base}/protocol/openid-connect/logout`;\n}\n\n/**\n * Perform a token refresh against Keycloak and parse the result.\n *\n * Returns a discriminated union — check `result.ok` before accessing tokens.\n * Automatically parses roles from the new access token.\n *\n * @example\n * ```typescript\n * const result = await refreshKeycloakToken(config, currentRefreshToken)\n * if (result.ok) {\n * token.accessToken = result.tokens.accessToken\n * token.roles = result.tokens.roles\n * } else {\n * // Force re-login\n * token.error = 'RefreshTokenError'\n * }\n * ```\n */\nexport async function refreshKeycloakToken(\n config: KeycloakConfig,\n refreshToken: string,\n additionalDefaultRoles?: string[],\n): Promise<TokenRefreshResult> {\n try {\n const endpoint = getTokenEndpoint(config.issuer);\n const params = buildTokenRefreshParams(config, refreshToken);\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: params,\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n return {\n ok: false,\n error: `Token refresh failed: HTTP ${response.status} - ${body}`,\n };\n }\n\n const data = await response.json();\n\n return {\n ok: true,\n tokens: {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n idToken: data.id_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n roles: parseKeycloakRoles(data.access_token, additionalDefaultRoles),\n },\n };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"Token refresh failed\",\n };\n }\n}\n","/**\n * Next.js Auth.js + Keycloak Configuration Builder\n *\n * Generates Auth.js callbacks for Keycloak OIDC integration.\n * Handles JWT token storage, role parsing, token refresh, and session mapping.\n *\n * Edge-runtime compatible: uses only atob() and fetch(), no Node.js-only APIs.\n *\n * @example\n * ```typescript\n * // auth.config.ts (Edge-compatible)\n * import { buildKeycloakCallbacks } from '@digilogiclabs/platform-core'\n * import Keycloak from 'next-auth/providers/keycloak'\n *\n * const callbacks = buildKeycloakCallbacks({\n * issuer: process.env.AUTH_KEYCLOAK_ISSUER!,\n * clientId: process.env.AUTH_KEYCLOAK_ID!,\n * clientSecret: process.env.AUTH_KEYCLOAK_SECRET!,\n * defaultRoles: ['default-roles-my-realm'],\n * })\n *\n * export const authConfig = {\n * providers: [Keycloak({ ... })],\n * session: { strategy: 'jwt' },\n * callbacks,\n * }\n * ```\n */\n\nimport {\n parseKeycloakRoles,\n isTokenExpired,\n refreshKeycloakToken,\n} from \"./keycloak\";\nimport type { KeycloakConfig } from \"./keycloak\";\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\nexport interface KeycloakCallbacksConfig {\n /** Keycloak issuer URL */\n issuer: string;\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Realm-specific default roles to filter (e.g. ['default-roles-my-realm']) */\n defaultRoles?: string[];\n /** Enable debug logging (default: NODE_ENV === 'development') */\n debug?: boolean;\n}\n\n/**\n * Extended JWT token shape used by the Keycloak callbacks.\n * These fields are added to the Auth.js JWT token during sign-in and refresh.\n */\nexport interface KeycloakJwtFields {\n id?: string;\n accessToken?: string;\n refreshToken?: string;\n idToken?: string;\n accessTokenExpires?: number;\n roles?: string[];\n error?: string;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH.JS COOKIE CONFIGURATION\n// ═══════════════════════════════════════════════════════════════\n\nexport interface AuthCookiesConfig {\n /** Production cookie domain (e.g. '.digilogiclabs.com'). Omit for default domain. */\n domain?: string;\n /** Include session token cookie config (default: true) */\n sessionToken?: boolean;\n /** Include callback URL cookie config (default: true) */\n callbackUrl?: boolean;\n}\n\n/**\n * Build Auth.js cookie configuration for cross-domain OIDC.\n *\n * Handles the common pattern of configuring cookies to work across\n * www and non-www domains (e.g. digilogiclabs.com and www.digilogiclabs.com).\n *\n * PKCE and state cookies are always included for OAuth security.\n * Session token and callback URL cookies are included by default.\n *\n * @example\n * ```typescript\n * import { buildAuthCookies } from '@digilogiclabs/platform-core'\n *\n * export const authConfig = {\n * cookies: buildAuthCookies({ domain: '.digilogiclabs.com' }),\n * // ...\n * }\n * ```\n */\nexport function buildAuthCookies(config: AuthCookiesConfig = {}) {\n const { domain, sessionToken = true, callbackUrl = true } = config;\n const isProduction = process.env.NODE_ENV === \"production\";\n const cookieDomain = isProduction ? domain : undefined;\n\n const baseOptions = {\n httpOnly: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n secure: isProduction,\n domain: cookieDomain,\n };\n\n const cookies: Record<string, { name: string; options: typeof baseOptions }> =\n {\n pkceCodeVerifier: {\n name: \"authjs.pkce.code_verifier\",\n options: { ...baseOptions },\n },\n state: {\n name: \"authjs.state\",\n options: { ...baseOptions },\n },\n };\n\n if (sessionToken) {\n cookies.sessionToken = {\n name: isProduction\n ? \"__Secure-authjs.session-token\"\n : \"authjs.session-token\",\n options: { ...baseOptions },\n };\n }\n\n if (callbackUrl) {\n cookies.callbackUrl = {\n name: isProduction\n ? \"__Secure-authjs.callback-url\"\n : \"authjs.callback-url\",\n options: { ...baseOptions },\n };\n }\n\n return cookies;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH.JS REDIRECT CALLBACK\n// ═══════════════════════════════════════════════════════════════\n\nexport interface RedirectCallbackConfig {\n /** Allow www variant redirects (e.g. www.example.com ↔ example.com) */\n allowWwwVariant?: boolean;\n}\n\n/**\n * Build a standard Auth.js redirect callback.\n *\n * Handles common redirect patterns:\n * - Relative URLs → prefixed with baseUrl\n * - Same-origin URLs → allowed\n * - www variant URLs → optionally allowed\n * - Everything else → baseUrl\n *\n * @example\n * ```typescript\n * import { buildRedirectCallback } from '@digilogiclabs/platform-core'\n *\n * export const authConfig = {\n * callbacks: {\n * redirect: buildRedirectCallback({ allowWwwVariant: true }),\n * },\n * }\n * ```\n */\nexport function buildRedirectCallback(config: RedirectCallbackConfig = {}) {\n const { allowWwwVariant = false } = config;\n\n return async ({ url, baseUrl }: { url: string; baseUrl: string }) => {\n if (url.startsWith(\"/\")) return `${baseUrl}${url}`;\n try {\n if (new URL(url).origin === baseUrl) return url;\n } catch {\n return baseUrl;\n }\n\n if (allowWwwVariant) {\n try {\n const urlHost = new URL(url).hostname;\n const baseHost = new URL(baseUrl).hostname;\n if (urlHost === `www.${baseHost}` || baseHost === `www.${urlHost}`) {\n return url;\n }\n } catch {\n // Invalid URL, fall through\n }\n }\n\n return baseUrl;\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// CALLBACK BUILDERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Build Auth.js JWT and session callbacks for Keycloak.\n *\n * Returns an object with `jwt` and `session` callbacks that handle:\n * - Storing Keycloak tokens on initial sign-in\n * - Parsing realm roles from the access token\n * - Automatic token refresh when expired\n * - Mapping tokens/roles to the session object\n *\n * The callbacks are Edge-runtime compatible.\n */\nexport function buildKeycloakCallbacks(config: KeycloakCallbacksConfig) {\n const {\n issuer,\n clientId,\n clientSecret,\n defaultRoles = [],\n debug = process.env.NODE_ENV === \"development\",\n } = config;\n\n const kcConfig: KeycloakConfig = { issuer, clientId, clientSecret };\n\n function log(message: string, meta?: Record<string, unknown>) {\n if (debug) {\n console.log(`[Auth] ${message}`, meta ? JSON.stringify(meta) : \"\");\n }\n }\n\n return {\n /**\n * JWT callback — stores Keycloak tokens and handles refresh.\n *\n * Compatible with Auth.js v5 JWT callback signature.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async jwt({\n token,\n user,\n account,\n }: {\n token: any;\n user?: any;\n account?: any;\n }) {\n // Initial sign-in — store Keycloak tokens\n if (user) {\n token.id = token.sub ?? (user.id as string);\n }\n\n if (account?.provider === \"keycloak\") {\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.idToken = account.id_token;\n token.roles = parseKeycloakRoles(\n account.access_token as string | undefined,\n defaultRoles,\n );\n token.accessTokenExpires = account.expires_at\n ? (account.expires_at as number) * 1000\n : Date.now() + 300_000;\n return token;\n }\n\n // Return token if still valid\n if (!isTokenExpired(token.accessTokenExpires as number | undefined)) {\n return token;\n }\n\n // Attempt token refresh\n if (token.refreshToken) {\n log(\"Token expired, attempting refresh...\");\n\n const result = await refreshKeycloakToken(\n kcConfig,\n token.refreshToken as string,\n defaultRoles,\n );\n\n if (result.ok) {\n token.accessToken = result.tokens.accessToken;\n token.idToken = result.tokens.idToken ?? token.idToken;\n token.refreshToken = result.tokens.refreshToken ?? token.refreshToken;\n token.accessTokenExpires = result.tokens.expiresAt;\n token.roles = result.tokens.roles;\n delete token.error;\n log(\"Token refreshed OK\");\n return token;\n }\n\n log(\"Token refresh failed\", { error: result.error });\n return { ...token, error: \"RefreshTokenError\" };\n }\n\n log(\"Token expired but no refresh token available\");\n return { ...token, error: \"RefreshTokenError\" };\n },\n\n /**\n * Session callback — maps JWT fields to the session object.\n *\n * Compatible with Auth.js v5 session callback signature.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async session({ session, token }: { session: any; token: any }) {\n const user = session.user as Record<string, unknown> | undefined;\n if (user) {\n user.id = (token.id as string) || (token.sub as string);\n user.roles = (token.roles as string[]) || [];\n }\n session.idToken = token.idToken;\n session.accessToken = token.accessToken;\n if (token.error) {\n session.error = token.error;\n }\n return session;\n },\n };\n}\n","/**\n * API Security Types & Helpers\n *\n * Framework-agnostic types and utilities for building composable\n * API security wrappers. These define the shared contract that\n * framework-specific implementations (Next.js, Express, .NET) follow.\n *\n * The actual wrappers (withPublicApi, withAdminApi, etc.) live in each\n * app because they depend on framework-specific request/response types.\n * This module provides the shared types and logic they all use.\n */\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH METHOD\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * How a request was authenticated.\n * Used by audit logging and authorization decisions.\n */\nexport type AuthMethod =\n | \"session\" // Auth.js / NextAuth session (JWT verified)\n | \"bearer_token\" // Bearer token in Authorization header\n | \"api_key\" // API key in header (e.g. X-Api-Key)\n | \"webhook_signature\" // HMAC signature verification (Stripe, etc.)\n | \"cron_secret\" // Scheduled job secret\n | \"none\"; // Unauthenticated (public routes)\n\n// ═══════════════════════════════════════════════════════════════\n// SECURITY CONFIGURATION\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Audit configuration for a secured route.\n * Tells the security wrapper what to log.\n */\nexport interface RouteAuditConfig {\n /** The action being performed (e.g. 'game.server.create') */\n action: string;\n /** The resource type being acted on (e.g. 'game_server') */\n resourceType: string;\n}\n\n/**\n * Rate limit preset configuration.\n * Matches the structure used by all apps.\n */\nexport interface RateLimitPreset {\n /** Maximum requests allowed in the window */\n limit: number;\n /** Window duration in seconds */\n windowSeconds: number;\n /** Higher limit for authenticated users */\n authenticatedLimit?: number;\n /** Block duration when limit exceeded (seconds) */\n blockDurationSeconds?: number;\n}\n\n/**\n * Standard rate limit presets shared across all apps.\n *\n * Apps can extend with their own domain-specific presets:\n * ```typescript\n * const APP_RATE_LIMITS = {\n * ...StandardRateLimitPresets,\n * gameServerCreate: { limit: 5, windowSeconds: 3600, blockDurationSeconds: 3600 },\n * }\n * ```\n */\nexport const StandardRateLimitPresets = {\n /** General API: 100/min, 200/min authenticated */\n apiGeneral: {\n limit: 100,\n windowSeconds: 60,\n authenticatedLimit: 200,\n },\n /** Admin operations: 100/min (admins are trusted) */\n adminAction: {\n limit: 100,\n windowSeconds: 60,\n },\n /** AI/expensive operations: 20/hour, 50/hour authenticated */\n aiRequest: {\n limit: 20,\n windowSeconds: 3600,\n authenticatedLimit: 50,\n },\n /** Auth attempts: 5/15min with 15min block */\n authAttempt: {\n limit: 5,\n windowSeconds: 900,\n blockDurationSeconds: 900,\n },\n /** Contact/public forms: 10/hour */\n publicForm: {\n limit: 10,\n windowSeconds: 3600,\n blockDurationSeconds: 1800,\n },\n /** Checkout/billing: 10/hour with 1hr block */\n checkout: {\n limit: 10,\n windowSeconds: 3600,\n blockDurationSeconds: 3600,\n },\n} as const satisfies Record<string, RateLimitPreset>;\n\n/**\n * Configuration for a secured API handler.\n *\n * This is the framework-agnostic config shape. Each framework's\n * wrapper (withPublicApi, withAdminApi, etc.) maps to this structure.\n */\nexport interface ApiSecurityConfig {\n /** Whether authentication is required */\n requireAuth: boolean;\n /** Whether admin role is required */\n requireAdmin: boolean;\n /** Required roles (user must have at least one) */\n requireRoles?: string[];\n /** Allow legacy bearer token as alternative to session auth */\n allowBearerToken?: boolean;\n /** Rate limit preset name or custom config */\n rateLimit?: string | RateLimitPreset;\n /** Audit logging config */\n audit?: RouteAuditConfig;\n /** Human-readable operation name for logging */\n operation?: string;\n /** Skip rate limiting */\n skipRateLimit?: boolean;\n /** Skip audit logging */\n skipAudit?: boolean;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// SECURITY CONTEXT\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Minimal session shape that all auth systems provide.\n * Maps to Auth.js Session, .NET ClaimsPrincipal, etc.\n */\nexport interface SecuritySession {\n user: {\n id: string;\n email?: string | null;\n name?: string | null;\n roles?: string[];\n };\n}\n\n/**\n * Context available to secured route handlers after all security\n * checks have passed. Framework wrappers extend this with their\n * own fields (e.g. NextRequest, validated body, etc.).\n */\nexport interface ApiSecurityContext {\n /** Authenticated session (null for public routes or token auth) */\n session: SecuritySession | null;\n /** How the request was authenticated */\n authMethod: AuthMethod;\n /** Whether the user has admin privileges */\n isAdmin: boolean;\n /** Request correlation ID */\n requestId: string;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// SECURITY PIPELINE HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Determine the appropriate rate limit key for a request.\n *\n * Priority: user ID > email > IP address.\n * Authenticated users get their own bucket (and potentially higher limits).\n *\n * @param session - Current session (if any)\n * @param clientIp - Client IP address\n * @returns { identifier, isAuthenticated }\n */\nexport function resolveRateLimitIdentifier(\n session: SecuritySession | null,\n clientIp: string,\n): { identifier: string; isAuthenticated: boolean } {\n if (session?.user?.id) {\n return { identifier: `user:${session.user.id}`, isAuthenticated: true };\n }\n if (session?.user?.email) {\n return {\n identifier: `email:${session.user.email}`,\n isAuthenticated: true,\n };\n }\n return { identifier: `ip:${clientIp}`, isAuthenticated: false };\n}\n\n/**\n * Extract the client IP from standard proxy headers.\n *\n * Checks (in order): CF-Connecting-IP, X-Real-IP, X-Forwarded-For.\n * Use this to ensure consistent IP extraction across all apps.\n *\n * @param getHeader - Header getter function\n * @returns Client IP or 'unknown'\n */\nexport function extractClientIp(\n getHeader: (name: string) => string | null | undefined,\n): string {\n return (\n getHeader(\"cf-connecting-ip\") ||\n getHeader(\"x-real-ip\") ||\n getHeader(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ||\n \"unknown\"\n );\n}\n\n/**\n * Build the standard rate limit response headers.\n *\n * @returns Headers object with X-RateLimit-* headers\n */\nexport function buildRateLimitHeaders(\n limit: number,\n remaining: number,\n resetAtMs: number,\n): Record<string, string> {\n return {\n \"X-RateLimit-Limit\": String(limit),\n \"X-RateLimit-Remaining\": String(Math.max(0, remaining)),\n \"X-RateLimit-Reset\": String(Math.ceil(resetAtMs / 1000)),\n };\n}\n\n/**\n * Build a standard error response body.\n * Ensures consistent error shape across all apps.\n */\nexport function buildErrorBody(\n error: string,\n extra?: Record<string, unknown>,\n): Record<string, unknown> {\n return { error, ...extra };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// WRAPPER PRESETS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Pre-built security configurations for common route types.\n *\n * Use these as the base for framework-specific wrappers:\n * ```typescript\n * // In your app's api-security.ts\n * export function withPublicApi(config, handler) {\n * return createSecureHandler({\n * ...WrapperPresets.public,\n * ...config,\n * }, handler)\n * }\n * ```\n */\nexport const WrapperPresets = {\n /** Public route: no auth, rate limited */\n public: {\n requireAuth: false,\n requireAdmin: false,\n rateLimit: \"apiGeneral\",\n },\n /** Authenticated route: requires session */\n authenticated: {\n requireAuth: true,\n requireAdmin: false,\n rateLimit: \"apiGeneral\",\n },\n /** Admin route: requires session with admin role */\n admin: {\n requireAuth: true,\n requireAdmin: true,\n rateLimit: \"adminAction\",\n },\n /** Legacy admin: accepts session OR bearer token */\n legacyAdmin: {\n requireAuth: true,\n requireAdmin: true,\n allowBearerToken: true,\n rateLimit: \"adminAction\",\n },\n /** AI/expensive: requires auth, strict rate limit */\n ai: {\n requireAuth: true,\n requireAdmin: false,\n rateLimit: \"aiRequest\",\n },\n /** Cron: no rate limit, admin-level access */\n cron: {\n requireAuth: true,\n requireAdmin: false,\n skipRateLimit: true,\n skipAudit: false,\n },\n} as const satisfies Record<string, Partial<ApiSecurityConfig>>;\n","/**\n * Common Validation Schemas\n *\n * Reusable Zod schemas for request validation across all apps.\n * These are the building blocks — apps compose them into\n * route-specific schemas for their own domain logic.\n *\n * Requires `zod` as a peer dependency (already in platform-core).\n */\n\nimport { z } from \"zod\";\nimport {\n HTML_TAG_PATTERN,\n URL_PROTOCOL_PATTERN,\n URL_DOMAIN_PATTERN,\n} from \"../security\";\n\n// ═══════════════════════════════════════════════════════════════\n// PRIMITIVE SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/** Validated, normalized email address */\nexport const EmailSchema = z\n .string()\n .trim()\n .toLowerCase()\n .email(\"Invalid email address\");\n\n/** Password with minimum security requirements */\nexport const PasswordSchema = z\n .string()\n .min(8, \"Password must be at least 8 characters\")\n .max(100, \"Password must be less than 100 characters\");\n\n/** URL-safe slug (lowercase alphanumeric + hyphens) */\nexport const SlugSchema = z\n .string()\n .min(1, \"Slug is required\")\n .max(100, \"Slug must be less than 100 characters\")\n .regex(\n /^[a-z0-9-]+$/,\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n\n/** Phone number (international format, flexible) */\nexport const PhoneSchema = z\n .string()\n .regex(/^[\\d\\s()+.\\-]{7,20}$/, \"Invalid phone number format\");\n\n/** Human name (letters, spaces, hyphens, apostrophes) */\nexport const PersonNameSchema = z\n .string()\n .min(2, \"Name must be at least 2 characters\")\n .max(100, \"Name must be less than 100 characters\")\n .regex(\n /^[a-zA-Z\\s\\-']+$/,\n \"Name can only contain letters, spaces, hyphens and apostrophes\",\n );\n\n// ═══════════════════════════════════════════════════════════════\n// SANITIZED TEXT SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Create a text schema that blocks HTML tags and links.\n * Use for user-facing text fields (contact forms, comments, etc.)\n *\n * @param options - Customize min/max length and error messages\n *\n * @example\n * ```typescript\n * const MessageSchema = createSafeTextSchema({ min: 10, max: 1000 })\n * const CommentSchema = createSafeTextSchema({ max: 500, allowUrls: true })\n * ```\n */\nexport function createSafeTextSchema(options?: {\n min?: number;\n max?: number;\n allowHtml?: boolean;\n allowUrls?: boolean;\n fieldName?: string;\n}): z.ZodType<string, z.ZodTypeDef, string> {\n const {\n min,\n max,\n allowHtml = false,\n allowUrls = false,\n fieldName = \"Text\",\n } = options ?? {};\n\n let schema: z.ZodString = z.string();\n if (min !== undefined)\n schema = schema.min(min, `${fieldName} must be at least ${min} characters`);\n if (max !== undefined)\n schema = schema.max(\n max,\n `${fieldName} must be less than ${max} characters`,\n );\n\n // Apply refinements for HTML/URL blocking\n if (!allowHtml && !allowUrls) {\n return schema\n .refine((val) => !HTML_TAG_PATTERN.test(val), \"HTML tags are not allowed\")\n .refine(\n (val) => !URL_PROTOCOL_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n )\n .refine(\n (val) => !URL_DOMAIN_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n );\n }\n\n if (!allowHtml) {\n return schema.refine(\n (val) => !HTML_TAG_PATTERN.test(val),\n \"HTML tags are not allowed\",\n );\n }\n\n if (!allowUrls) {\n return schema\n .refine(\n (val) => !URL_PROTOCOL_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n )\n .refine(\n (val) => !URL_DOMAIN_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n );\n }\n\n return schema;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// PAGINATION & QUERY SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/** Standard pagination query parameters */\nexport const PaginationSchema = z.object({\n page: z.coerce.number().int().positive().default(1),\n limit: z.coerce.number().int().positive().max(100).default(20),\n sortBy: z.string().optional(),\n sortOrder: z.enum([\"asc\", \"desc\"]).default(\"desc\"),\n});\n\n/** Date range filter (ISO 8601 datetime strings) */\nexport const DateRangeSchema = z\n .object({\n startDate: z.string().datetime(),\n endDate: z.string().datetime(),\n })\n .refine((data) => new Date(data.startDate) <= new Date(data.endDate), {\n message: \"Start date must be before end date\",\n });\n\n/** Search query with optional filters */\nexport const SearchQuerySchema = z.object({\n query: z.string().min(1).max(200).trim(),\n page: z.coerce.number().int().positive().default(1),\n limit: z.coerce.number().int().positive().max(50).default(10),\n});\n\n// ═══════════════════════════════════════════════════════════════\n// COMPOSED SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/** Login credentials */\nexport const LoginSchema = z.object({\n email: EmailSchema,\n password: PasswordSchema,\n});\n\n/** Signup with optional name */\nexport const SignupSchema = z.object({\n email: EmailSchema,\n password: PasswordSchema,\n name: z.string().min(2).max(100).optional(),\n});\n\n// ═══════════════════════════════════════════════════════════════\n// TYPE EXPORTS\n// ═══════════════════════════════════════════════════════════════\n\nexport type EmailInput = z.infer<typeof EmailSchema>;\nexport type PaginationInput = z.infer<typeof PaginationSchema>;\nexport type DateRangeInput = z.infer<typeof DateRangeSchema>;\nexport type SearchQueryInput = z.infer<typeof SearchQuerySchema>;\nexport type LoginInput = z.infer<typeof LoginSchema>;\nexport type SignupInput = z.infer<typeof SignupSchema>;\n","/**\n * Security Utilities\n *\n * HTML escaping, input detection, sanitization helpers,\n * timing-safe comparison, error sanitization, and request\n * correlation for safe, consistent security across all apps.\n */\n\nimport { timingSafeEqual } from \"crypto\";\n\n/** Regex for protocol-prefixed URLs */\nexport const URL_PROTOCOL_PATTERN = /(https?:\\/\\/|ftp:\\/\\/|www\\.)\\S+/i;\n\n/** Regex for bare domain names with common TLDs */\nexport const URL_DOMAIN_PATTERN =\n /\\b[\\w.-]+\\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\\b/i;\n\n/** Regex for HTML tags */\nexport const HTML_TAG_PATTERN = /<[^>]*>/;\n\n/**\n * Escape HTML special characters to prevent injection.\n * Use when inserting user content into HTML email templates or rendered HTML.\n */\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\n\n/** Check if a string contains protocol-prefixed URLs or bare domains */\nexport function containsUrls(str: string): boolean {\n return URL_PROTOCOL_PATTERN.test(str) || URL_DOMAIN_PATTERN.test(str);\n}\n\n/** Check if a string contains HTML tags */\nexport function containsHtml(str: string): boolean {\n return HTML_TAG_PATTERN.test(str);\n}\n\n/** Strip all HTML tags from a string */\nexport function stripHtml(str: string): string {\n return str.replace(/<[^>]*>/g, \"\");\n}\n\n/**\n * Defang URLs to prevent auto-linking in email clients.\n * Converts https://evil.com → hxxps://evil[.]com\n */\nexport function defangUrl(str: string): string {\n return str\n .replace(/https:\\/\\//gi, \"hxxps://\")\n .replace(/http:\\/\\//gi, \"hxxp://\")\n .replace(/ftp:\\/\\//gi, \"fxp://\")\n .replace(\n /\\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\\b/gi,\n \"[$1]\",\n );\n}\n\n/**\n * Sanitize user content for safe insertion into HTML email templates.\n * Escapes HTML entities AND defangs any URLs that slipped through validation.\n */\nexport function sanitizeForEmail(str: string): string {\n return escapeHtml(str);\n}\n\n// ═══════════════════════════════════════════════════════════════\n// API SECURITY UTILITIES\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Constant-time string comparison to prevent timing side-channel attacks.\n * Use for comparing secrets, tokens, API keys, HMAC signatures, etc.\n *\n * Returns false (not throws) for length mismatches — still constant-time\n * relative to the shorter string to avoid leaking length info.\n *\n * @example\n * ```typescript\n * if (!constantTimeEqual(providedToken, expectedSecret)) {\n * return { status: 401, error: 'Invalid token' }\n * }\n * ```\n */\nexport function constantTimeEqual(a: string, b: string): boolean {\n try {\n const aBuf = Buffer.from(a, \"utf-8\");\n const bBuf = Buffer.from(b, \"utf-8\");\n if (aBuf.length !== bBuf.length) return false;\n return timingSafeEqual(aBuf, bBuf);\n } catch {\n return false;\n }\n}\n\n/**\n * Sanitize an error for client-facing API responses.\n *\n * - 4xx errors: returns the actual message (client needs to know what went wrong)\n * - 5xx errors: returns a generic message (never leak internals to clients)\n * - Development mode: optionally includes stack trace for debugging\n *\n * @example\n * ```typescript\n * catch (error) {\n * const { message, code } = sanitizeApiError(error, 500)\n * return Response.json({ error: message, code }, { status: 500 })\n * }\n * ```\n */\nexport function sanitizeApiError(\n error: unknown,\n statusCode: number,\n isDevelopment = false,\n): { message: string; code?: string; stack?: string } {\n // Client errors — safe to expose the message\n if (statusCode >= 400 && statusCode < 500) {\n const message =\n error instanceof Error ? error.message : String(error || \"Bad request\");\n return { message };\n }\n\n // Server errors — generic message to clients, real error in logs only\n const result: { message: string; code: string; stack?: string } = {\n message: \"An internal error occurred. Please try again later.\",\n code: \"INTERNAL_ERROR\",\n };\n\n if (isDevelopment && error instanceof Error) {\n result.stack = error.stack;\n }\n\n return result;\n}\n\n/**\n * Extract a correlation/request ID from standard headers, or generate one.\n *\n * Checks (in order): X-Request-ID, X-Correlation-ID, then falls back to\n * crypto.randomUUID(). Works with any headers-like object (plain object,\n * Headers API, or a getter function).\n *\n * @example\n * ```typescript\n * // With Next.js request\n * const id = getCorrelationId((name) => request.headers.get(name))\n *\n * // With plain object\n * const id = getCorrelationId({ 'x-request-id': 'abc-123' })\n * ```\n */\nexport function getCorrelationId(\n headers:\n | Record<string, string | string[] | undefined>\n | ((name: string) => string | null | undefined),\n): string {\n const get =\n typeof headers === \"function\"\n ? headers\n : (name: string) => {\n const val = headers[name] ?? headers[name.toLowerCase()];\n return Array.isArray(val) ? val[0] : val;\n };\n\n return (\n get(\"x-request-id\") ||\n get(\"X-Request-ID\") ||\n get(\"x-correlation-id\") ||\n get(\"X-Correlation-ID\") ||\n crypto.randomUUID()\n );\n}\n","/**\n * Feature Flag System\n *\n * Generic, type-safe feature flag builder for staged rollout.\n * Each app defines its own flags; this module provides the\n * infrastructure for evaluating them by environment.\n *\n * @example\n * ```typescript\n * // Define your app's flags\n * const flags = createFeatureFlags({\n * STRIPE_PAYMENTS: {\n * development: true,\n * staging: true,\n * production: { envVar: 'ENABLE_PAYMENTS' },\n * },\n * PUBLIC_SIGNUP: {\n * development: true,\n * staging: false,\n * production: { envVar: 'ENABLE_PUBLIC_SIGNUP' },\n * },\n * AI_FEATURES: {\n * development: true,\n * staging: { envVar: 'ENABLE_AI' },\n * production: false,\n * },\n * })\n *\n * // Evaluate at runtime\n * const resolved = flags.resolve() // reads NODE_ENV + DEPLOYMENT_STAGE\n * if (resolved.STRIPE_PAYMENTS) { ... }\n *\n * // Check a single flag\n * if (flags.isEnabled('AI_FEATURES')) { ... }\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\nexport type DeploymentStage =\n | \"development\"\n | \"staging\"\n | \"preview\"\n | \"production\";\n\n/**\n * How a flag is resolved in a given environment.\n * - `true` / `false` — hardcoded on/off\n * - `{ envVar: string }` — read from environment variable (truthy = \"true\")\n * - `{ envVar: string, default: boolean }` — with fallback\n */\nexport type FlagValue = boolean | { envVar: string; default?: boolean };\n\n/**\n * Flag definition across deployment stages.\n */\nexport interface FlagDefinition {\n development: FlagValue;\n staging: FlagValue;\n production: FlagValue;\n}\n\n/**\n * Map of flag name to definition.\n */\nexport type FlagDefinitions<T extends string = string> = Record<\n T,\n FlagDefinition\n>;\n\n/**\n * Resolved flags — all booleans.\n */\nexport type ResolvedFlags<T extends string = string> = Record<T, boolean>;\n\n/**\n * Allowlist configuration for tester-gated access.\n */\nexport interface AllowlistConfig {\n /** Environment variable containing comma-separated emails */\n envVar?: string;\n /** Hardcoded fallback emails */\n fallback?: string[];\n}\n\n// ═══════════════════════════════════════════════════════════════\n// RESOLUTION LOGIC\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Detect the current deployment stage from environment variables.\n */\nexport function detectStage(): DeploymentStage {\n const stage = process.env.DEPLOYMENT_STAGE;\n if (stage === \"staging\" || stage === \"preview\") return stage;\n if (process.env.NODE_ENV === \"production\") return \"production\";\n return \"development\";\n}\n\n/**\n * Resolve a single flag value.\n */\nfunction resolveFlagValue(value: FlagValue): boolean {\n if (typeof value === \"boolean\") return value;\n const envValue = process.env[value.envVar];\n if (envValue === undefined || envValue === \"\") {\n return value.default ?? false;\n }\n return envValue === \"true\" || envValue === \"1\";\n}\n\n// ═══════════════════════════════════════════════════════════════\n// PUBLIC API\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Create a type-safe feature flag system.\n *\n * @param definitions - Flag definitions per environment\n * @returns Feature flag accessor with resolve() and isEnabled()\n */\nexport function createFeatureFlags<T extends string>(\n definitions: FlagDefinitions<T>,\n) {\n return {\n /**\n * Resolve all flags for the current environment.\n * Call this once at startup or per-request for dynamic flags.\n */\n resolve(stage?: DeploymentStage): ResolvedFlags<T> {\n const currentStage = stage ?? detectStage();\n const resolved = {} as ResolvedFlags<T>;\n\n for (const [key, def] of Object.entries(definitions) as [\n T,\n FlagDefinition,\n ][]) {\n // Preview uses staging config\n const stageKey = currentStage === \"preview\" ? \"staging\" : currentStage;\n resolved[key] = resolveFlagValue(def[stageKey]);\n }\n\n return resolved;\n },\n\n /**\n * Check if a single flag is enabled.\n */\n isEnabled(flag: T, stage?: DeploymentStage): boolean {\n const currentStage = stage ?? detectStage();\n const def = definitions[flag];\n const stageKey = currentStage === \"preview\" ? \"staging\" : currentStage;\n return resolveFlagValue(def[stageKey]);\n },\n\n /**\n * Get the flag definitions (for introspection/admin UI).\n */\n definitions,\n };\n}\n\n/**\n * Build an allowlist from environment variable + fallback emails.\n *\n * @example\n * ```typescript\n * const testers = buildAllowlist({\n * envVar: 'ADMIN_EMAILS',\n * fallback: ['admin@example.com'],\n * })\n * if (testers.includes(userEmail)) { ... }\n * ```\n */\nexport function buildAllowlist(config: AllowlistConfig): string[] {\n const fromEnv = config.envVar\n ? (process.env[config.envVar]\n ?.split(\",\")\n .map((e) => e.trim().toLowerCase())\n .filter(Boolean) ?? [])\n : [];\n\n const fallback = config.fallback?.map((e) => e.toLowerCase()) ?? [];\n\n // Deduplicate\n return [...new Set([...fromEnv, ...fallback])];\n}\n\n/**\n * Check if an email is in the allowlist (case-insensitive).\n */\nexport function isAllowlisted(email: string, allowlist: string[]): boolean {\n return allowlist.includes(email.toLowerCase());\n}\n","/**\n * Standalone Rate Limiter\n *\n * Framework-agnostic sliding window rate limiter that works with\n * any storage backend (Redis, memory, etc.). Apps provide a storage\n * adapter; this module handles the algorithm and types.\n *\n * @example\n * ```typescript\n * import {\n * checkRateLimit,\n * createMemoryRateLimitStore,\n * CommonRateLimits,\n * } from '@digilogiclabs/platform-core'\n *\n * const store = createMemoryRateLimitStore()\n *\n * const result = await checkRateLimit('api-call', 'user:123', CommonRateLimits.apiGeneral, { store })\n * if (!result.allowed) {\n * // Return 429 with result.retryAfterSeconds\n * }\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\n/** Configuration for a rate limit rule */\nexport interface RateLimitRule {\n /** Maximum requests allowed in the window */\n limit: number;\n /** Time window in seconds */\n windowSeconds: number;\n /** Different limit for authenticated users (optional) */\n authenticatedLimit?: number;\n /** Block duration in seconds when limit is exceeded (optional) */\n blockDurationSeconds?: number;\n}\n\n/** Result of a rate limit check */\nexport interface RateLimitCheckResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in the current window */\n remaining: number;\n /** Unix timestamp (ms) when the window resets */\n resetAt: number;\n /** Current request count in the window */\n current: number;\n /** The limit that was applied */\n limit: number;\n /** Seconds until retry is allowed (for 429 Retry-After header) */\n retryAfterSeconds: number;\n}\n\n/**\n * Storage backend for rate limiting.\n *\n * Implementations should support sorted-set-like semantics for\n * accurate sliding window rate limiting.\n */\nexport interface RateLimitStore {\n /**\n * Add an entry to the sliding window and return the current count.\n * Should atomically: remove entries older than windowStart,\n * add the new entry, and return the total count.\n */\n increment(\n key: string,\n windowMs: number,\n now: number,\n ): Promise<{ count: number }>;\n\n /** Check if a key is blocked and return remaining TTL */\n isBlocked(key: string): Promise<{ blocked: boolean; ttlMs: number }>;\n\n /** Set a temporary block on a key */\n setBlock(key: string, durationSeconds: number): Promise<void>;\n\n /** Remove all entries for a key (for reset/testing) */\n reset(key: string): Promise<void>;\n}\n\n/** Options for checkRateLimit */\nexport interface RateLimitOptions {\n /** Storage backend (defaults to in-memory if not provided) */\n store?: RateLimitStore;\n /** Whether the user is authenticated (uses authenticatedLimit if available) */\n isAuthenticated?: boolean;\n /** Logger for warnings/errors (optional) */\n logger?: {\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// COMMON RATE LIMIT PRESETS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Common rate limit rules for typical operations.\n * Apps can extend with domain-specific presets.\n *\n * @example\n * ```typescript\n * const APP_LIMITS = {\n * ...CommonRateLimits,\n * serverCreate: { limit: 5, windowSeconds: 3600, blockDurationSeconds: 3600 },\n * }\n * ```\n */\nexport const CommonRateLimits = {\n /** General API: 100/min, 200/min authenticated */\n apiGeneral: {\n limit: 100,\n windowSeconds: 60,\n authenticatedLimit: 200,\n },\n /** Admin actions: 100/min */\n adminAction: {\n limit: 100,\n windowSeconds: 60,\n },\n /** Auth attempts: 10/15min with 30min block */\n authAttempt: {\n limit: 10,\n windowSeconds: 900,\n blockDurationSeconds: 1800,\n },\n /** AI/expensive requests: 20/hour, 50/hour authenticated */\n aiRequest: {\n limit: 20,\n windowSeconds: 3600,\n authenticatedLimit: 50,\n },\n /** Public form submissions: 5/hour with 1hr block */\n publicForm: {\n limit: 5,\n windowSeconds: 3600,\n blockDurationSeconds: 3600,\n },\n /** Checkout/billing: 10/hour with 1hr block */\n checkout: {\n limit: 10,\n windowSeconds: 3600,\n blockDurationSeconds: 3600,\n },\n} as const satisfies Record<string, RateLimitRule>;\n\n// ═══════════════════════════════════════════════════════════════\n// IN-MEMORY STORE (for testing and fallback)\n// ═══════════════════════════════════════════════════════════════\n\ninterface WindowEntry {\n timestamps: number[];\n expiresAt: number;\n}\n\n/**\n * In-memory rate limit store for testing and graceful degradation.\n * Not suitable for multi-process or distributed environments.\n */\nexport function createMemoryRateLimitStore(): RateLimitStore {\n const windows = new Map<string, WindowEntry>();\n const blocks = new Map<string, number>(); // key -> expiry timestamp\n\n // Periodic cleanup (every 60s)\n const cleanupInterval = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of windows) {\n if (entry.expiresAt < now) windows.delete(key);\n }\n for (const [key, expiry] of blocks) {\n if (expiry < now) blocks.delete(key);\n }\n }, 60 * 1000);\n\n // Allow GC if store is abandoned\n if (cleanupInterval.unref) {\n cleanupInterval.unref();\n }\n\n return {\n async increment(\n key: string,\n windowMs: number,\n now: number,\n ): Promise<{ count: number }> {\n const windowStart = now - windowMs;\n\n let entry = windows.get(key);\n if (!entry) {\n entry = { timestamps: [], expiresAt: now + windowMs + 60000 };\n windows.set(key, entry);\n }\n\n // Remove old entries\n entry.timestamps = entry.timestamps.filter((t) => t > windowStart);\n\n // Add new entry\n entry.timestamps.push(now);\n entry.expiresAt = now + windowMs + 60000;\n\n return { count: entry.timestamps.length };\n },\n\n async isBlocked(key: string): Promise<{ blocked: boolean; ttlMs: number }> {\n const expiry = blocks.get(key);\n if (!expiry || expiry < Date.now()) {\n blocks.delete(key);\n return { blocked: false, ttlMs: 0 };\n }\n return { blocked: true, ttlMs: expiry - Date.now() };\n },\n\n async setBlock(key: string, durationSeconds: number): Promise<void> {\n blocks.set(key, Date.now() + durationSeconds * 1000);\n },\n\n async reset(key: string): Promise<void> {\n windows.delete(key);\n blocks.delete(`block:${key}`);\n blocks.delete(key);\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// CORE RATE LIMIT FUNCTION\n// ═══════════════════════════════════════════════════════════════\n\n/** Default in-memory store (lazily created) */\nlet defaultStore: RateLimitStore | undefined;\n\nfunction getDefaultStore(): RateLimitStore {\n if (!defaultStore) {\n defaultStore = createMemoryRateLimitStore();\n }\n return defaultStore;\n}\n\n/**\n * Check rate limit for an operation.\n *\n * @param operation - Name of the operation (e.g., 'server-create', 'api-call')\n * @param identifier - Who is making the request (e.g., 'user:123', 'ip:1.2.3.4')\n * @param rule - Rate limit configuration\n * @param options - Storage, auth status, logger\n * @returns Rate limit check result\n */\nexport async function checkRateLimit(\n operation: string,\n identifier: string,\n rule: RateLimitRule,\n options: RateLimitOptions = {},\n): Promise<RateLimitCheckResult> {\n const store = options.store ?? getDefaultStore();\n const limit =\n options.isAuthenticated && rule.authenticatedLimit\n ? rule.authenticatedLimit\n : rule.limit;\n\n const now = Date.now();\n const windowMs = rule.windowSeconds * 1000;\n const resetAt = now + windowMs;\n\n const key = `ratelimit:${operation}:${identifier}`;\n const blockKey = `ratelimit:block:${operation}:${identifier}`;\n\n try {\n // Check if currently blocked\n if (rule.blockDurationSeconds) {\n const blockStatus = await store.isBlocked(blockKey);\n if (blockStatus.blocked) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: now + blockStatus.ttlMs,\n current: limit + 1,\n limit,\n retryAfterSeconds: Math.ceil(blockStatus.ttlMs / 1000),\n };\n }\n }\n\n // Sliding window check + increment\n const { count } = await store.increment(key, windowMs, now);\n\n if (count > limit) {\n // Exceeded — the increment already added, so count includes this request\n options.logger?.warn(\"Rate limit exceeded\", {\n operation,\n identifier,\n current: count,\n limit,\n });\n\n // Set block if configured\n if (rule.blockDurationSeconds) {\n await store.setBlock(blockKey, rule.blockDurationSeconds);\n }\n\n return {\n allowed: false,\n remaining: 0,\n resetAt,\n current: count,\n limit,\n retryAfterSeconds: Math.ceil(windowMs / 1000),\n };\n }\n\n return {\n allowed: true,\n remaining: limit - count,\n resetAt,\n current: count,\n limit,\n retryAfterSeconds: 0,\n };\n } catch (error) {\n // On storage error, allow the request (fail-open)\n options.logger?.error(\"Rate limit check failed, allowing request\", {\n error: error instanceof Error ? error.message : String(error),\n operation,\n identifier,\n });\n\n return {\n allowed: true,\n remaining: limit,\n resetAt,\n current: 0,\n limit,\n retryAfterSeconds: 0,\n };\n }\n}\n\n/**\n * Get current rate limit status without incrementing the counter.\n */\nexport async function getRateLimitStatus(\n operation: string,\n identifier: string,\n rule: RateLimitRule,\n store?: RateLimitStore,\n): Promise<RateLimitCheckResult | null> {\n const s = store ?? getDefaultStore();\n const key = `ratelimit:${operation}:${identifier}`;\n const now = Date.now();\n const windowMs = rule.windowSeconds * 1000;\n\n try {\n // Use increment with a past timestamp to just clean + count\n // without adding a real entry — actually we need a read-only method\n // For memory store, this works. For Redis, apps should implement peek.\n const { count } = await s.increment(key, windowMs, now);\n // Remove the entry we just added (hacky but works for status check)\n // Better approach: just expose count. For now, return estimate.\n return {\n allowed: count <= rule.limit,\n remaining: Math.max(0, rule.limit - count),\n resetAt: now + windowMs,\n current: count,\n limit: rule.limit,\n retryAfterSeconds: count > rule.limit ? Math.ceil(windowMs / 1000) : 0,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Reset rate limit for an identifier (for admin/testing).\n */\nexport async function resetRateLimitForKey(\n operation: string,\n identifier: string,\n store?: RateLimitStore,\n): Promise<void> {\n const s = store ?? getDefaultStore();\n const key = `ratelimit:${operation}:${identifier}`;\n const blockKey = `ratelimit:block:${operation}:${identifier}`;\n await s.reset(key);\n await s.reset(blockKey);\n}\n\n// ═══════════════════════════════════════════════════════════════\n// HEADER HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Build standard rate limit headers for HTTP responses.\n */\nexport function buildRateLimitResponseHeaders(\n result: RateLimitCheckResult,\n): Record<string, string> {\n const headers: Record<string, string> = {\n \"X-RateLimit-Limit\": String(result.limit),\n \"X-RateLimit-Remaining\": String(result.remaining),\n \"X-RateLimit-Reset\": String(Math.ceil(result.resetAt / 1000)),\n };\n\n if (!result.allowed) {\n headers[\"Retry-After\"] = String(result.retryAfterSeconds);\n }\n\n return headers;\n}\n\n/**\n * Resolve a rate limit identifier from a request-like context.\n * Prefers user ID > email > IP for most accurate limiting.\n */\nexport function resolveIdentifier(\n session: { user?: { id?: string; email?: string } } | null | undefined,\n clientIp?: string,\n): { identifier: string; isAuthenticated: boolean } {\n if (session?.user?.id) {\n return { identifier: `user:${session.user.id}`, isAuthenticated: true };\n }\n if (session?.user?.email) {\n return {\n identifier: `email:${session.user.email}`,\n isAuthenticated: true,\n };\n }\n return { identifier: `ip:${clientIp ?? \"unknown\"}`, isAuthenticated: false };\n}\n","/**\n * Redis Rate Limit Store\n *\n * Production-ready `RateLimitStore` implementation using Redis sorted sets\n * for accurate sliding window rate limiting. Works with ioredis or any\n * Redis client that supports the required commands.\n *\n * @example\n * ```typescript\n * import Redis from 'ioredis'\n * import {\n * createRedisRateLimitStore,\n * checkRateLimit,\n * CommonRateLimits,\n * } from '@digilogiclabs/platform-core/auth'\n *\n * const redis = new Redis(process.env.REDIS_URL)\n * const store = createRedisRateLimitStore(redis, { keyPrefix: 'myapp:' })\n *\n * const result = await checkRateLimit('api-call', 'user:123', CommonRateLimits.apiGeneral, { store })\n * ```\n */\n\nimport type { RateLimitStore } from \"./rate-limiter\";\n\n/**\n * Minimal Redis client interface.\n *\n * Compatible with ioredis, node-redis v4, and @upstash/redis.\n * Only the commands used by the rate limiter are required.\n */\nexport interface RedisRateLimitClient {\n zremrangebyscore(\n key: string,\n min: number | string,\n max: number | string,\n ): Promise<number>;\n zadd(key: string, score: number, member: string): Promise<number>;\n zcard(key: string): Promise<number>;\n expire(key: string, seconds: number): Promise<number>;\n get(key: string): Promise<string | null>;\n ttl(key: string): Promise<number>;\n setex(key: string, seconds: number, value: string): Promise<string>;\n del(...keys: string[]): Promise<number>;\n}\n\nexport interface RedisRateLimitStoreOptions {\n /** Prefix for all Redis keys (e.g., 'myapp:') */\n keyPrefix?: string;\n}\n\n/**\n * Create a Redis-backed rate limit store using sorted sets.\n *\n * Uses the sliding window algorithm:\n * - Each request adds a timestamped entry to a sorted set\n * - Old entries (outside the window) are pruned on each check\n * - The set cardinality gives the request count\n *\n * Blocks use simple key-value with TTL.\n */\nexport function createRedisRateLimitStore(\n redis: RedisRateLimitClient,\n options: RedisRateLimitStoreOptions = {},\n): RateLimitStore {\n const prefix = options.keyPrefix ?? \"\";\n\n return {\n async increment(\n key: string,\n windowMs: number,\n now: number,\n ): Promise<{ count: number }> {\n const fullKey = `${prefix}${key}`;\n const windowStart = now - windowMs;\n const windowSeconds = Math.ceil(windowMs / 1000) + 60; // buffer\n\n // Remove expired entries\n await redis.zremrangebyscore(fullKey, 0, windowStart);\n\n // Count current entries before adding\n const current = await redis.zcard(fullKey);\n\n // Add new entry with unique member\n const member = `${now}:${Math.random().toString(36).slice(2, 10)}`;\n await redis.zadd(fullKey, now, member);\n\n // Set TTL so Redis auto-cleans abandoned keys\n await redis.expire(fullKey, windowSeconds);\n\n return { count: current + 1 };\n },\n\n async isBlocked(key: string): Promise<{ blocked: boolean; ttlMs: number }> {\n const fullKey = `${prefix}${key}`;\n const value = await redis.get(fullKey);\n\n if (!value) {\n return { blocked: false, ttlMs: 0 };\n }\n\n const ttlSeconds = await redis.ttl(fullKey);\n if (ttlSeconds <= 0) {\n return { blocked: false, ttlMs: 0 };\n }\n\n return { blocked: true, ttlMs: ttlSeconds * 1000 };\n },\n\n async setBlock(key: string, durationSeconds: number): Promise<void> {\n const fullKey = `${prefix}${key}`;\n await redis.setex(fullKey, durationSeconds, \"1\");\n },\n\n async reset(key: string): Promise<void> {\n const fullKey = `${prefix}${key}`;\n await redis.del(fullKey);\n },\n };\n}\n","/**\n * Audit Logging System\n *\n * Framework-agnostic audit logging for security-sensitive operations.\n * Provides structured audit events with actor, action, resource, and\n * outcome tracking. Apps provide their own persistence (Redis, DB, etc.)\n * via a simple callback; this module handles the event model and helpers.\n *\n * @example\n * ```typescript\n * import { createAuditLogger, StandardAuditActions } from '@digilogiclabs/platform-core'\n *\n * const audit = createAuditLogger({\n * persist: async (record) => {\n * await redis.setex(`audit:${record.id}`, 90 * 86400, JSON.stringify(record))\n * },\n * })\n *\n * await audit.log({\n * actor: { id: userId, email, type: 'user' },\n * action: StandardAuditActions.LOGIN_SUCCESS,\n * outcome: 'success',\n * })\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\n/** Who performed the action */\nexport interface OpsAuditActor {\n id: string;\n email?: string;\n type: \"user\" | \"admin\" | \"system\" | \"api_key\" | \"anonymous\";\n sessionId?: string;\n}\n\n/** What resource was affected */\nexport interface OpsAuditResource {\n type: string;\n id: string;\n name?: string;\n}\n\n/** The audit event to log */\nexport interface OpsAuditEvent {\n actor: OpsAuditActor;\n action: string;\n resource?: OpsAuditResource;\n outcome: \"success\" | \"failure\" | \"blocked\" | \"pending\";\n metadata?: Record<string, unknown>;\n reason?: string;\n}\n\n/** Complete audit record with context */\nexport interface OpsAuditRecord extends OpsAuditEvent {\n id: string;\n timestamp: string;\n ip?: string;\n userAgent?: string;\n requestId?: string;\n duration?: number;\n}\n\n/** Options for creating an audit logger */\nexport interface OpsAuditLoggerOptions {\n /**\n * Persist an audit record to storage (Redis, DB, etc.).\n * Called after console logging. Errors are caught and logged,\n * never thrown — audit failures must not break operations.\n */\n persist?: (record: OpsAuditRecord) => Promise<void>;\n\n /**\n * Console logger for structured output.\n * Defaults to console.log/console.warn.\n */\n logger?: {\n info: (msg: string, meta?: Record<string, unknown>) => void;\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n };\n}\n\n/** Request-like object for extracting IP, user agent, request ID */\nexport interface AuditRequest {\n headers: {\n get(name: string): string | null;\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// STANDARD AUDIT ACTIONS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Standard audit action constants.\n * Apps should extend with domain-specific actions:\n *\n * @example\n * ```typescript\n * const AppAuditActions = {\n * ...StandardAuditActions,\n * SERVER_CREATE: 'game.server.create',\n * SERVER_DELETE: 'game.server.delete',\n * } as const\n * ```\n */\nexport const StandardAuditActions = {\n // Authentication\n LOGIN_SUCCESS: \"auth.login.success\",\n LOGIN_FAILURE: \"auth.login.failure\",\n LOGOUT: \"auth.logout\",\n SESSION_REFRESH: \"auth.session.refresh\",\n PASSWORD_CHANGE: \"auth.password.change\",\n PASSWORD_RESET: \"auth.password.reset\",\n\n // Billing\n CHECKOUT_START: \"billing.checkout.start\",\n CHECKOUT_COMPLETE: \"billing.checkout.complete\",\n SUBSCRIPTION_CREATE: \"billing.subscription.create\",\n SUBSCRIPTION_CANCEL: \"billing.subscription.cancel\",\n SUBSCRIPTION_UPDATE: \"billing.subscription.update\",\n PAYMENT_FAILED: \"billing.payment.failed\",\n\n // Admin\n ADMIN_LOGIN: \"admin.login\",\n ADMIN_USER_VIEW: \"admin.user.view\",\n ADMIN_USER_UPDATE: \"admin.user.update\",\n ADMIN_CONFIG_CHANGE: \"admin.config.change\",\n\n // Security Events\n RATE_LIMIT_EXCEEDED: \"security.rate_limit.exceeded\",\n INVALID_INPUT: \"security.input.invalid\",\n UNAUTHORIZED_ACCESS: \"security.access.unauthorized\",\n OWNERSHIP_VIOLATION: \"security.ownership.violation\",\n WEBHOOK_SIGNATURE_INVALID: \"security.webhook.signature_invalid\",\n\n // Data\n DATA_EXPORT: \"data.export\",\n DATA_DELETE: \"data.delete\",\n DATA_UPDATE: \"data.update\",\n} as const;\n\nexport type StandardAuditActionType =\n (typeof StandardAuditActions)[keyof typeof StandardAuditActions];\n\n// ═══════════════════════════════════════════════════════════════\n// REQUEST CONTEXT HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Extract client IP from request headers.\n * Checks common proxy headers in order of reliability.\n */\nexport function extractAuditIp(request?: AuditRequest): string | undefined {\n if (!request) return undefined;\n\n const headers = [\n \"cf-connecting-ip\", // Cloudflare\n \"x-real-ip\", // Nginx\n \"x-forwarded-for\", // Standard proxy\n \"x-client-ip\", // Apache\n ];\n\n for (const header of headers) {\n const value = request.headers.get(header);\n if (value) {\n return value.split(\",\")[0]?.trim();\n }\n }\n\n return undefined;\n}\n\n/**\n * Extract user agent from request.\n */\nexport function extractAuditUserAgent(\n request?: AuditRequest,\n): string | undefined {\n return request?.headers.get(\"user-agent\") ?? undefined;\n}\n\n/**\n * Extract or generate request ID.\n */\nexport function extractAuditRequestId(request?: AuditRequest): string {\n return request?.headers.get(\"x-request-id\") ?? crypto.randomUUID();\n}\n\n/**\n * Create an AuditActor from a session object.\n * Works with Auth.js/NextAuth session shape.\n */\nexport function createAuditActor(\n session:\n | {\n user?: { id?: string; email?: string | null };\n }\n | null\n | undefined,\n): OpsAuditActor {\n if (!session?.user) {\n return { id: \"anonymous\", type: \"anonymous\" };\n }\n\n return {\n id: session.user.id ?? session.user.email ?? \"unknown\",\n email: session.user.email ?? undefined,\n type: \"user\",\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUDIT LOGGER FACTORY\n// ═══════════════════════════════════════════════════════════════\n\nconst defaultLogger = {\n info: (msg: string, meta?: Record<string, unknown>) =>\n console.log(msg, meta ? JSON.stringify(meta) : \"\"),\n warn: (msg: string, meta?: Record<string, unknown>) =>\n console.warn(msg, meta ? JSON.stringify(meta) : \"\"),\n error: (msg: string, meta?: Record<string, unknown>) =>\n console.error(msg, meta ? JSON.stringify(meta) : \"\"),\n};\n\n/**\n * Create an audit logger instance.\n *\n * @param options - Persistence callback and optional logger\n * @returns Audit logger with log() and createTimedAudit() methods\n */\nexport function createAuditLogger(options: OpsAuditLoggerOptions = {}) {\n const { persist, logger = defaultLogger } = options;\n\n /**\n * Log an audit event.\n */\n async function log(\n event: OpsAuditEvent,\n request?: AuditRequest,\n ): Promise<OpsAuditRecord> {\n const record: OpsAuditRecord = {\n id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n ip: extractAuditIp(request),\n userAgent: extractAuditUserAgent(request),\n requestId: extractAuditRequestId(request),\n ...event,\n };\n\n // Always log to console/structured logger\n const logFn =\n event.outcome === \"failure\" || event.outcome === \"blocked\"\n ? logger.warn\n : logger.info;\n\n logFn(`[AUDIT] ${event.action}`, {\n auditId: record.id,\n actor: record.actor,\n action: record.action,\n resource: record.resource,\n outcome: record.outcome,\n ip: record.ip,\n metadata: record.metadata,\n reason: record.reason,\n });\n\n // Persist if configured\n if (persist) {\n try {\n await persist(record);\n } catch (error) {\n logger.error(\"Failed to persist audit log\", {\n error: error instanceof Error ? error.message : String(error),\n auditId: record.id,\n });\n }\n }\n\n return record;\n }\n\n /**\n * Create a timed audit that tracks operation duration.\n * Call success(), failure(), or blocked() when the operation completes.\n */\n function createTimedAudit(\n event: Omit<OpsAuditEvent, \"outcome\">,\n request?: AuditRequest,\n ): {\n success: (metadata?: Record<string, unknown>) => Promise<OpsAuditRecord>;\n failure: (\n reason: string,\n metadata?: Record<string, unknown>,\n ) => Promise<OpsAuditRecord>;\n blocked: (\n reason: string,\n metadata?: Record<string, unknown>,\n ) => Promise<OpsAuditRecord>;\n } {\n const startTime = Date.now();\n\n return {\n success: async (metadata?: Record<string, unknown>) => {\n return log(\n {\n ...event,\n outcome: \"success\",\n metadata: {\n ...event.metadata,\n ...metadata,\n durationMs: Date.now() - startTime,\n },\n },\n request,\n );\n },\n failure: async (reason: string, metadata?: Record<string, unknown>) => {\n return log(\n {\n ...event,\n outcome: \"failure\",\n reason,\n metadata: {\n ...event.metadata,\n ...metadata,\n durationMs: Date.now() - startTime,\n },\n },\n request,\n );\n },\n blocked: async (reason: string, metadata?: Record<string, unknown>) => {\n return log(\n {\n ...event,\n outcome: \"blocked\",\n reason,\n metadata: {\n ...event.metadata,\n ...metadata,\n durationMs: Date.now() - startTime,\n },\n },\n request,\n );\n },\n };\n }\n\n return { log, createTimedAudit };\n}\n","/**\n * API Utilities\n *\n * Framework-agnostic error codes, error class, response types,\n * and database error mapping for consistent API behavior across apps.\n */\n\n/** Standard API error codes */\nexport const ApiErrorCode = {\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n FORBIDDEN: \"FORBIDDEN\",\n NOT_FOUND: \"NOT_FOUND\",\n CONFLICT: \"CONFLICT\",\n RATE_LIMIT_EXCEEDED: \"RATE_LIMIT_EXCEEDED\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n DATABASE_ERROR: \"DATABASE_ERROR\",\n EXTERNAL_SERVICE_ERROR: \"EXTERNAL_SERVICE_ERROR\",\n CONFIGURATION_ERROR: \"CONFIGURATION_ERROR\",\n} as const;\n\nexport type ApiErrorCodeType = (typeof ApiErrorCode)[keyof typeof ApiErrorCode];\n\n/** Custom API Error class with status code and error code */\nexport class ApiError extends Error {\n public readonly statusCode: number;\n public readonly code: ApiErrorCodeType;\n public readonly details?: unknown;\n\n constructor(\n statusCode: number,\n message: string,\n code: ApiErrorCodeType = ApiErrorCode.INTERNAL_ERROR,\n details?: unknown,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.statusCode = statusCode;\n this.code = code;\n this.details = details;\n }\n}\n\n/** Type guard for ApiError */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\n/** Pre-built error factories */\nexport const CommonApiErrors = {\n unauthorized: (msg = \"Unauthorized\") =>\n new ApiError(401, msg, ApiErrorCode.UNAUTHORIZED),\n forbidden: (msg = \"Forbidden\") =>\n new ApiError(403, msg, ApiErrorCode.FORBIDDEN),\n notFound: (resource = \"Resource\") =>\n new ApiError(404, `${resource} not found`, ApiErrorCode.NOT_FOUND),\n conflict: (msg = \"Resource already exists\") =>\n new ApiError(409, msg, ApiErrorCode.CONFLICT),\n rateLimitExceeded: (msg = \"Rate limit exceeded\") =>\n new ApiError(429, msg, ApiErrorCode.RATE_LIMIT_EXCEEDED),\n validationError: (details?: unknown) =>\n new ApiError(\n 400,\n \"Validation failed\",\n ApiErrorCode.VALIDATION_ERROR,\n details,\n ),\n internalError: (msg = \"Internal server error\") =>\n new ApiError(500, msg, ApiErrorCode.INTERNAL_ERROR),\n};\n\n/** PostgreSQL error code → HTTP status mapping */\nexport const PG_ERROR_MAP: Record<\n string,\n { status: number; code: ApiErrorCodeType; message: string }\n> = {\n \"23505\": {\n status: 409,\n code: ApiErrorCode.CONFLICT,\n message: \"Resource already exists\",\n },\n \"23503\": {\n status: 404,\n code: ApiErrorCode.NOT_FOUND,\n message: \"Referenced resource not found\",\n },\n PGRST116: {\n status: 404,\n code: ApiErrorCode.NOT_FOUND,\n message: \"Resource not found\",\n },\n};\n\n/**\n * Classify any error into a standardized shape.\n * Framework-agnostic — returns a plain object, not an HTTP response.\n */\nexport function classifyError(\n error: unknown,\n isDev = false,\n): {\n status: number;\n body: { error: string; code: string; details?: unknown };\n} {\n // ApiError\n if (isApiError(error)) {\n return {\n status: error.statusCode,\n body: { error: error.message, code: error.code, details: error.details },\n };\n }\n\n // Zod-like validation errors (duck typing — has .issues array)\n if (\n error &&\n typeof error === \"object\" &&\n \"issues\" in error &&\n Array.isArray((error as Record<string, unknown>).issues)\n ) {\n return {\n status: 400,\n body: {\n error: \"Validation failed\",\n code: ApiErrorCode.VALIDATION_ERROR,\n details: (\n (error as Record<string, unknown>).issues as Array<{\n path?: string[];\n message?: string;\n }>\n ).map((i) => ({\n field: i.path?.join(\".\"),\n message: i.message,\n })),\n },\n };\n }\n\n // PostgreSQL errors (duck typing — has .code string property)\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as Record<string, unknown>).code === \"string\"\n ) {\n const pgCode = (error as Record<string, unknown>).code as string;\n const mapped = PG_ERROR_MAP[pgCode];\n if (mapped) {\n return {\n status: mapped.status,\n body: { error: mapped.message, code: mapped.code },\n };\n }\n return {\n status: 500,\n body: {\n error: \"Database error\",\n code: ApiErrorCode.DATABASE_ERROR,\n details: isDev ? (error as Record<string, unknown>).message : undefined,\n },\n };\n }\n\n // Generic Error\n if (error instanceof Error) {\n return {\n status: 500,\n body: {\n error: isDev ? error.message : \"Internal server error\",\n code: ApiErrorCode.INTERNAL_ERROR,\n details: isDev ? error.stack : undefined,\n },\n };\n }\n\n // Unknown\n return {\n status: 500,\n body: {\n error: \"An unexpected error occurred\",\n code: ApiErrorCode.INTERNAL_ERROR,\n },\n };\n}\n\n/** Standard success response shape */\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n message?: string;\n timestamp?: string;\n}\n\n/** Standard paginated response shape */\nexport interface ApiPaginatedResponse<T = unknown> extends ApiSuccessResponse<\n T[]\n> {\n pagination: {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasMore: boolean;\n };\n}\n\n/** Build a pagination metadata object */\nexport function buildPagination(page: number, limit: number, total: number) {\n return {\n page,\n limit,\n total,\n totalPages: Math.ceil(total / limit),\n hasMore: page * limit < total,\n };\n}\n","/**\n * Next.js API Route Helpers\n *\n * Shared utilities for Next.js API routes that all apps need.\n * These wrap platform-core's framework-agnostic primitives into\n * Next.js-specific patterns (NextResponse, NextRequest).\n *\n * @example\n * ```typescript\n * import {\n * enforceRateLimit,\n * zodErrorResponse,\n * errorResponse,\n * extractBearerToken,\n * isValidBearerToken,\n * } from '@digilogiclabs/platform-core/auth'\n *\n * export async function POST(request: NextRequest) {\n * const rl = await enforceRateLimit(request, 'submit', CommonRateLimits.publicForm)\n * if (rl) return rl\n *\n * const parsed = schema.safeParse(await request.json())\n * if (!parsed.success) return zodErrorResponse(parsed.error)\n *\n * try { ... }\n * catch (error) { return errorResponse(error) }\n * }\n * ```\n */\n\nimport { checkRateLimit, buildRateLimitResponseHeaders } from \"./rate-limiter\";\nimport type {\n RateLimitRule,\n RateLimitCheckResult,\n RateLimitOptions,\n} from \"./rate-limiter\";\nimport { extractClientIp } from \"./api-security\";\nimport { classifyError } from \"../api\";\nimport { constantTimeEqual } from \"../security\";\n\n// ═══════════════════════════════════════════════════════════════\n// RATE LIMITING\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Enforce rate limiting on a Next.js API request.\n *\n * Returns a 429 NextResponse if the limit is exceeded, or `null` if allowed.\n * Apps use this at the top of route handlers for early rejection.\n *\n * @example\n * ```typescript\n * const rateLimited = await enforceRateLimit(request, 'sync', CommonRateLimits.adminAction)\n * if (rateLimited) return rateLimited\n * ```\n */\nexport async function enforceRateLimit(\n request: { headers: { get(name: string): string | null } },\n operation: string,\n rule: RateLimitRule,\n options?: {\n /** Override identifier (default: extracted from x-forwarded-for) */\n identifier?: string;\n /** User ID for per-user limiting */\n userId?: string;\n /** Rate limit store and options */\n rateLimitOptions?: RateLimitOptions;\n },\n): Promise<Response | null> {\n const identifier =\n options?.identifier ??\n (options?.userId ? `user:${options.userId}` : undefined) ??\n `ip:${extractClientIp((name) => request.headers.get(name))}`;\n\n const isAuthenticated = !!options?.userId;\n\n const result = await checkRateLimit(operation, identifier, rule, {\n ...options?.rateLimitOptions,\n isAuthenticated,\n });\n\n if (!result.allowed) {\n const headers = buildRateLimitResponseHeaders(result);\n return new Response(\n JSON.stringify({\n error: \"Rate limit exceeded. Please try again later.\",\n retryAfter: result.retryAfterSeconds,\n }),\n {\n status: 429,\n headers: { \"Content-Type\": \"application/json\", ...headers },\n },\n );\n }\n\n return null;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// ERROR RESPONSES\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Convert any error into a structured JSON response.\n *\n * Uses platform-core's `classifyError()` to handle ApiError,\n * Zod errors, PostgreSQL errors, and generic errors consistently.\n *\n * @example\n * ```typescript\n * catch (error) {\n * return errorResponse(error)\n * }\n * ```\n */\nexport function errorResponse(\n error: unknown,\n options?: { isDevelopment?: boolean },\n): Response {\n const isDev =\n options?.isDevelopment ?? process.env.NODE_ENV === \"development\";\n const { status, body } = classifyError(error, isDev);\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n/**\n * Convert a Zod validation error into a user-friendly 400 response.\n *\n * Extracts the first issue and returns a clear error message\n * with the field path (e.g., \"email: Invalid email\").\n *\n * @example\n * ```typescript\n * const parsed = schema.safeParse(await request.json())\n * if (!parsed.success) return zodErrorResponse(parsed.error)\n * ```\n */\nexport function zodErrorResponse(error: {\n issues: Array<{ path: (string | number)[]; message: string }>;\n}): Response {\n const firstIssue = error.issues[0];\n const message = firstIssue\n ? `${firstIssue.path.join(\".\") || \"input\"}: ${firstIssue.message}`\n : \"Validation error\";\n return new Response(JSON.stringify({ error: message }), {\n status: 400,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Extract a bearer token from the Authorization header.\n *\n * @returns The token string, or null if not present/malformed.\n */\nexport function extractBearerToken(request: {\n headers: { get(name: string): string | null };\n}): string | null {\n const auth = request.headers.get(\"authorization\");\n if (!auth?.startsWith(\"Bearer \")) return null;\n return auth.slice(7).trim() || null;\n}\n\n/**\n * Check if a request's bearer token matches a secret.\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @example\n * ```typescript\n * if (!isValidBearerToken(request, process.env.ADMIN_SECRET)) {\n * return new Response('Unauthorized', { status: 401 })\n * }\n * ```\n */\nexport function isValidBearerToken(\n request: { headers: { get(name: string): string | null } },\n secret: string | undefined,\n): boolean {\n if (!secret) return false;\n const token = extractBearerToken(request);\n if (!token) return false;\n return constantTimeEqual(token, secret);\n}\n","/**\n * Environment Variable Helpers\n *\n * Type-safe, fail-fast utilities for reading environment variables.\n * Every app needs these — extracted from DLL, WIAN, and OSS patterns.\n *\n * @example\n * ```typescript\n * import { getRequiredEnv, getOptionalEnv, getBoolEnv, validateEnvVars } from '@digilogiclabs/platform-core'\n *\n * const config = {\n * stripe: { secretKey: getRequiredEnv('STRIPE_SECRET_KEY') },\n * redis: { url: getOptionalEnv('REDIS_URL', '') },\n * features: { debug: getBoolEnv('DEBUG', false) },\n * }\n *\n * // Validate at startup\n * validateEnvVars({\n * required: ['STRIPE_SECRET_KEY', 'DATABASE_URL'],\n * requireOneOf: [['DATABASE_URL', 'SUPABASE_URL']], // at least one\n * validators: {\n * DATABASE_URL: (v) => v.startsWith('postgres') || 'must start with postgres://',\n * ADMIN_SECRET: (v) => v.length >= 32 || 'must be at least 32 characters',\n * },\n * })\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// BASIC HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Get a required environment variable.\n * Throws immediately if not set — fail-fast at startup.\n */\nexport function getRequiredEnv(key: string): string {\n const value = process.env[key];\n if (!value) {\n throw new Error(`Missing required environment variable: ${key}`);\n }\n return value;\n}\n\n/**\n * Get an optional environment variable with a default value.\n */\nexport function getOptionalEnv(key: string, defaultValue: string): string {\n return process.env[key] || defaultValue;\n}\n\n/**\n * Get a boolean environment variable.\n * Treats \"true\" and \"1\" as true, everything else as false.\n */\nexport function getBoolEnv(key: string, defaultValue = false): boolean {\n const value = process.env[key];\n if (value === undefined || value === \"\") return defaultValue;\n return value === \"true\" || value === \"1\";\n}\n\n/**\n * Get a numeric environment variable with a default.\n */\nexport function getIntEnv(key: string, defaultValue: number): number {\n const value = process.env[key];\n if (value === undefined || value === \"\") return defaultValue;\n const parsed = parseInt(value, 10);\n return isNaN(parsed) ? defaultValue : parsed;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// VALIDATION\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Configuration for environment validation.\n */\nexport interface EnvValidationConfig {\n /** Variables that must be set (non-empty) */\n required?: string[];\n /** Groups where at least one must be set (e.g., DATABASE_URL or SUPABASE_URL) */\n requireOneOf?: string[][];\n /** Custom validators — return true or an error message */\n validators?: Record<string, (value: string) => true | string>;\n}\n\n/**\n * Validation result with categorized errors.\n */\nexport interface EnvValidationResult {\n valid: boolean;\n missing: string[];\n invalid: { key: string; reason: string }[];\n missingOneOf: string[][];\n}\n\n/**\n * Validate environment variables against a configuration.\n *\n * @throws Error with details about all missing/invalid variables\n */\nexport function validateEnvVars(config: EnvValidationConfig): void {\n const result = checkEnvVars(config);\n\n if (!result.valid) {\n const lines: string[] = [];\n\n if (result.missing.length > 0) {\n lines.push(\n \"Missing required environment variables:\",\n ...result.missing.map((v) => ` - ${v}`),\n );\n }\n\n if (result.missingOneOf.length > 0) {\n for (const group of result.missingOneOf) {\n lines.push(`Missing one of: ${group.join(\" | \")}`);\n }\n }\n\n if (result.invalid.length > 0) {\n lines.push(\n \"Invalid environment variables:\",\n ...result.invalid.map((v) => ` - ${v.key}: ${v.reason}`),\n );\n }\n\n throw new Error(lines.join(\"\\n\"));\n }\n}\n\n/**\n * Check environment variables without throwing.\n * Returns a result object for custom error handling.\n */\nexport function checkEnvVars(config: EnvValidationConfig): EnvValidationResult {\n const missing: string[] = [];\n const invalid: { key: string; reason: string }[] = [];\n const missingOneOf: string[][] = [];\n\n // Check required\n if (config.required) {\n for (const key of config.required) {\n if (!process.env[key]) {\n missing.push(key);\n }\n }\n }\n\n // Check requireOneOf groups\n if (config.requireOneOf) {\n for (const group of config.requireOneOf) {\n const hasAny = group.some((key) => !!process.env[key]);\n if (!hasAny) {\n missingOneOf.push(group);\n }\n }\n }\n\n // Run custom validators (only on set variables)\n if (config.validators) {\n for (const [key, validator] of Object.entries(config.validators)) {\n const value = process.env[key];\n if (value) {\n const result = validator(value);\n if (result !== true) {\n invalid.push({ key, reason: result });\n }\n }\n }\n }\n\n return {\n valid:\n missing.length === 0 && invalid.length === 0 && missingOneOf.length === 0,\n missing,\n invalid,\n missingOneOf,\n };\n}\n\n/**\n * Get a redacted config summary for debugging.\n * Shows which variables are configured without exposing values.\n */\nexport function getEnvSummary(keys: string[]): Record<string, boolean> {\n const summary: Record<string, boolean> = {};\n for (const key of keys) {\n summary[key] = !!process.env[key];\n }\n return summary;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwDO,IAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AACF;AAyBO,SAAS,mBACd,aACA,yBAAmC,CAAC,GAC1B;AACV,MAAI,CAAC,YAAa,QAAO,CAAC;AAE1B,MAAI;AACF,UAAM,QAAQ,YAAY,MAAM,GAAG;AACnC,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAGhC,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,KAAK,MAAM,KAAK,OAAO,CAAC;AAGxC,UAAM,aACJ,QAAQ,eAAe,QAAQ,cAAc;AAC/C,QAAI,CAAC,MAAM,QAAQ,UAAU,EAAG,QAAO,CAAC;AAGxC,UAAM,YAAY,oBAAI,IAAY;AAAA,MAChC,GAAG;AAAA,MACH,GAAG;AAAA,IACL,CAAC;AAED,WAAO,WAAW;AAAA,MAChB,CAAC,SACC,OAAO,SAAS,YAAY,CAAC,UAAU,IAAI,IAAI;AAAA,IACnD;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAYO,SAAS,QACd,OACA,MACS;AACT,SAAO,OAAO,SAAS,IAAI,KAAK;AAClC;AAYO,SAAS,WACd,OACA,eACS;AACT,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,SAAO,cAAc,KAAK,CAAC,SAAS,MAAM,SAAS,IAAI,CAAC;AAC1D;AAYO,SAAS,YACd,OACA,eACS;AACT,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,SAAO,cAAc,MAAM,CAAC,SAAS,MAAM,SAAS,IAAI,CAAC;AAC3D;AAaO,SAAS,eACd,WACA,WAAW,KACF;AACT,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,KAAK,IAAI,KAAK,YAAY;AACnC;AAkBO,SAAS,wBACd,QACA,cACiB;AACjB,SAAO,IAAI,gBAAgB;AAAA,IACzB,YAAY;AAAA,IACZ,WAAW,OAAO;AAAA,IAClB,eAAe,OAAO;AAAA,IACtB,eAAe;AAAA,EACjB,CAAC;AACH;AAKO,SAAS,iBAAiB,QAAwB;AAEvD,QAAM,OAAO,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI;AAC1D,SAAO,GAAG,IAAI;AAChB;AAKO,SAAS,sBAAsB,QAAwB;AAC5D,QAAM,OAAO,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI;AAC1D,SAAO,GAAG,IAAI;AAChB;AAoBA,eAAsB,qBACpB,QACA,cACA,wBAC6B;AAC7B,MAAI;AACF,UAAM,WAAW,iBAAiB,OAAO,MAAM;AAC/C,UAAM,SAAS,wBAAwB,QAAQ,YAAY;AAE3D,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,MAC/D,MAAM;AAAA,IACR,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,8BAA8B,SAAS,MAAM,MAAM,IAAI;AAAA,MAChE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,QACN,aAAa,KAAK;AAAA,QAClB,cAAc,KAAK,iBAAiB;AAAA,QACpC,SAAS,KAAK;AAAA,QACd,WAAW,KAAK,IAAI,IAAI,KAAK,aAAa;AAAA,QAC1C,OAAO,mBAAmB,KAAK,cAAc,sBAAsB;AAAA,MACrE;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAClD;AAAA,EACF;AACF;;;ACnMO,SAAS,iBAAiB,SAA4B,CAAC,GAAG;AAC/D,QAAM,EAAE,QAAQ,eAAe,MAAM,cAAc,KAAK,IAAI;AAC5D,QAAM,eAAe,QAAQ,IAAI,aAAa;AAC9C,QAAM,eAAe,eAAe,SAAS;AAE7C,QAAM,cAAc;AAAA,IAClB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAEA,QAAM,UACJ;AAAA,IACE,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,SAAS,EAAE,GAAG,YAAY;AAAA,IAC5B;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,EAAE,GAAG,YAAY;AAAA,IAC5B;AAAA,EACF;AAEF,MAAI,cAAc;AAChB,YAAQ,eAAe;AAAA,MACrB,MAAM,eACF,kCACA;AAAA,MACJ,SAAS,EAAE,GAAG,YAAY;AAAA,IAC5B;AAAA,EACF;AAEA,MAAI,aAAa;AACf,YAAQ,cAAc;AAAA,MACpB,MAAM,eACF,iCACA;AAAA,MACJ,SAAS,EAAE,GAAG,YAAY;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AA+BO,SAAS,sBAAsB,SAAiC,CAAC,GAAG;AACzE,QAAM,EAAE,kBAAkB,MAAM,IAAI;AAEpC,SAAO,OAAO,EAAE,KAAK,QAAQ,MAAwC;AACnE,QAAI,IAAI,WAAW,GAAG,EAAG,QAAO,GAAG,OAAO,GAAG,GAAG;AAChD,QAAI;AACF,UAAI,IAAI,IAAI,GAAG,EAAE,WAAW,QAAS,QAAO;AAAA,IAC9C,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,QAAI,iBAAiB;AACnB,UAAI;AACF,cAAM,UAAU,IAAI,IAAI,GAAG,EAAE;AAC7B,cAAM,WAAW,IAAI,IAAI,OAAO,EAAE;AAClC,YAAI,YAAY,OAAO,QAAQ,MAAM,aAAa,OAAO,OAAO,IAAI;AAClE,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAiBO,SAAS,uBAAuB,QAAiC;AACtE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,CAAC;AAAA,IAChB,QAAQ,QAAQ,IAAI,aAAa;AAAA,EACnC,IAAI;AAEJ,QAAM,WAA2B,EAAE,QAAQ,UAAU,aAAa;AAElE,WAAS,IAAI,SAAiB,MAAgC;AAC5D,QAAI,OAAO;AACT,cAAQ,IAAI,UAAU,OAAO,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI,EAAE;AAAA,IACnE;AAAA,EACF;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOL,MAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF,GAIG;AAED,UAAI,MAAM;AACR,cAAM,KAAK,MAAM,OAAQ,KAAK;AAAA,MAChC;AAEA,UAAI,SAAS,aAAa,YAAY;AACpC,cAAM,cAAc,QAAQ;AAC5B,cAAM,eAAe,QAAQ;AAC7B,cAAM,UAAU,QAAQ;AACxB,cAAM,QAAQ;AAAA,UACZ,QAAQ;AAAA,UACR;AAAA,QACF;AACA,cAAM,qBAAqB,QAAQ,aAC9B,QAAQ,aAAwB,MACjC,KAAK,IAAI,IAAI;AACjB,eAAO;AAAA,MACT;AAGA,UAAI,CAAC,eAAe,MAAM,kBAAwC,GAAG;AACnE,eAAO;AAAA,MACT;AAGA,UAAI,MAAM,cAAc;AACtB,YAAI,sCAAsC;AAE1C,cAAM,SAAS,MAAM;AAAA,UACnB;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AAEA,YAAI,OAAO,IAAI;AACb,gBAAM,cAAc,OAAO,OAAO;AAClC,gBAAM,UAAU,OAAO,OAAO,WAAW,MAAM;AAC/C,gBAAM,eAAe,OAAO,OAAO,gBAAgB,MAAM;AACzD,gBAAM,qBAAqB,OAAO,OAAO;AACzC,gBAAM,QAAQ,OAAO,OAAO;AAC5B,iBAAO,MAAM;AACb,cAAI,oBAAoB;AACxB,iBAAO;AAAA,QACT;AAEA,YAAI,wBAAwB,EAAE,OAAO,OAAO,MAAM,CAAC;AACnD,eAAO,EAAE,GAAG,OAAO,OAAO,oBAAoB;AAAA,MAChD;AAEA,UAAI,8CAA8C;AAClD,aAAO,EAAE,GAAG,OAAO,OAAO,oBAAoB;AAAA,IAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,MAAM,QAAQ,EAAE,SAAS,MAAM,GAAiC;AAC9D,YAAM,OAAO,QAAQ;AACrB,UAAI,MAAM;AACR,aAAK,KAAM,MAAM,MAAkB,MAAM;AACzC,aAAK,QAAS,MAAM,SAAsB,CAAC;AAAA,MAC7C;AACA,cAAQ,UAAU,MAAM;AACxB,cAAQ,cAAc,MAAM;AAC5B,UAAI,MAAM,OAAO;AACf,gBAAQ,QAAQ,MAAM;AAAA,MACxB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC7PO,IAAM,2BAA2B;AAAA;AAAA,EAEtC,YAAY;AAAA,IACV,OAAO;AAAA,IACP,eAAe;AAAA,IACf,oBAAoB;AAAA,EACtB;AAAA;AAAA,EAEA,aAAa;AAAA,IACX,OAAO;AAAA,IACP,eAAe;AAAA,EACjB;AAAA;AAAA,EAEA,WAAW;AAAA,IACT,OAAO;AAAA,IACP,eAAe;AAAA,IACf,oBAAoB;AAAA,EACtB;AAAA;AAAA,EAEA,aAAa;AAAA,IACX,OAAO;AAAA,IACP,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AAAA;AAAA,EAEA,YAAY;AAAA,IACV,OAAO;AAAA,IACP,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AAAA;AAAA,EAEA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AACF;AA4EO,SAAS,2BACd,SACA,UACkD;AAClD,MAAI,SAAS,MAAM,IAAI;AACrB,WAAO,EAAE,YAAY,QAAQ,QAAQ,KAAK,EAAE,IAAI,iBAAiB,KAAK;AAAA,EACxE;AACA,MAAI,SAAS,MAAM,OAAO;AACxB,WAAO;AAAA,MACL,YAAY,SAAS,QAAQ,KAAK,KAAK;AAAA,MACvC,iBAAiB;AAAA,IACnB;AAAA,EACF;AACA,SAAO,EAAE,YAAY,MAAM,QAAQ,IAAI,iBAAiB,MAAM;AAChE;AAWO,SAAS,gBACd,WACQ;AACR,SACE,UAAU,kBAAkB,KAC5B,UAAU,WAAW,KACrB,UAAU,iBAAiB,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAClD;AAEJ;AAOO,SAAS,sBACd,OACA,WACA,WACwB;AACxB,SAAO;AAAA,IACL,qBAAqB,OAAO,KAAK;AAAA,IACjC,yBAAyB,OAAO,KAAK,IAAI,GAAG,SAAS,CAAC;AAAA,IACtD,qBAAqB,OAAO,KAAK,KAAK,YAAY,GAAI,CAAC;AAAA,EACzD;AACF;AAMO,SAAS,eACd,OACA,OACyB;AACzB,SAAO,EAAE,OAAO,GAAG,MAAM;AAC3B;AAoBO,IAAM,iBAAiB;AAAA;AAAA,EAE5B,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA;AAAA,EAEA,eAAe;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA;AAAA,EAEA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA;AAAA,EAEA,aAAa;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,WAAW;AAAA,EACb;AAAA;AAAA,EAEA,IAAI;AAAA,IACF,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA;AAAA,EAEA,MAAM;AAAA,IACJ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AACF;;;ACpSA,iBAAkB;;;ACFlB,oBAAgC;AAGzB,IAAM,uBAAuB;AAG7B,IAAM,qBACX;AAGK,IAAM,mBAAmB;AAMzB,SAAS,WAAW,KAAqB;AAC9C,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAGO,SAAS,aAAa,KAAsB;AACjD,SAAO,qBAAqB,KAAK,GAAG,KAAK,mBAAmB,KAAK,GAAG;AACtE;AAGO,SAAS,aAAa,KAAsB;AACjD,SAAO,iBAAiB,KAAK,GAAG;AAClC;AAGO,SAAS,UAAU,KAAqB;AAC7C,SAAO,IAAI,QAAQ,YAAY,EAAE;AACnC;AA2CO,SAAS,kBAAkB,GAAW,GAAoB;AAC/D,MAAI;AACF,UAAM,OAAO,OAAO,KAAK,GAAG,OAAO;AACnC,UAAM,OAAO,OAAO,KAAK,GAAG,OAAO;AACnC,QAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,eAAO,+BAAgB,MAAM,IAAI;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBO,SAAS,iBACd,OACA,YACA,gBAAgB,OACoC;AAEpD,MAAI,cAAc,OAAO,aAAa,KAAK;AACzC,UAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,SAAS,aAAa;AACxE,WAAO,EAAE,QAAQ;AAAA,EACnB;AAGA,QAAM,SAA4D;AAAA,IAChE,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AAEA,MAAI,iBAAiB,iBAAiB,OAAO;AAC3C,WAAO,QAAQ,MAAM;AAAA,EACvB;AAEA,SAAO;AACT;AAkBO,SAAS,iBACd,SAGQ;AACR,QAAM,MACJ,OAAO,YAAY,aACf,UACA,CAAC,SAAiB;AAChB,UAAM,MAAM,QAAQ,IAAI,KAAK,QAAQ,KAAK,YAAY,CAAC;AACvD,WAAO,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI;AAAA,EACvC;AAEN,SACE,IAAI,cAAc,KAClB,IAAI,cAAc,KAClB,IAAI,kBAAkB,KACtB,IAAI,kBAAkB,KACtB,OAAO,WAAW;AAEtB;;;AD1JO,IAAM,cAAc,aACxB,OAAO,EACP,KAAK,EACL,YAAY,EACZ,MAAM,uBAAuB;AAGzB,IAAM,iBAAiB,aAC3B,OAAO,EACP,IAAI,GAAG,wCAAwC,EAC/C,IAAI,KAAK,2CAA2C;AAGhD,IAAM,aAAa,aACvB,OAAO,EACP,IAAI,GAAG,kBAAkB,EACzB,IAAI,KAAK,uCAAuC,EAChD;AAAA,EACC;AAAA,EACA;AACF;AAGK,IAAM,cAAc,aACxB,OAAO,EACP,MAAM,wBAAwB,6BAA6B;AAGvD,IAAM,mBAAmB,aAC7B,OAAO,EACP,IAAI,GAAG,oCAAoC,EAC3C,IAAI,KAAK,uCAAuC,EAChD;AAAA,EACC;AAAA,EACA;AACF;AAkBK,SAAS,qBAAqB,SAMO;AAC1C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,EACd,IAAI,WAAW,CAAC;AAEhB,MAAI,SAAsB,aAAE,OAAO;AACnC,MAAI,QAAQ;AACV,aAAS,OAAO,IAAI,KAAK,GAAG,SAAS,qBAAqB,GAAG,aAAa;AAC5E,MAAI,QAAQ;AACV,aAAS,OAAO;AAAA,MACd;AAAA,MACA,GAAG,SAAS,sBAAsB,GAAG;AAAA,IACvC;AAGF,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,WAAO,OACJ,OAAO,CAAC,QAAQ,CAAC,iBAAiB,KAAK,GAAG,GAAG,2BAA2B,EACxE;AAAA,MACC,CAAC,QAAQ,CAAC,qBAAqB,KAAK,GAAG;AAAA,MACvC;AAAA,IACF,EACC;AAAA,MACC,CAAC,QAAQ,CAAC,mBAAmB,KAAK,GAAG;AAAA,MACrC;AAAA,IACF;AAAA,EACJ;AAEA,MAAI,CAAC,WAAW;AACd,WAAO,OAAO;AAAA,MACZ,CAAC,QAAQ,CAAC,iBAAiB,KAAK,GAAG;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,WAAW;AACd,WAAO,OACJ;AAAA,MACC,CAAC,QAAQ,CAAC,qBAAqB,KAAK,GAAG;AAAA,MACvC;AAAA,IACF,EACC;AAAA,MACC,CAAC,QAAQ,CAAC,mBAAmB,KAAK,GAAG;AAAA,MACrC;AAAA,IACF;AAAA,EACJ;AAEA,SAAO;AACT;AAOO,IAAM,mBAAmB,aAAE,OAAO;AAAA,EACvC,MAAM,aAAE,OAAO,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EAClD,OAAO,aAAE,OAAO,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EAC7D,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,WAAW,aAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,QAAQ,MAAM;AACnD,CAAC;AAGM,IAAM,kBAAkB,aAC5B,OAAO;AAAA,EACN,WAAW,aAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,aAAE,OAAO,EAAE,SAAS;AAC/B,CAAC,EACA,OAAO,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG;AAAA,EACpE,SAAS;AACX,CAAC;AAGI,IAAM,oBAAoB,aAAE,OAAO;AAAA,EACxC,OAAO,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,KAAK;AAAA,EACvC,MAAM,aAAE,OAAO,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EAClD,OAAO,aAAE,OAAO,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE;AAC9D,CAAC;AAOM,IAAM,cAAc,aAAE,OAAO;AAAA,EAClC,OAAO;AAAA,EACP,UAAU;AACZ,CAAC;AAGM,IAAM,eAAe,aAAE,OAAO;AAAA,EACnC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAC5C,CAAC;;;AErFM,SAAS,cAA+B;AAC7C,QAAM,QAAQ,QAAQ,IAAI;AAC1B,MAAI,UAAU,aAAa,UAAU,UAAW,QAAO;AACvD,MAAI,QAAQ,IAAI,aAAa,aAAc,QAAO;AAClD,SAAO;AACT;AAKA,SAAS,iBAAiB,OAA2B;AACnD,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,QAAM,WAAW,QAAQ,IAAI,MAAM,MAAM;AACzC,MAAI,aAAa,UAAa,aAAa,IAAI;AAC7C,WAAO,MAAM,WAAW;AAAA,EAC1B;AACA,SAAO,aAAa,UAAU,aAAa;AAC7C;AAYO,SAAS,mBACd,aACA;AACA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,QAAQ,OAA2C;AACjD,YAAM,eAAe,SAAS,YAAY;AAC1C,YAAM,WAAW,CAAC;AAElB,iBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,WAAW,GAG9C;AAEH,cAAM,WAAW,iBAAiB,YAAY,YAAY;AAC1D,iBAAS,GAAG,IAAI,iBAAiB,IAAI,QAAQ,CAAC;AAAA,MAChD;AAEA,aAAO;AAAA,IACT;AAAA;AAAA;AAAA;AAAA,IAKA,UAAU,MAAS,OAAkC;AACnD,YAAM,eAAe,SAAS,YAAY;AAC1C,YAAM,MAAM,YAAY,IAAI;AAC5B,YAAM,WAAW,iBAAiB,YAAY,YAAY;AAC1D,aAAO,iBAAiB,IAAI,QAAQ,CAAC;AAAA,IACvC;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA,EACF;AACF;AAcO,SAAS,eAAe,QAAmC;AAChE,QAAM,UAAU,OAAO,SAClB,QAAQ,IAAI,OAAO,MAAM,GACtB,MAAM,GAAG,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,EACjC,OAAO,OAAO,KAAK,CAAC,IACvB,CAAC;AAEL,QAAM,WAAW,OAAO,UAAU,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC;AAGlE,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AAC/C;AAKO,SAAS,cAAc,OAAe,WAA8B;AACzE,SAAO,UAAU,SAAS,MAAM,YAAY,CAAC;AAC/C;;;AClFO,IAAM,mBAAmB;AAAA;AAAA,EAE9B,YAAY;AAAA,IACV,OAAO;AAAA,IACP,eAAe;AAAA,IACf,oBAAoB;AAAA,EACtB;AAAA;AAAA,EAEA,aAAa;AAAA,IACX,OAAO;AAAA,IACP,eAAe;AAAA,EACjB;AAAA;AAAA,EAEA,aAAa;AAAA,IACX,OAAO;AAAA,IACP,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AAAA;AAAA,EAEA,WAAW;AAAA,IACT,OAAO;AAAA,IACP,eAAe;AAAA,IACf,oBAAoB;AAAA,EACtB;AAAA;AAAA,EAEA,YAAY;AAAA,IACV,OAAO;AAAA,IACP,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AAAA;AAAA,EAEA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AACF;AAeO,SAAS,6BAA6C;AAC3D,QAAM,UAAU,oBAAI,IAAyB;AAC7C,QAAM,SAAS,oBAAI,IAAoB;AAGvC,QAAM,kBAAkB,YAAY,MAAM;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,UAAI,MAAM,YAAY,IAAK,SAAQ,OAAO,GAAG;AAAA,IAC/C;AACA,eAAW,CAAC,KAAK,MAAM,KAAK,QAAQ;AAClC,UAAI,SAAS,IAAK,QAAO,OAAO,GAAG;AAAA,IACrC;AAAA,EACF,GAAG,KAAK,GAAI;AAGZ,MAAI,gBAAgB,OAAO;AACzB,oBAAgB,MAAM;AAAA,EACxB;AAEA,SAAO;AAAA,IACL,MAAM,UACJ,KACA,UACA,KAC4B;AAC5B,YAAM,cAAc,MAAM;AAE1B,UAAI,QAAQ,QAAQ,IAAI,GAAG;AAC3B,UAAI,CAAC,OAAO;AACV,gBAAQ,EAAE,YAAY,CAAC,GAAG,WAAW,MAAM,WAAW,IAAM;AAC5D,gBAAQ,IAAI,KAAK,KAAK;AAAA,MACxB;AAGA,YAAM,aAAa,MAAM,WAAW,OAAO,CAAC,MAAM,IAAI,WAAW;AAGjE,YAAM,WAAW,KAAK,GAAG;AACzB,YAAM,YAAY,MAAM,WAAW;AAEnC,aAAO,EAAE,OAAO,MAAM,WAAW,OAAO;AAAA,IAC1C;AAAA,IAEA,MAAM,UAAU,KAA2D;AACzE,YAAM,SAAS,OAAO,IAAI,GAAG;AAC7B,UAAI,CAAC,UAAU,SAAS,KAAK,IAAI,GAAG;AAClC,eAAO,OAAO,GAAG;AACjB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE;AAAA,MACpC;AACA,aAAO,EAAE,SAAS,MAAM,OAAO,SAAS,KAAK,IAAI,EAAE;AAAA,IACrD;AAAA,IAEA,MAAM,SAAS,KAAa,iBAAwC;AAClE,aAAO,IAAI,KAAK,KAAK,IAAI,IAAI,kBAAkB,GAAI;AAAA,IACrD;AAAA,IAEA,MAAM,MAAM,KAA4B;AACtC,cAAQ,OAAO,GAAG;AAClB,aAAO,OAAO,SAAS,GAAG,EAAE;AAC5B,aAAO,OAAO,GAAG;AAAA,IACnB;AAAA,EACF;AACF;AAOA,IAAI;AAEJ,SAAS,kBAAkC;AACzC,MAAI,CAAC,cAAc;AACjB,mBAAe,2BAA2B;AAAA,EAC5C;AACA,SAAO;AACT;AAWA,eAAsB,eACpB,WACA,YACA,MACA,UAA4B,CAAC,GACE;AAC/B,QAAM,QAAQ,QAAQ,SAAS,gBAAgB;AAC/C,QAAM,QACJ,QAAQ,mBAAmB,KAAK,qBAC5B,KAAK,qBACL,KAAK;AAEX,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,KAAK,gBAAgB;AACtC,QAAM,UAAU,MAAM;AAEtB,QAAM,MAAM,aAAa,SAAS,IAAI,UAAU;AAChD,QAAM,WAAW,mBAAmB,SAAS,IAAI,UAAU;AAE3D,MAAI;AAEF,QAAI,KAAK,sBAAsB;AAC7B,YAAM,cAAc,MAAM,MAAM,UAAU,QAAQ;AAClD,UAAI,YAAY,SAAS;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,WAAW;AAAA,UACX,SAAS,MAAM,YAAY;AAAA,UAC3B,SAAS,QAAQ;AAAA,UACjB;AAAA,UACA,mBAAmB,KAAK,KAAK,YAAY,QAAQ,GAAI;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAGA,UAAM,EAAE,MAAM,IAAI,MAAM,MAAM,UAAU,KAAK,UAAU,GAAG;AAE1D,QAAI,QAAQ,OAAO;AAEjB,cAAQ,QAAQ,KAAK,uBAAuB;AAAA,QAC1C;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT;AAAA,MACF,CAAC;AAGD,UAAI,KAAK,sBAAsB;AAC7B,cAAM,MAAM,SAAS,UAAU,KAAK,oBAAoB;AAAA,MAC1D;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,WAAW;AAAA,QACX;AAAA,QACA,SAAS;AAAA,QACT;AAAA,QACA,mBAAmB,KAAK,KAAK,WAAW,GAAI;AAAA,MAC9C;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,mBAAmB;AAAA,IACrB;AAAA,EACF,SAAS,OAAO;AAEd,YAAQ,QAAQ,MAAM,6CAA6C;AAAA,MACjE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC5D;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,MACX;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,mBAAmB;AAAA,IACrB;AAAA,EACF;AACF;AAKA,eAAsB,mBACpB,WACA,YACA,MACA,OACsC;AACtC,QAAM,IAAI,SAAS,gBAAgB;AACnC,QAAM,MAAM,aAAa,SAAS,IAAI,UAAU;AAChD,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,KAAK,gBAAgB;AAEtC,MAAI;AAIF,UAAM,EAAE,MAAM,IAAI,MAAM,EAAE,UAAU,KAAK,UAAU,GAAG;AAGtD,WAAO;AAAA,MACL,SAAS,SAAS,KAAK;AAAA,MACvB,WAAW,KAAK,IAAI,GAAG,KAAK,QAAQ,KAAK;AAAA,MACzC,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,mBAAmB,QAAQ,KAAK,QAAQ,KAAK,KAAK,WAAW,GAAI,IAAI;AAAA,IACvE;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,qBACpB,WACA,YACA,OACe;AACf,QAAM,IAAI,SAAS,gBAAgB;AACnC,QAAM,MAAM,aAAa,SAAS,IAAI,UAAU;AAChD,QAAM,WAAW,mBAAmB,SAAS,IAAI,UAAU;AAC3D,QAAM,EAAE,MAAM,GAAG;AACjB,QAAM,EAAE,MAAM,QAAQ;AACxB;AASO,SAAS,8BACd,QACwB;AACxB,QAAM,UAAkC;AAAA,IACtC,qBAAqB,OAAO,OAAO,KAAK;AAAA,IACxC,yBAAyB,OAAO,OAAO,SAAS;AAAA,IAChD,qBAAqB,OAAO,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;AAAA,EAC9D;AAEA,MAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,aAAa,IAAI,OAAO,OAAO,iBAAiB;AAAA,EAC1D;AAEA,SAAO;AACT;AAMO,SAAS,kBACd,SACA,UACkD;AAClD,MAAI,SAAS,MAAM,IAAI;AACrB,WAAO,EAAE,YAAY,QAAQ,QAAQ,KAAK,EAAE,IAAI,iBAAiB,KAAK;AAAA,EACxE;AACA,MAAI,SAAS,MAAM,OAAO;AACxB,WAAO;AAAA,MACL,YAAY,SAAS,QAAQ,KAAK,KAAK;AAAA,MACvC,iBAAiB;AAAA,IACnB;AAAA,EACF;AACA,SAAO,EAAE,YAAY,MAAM,YAAY,SAAS,IAAI,iBAAiB,MAAM;AAC7E;;;AClXO,SAAS,0BACd,OACA,UAAsC,CAAC,GACvB;AAChB,QAAM,SAAS,QAAQ,aAAa;AAEpC,SAAO;AAAA,IACL,MAAM,UACJ,KACA,UACA,KAC4B;AAC5B,YAAM,UAAU,GAAG,MAAM,GAAG,GAAG;AAC/B,YAAM,cAAc,MAAM;AAC1B,YAAM,gBAAgB,KAAK,KAAK,WAAW,GAAI,IAAI;AAGnD,YAAM,MAAM,iBAAiB,SAAS,GAAG,WAAW;AAGpD,YAAM,UAAU,MAAM,MAAM,MAAM,OAAO;AAGzC,YAAM,SAAS,GAAG,GAAG,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAChE,YAAM,MAAM,KAAK,SAAS,KAAK,MAAM;AAGrC,YAAM,MAAM,OAAO,SAAS,aAAa;AAEzC,aAAO,EAAE,OAAO,UAAU,EAAE;AAAA,IAC9B;AAAA,IAEA,MAAM,UAAU,KAA2D;AACzE,YAAM,UAAU,GAAG,MAAM,GAAG,GAAG;AAC/B,YAAM,QAAQ,MAAM,MAAM,IAAI,OAAO;AAErC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE;AAAA,MACpC;AAEA,YAAM,aAAa,MAAM,MAAM,IAAI,OAAO;AAC1C,UAAI,cAAc,GAAG;AACnB,eAAO,EAAE,SAAS,OAAO,OAAO,EAAE;AAAA,MACpC;AAEA,aAAO,EAAE,SAAS,MAAM,OAAO,aAAa,IAAK;AAAA,IACnD;AAAA,IAEA,MAAM,SAAS,KAAa,iBAAwC;AAClE,YAAM,UAAU,GAAG,MAAM,GAAG,GAAG;AAC/B,YAAM,MAAM,MAAM,SAAS,iBAAiB,GAAG;AAAA,IACjD;AAAA,IAEA,MAAM,MAAM,KAA4B;AACtC,YAAM,UAAU,GAAG,MAAM,GAAG,GAAG;AAC/B,YAAM,MAAM,IAAI,OAAO;AAAA,IACzB;AAAA,EACF;AACF;;;ACVO,IAAM,uBAAuB;AAAA;AAAA,EAElC,eAAe;AAAA,EACf,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA;AAAA,EAGhB,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA;AAAA,EAGhB,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA;AAAA,EAGrB,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,2BAA2B;AAAA;AAAA,EAG3B,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf;AAaO,SAAS,eAAe,SAA4C;AACzE,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,UAAU;AAAA,IACd;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,EACF;AAEA,aAAW,UAAU,SAAS;AAC5B,UAAM,QAAQ,QAAQ,QAAQ,IAAI,MAAM;AACxC,QAAI,OAAO;AACT,aAAO,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AAAA,IACnC;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,sBACd,SACoB;AACpB,SAAO,SAAS,QAAQ,IAAI,YAAY,KAAK;AAC/C;AAKO,SAAS,sBAAsB,SAAgC;AACpE,SAAO,SAAS,QAAQ,IAAI,cAAc,KAAK,OAAO,WAAW;AACnE;AAMO,SAAS,iBACd,SAMe;AACf,MAAI,CAAC,SAAS,MAAM;AAClB,WAAO,EAAE,IAAI,aAAa,MAAM,YAAY;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,IAAI,QAAQ,KAAK,MAAM,QAAQ,KAAK,SAAS;AAAA,IAC7C,OAAO,QAAQ,KAAK,SAAS;AAAA,IAC7B,MAAM;AAAA,EACR;AACF;AAMA,IAAM,gBAAgB;AAAA,EACpB,MAAM,CAAC,KAAa,SAClB,QAAQ,IAAI,KAAK,OAAO,KAAK,UAAU,IAAI,IAAI,EAAE;AAAA,EACnD,MAAM,CAAC,KAAa,SAClB,QAAQ,KAAK,KAAK,OAAO,KAAK,UAAU,IAAI,IAAI,EAAE;AAAA,EACpD,OAAO,CAAC,KAAa,SACnB,QAAQ,MAAM,KAAK,OAAO,KAAK,UAAU,IAAI,IAAI,EAAE;AACvD;AAQO,SAAS,kBAAkB,UAAiC,CAAC,GAAG;AACrE,QAAM,EAAE,SAAS,SAAS,cAAc,IAAI;AAK5C,iBAAe,IACb,OACA,SACyB;AACzB,UAAM,SAAyB;AAAA,MAC7B,IAAI,OAAO,WAAW;AAAA,MACtB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,IAAI,eAAe,OAAO;AAAA,MAC1B,WAAW,sBAAsB,OAAO;AAAA,MACxC,WAAW,sBAAsB,OAAO;AAAA,MACxC,GAAG;AAAA,IACL;AAGA,UAAM,QACJ,MAAM,YAAY,aAAa,MAAM,YAAY,YAC7C,OAAO,OACP,OAAO;AAEb,UAAM,WAAW,MAAM,MAAM,IAAI;AAAA,MAC/B,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,IAAI,OAAO;AAAA,MACX,UAAU,OAAO;AAAA,MACjB,QAAQ,OAAO;AAAA,IACjB,CAAC;AAGD,QAAI,SAAS;AACX,UAAI;AACF,cAAM,QAAQ,MAAM;AAAA,MACtB,SAAS,OAAO;AACd,eAAO,MAAM,+BAA+B;AAAA,UAC1C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC5D,SAAS,OAAO;AAAA,QAClB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAMA,WAAS,iBACP,OACA,SAWA;AACA,UAAM,YAAY,KAAK,IAAI;AAE3B,WAAO;AAAA,MACL,SAAS,OAAO,aAAuC;AACrD,eAAO;AAAA,UACL;AAAA,YACE,GAAG;AAAA,YACH,SAAS;AAAA,YACT,UAAU;AAAA,cACR,GAAG,MAAM;AAAA,cACT,GAAG;AAAA,cACH,YAAY,KAAK,IAAI,IAAI;AAAA,YAC3B;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,OAAO,QAAgB,aAAuC;AACrE,eAAO;AAAA,UACL;AAAA,YACE,GAAG;AAAA,YACH,SAAS;AAAA,YACT;AAAA,YACA,UAAU;AAAA,cACR,GAAG,MAAM;AAAA,cACT,GAAG;AAAA,cACH,YAAY,KAAK,IAAI,IAAI;AAAA,YAC3B;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,OAAO,QAAgB,aAAuC;AACrE,eAAO;AAAA,UACL;AAAA,YACE,GAAG;AAAA,YACH,SAAS;AAAA,YACT;AAAA,YACA,UAAU;AAAA,cACR,GAAG,MAAM;AAAA,cACT,GAAG;AAAA,cACH,YAAY,KAAK,IAAI,IAAI;AAAA,YAC3B;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,KAAK,iBAAiB;AACjC;;;AC1VO,IAAM,eAAe;AAAA,EAC1B,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,wBAAwB;AAAA,EACxB,qBAAqB;AACvB;AAKO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EAEhB,YACE,YACA,SACA,OAAyB,aAAa,gBACtC,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;AAGO,SAAS,WAAW,OAAmC;AAC5D,SAAO,iBAAiB;AAC1B;AAGO,IAAM,kBAAkB;AAAA,EAC7B,cAAc,CAAC,MAAM,mBACnB,IAAI,SAAS,KAAK,KAAK,aAAa,YAAY;AAAA,EAClD,WAAW,CAAC,MAAM,gBAChB,IAAI,SAAS,KAAK,KAAK,aAAa,SAAS;AAAA,EAC/C,UAAU,CAAC,WAAW,eACpB,IAAI,SAAS,KAAK,GAAG,QAAQ,cAAc,aAAa,SAAS;AAAA,EACnE,UAAU,CAAC,MAAM,8BACf,IAAI,SAAS,KAAK,KAAK,aAAa,QAAQ;AAAA,EAC9C,mBAAmB,CAAC,MAAM,0BACxB,IAAI,SAAS,KAAK,KAAK,aAAa,mBAAmB;AAAA,EACzD,iBAAiB,CAAC,YAChB,IAAI;AAAA,IACF;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,EACF;AAAA,EACF,eAAe,CAAC,MAAM,4BACpB,IAAI,SAAS,KAAK,KAAK,aAAa,cAAc;AACtD;AAGO,IAAM,eAGT;AAAA,EACF,SAAS;AAAA,IACP,QAAQ;AAAA,IACR,MAAM,aAAa;AAAA,IACnB,SAAS;AAAA,EACX;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,IACR,MAAM,aAAa;AAAA,IACnB,SAAS;AAAA,EACX;AAAA,EACA,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,MAAM,aAAa;AAAA,IACnB,SAAS;AAAA,EACX;AACF;AAMO,SAAS,cACd,OACA,QAAQ,OAIR;AAEA,MAAI,WAAW,KAAK,GAAG;AACrB,WAAO;AAAA,MACL,QAAQ,MAAM;AAAA,MACd,MAAM,EAAE,OAAO,MAAM,SAAS,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ;AAAA,IACzE;AAAA,EACF;AAGA,MACE,SACA,OAAO,UAAU,YACjB,YAAY,SACZ,MAAM,QAAS,MAAkC,MAAM,GACvD;AACA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,OAAO;AAAA,QACP,MAAM,aAAa;AAAA,QACnB,SACG,MAAkC,OAInC,IAAI,CAAC,OAAO;AAAA,UACZ,OAAO,EAAE,MAAM,KAAK,GAAG;AAAA,UACvB,SAAS,EAAE;AAAA,QACb,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAGA,MACE,SACA,OAAO,UAAU,YACjB,UAAU,SACV,OAAQ,MAAkC,SAAS,UACnD;AACA,UAAM,SAAU,MAAkC;AAClD,UAAM,SAAS,aAAa,MAAM;AAClC,QAAI,QAAQ;AACV,aAAO;AAAA,QACL,QAAQ,OAAO;AAAA,QACf,MAAM,EAAE,OAAO,OAAO,SAAS,MAAM,OAAO,KAAK;AAAA,MACnD;AAAA,IACF;AACA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,OAAO;AAAA,QACP,MAAM,aAAa;AAAA,QACnB,SAAS,QAAS,MAAkC,UAAU;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAGA,MAAI,iBAAiB,OAAO;AAC1B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,OAAO,QAAQ,MAAM,UAAU;AAAA,QAC/B,MAAM,aAAa;AAAA,QACnB,SAAS,QAAQ,MAAM,QAAQ;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,aAAa;AAAA,IACrB;AAAA,EACF;AACF;AAwBO,SAAS,gBAAgB,MAAc,OAAe,OAAe;AAC1E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,KAAK,KAAK,QAAQ,KAAK;AAAA,IACnC,SAAS,OAAO,QAAQ;AAAA,EAC1B;AACF;;;AC9JA,eAAsB,iBACpB,SACA,WACA,MACA,SAQ0B;AAC1B,QAAM,aACJ,SAAS,eACR,SAAS,SAAS,QAAQ,QAAQ,MAAM,KAAK,WAC9C,MAAM,gBAAgB,CAAC,SAAS,QAAQ,QAAQ,IAAI,IAAI,CAAC,CAAC;AAE5D,QAAM,kBAAkB,CAAC,CAAC,SAAS;AAEnC,QAAM,SAAS,MAAM,eAAe,WAAW,YAAY,MAAM;AAAA,IAC/D,GAAG,SAAS;AAAA,IACZ;AAAA,EACF,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,UAAU,8BAA8B,MAAM;AACpD,WAAO,IAAI;AAAA,MACT,KAAK,UAAU;AAAA,QACb,OAAO;AAAA,QACP,YAAY,OAAO;AAAA,MACrB,CAAC;AAAA,MACD;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,QAAQ;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAmBO,SAAS,cACd,OACA,SACU;AACV,QAAM,QACJ,SAAS,iBAAiB,QAAQ,IAAI,aAAa;AACrD,QAAM,EAAE,QAAQ,KAAK,IAAI,cAAc,OAAO,KAAK;AACnD,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAcO,SAAS,iBAAiB,OAEpB;AACX,QAAM,aAAa,MAAM,OAAO,CAAC;AACjC,QAAM,UAAU,aACZ,GAAG,WAAW,KAAK,KAAK,GAAG,KAAK,OAAO,KAAK,WAAW,OAAO,KAC9D;AACJ,SAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC,GAAG;AAAA,IACtD,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAWO,SAAS,mBAAmB,SAEjB;AAChB,QAAM,OAAO,QAAQ,QAAQ,IAAI,eAAe;AAChD,MAAI,CAAC,MAAM,WAAW,SAAS,EAAG,QAAO;AACzC,SAAO,KAAK,MAAM,CAAC,EAAE,KAAK,KAAK;AACjC;AAaO,SAAS,mBACd,SACA,QACS;AACT,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,QAAQ,mBAAmB,OAAO;AACxC,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,kBAAkB,OAAO,MAAM;AACxC;;;ACzJO,SAAS,eAAe,KAAqB;AAClD,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0CAA0C,GAAG,EAAE;AAAA,EACjE;AACA,SAAO;AACT;AAKO,SAAS,eAAe,KAAa,cAA8B;AACxE,SAAO,QAAQ,IAAI,GAAG,KAAK;AAC7B;AAMO,SAAS,WAAW,KAAa,eAAe,OAAgB;AACrE,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,UAAa,UAAU,GAAI,QAAO;AAChD,SAAO,UAAU,UAAU,UAAU;AACvC;AAKO,SAAS,UAAU,KAAa,cAA8B;AACnE,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,UAAa,UAAU,GAAI,QAAO;AAChD,QAAM,SAAS,SAAS,OAAO,EAAE;AACjC,SAAO,MAAM,MAAM,IAAI,eAAe;AACxC;AAiCO,SAAS,gBAAgB,QAAmC;AACjE,QAAM,SAAS,aAAa,MAAM;AAElC,MAAI,CAAC,OAAO,OAAO;AACjB,UAAM,QAAkB,CAAC;AAEzB,QAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,YAAM;AAAA,QACJ;AAAA,QACA,GAAG,OAAO,QAAQ,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE;AAAA,MACzC;AAAA,IACF;AAEA,QAAI,OAAO,aAAa,SAAS,GAAG;AAClC,iBAAW,SAAS,OAAO,cAAc;AACvC,cAAM,KAAK,mBAAmB,MAAM,KAAK,KAAK,CAAC,EAAE;AAAA,MACnD;AAAA,IACF;AAEA,QAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,YAAM;AAAA,QACJ;AAAA,QACA,GAAG,OAAO,QAAQ,IAAI,CAAC,MAAM,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE;AAAA,MAC1D;AAAA,IACF;AAEA,UAAM,IAAI,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,EAClC;AACF;AAMO,SAAS,aAAa,QAAkD;AAC7E,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAA6C,CAAC;AACpD,QAAM,eAA2B,CAAC;AAGlC,MAAI,OAAO,UAAU;AACnB,eAAW,OAAO,OAAO,UAAU;AACjC,UAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,gBAAQ,KAAK,GAAG;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,cAAc;AACvB,eAAW,SAAS,OAAO,cAAc;AACvC,YAAM,SAAS,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC;AACrD,UAAI,CAAC,QAAQ;AACX,qBAAa,KAAK,KAAK;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,YAAY;AACrB,eAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,OAAO,UAAU,GAAG;AAChE,YAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,UAAI,OAAO;AACT,cAAM,SAAS,UAAU,KAAK;AAC9B,YAAI,WAAW,MAAM;AACnB,kBAAQ,KAAK,EAAE,KAAK,QAAQ,OAAO,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OACE,QAAQ,WAAW,KAAK,QAAQ,WAAW,KAAK,aAAa,WAAW;AAAA,IAC1E;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,cAAc,MAAyC;AACrE,QAAM,UAAmC,CAAC;AAC1C,aAAW,OAAO,MAAM;AACtB,YAAQ,GAAG,IAAI,CAAC,CAAC,QAAQ,IAAI,GAAG;AAAA,EAClC;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/auth/index.ts","../src/auth/keycloak.ts","../src/auth/nextjs-keycloak.ts","../src/auth/api-security.ts","../src/auth/schemas.ts","../src/security.ts","../src/auth/feature-flags.ts","../src/auth/rate-limiter.ts","../src/auth/rate-limit-store-redis.ts","../src/auth/audit.ts","../src/api.ts","../src/auth/nextjs-api.ts","../src/auth/beta-client.ts","../src/env.ts"],"sourcesContent":["/**\n * Auth Module\n *\n * Keycloak OIDC helpers, API security types, validation schemas,\n * feature flags, rate limiting, audit logging, and essential utilities\n * shared across all apps.\n *\n * This sub-path export (`@digilogiclabs/platform-core/auth`) is lightweight —\n * it does NOT pull in database, storage, email, or payment adapters.\n * Use it in next.config.ts, middleware, and API routes without bloating bundles.\n */\n\nexport * from \"./keycloak\";\nexport * from \"./nextjs-keycloak\";\nexport * from \"./api-security\";\nexport * from \"./schemas\";\nexport * from \"./feature-flags\";\nexport * from \"./rate-limiter\";\nexport * from \"./rate-limit-store-redis\";\nexport * from \"./audit\";\nexport * from \"./nextjs-api\";\nexport * from \"./beta-client\";\n\n// Re-export lightweight utilities from sibling modules so apps don't need\n// the main barrel import (which pulls in all adapters including stripe, pg, etc.)\nexport {\n // Security utilities\n constantTimeEqual,\n escapeHtml,\n containsUrls,\n containsHtml,\n stripHtml,\n sanitizeApiError,\n getCorrelationId,\n} from \"../security\";\n\nexport {\n // API error handling\n classifyError,\n ApiError,\n isApiError,\n CommonApiErrors,\n ApiErrorCode,\n buildPagination,\n} from \"../api\";\nexport type {\n ApiErrorCodeType,\n ApiSuccessResponse,\n ApiPaginatedResponse,\n} from \"../api\";\n\nexport {\n // Environment helpers\n getRequiredEnv,\n getOptionalEnv,\n getBoolEnv,\n getIntEnv,\n validateEnvVars,\n checkEnvVars,\n getEnvSummary,\n} from \"../env\";\nexport type { EnvValidationConfig, EnvValidationResult } from \"../env\";\n","/**\n * Keycloak Authentication Utilities\n *\n * Framework-agnostic helpers for working with Keycloak OIDC tokens.\n * Used by Next.js apps (Auth.js middleware + API routes) and .NET services\n * share the same role model — these helpers ensure consistent behavior.\n *\n * Edge-runtime compatible: uses atob() for JWT decoding, not Buffer.\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Keycloak provider configuration for Auth.js / NextAuth.\n * Everything needed to configure the Keycloak OIDC provider.\n */\nexport interface KeycloakConfig {\n /** Keycloak issuer URL (e.g. https://auth.example.com/realms/my-realm) */\n issuer: string;\n /** OAuth client ID registered in Keycloak */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n}\n\n/**\n * Token set returned by Keycloak after authentication or refresh.\n */\nexport interface KeycloakTokenSet {\n accessToken: string;\n refreshToken?: string;\n idToken?: string;\n /** Expiry as epoch milliseconds */\n expiresAt: number;\n /** Parsed realm roles (filtered, no Keycloak defaults) */\n roles: string[];\n}\n\n/**\n * Result of a token refresh attempt.\n */\nexport type TokenRefreshResult =\n | { ok: true; tokens: KeycloakTokenSet }\n | { ok: false; error: string };\n\n// ═══════════════════════════════════════════════════════════════\n// ROLE MANAGEMENT\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Default Keycloak roles that should be filtered out when checking\n * application-level roles. These are always present and not meaningful\n * for authorization decisions.\n */\nexport const KEYCLOAK_DEFAULT_ROLES = [\n \"offline_access\",\n \"uma_authorization\",\n] as const;\n\n/**\n * Parse realm roles from a Keycloak JWT access token.\n *\n * Supports two token formats:\n * - `realm_roles` (flat array) — Custom client mapper configuration\n * - `realm_access.roles` (nested) — Keycloak default format\n *\n * Uses atob() for Edge runtime compatibility (not Buffer.from).\n * Filters out Keycloak default roles automatically.\n *\n * @param accessToken - Raw JWT string from Keycloak\n * @param additionalDefaultRoles - Extra role names to filter (e.g. realm-specific defaults)\n * @returns Array of meaningful role names\n *\n * @example\n * ```typescript\n * const roles = parseKeycloakRoles(account.access_token)\n * // ['admin', 'editor']\n *\n * // With realm-specific defaults\n * const roles = parseKeycloakRoles(token, ['default-roles-my-realm'])\n * ```\n */\nexport function parseKeycloakRoles(\n accessToken: string | undefined | null,\n additionalDefaultRoles: string[] = [],\n): string[] {\n if (!accessToken) return [];\n\n try {\n const parts = accessToken.split(\".\");\n if (parts.length !== 3) return [];\n\n // Decode JWT payload — use atob for Edge runtime compatibility\n const payload = parts[1]!;\n const decoded = JSON.parse(atob(payload));\n\n // Try flat realm_roles first (custom mapper), then nested (Keycloak default)\n const realmRoles: unknown =\n decoded.realm_roles ?? decoded.realm_access?.roles;\n if (!Array.isArray(realmRoles)) return [];\n\n // Filter out Keycloak defaults and any realm-specific defaults\n const filterSet = new Set<string>([\n ...KEYCLOAK_DEFAULT_ROLES,\n ...additionalDefaultRoles,\n ]);\n\n return realmRoles.filter(\n (role): role is string =>\n typeof role === \"string\" && !filterSet.has(role),\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Check if a user has a specific role.\n *\n * @example\n * ```typescript\n * if (hasRole(session.user.roles, 'admin')) {\n * // grant access\n * }\n * ```\n */\nexport function hasRole(\n roles: string[] | undefined | null,\n role: string,\n): boolean {\n return roles?.includes(role) ?? false;\n}\n\n/**\n * Check if a user has ANY of the specified roles (OR logic).\n *\n * @example\n * ```typescript\n * if (hasAnyRole(session.user.roles, ['admin', 'editor'])) {\n * // grant access\n * }\n * ```\n */\nexport function hasAnyRole(\n roles: string[] | undefined | null,\n requiredRoles: string[],\n): boolean {\n if (!roles || roles.length === 0) return false;\n return requiredRoles.some((role) => roles.includes(role));\n}\n\n/**\n * Check if a user has ALL of the specified roles (AND logic).\n *\n * @example\n * ```typescript\n * if (hasAllRoles(session.user.roles, ['admin', 'billing'])) {\n * // grant access to billing admin\n * }\n * ```\n */\nexport function hasAllRoles(\n roles: string[] | undefined | null,\n requiredRoles: string[],\n): boolean {\n if (!roles || roles.length === 0) return false;\n return requiredRoles.every((role) => roles.includes(role));\n}\n\n// ═══════════════════════════════════════════════════════════════\n// TOKEN REFRESH\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Check if a token needs refreshing.\n *\n * @param expiresAt - Token expiry as epoch milliseconds\n * @param bufferMs - Refresh this many ms before actual expiry (default: 60s)\n * @returns true if the token should be refreshed\n */\nexport function isTokenExpired(\n expiresAt: number | undefined | null,\n bufferMs = 60_000,\n): boolean {\n if (!expiresAt) return true;\n return Date.now() >= expiresAt - bufferMs;\n}\n\n/**\n * Build the URLSearchParams for a Keycloak token refresh request.\n *\n * This is the body for POST to `{issuer}/protocol/openid-connect/token`.\n * Framework-agnostic — use with fetch(), axios, or any HTTP client.\n *\n * @example\n * ```typescript\n * const params = buildTokenRefreshParams(config, refreshToken)\n * const response = await fetch(`${config.issuer}/protocol/openid-connect/token`, {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n * body: params,\n * })\n * ```\n */\nexport function buildTokenRefreshParams(\n config: KeycloakConfig,\n refreshToken: string,\n): URLSearchParams {\n return new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: config.clientId,\n client_secret: config.clientSecret,\n refresh_token: refreshToken,\n });\n}\n\n/**\n * Get the Keycloak token endpoint URL for a given issuer.\n */\nexport function getTokenEndpoint(issuer: string): string {\n // Remove trailing slash if present\n const base = issuer.endsWith(\"/\") ? issuer.slice(0, -1) : issuer;\n return `${base}/protocol/openid-connect/token`;\n}\n\n/**\n * Get the Keycloak end session (logout) endpoint URL.\n */\nexport function getEndSessionEndpoint(issuer: string): string {\n const base = issuer.endsWith(\"/\") ? issuer.slice(0, -1) : issuer;\n return `${base}/protocol/openid-connect/logout`;\n}\n\n/**\n * Perform a token refresh against Keycloak and parse the result.\n *\n * Returns a discriminated union — check `result.ok` before accessing tokens.\n * Automatically parses roles from the new access token.\n *\n * @example\n * ```typescript\n * const result = await refreshKeycloakToken(config, currentRefreshToken)\n * if (result.ok) {\n * token.accessToken = result.tokens.accessToken\n * token.roles = result.tokens.roles\n * } else {\n * // Force re-login\n * token.error = 'RefreshTokenError'\n * }\n * ```\n */\nexport async function refreshKeycloakToken(\n config: KeycloakConfig,\n refreshToken: string,\n additionalDefaultRoles?: string[],\n): Promise<TokenRefreshResult> {\n try {\n const endpoint = getTokenEndpoint(config.issuer);\n const params = buildTokenRefreshParams(config, refreshToken);\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: params,\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n return {\n ok: false,\n error: `Token refresh failed: HTTP ${response.status} - ${body}`,\n };\n }\n\n const data = await response.json();\n\n return {\n ok: true,\n tokens: {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n idToken: data.id_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n roles: parseKeycloakRoles(data.access_token, additionalDefaultRoles),\n },\n };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"Token refresh failed\",\n };\n }\n}\n","/**\n * Next.js Auth.js + Keycloak Configuration Builder\n *\n * Generates Auth.js callbacks for Keycloak OIDC integration.\n * Handles JWT token storage, role parsing, token refresh, and session mapping.\n *\n * Edge-runtime compatible: uses only atob() and fetch(), no Node.js-only APIs.\n *\n * @example\n * ```typescript\n * // auth.config.ts (Edge-compatible)\n * import { buildKeycloakCallbacks } from '@digilogiclabs/platform-core'\n * import Keycloak from 'next-auth/providers/keycloak'\n *\n * const callbacks = buildKeycloakCallbacks({\n * issuer: process.env.AUTH_KEYCLOAK_ISSUER!,\n * clientId: process.env.AUTH_KEYCLOAK_ID!,\n * clientSecret: process.env.AUTH_KEYCLOAK_SECRET!,\n * defaultRoles: ['default-roles-my-realm'],\n * })\n *\n * export const authConfig = {\n * providers: [Keycloak({ ... })],\n * session: { strategy: 'jwt' },\n * callbacks,\n * }\n * ```\n */\n\nimport {\n parseKeycloakRoles,\n isTokenExpired,\n refreshKeycloakToken,\n} from \"./keycloak\";\nimport type { KeycloakConfig } from \"./keycloak\";\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\nexport interface KeycloakCallbacksConfig {\n /** Keycloak issuer URL */\n issuer: string;\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Realm-specific default roles to filter (e.g. ['default-roles-my-realm']) */\n defaultRoles?: string[];\n /** Enable debug logging (default: NODE_ENV === 'development') */\n debug?: boolean;\n}\n\n/**\n * Extended JWT token shape used by the Keycloak callbacks.\n * These fields are added to the Auth.js JWT token during sign-in and refresh.\n */\nexport interface KeycloakJwtFields {\n id?: string;\n accessToken?: string;\n refreshToken?: string;\n idToken?: string;\n accessTokenExpires?: number;\n roles?: string[];\n error?: string;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH.JS COOKIE CONFIGURATION\n// ═══════════════════════════════════════════════════════════════\n\nexport interface AuthCookiesConfig {\n /** Production cookie domain (e.g. '.digilogiclabs.com'). Omit for default domain. */\n domain?: string;\n /** Include session token cookie config (default: true) */\n sessionToken?: boolean;\n /** Include callback URL cookie config (default: true) */\n callbackUrl?: boolean;\n}\n\n/**\n * Build Auth.js cookie configuration for cross-domain OIDC.\n *\n * Handles the common pattern of configuring cookies to work across\n * www and non-www domains (e.g. digilogiclabs.com and www.digilogiclabs.com).\n *\n * PKCE and state cookies are always included for OAuth security.\n * Session token and callback URL cookies are included by default.\n *\n * @example\n * ```typescript\n * import { buildAuthCookies } from '@digilogiclabs/platform-core'\n *\n * export const authConfig = {\n * cookies: buildAuthCookies({ domain: '.digilogiclabs.com' }),\n * // ...\n * }\n * ```\n */\nexport function buildAuthCookies(config: AuthCookiesConfig = {}) {\n const { domain, sessionToken = true, callbackUrl = true } = config;\n const isProduction = process.env.NODE_ENV === \"production\";\n const cookieDomain = isProduction ? domain : undefined;\n\n const baseOptions = {\n httpOnly: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n secure: isProduction,\n domain: cookieDomain,\n };\n\n const cookies: Record<string, { name: string; options: typeof baseOptions }> =\n {\n pkceCodeVerifier: {\n name: \"authjs.pkce.code_verifier\",\n options: { ...baseOptions },\n },\n state: {\n name: \"authjs.state\",\n options: { ...baseOptions },\n },\n };\n\n if (sessionToken) {\n cookies.sessionToken = {\n name: isProduction\n ? \"__Secure-authjs.session-token\"\n : \"authjs.session-token\",\n options: { ...baseOptions },\n };\n }\n\n if (callbackUrl) {\n cookies.callbackUrl = {\n name: isProduction\n ? \"__Secure-authjs.callback-url\"\n : \"authjs.callback-url\",\n options: { ...baseOptions },\n };\n }\n\n return cookies;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH.JS REDIRECT CALLBACK\n// ═══════════════════════════════════════════════════════════════\n\nexport interface RedirectCallbackConfig {\n /** Allow www variant redirects (e.g. www.example.com ↔ example.com) */\n allowWwwVariant?: boolean;\n}\n\n/**\n * Build a standard Auth.js redirect callback.\n *\n * Handles common redirect patterns:\n * - Relative URLs → prefixed with baseUrl\n * - Same-origin URLs → allowed\n * - www variant URLs → optionally allowed\n * - Everything else → baseUrl\n *\n * @example\n * ```typescript\n * import { buildRedirectCallback } from '@digilogiclabs/platform-core'\n *\n * export const authConfig = {\n * callbacks: {\n * redirect: buildRedirectCallback({ allowWwwVariant: true }),\n * },\n * }\n * ```\n */\nexport function buildRedirectCallback(config: RedirectCallbackConfig = {}) {\n const { allowWwwVariant = false } = config;\n\n return async ({ url, baseUrl }: { url: string; baseUrl: string }) => {\n if (url.startsWith(\"/\")) return `${baseUrl}${url}`;\n try {\n if (new URL(url).origin === baseUrl) return url;\n } catch {\n return baseUrl;\n }\n\n if (allowWwwVariant) {\n try {\n const urlHost = new URL(url).hostname;\n const baseHost = new URL(baseUrl).hostname;\n if (urlHost === `www.${baseHost}` || baseHost === `www.${urlHost}`) {\n return url;\n }\n } catch {\n // Invalid URL, fall through\n }\n }\n\n return baseUrl;\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// CALLBACK BUILDERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Build Auth.js JWT and session callbacks for Keycloak.\n *\n * Returns an object with `jwt` and `session` callbacks that handle:\n * - Storing Keycloak tokens on initial sign-in\n * - Parsing realm roles from the access token\n * - Automatic token refresh when expired\n * - Mapping tokens/roles to the session object\n *\n * The callbacks are Edge-runtime compatible.\n */\nexport function buildKeycloakCallbacks(config: KeycloakCallbacksConfig) {\n const {\n issuer,\n clientId,\n clientSecret,\n defaultRoles = [],\n debug = process.env.NODE_ENV === \"development\",\n } = config;\n\n const kcConfig: KeycloakConfig = { issuer, clientId, clientSecret };\n\n function log(message: string, meta?: Record<string, unknown>) {\n if (debug) {\n console.log(`[Auth] ${message}`, meta ? JSON.stringify(meta) : \"\");\n }\n }\n\n return {\n /**\n * JWT callback — stores Keycloak tokens and handles refresh.\n *\n * Compatible with Auth.js v5 JWT callback signature.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async jwt({\n token,\n user,\n account,\n }: {\n token: any;\n user?: any;\n account?: any;\n }) {\n // Initial sign-in — store Keycloak tokens\n if (user) {\n token.id = token.sub ?? (user.id as string);\n }\n\n if (account?.provider === \"keycloak\") {\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.idToken = account.id_token;\n token.roles = parseKeycloakRoles(\n account.access_token as string | undefined,\n defaultRoles,\n );\n token.accessTokenExpires = account.expires_at\n ? (account.expires_at as number) * 1000\n : Date.now() + 300_000;\n return token;\n }\n\n // Return token if still valid\n if (!isTokenExpired(token.accessTokenExpires as number | undefined)) {\n return token;\n }\n\n // Attempt token refresh\n if (token.refreshToken) {\n log(\"Token expired, attempting refresh...\");\n\n const result = await refreshKeycloakToken(\n kcConfig,\n token.refreshToken as string,\n defaultRoles,\n );\n\n if (result.ok) {\n token.accessToken = result.tokens.accessToken;\n token.idToken = result.tokens.idToken ?? token.idToken;\n token.refreshToken = result.tokens.refreshToken ?? token.refreshToken;\n token.accessTokenExpires = result.tokens.expiresAt;\n token.roles = result.tokens.roles;\n delete token.error;\n log(\"Token refreshed OK\");\n return token;\n }\n\n log(\"Token refresh failed\", { error: result.error });\n return { ...token, error: \"RefreshTokenError\" };\n }\n\n log(\"Token expired but no refresh token available\");\n return { ...token, error: \"RefreshTokenError\" };\n },\n\n /**\n * Session callback — maps JWT fields to the session object.\n *\n * Compatible with Auth.js v5 session callback signature.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async session({ session, token }: { session: any; token: any }) {\n const user = session.user as Record<string, unknown> | undefined;\n if (user) {\n user.id = (token.id as string) || (token.sub as string);\n user.roles = (token.roles as string[]) || [];\n }\n session.idToken = token.idToken;\n session.accessToken = token.accessToken;\n if (token.error) {\n session.error = token.error;\n }\n return session;\n },\n };\n}\n","/**\n * API Security Types & Helpers\n *\n * Framework-agnostic types and utilities for building composable\n * API security wrappers. These define the shared contract that\n * framework-specific implementations (Next.js, Express, .NET) follow.\n *\n * The actual wrappers (withPublicApi, withAdminApi, etc.) live in each\n * app because they depend on framework-specific request/response types.\n * This module provides the shared types and logic they all use.\n */\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH METHOD\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * How a request was authenticated.\n * Used by audit logging and authorization decisions.\n */\nexport type AuthMethod =\n | \"session\" // Auth.js / NextAuth session (JWT verified)\n | \"bearer_token\" // Bearer token in Authorization header\n | \"api_key\" // API key in header (e.g. X-Api-Key)\n | \"webhook_signature\" // HMAC signature verification (Stripe, etc.)\n | \"cron_secret\" // Scheduled job secret\n | \"none\"; // Unauthenticated (public routes)\n\n// ═══════════════════════════════════════════════════════════════\n// SECURITY CONFIGURATION\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Audit configuration for a secured route.\n * Tells the security wrapper what to log.\n */\nexport interface RouteAuditConfig {\n /** The action being performed (e.g. 'game.server.create') */\n action: string;\n /** The resource type being acted on (e.g. 'game_server') */\n resourceType: string;\n}\n\n/**\n * Rate limit preset configuration.\n * Matches the structure used by all apps.\n */\nexport interface RateLimitPreset {\n /** Maximum requests allowed in the window */\n limit: number;\n /** Window duration in seconds */\n windowSeconds: number;\n /** Higher limit for authenticated users */\n authenticatedLimit?: number;\n /** Block duration when limit exceeded (seconds) */\n blockDurationSeconds?: number;\n}\n\n/**\n * Standard rate limit presets shared across all apps.\n *\n * Apps can extend with their own domain-specific presets:\n * ```typescript\n * const APP_RATE_LIMITS = {\n * ...StandardRateLimitPresets,\n * gameServerCreate: { limit: 5, windowSeconds: 3600, blockDurationSeconds: 3600 },\n * }\n * ```\n */\nexport const StandardRateLimitPresets = {\n /** General API: 100/min, 200/min authenticated */\n apiGeneral: {\n limit: 100,\n windowSeconds: 60,\n authenticatedLimit: 200,\n },\n /** Admin operations: 100/min (admins are trusted) */\n adminAction: {\n limit: 100,\n windowSeconds: 60,\n },\n /** AI/expensive operations: 20/hour, 50/hour authenticated */\n aiRequest: {\n limit: 20,\n windowSeconds: 3600,\n authenticatedLimit: 50,\n },\n /** Auth attempts: 5/15min with 15min block */\n authAttempt: {\n limit: 5,\n windowSeconds: 900,\n blockDurationSeconds: 900,\n },\n /** Contact/public forms: 10/hour */\n publicForm: {\n limit: 10,\n windowSeconds: 3600,\n blockDurationSeconds: 1800,\n },\n /** Checkout/billing: 10/hour with 1hr block */\n checkout: {\n limit: 10,\n windowSeconds: 3600,\n blockDurationSeconds: 3600,\n },\n} as const satisfies Record<string, RateLimitPreset>;\n\n/**\n * Configuration for a secured API handler.\n *\n * This is the framework-agnostic config shape. Each framework's\n * wrapper (withPublicApi, withAdminApi, etc.) maps to this structure.\n */\nexport interface ApiSecurityConfig {\n /** Whether authentication is required */\n requireAuth: boolean;\n /** Whether admin role is required */\n requireAdmin: boolean;\n /** Required roles (user must have at least one) */\n requireRoles?: string[];\n /** Allow legacy bearer token as alternative to session auth */\n allowBearerToken?: boolean;\n /** Rate limit preset name or custom config */\n rateLimit?: string | RateLimitPreset;\n /** Audit logging config */\n audit?: RouteAuditConfig;\n /** Human-readable operation name for logging */\n operation?: string;\n /** Skip rate limiting */\n skipRateLimit?: boolean;\n /** Skip audit logging */\n skipAudit?: boolean;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// SECURITY CONTEXT\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Minimal session shape that all auth systems provide.\n * Maps to Auth.js Session, .NET ClaimsPrincipal, etc.\n */\nexport interface SecuritySession {\n user: {\n id: string;\n email?: string | null;\n name?: string | null;\n roles?: string[];\n };\n}\n\n/**\n * Context available to secured route handlers after all security\n * checks have passed. Framework wrappers extend this with their\n * own fields (e.g. NextRequest, validated body, etc.).\n */\nexport interface ApiSecurityContext {\n /** Authenticated session (null for public routes or token auth) */\n session: SecuritySession | null;\n /** How the request was authenticated */\n authMethod: AuthMethod;\n /** Whether the user has admin privileges */\n isAdmin: boolean;\n /** Request correlation ID */\n requestId: string;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// SECURITY PIPELINE HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Determine the appropriate rate limit key for a request.\n *\n * Priority: user ID > email > IP address.\n * Authenticated users get their own bucket (and potentially higher limits).\n *\n * @param session - Current session (if any)\n * @param clientIp - Client IP address\n * @returns { identifier, isAuthenticated }\n */\nexport function resolveRateLimitIdentifier(\n session: SecuritySession | null,\n clientIp: string,\n): { identifier: string; isAuthenticated: boolean } {\n if (session?.user?.id) {\n return { identifier: `user:${session.user.id}`, isAuthenticated: true };\n }\n if (session?.user?.email) {\n return {\n identifier: `email:${session.user.email}`,\n isAuthenticated: true,\n };\n }\n return { identifier: `ip:${clientIp}`, isAuthenticated: false };\n}\n\n/**\n * Extract the client IP from standard proxy headers.\n *\n * Checks (in order): CF-Connecting-IP, X-Real-IP, X-Forwarded-For.\n * Use this to ensure consistent IP extraction across all apps.\n *\n * @param getHeader - Header getter function\n * @returns Client IP or 'unknown'\n */\nexport function extractClientIp(\n getHeader: (name: string) => string | null | undefined,\n): string {\n return (\n getHeader(\"cf-connecting-ip\") ||\n getHeader(\"x-real-ip\") ||\n getHeader(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ||\n \"unknown\"\n );\n}\n\n/**\n * Build the standard rate limit response headers.\n *\n * @returns Headers object with X-RateLimit-* headers\n */\nexport function buildRateLimitHeaders(\n limit: number,\n remaining: number,\n resetAtMs: number,\n): Record<string, string> {\n return {\n \"X-RateLimit-Limit\": String(limit),\n \"X-RateLimit-Remaining\": String(Math.max(0, remaining)),\n \"X-RateLimit-Reset\": String(Math.ceil(resetAtMs / 1000)),\n };\n}\n\n/**\n * Build a standard error response body.\n * Ensures consistent error shape across all apps.\n */\nexport function buildErrorBody(\n error: string,\n extra?: Record<string, unknown>,\n): Record<string, unknown> {\n return { error, ...extra };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// WRAPPER PRESETS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Pre-built security configurations for common route types.\n *\n * Use these as the base for framework-specific wrappers:\n * ```typescript\n * // In your app's api-security.ts\n * export function withPublicApi(config, handler) {\n * return createSecureHandler({\n * ...WrapperPresets.public,\n * ...config,\n * }, handler)\n * }\n * ```\n */\nexport const WrapperPresets = {\n /** Public route: no auth, rate limited */\n public: {\n requireAuth: false,\n requireAdmin: false,\n rateLimit: \"apiGeneral\",\n },\n /** Authenticated route: requires session */\n authenticated: {\n requireAuth: true,\n requireAdmin: false,\n rateLimit: \"apiGeneral\",\n },\n /** Admin route: requires session with admin role */\n admin: {\n requireAuth: true,\n requireAdmin: true,\n rateLimit: \"adminAction\",\n },\n /** Legacy admin: accepts session OR bearer token */\n legacyAdmin: {\n requireAuth: true,\n requireAdmin: true,\n allowBearerToken: true,\n rateLimit: \"adminAction\",\n },\n /** AI/expensive: requires auth, strict rate limit */\n ai: {\n requireAuth: true,\n requireAdmin: false,\n rateLimit: \"aiRequest\",\n },\n /** Cron: no rate limit, admin-level access */\n cron: {\n requireAuth: true,\n requireAdmin: false,\n skipRateLimit: true,\n skipAudit: false,\n },\n} as const satisfies Record<string, Partial<ApiSecurityConfig>>;\n","/**\n * Common Validation Schemas\n *\n * Reusable Zod schemas for request validation across all apps.\n * These are the building blocks — apps compose them into\n * route-specific schemas for their own domain logic.\n *\n * Requires `zod` as a peer dependency (already in platform-core).\n */\n\nimport { z } from \"zod\";\nimport {\n HTML_TAG_PATTERN,\n URL_PROTOCOL_PATTERN,\n URL_DOMAIN_PATTERN,\n} from \"../security\";\n\n// ═══════════════════════════════════════════════════════════════\n// PRIMITIVE SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/** Validated, normalized email address */\nexport const EmailSchema = z\n .string()\n .trim()\n .toLowerCase()\n .email(\"Invalid email address\");\n\n/** Password with minimum security requirements */\nexport const PasswordSchema = z\n .string()\n .min(8, \"Password must be at least 8 characters\")\n .max(100, \"Password must be less than 100 characters\");\n\n/** URL-safe slug (lowercase alphanumeric + hyphens) */\nexport const SlugSchema = z\n .string()\n .min(1, \"Slug is required\")\n .max(100, \"Slug must be less than 100 characters\")\n .regex(\n /^[a-z0-9-]+$/,\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n\n/** Phone number (international format, flexible) */\nexport const PhoneSchema = z\n .string()\n .regex(/^[\\d\\s()+.\\-]{7,20}$/, \"Invalid phone number format\");\n\n/** Human name (letters, spaces, hyphens, apostrophes) */\nexport const PersonNameSchema = z\n .string()\n .min(2, \"Name must be at least 2 characters\")\n .max(100, \"Name must be less than 100 characters\")\n .regex(\n /^[a-zA-Z\\s\\-']+$/,\n \"Name can only contain letters, spaces, hyphens and apostrophes\",\n );\n\n// ═══════════════════════════════════════════════════════════════\n// SANITIZED TEXT SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Create a text schema that blocks HTML tags and links.\n * Use for user-facing text fields (contact forms, comments, etc.)\n *\n * @param options - Customize min/max length and error messages\n *\n * @example\n * ```typescript\n * const MessageSchema = createSafeTextSchema({ min: 10, max: 1000 })\n * const CommentSchema = createSafeTextSchema({ max: 500, allowUrls: true })\n * ```\n */\nexport function createSafeTextSchema(options?: {\n min?: number;\n max?: number;\n allowHtml?: boolean;\n allowUrls?: boolean;\n fieldName?: string;\n}): z.ZodType<string, z.ZodTypeDef, string> {\n const {\n min,\n max,\n allowHtml = false,\n allowUrls = false,\n fieldName = \"Text\",\n } = options ?? {};\n\n let schema: z.ZodString = z.string();\n if (min !== undefined)\n schema = schema.min(min, `${fieldName} must be at least ${min} characters`);\n if (max !== undefined)\n schema = schema.max(\n max,\n `${fieldName} must be less than ${max} characters`,\n );\n\n // Apply refinements for HTML/URL blocking\n if (!allowHtml && !allowUrls) {\n return schema\n .refine((val) => !HTML_TAG_PATTERN.test(val), \"HTML tags are not allowed\")\n .refine(\n (val) => !URL_PROTOCOL_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n )\n .refine(\n (val) => !URL_DOMAIN_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n );\n }\n\n if (!allowHtml) {\n return schema.refine(\n (val) => !HTML_TAG_PATTERN.test(val),\n \"HTML tags are not allowed\",\n );\n }\n\n if (!allowUrls) {\n return schema\n .refine(\n (val) => !URL_PROTOCOL_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n )\n .refine(\n (val) => !URL_DOMAIN_PATTERN.test(val),\n \"Links are not allowed for security reasons\",\n );\n }\n\n return schema;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// PAGINATION & QUERY SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/** Standard pagination query parameters */\nexport const PaginationSchema = z.object({\n page: z.coerce.number().int().positive().default(1),\n limit: z.coerce.number().int().positive().max(100).default(20),\n sortBy: z.string().optional(),\n sortOrder: z.enum([\"asc\", \"desc\"]).default(\"desc\"),\n});\n\n/** Date range filter (ISO 8601 datetime strings) */\nexport const DateRangeSchema = z\n .object({\n startDate: z.string().datetime(),\n endDate: z.string().datetime(),\n })\n .refine((data) => new Date(data.startDate) <= new Date(data.endDate), {\n message: \"Start date must be before end date\",\n });\n\n/** Search query with optional filters */\nexport const SearchQuerySchema = z.object({\n query: z.string().min(1).max(200).trim(),\n page: z.coerce.number().int().positive().default(1),\n limit: z.coerce.number().int().positive().max(50).default(10),\n});\n\n// ═══════════════════════════════════════════════════════════════\n// COMPOSED SCHEMAS\n// ═══════════════════════════════════════════════════════════════\n\n/** Login credentials */\nexport const LoginSchema = z.object({\n email: EmailSchema,\n password: PasswordSchema,\n});\n\n/** Signup with optional name */\nexport const SignupSchema = z.object({\n email: EmailSchema,\n password: PasswordSchema,\n name: z.string().min(2).max(100).optional(),\n});\n\n// ═══════════════════════════════════════════════════════════════\n// TYPE EXPORTS\n// ═══════════════════════════════════════════════════════════════\n\nexport type EmailInput = z.infer<typeof EmailSchema>;\nexport type PaginationInput = z.infer<typeof PaginationSchema>;\nexport type DateRangeInput = z.infer<typeof DateRangeSchema>;\nexport type SearchQueryInput = z.infer<typeof SearchQuerySchema>;\nexport type LoginInput = z.infer<typeof LoginSchema>;\nexport type SignupInput = z.infer<typeof SignupSchema>;\n","/**\n * Security Utilities\n *\n * HTML escaping, input detection, sanitization helpers,\n * timing-safe comparison, error sanitization, and request\n * correlation for safe, consistent security across all apps.\n */\n\nimport { timingSafeEqual } from \"crypto\";\n\n/** Regex for protocol-prefixed URLs */\nexport const URL_PROTOCOL_PATTERN = /(https?:\\/\\/|ftp:\\/\\/|www\\.)\\S+/i;\n\n/** Regex for bare domain names with common TLDs */\nexport const URL_DOMAIN_PATTERN =\n /\\b[\\w.-]+\\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\\b/i;\n\n/** Regex for HTML tags */\nexport const HTML_TAG_PATTERN = /<[^>]*>/;\n\n/**\n * Escape HTML special characters to prevent injection.\n * Use when inserting user content into HTML email templates or rendered HTML.\n */\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\n\n/** Check if a string contains protocol-prefixed URLs or bare domains */\nexport function containsUrls(str: string): boolean {\n return URL_PROTOCOL_PATTERN.test(str) || URL_DOMAIN_PATTERN.test(str);\n}\n\n/** Check if a string contains HTML tags */\nexport function containsHtml(str: string): boolean {\n return HTML_TAG_PATTERN.test(str);\n}\n\n/** Strip all HTML tags from a string */\nexport function stripHtml(str: string): string {\n return str.replace(/<[^>]*>/g, \"\");\n}\n\n/**\n * Defang URLs to prevent auto-linking in email clients.\n * Converts https://evil.com → hxxps://evil[.]com\n */\nexport function defangUrl(str: string): string {\n return str\n .replace(/https:\\/\\//gi, \"hxxps://\")\n .replace(/http:\\/\\//gi, \"hxxp://\")\n .replace(/ftp:\\/\\//gi, \"fxp://\")\n .replace(\n /\\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\\b/gi,\n \"[$1]\",\n );\n}\n\n/**\n * Sanitize user content for safe insertion into HTML email templates.\n * Escapes HTML entities AND defangs any URLs that slipped through validation.\n */\nexport function sanitizeForEmail(str: string): string {\n return escapeHtml(str);\n}\n\n// ═══════════════════════════════════════════════════════════════\n// API SECURITY UTILITIES\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Constant-time string comparison to prevent timing side-channel attacks.\n * Use for comparing secrets, tokens, API keys, HMAC signatures, etc.\n *\n * Returns false (not throws) for length mismatches — still constant-time\n * relative to the shorter string to avoid leaking length info.\n *\n * @example\n * ```typescript\n * if (!constantTimeEqual(providedToken, expectedSecret)) {\n * return { status: 401, error: 'Invalid token' }\n * }\n * ```\n */\nexport function constantTimeEqual(a: string, b: string): boolean {\n try {\n const aBuf = Buffer.from(a, \"utf-8\");\n const bBuf = Buffer.from(b, \"utf-8\");\n if (aBuf.length !== bBuf.length) return false;\n return timingSafeEqual(aBuf, bBuf);\n } catch {\n return false;\n }\n}\n\n/**\n * Sanitize an error for client-facing API responses.\n *\n * - 4xx errors: returns the actual message (client needs to know what went wrong)\n * - 5xx errors: returns a generic message (never leak internals to clients)\n * - Development mode: optionally includes stack trace for debugging\n *\n * @example\n * ```typescript\n * catch (error) {\n * const { message, code } = sanitizeApiError(error, 500)\n * return Response.json({ error: message, code }, { status: 500 })\n * }\n * ```\n */\nexport function sanitizeApiError(\n error: unknown,\n statusCode: number,\n isDevelopment = false,\n): { message: string; code?: string; stack?: string } {\n // Client errors — safe to expose the message\n if (statusCode >= 400 && statusCode < 500) {\n const message =\n error instanceof Error ? error.message : String(error || \"Bad request\");\n return { message };\n }\n\n // Server errors — generic message to clients, real error in logs only\n const result: { message: string; code: string; stack?: string } = {\n message: \"An internal error occurred. Please try again later.\",\n code: \"INTERNAL_ERROR\",\n };\n\n if (isDevelopment && error instanceof Error) {\n result.stack = error.stack;\n }\n\n return result;\n}\n\n/**\n * Extract a correlation/request ID from standard headers, or generate one.\n *\n * Checks (in order): X-Request-ID, X-Correlation-ID, then falls back to\n * crypto.randomUUID(). Works with any headers-like object (plain object,\n * Headers API, or a getter function).\n *\n * @example\n * ```typescript\n * // With Next.js request\n * const id = getCorrelationId((name) => request.headers.get(name))\n *\n * // With plain object\n * const id = getCorrelationId({ 'x-request-id': 'abc-123' })\n * ```\n */\nexport function getCorrelationId(\n headers:\n | Record<string, string | string[] | undefined>\n | ((name: string) => string | null | undefined),\n): string {\n const get =\n typeof headers === \"function\"\n ? headers\n : (name: string) => {\n const val = headers[name] ?? headers[name.toLowerCase()];\n return Array.isArray(val) ? val[0] : val;\n };\n\n return (\n get(\"x-request-id\") ||\n get(\"X-Request-ID\") ||\n get(\"x-correlation-id\") ||\n get(\"X-Correlation-ID\") ||\n crypto.randomUUID()\n );\n}\n","/**\n * Feature Flag System\n *\n * Generic, type-safe feature flag builder for staged rollout.\n * Each app defines its own flags; this module provides the\n * infrastructure for evaluating them by environment.\n *\n * @example\n * ```typescript\n * // Define your app's flags\n * const flags = createFeatureFlags({\n * STRIPE_PAYMENTS: {\n * development: true,\n * staging: true,\n * production: { envVar: 'ENABLE_PAYMENTS' },\n * },\n * PUBLIC_SIGNUP: {\n * development: true,\n * staging: false,\n * production: { envVar: 'ENABLE_PUBLIC_SIGNUP' },\n * },\n * AI_FEATURES: {\n * development: true,\n * staging: { envVar: 'ENABLE_AI' },\n * production: false,\n * },\n * })\n *\n * // Evaluate at runtime\n * const resolved = flags.resolve() // reads NODE_ENV + DEPLOYMENT_STAGE\n * if (resolved.STRIPE_PAYMENTS) { ... }\n *\n * // Check a single flag\n * if (flags.isEnabled('AI_FEATURES')) { ... }\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\nexport type DeploymentStage =\n | \"development\"\n | \"staging\"\n | \"preview\"\n | \"production\";\n\n/**\n * How a flag is resolved in a given environment.\n * - `true` / `false` — hardcoded on/off\n * - `{ envVar: string }` — read from environment variable (truthy = \"true\")\n * - `{ envVar: string, default: boolean }` — with fallback\n */\nexport type FlagValue = boolean | { envVar: string; default?: boolean };\n\n/**\n * Flag definition across deployment stages.\n */\nexport interface FlagDefinition {\n development: FlagValue;\n staging: FlagValue;\n production: FlagValue;\n}\n\n/**\n * Map of flag name to definition.\n */\nexport type FlagDefinitions<T extends string = string> = Record<\n T,\n FlagDefinition\n>;\n\n/**\n * Resolved flags — all booleans.\n */\nexport type ResolvedFlags<T extends string = string> = Record<T, boolean>;\n\n/**\n * Allowlist configuration for tester-gated access.\n */\nexport interface AllowlistConfig {\n /** Environment variable containing comma-separated emails */\n envVar?: string;\n /** Hardcoded fallback emails */\n fallback?: string[];\n}\n\n// ═══════════════════════════════════════════════════════════════\n// RESOLUTION LOGIC\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Detect the current deployment stage from environment variables.\n */\nexport function detectStage(): DeploymentStage {\n const stage = process.env.DEPLOYMENT_STAGE;\n if (stage === \"staging\" || stage === \"preview\") return stage;\n if (process.env.NODE_ENV === \"production\") return \"production\";\n return \"development\";\n}\n\n/**\n * Resolve a single flag value.\n */\nfunction resolveFlagValue(value: FlagValue): boolean {\n if (typeof value === \"boolean\") return value;\n const envValue = process.env[value.envVar];\n if (envValue === undefined || envValue === \"\") {\n return value.default ?? false;\n }\n return envValue === \"true\" || envValue === \"1\";\n}\n\n// ═══════════════════════════════════════════════════════════════\n// PUBLIC API\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Create a type-safe feature flag system.\n *\n * @param definitions - Flag definitions per environment\n * @returns Feature flag accessor with resolve() and isEnabled()\n */\nexport function createFeatureFlags<T extends string>(\n definitions: FlagDefinitions<T>,\n) {\n return {\n /**\n * Resolve all flags for the current environment.\n * Call this once at startup or per-request for dynamic flags.\n */\n resolve(stage?: DeploymentStage): ResolvedFlags<T> {\n const currentStage = stage ?? detectStage();\n const resolved = {} as ResolvedFlags<T>;\n\n for (const [key, def] of Object.entries(definitions) as [\n T,\n FlagDefinition,\n ][]) {\n // Preview uses staging config\n const stageKey = currentStage === \"preview\" ? \"staging\" : currentStage;\n resolved[key] = resolveFlagValue(def[stageKey]);\n }\n\n return resolved;\n },\n\n /**\n * Check if a single flag is enabled.\n */\n isEnabled(flag: T, stage?: DeploymentStage): boolean {\n const currentStage = stage ?? detectStage();\n const def = definitions[flag];\n const stageKey = currentStage === \"preview\" ? \"staging\" : currentStage;\n return resolveFlagValue(def[stageKey]);\n },\n\n /**\n * Get the flag definitions (for introspection/admin UI).\n */\n definitions,\n };\n}\n\n/**\n * Build an allowlist from environment variable + fallback emails.\n *\n * @example\n * ```typescript\n * const testers = buildAllowlist({\n * envVar: 'ADMIN_EMAILS',\n * fallback: ['admin@example.com'],\n * })\n * if (testers.includes(userEmail)) { ... }\n * ```\n */\nexport function buildAllowlist(config: AllowlistConfig): string[] {\n const fromEnv = config.envVar\n ? (process.env[config.envVar]\n ?.split(\",\")\n .map((e) => e.trim().toLowerCase())\n .filter(Boolean) ?? [])\n : [];\n\n const fallback = config.fallback?.map((e) => e.toLowerCase()) ?? [];\n\n // Deduplicate\n return [...new Set([...fromEnv, ...fallback])];\n}\n\n/**\n * Check if an email is in the allowlist (case-insensitive).\n */\nexport function isAllowlisted(email: string, allowlist: string[]): boolean {\n return allowlist.includes(email.toLowerCase());\n}\n","/**\n * Standalone Rate Limiter\n *\n * Framework-agnostic sliding window rate limiter that works with\n * any storage backend (Redis, memory, etc.). Apps provide a storage\n * adapter; this module handles the algorithm and types.\n *\n * @example\n * ```typescript\n * import {\n * checkRateLimit,\n * createMemoryRateLimitStore,\n * CommonRateLimits,\n * } from '@digilogiclabs/platform-core'\n *\n * const store = createMemoryRateLimitStore()\n *\n * const result = await checkRateLimit('api-call', 'user:123', CommonRateLimits.apiGeneral, { store })\n * if (!result.allowed) {\n * // Return 429 with result.retryAfterSeconds\n * }\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\n/** Configuration for a rate limit rule */\nexport interface RateLimitRule {\n /** Maximum requests allowed in the window */\n limit: number;\n /** Time window in seconds */\n windowSeconds: number;\n /** Different limit for authenticated users (optional) */\n authenticatedLimit?: number;\n /** Block duration in seconds when limit is exceeded (optional) */\n blockDurationSeconds?: number;\n}\n\n/** Result of a rate limit check */\nexport interface RateLimitCheckResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in the current window */\n remaining: number;\n /** Unix timestamp (ms) when the window resets */\n resetAt: number;\n /** Current request count in the window */\n current: number;\n /** The limit that was applied */\n limit: number;\n /** Seconds until retry is allowed (for 429 Retry-After header) */\n retryAfterSeconds: number;\n}\n\n/**\n * Storage backend for rate limiting.\n *\n * Implementations should support sorted-set-like semantics for\n * accurate sliding window rate limiting.\n */\nexport interface RateLimitStore {\n /**\n * Add an entry to the sliding window and return the current count.\n * Should atomically: remove entries older than windowStart,\n * add the new entry, and return the total count.\n */\n increment(\n key: string,\n windowMs: number,\n now: number,\n ): Promise<{ count: number }>;\n\n /** Check if a key is blocked and return remaining TTL */\n isBlocked(key: string): Promise<{ blocked: boolean; ttlMs: number }>;\n\n /** Set a temporary block on a key */\n setBlock(key: string, durationSeconds: number): Promise<void>;\n\n /** Remove all entries for a key (for reset/testing) */\n reset(key: string): Promise<void>;\n}\n\n/** Options for checkRateLimit */\nexport interface RateLimitOptions {\n /** Storage backend (defaults to in-memory if not provided) */\n store?: RateLimitStore;\n /** Whether the user is authenticated (uses authenticatedLimit if available) */\n isAuthenticated?: boolean;\n /** Logger for warnings/errors (optional) */\n logger?: {\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// COMMON RATE LIMIT PRESETS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Common rate limit rules for typical operations.\n * Apps can extend with domain-specific presets.\n *\n * @example\n * ```typescript\n * const APP_LIMITS = {\n * ...CommonRateLimits,\n * serverCreate: { limit: 5, windowSeconds: 3600, blockDurationSeconds: 3600 },\n * }\n * ```\n */\nexport const CommonRateLimits = {\n /** General API: 100/min, 200/min authenticated */\n apiGeneral: {\n limit: 100,\n windowSeconds: 60,\n authenticatedLimit: 200,\n },\n /** Admin actions: 100/min */\n adminAction: {\n limit: 100,\n windowSeconds: 60,\n },\n /** Auth attempts: 10/15min with 30min block */\n authAttempt: {\n limit: 10,\n windowSeconds: 900,\n blockDurationSeconds: 1800,\n },\n /** AI/expensive requests: 20/hour, 50/hour authenticated */\n aiRequest: {\n limit: 20,\n windowSeconds: 3600,\n authenticatedLimit: 50,\n },\n /** Public form submissions: 5/hour with 1hr block */\n publicForm: {\n limit: 5,\n windowSeconds: 3600,\n blockDurationSeconds: 3600,\n },\n /** Checkout/billing: 10/hour with 1hr block */\n checkout: {\n limit: 10,\n windowSeconds: 3600,\n blockDurationSeconds: 3600,\n },\n} as const satisfies Record<string, RateLimitRule>;\n\n// ═══════════════════════════════════════════════════════════════\n// IN-MEMORY STORE (for testing and fallback)\n// ═══════════════════════════════════════════════════════════════\n\ninterface WindowEntry {\n timestamps: number[];\n expiresAt: number;\n}\n\n/**\n * In-memory rate limit store for testing and graceful degradation.\n * Not suitable for multi-process or distributed environments.\n */\nexport function createMemoryRateLimitStore(): RateLimitStore {\n const windows = new Map<string, WindowEntry>();\n const blocks = new Map<string, number>(); // key -> expiry timestamp\n\n // Periodic cleanup (every 60s)\n const cleanupInterval = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of windows) {\n if (entry.expiresAt < now) windows.delete(key);\n }\n for (const [key, expiry] of blocks) {\n if (expiry < now) blocks.delete(key);\n }\n }, 60 * 1000);\n\n // Allow GC if store is abandoned\n if (cleanupInterval.unref) {\n cleanupInterval.unref();\n }\n\n return {\n async increment(\n key: string,\n windowMs: number,\n now: number,\n ): Promise<{ count: number }> {\n const windowStart = now - windowMs;\n\n let entry = windows.get(key);\n if (!entry) {\n entry = { timestamps: [], expiresAt: now + windowMs + 60000 };\n windows.set(key, entry);\n }\n\n // Remove old entries\n entry.timestamps = entry.timestamps.filter((t) => t > windowStart);\n\n // Add new entry\n entry.timestamps.push(now);\n entry.expiresAt = now + windowMs + 60000;\n\n return { count: entry.timestamps.length };\n },\n\n async isBlocked(key: string): Promise<{ blocked: boolean; ttlMs: number }> {\n const expiry = blocks.get(key);\n if (!expiry || expiry < Date.now()) {\n blocks.delete(key);\n return { blocked: false, ttlMs: 0 };\n }\n return { blocked: true, ttlMs: expiry - Date.now() };\n },\n\n async setBlock(key: string, durationSeconds: number): Promise<void> {\n blocks.set(key, Date.now() + durationSeconds * 1000);\n },\n\n async reset(key: string): Promise<void> {\n windows.delete(key);\n blocks.delete(`block:${key}`);\n blocks.delete(key);\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// CORE RATE LIMIT FUNCTION\n// ═══════════════════════════════════════════════════════════════\n\n/** Default in-memory store (lazily created) */\nlet defaultStore: RateLimitStore | undefined;\n\nfunction getDefaultStore(): RateLimitStore {\n if (!defaultStore) {\n defaultStore = createMemoryRateLimitStore();\n }\n return defaultStore;\n}\n\n/**\n * Check rate limit for an operation.\n *\n * @param operation - Name of the operation (e.g., 'server-create', 'api-call')\n * @param identifier - Who is making the request (e.g., 'user:123', 'ip:1.2.3.4')\n * @param rule - Rate limit configuration\n * @param options - Storage, auth status, logger\n * @returns Rate limit check result\n */\nexport async function checkRateLimit(\n operation: string,\n identifier: string,\n rule: RateLimitRule,\n options: RateLimitOptions = {},\n): Promise<RateLimitCheckResult> {\n const store = options.store ?? getDefaultStore();\n const limit =\n options.isAuthenticated && rule.authenticatedLimit\n ? rule.authenticatedLimit\n : rule.limit;\n\n const now = Date.now();\n const windowMs = rule.windowSeconds * 1000;\n const resetAt = now + windowMs;\n\n const key = `ratelimit:${operation}:${identifier}`;\n const blockKey = `ratelimit:block:${operation}:${identifier}`;\n\n try {\n // Check if currently blocked\n if (rule.blockDurationSeconds) {\n const blockStatus = await store.isBlocked(blockKey);\n if (blockStatus.blocked) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: now + blockStatus.ttlMs,\n current: limit + 1,\n limit,\n retryAfterSeconds: Math.ceil(blockStatus.ttlMs / 1000),\n };\n }\n }\n\n // Sliding window check + increment\n const { count } = await store.increment(key, windowMs, now);\n\n if (count > limit) {\n // Exceeded — the increment already added, so count includes this request\n options.logger?.warn(\"Rate limit exceeded\", {\n operation,\n identifier,\n current: count,\n limit,\n });\n\n // Set block if configured\n if (rule.blockDurationSeconds) {\n await store.setBlock(blockKey, rule.blockDurationSeconds);\n }\n\n return {\n allowed: false,\n remaining: 0,\n resetAt,\n current: count,\n limit,\n retryAfterSeconds: Math.ceil(windowMs / 1000),\n };\n }\n\n return {\n allowed: true,\n remaining: limit - count,\n resetAt,\n current: count,\n limit,\n retryAfterSeconds: 0,\n };\n } catch (error) {\n // On storage error, allow the request (fail-open)\n options.logger?.error(\"Rate limit check failed, allowing request\", {\n error: error instanceof Error ? error.message : String(error),\n operation,\n identifier,\n });\n\n return {\n allowed: true,\n remaining: limit,\n resetAt,\n current: 0,\n limit,\n retryAfterSeconds: 0,\n };\n }\n}\n\n/**\n * Get current rate limit status without incrementing the counter.\n */\nexport async function getRateLimitStatus(\n operation: string,\n identifier: string,\n rule: RateLimitRule,\n store?: RateLimitStore,\n): Promise<RateLimitCheckResult | null> {\n const s = store ?? getDefaultStore();\n const key = `ratelimit:${operation}:${identifier}`;\n const now = Date.now();\n const windowMs = rule.windowSeconds * 1000;\n\n try {\n // Use increment with a past timestamp to just clean + count\n // without adding a real entry — actually we need a read-only method\n // For memory store, this works. For Redis, apps should implement peek.\n const { count } = await s.increment(key, windowMs, now);\n // Remove the entry we just added (hacky but works for status check)\n // Better approach: just expose count. For now, return estimate.\n return {\n allowed: count <= rule.limit,\n remaining: Math.max(0, rule.limit - count),\n resetAt: now + windowMs,\n current: count,\n limit: rule.limit,\n retryAfterSeconds: count > rule.limit ? Math.ceil(windowMs / 1000) : 0,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Reset rate limit for an identifier (for admin/testing).\n */\nexport async function resetRateLimitForKey(\n operation: string,\n identifier: string,\n store?: RateLimitStore,\n): Promise<void> {\n const s = store ?? getDefaultStore();\n const key = `ratelimit:${operation}:${identifier}`;\n const blockKey = `ratelimit:block:${operation}:${identifier}`;\n await s.reset(key);\n await s.reset(blockKey);\n}\n\n// ═══════════════════════════════════════════════════════════════\n// HEADER HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Build standard rate limit headers for HTTP responses.\n */\nexport function buildRateLimitResponseHeaders(\n result: RateLimitCheckResult,\n): Record<string, string> {\n const headers: Record<string, string> = {\n \"X-RateLimit-Limit\": String(result.limit),\n \"X-RateLimit-Remaining\": String(result.remaining),\n \"X-RateLimit-Reset\": String(Math.ceil(result.resetAt / 1000)),\n };\n\n if (!result.allowed) {\n headers[\"Retry-After\"] = String(result.retryAfterSeconds);\n }\n\n return headers;\n}\n\n/**\n * Resolve a rate limit identifier from a request-like context.\n * Prefers user ID > email > IP for most accurate limiting.\n */\nexport function resolveIdentifier(\n session: { user?: { id?: string; email?: string } } | null | undefined,\n clientIp?: string,\n): { identifier: string; isAuthenticated: boolean } {\n if (session?.user?.id) {\n return { identifier: `user:${session.user.id}`, isAuthenticated: true };\n }\n if (session?.user?.email) {\n return {\n identifier: `email:${session.user.email}`,\n isAuthenticated: true,\n };\n }\n return { identifier: `ip:${clientIp ?? \"unknown\"}`, isAuthenticated: false };\n}\n","/**\n * Redis Rate Limit Store\n *\n * Production-ready `RateLimitStore` implementation using Redis sorted sets\n * for accurate sliding window rate limiting. Works with ioredis or any\n * Redis client that supports the required commands.\n *\n * @example\n * ```typescript\n * import Redis from 'ioredis'\n * import {\n * createRedisRateLimitStore,\n * checkRateLimit,\n * CommonRateLimits,\n * } from '@digilogiclabs/platform-core/auth'\n *\n * const redis = new Redis(process.env.REDIS_URL)\n * const store = createRedisRateLimitStore(redis, { keyPrefix: 'myapp:' })\n *\n * const result = await checkRateLimit('api-call', 'user:123', CommonRateLimits.apiGeneral, { store })\n * ```\n */\n\nimport type { RateLimitStore } from \"./rate-limiter\";\n\n/**\n * Minimal Redis client interface.\n *\n * Compatible with ioredis, node-redis v4, and @upstash/redis.\n * Only the commands used by the rate limiter are required.\n */\nexport interface RedisRateLimitClient {\n zremrangebyscore(\n key: string,\n min: number | string,\n max: number | string,\n ): Promise<number>;\n zadd(key: string, score: number, member: string): Promise<number>;\n zcard(key: string): Promise<number>;\n expire(key: string, seconds: number): Promise<number>;\n get(key: string): Promise<string | null>;\n ttl(key: string): Promise<number>;\n setex(key: string, seconds: number, value: string): Promise<string>;\n del(...keys: string[]): Promise<number>;\n}\n\nexport interface RedisRateLimitStoreOptions {\n /** Prefix for all Redis keys (e.g., 'myapp:') */\n keyPrefix?: string;\n}\n\n/**\n * Create a Redis-backed rate limit store using sorted sets.\n *\n * Uses the sliding window algorithm:\n * - Each request adds a timestamped entry to a sorted set\n * - Old entries (outside the window) are pruned on each check\n * - The set cardinality gives the request count\n *\n * Blocks use simple key-value with TTL.\n */\nexport function createRedisRateLimitStore(\n redis: RedisRateLimitClient,\n options: RedisRateLimitStoreOptions = {},\n): RateLimitStore {\n const prefix = options.keyPrefix ?? \"\";\n\n return {\n async increment(\n key: string,\n windowMs: number,\n now: number,\n ): Promise<{ count: number }> {\n const fullKey = `${prefix}${key}`;\n const windowStart = now - windowMs;\n const windowSeconds = Math.ceil(windowMs / 1000) + 60; // buffer\n\n // Remove expired entries\n await redis.zremrangebyscore(fullKey, 0, windowStart);\n\n // Count current entries before adding\n const current = await redis.zcard(fullKey);\n\n // Add new entry with unique member\n const member = `${now}:${Math.random().toString(36).slice(2, 10)}`;\n await redis.zadd(fullKey, now, member);\n\n // Set TTL so Redis auto-cleans abandoned keys\n await redis.expire(fullKey, windowSeconds);\n\n return { count: current + 1 };\n },\n\n async isBlocked(key: string): Promise<{ blocked: boolean; ttlMs: number }> {\n const fullKey = `${prefix}${key}`;\n const value = await redis.get(fullKey);\n\n if (!value) {\n return { blocked: false, ttlMs: 0 };\n }\n\n const ttlSeconds = await redis.ttl(fullKey);\n if (ttlSeconds <= 0) {\n return { blocked: false, ttlMs: 0 };\n }\n\n return { blocked: true, ttlMs: ttlSeconds * 1000 };\n },\n\n async setBlock(key: string, durationSeconds: number): Promise<void> {\n const fullKey = `${prefix}${key}`;\n await redis.setex(fullKey, durationSeconds, \"1\");\n },\n\n async reset(key: string): Promise<void> {\n const fullKey = `${prefix}${key}`;\n await redis.del(fullKey);\n },\n };\n}\n","/**\n * Audit Logging System\n *\n * Framework-agnostic audit logging for security-sensitive operations.\n * Provides structured audit events with actor, action, resource, and\n * outcome tracking. Apps provide their own persistence (Redis, DB, etc.)\n * via a simple callback; this module handles the event model and helpers.\n *\n * @example\n * ```typescript\n * import { createAuditLogger, StandardAuditActions } from '@digilogiclabs/platform-core'\n *\n * const audit = createAuditLogger({\n * persist: async (record) => {\n * await redis.setex(`audit:${record.id}`, 90 * 86400, JSON.stringify(record))\n * },\n * })\n *\n * await audit.log({\n * actor: { id: userId, email, type: 'user' },\n * action: StandardAuditActions.LOGIN_SUCCESS,\n * outcome: 'success',\n * })\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════\n\n/** Who performed the action */\nexport interface OpsAuditActor {\n id: string;\n email?: string;\n type: \"user\" | \"admin\" | \"system\" | \"api_key\" | \"anonymous\";\n sessionId?: string;\n}\n\n/** What resource was affected */\nexport interface OpsAuditResource {\n type: string;\n id: string;\n name?: string;\n}\n\n/** The audit event to log */\nexport interface OpsAuditEvent {\n actor: OpsAuditActor;\n action: string;\n resource?: OpsAuditResource;\n outcome: \"success\" | \"failure\" | \"blocked\" | \"pending\";\n metadata?: Record<string, unknown>;\n reason?: string;\n}\n\n/** Complete audit record with context */\nexport interface OpsAuditRecord extends OpsAuditEvent {\n id: string;\n timestamp: string;\n ip?: string;\n userAgent?: string;\n requestId?: string;\n duration?: number;\n}\n\n/** Options for creating an audit logger */\nexport interface OpsAuditLoggerOptions {\n /**\n * Persist an audit record to storage (Redis, DB, etc.).\n * Called after console logging. Errors are caught and logged,\n * never thrown — audit failures must not break operations.\n */\n persist?: (record: OpsAuditRecord) => Promise<void>;\n\n /**\n * Console logger for structured output.\n * Defaults to console.log/console.warn.\n */\n logger?: {\n info: (msg: string, meta?: Record<string, unknown>) => void;\n warn: (msg: string, meta?: Record<string, unknown>) => void;\n error: (msg: string, meta?: Record<string, unknown>) => void;\n };\n}\n\n/** Request-like object for extracting IP, user agent, request ID */\nexport interface AuditRequest {\n headers: {\n get(name: string): string | null;\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// STANDARD AUDIT ACTIONS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Standard audit action constants.\n * Apps should extend with domain-specific actions:\n *\n * @example\n * ```typescript\n * const AppAuditActions = {\n * ...StandardAuditActions,\n * SERVER_CREATE: 'game.server.create',\n * SERVER_DELETE: 'game.server.delete',\n * } as const\n * ```\n */\nexport const StandardAuditActions = {\n // Authentication\n LOGIN_SUCCESS: \"auth.login.success\",\n LOGIN_FAILURE: \"auth.login.failure\",\n LOGOUT: \"auth.logout\",\n SESSION_REFRESH: \"auth.session.refresh\",\n PASSWORD_CHANGE: \"auth.password.change\",\n PASSWORD_RESET: \"auth.password.reset\",\n\n // Billing\n CHECKOUT_START: \"billing.checkout.start\",\n CHECKOUT_COMPLETE: \"billing.checkout.complete\",\n SUBSCRIPTION_CREATE: \"billing.subscription.create\",\n SUBSCRIPTION_CANCEL: \"billing.subscription.cancel\",\n SUBSCRIPTION_UPDATE: \"billing.subscription.update\",\n PAYMENT_FAILED: \"billing.payment.failed\",\n\n // Admin\n ADMIN_LOGIN: \"admin.login\",\n ADMIN_USER_VIEW: \"admin.user.view\",\n ADMIN_USER_UPDATE: \"admin.user.update\",\n ADMIN_CONFIG_CHANGE: \"admin.config.change\",\n\n // Security Events\n RATE_LIMIT_EXCEEDED: \"security.rate_limit.exceeded\",\n INVALID_INPUT: \"security.input.invalid\",\n UNAUTHORIZED_ACCESS: \"security.access.unauthorized\",\n OWNERSHIP_VIOLATION: \"security.ownership.violation\",\n WEBHOOK_SIGNATURE_INVALID: \"security.webhook.signature_invalid\",\n\n // Data\n DATA_EXPORT: \"data.export\",\n DATA_DELETE: \"data.delete\",\n DATA_UPDATE: \"data.update\",\n} as const;\n\nexport type StandardAuditActionType =\n (typeof StandardAuditActions)[keyof typeof StandardAuditActions];\n\n// ═══════════════════════════════════════════════════════════════\n// REQUEST CONTEXT HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Extract client IP from request headers.\n * Checks common proxy headers in order of reliability.\n */\nexport function extractAuditIp(request?: AuditRequest): string | undefined {\n if (!request) return undefined;\n\n const headers = [\n \"cf-connecting-ip\", // Cloudflare\n \"x-real-ip\", // Nginx\n \"x-forwarded-for\", // Standard proxy\n \"x-client-ip\", // Apache\n ];\n\n for (const header of headers) {\n const value = request.headers.get(header);\n if (value) {\n return value.split(\",\")[0]?.trim();\n }\n }\n\n return undefined;\n}\n\n/**\n * Extract user agent from request.\n */\nexport function extractAuditUserAgent(\n request?: AuditRequest,\n): string | undefined {\n return request?.headers.get(\"user-agent\") ?? undefined;\n}\n\n/**\n * Extract or generate request ID.\n */\nexport function extractAuditRequestId(request?: AuditRequest): string {\n return request?.headers.get(\"x-request-id\") ?? crypto.randomUUID();\n}\n\n/**\n * Create an AuditActor from a session object.\n * Works with Auth.js/NextAuth session shape.\n */\nexport function createAuditActor(\n session:\n | {\n user?: { id?: string; email?: string | null };\n }\n | null\n | undefined,\n): OpsAuditActor {\n if (!session?.user) {\n return { id: \"anonymous\", type: \"anonymous\" };\n }\n\n return {\n id: session.user.id ?? session.user.email ?? \"unknown\",\n email: session.user.email ?? undefined,\n type: \"user\",\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUDIT LOGGER FACTORY\n// ═══════════════════════════════════════════════════════════════\n\nconst defaultLogger = {\n info: (msg: string, meta?: Record<string, unknown>) =>\n console.log(msg, meta ? JSON.stringify(meta) : \"\"),\n warn: (msg: string, meta?: Record<string, unknown>) =>\n console.warn(msg, meta ? JSON.stringify(meta) : \"\"),\n error: (msg: string, meta?: Record<string, unknown>) =>\n console.error(msg, meta ? JSON.stringify(meta) : \"\"),\n};\n\n/**\n * Create an audit logger instance.\n *\n * @param options - Persistence callback and optional logger\n * @returns Audit logger with log() and createTimedAudit() methods\n */\nexport function createAuditLogger(options: OpsAuditLoggerOptions = {}) {\n const { persist, logger = defaultLogger } = options;\n\n /**\n * Log an audit event.\n */\n async function log(\n event: OpsAuditEvent,\n request?: AuditRequest,\n ): Promise<OpsAuditRecord> {\n const record: OpsAuditRecord = {\n id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n ip: extractAuditIp(request),\n userAgent: extractAuditUserAgent(request),\n requestId: extractAuditRequestId(request),\n ...event,\n };\n\n // Always log to console/structured logger\n const logFn =\n event.outcome === \"failure\" || event.outcome === \"blocked\"\n ? logger.warn\n : logger.info;\n\n logFn(`[AUDIT] ${event.action}`, {\n auditId: record.id,\n actor: record.actor,\n action: record.action,\n resource: record.resource,\n outcome: record.outcome,\n ip: record.ip,\n metadata: record.metadata,\n reason: record.reason,\n });\n\n // Persist if configured\n if (persist) {\n try {\n await persist(record);\n } catch (error) {\n logger.error(\"Failed to persist audit log\", {\n error: error instanceof Error ? error.message : String(error),\n auditId: record.id,\n });\n }\n }\n\n return record;\n }\n\n /**\n * Create a timed audit that tracks operation duration.\n * Call success(), failure(), or blocked() when the operation completes.\n */\n function createTimedAudit(\n event: Omit<OpsAuditEvent, \"outcome\">,\n request?: AuditRequest,\n ): {\n success: (metadata?: Record<string, unknown>) => Promise<OpsAuditRecord>;\n failure: (\n reason: string,\n metadata?: Record<string, unknown>,\n ) => Promise<OpsAuditRecord>;\n blocked: (\n reason: string,\n metadata?: Record<string, unknown>,\n ) => Promise<OpsAuditRecord>;\n } {\n const startTime = Date.now();\n\n return {\n success: async (metadata?: Record<string, unknown>) => {\n return log(\n {\n ...event,\n outcome: \"success\",\n metadata: {\n ...event.metadata,\n ...metadata,\n durationMs: Date.now() - startTime,\n },\n },\n request,\n );\n },\n failure: async (reason: string, metadata?: Record<string, unknown>) => {\n return log(\n {\n ...event,\n outcome: \"failure\",\n reason,\n metadata: {\n ...event.metadata,\n ...metadata,\n durationMs: Date.now() - startTime,\n },\n },\n request,\n );\n },\n blocked: async (reason: string, metadata?: Record<string, unknown>) => {\n return log(\n {\n ...event,\n outcome: \"blocked\",\n reason,\n metadata: {\n ...event.metadata,\n ...metadata,\n durationMs: Date.now() - startTime,\n },\n },\n request,\n );\n },\n };\n }\n\n return { log, createTimedAudit };\n}\n","/**\n * API Utilities\n *\n * Framework-agnostic error codes, error class, response types,\n * and database error mapping for consistent API behavior across apps.\n */\n\n/** Standard API error codes */\nexport const ApiErrorCode = {\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n FORBIDDEN: \"FORBIDDEN\",\n NOT_FOUND: \"NOT_FOUND\",\n CONFLICT: \"CONFLICT\",\n RATE_LIMIT_EXCEEDED: \"RATE_LIMIT_EXCEEDED\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n DATABASE_ERROR: \"DATABASE_ERROR\",\n EXTERNAL_SERVICE_ERROR: \"EXTERNAL_SERVICE_ERROR\",\n CONFIGURATION_ERROR: \"CONFIGURATION_ERROR\",\n} as const;\n\nexport type ApiErrorCodeType = (typeof ApiErrorCode)[keyof typeof ApiErrorCode];\n\n/** Custom API Error class with status code and error code */\nexport class ApiError extends Error {\n public readonly statusCode: number;\n public readonly code: ApiErrorCodeType;\n public readonly details?: unknown;\n\n constructor(\n statusCode: number,\n message: string,\n code: ApiErrorCodeType = ApiErrorCode.INTERNAL_ERROR,\n details?: unknown,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.statusCode = statusCode;\n this.code = code;\n this.details = details;\n }\n}\n\n/** Type guard for ApiError */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\n/** Pre-built error factories */\nexport const CommonApiErrors = {\n unauthorized: (msg = \"Unauthorized\") =>\n new ApiError(401, msg, ApiErrorCode.UNAUTHORIZED),\n forbidden: (msg = \"Forbidden\") =>\n new ApiError(403, msg, ApiErrorCode.FORBIDDEN),\n notFound: (resource = \"Resource\") =>\n new ApiError(404, `${resource} not found`, ApiErrorCode.NOT_FOUND),\n conflict: (msg = \"Resource already exists\") =>\n new ApiError(409, msg, ApiErrorCode.CONFLICT),\n rateLimitExceeded: (msg = \"Rate limit exceeded\") =>\n new ApiError(429, msg, ApiErrorCode.RATE_LIMIT_EXCEEDED),\n validationError: (details?: unknown) =>\n new ApiError(\n 400,\n \"Validation failed\",\n ApiErrorCode.VALIDATION_ERROR,\n details,\n ),\n internalError: (msg = \"Internal server error\") =>\n new ApiError(500, msg, ApiErrorCode.INTERNAL_ERROR),\n};\n\n/** PostgreSQL error code → HTTP status mapping */\nexport const PG_ERROR_MAP: Record<\n string,\n { status: number; code: ApiErrorCodeType; message: string }\n> = {\n \"23505\": {\n status: 409,\n code: ApiErrorCode.CONFLICT,\n message: \"Resource already exists\",\n },\n \"23503\": {\n status: 404,\n code: ApiErrorCode.NOT_FOUND,\n message: \"Referenced resource not found\",\n },\n PGRST116: {\n status: 404,\n code: ApiErrorCode.NOT_FOUND,\n message: \"Resource not found\",\n },\n};\n\n/**\n * Classify any error into a standardized shape.\n * Framework-agnostic — returns a plain object, not an HTTP response.\n */\nexport function classifyError(\n error: unknown,\n isDev = false,\n): {\n status: number;\n body: { error: string; code: string; details?: unknown };\n} {\n // ApiError\n if (isApiError(error)) {\n return {\n status: error.statusCode,\n body: { error: error.message, code: error.code, details: error.details },\n };\n }\n\n // Zod-like validation errors (duck typing — has .issues array)\n if (\n error &&\n typeof error === \"object\" &&\n \"issues\" in error &&\n Array.isArray((error as Record<string, unknown>).issues)\n ) {\n return {\n status: 400,\n body: {\n error: \"Validation failed\",\n code: ApiErrorCode.VALIDATION_ERROR,\n details: (\n (error as Record<string, unknown>).issues as Array<{\n path?: string[];\n message?: string;\n }>\n ).map((i) => ({\n field: i.path?.join(\".\"),\n message: i.message,\n })),\n },\n };\n }\n\n // PostgreSQL errors (duck typing — has .code string property)\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as Record<string, unknown>).code === \"string\"\n ) {\n const pgCode = (error as Record<string, unknown>).code as string;\n const mapped = PG_ERROR_MAP[pgCode];\n if (mapped) {\n return {\n status: mapped.status,\n body: { error: mapped.message, code: mapped.code },\n };\n }\n return {\n status: 500,\n body: {\n error: \"Database error\",\n code: ApiErrorCode.DATABASE_ERROR,\n details: isDev ? (error as Record<string, unknown>).message : undefined,\n },\n };\n }\n\n // Generic Error\n if (error instanceof Error) {\n return {\n status: 500,\n body: {\n error: isDev ? error.message : \"Internal server error\",\n code: ApiErrorCode.INTERNAL_ERROR,\n details: isDev ? error.stack : undefined,\n },\n };\n }\n\n // Unknown\n return {\n status: 500,\n body: {\n error: \"An unexpected error occurred\",\n code: ApiErrorCode.INTERNAL_ERROR,\n },\n };\n}\n\n/** Standard success response shape */\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n message?: string;\n timestamp?: string;\n}\n\n/** Standard paginated response shape */\nexport interface ApiPaginatedResponse<T = unknown> extends ApiSuccessResponse<\n T[]\n> {\n pagination: {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasMore: boolean;\n };\n}\n\n/** Build a pagination metadata object */\nexport function buildPagination(page: number, limit: number, total: number) {\n return {\n page,\n limit,\n total,\n totalPages: Math.ceil(total / limit),\n hasMore: page * limit < total,\n };\n}\n","/**\n * Next.js API Route Helpers\n *\n * Shared utilities for Next.js API routes that all apps need.\n * These wrap platform-core's framework-agnostic primitives into\n * Next.js-specific patterns (NextResponse, NextRequest).\n *\n * @example\n * ```typescript\n * import {\n * enforceRateLimit,\n * zodErrorResponse,\n * errorResponse,\n * extractBearerToken,\n * isValidBearerToken,\n * } from '@digilogiclabs/platform-core/auth'\n *\n * export async function POST(request: NextRequest) {\n * const rl = await enforceRateLimit(request, 'submit', CommonRateLimits.publicForm)\n * if (rl) return rl\n *\n * const parsed = schema.safeParse(await request.json())\n * if (!parsed.success) return zodErrorResponse(parsed.error)\n *\n * try { ... }\n * catch (error) { return errorResponse(error) }\n * }\n * ```\n */\n\nimport { checkRateLimit, buildRateLimitResponseHeaders } from \"./rate-limiter\";\nimport type {\n RateLimitRule,\n RateLimitCheckResult,\n RateLimitOptions,\n} from \"./rate-limiter\";\nimport { extractClientIp } from \"./api-security\";\nimport { classifyError } from \"../api\";\nimport { constantTimeEqual } from \"../security\";\n\n// ═══════════════════════════════════════════════════════════════\n// RATE LIMITING\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Enforce rate limiting on a Next.js API request.\n *\n * Returns a 429 NextResponse if the limit is exceeded, or `null` if allowed.\n * Apps use this at the top of route handlers for early rejection.\n *\n * @example\n * ```typescript\n * const rateLimited = await enforceRateLimit(request, 'sync', CommonRateLimits.adminAction)\n * if (rateLimited) return rateLimited\n * ```\n */\nexport async function enforceRateLimit(\n request: { headers: { get(name: string): string | null } },\n operation: string,\n rule: RateLimitRule,\n options?: {\n /** Override identifier (default: extracted from x-forwarded-for) */\n identifier?: string;\n /** User ID for per-user limiting */\n userId?: string;\n /** Rate limit store and options */\n rateLimitOptions?: RateLimitOptions;\n },\n): Promise<Response | null> {\n const identifier =\n options?.identifier ??\n (options?.userId ? `user:${options.userId}` : undefined) ??\n `ip:${extractClientIp((name) => request.headers.get(name))}`;\n\n const isAuthenticated = !!options?.userId;\n\n const result = await checkRateLimit(operation, identifier, rule, {\n ...options?.rateLimitOptions,\n isAuthenticated,\n });\n\n if (!result.allowed) {\n const headers = buildRateLimitResponseHeaders(result);\n return new Response(\n JSON.stringify({\n error: \"Rate limit exceeded. Please try again later.\",\n retryAfter: result.retryAfterSeconds,\n }),\n {\n status: 429,\n headers: { \"Content-Type\": \"application/json\", ...headers },\n },\n );\n }\n\n return null;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// ERROR RESPONSES\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Convert any error into a structured JSON response.\n *\n * Uses platform-core's `classifyError()` to handle ApiError,\n * Zod errors, PostgreSQL errors, and generic errors consistently.\n *\n * @example\n * ```typescript\n * catch (error) {\n * return errorResponse(error)\n * }\n * ```\n */\nexport function errorResponse(\n error: unknown,\n options?: { isDevelopment?: boolean },\n): Response {\n const isDev =\n options?.isDevelopment ?? process.env.NODE_ENV === \"development\";\n const { status, body } = classifyError(error, isDev);\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n/**\n * Convert a Zod validation error into a user-friendly 400 response.\n *\n * Extracts the first issue and returns a clear error message\n * with the field path (e.g., \"email: Invalid email\").\n *\n * @example\n * ```typescript\n * const parsed = schema.safeParse(await request.json())\n * if (!parsed.success) return zodErrorResponse(parsed.error)\n * ```\n */\nexport function zodErrorResponse(error: {\n issues: Array<{ path: (string | number)[]; message: string }>;\n}): Response {\n const firstIssue = error.issues[0];\n const message = firstIssue\n ? `${firstIssue.path.join(\".\") || \"input\"}: ${firstIssue.message}`\n : \"Validation error\";\n return new Response(JSON.stringify({ error: message }), {\n status: 400,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n// ═══════════════════════════════════════════════════════════════\n// AUTH HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Extract a bearer token from the Authorization header.\n *\n * @returns The token string, or null if not present/malformed.\n */\nexport function extractBearerToken(request: {\n headers: { get(name: string): string | null };\n}): string | null {\n const auth = request.headers.get(\"authorization\");\n if (!auth?.startsWith(\"Bearer \")) return null;\n return auth.slice(7).trim() || null;\n}\n\n/**\n * Check if a request's bearer token matches a secret.\n * Uses timing-safe comparison to prevent timing attacks.\n *\n * @example\n * ```typescript\n * if (!isValidBearerToken(request, process.env.ADMIN_SECRET)) {\n * return new Response('Unauthorized', { status: 401 })\n * }\n * ```\n */\nexport function isValidBearerToken(\n request: { headers: { get(name: string): string | null } },\n secret: string | undefined,\n): boolean {\n if (!secret) return false;\n const token = extractBearerToken(request);\n if (!token) return false;\n return constantTimeEqual(token, secret);\n}\n","/**\n * Beta Client Utilities\n *\n * Shared client-side helpers for beta access management.\n * These run in the browser and talk to your app's beta API endpoints.\n *\n * Standardizes the pattern used across DLL, WIAN, and future apps:\n * - Fetch beta settings from API\n * - Validate invite codes\n * - Store/retrieve beta code across OAuth redirect flows (sessionStorage)\n *\n * @example\n * ```typescript\n * import {\n * fetchBetaSettings,\n * validateBetaCode,\n * storeBetaCode,\n * getStoredBetaCode,\n * clearStoredBetaCode,\n * } from '@digilogiclabs/platform-core/auth';\n *\n * // On sign-in page mount\n * const settings = await fetchBetaSettings();\n * if (settings.requireInviteCode) {\n * const result = await validateBetaCode(userInput);\n * if (result.valid) {\n * storeBetaCode(userInput); // Survives OAuth redirect\n * // Proceed with sign-in\n * }\n * }\n * ```\n */\n\nimport type { BetaSettings, BetaValidationResult } from \"../interfaces/IBeta\";\n\n// ═══════════════════════════════════════════════════════════════\n// CONFIGURATION\n// ═══════════════════════════════════════════════════════════════\n\nexport interface BetaClientConfig {\n /** Base URL for API calls (default: '' for same-origin) */\n baseUrl?: string;\n /** API endpoint for settings (default: '/api/beta-settings') */\n settingsEndpoint?: string;\n /** API endpoint for validation (default: '/api/validate-beta-code') */\n validateEndpoint?: string;\n /** sessionStorage key for storing beta code (default: 'beta_code') */\n storageKey?: string;\n /** Default settings to use when fetch fails */\n failSafeDefaults?: Partial<BetaSettings>;\n}\n\nconst DEFAULT_CONFIG: Required<BetaClientConfig> = {\n baseUrl: \"\",\n settingsEndpoint: \"/api/beta-settings\",\n validateEndpoint: \"/api/validate-beta-code\",\n storageKey: \"beta_code\",\n failSafeDefaults: {\n betaMode: true,\n requireInviteCode: true,\n betaMessage: \"\",\n },\n};\n\n// ═══════════════════════════════════════════════════════════════\n// CLIENT FUNCTIONS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Create a configured beta client with app-specific settings.\n *\n * @example\n * ```typescript\n * // DLL app\n * const betaClient = createBetaClient({\n * storageKey: 'dll_beta_code',\n * validateEndpoint: '/api/validate-beta-code',\n * });\n *\n * // WIAN app\n * const betaClient = createBetaClient({\n * storageKey: 'wian_beta_code',\n * validateEndpoint: '/api/validate-invite-code',\n * });\n * ```\n */\nexport function createBetaClient(config: BetaClientConfig = {}) {\n const cfg: Required<BetaClientConfig> = {\n ...DEFAULT_CONFIG,\n ...config,\n failSafeDefaults: {\n ...DEFAULT_CONFIG.failSafeDefaults,\n ...config.failSafeDefaults,\n },\n };\n\n return {\n fetchSettings: () => fetchBetaSettings(cfg),\n validateCode: (code: string) => validateBetaCode(code, cfg),\n storeCode: (code: string) => storeBetaCode(code, cfg),\n getStoredCode: () => getStoredBetaCode(cfg),\n clearStoredCode: () => clearStoredBetaCode(cfg),\n };\n}\n\n/**\n * Fetch beta settings from the server.\n * Returns fail-safe defaults if the fetch fails.\n */\nexport async function fetchBetaSettings(\n config: BetaClientConfig = {},\n): Promise<BetaSettings> {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n\n try {\n const response = await fetch(`${cfg.baseUrl}${cfg.settingsEndpoint}`, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" },\n cache: \"no-store\",\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch beta settings: ${response.status}`);\n }\n\n const data = await response.json();\n return {\n betaMode: data.betaMode ?? cfg.failSafeDefaults.betaMode ?? true,\n requireInviteCode:\n data.requireInviteCode ??\n cfg.failSafeDefaults.requireInviteCode ??\n true,\n betaMessage: data.betaMessage ?? cfg.failSafeDefaults.betaMessage ?? \"\",\n };\n } catch (error) {\n console.error(\"Error fetching beta settings:\", error);\n return {\n betaMode: cfg.failSafeDefaults.betaMode ?? true,\n requireInviteCode: cfg.failSafeDefaults.requireInviteCode ?? true,\n betaMessage: cfg.failSafeDefaults.betaMessage ?? \"\",\n };\n }\n}\n\n/**\n * Validate a beta invite code against the server.\n * Handles rate limiting (429) gracefully.\n */\nexport async function validateBetaCode(\n code: string,\n config: BetaClientConfig = {},\n): Promise<BetaValidationResult> {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n\n if (!code || code.trim().length < 3) {\n return {\n valid: false,\n message: \"Please enter a valid invite code.\",\n };\n }\n\n try {\n const response = await fetch(`${cfg.baseUrl}${cfg.validateEndpoint}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code: code.trim().toUpperCase() }),\n });\n\n if (response.status === 429) {\n return {\n valid: false,\n message: \"Too many attempts. Please try again later.\",\n };\n }\n\n if (!response.ok) {\n throw new Error(`Validation request failed: ${response.status}`);\n }\n\n return await response.json();\n } catch (error) {\n console.error(\"Error validating invite code:\", error);\n return {\n valid: false,\n message: \"Unable to validate code. Please try again.\",\n };\n }\n}\n\n/**\n * Store a validated beta code in sessionStorage.\n * Used to pass the code through OAuth redirect flows.\n */\nexport function storeBetaCode(\n code: string,\n config: BetaClientConfig = {},\n): void {\n const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;\n if (typeof window !== \"undefined\") {\n sessionStorage.setItem(key, code.trim().toUpperCase());\n }\n}\n\n/**\n * Retrieve stored beta code from sessionStorage.\n */\nexport function getStoredBetaCode(\n config: BetaClientConfig = {},\n): string | null {\n const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;\n if (typeof window !== \"undefined\") {\n return sessionStorage.getItem(key);\n }\n return null;\n}\n\n/**\n * Clear stored beta code from sessionStorage.\n */\nexport function clearStoredBetaCode(config: BetaClientConfig = {}): void {\n const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;\n if (typeof window !== \"undefined\") {\n sessionStorage.removeItem(key);\n }\n}\n","/**\n * Environment Variable Helpers\n *\n * Type-safe, fail-fast utilities for reading environment variables.\n * Every app needs these — extracted from DLL, WIAN, and OSS patterns.\n *\n * @example\n * ```typescript\n * import { getRequiredEnv, getOptionalEnv, getBoolEnv, validateEnvVars } from '@digilogiclabs/platform-core'\n *\n * const config = {\n * stripe: { secretKey: getRequiredEnv('STRIPE_SECRET_KEY') },\n * redis: { url: getOptionalEnv('REDIS_URL', '') },\n * features: { debug: getBoolEnv('DEBUG', false) },\n * }\n *\n * // Validate at startup\n * validateEnvVars({\n * required: ['STRIPE_SECRET_KEY', 'DATABASE_URL'],\n * requireOneOf: [['DATABASE_URL', 'SUPABASE_URL']], // at least one\n * validators: {\n * DATABASE_URL: (v) => v.startsWith('postgres') || 'must start with postgres://',\n * ADMIN_SECRET: (v) => v.length >= 32 || 'must be at least 32 characters',\n * },\n * })\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════\n// BASIC HELPERS\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Get a required environment variable.\n * Throws immediately if not set — fail-fast at startup.\n */\nexport function getRequiredEnv(key: string): string {\n const value = process.env[key];\n if (!value) {\n throw new Error(`Missing required environment variable: ${key}`);\n }\n return value;\n}\n\n/**\n * Get an optional environment variable with a default value.\n */\nexport function getOptionalEnv(key: string, defaultValue: string): string {\n return process.env[key] || defaultValue;\n}\n\n/**\n * Get a boolean environment variable.\n * Treats \"true\" and \"1\" as true, everything else as false.\n */\nexport function getBoolEnv(key: string, defaultValue = false): boolean {\n const value = process.env[key];\n if (value === undefined || value === \"\") return defaultValue;\n return value === \"true\" || value === \"1\";\n}\n\n/**\n * Get a numeric environment variable with a default.\n */\nexport function getIntEnv(key: string, defaultValue: number): number {\n const value = process.env[key];\n if (value === undefined || value === \"\") return defaultValue;\n const parsed = parseInt(value, 10);\n return isNaN(parsed) ? defaultValue : parsed;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// VALIDATION\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Configuration for environment validation.\n */\nexport interface EnvValidationConfig {\n /** Variables that must be set (non-empty) */\n required?: string[];\n /** Groups where at least one must be set (e.g., DATABASE_URL or SUPABASE_URL) */\n requireOneOf?: string[][];\n /** Custom validators — return true or an error message */\n validators?: Record<string, (value: string) => true | string>;\n}\n\n/**\n * Validation result with categorized errors.\n */\nexport interface EnvValidationResult {\n valid: boolean;\n missing: string[];\n invalid: { key: string; reason: string }[];\n missingOneOf: string[][];\n}\n\n/**\n * Validate environment variables against a configuration.\n *\n * @throws Error with details about all missing/invalid variables\n */\nexport function validateEnvVars(config: EnvValidationConfig): void {\n const result = checkEnvVars(config);\n\n if (!result.valid) {\n const lines: string[] = [];\n\n if (result.missing.length > 0) {\n lines.push(\n \"Missing required environment variables:\",\n ...result.missing.map((v) => ` - ${v}`),\n );\n }\n\n if (result.missingOneOf.length > 0) {\n for (const group of result.missingOneOf) {\n lines.push(`Missing one of: ${group.join(\" | \")}`);\n }\n }\n\n if (result.invalid.length > 0) {\n lines.push(\n \"Invalid environment variables:\",\n ...result.invalid.map((v) => ` - ${v.key}: ${v.reason}`),\n );\n }\n\n throw new Error(lines.join(\"\\n\"));\n }\n}\n\n/**\n * Check environment variables without throwing.\n * Returns a result object for custom error handling.\n */\nexport function checkEnvVars(config: EnvValidationConfig): EnvValidationResult {\n const missing: string[] = [];\n const invalid: { key: string; reason: string }[] = [];\n const missingOneOf: string[][] = [];\n\n // Check required\n if (config.required) {\n for (const key of config.required) {\n if (!process.env[key]) {\n missing.push(key);\n }\n }\n }\n\n // Check requireOneOf groups\n if (config.requireOneOf) {\n for (const group of config.requireOneOf) {\n const hasAny = group.some((key) => !!process.env[key]);\n if (!hasAny) {\n missingOneOf.push(group);\n }\n }\n }\n\n // Run custom validators (only on set variables)\n if (config.validators) {\n for (const [key, validator] of Object.entries(config.validators)) {\n const value = process.env[key];\n if (value) {\n const result = validator(value);\n if (result !== true) {\n invalid.push({ key, reason: result });\n }\n }\n }\n }\n\n return {\n valid:\n missing.length === 0 && invalid.length === 0 && missingOneOf.length === 0,\n missing,\n invalid,\n missingOneOf,\n };\n}\n\n/**\n * Get a redacted config summary for debugging.\n * Shows which variables are configured without exposing values.\n */\nexport function getEnvSummary(keys: string[]): Record<string, boolean> {\n const summary: Record<string, boolean> = {};\n for (const key of keys) {\n summary[key] = !!process.env[key];\n }\n return summary;\n}\n"],"mappings":"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","names":[]}