{"version":3,"sources":["../../src/theme/server.ts","../../src/server/request.ts","../../src/server/request-bridge.ts","../../src/theme/bridge.ts","../../src/theme/config.ts"],"sourcesContent":["import { getCurrentRequestOrNull } from \"../server/request\";\nimport { _setFarmThemeServerSnapshotResolver } from \"./bridge\";\nimport { resolveFarmThemeConfig } from \"./config\";\nimport type {\n  FarmThemeConfig,\n  FarmThemePreference,\n  FarmThemeSnapshot,\n  ResolvedFarmThemeConfig,\n} from \"./types\";\n\nlet defaultThemeConfig = resolveFarmThemeConfig(undefined);\n\nexport function _setDefaultFarmThemeConfig(\n  config: FarmThemeConfig | ResolvedFarmThemeConfig | false | undefined,\n  basePath = \"/\",\n): void {\n  defaultThemeConfig = resolveFarmThemeConfig(config, basePath);\n}\n\n// The theme preference is cosmetic, so a missing request context must never\n// crash a render: without a request to read the cookie from, the configured\n// default applies. Runtimes with partial AsyncLocalStorage support (for\n// example StackBlitz WebContainers) can lose the store mid-render, and a\n// thrown error here would also take down the error page itself.\nexport function getTheme(request: Request | null = getCurrentRequestOrNull()): FarmThemePreference {\n  if (!request) return defaultThemeConfig.default;\n  return readFarmThemePreference(request, defaultThemeConfig);\n}\n\nexport function getThemeSnapshot(\n  request: Request | null = getCurrentRequestOrNull(),\n): FarmThemeSnapshot {\n  const theme = request\n    ? readFarmThemePreference(request, defaultThemeConfig)\n    : defaultThemeConfig.default;\n  return {\n    theme,\n    resolvedTheme: theme === \"system\" ? undefined : theme,\n    mounted: false,\n  };\n}\n\nexport function readFarmThemePreference(\n  request: Request,\n  config: ResolvedFarmThemeConfig,\n): FarmThemePreference {\n  if (!config.enabled) return config.default;\n  const stored = readCookie(request.headers.get(\"cookie\"), config.storageKey);\n  return isFarmThemePreference(stored) ? stored : config.default;\n}\n\nfunction readCookie(cookieHeader: string | null, name: string): string | undefined {\n  if (!cookieHeader) return undefined;\n  for (const entry of cookieHeader.split(\";\")) {\n    const separator = entry.indexOf(\"=\");\n    if (separator < 0) continue;\n    const key = decodeCookieValue(entry.slice(0, separator).trim());\n    if (key !== name) continue;\n    return decodeCookieValue(entry.slice(separator + 1).trim());\n  }\n  return undefined;\n}\n\nfunction decodeCookieValue(value: string): string {\n  try {\n    return decodeURIComponent(value);\n  } catch {\n    return value;\n  }\n}\n\nfunction isFarmThemePreference(value: unknown): value is FarmThemePreference {\n  return value === \"light\" || value === \"dark\" || value === \"system\";\n}\n\n_setFarmThemeServerSnapshotResolver(() => {\n  try {\n    return getThemeSnapshot();\n  } catch {\n    const theme = defaultThemeConfig.default;\n    return {\n      theme,\n      resolvedTheme: theme === \"system\" ? undefined : theme,\n      mounted: false,\n    };\n  }\n});\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { Readable } from \"node:stream\";\nimport type { FarmRequest } from \"../types\";\nimport { _setCurrentRequestResolver } from \"./request-bridge\";\n\nconst REQUEST_STORAGE_KEY = Symbol.for(\"@farm.js/core/request-storage\");\n\nfunction getRequestStore(): AsyncLocalStorage<Request> {\n  const runtime = globalThis as typeof globalThis & Record<PropertyKey, unknown>;\n  const existing = runtime[REQUEST_STORAGE_KEY];\n  if (existing instanceof AsyncLocalStorage) {\n    return existing as AsyncLocalStorage<Request>;\n  }\n\n  const storage = new AsyncLocalStorage<Request>();\n  runtime[REQUEST_STORAGE_KEY] = storage;\n  return storage;\n}\n\nconst requestStore = getRequestStore();\n\n_setCurrentRequestResolver(() => requestStore.getStore());\n\nexport interface FarmRequestURLOptions {\n  origin?: string | URL;\n  trustProxy?: boolean;\n}\n\nexport function resolveFarmRequestURL(req: FarmRequest, options: FarmRequestURLOptions = {}): URL {\n  if (options.origin) {\n    return new URL(req.url || \"/\", options.origin);\n  }\n\n  const forwardedHost = options.trustProxy\n    ? firstForwardedHeaderValue(req.headers[\"x-forwarded-host\"])\n    : undefined;\n  const fallbackHost = firstForwardedHeaderValue(req.headers.host) || \"localhost\";\n  const forwardedProto = options.trustProxy\n    ? firstForwardedHeaderValue(req.headers[\"x-forwarded-proto\"])\n    : undefined;\n  const normalizedProto = forwardedProto?.toLowerCase();\n  const proto =\n    normalizedProto === \"https\" || normalizedProto === \"http\"\n      ? normalizedProto\n      : isEncryptedFarmRequest(req)\n        ? \"https\"\n        : \"http\";\n  return new URL(req.url || \"/\", resolveRequestOrigin(proto, forwardedHost, fallbackHost));\n}\n\nexport function createWebRequestFromFarmRequest(\n  req: FarmRequest,\n  options: FarmRequestURLOptions = {},\n): Request {\n  const fullUrl = resolveFarmRequestURL(req, options).toString();\n\n  const headers = new Headers();\n  for (const [key, value] of Object.entries(req.headers)) {\n    if (value == null) {\n      continue;\n    }\n\n    if (Array.isArray(value)) {\n      for (const item of value) {\n        headers.append(key, item);\n      }\n      continue;\n    }\n\n    headers.set(key, value);\n  }\n\n  const method = (req.method || \"GET\").toUpperCase();\n  const init: RequestInit & { duplex?: \"half\" } = {\n    method: req.method,\n    headers,\n  };\n\n  if (method !== \"GET\" && method !== \"HEAD\") {\n    init.body = Readable.toWeb(req) as ReadableStream<Uint8Array>;\n    init.duplex = \"half\";\n  }\n\n  return new Request(fullUrl, init);\n}\n\nfunction isEncryptedFarmRequest(req: FarmRequest): boolean {\n  return Boolean((req.socket as { encrypted?: boolean } | undefined)?.encrypted);\n}\n\nfunction firstForwardedHeaderValue(value: string | string[] | undefined): string | undefined {\n  const first = Array.isArray(value) ? value[0] : value;\n  const token = first?.split(\",\", 1)[0]?.trim();\n  return token || undefined;\n}\n\nfunction resolveRequestOrigin(proto: \"http\" | \"https\", host: string | undefined, fallback: string) {\n  for (const candidate of [host, fallback, \"localhost\"]) {\n    if (!candidate) continue;\n    if (/[\\s/?#@\\\\]/u.test(candidate)) continue;\n    try {\n      const url = new URL(`${proto}://${candidate}`);\n      if (url.username || url.password || url.pathname !== \"/\" || url.search || url.hash) continue;\n      return url.origin;\n    } catch {\n      // Try the next host instead of turning an untrusted proxy header into a 500.\n    }\n  }\n  return `${proto}://localhost`;\n}\n\nexport async function _runWithCurrentRequest<T>(\n  request: Request,\n  fn: () => Promise<T> | T,\n): Promise<T> {\n  return requestStore.run(request, fn);\n}\n\nexport function getCurrentRequest(): Request {\n  const request = requestStore.getStore();\n  if (!request) {\n    throw new Error(\n      \"No current request is available. getCurrentRequest() can only be used during server rendering.\",\n    );\n  }\n\n  return request;\n}\n\n// Some runtimes (StackBlitz WebContainers among them) lose AsyncLocalStorage\n// context across async boundaries mid-render. Callers whose feature can\n// degrade gracefully should use this instead of getCurrentRequest() so a\n// missing store never turns into a 500.\nexport function getCurrentRequestOrNull(): Request | null {\n  return requestStore.getStore() ?? null;\n}\n","type CurrentRequestResolver = () => Request | undefined;\n\nconst CURRENT_REQUEST_RESOLVER_KEY = Symbol.for(\"farm.currentRequestResolver\");\n\ntype GlobalWithCurrentRequestResolver = typeof globalThis & {\n  [CURRENT_REQUEST_RESOLVER_KEY]?: CurrentRequestResolver;\n};\n\nfunction getGlobalState(): GlobalWithCurrentRequestResolver {\n  return globalThis as GlobalWithCurrentRequestResolver;\n}\n\nexport function _setCurrentRequestResolver(resolver: CurrentRequestResolver | undefined): void {\n  getGlobalState()[CURRENT_REQUEST_RESOLVER_KEY] = resolver;\n}\n\nexport function _resolveCurrentRequest(): Request | undefined {\n  return getGlobalState()[CURRENT_REQUEST_RESOLVER_KEY]?.();\n}\n","import type { FarmThemeSnapshot } from \"./types\";\n\nconst FALLBACK_SERVER_SNAPSHOT: FarmThemeSnapshot = Object.freeze({\n  theme: \"system\",\n  resolvedTheme: undefined,\n  mounted: false,\n});\n\nconst FARM_THEME_SERVER_SNAPSHOT_RESOLVER = Symbol.for(\"farm.js.theme.server-snapshot-resolver\");\n\ntype FarmThemeBridgeGlobal = typeof globalThis & {\n  [FARM_THEME_SERVER_SNAPSHOT_RESOLVER]?: () => FarmThemeSnapshot;\n};\n\nexport function _setFarmThemeServerSnapshotResolver(resolver: () => FarmThemeSnapshot): void {\n  (globalThis as FarmThemeBridgeGlobal)[FARM_THEME_SERVER_SNAPSHOT_RESOLVER] = resolver;\n}\n\nexport function getFarmThemeServerSnapshot(): FarmThemeSnapshot {\n  return (\n    (globalThis as FarmThemeBridgeGlobal)[FARM_THEME_SERVER_SNAPSHOT_RESOLVER]?.() ??\n    FALLBACK_SERVER_SNAPSHOT\n  );\n}\n","import type { FarmThemeConfig, ResolvedFarmThemeConfig } from \"./types\";\n\nexport const DEFAULT_FARM_THEME_STORAGE_KEY = \"farm-theme\";\n\nconst STORAGE_KEY_PATTERN = /^[A-Za-z0-9._-]+$/;\n\nexport function resolveFarmThemeConfig(\n  config: FarmThemeConfig | ResolvedFarmThemeConfig | false | undefined,\n  basePath = \"/\",\n): ResolvedFarmThemeConfig {\n  if (config && \"enabled\" in config) {\n    return config;\n  }\n\n  if (!config) {\n    return {\n      enabled: false,\n      default: \"system\",\n      storageKey: DEFAULT_FARM_THEME_STORAGE_KEY,\n      cookiePath: normalizeCookiePath(basePath),\n    };\n  }\n\n  const storageKey = config.storageKey?.trim() || DEFAULT_FARM_THEME_STORAGE_KEY;\n  if (!STORAGE_KEY_PATTERN.test(storageKey)) {\n    throw new Error(\n      \"theme.storageKey may only contain letters, numbers, dots, underscores, and hyphens.\",\n    );\n  }\n\n  const defaultTheme = config.default ?? \"system\";\n  if (defaultTheme !== \"light\" && defaultTheme !== \"dark\" && defaultTheme !== \"system\") {\n    throw new Error('theme.default must be \"light\", \"dark\", or \"system\".');\n  }\n\n  return {\n    enabled: true,\n    default: defaultTheme,\n    storageKey,\n    cookiePath: normalizeCookiePath(basePath),\n  };\n}\n\nfunction normalizeCookiePath(basePath: string): string {\n  const normalized = `/${basePath}`.replace(/\\/{2,}/g, \"/\");\n  if (normalized === \"/\") return normalized;\n  return normalized.replace(/\\/$/, \"\");\n}\n"],"mappings":"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","names":[]}