@pyreon/zero 0.12.3 → 0.12.4
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/lib/index.js +314 -4047
- package/lib/index.js.map +1 -1
- package/lib/meta.js +22 -48
- package/lib/meta.js.map +1 -1
- package/lib/server.js +1534 -0
- package/lib/server.js.map +1 -0
- package/lib/types/index.d.ts +173 -1540
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/meta.d.ts +11 -46
- package/lib/types/meta.d.ts.map +1 -1
- package/lib/types/server.d.ts +455 -0
- package/lib/types/server.d.ts.map +1 -0
- package/package.json +15 -10
- package/src/index.ts +19 -182
- package/src/meta.tsx +37 -3
- package/src/server.ts +70 -0
- package/lib/fs-router-Dil4IKZR.js +0 -290
- package/lib/fs-router-Dil4IKZR.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":["flattenRoutePatterns"],"sources":["../src/app.ts","../src/api-routes.ts","../src/not-found.ts","../src/entry-server.ts","../src/config.ts","../src/fs-router.ts","../src/isr.ts","../src/adapters/validate.ts","../src/adapters/bun.ts","../src/adapters/cloudflare.ts","../src/adapters/netlify.ts","../src/adapters/node.ts","../src/adapters/static.ts","../src/adapters/vercel.ts","../src/adapters/index.ts","../src/middleware.ts","../src/error-overlay.ts","../src/vite-plugin.ts","../src/i18n-routing.ts"],"sourcesContent":["import type { ComponentFn, Props } from '@pyreon/core'\nimport { Fragment, h } from '@pyreon/core'\nimport { HeadProvider } from '@pyreon/head'\nimport type { RouteRecord } from '@pyreon/router'\nimport { createRouter, RouterProvider, RouterView } from '@pyreon/router'\n\n// ─── App assembly ────────────────────────────────────────────────────────────\n\nexport interface CreateAppOptions {\n /** Route definitions (from file-based routing or manual). */\n routes: RouteRecord[]\n\n /** Router mode. Default: \"history\" for SSR, \"hash\" for SPA. */\n routerMode?: 'hash' | 'history'\n\n /** Initial URL for SSR. */\n url?: string\n\n /** Root layout component wrapping all routes. */\n layout?: ComponentFn\n\n /** Global error component. */\n errorComponent?: ComponentFn\n}\n\n/**\n * Create a full Zero app — assembles router, head provider, and root layout.\n *\n * Used internally by entry-server and entry-client.\n */\nexport function createApp(options: CreateAppOptions) {\n const router = createRouter({\n routes: options.routes,\n mode: options.routerMode ?? 'history',\n ...(options.url ? { url: options.url } : {}),\n scrollBehavior: 'top',\n })\n\n const Layout = options.layout ?? DefaultLayout\n\n function App() {\n return h(\n HeadProvider,\n null,\n h(\n RouterProvider as ComponentFn<Props>,\n { router },\n h(Layout, null, h(RouterView as ComponentFn<Props>, null)),\n ),\n )\n }\n\n return { App, router }\n}\n\nfunction DefaultLayout(props: Props) {\n return h(Fragment, null, ...(Array.isArray(props.children) ? props.children : [props.children]))\n}\n","import type { Middleware, MiddlewareContext } from '@pyreon/server'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** HTTP methods supported by API routes. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** Context passed to API route handlers. */\nexport interface ApiContext {\n /** The incoming request. */\n request: Request\n /** Parsed URL. */\n url: URL\n /** URL path. */\n path: string\n /** Dynamic route parameters (e.g., { id: \"123\" }). */\n params: Record<string, string>\n /** Request headers. */\n headers: Headers\n}\n\n/** An API route handler function. */\nexport type ApiHandler = (ctx: ApiContext) => Response | Promise<Response>\n\n/** An API route module — exports named HTTP method handlers. */\nexport interface ApiRouteModule {\n GET?: ApiHandler\n POST?: ApiHandler\n PUT?: ApiHandler\n PATCH?: ApiHandler\n DELETE?: ApiHandler\n HEAD?: ApiHandler\n OPTIONS?: ApiHandler\n}\n\n/** A registered API route entry. */\nexport interface ApiRouteEntry {\n /** URL pattern (e.g., \"/api/posts/:id\"). */\n pattern: string\n /** The route module with method handlers. */\n module: ApiRouteModule\n}\n\n// ─── Pattern matching ────────────────────────────────────────────────────────\n\n/**\n * Match a URL path against an API route pattern.\n * Returns extracted params or null if no match.\n */\nexport function matchApiRoute(pattern: string, path: string): Record<string, string> | null {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n const params: Record<string, string> = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i]\n if (!pp) continue\n\n // Catch-all: :param*\n if (pp.endsWith('*')) {\n const paramName = pp.slice(1, -1)\n params[paramName] = pathParts.slice(i).join('/')\n return params\n }\n\n // No more path segments\n if (i >= pathParts.length) return null\n\n // Dynamic segment: :param\n if (pp.startsWith(':')) {\n params[pp.slice(1)] = pathParts[i]!\n continue\n }\n\n // Static segment\n if (pp !== pathParts[i]) return null\n }\n\n return patternParts.length === pathParts.length ? params : null\n}\n\n// ─── Middleware ───────────────────────────────────────────────────────────────\n\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n\n/**\n * Create a middleware that dispatches API route requests.\n * API routes are matched by URL pattern and HTTP method.\n */\nexport function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {\n return async (ctx: MiddlewareContext) => {\n for (const route of routes) {\n const params = matchApiRoute(route.pattern, ctx.path)\n if (!params) continue\n\n const method = ctx.req.method.toUpperCase() as HttpMethod\n const handler = route.module[method]\n\n if (!handler) {\n // Route matched but method not supported\n const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(', ')\n return new Response(null, {\n status: 405,\n headers: {\n Allow: allowed,\n 'Content-Type': 'application/json',\n },\n })\n }\n\n return handler({\n request: ctx.req,\n url: ctx.url,\n path: ctx.path,\n params,\n headers: ctx.req.headers,\n })\n }\n }\n}\n\n// ─── Virtual module generation ───────────────────────────────────────────────\n\n/**\n * Detect whether a route file is an API route.\n * API routes are `.ts` or `.js` files inside an `api/` directory.\n */\nexport function isApiRoute(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/')\n return (\n normalized.startsWith('api/') &&\n (normalized.endsWith('.ts') || normalized.endsWith('.js')) &&\n !normalized.endsWith('.tsx') &&\n !normalized.endsWith('.jsx')\n )\n}\n\n/**\n * Convert an API route file path to a URL pattern.\n *\n * Examples:\n * \"api/posts.ts\" → \"/api/posts\"\n * \"api/posts/index.ts\" → \"/api/posts\"\n * \"api/posts/[id].ts\" → \"/api/posts/:id\"\n * \"api/[...path].ts\" → \"/api/:path*\"\n */\nexport function apiFilePathToPattern(filePath: string): string {\n let route = filePath\n // Remove extension\n for (const ext of ['.ts', '.js']) {\n if (route.endsWith(ext)) {\n route = route.slice(0, -ext.length)\n break\n }\n }\n\n const segments = route.split('/')\n const urlSegments: string[] = []\n\n for (const seg of segments) {\n if (seg === 'index') continue\n\n // Catch-all: [...param]\n const catchAll = seg.match(/^\\[\\.\\.\\.(\\w+)\\]$/)\n if (catchAll) {\n urlSegments.push(`:${catchAll[1]}*`)\n continue\n }\n\n // Dynamic: [param]\n const dynamic = seg.match(/^\\[(\\w+)\\]$/)\n if (dynamic) {\n urlSegments.push(`:${dynamic[1]}`)\n continue\n }\n\n urlSegments.push(seg)\n }\n\n return `/${urlSegments.join('/')}`\n}\n\n/**\n * Generate a virtual module that exports API route entries.\n * Each entry maps a URL pattern to a module with HTTP method handlers.\n */\nexport function generateApiRouteModule(files: string[], routesDir: string): string {\n const apiFiles = files.filter(isApiRoute)\n\n if (apiFiles.length === 0) {\n return 'export const apiRoutes = []\\n'\n }\n\n const imports: string[] = []\n const entries: string[] = []\n\n for (let i = 0; i < apiFiles.length; i++) {\n const name = `_api${i}`\n const file = apiFiles[i]\n if (!file) continue\n const fullPath = `${routesDir}/${file}`\n const pattern = apiFilePathToPattern(file)\n\n imports.push(`import * as ${name} from \"${fullPath}\"`)\n entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`)\n }\n\n return [...imports, '', 'export const apiRoutes = [', entries.join(',\\n'), ']'].join('\\n')\n}\n","import type { ComponentFn } from \"@pyreon/core\";\nimport { h } from \"@pyreon/core\";\nimport { renderToString } from \"@pyreon/runtime-server\";\n\n// ─── 404 Not Found rendering ────────────────────────────────────────────────\n//\n// Shared utility for rendering 404 pages in both dev (vite-plugin) and\n// production (entry-server). Renders the notFoundComponent into HTML\n// and wraps it in a minimal document if no template is provided.\n\nconst DEFAULT_404_BODY =\n\t\"<h1>404 — Not Found</h1><p>The page you requested does not exist.</p>\";\n\n/**\n * Render a 404 component to a full HTML string.\n * If no component is provided, returns a default 404 page.\n */\nexport async function render404Page(\n\tcomponent: ComponentFn | undefined,\n\ttemplate?: string,\n): Promise<string> {\n\tlet body: string;\n\tif (component) {\n\t\tbody = await renderToString(h(component, null));\n\t} else {\n\t\tbody = DEFAULT_404_BODY;\n\t}\n\n\tif (template?.includes(\"<!--pyreon-app-->\")) {\n\t\treturn template.replace(\"<!--pyreon-app-->\", body);\n\t}\n\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>404 — Not Found</title>\n</head>\n<body>\n ${body}\n</body>\n</html>`;\n}\n","import type { ComponentFn } from \"@pyreon/core\";\nimport type { RouteRecord } from \"@pyreon/router\";\nimport type { Middleware, MiddlewareContext } from \"@pyreon/server\";\nimport { createHandler } from \"@pyreon/server\";\nimport type { ApiRouteEntry } from \"./api-routes\";\nimport { createApiMiddleware } from \"./api-routes\";\nimport { createApp } from \"./app\";\nimport { render404Page } from \"./not-found\";\nimport type { RouteMiddlewareEntry, ZeroConfig } from \"./types\";\n\n// ─── Server entry factory ───────────────────────────────────────────────────\n\nexport interface CreateServerOptions {\n\t/** Route definitions. */\n\troutes: RouteRecord[];\n\t/** Zero config. */\n\tconfig?: ZeroConfig;\n\t/** Additional middleware. */\n\tmiddleware?: Middleware[];\n\t/** Per-route middleware from virtual:zero/route-middleware. */\n\trouteMiddleware?: RouteMiddlewareEntry[];\n\t/** API route entries from virtual:zero/api-routes. */\n\tapiRoutes?: ApiRouteEntry[];\n\t/** HTML template override. */\n\ttemplate?: string;\n\t/** Client entry path. */\n\tclientEntry?: string;\n\t/** Component to render when no route matches (from _404.tsx). */\n\tnotFoundComponent?: ComponentFn;\n}\n\n/**\n * Create a middleware that dispatches per-route middleware based on URL pattern matching.\n */\nfunction createRouteMiddlewareDispatcher(\n\tentries: RouteMiddlewareEntry[],\n): Middleware {\n\treturn async (ctx: MiddlewareContext) => {\n\t\tfor (const entry of entries) {\n\t\t\tif (matchPattern(entry.pattern, ctx.path)) {\n\t\t\t\tconst mw = Array.isArray(entry.middleware)\n\t\t\t\t\t? entry.middleware\n\t\t\t\t\t: [entry.middleware];\n\t\t\t\tfor (const fn of mw) {\n\t\t\t\t\tconst result = await fn(ctx);\n\t\t\t\t\tif (result) return result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * URL pattern matcher supporting :param and :param* segments.\n *\n * Rules:\n * - Static segments must match exactly\n * - `:param` matches a single path segment\n * - `:param*` matches all remaining segments (must be last, and path must\n * have matched all preceding segments)\n * - Path length must match pattern length (unless catch-all)\n */\nexport function matchPattern(pattern: string, path: string): boolean {\n\tconst patternParts = pattern.split(\"/\").filter(Boolean);\n\tconst pathParts = path.split(\"/\").filter(Boolean);\n\n\tfor (let i = 0; i < patternParts.length; i++) {\n\t\tconst pp = patternParts[i]!;\n\n\t\t// Catch-all: matches remaining segments, but only if we've matched\n\t\t// all preceding segments up to this point\n\t\tif (pp.endsWith(\"*\")) {\n\t\t\t// All segments before the catch-all must have matched (we got here)\n\t\t\t// and there must be at least one remaining path segment\n\t\t\treturn i <= pathParts.length;\n\t\t}\n\n\t\t// No more path segments to match against\n\t\tif (i >= pathParts.length) return false;\n\n\t\t// Dynamic segment matches any single segment\n\t\tif (pp.startsWith(\":\")) continue;\n\n\t\t// Static segment must match exactly\n\t\tif (pp !== pathParts[i]) return false;\n\t}\n\n\t// All pattern parts consumed — path must also be fully consumed\n\treturn patternParts.length === pathParts.length;\n}\n\n/**\n * Create the SSR request handler for production.\n *\n * @example\n * import { routes } from \"virtual:zero/routes\"\n * import { routeMiddleware } from \"virtual:zero/route-middleware\"\n * import { createServer } from \"@pyreon/zero\"\n *\n * export default createServer({ routes, routeMiddleware, apiRoutes })\n */\nexport function createServer(options: CreateServerOptions) {\n\tconst config = options.config ?? {};\n\n\tconst allMiddleware: Middleware[] = [];\n\n\t// API routes run first — they short-circuit before SSR\n\tif (options.apiRoutes?.length) {\n\t\tallMiddleware.push(createApiMiddleware(options.apiRoutes));\n\t}\n\n\t// Per-route middleware runs next\n\tif (options.routeMiddleware?.length) {\n\t\tallMiddleware.push(\n\t\t\tcreateRouteMiddlewareDispatcher(options.routeMiddleware),\n\t\t);\n\t}\n\n\t// Then global middleware from config and options\n\tallMiddleware.push(...(config.middleware ?? []));\n\tallMiddleware.push(...(options.middleware ?? []));\n\n\tconst { App } = createApp({\n\t\troutes: options.routes,\n\t\trouterMode: \"history\",\n\t});\n\n\tconst handler = createHandler({\n\t\tApp,\n\t\troutes: options.routes,\n\t\tmiddleware: allMiddleware,\n\t\tmode: config.ssr?.mode ?? \"string\",\n\t\t...(options.template ? { template: options.template } : {}),\n\t\t...(options.clientEntry ? { clientEntry: options.clientEntry } : {}),\n\t});\n\n\t// Wrap handler with 404 detection when a notFoundComponent is provided\n\tif (!options.notFoundComponent) return handler;\n\n\tconst NotFound = options.notFoundComponent;\n\tconst routePatterns = flattenRoutePatterns(options.routes);\n\n\treturn async (req: Request) => {\n\t\tconst url = new URL(req.url);\n\t\tconst pathname = url.pathname;\n\n\t\t// Check if any defined route matches this path\n\t\tif (!routePatterns.some((pattern) => matchPattern(pattern, pathname))) {\n\t\t\tconst fullHtml = await render404Page(NotFound, options.template);\n\t\t\treturn new Response(fullHtml, {\n\t\t\t\tstatus: 404,\n\t\t\t\theaders: { \"Content-Type\": \"text/html; charset=utf-8\" },\n\t\t\t});\n\t\t}\n\n\t\treturn handler(req);\n\t};\n}\n\n/** Extract all URL patterns from a nested route tree. */\nfunction flattenRoutePatterns(routes: RouteRecord[], prefix = \"\"): string[] {\n\tconst patterns: string[] = [];\n\tfor (const route of routes) {\n\t\tconst fullPath =\n\t\t\troute.path === \"/\" && prefix ? prefix : `${prefix}${route.path}`;\n\t\tpatterns.push(fullPath);\n\t\tif (route.children) {\n\t\t\tpatterns.push(\n\t\t\t\t...flattenRoutePatterns(route.children as RouteRecord[], fullPath),\n\t\t\t);\n\t\t}\n\t}\n\treturn patterns;\n}\n","import type { ZeroConfig } from './types'\n\n/**\n * Define a Zero configuration.\n * Used in `zero.config.ts` at the project root.\n *\n * @example\n * import { defineConfig } from \"@pyreon/zero/config\"\n *\n * export default defineConfig({\n * mode: \"ssr\",\n * ssr: { mode: \"stream\" },\n * port: 3000,\n * })\n */\nexport function defineConfig(config: ZeroConfig): ZeroConfig {\n return config\n}\n\n/** Merge user config with defaults. */\nexport function resolveConfig(\n userConfig: ZeroConfig = {},\n): Required<Pick<ZeroConfig, 'mode' | 'base' | 'port' | 'adapter'>> & ZeroConfig {\n return {\n mode: 'ssr',\n base: '/',\n port: 3000,\n adapter: 'node',\n ...userConfig,\n ssr: {\n mode: 'string',\n ...userConfig.ssr,\n },\n }\n}\n","import type { FileRoute, RenderMode } from './types'\n\n// ─── File-system route conventions ──────────────────────────────────────────\n//\n// src/routes/\n// _layout.tsx → layout for all routes\n// index.tsx → /\n// about.tsx → /about\n// users/\n// _layout.tsx → layout for /users/*\n// _loading.tsx → loading fallback for /users/*\n// _error.tsx → error boundary for /users/*\n// index.tsx → /users\n// [id].tsx → /users/:id\n// [id]/\n// settings.tsx → /users/:id/settings\n// blog/\n// [...slug].tsx → /blog/* (catch-all)\n//\n// Conventions:\n// [param] → dynamic segment → :param\n// [...param] → catch-all → :param*\n// _layout → layout wrapper — must use <RouterView /> to render child routes\n// (props.children is NOT passed — the router handles nesting)\n// _error → error component\n// _loading → loading component\n// _404 → not-found component (renders on 404)\n// _not-found → alias for _404\n// (group) → route group (directory ignored in URL)\n\nconst ROUTE_EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js']\n\n/**\n * Parse a set of file paths (relative to routes dir) into FileRoute objects.\n *\n * @param files Array of file paths like [\"index.tsx\", \"users/[id].tsx\"]\n * @param defaultMode Default rendering mode from config\n */\nexport function parseFileRoutes(files: string[], defaultMode: RenderMode = 'ssr'): FileRoute[] {\n return files\n .filter((f) => ROUTE_EXTENSIONS.some((ext) => f.endsWith(ext)))\n .map((filePath) => parseFilePath(filePath, defaultMode))\n .sort(sortRoutes)\n}\n\nfunction parseFilePath(filePath: string, defaultMode: RenderMode): FileRoute {\n // Remove extension\n let route = filePath\n for (const ext of ROUTE_EXTENSIONS) {\n if (route.endsWith(ext)) {\n route = route.slice(0, -ext.length)\n break\n }\n }\n\n const fileName = getFileName(route)\n const isLayout = fileName === '_layout'\n const isError = fileName === '_error'\n const isLoading = fileName === '_loading'\n const isNotFound = fileName === '_404' || fileName === '_not-found'\n const isCatchAll = route.includes('[...')\n\n // Get directory path (strip groups for consistent grouping)\n const parts = route.split('/')\n parts.pop() // remove filename\n const dirPath = parts.filter((s) => !(s.startsWith('(') && s.endsWith(')'))).join('/')\n\n // Convert file path to URL pattern\n const urlPath = filePathToUrlPath(route)\n const depth = urlPath === '/' ? 0 : urlPath.split('/').filter(Boolean).length\n\n return {\n filePath,\n urlPath,\n dirPath,\n depth,\n isLayout,\n isError,\n isLoading,\n isNotFound,\n isCatchAll,\n renderMode: defaultMode,\n }\n}\n\n/**\n * Convert a file path (without extension) to a URL path pattern.\n *\n * Examples:\n * \"index\" → \"/\"\n * \"about\" → \"/about\"\n * \"users/index\" → \"/users\"\n * \"users/[id]\" → \"/users/:id\"\n * \"blog/[...slug]\" → \"/blog/:slug*\"\n * \"(auth)/login\" → \"/login\" (group stripped)\n * \"_layout\" → \"/\" (layout marker)\n */\nexport function filePathToUrlPath(filePath: string): string {\n const segments = filePath.split('/')\n const urlSegments: string[] = []\n\n for (const seg of segments) {\n // Skip route groups \"(name)\"\n if (seg.startsWith('(') && seg.endsWith(')')) continue\n\n // Skip special files\n if (seg === '_layout' || seg === '_error' || seg === '_loading' || seg === '_404' || seg === '_not-found') continue\n\n // \"index\" maps to the parent path\n if (seg === 'index') continue\n\n // Catch-all: [...param] → :param*\n const catchAll = seg.match(/^\\[\\.\\.\\.(\\w+)\\]$/)\n if (catchAll) {\n urlSegments.push(`:${catchAll[1]}*`)\n continue\n }\n\n // Dynamic: [param] → :param\n const dynamic = seg.match(/^\\[(\\w+)\\]$/)\n if (dynamic) {\n urlSegments.push(`:${dynamic[1]}`)\n continue\n }\n\n urlSegments.push(seg)\n }\n\n const path = `/${urlSegments.join('/')}`\n return path || '/'\n}\n\n/** Sort routes: static before dynamic, catch-all last. */\nfunction sortRoutes(a: FileRoute, b: FileRoute): number {\n // Catch-all routes go last\n if (a.isCatchAll !== b.isCatchAll) return a.isCatchAll ? 1 : -1\n // Layouts go first within same depth\n if (a.isLayout !== b.isLayout) return a.isLayout ? -1 : 1\n // Static segments before dynamic\n const aDynamic = a.urlPath.includes(':')\n const bDynamic = b.urlPath.includes(':')\n if (aDynamic !== bDynamic) return aDynamic ? 1 : -1\n // Alphabetical\n return a.urlPath.localeCompare(b.urlPath)\n}\n\nfunction getFileName(filePath: string): string {\n const parts = filePath.split('/')\n return parts[parts.length - 1] ?? ''\n}\n\n// ─── Route generation (for Vite plugin) ─────────────────────────────────────\n\n/** Internal tree node for building nested route structures. */\ninterface RouteNode {\n /** Page routes at this directory level. */\n pages: FileRoute[]\n /** Layout file for this directory (if any). */\n layout?: FileRoute\n /** Error boundary file (if any). */\n error?: FileRoute\n /** Loading fallback file (if any). */\n loading?: FileRoute\n /** Not-found (404) file (if any). */\n notFound?: FileRoute\n /** Child directories. */\n children: Map<string, RouteNode>\n}\n\n/**\n * Group flat file routes into a directory tree.\n */\nfunction getOrCreateChild(node: RouteNode, segment: string): RouteNode {\n let child = node.children.get(segment)\n if (!child) {\n child = { pages: [], children: new Map() }\n node.children.set(segment, child)\n }\n return child\n}\n\nfunction resolveNode(root: RouteNode, dirPath: string): RouteNode {\n let node = root\n if (dirPath) {\n for (const segment of dirPath.split('/')) {\n node = getOrCreateChild(node, segment)\n }\n }\n return node\n}\n\nfunction placeRoute(node: RouteNode, route: FileRoute) {\n if (route.isLayout) node.layout = route\n else if (route.isError) node.error = route\n else if (route.isLoading) node.loading = route\n else if (route.isNotFound) node.notFound = route\n else node.pages.push(route)\n}\n\nfunction buildRouteTree(routes: FileRoute[]): RouteNode {\n const root: RouteNode = { pages: [], children: new Map() }\n for (const route of routes) {\n placeRoute(resolveNode(root, route.dirPath), route)\n }\n return root\n}\n\n/**\n * Generate a virtual module that exports a nested route tree.\n * Wires up layouts as parent routes with children, loaders, guards,\n * error/loading components, middleware, and meta from route module exports.\n */\nexport interface GenerateRouteModuleOptions {\n /**\n * When true, skip lazy() for route components and use static imports.\n * Use for SSG/prerender mode where all routes are rendered at build time\n * and code splitting provides no benefit. Avoids Rolldown warnings about\n * static + dynamic imports of the same module.\n */\n staticImports?: boolean\n}\n\nexport function generateRouteModule(\n files: string[],\n routesDir: string,\n options?: GenerateRouteModuleOptions,\n): string {\n const routes = parseFileRoutes(files)\n const tree = buildRouteTree(routes)\n const imports: string[] = []\n let importCounter = 0\n const useStaticImports = options?.staticImports ?? false\n\n function nextImport(filePath: string, exportName = 'default'): string {\n const name = `_${importCounter++}`\n const fullPath = `${routesDir}/${filePath}`\n if (exportName === 'default') {\n imports.push(`import ${name} from \"${fullPath}\"`)\n } else {\n imports.push(`import { ${exportName} as ${name} } from \"${fullPath}\"`)\n }\n return name\n }\n\n function nextLazy(filePath: string, loadingName?: string, errorName?: string): string {\n const name = `_${importCounter++}`\n const fullPath = `${routesDir}/${filePath}`\n\n if (useStaticImports) {\n // SSG mode: static import avoids Rolldown warnings about\n // static + dynamic imports of the same module\n imports.push(`import ${name} from \"${fullPath}\"`)\n } else {\n const opts: string[] = []\n if (loadingName) opts.push(`loading: ${loadingName}`)\n if (errorName) opts.push(`error: ${errorName}`)\n const optsStr = opts.length > 0 ? `, { ${opts.join(', ')} }` : ''\n imports.push(`const ${name} = lazy(() => import(\"${fullPath}\")${optsStr})`)\n }\n return name\n }\n\n function nextModuleImport(filePath: string): string {\n const name = `_m${importCounter++}`\n const fullPath = `${routesDir}/${filePath}`\n imports.push(`import * as ${name} from \"${fullPath}\"`)\n return name\n }\n\n function generatePageRoute(\n page: FileRoute,\n indent: string,\n loadingName: string | undefined,\n errorName: string | undefined,\n notFoundName: string | undefined,\n ): string {\n const mod = nextModuleImport(page.filePath)\n const comp = nextLazy(page.filePath, loadingName, errorName)\n\n const props: string[] = [\n `${indent} path: ${JSON.stringify(page.urlPath)}`,\n `${indent} component: ${comp}`,\n `${indent} loader: ${mod}.loader`,\n `${indent} beforeEnter: ${mod}.guard`,\n `${indent} meta: { ...${mod}.meta, renderMode: ${mod}.renderMode }`,\n ]\n\n // Only emit errorComponent when there's an actual _error file in scope\n // or the route module exports an error component. Avoids referencing\n // undefined .error exports that produce noisy bundler warnings.\n if (errorName) {\n props.push(`${indent} errorComponent: ${mod}.error || ${errorName}`)\n }\n\n if (notFoundName) {\n props.push(`${indent} notFoundComponent: ${notFoundName}`)\n }\n\n return `${indent}{\\n${props.join(',\\n')}\\n${indent}}`\n }\n\n function wrapWithLayout(\n node: RouteNode,\n children: string[],\n indent: string,\n errorName: string | undefined,\n notFoundName: string | undefined,\n ): string {\n const layout = node.layout as FileRoute\n const layoutMod = nextModuleImport(layout.filePath)\n const layoutComp = nextImport(layout.filePath, 'layout')\n\n const props: string[] = [\n `${indent}path: ${JSON.stringify(layout.urlPath)}`,\n `${indent}component: ${layoutComp}`,\n `${indent}loader: ${layoutMod}.loader`,\n `${indent}beforeEnter: ${layoutMod}.guard`,\n `${indent}meta: { ...${layoutMod}.meta, renderMode: ${layoutMod}.renderMode }`,\n ]\n if (errorName) {\n props.push(`${indent}errorComponent: ${errorName}`)\n }\n if (notFoundName) {\n props.push(`${indent}notFoundComponent: ${notFoundName}`)\n }\n if (children.length > 0) {\n props.push(`${indent}children: [\\n${children.join(',\\n')}\\n${indent}]`)\n }\n\n return `${indent}{\\n${props.map((p) => ` ${p}`).join(',\\n')}\\n${indent}}`\n }\n\n /**\n * Generate route definitions for a tree node.\n */\n function generateNode(node: RouteNode, depth: number): string[] {\n const indent = ' '.repeat(depth + 1)\n\n const errorName = node.error ? nextImport(node.error.filePath) : undefined\n const loadingName = node.loading ? nextImport(node.loading.filePath) : undefined\n const notFoundName = node.notFound ? nextImport(node.notFound.filePath) : undefined\n\n const childRouteDefs: string[] = []\n for (const [, childNode] of node.children) {\n childRouteDefs.push(...generateNode(childNode, depth + 1))\n }\n\n const pageRouteDefs = node.pages.map((page) =>\n generatePageRoute(page, indent, loadingName, errorName, notFoundName),\n )\n\n const allChildren = [...pageRouteDefs, ...childRouteDefs]\n\n if (node.layout) {\n return [wrapWithLayout(node, allChildren, indent, errorName, notFoundName)]\n }\n return allChildren\n }\n\n const routeDefs = generateNode(tree, 0)\n\n return [\n `import { lazy } from \"@pyreon/router\"`,\n '',\n ...imports,\n '',\n // Filter out undefined properties at runtime\n `function clean(routes) {`,\n ` return routes.map(r => {`,\n ` const c = {}`,\n ` for (const k in r) if (r[k] !== undefined) c[k] = r[k]`,\n ` if (c.children) c.children = clean(c.children)`,\n ` return c`,\n ` })`,\n `}`,\n '',\n `export const routes = clean([`,\n routeDefs.join(',\\n'),\n `])`,\n ].join('\\n')\n}\n\n/**\n * Generate a virtual module that maps URL patterns to their middleware exports.\n * Used by the server entry to dispatch per-route middleware.\n */\nexport function generateMiddlewareModule(files: string[], routesDir: string): string {\n const routes = parseFileRoutes(files)\n const imports: string[] = []\n const entries: string[] = []\n let counter = 0\n\n for (const route of routes) {\n if (route.isLayout || route.isError || route.isLoading || route.isNotFound) continue\n const name = `_mw${counter++}`\n const fullPath = `${routesDir}/${route.filePath}`\n imports.push(`import { middleware as ${name} } from \"${fullPath}\"`)\n entries.push(` { pattern: ${JSON.stringify(route.urlPath)}, middleware: ${name} }`)\n }\n\n return [\n ...imports,\n '',\n `export const routeMiddleware = [`,\n entries.join(',\\n'),\n `].filter(e => e.middleware)`,\n ].join('\\n')\n}\n\n/**\n * Scan a directory for route files.\n * Returns paths relative to the routes directory.\n */\nexport async function scanRouteFiles(routesDir: string): Promise<string[]> {\n const { readdir } = await import('node:fs/promises')\n const { join, relative } = await import('node:path')\n\n const files: string[] = []\n\n async function walk(dir: string) {\n const entries = await readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory()) {\n await walk(fullPath)\n } else if (ROUTE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {\n files.push(relative(routesDir, fullPath))\n }\n }\n }\n\n await walk(routesDir)\n return files\n}\n","import type { ISRConfig } from './types'\n\n// ─── ISR Cache ───────────────────────────────────────────────────────────────\n\ninterface CacheEntry {\n html: string\n headers: Record<string, string>\n timestamp: number\n}\n\n/**\n * In-memory ISR cache with stale-while-revalidate semantics.\n *\n * Wraps an SSR handler and caches responses per URL path.\n * Serves stale content immediately while revalidating in the background.\n */\nexport function createISRHandler(\n handler: (req: Request) => Promise<Response>,\n config: ISRConfig,\n): (req: Request) => Promise<Response> {\n const cache = new Map<string, CacheEntry>()\n const revalidating = new Set<string>()\n const revalidateMs = config.revalidate * 1000\n\n async function revalidate(url: URL) {\n const key = url.pathname\n if (revalidating.has(key)) return\n revalidating.add(key)\n\n try {\n const req = new Request(url.href, { method: 'GET' })\n const res = await handler(req)\n const html = await res.text()\n const headers: Record<string, string> = {}\n res.headers.forEach((v, k) => {\n headers[k] = v\n })\n\n cache.set(key, { html, headers, timestamp: Date.now() })\n } catch {\n // Revalidation failed — stale cache entry remains valid\n } finally {\n revalidating.delete(key)\n }\n }\n\n return async (req: Request): Promise<Response> => {\n // Only cache GET requests\n if (req.method !== 'GET') {\n return handler(req)\n }\n\n const url = new URL(req.url)\n const key = url.pathname\n const entry = cache.get(key)\n\n if (entry) {\n const age = Date.now() - entry.timestamp\n\n if (age > revalidateMs) {\n // Stale — serve cached but revalidate in background\n revalidate(url)\n }\n\n return new Response(entry.html, {\n status: 200,\n headers: {\n ...entry.headers,\n 'content-type': 'text/html; charset=utf-8',\n 'x-isr-cache': age > revalidateMs ? 'STALE' : 'HIT',\n 'x-isr-age': String(Math.round(age / 1000)),\n },\n })\n }\n\n // Cache miss — render, cache, and return\n const res = await handler(req)\n const html = await res.text()\n const headers: Record<string, string> = {}\n res.headers.forEach((v, k) => {\n headers[k] = v\n })\n\n cache.set(key, { html, headers, timestamp: Date.now() })\n\n return new Response(html, {\n status: 200,\n headers: {\n ...headers,\n 'content-type': 'text/html; charset=utf-8',\n 'x-isr-cache': 'MISS',\n },\n })\n }\n}\n","import type { AdapterBuildOptions } from '../types'\n\n/**\n * Validate that adapter build inputs exist before copying.\n * Throws with a clear error message if directories are missing.\n * @internal\n */\nexport async function validateBuildInputs(options: AdapterBuildOptions): Promise<void> {\n const { existsSync } = await import('node:fs')\n if (!existsSync(options.clientOutDir)) {\n throw new Error(`[zero:adapter] Client build output not found: ${options.clientOutDir}. Run \"vite build\" first.`)\n }\n if (!existsSync(options.serverEntry)) {\n throw new Error(`[zero:adapter] Server entry not found: ${options.serverEntry}. Run \"vite build --ssr\" first.`)\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Bun adapter — generates a standalone Bun.serve() entry.\n */\nexport function bunAdapter(): Adapter {\n return {\n name: 'bun',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n await mkdir(outDir, { recursive: true })\n\n // Copy server and client builds\n await cp(options.clientOutDir, join(outDir, 'client'), {\n recursive: true,\n })\n await cp(join(options.serverEntry, '..'), join(outDir, 'server'), {\n recursive: true,\n })\n\n const port = options.config.port ?? 3000\n const serverEntry = `\nconst handler = (await import(\"./server/entry-server.js\")).default\nconst clientDir = new URL(\"./client/\", import.meta.url).pathname\n\nBun.serve({\n port: ${port},\n async fetch(req) {\n const url = new URL(req.url)\n\n // Try static files first\n if (req.method === \"GET\") {\n const filePath = clientDir + (url.pathname === \"/\" ? \"index.html\" : url.pathname)\n // Prevent path traversal — ensure resolved path stays within clientDir\n const resolved = Bun.resolveSync(filePath, \".\")\n if (!resolved.startsWith(Bun.resolveSync(clientDir, \".\"))) {\n return new Response(\"Forbidden\", { status: 403 })\n }\n const file = Bun.file(filePath)\n if (await file.exists()) {\n return new Response(file, {\n headers: {\n \"cache-control\": filePath.endsWith(\".js\") || filePath.endsWith(\".css\")\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\",\n },\n })\n }\n }\n\n // Fall through to SSR handler\n return handler(req)\n },\n})\n\nconsole.log(\"\\\\n ⚡ Zero production server running on http://localhost:${port}\\\\n\")\n`.trimStart()\n\n await writeFile(join(outDir, 'index.ts'), serverEntry)\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Cloudflare Pages adapter — generates output for Cloudflare Pages with Functions.\n *\n * Produces:\n * - Client assets in the output directory root (served as static)\n * - `_worker.js` — Cloudflare Pages Function for SSR\n *\n * Note: Cloudflare Pages Functions have a ~1MB module size limit.\n * For large apps, configure Vite's SSR build to bundle server code:\n * `ssr: { noExternal: true }` in vite.config.ts.\n *\n * Deploy with: `npx wrangler pages deploy ./dist`\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { defineConfig } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * adapter: \"cloudflare\",\n * })\n * ```\n */\nexport function cloudflareAdapter(): Adapter {\n return {\n name: 'cloudflare',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n await mkdir(outDir, { recursive: true })\n\n // Copy client assets to root (Cloudflare serves static files from root)\n await cp(options.clientOutDir, outDir, { recursive: true })\n\n // Copy server build\n await cp(join(options.serverEntry, '..'), join(outDir, '_server'), {\n recursive: true,\n })\n\n // Generate Cloudflare Pages _worker.js (ES module format)\n const workerEntry = `\nimport handler from \"./_server/entry-server.js\"\n\nexport default {\n async fetch(request, env, ctx) {\n const url = new URL(request.url)\n\n // Let Cloudflare serve static assets (files with extensions)\n // This check is a fallback — Pages routes static files automatically\n const ext = url.pathname.split(\".\").pop()\n if (ext && ext !== url.pathname && !url.pathname.endsWith(\"/\")) {\n // Cloudflare Pages handles static assets automatically via its asset binding\n // Only reach here if the file doesn't exist — fall through to SSR\n }\n\n // SSR handler\n try {\n return await handler(request)\n } catch (err) {\n return new Response(\"Internal Server Error\", { status: 500 })\n }\n },\n}\n`.trimStart()\n\n await writeFile(join(outDir, '_worker.js'), workerEntry)\n\n // Cloudflare Pages config — _routes.json for routing\n const routesConfig = {\n version: 1,\n include: ['/*'],\n exclude: ['/assets/*', '/favicon.*', '/site.webmanifest', '/robots.txt', '/sitemap.xml'],\n }\n\n await writeFile(join(outDir, '_routes.json'), JSON.stringify(routesConfig, null, 2))\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Netlify adapter — generates output for Netlify Functions (v2).\n *\n * Produces:\n * - Client assets in `publish/` directory\n * - `netlify/functions/ssr.mjs` — Netlify Function for SSR\n * - `netlify.toml` — routing configuration\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { defineConfig } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * adapter: \"netlify\",\n * })\n * ```\n */\nexport function netlifyAdapter(): Adapter {\n return {\n name: 'netlify',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n const publishDir = join(outDir, 'publish')\n const functionsDir = join(outDir, 'netlify', 'functions')\n\n await mkdir(publishDir, { recursive: true })\n await mkdir(functionsDir, { recursive: true })\n\n // Copy client assets to publish/\n await cp(options.clientOutDir, publishDir, { recursive: true })\n\n // Copy server build to functions directory\n await cp(join(options.serverEntry, '..'), join(functionsDir, '_server'), {\n recursive: true,\n })\n\n // Generate Netlify Function (v2 format — ESM, Web-standard Request/Response)\n const funcEntry = `\nimport handler from \"./_server/entry-server.js\"\n\nexport default async function(req, context) {\n try {\n return await handler(req)\n } catch (err) {\n return new Response(\"Internal Server Error\", { status: 500 })\n }\n}\n\nexport const config = {\n path: \"/*\",\n preferStatic: true,\n}\n`.trimStart()\n\n await writeFile(join(functionsDir, 'ssr.mjs'), funcEntry)\n\n // Generate netlify.toml\n const toml = `\n[build]\n publish = \"publish\"\n functions = \"netlify/functions\"\n\n[[headers]]\n for = \"/assets/*\"\n [headers.values]\n Cache-Control = \"public, max-age=31536000, immutable\"\n\n[[redirects]]\n from = \"/*\"\n to = \"/.netlify/functions/ssr\"\n status = 200\n conditions = {Role = [\"admin\", \"user\", \"\"]}\n`.trimStart()\n\n await writeFile(join(outDir, 'netlify.toml'), toml)\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Node.js adapter — generates a standalone server entry using node:http.\n */\nexport function nodeAdapter(): Adapter {\n return {\n name: 'node',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n await mkdir(outDir, { recursive: true })\n\n // Copy server and client builds\n await cp(options.clientOutDir, join(outDir, 'client'), {\n recursive: true,\n })\n await cp(join(options.serverEntry, '..'), join(outDir, 'server'), {\n recursive: true,\n })\n\n // Generate standalone server entry\n const port = options.config.port ?? 3000\n const serverEntry = `\nimport { createServer } from \"node:http\"\nimport { readFile } from \"node:fs/promises\"\nimport { join, extname } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url))\nconst handler = (await import(\"./server/entry-server.js\")).default\nconst clientDir = join(__dirname, \"client\")\n\nconst MIME_TYPES = {\n \".html\": \"text/html\",\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".svg\": \"image/svg+xml\",\n \".woff2\": \"font/woff2\",\n \".woff\": \"font/woff\",\n \".ico\": \"image/x-icon\",\n}\n\nconst server = createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://localhost\")\n\n // Try to serve static files first\n if (req.method === \"GET\") {\n try {\n const filePath = join(clientDir, url.pathname === \"/\" ? \"index.html\" : url.pathname)\n // Prevent path traversal — ensure resolved path stays within clientDir\n const { resolve } = await import(\"node:path\")\n const resolved = resolve(filePath)\n if (!resolved.startsWith(resolve(clientDir))) {\n res.writeHead(403)\n res.end(\"Forbidden\")\n return\n }\n const ext = extname(filePath)\n if (ext && ext !== \".html\") {\n const data = await readFile(filePath)\n const mime = MIME_TYPES[ext] || \"application/octet-stream\"\n res.writeHead(200, {\n \"content-type\": mime,\n \"cache-control\": ext === \".js\" || ext === \".css\"\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\",\n })\n res.end(data)\n return\n }\n } catch {}\n }\n\n // Fall through to SSR handler\n const headers = {}\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) headers[key] = Array.isArray(value) ? value.join(\", \") : value\n }\n\n const request = new Request(url.href, {\n method: req.method,\n headers,\n })\n\n const response = await handler(request)\n const body = await response.text()\n\n const responseHeaders = {}\n response.headers.forEach((v, k) => { responseHeaders[k] = v })\n\n res.writeHead(response.status, responseHeaders)\n res.end(body)\n})\n\nserver.listen(${port}, () => {\n console.log(\"\\\\n ⚡ Zero production server running on http://localhost:${port}\\\\n\")\n})\n`.trimStart()\n\n await writeFile(join(outDir, 'index.js'), serverEntry)\n await writeFile(join(outDir, 'package.json'), JSON.stringify({ type: 'module' }, null, 2))\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\n\n/**\n * Static adapter — just copies the client build output.\n * Used with SSG mode where all pages are pre-rendered at build time.\n */\nexport function staticAdapter(): Adapter {\n return {\n name: 'static',\n async build(options: AdapterBuildOptions) {\n const { cp, mkdir } = await import('node:fs/promises')\n\n await mkdir(options.outDir, { recursive: true })\n await cp(options.clientOutDir, options.outDir, { recursive: true })\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Vercel adapter — generates output for Vercel's Build Output API v3.\n *\n * Produces a `.vercel/output` directory with:\n * - `static/` — client-side assets (JS, CSS, images)\n * - `functions/ssr.func/` — serverless function for SSR\n * - `config.json` — routing configuration\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { defineConfig } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * adapter: \"vercel\",\n * })\n * ```\n */\nexport function vercelAdapter(): Adapter {\n return {\n name: 'vercel',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const vercelDir = join(options.outDir, '.vercel', 'output')\n const staticDir = join(vercelDir, 'static')\n const funcDir = join(vercelDir, 'functions', 'ssr.func')\n\n await mkdir(staticDir, { recursive: true })\n await mkdir(funcDir, { recursive: true })\n\n // Copy client assets to static/\n await cp(options.clientOutDir, staticDir, { recursive: true })\n\n // Copy server build to function directory\n await cp(join(options.serverEntry, '..'), funcDir, { recursive: true })\n\n // Generate serverless function entry\n const funcEntry = `\nexport default async function handler(req) {\n const handler = (await import(\"./entry-server.js\")).default\n return handler(req)\n}\n`.trimStart()\n\n await writeFile(join(funcDir, 'index.js'), funcEntry)\n\n // Function config\n await writeFile(\n join(funcDir, '.vc-config.json'),\n JSON.stringify(\n {\n runtime: 'nodejs20.x',\n handler: 'index.js',\n launcherType: 'Nodejs',\n },\n null,\n 2,\n ),\n )\n\n // Vercel Build Output config\n const config = {\n version: 3,\n routes: [\n // Serve static assets directly\n {\n src: '/assets/(.*)',\n headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },\n },\n // Favicon and manifest\n { src: '/(favicon\\\\..*|site\\\\.webmanifest|robots\\\\.txt|sitemap\\\\.xml)', dest: '/$1' },\n // All other routes → SSR function\n { src: '/(.*)', dest: '/ssr' },\n ],\n }\n\n await writeFile(join(vercelDir, 'config.json'), JSON.stringify(config, null, 2))\n },\n }\n}\n","export { bunAdapter } from './bun'\nexport { cloudflareAdapter } from './cloudflare'\nexport { netlifyAdapter } from './netlify'\nexport { nodeAdapter } from './node'\nexport { staticAdapter } from './static'\nexport { vercelAdapter } from './vercel'\n\nimport type { Adapter, ZeroConfig } from '../types'\nimport { bunAdapter } from './bun'\nimport { cloudflareAdapter } from './cloudflare'\nimport { netlifyAdapter } from './netlify'\nimport { nodeAdapter } from './node'\nimport { staticAdapter } from './static'\nimport { vercelAdapter } from './vercel'\n\n/**\n * Resolve the adapter from config.\n * Returns a built-in adapter or throws if unknown.\n */\nexport function resolveAdapter(config: ZeroConfig): Adapter {\n const name = config.adapter ?? 'node'\n\n switch (name) {\n case 'node':\n return nodeAdapter()\n case 'bun':\n return bunAdapter()\n case 'static':\n return staticAdapter()\n case 'vercel':\n return vercelAdapter()\n case 'cloudflare':\n return cloudflareAdapter()\n case 'netlify':\n return netlifyAdapter()\n default:\n throw new Error(`[zero] Unknown adapter: \"${name}\". Use \"node\", \"bun\", \"static\", \"vercel\", \"cloudflare\", or \"netlify\".`)\n }\n}\n","import type { Middleware, MiddlewareContext } from '@pyreon/server'\n\n// ─── Middleware composition ─────────────────────────────────────────────────\n//\n// Chains multiple middleware functions into a single middleware.\n// Each middleware runs in order. If any returns a Response, the chain\n// short-circuits and that Response is returned. If all return void,\n// the composed middleware returns void (continues to rendering).\n\n/**\n * Compose multiple middleware into a single middleware function.\n * Middleware runs sequentially — if any returns a Response, the chain stops.\n *\n * @example\n * import { compose } from \"@pyreon/zero/middleware\"\n * import { corsMiddleware } from \"@pyreon/zero/cors\"\n * import { rateLimitMiddleware } from \"@pyreon/zero/rate-limit\"\n *\n * const combined = compose(\n * corsMiddleware({ origin: \"*\" }),\n * rateLimitMiddleware({ max: 100 }),\n * cacheMiddleware(),\n * )\n */\nexport function compose(...middlewares: Middleware[]): Middleware {\n return async (ctx: MiddlewareContext) => {\n for (const mw of middlewares) {\n const result = await mw(ctx)\n if (result instanceof Response) return result\n }\n }\n}\n\n// ─── Shared request context ─────────────────────────────────────────────────\n//\n// Lightweight context bag attached to MiddlewareContext.locals so middleware\n// can communicate without coupling. Uses a namespaced key to avoid collisions\n// with user-defined locals.\n\nconst ZERO_CTX_KEY = '__zeroCtx'\n\n/**\n * Get the shared Zero context from a middleware context.\n * Creates one if it doesn't exist. Middleware can use this to\n * pass data to downstream middleware without polluting `ctx.locals`.\n *\n * @example\n * const authMiddleware: Middleware = (ctx) => {\n * const zctx = getContext(ctx)\n * zctx.userId = \"user_123\"\n * }\n *\n * const loggingMiddleware: Middleware = (ctx) => {\n * const zctx = getContext(ctx)\n * console.log(\"User:\", zctx.userId)\n * }\n */\nexport function getContext(ctx: MiddlewareContext): Record<string, unknown> {\n let zctx = ctx.locals[ZERO_CTX_KEY] as Record<string, unknown> | undefined\n if (!zctx) {\n zctx = {}\n ctx.locals[ZERO_CTX_KEY] = zctx\n }\n return zctx\n}\n","/**\n * Dev-only error overlay for SSR/loader errors.\n * Renders a styled HTML page with the error stack trace.\n */\nexport function renderErrorOverlay(error: Error): string {\n const title = escapeHtml(error.message || 'Unknown error')\n const stack = escapeHtml(error.stack || '')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>SSR Error — Pyreon Zero</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: ui-monospace, \"Cascadia Code\", \"Source Code Pro\", Menlo, Consolas, monospace;\n background: #1a1a2e;\n color: #e0e0e0;\n min-height: 100vh;\n padding: 2rem;\n }\n .overlay {\n max-width: 900px;\n margin: 0 auto;\n }\n .header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 1.5rem;\n }\n .badge {\n background: #e74c3c;\n color: white;\n padding: 0.25rem 0.75rem;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n .label {\n color: #888;\n font-size: 0.85rem;\n }\n .message {\n font-size: 1.25rem;\n color: #ff6b6b;\n margin-bottom: 1.5rem;\n line-height: 1.5;\n word-break: break-word;\n }\n .stack {\n background: #16213e;\n border: 1px solid #2a2a4a;\n border-radius: 8px;\n padding: 1.25rem;\n overflow-x: auto;\n font-size: 0.8rem;\n line-height: 1.7;\n white-space: pre-wrap;\n word-break: break-all;\n }\n .stack .at { color: #888; }\n .stack .file { color: #4ecdc4; }\n .hint {\n margin-top: 1.5rem;\n padding: 1rem;\n background: #1e2a45;\n border-radius: 6px;\n border-left: 3px solid #3498db;\n font-size: 0.8rem;\n color: #aaa;\n line-height: 1.5;\n }\n </style>\n</head>\n<body>\n <div class=\"overlay\">\n <div class=\"header\">\n <span class=\"badge\">SSR Error</span>\n <span class=\"label\">Pyreon Zero — Dev Mode</span>\n </div>\n <div class=\"message\">${title}</div>\n <pre class=\"stack\">${formatStack(stack)}</pre>\n <div class=\"hint\">\n This error occurred during server-side rendering. Check the terminal for\n the full stack trace. This overlay is only shown in development.\n </div>\n </div>\n</body>\n</html>`\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction formatStack(stack: string): string {\n return stack\n .split('\\n')\n .map((line) => {\n if (line.includes('at ')) {\n const fileMatch = line.match(/\\(([^)]+)\\)/)\n if (fileMatch) {\n return line.replace(fileMatch[0], `(<span class=\"file\">${fileMatch[1]}</span>)`)\n }\n }\n return line\n })\n .join('\\n')\n}\n","import { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Plugin } from 'vite'\nimport { generateApiRouteModule } from './api-routes'\nimport { resolveConfig } from './config'\n\n/**\n * Scan node_modules/@pyreon/ to discover all installed Pyreon packages.\n * Returns package names to exclude from Vite's dep optimizer.\n */\nfunction scanPyreonPackages(root: string): string[] {\n const pyreonDir = join(root, 'node_modules', '@pyreon')\n if (!existsSync(pyreonDir)) return []\n\n try {\n return readdirSync(pyreonDir)\n .filter((name) => !name.startsWith('.'))\n .map((name) => `@pyreon/${name}`)\n } catch {\n return []\n }\n}\nimport { matchPattern } from \"./entry-server\";\nimport { renderErrorOverlay } from \"./error-overlay\";\nimport {\n\tgenerateMiddlewareModule,\n\tgenerateRouteModule,\n\tscanRouteFiles,\n} from \"./fs-router\";\nimport { render404Page } from \"./not-found\";\nimport type { ZeroConfig } from \"./types\";\n\nconst VIRTUAL_ROUTES_ID = \"virtual:zero/routes\";\nconst RESOLVED_VIRTUAL_ROUTES_ID = `\\0${VIRTUAL_ROUTES_ID}`;\n\nconst VIRTUAL_MIDDLEWARE_ID = \"virtual:zero/route-middleware\";\nconst RESOLVED_VIRTUAL_MIDDLEWARE_ID = `\\0${VIRTUAL_MIDDLEWARE_ID}`;\n\nconst VIRTUAL_API_ROUTES_ID = \"virtual:zero/api-routes\";\nconst RESOLVED_VIRTUAL_API_ROUTES_ID = `\\0${VIRTUAL_API_ROUTES_ID}`;\n\n/**\n * Zero Vite plugin — adds file-based routing and zero-config conventions\n * on top of @pyreon/vite-plugin.\n *\n * @example\n * // vite.config.ts\n * import pyreon from \"@pyreon/vite-plugin\"\n * import zero from \"@pyreon/zero\"\n *\n * export default {\n * plugins: [pyreon(), zero()],\n * }\n */\nexport function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {\n\tconst config = resolveConfig(userConfig);\n\tlet routesDir: string;\n\tlet root: string;\n\n\tconst plugin: Plugin & { _zeroConfig: ZeroConfig } = {\n\t\tname: \"pyreon-zero\",\n\t\tenforce: \"pre\",\n\t\t_zeroConfig: userConfig,\n\n\t\tconfigResolved(resolvedConfig) {\n\t\t\troot = resolvedConfig.root;\n\t\t\troutesDir = `${root}/src/routes`;\n\t\t},\n\n\t\tresolveId(id) {\n\t\t\tif (id === VIRTUAL_ROUTES_ID) return RESOLVED_VIRTUAL_ROUTES_ID;\n\t\t\tif (id === VIRTUAL_MIDDLEWARE_ID) return RESOLVED_VIRTUAL_MIDDLEWARE_ID;\n\t\t\tif (id === VIRTUAL_API_ROUTES_ID) return RESOLVED_VIRTUAL_API_ROUTES_ID;\n\t\t},\n\n\t\tasync load(id) {\n\t\t\tif (id === RESOLVED_VIRTUAL_ROUTES_ID) {\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await scanRouteFiles(routesDir);\n\t\t\t\t\treturn generateRouteModule(files, routesDir, {\n\t\t\t\t\tstaticImports: config.mode === 'ssg',\n\t\t\t\t});\n\t\t\t\t} catch (_err) {\n\t\t\t\t\treturn `export const routes = []`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (id === RESOLVED_VIRTUAL_MIDDLEWARE_ID) {\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await scanRouteFiles(routesDir);\n\t\t\t\t\treturn generateMiddlewareModule(files, routesDir);\n\t\t\t\t} catch (_err) {\n\t\t\t\t\treturn `export const routeMiddleware = []`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (id === RESOLVED_VIRTUAL_API_ROUTES_ID) {\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await scanRouteFiles(routesDir);\n\t\t\t\t\treturn generateApiRouteModule(files, routesDir);\n\t\t\t\t} catch (_err) {\n\t\t\t\t\treturn `export const apiRoutes = []`;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tconfigureServer(server) {\n\t\t\t// 404 handler — check if the requested path matches any route.\n\t\t\t// If not, render the nearest _404.tsx component with a 404 status.\n\t\t\t// Uses a sync wrapper that calls the async handler, since Connect\n\t\t\t// middleware does not natively support async functions.\n\t\t\tserver.middlewares.use((req, res, next) => {\n\t\t\t\tconst accept = req.headers.accept ?? \"\";\n\t\t\t\t// Accept HTML requests and wildcard requests (fetch without explicit Accept header)\n\t\t\t\tif (!accept.includes(\"text/html\") && !accept.includes(\"*/*\"))\n\t\t\t\t\treturn next();\n\n\t\t\t\tconst pathname = req.url?.split(\"?\")[0] ?? \"/\";\n\n\t\t\t\t// Skip static assets, Vite internal requests, and file-like paths (with extensions)\n\t\t\t\tif (pathname.startsWith(\"/@\") || pathname.startsWith(\"/__\"))\n\t\t\t\t\treturn next();\n\t\t\t\tif (/\\.\\w+$/.test(pathname)) return next();\n\n\t\t\t\thandle404(server, routesDir, pathname, res).then(\n\t\t\t\t\t(handled) => {\n\t\t\t\t\t\tif (!handled) next();\n\t\t\t\t\t},\n\t\t\t\t\t(err) => {\n\t\t\t\t\t\t// oxlint-disable-next-line no-console\n\t\t\t\t\t\tconsole.error('[zero] Error in 404 handler:', err);\n\t\t\t\t\t\tnext();\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\n\t\t\t// SSR error overlay — intercept HTML requests and catch SSR errors\n\t\t\t// This runs as a late middleware (return function) so it wraps\n\t\t\t// Vite's own SSR handling and catches rendering failures.\n\t\t\tserver.middlewares.use((req, res, next) => {\n\t\t\t\tconst accept = req.headers.accept ?? \"\";\n\t\t\t\tif (!accept.includes(\"text/html\")) return next();\n\n\t\t\t\tconst originalEnd = res.end.bind(res);\n\t\t\t\tlet errored = false;\n\n\t\t\t\tconst handleError = (err: unknown) => {\n\t\t\t\t\tif (errored) return;\n\t\t\t\t\terrored = true;\n\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.ssrFixStacktrace(error);\n\t\t\t\t\tconst html = renderErrorOverlay(error);\n\t\t\t\t\tres.statusCode = 500;\n\t\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\t\tres.setHeader(\"Content-Length\", Buffer.byteLength(html));\n\t\t\t\t\toriginalEnd(html);\n\t\t\t\t};\n\n\t\t\t\tres.on(\"error\", handleError);\n\n\t\t\t\t// Wrap next() in try/catch to handle both sync and async errors.\n\t\t\t\t// Express-style middleware may throw synchronously or pass errors\n\t\t\t\t// through next(err), and Vite's SSR pipeline may reject promises.\n\t\t\t\ttry {\n\t\t\t\t\tconst result = next() as unknown;\n\t\t\t\t\t// Handle async errors from Vite's SSR pipeline\n\t\t\t\t\tif (\n\t\t\t\t\t\tresult &&\n\t\t\t\t\t\ttypeof (result as Promise<unknown>).catch === \"function\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t(result as Promise<unknown>).catch(handleError);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\thandleError(err);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Watch routes directory for changes\n\t\t\tserver.watcher.add(`${routesDir}/**/*.{tsx,jsx,ts,js}`);\n\n\t\t\t// Invalidate virtual modules when route files change\n\t\t\tserver.watcher.on(\"all\", (event, path) => {\n\t\t\t\tif (\n\t\t\t\t\tpath.startsWith(routesDir) &&\n\t\t\t\t\t(event === \"add\" || event === \"unlink\")\n\t\t\t\t) {\n\t\t\t\t\tfor (const resolvedId of [\n\t\t\t\t\t\tRESOLVED_VIRTUAL_ROUTES_ID,\n\t\t\t\t\t\tRESOLVED_VIRTUAL_MIDDLEWARE_ID,\n\t\t\t\t\t\tRESOLVED_VIRTUAL_API_ROUTES_ID,\n\t\t\t\t\t]) {\n\t\t\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedId);\n\t\t\t\t\t\tif (mod) server.moduleGraph.invalidateModule(mod);\n\t\t\t\t\t}\n\t\t\t\t\tserver.ws.send({ type: \"full-reload\" });\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tconfig(userConfig) {\n\t\t\t// Discover all @pyreon/* packages installed in node_modules.\n\t\t\t// The \"bun\" export condition points to TS source — esbuild's\n\t\t\t// dep optimizer would compile them with the wrong JSX runtime.\n\t\t\tconst root = userConfig.root ?? process.cwd()\n\t\t\tconst pyreonExclude = scanPyreonPackages(root)\n\n\t\t\treturn {\n\t\t\t\tresolve: {\n\t\t\t\t\tconditions: ['bun'],\n\t\t\t\t},\n\t\t\t\toptimizeDeps: {\n\t\t\t\t\texclude: pyreonExclude,\n\t\t\t\t},\n\t\t\t\tserver: {\n\t\t\t\t\tport: config.port,\n\t\t\t\t},\n\t\t\t\tdefine: {\n\t\t\t\t\t__ZERO_MODE__: JSON.stringify(config.mode),\n\t\t\t\t\t__ZERO_BASE__: JSON.stringify(config.base),\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n\n\treturn plugin;\n}\n\n/**\n * Check if the requested path matches any route. If not, render a 404 page.\n * Returns true if the 404 was handled (response sent), false otherwise.\n *\n * In dev mode, the _404.tsx component cannot be SSR-rendered because\n * the compiler emits _tpl() calls that require `document`. Instead,\n * we return a static 404 page. The actual component rendering happens\n * on the client side when the SPA loads.\n */\nasync function handle404(\n\tserver: import(\"vite\").ViteDevServer,\n\t_routesDir: string,\n\tpathname: string,\n\tres: import(\"http\").ServerResponse,\n): Promise<boolean> {\n\tconst mod = await server.ssrLoadModule(VIRTUAL_ROUTES_ID);\n\tconst routes = mod.routes as Array<{ path?: string; children?: unknown[] }>;\n\tconst patterns = flattenRoutePatterns(routes);\n\n\tif (patterns.some((pattern) => matchPattern(pattern, pathname))) {\n\t\treturn false; // Route matches — not a 404\n\t}\n\n\t// No route matched — return a 404.\n\t// In dev, we return a static page since the compiler emits _tpl() calls\n\t// that require document (unavailable in SSR). The _404.tsx component\n\t// renders on the client side after hydration.\n\tconst html = await render404Page(undefined);\n\n\tres.statusCode = 404;\n\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\tres.setHeader(\"Content-Length\", Buffer.byteLength(html));\n\tres.end(html);\n\treturn true;\n}\n\n/** Extract all URL patterns from a nested route tree. */\nfunction flattenRoutePatterns(\n\troutes: Array<{ path?: string; children?: unknown[] }>,\n\tprefix = \"\",\n): string[] {\n\tconst patterns: string[] = [];\n\tfor (const route of routes) {\n\t\tif (!route.path) continue;\n\t\tconst fullPath =\n\t\t\troute.path === \"/\" && prefix ? prefix : `${prefix}${route.path}`;\n\t\tpatterns.push(fullPath);\n\t\tif (route.children) {\n\t\t\tpatterns.push(\n\t\t\t\t...flattenRoutePatterns(\n\t\t\t\t\troute.children as Array<{ path?: string; children?: unknown[] }>,\n\t\t\t\t\tfullPath,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn patterns;\n}\n","import { createContext } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { Plugin } from 'vite'\n\n// ─── Localized routing ─────────────────────────────────────────────────────\n//\n// Adds locale-prefixed routes to Zero's file-system router:\n// - /about → /en/about, /de/about, /cs/about\n// - / → /en, /de, /cs (or default locale without prefix)\n// - Automatic locale detection from Accept-Language header\n// - Redirect to preferred locale\n// - hreflang link generation\n//\n// Usage:\n// import { i18nRouting } from \"@pyreon/zero\"\n// export default { plugins: [zero(), i18nRouting({ locales: [\"en\", \"de\"], defaultLocale: \"en\" })] }\n\nexport interface I18nRoutingConfig {\n /** Supported locales. e.g. [\"en\", \"de\", \"cs\"] */\n locales: string[]\n /** Default locale — served without prefix (/ instead of /en/). */\n defaultLocale: string\n /** Redirect root to detected locale. Default: true */\n detectLocale?: boolean\n /** Cookie name to persist locale preference. Default: \"locale\" */\n cookieName?: string\n /** URL strategy. Default: \"prefix-except-default\" */\n strategy?: 'prefix' | 'prefix-except-default'\n}\n\nexport interface LocaleContext {\n /** Current locale code. e.g. \"en\", \"de\" */\n locale: string\n /** All supported locales. */\n locales: string[]\n /** Default locale. */\n defaultLocale: string\n /** Build a localized path. e.g. localePath(\"/about\", \"de\") → \"/de/about\" */\n localePath: (path: string, locale?: string) => string\n /** Get hreflang alternates for the current path. */\n alternates: () => Array<{ locale: string; url: string }>\n}\n\n/**\n * Detect preferred locale from Accept-Language header.\n */\nexport function detectLocaleFromHeader(\n acceptLanguage: string | null | undefined,\n locales: string[],\n defaultLocale: string,\n): string {\n if (!acceptLanguage) return defaultLocale\n\n // Parse Accept-Language: en-US,en;q=0.9,de;q=0.8\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q] = part.trim().split(';q=')\n return {\n lang: lang?.split('-')[0]?.toLowerCase() ?? '',\n quality: q ? Number.parseFloat(q) : 1,\n }\n })\n .sort((a, b) => b.quality - a.quality)\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) return lang\n }\n\n return defaultLocale\n}\n\n/**\n * Extract locale from a URL path.\n * Returns { locale, pathWithoutLocale }.\n */\nexport function extractLocaleFromPath(\n path: string,\n locales: string[],\n defaultLocale: string,\n): { locale: string; pathWithoutLocale: string } {\n const segments = path.split('/').filter(Boolean)\n const firstSegment = segments[0]?.toLowerCase()\n\n if (firstSegment && locales.includes(firstSegment)) {\n return {\n locale: firstSegment,\n pathWithoutLocale: '/' + segments.slice(1).join('/') || '/',\n }\n }\n\n return { locale: defaultLocale, pathWithoutLocale: path }\n}\n\n/**\n * Build a localized path.\n */\nexport function buildLocalePath(\n path: string,\n locale: string,\n defaultLocale: string,\n strategy: 'prefix' | 'prefix-except-default',\n): string {\n const clean = path === '/' ? '' : path\n if (strategy === 'prefix-except-default' && locale === defaultLocale) {\n return path\n }\n return `/${locale}${clean}`\n}\n\n/**\n * Create a LocaleContext for use in components and loaders.\n */\nexport function createLocaleContext(\n locale: string,\n path: string,\n config: I18nRoutingConfig,\n): LocaleContext {\n const strategy = config.strategy ?? 'prefix-except-default'\n\n return {\n locale,\n locales: config.locales,\n defaultLocale: config.defaultLocale,\n\n localePath(targetPath: string, targetLocale?: string) {\n return buildLocalePath(\n targetPath,\n targetLocale ?? locale,\n config.defaultLocale,\n strategy,\n )\n },\n\n alternates() {\n const { pathWithoutLocale } = extractLocaleFromPath(\n path,\n config.locales,\n config.defaultLocale,\n )\n return config.locales.map((loc) => ({\n locale: loc,\n url: buildLocalePath(pathWithoutLocale, loc, config.defaultLocale, strategy),\n }))\n },\n }\n}\n\n/**\n * I18n routing middleware for Zero's server.\n *\n * - Detects locale from URL prefix or Accept-Language header\n * - Redirects root to preferred locale (when detectLocale is true)\n * - Sets locale context for loaders and components\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { i18nRouting } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * plugins: [\n * i18nRouting({\n * locales: [\"en\", \"de\", \"cs\"],\n * defaultLocale: \"en\",\n * }),\n * ],\n * })\n * ```\n */\nexport function i18nRouting(config: I18nRoutingConfig): Plugin {\n const strategy = config.strategy ?? 'prefix-except-default'\n const detectEnabled = config.detectLocale !== false\n const cookieName = config.cookieName ?? 'locale'\n\n return {\n name: 'pyreon-zero-i18n-routing',\n\n // Route duplication is NOT handled here. The fs-router's `scanRouteFiles`\n // consumes the i18n config to duplicate routes per locale at build time.\n // This plugin only provides: (1) the server middleware for locale detection\n // and (2) the runtime hooks (useLocale, setLocale) for client-side use.\n configResolved() {},\n\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '/'\n\n // Skip static assets\n if (url.startsWith('/@') || url.startsWith('/__') || url.includes('.')) {\n return next()\n }\n\n const { locale } = extractLocaleFromPath(\n url,\n config.locales,\n config.defaultLocale,\n )\n\n // Redirect root to detected locale\n if (detectEnabled && url === '/') {\n const cookies = parseCookies(req.headers.cookie)\n const preferredFromCookie = cookies[cookieName]\n const preferredFromHeader = detectLocaleFromHeader(\n req.headers['accept-language'],\n config.locales,\n config.defaultLocale,\n )\n const preferred = preferredFromCookie && config.locales.includes(preferredFromCookie)\n ? preferredFromCookie\n : preferredFromHeader\n\n if (strategy === 'prefix' || preferred !== config.defaultLocale) {\n res.writeHead(302, { Location: `/${preferred}/` })\n res.end()\n return\n }\n }\n\n // Attach locale context to request for loaders\n ;(req as any).__locale = locale\n ;(req as any).__localeContext = createLocaleContext(locale, url, config)\n\n // Update the module-level signal so useLocale() returns the correct value\n localeSignal.set(locale)\n\n next()\n })\n },\n }\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n if (!header) return {}\n const result: Record<string, string> = {}\n for (const pair of header.split(';')) {\n const [key, value] = pair.trim().split('=')\n if (key && value) result[key] = decodeURIComponent(value)\n }\n return result\n}\n\n// ─── Reactive locale hook ───────────────────────────────────────────────────\n\n/** @internal Context for the current locale. */\nexport const LocaleCtx = createContext<string>('en')\n\n/** Current locale signal — set by the server middleware or client-side detection. */\nexport const localeSignal = signal('en')\n\n/**\n * Read the current locale reactively.\n *\n * Returns the locale signal value directly — reactive in both SSR and CSR.\n * The server middleware sets `localeSignal` per-request, and client-side\n * `setLocale()` updates it as well.\n *\n * @example\n * ```tsx\n * const locale = useLocale() // \"en\", \"de\", etc.\n * ```\n */\nexport function useLocale(): string {\n return localeSignal()\n}\n\n/**\n * Set the locale client-side and update the URL.\n *\n * @example\n * ```tsx\n * <button onClick={() => setLocale('de')}>Deutsch</button>\n * ```\n */\nexport function setLocale(\n locale: string,\n config: I18nRoutingConfig,\n): void {\n localeSignal.set(locale)\n\n // Persist to cookie\n if (typeof document !== 'undefined') {\n document.cookie = `${config.cookieName ?? 'locale'}=${locale}; path=/; max-age=31536000`\n }\n\n // Navigate to localized URL — use pushState to avoid full page reload\n if (typeof window !== 'undefined') {\n const strategy = config.strategy ?? 'prefix-except-default'\n const { pathWithoutLocale } = extractLocaleFromPath(\n window.location.pathname,\n config.locales,\n config.defaultLocale,\n )\n const newPath = buildLocalePath(pathWithoutLocale, locale, config.defaultLocale, strategy)\n window.history.pushState(null, '', newPath)\n // Dispatch popstate so @pyreon/router picks up the URL change\n window.dispatchEvent(new PopStateEvent('popstate'))\n }\n}\n"],"mappings":"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"}
|