{"version":3,"file":"shared-B0fOuAmY.cjs","names":[],"sources":["../../src/adapters/shared.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readdir, copyFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { scanRoutes } from \"../router/route-scanner.js\";\nimport type { AdapterOptions } from \"./index.js\";\nimport type { PageRoute } from \"../router/route-scanner.js\";\n\n/**\n * Shared helper: copy a directory recursively.\n */\nexport async function copyStatic(from: string, to: string): Promise<void> {\n  await mkdir(to, { recursive: true });\n  const entries = await readdir(from, { withFileTypes: true });\n  for (const entry of entries) {\n    const src = join(from, entry.name);\n    const dest = join(to, entry.name);\n    if (entry.isDirectory()) {\n      await copyStatic(src, dest);\n    } else {\n      await copyFile(src, dest);\n    }\n  }\n}\n\n/** Adds every module the SSR runtime may import for a page to the registry. */\nfunction collectPageModules(\n  page: PageRoute,\n  moduleSet: Set<string>,\n  actionPathsByPage: Map<string, Set<string>>,\n): void {\n  moduleSet.add(page.pagePath);\n  if (page.dataPath) moduleSet.add(page.dataPath);\n  if (page.loadingPath) moduleSet.add(page.loadingPath);\n  for (const layout of page.layouts) {\n    moduleSet.add(layout);\n    const layoutDataPath = layout.replace(/layout\\.ts$/, \"layout.data.ts\");\n    if (layoutDataPath !== layout && existsSync(layoutDataPath)) {\n      moduleSet.add(layoutDataPath);\n    }\n  }\n  if (page.actionPath) {\n    moduleSet.add(page.actionPath);\n    let set = actionPathsByPage.get(page.path);\n    if (!set) {\n      set = new Set<string>();\n      actionPathsByPage.set(page.path, set);\n    }\n    set.add(page.actionPath);\n  }\n}\n\n/**\n * Build a self-contained SSR entry file for a platform adapter.\n * The generated module exports a default `handler(request: Request): Response`\n * and embeds the full route table plus a registry of all page/layout/data/\n * action modules so the runtime never touches the file system.\n */\nexport async function buildSsrEntry(\n  routes: Awaited<ReturnType<typeof scanRoutes>>,\n  options: AdapterOptions,\n  entryDir: string,\n): Promise<string> {\n  // Collect all module paths that the SSR runtime may need to import.\n  const moduleSet = new Set<string>();\n  const actionPathsByPage = new Map<string, Set<string>>();\n  for (const page of routes.pages) {\n    collectPageModules(page, moduleSet, actionPathsByPage);\n  }\n  if (routes.error404) collectPageModules(routes.error404, moduleSet, actionPathsByPage);\n  if (routes.error500) collectPageModules(routes.error500, moduleSet, actionPathsByPage);\n  for (const api of routes.api) {\n    moduleSet.add(api.routePath);\n  }\n  const modules = Array.from(moduleSet);\n  const moduleIndex = new Map(modules.map((path, index) => [path, index]));\n\n  const imports = modules\n    .map((path, index) => {\n      const rel = relativeToPosix(entryDir, path);\n      return `import * as m_${index} from ${JSON.stringify(rel)};`;\n    })\n    .join(\"\\n\");\n\n  const renderPageRecord = (page: PageRoute): string => `{\n    path: ${JSON.stringify(page.path)},\n    pagePath: ${JSON.stringify(page.pagePath)},\n    dataPath: ${JSON.stringify(page.dataPath ?? null)},\n    actionPath: ${JSON.stringify(page.actionPath ?? null)},\n    loadingPath: ${JSON.stringify(page.loadingPath ?? null)},\n    layouts: ${JSON.stringify(page.layouts)},\n    params: ${JSON.stringify(page.params)},\n  }`;\n\n  const pages = routes.pages.map(renderPageRecord).join(\",\\n\");\n\n  const apiRoutes = routes.api\n    .map((api) => {\n      const index = moduleIndex.get(api.routePath);\n      return `  { path: ${JSON.stringify(api.path)}, routePath: m_${index} },`;\n    })\n    .join(\"\\n\");\n\n  const actionModules = Array.from(actionPathsByPage.entries())\n    .map(([pagePath, paths]) => {\n      const entries = Array.from(paths)\n        .map((path) => {\n          const index = moduleIndex.get(path);\n          return `      [${JSON.stringify(path)}, m_${index}],`;\n        })\n        .join(\"\\n\");\n      return `  [${JSON.stringify(pagePath)}, new Map([\\n${entries}\\n    ])],`;\n    })\n    .join(\"\\n\");\n\n  const actionsRegistry: Record<string, string[]> = {};\n  for (const page of routes.pages) {\n    if (!page.actionPath) continue;\n    const mod = (await import(page.actionPath)) as Record<string, unknown>;\n    const names: string[] = [];\n    for (const [name, value] of Object.entries(mod)) {\n      if (name === \"default\") continue;\n      if (typeof value === \"function\") {\n        names.push(name);\n      }\n    }\n    if (names.length > 0) {\n      actionsRegistry[page.path] = names;\n    }\n  }\n\n  return `// AUTO-GENERATED by @deijose/nix-js-kit. Do not edit.\nimport { handleActionRequest, matchApiRoute, matchRoute, renderPage, renderErrorPage } from \"@deijose/nix-js-kit\";\n${imports}\n\nconst registry = new Map<string, unknown>([\n${modules.map((path, index) => `  [${JSON.stringify(path)}, m_${index}],`).join(\"\\n\")}\n]);\n\nconst pages = [\n${pages},\n];\n\nconst apiRoutes = [\n${apiRoutes}\n];\n\nconst actionModules = new Map<string, Map<string, unknown>>([\n${actionModules}\n]);\n\nconst actions = ${JSON.stringify(actionsRegistry)};\n\nconst routes = {\n  pages,\n  api: apiRoutes,\n  error404: ${routes.error404 ? renderPageRecord(routes.error404) : \"undefined\"},\n  error500: ${routes.error500 ? renderPageRecord(routes.error500) : \"undefined\"},\n};\n\nconst clientEntry = ${JSON.stringify(options.clientEntry)};\nconst lang = ${JSON.stringify(options.lang)};\n\nfunction loadModule(path: string) {\n  const mod = registry.get(path);\n  if (mod) return mod;\n  throw new Error(\\`Module not found in registry: \\${path}\\`);\n}\n\nasync function resolveAction(name: string, page?: string) {\n  // Match concrete page paths (e.g. /movies/inception) to their route pattern\n  // (/movies/:slug) so actions on dynamic routes resolve by scope.\n  let pageKey: string | undefined;\n  if (page) {\n    pageKey = routes.pages.some((route) => route.path === page)\n      ? page\n      : (matchRoute(page, routes.pages)?.route.path ?? page);\n  }\n  const pageModules = pageKey ? actionModules.get(pageKey) : undefined;\n  const candidates = pageModules ? [...pageModules.values()] : [];\n  if (!pageModules) {\n    for (const mods of actionModules.values()) {\n      for (const mod of mods.values()) {\n        const action = (mod as Record<string, unknown>)[name];\n        if (typeof action === \"function\") return action;\n      }\n    }\n  }\n  for (const mod of candidates) {\n    const action = (mod as Record<string, unknown>)[name];\n    if (typeof action === \"function\") {\n      return action as (...args: unknown[]) => unknown;\n    }\n  }\n  return undefined;\n}\n\nexport default async function handler(request: Request): Promise<Response> {\n  const url = new URL(request.url);\n\n  if (url.pathname === \"/__nix-js/actions\") {\n    return handleActionRequest(request, resolveAction);\n  }\n\n  // Render endpoint used by the SPA router and streaming boundaries.\n  if (url.pathname === \"/__nix-js/render\") {\n    const page = url.searchParams.get(\"page\") ?? \"/\";\n    const search = url.searchParams.get(\"search\") ?? \"\";\n    const wantsJson = (request.headers.get(\"Accept\") ?? \"\").includes(\"application/json\");\n    try {\n      const match = matchRoute(page, routes.pages);\n      if (!match) throw new Error(\\`No route found for \\${page}\\`);\n      const { html } = await renderPage({\n        route: match.route,\n        params: match.params,\n        searchParams: new URLSearchParams(search),\n        config: { lang, clientEntry },\n        importer: loadModule,\n        actions,\n        request,\n      });\n      const bodyStart = html.indexOf('<div id=\"app\">');\n      const bodyEnd = html.lastIndexOf(\"</div>\");\n      const body = bodyStart >= 0 && bodyEnd > bodyStart\n        ? html.slice(bodyStart + 13, bodyEnd).trim()\n        : html;\n      const titleStart = html.indexOf(\"<title>\");\n      const titleEnd = html.indexOf(\"</title>\");\n      const title = titleStart >= 0 && titleEnd > titleStart\n        ? html.slice(titleStart + 7, titleEnd)\n        : \"\";\n      if (wantsJson) {\n        return new Response(JSON.stringify({ title, body }), {\n          status: 200,\n          headers: { \"Content-Type\": \"application/json; charset=utf-8\" },\n        });\n      }\n      return new Response(body, {\n        status: 200,\n        headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n      });\n    } catch (err) {\n      if ((err as { name?: string }).name === \"RouteNotFoundError\") {\n        return new Response(\"Not Found\", {\n          status: 404,\n          headers: { \"Content-Type\": \"text/plain\" },\n        });\n      }\n      console.error(\"[nix-js-kit] render endpoint error:\", err);\n      return new Response(\"Internal Server Error\", {\n        status: 500,\n        headers: { \"Content-Type\": \"text/plain\" },\n      });\n    }\n  }\n\n  const apiMatch = matchApiRoute(url.pathname, apiRoutes);\n  if (apiMatch) {\n    const mod = apiMatch.route.routePath as Record<\n      string,\n      (request: Request, context?: { params: Record<string, string | string[]> }) => unknown\n    >;\n    const handler = mod[request.method ?? \"GET\"];\n    if (typeof handler !== \"function\") {\n      return new Response(\"Method not allowed: \" + request.method, { status: 405, headers: { \"Content-Type\": \"text/plain\" } });\n    }\n    return (await handler(request, { params: apiMatch.params })) as Response;\n  }\n\n  const match = matchRoute(url.pathname, routes.pages);\n  if (!match) {\n    const errorResult = await renderErrorPage({ routes, status: 404, config: { lang, clientEntry }, actions, importer: loadModule });\n    if (errorResult) {\n      return new Response(errorResult.html, { status: errorResult.status, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n    }\n    return new Response(\"Not Found\", { status: 404, headers: { \"Content-Type\": \"text/plain\" } });\n  }\n\n  try {\n    const { html } = await renderPage({\n      route: match.route,\n      params: match.params,\n      searchParams: new URLSearchParams(url.search),\n      config: { lang, clientEntry },\n      importer: loadModule,\n      actions,\n      request,\n    });\n    return new Response(html, { status: 200, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n  } catch (err) {\n    console.error(\"[nix-js-kit] SSR render error:\", err);\n    const errorResult = await renderErrorPage({ routes, status: 500, error: err, config: { lang, clientEntry }, actions, importer: loadModule });\n    if (errorResult) {\n      return new Response(errorResult.html, { status: errorResult.status, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n    }\n    return new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n  }\n}\n`;\n}\n\nfunction relativeToPosix(from: string, to: string): string {\n  return relative(from, to).split(\"\\\\\").join(\"/\");\n}\n\n/**\n * Write a generated SSR entry file for an adapter.\n */\nexport async function writeSsrEntry(\n  entryPath: string,\n  routes: Awaited<ReturnType<typeof scanRoutes>>,\n  options: AdapterOptions,\n): Promise<void> {\n  await writeFile(\n    entryPath,\n    await buildSsrEntry(routes, options, dirname(entryPath)),\n    \"utf8\",\n  );\n}\n"],"mappings":"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"}