@pyreon/router 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/loader.ts","../src/match.ts","../src/scroll.ts","../src/types.ts","../src/router.ts","../src/components.tsx"],"sourcesContent":["import type { Context } from \"@pyreon/core\"\nimport { createContext, useContext } from \"@pyreon/core\"\nimport type { RouterInstance } from \"./types\"\n\n/**\n * Context frame that holds the loader data for the currently rendered route record.\n * Pushed by RouterView's withLoaderData wrapper before invoking the route component.\n */\nexport const LoaderDataContext: Context<unknown> = createContext<unknown>(undefined)\n\n/**\n * Returns the data resolved by the current route's `loader` function.\n * Must be called inside a route component rendered by <RouterView />.\n *\n * @example\n * const routes = [{ path: \"/users\", component: Users, loader: fetchUsers }]\n *\n * function Users() {\n * const users = useLoaderData<User[]>()\n * return h(\"ul\", null, users.map(u => h(\"li\", null, u.name)))\n * }\n */\nexport function useLoaderData<T = unknown>(): T {\n return useContext(LoaderDataContext) as T\n}\n\n/**\n * SSR helper: pre-run all loaders for the given path before rendering.\n * Call this before `renderToString` so route components can read data via `useLoaderData()`.\n *\n * @example\n * const router = createRouter({ routes, url: req.url })\n * await prefetchLoaderData(router, req.url)\n * const html = await renderToString(h(App, { router }))\n */\nexport async function prefetchLoaderData(router: RouterInstance, path: string): Promise<void> {\n const route = router._resolve(path)\n const ac = new AbortController()\n router._abortController = ac\n await Promise.all(\n route.matched\n .filter((r) => r.loader)\n .map(async (r) => {\n const data = await r.loader?.({\n params: route.params,\n query: route.query,\n signal: ac.signal,\n })\n router._loaderData.set(r, data)\n }),\n )\n}\n\n/**\n * Serialize loader data to a JSON-safe plain object for embedding in SSR HTML.\n * Keys are route path patterns (stable across server and client).\n *\n * @example — SSR handler:\n * await prefetchLoaderData(router, req.url)\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `...${head}\n * <script>window.__PYREON_LOADER_DATA__=${JSON.stringify(serializeLoaderData(router))}</script>\n * ...${html}...`\n */\nexport function serializeLoaderData(router: RouterInstance): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const [record, data] of router._loaderData) {\n result[record.path] = data\n }\n return result\n}\n\n/**\n * Hydrate loader data from a serialized object (e.g. `window.__PYREON_LOADER_DATA__`).\n * Populates the router's internal `_loaderData` map so the initial render uses\n * server-fetched data without re-running loaders on the client.\n *\n * Call this before `mount()`, after `createRouter()`.\n *\n * @example — client entry:\n * import { hydrateLoaderData } from \"@pyreon/router\"\n * const router = createRouter({ routes })\n * hydrateLoaderData(router, window.__PYREON_LOADER_DATA__ ?? {})\n * mount(h(App, null), document.getElementById(\"app\")!)\n */\nexport function hydrateLoaderData(\n router: RouterInstance,\n serialized: Record<string, unknown>,\n): void {\n if (!serialized || typeof serialized !== \"object\") return\n const route = router._resolve(router.currentRoute().path)\n for (const record of route.matched) {\n if (Object.hasOwn(serialized, record.path)) {\n router._loaderData.set(record, serialized[record.path])\n }\n }\n}\n","import type { ResolvedRoute, RouteMeta, RouteRecord } from \"./types\"\n\n// ─── Query string ─────────────────────────────────────────────────────────────\n\n/**\n * Parse a query string into key-value pairs. Duplicate keys are overwritten\n * (last value wins). Use `parseQueryMulti` to preserve duplicates as arrays.\n */\nexport function parseQuery(qs: string): Record<string, string> {\n if (!qs) return {}\n const result: Record<string, string> = {}\n for (const part of qs.split(\"&\")) {\n const eqIdx = part.indexOf(\"=\")\n if (eqIdx < 0) {\n const key = decodeURIComponent(part)\n if (key) result[key] = \"\"\n } else {\n const key = decodeURIComponent(part.slice(0, eqIdx))\n const val = decodeURIComponent(part.slice(eqIdx + 1))\n if (key) result[key] = val\n }\n }\n return result\n}\n\n/**\n * Parse a query string preserving duplicate keys as arrays.\n *\n * @example\n * parseQueryMulti(\"color=red&color=blue&size=lg\")\n * // → { color: [\"red\", \"blue\"], size: \"lg\" }\n */\nexport function parseQueryMulti(qs: string): Record<string, string | string[]> {\n if (!qs) return {}\n const result: Record<string, string | string[]> = {}\n for (const part of qs.split(\"&\")) {\n const eqIdx = part.indexOf(\"=\")\n let key: string\n let val: string\n if (eqIdx < 0) {\n key = decodeURIComponent(part)\n val = \"\"\n } else {\n key = decodeURIComponent(part.slice(0, eqIdx))\n val = decodeURIComponent(part.slice(eqIdx + 1))\n }\n if (!key) continue\n const existing = result[key]\n if (existing === undefined) {\n result[key] = val\n } else if (Array.isArray(existing)) {\n existing.push(val)\n } else {\n result[key] = [existing, val]\n }\n }\n return result\n}\n\nexport function stringifyQuery(query: Record<string, string>): string {\n const parts: string[] = []\n for (const [k, v] of Object.entries(query)) {\n parts.push(v ? `${encodeURIComponent(k)}=${encodeURIComponent(v)}` : encodeURIComponent(k))\n }\n return parts.length ? `?${parts.join(\"&\")}` : \"\"\n}\n\n// ─── Path matching ────────────────────────────────────────────────────────────\n\n/**\n * Match a single route pattern against a path segment.\n * Returns extracted params or null if no match.\n *\n * Supports:\n * - Exact segments: \"/about\"\n * - Param segments: \"/user/:id\"\n * - Wildcard: \"(.*)\" matches everything\n */\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n // Wildcard pattern\n if (pattern === \"(.*)\" || pattern === \"*\") return {}\n\n const patternParts = pattern.split(\"/\").filter(Boolean)\n const pathParts = path.split(\"/\").filter(Boolean)\n\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i] as string\n const pt = pathParts[i] as string\n // Splat param — captures the rest of the path (e.g. \":path*\")\n if (pp.endsWith(\"*\") && pp.startsWith(\":\")) {\n const paramName = pp.slice(1, -1)\n params[paramName] = pathParts.slice(i).map(decodeURIComponent).join(\"/\")\n return params\n }\n if (pp.startsWith(\":\")) {\n params[pp.slice(1)] = decodeURIComponent(pt)\n } else if (pp !== pt) {\n return null\n }\n }\n\n if (patternParts.length !== pathParts.length) return null\n return params\n}\n\n/**\n * Check if a path starts with a route's prefix (for nested route matching).\n * Returns the remaining path suffix, or null if no match.\n */\nfunction matchPrefix(\n pattern: string,\n path: string,\n): { params: Record<string, string>; rest: string } | null {\n if (pattern === \"(.*)\" || pattern === \"*\") return { params: {}, rest: path }\n\n const patternParts = pattern.split(\"/\").filter(Boolean)\n const pathParts = path.split(\"/\").filter(Boolean)\n\n if (pathParts.length < patternParts.length) return null\n\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i] as string\n const pt = pathParts[i] as string\n // Splat param in prefix — captures the rest\n if (pp.endsWith(\"*\") && pp.startsWith(\":\")) {\n const paramName = pp.slice(1, -1)\n params[paramName] = pathParts.slice(i).map(decodeURIComponent).join(\"/\")\n return { params, rest: \"/\" }\n }\n if (pp.startsWith(\":\")) {\n params[pp.slice(1)] = decodeURIComponent(pt)\n } else if (pp !== pt) {\n return null\n }\n }\n\n const rest = `/${pathParts.slice(patternParts.length).join(\"/\")}`\n return { params, rest }\n}\n\n// ─── Route resolution ─────────────────────────────────────────────────────────\n\n/**\n * Resolve a raw path (including query string and hash) against the route tree.\n * Handles nested routes recursively.\n */\nexport function resolveRoute(rawPath: string, routes: RouteRecord[]): ResolvedRoute {\n const qIdx = rawPath.indexOf(\"?\")\n const pathAndHash = qIdx >= 0 ? rawPath.slice(0, qIdx) : rawPath\n const queryPart = qIdx >= 0 ? rawPath.slice(qIdx + 1) : \"\"\n\n const hIdx = pathAndHash.indexOf(\"#\")\n const cleanPath = hIdx >= 0 ? pathAndHash.slice(0, hIdx) : pathAndHash\n const hash = hIdx >= 0 ? pathAndHash.slice(hIdx + 1) : \"\"\n\n const query = parseQuery(queryPart)\n\n const match = matchRoutes(cleanPath, routes, [])\n if (match) {\n return {\n path: cleanPath,\n params: match.params,\n query,\n hash,\n matched: match.matched,\n meta: mergeMeta(match.matched),\n }\n }\n\n return { path: cleanPath, params: {}, query, hash, matched: [], meta: {} }\n}\n\ninterface MatchResult {\n params: Record<string, string>\n matched: RouteRecord[]\n}\n\nfunction matchRoutes(\n path: string,\n routes: RouteRecord[],\n parentMatched: RouteRecord[],\n parentParams: Record<string, string> = {},\n): MatchResult | null {\n for (const route of routes) {\n const result = matchSingleRoute(path, route, parentMatched, parentParams)\n if (result) return result\n }\n return null\n}\n\nfunction matchSingleRoute(\n path: string,\n route: RouteRecord,\n parentMatched: RouteRecord[],\n parentParams: Record<string, string>,\n): MatchResult | null {\n if (!route.children || route.children.length === 0) {\n const params = matchPath(route.path, path)\n if (params === null) return null\n return { params: { ...parentParams, ...params }, matched: [...parentMatched, route] }\n }\n\n const prefix = matchPrefix(route.path, path)\n if (prefix === null) return null\n\n const allParams = { ...parentParams, ...prefix.params }\n const matched = [...parentMatched, route]\n\n const childMatch = matchRoutes(prefix.rest, route.children, matched, allParams)\n if (childMatch) return childMatch\n\n const exactParams = matchPath(route.path, path)\n if (exactParams === null) return null\n return { params: { ...parentParams, ...exactParams }, matched }\n}\n\n/** Merge meta from matched routes (leaf takes precedence) */\nfunction mergeMeta(matched: RouteRecord[]): RouteMeta {\n const meta: RouteMeta = {}\n for (const record of matched) {\n if (record.meta) Object.assign(meta, record.meta)\n }\n return meta\n}\n\n/** Build a path string from a named route's pattern and params */\nexport function buildPath(pattern: string, params: Record<string, string>): string {\n return pattern.replace(/:([^/]+)\\*?/g, (match, key) => {\n const val = params[key] ?? \"\"\n // Splat params contain slashes — don't encode them\n if (match.endsWith(\"*\")) return val.split(\"/\").map(encodeURIComponent).join(\"/\")\n return encodeURIComponent(val)\n })\n}\n\n/** Find a route record by name (recursive, O(n)). Prefer buildNameIndex for repeated lookups. */\nexport function findRouteByName(name: string, routes: RouteRecord[]): RouteRecord | null {\n for (const route of routes) {\n if (route.name === name) return route\n if (route.children) {\n const found = findRouteByName(name, route.children)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Pre-build a name → RouteRecord index from a route tree for O(1) named navigation.\n * Called once at router creation time; avoids O(n) depth-first search per push({ name }).\n */\nexport function buildNameIndex(routes: RouteRecord[]): Map<string, RouteRecord> {\n const index = new Map<string, RouteRecord>()\n function walk(list: RouteRecord[]): void {\n for (const route of list) {\n if (route.name) index.set(route.name, route)\n if (route.children) walk(route.children)\n }\n }\n walk(routes)\n return index\n}\n","import type { ResolvedRoute, RouterOptions } from \"./types\"\n\n/**\n * Scroll restoration manager.\n *\n * Saves scroll position before each navigation and restores it when\n * navigating back to a previously visited path.\n */\nexport class ScrollManager {\n private readonly _positions = new Map<string, number>()\n private readonly _behavior: RouterOptions[\"scrollBehavior\"]\n\n constructor(behavior: RouterOptions[\"scrollBehavior\"] = \"top\") {\n this._behavior = behavior\n }\n\n /** Call before navigating away — saves current scroll position for `fromPath` */\n save(fromPath: string): void {\n // save/restore are only called from browser navigation paths (guarded by caller)\n this._positions.set(fromPath, window.scrollY)\n }\n\n /** Call after navigation is committed — applies scroll behavior */\n restore(to: ResolvedRoute, from: ResolvedRoute): void {\n const behavior = (to.meta.scrollBehavior as typeof this._behavior) ?? this._behavior ?? \"top\"\n\n if (typeof behavior === \"function\") {\n const saved = this._positions.get(to.path) ?? null\n const result = behavior(to, from, saved)\n this._applyResult(result, to.path)\n return\n }\n\n this._applyResult(behavior, to.path)\n }\n\n private _applyResult(result: \"top\" | \"restore\" | \"none\" | number, toPath: string): void {\n if (result === \"none\") return\n if (result === \"top\" || result === undefined) {\n window.scrollTo({ top: 0, behavior: \"instant\" as ScrollBehavior })\n return\n }\n if (result === \"restore\") {\n const saved = this._positions.get(toPath) ?? 0\n window.scrollTo({ top: saved, behavior: \"instant\" as ScrollBehavior })\n return\n }\n // At this point result must be a number (all string cases handled above)\n window.scrollTo({ top: result as number, behavior: \"instant\" as ScrollBehavior })\n }\n\n getSavedPosition(path: string): number | null {\n return this._positions.get(path) ?? null\n }\n}\n","import type { ComponentFn } from \"@pyreon/core\"\nexport type { ComponentFn }\n\n// ─── Path param extraction ────────────────────────────────────────────────────\n\n/**\n * Extracts typed params from a path string at compile time.\n *\n * @example\n * ExtractParams<'/user/:id/posts/:postId'>\n * // → { id: string; postId: string }\n */\nexport type ExtractParams<T extends string> = T extends `${string}:${infer Param}*/${infer Rest}`\n ? { [K in Param]: string } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}*`\n ? { [K in Param]: string }\n : T extends `${string}:${infer Param}/${infer Rest}`\n ? { [K in Param]: string } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? { [K in Param]: string }\n : Record<never, never>\n\n// ─── Route meta ───────────────────────────────────────────────────────────────\n\n/**\n * Route metadata interface. Extend it via module augmentation to add custom fields:\n *\n * @example\n * // globals.d.ts\n * declare module \"@pyreon/router\" {\n * interface RouteMeta {\n * requiresRole?: \"admin\" | \"user\"\n * pageTitle?: string\n * }\n * }\n */\nexport interface RouteMeta {\n /** Sets document.title on navigation */\n title?: string\n /** Page description (for meta tags) */\n description?: string\n /** If true, guards can redirect to login */\n requiresAuth?: boolean\n /** Scroll behavior for this route */\n scrollBehavior?: \"top\" | \"restore\" | \"none\"\n}\n\n// ─── Resolved route ───────────────────────────────────────────────────────────\n\nexport interface ResolvedRoute<\n P extends Record<string, string> = Record<string, string>,\n Q extends Record<string, string> = Record<string, string>,\n> {\n path: string\n params: P\n query: Q\n hash: string\n /** All matched records from root to leaf (one per nesting level) */\n matched: RouteRecord[]\n meta: RouteMeta\n}\n\n// ─── Lazy component ───────────────────────────────────────────────────────────\n\nexport const LAZY_SYMBOL = Symbol(\"pyreon.lazy\")\n\nexport interface LazyComponent {\n readonly [LAZY_SYMBOL]: true\n readonly loader: () => Promise<ComponentFn | { default: ComponentFn }>\n /** Optional component shown while the lazy chunk is loading */\n readonly loadingComponent?: ComponentFn\n /** Optional component shown after all retries have failed */\n readonly errorComponent?: ComponentFn\n}\n\nexport function lazy(\n loader: () => Promise<ComponentFn | { default: ComponentFn }>,\n options?: { loading?: ComponentFn; error?: ComponentFn },\n): LazyComponent {\n return {\n [LAZY_SYMBOL]: true,\n loader,\n ...(options?.loading ? { loadingComponent: options.loading } : {}),\n ...(options?.error ? { errorComponent: options.error } : {}),\n }\n}\n\nexport function isLazy(c: RouteComponent): c is LazyComponent {\n return typeof c === \"object\" && c !== null && (c as LazyComponent)[LAZY_SYMBOL] === true\n}\n\nexport type RouteComponent = ComponentFn | LazyComponent\n\n// ─── Navigation guard ─────────────────────────────────────────────────────────\n\nexport type NavigationGuardResult = boolean | string | undefined\nexport type NavigationGuard = (\n to: ResolvedRoute,\n from: ResolvedRoute,\n) => NavigationGuardResult | Promise<NavigationGuardResult>\n\nexport type AfterEachHook = (to: ResolvedRoute, from: ResolvedRoute) => void\n\n// ─── Route loaders ────────────────────────────────────────────────────────────\n\nexport interface LoaderContext {\n params: Record<string, string>\n query: Record<string, string>\n /** Aborted when a newer navigation supersedes this one */\n signal: AbortSignal\n}\n\nexport type RouteLoaderFn = (ctx: LoaderContext) => Promise<unknown>\n\n// ─── Route record ─────────────────────────────────────────────────────────────\n\nexport interface RouteRecord<TPath extends string = string> {\n /** Path pattern — supports `:param` segments and `(.*)` wildcard */\n path: TPath\n component: RouteComponent\n /** Optional route name for named navigation */\n name?: string\n /** Metadata attached to this route */\n meta?: RouteMeta\n /**\n * Redirect target. Evaluated before guards.\n * String: redirect to that path.\n * Function: called with the resolved route, return path string.\n */\n redirect?: string | ((to: ResolvedRoute) => string)\n /** Guard(s) run only for this route, before global beforeEach guards */\n beforeEnter?: NavigationGuard | NavigationGuard[]\n /** Guard(s) run before leaving this route. Return false to cancel. */\n beforeLeave?: NavigationGuard | NavigationGuard[]\n /** Child routes rendered inside this route's component via <RouterView /> */\n children?: RouteRecord[]\n /**\n * Data loader — runs before navigation commits, in parallel with sibling loaders.\n * The result is accessible via `useLoaderData()` inside the route component.\n * Receives an AbortSignal that fires if a newer navigation supersedes this one.\n */\n loader?: RouteLoaderFn\n /** Component rendered when this route's loader throws an error */\n errorComponent?: ComponentFn\n}\n\n// ─── Router options ───────────────────────────────────────────────────────────\n\nexport type ScrollBehaviorFn = (\n to: ResolvedRoute,\n from: ResolvedRoute,\n savedPosition: number | null,\n) => \"top\" | \"restore\" | \"none\" | number\n\nexport interface RouterOptions {\n routes: RouteRecord[]\n /** \"hash\" (default) uses location.hash; \"history\" uses pushState */\n mode?: \"hash\" | \"history\"\n /**\n * Global scroll behavior. Per-route meta.scrollBehavior takes precedence.\n * Default: \"top\"\n */\n scrollBehavior?: ScrollBehaviorFn | \"top\" | \"restore\" | \"none\"\n /**\n * Initial URL for SSR. On the server, window.location is unavailable;\n * pass the request URL here so the router resolves the correct route.\n *\n * @example\n * // In your SSR handler:\n * const router = createRouter({ routes, url: req.url })\n */\n url?: string\n /**\n * Called when a route loader throws. If not provided, errors are logged\n * and the navigation continues with `undefined` data for the failed loader.\n * Return `false` to cancel the navigation.\n */\n onError?: (err: unknown, route: ResolvedRoute) => undefined | false\n /**\n * Maximum number of resolved lazy components to cache.\n * When exceeded, the oldest entry is evicted.\n * Default: 100.\n */\n maxCacheSize?: number\n}\n\n// ─── Router interface ─────────────────────────────────────────────────────────\n\nexport interface Router {\n /** Navigate to a path */\n push(path: string): Promise<void>\n /** Navigate to a path by name */\n push(location: {\n name: string\n params?: Record<string, string>\n query?: Record<string, string>\n }): Promise<void>\n /** Replace current history entry */\n replace(path: string): Promise<void>\n /** Go back */\n back(): void\n /** Register a global before-navigation guard. Returns an unregister function. */\n beforeEach(guard: NavigationGuard): () => void\n /** Register a global after-navigation hook. Returns an unregister function. */\n afterEach(hook: AfterEachHook): () => void\n /** Current resolved route (reactive signal) */\n readonly currentRoute: () => ResolvedRoute\n /** True while a navigation (guards + loaders) is in flight */\n readonly loading: () => boolean\n /** Remove all event listeners, clear caches, and abort in-flight navigations. */\n destroy(): void\n}\n\n// ─── Internal router instance ─────────────────────────────────────────────────\n\nimport type { Computed, Signal } from \"@pyreon/reactivity\"\n\nexport interface RouterInstance extends Router {\n routes: RouteRecord[]\n mode: \"hash\" | \"history\"\n _currentPath: Signal<string>\n _currentRoute: Computed<ResolvedRoute>\n _componentCache: Map<RouteRecord, ComponentFn>\n _loadingSignal: Signal<number>\n _resolve(rawPath: string): ResolvedRoute\n _scrollPositions: Map<string, number>\n _scrollBehavior: RouterOptions[\"scrollBehavior\"]\n _onError: RouterOptions[\"onError\"]\n _maxCacheSize: number\n /**\n * Current RouterView nesting depth. Incremented by each RouterView as it\n * mounts (in tree order = depth-first), so each view knows which level of\n * `matched[]` to render. Reset to 0 by RouterProvider.\n */\n _viewDepth: number\n /** Route records whose lazy chunk permanently failed (all retries exhausted) */\n _erroredChunks: Set<RouteRecord>\n /** Loader data keyed by route record — populated before each navigation commits */\n _loaderData: Map<RouteRecord, unknown>\n /** AbortController for the in-flight loader batch — aborted when a newer navigation starts */\n _abortController: AbortController | null\n}\n","import { createContext, useContext } from \"@pyreon/core\"\nimport { computed, signal } from \"@pyreon/reactivity\"\nimport { buildNameIndex, buildPath, resolveRoute } from \"./match\"\nimport { ScrollManager } from \"./scroll\"\nimport {\n type AfterEachHook,\n type ComponentFn,\n isLazy,\n type LoaderContext,\n type NavigationGuard,\n type NavigationGuardResult,\n type ResolvedRoute,\n type RouteRecord,\n type Router,\n type RouterInstance,\n type RouterOptions,\n} from \"./types\"\n\n// Evaluated once at module load — collapses to `true` in browser / happy-dom,\n// `false` on the server. Using a constant avoids per-call `typeof` branches\n// that are uncoverable in test environments.\nconst _isBrowser = typeof window !== \"undefined\"\n\n// ─── Router context ───────────────────────────────────────────────────────────\n// Context-based access: isolated per request in SSR (ALS-backed via\n// @pyreon/runtime-server), isolated per component tree in CSR.\n// Falls back to the module-level singleton for code running outside a component\n// tree (e.g. programmatic navigation from event handlers).\n\nexport const RouterContext = createContext<RouterInstance | null>(null)\n\n// Module-level fallback — safe for CSR (single-threaded), not for concurrent SSR.\n// RouterProvider also sets this so legacy useRouter() calls outside the tree work.\nlet _activeRouter: RouterInstance | null = null\n\nexport function getActiveRouter(): RouterInstance | null {\n return useContext(RouterContext) ?? _activeRouter\n}\n\nexport function setActiveRouter(router: RouterInstance | null): void {\n if (router) router._viewDepth = 0\n _activeRouter = router\n}\n\n// ─── Hooks ────────────────────────────────────────────────────────────────────\n\nexport function useRouter(): Router {\n const router = useContext(RouterContext) ?? _activeRouter\n if (!router)\n throw new Error(\n \"[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.\",\n )\n return router\n}\n\nexport function useRoute<TPath extends string = string>(): () => ResolvedRoute<\n import(\"./types\").ExtractParams<TPath>,\n Record<string, string>\n> {\n const router = useContext(RouterContext) ?? _activeRouter\n if (!router)\n throw new Error(\n \"[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.\",\n )\n return router.currentRoute as never\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createRouter(options: RouterOptions | RouteRecord[]): Router {\n const opts: RouterOptions = Array.isArray(options) ? { routes: options } : options\n const { routes, mode = \"hash\", scrollBehavior, onError, maxCacheSize = 100 } = opts\n\n // Pre-built O(1) name → record index. Computed once at startup.\n const nameIndex = buildNameIndex(routes)\n\n const guards: NavigationGuard[] = []\n const afterHooks: AfterEachHook[] = []\n const scrollManager = new ScrollManager(scrollBehavior)\n\n // Navigation generation counter — cancels in-flight navigations when a newer\n // one starts. Prevents out-of-order completion from stale async guards.\n let _navGen = 0\n\n // ── Initial location ──────────────────────────────────────────────────────\n\n const getInitialLocation = (): string => {\n // SSR: use explicitly provided url\n if (opts.url) return opts.url\n if (!_isBrowser) return \"/\"\n if (mode === \"history\") {\n return window.location.pathname + window.location.search\n }\n const hash = window.location.hash\n return hash.startsWith(\"#\") ? hash.slice(1) || \"/\" : \"/\"\n }\n\n const getCurrentLocation = (): string => {\n if (!_isBrowser) return currentPath()\n if (mode === \"history\") {\n return window.location.pathname + window.location.search\n }\n const hash = window.location.hash\n return hash.startsWith(\"#\") ? hash.slice(1) || \"/\" : \"/\"\n }\n\n // ── Signals ───────────────────────────────────────────────────────────────\n\n const currentPath = signal(getInitialLocation())\n const currentRoute = computed<ResolvedRoute>(() => resolveRoute(currentPath(), routes))\n\n // Browser event listeners — stored so destroy() can remove them\n let _popstateHandler: (() => void) | null = null\n let _hashchangeHandler: (() => void) | null = null\n\n if (_isBrowser) {\n if (mode === \"history\") {\n _popstateHandler = () => currentPath.set(getCurrentLocation())\n window.addEventListener(\"popstate\", _popstateHandler)\n } else {\n _hashchangeHandler = () => currentPath.set(getCurrentLocation())\n window.addEventListener(\"hashchange\", _hashchangeHandler)\n }\n }\n\n const componentCache = new Map<RouteRecord, ComponentFn>()\n const loadingSignal = signal(0)\n\n // ── Navigation ────────────────────────────────────────────────────────────\n\n type GuardOutcome =\n | { action: \"continue\" }\n | { action: \"cancel\" }\n | { action: \"redirect\"; target: string }\n\n async function evaluateGuard(\n guard: NavigationGuard,\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n const result = await runGuard(guard, to, from)\n if (gen !== _navGen) return { action: \"cancel\" }\n if (result === false) return { action: \"cancel\" }\n if (typeof result === \"string\") return { action: \"redirect\", target: result }\n return { action: \"continue\" }\n }\n\n async function runRouteGuards(\n records: RouteRecord[],\n guardKey: \"beforeLeave\" | \"beforeEnter\",\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n for (const record of records) {\n const raw = record[guardKey]\n if (!raw) continue\n const routeGuards = Array.isArray(raw) ? raw : [raw]\n for (const guard of routeGuards) {\n const outcome = await evaluateGuard(guard, to, from, gen)\n if (outcome.action !== \"continue\") return outcome\n }\n }\n return { action: \"continue\" }\n }\n\n async function runGlobalGuards(\n globalGuards: NavigationGuard[],\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n for (const guard of globalGuards) {\n const outcome = await evaluateGuard(guard, to, from, gen)\n if (outcome.action !== \"continue\") return outcome\n }\n return { action: \"continue\" }\n }\n\n function processLoaderResult(\n result: PromiseSettledResult<unknown>,\n record: RouteRecord,\n ac: AbortController,\n to: ResolvedRoute,\n ): boolean {\n if (result.status === \"fulfilled\") {\n router._loaderData.set(record, result.value)\n return true\n }\n if (ac.signal.aborted) return true\n if (router._onError) {\n const cancel = router._onError(result.reason, to)\n if (cancel === false) return false\n }\n router._loaderData.set(record, undefined)\n return true\n }\n\n function syncBrowserUrl(path: string, replace: boolean): void {\n if (!_isBrowser) return\n const url = mode === \"history\" ? path : `#${path}`\n if (replace) {\n window.history.replaceState(null, \"\", url)\n } else {\n window.history.pushState(null, \"\", url)\n }\n }\n\n function resolveRedirect(to: ResolvedRoute): string | null {\n const leaf = to.matched[to.matched.length - 1]\n if (!leaf?.redirect) return null\n return sanitizePath(typeof leaf.redirect === \"function\" ? leaf.redirect(to) : leaf.redirect)\n }\n\n async function runAllGuards(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n const leaveOutcome = await runRouteGuards(from.matched, \"beforeLeave\", to, from, gen)\n if (leaveOutcome.action !== \"continue\") return leaveOutcome\n\n const enterOutcome = await runRouteGuards(to.matched, \"beforeEnter\", to, from, gen)\n if (enterOutcome.action !== \"continue\") return enterOutcome\n\n return runGlobalGuards(guards, to, from, gen)\n }\n\n async function runLoaders(to: ResolvedRoute, gen: number, ac: AbortController): Promise<boolean> {\n const loadableRecords = to.matched.filter((r) => r.loader)\n if (loadableRecords.length === 0) return true\n\n const loaderCtx: LoaderContext = { params: to.params, query: to.query, signal: ac.signal }\n const results = await Promise.allSettled(\n loadableRecords.map((r) => {\n if (!r.loader) return Promise.resolve(undefined)\n return r.loader(loaderCtx)\n }),\n )\n if (gen !== _navGen) return false\n\n for (let i = 0; i < loadableRecords.length; i++) {\n const result = results[i]\n const record = loadableRecords[i]\n if (!result || !record) continue\n if (!processLoaderResult(result, record, ac, to)) return false\n }\n return true\n }\n\n function commitNavigation(\n path: string,\n replace: boolean,\n to: ResolvedRoute,\n from: ResolvedRoute,\n ): void {\n scrollManager.save(from.path)\n currentPath.set(path)\n syncBrowserUrl(path, replace)\n\n if (_isBrowser && to.meta.title) {\n document.title = to.meta.title\n }\n\n for (const record of router._loaderData.keys()) {\n if (!to.matched.includes(record)) {\n router._loaderData.delete(record)\n }\n }\n\n for (const hook of afterHooks) {\n try {\n hook(to, from)\n } catch (_err) {\n /* hook errors silently ignored */\n }\n }\n\n if (_isBrowser) {\n queueMicrotask(() => scrollManager.restore(to, from))\n }\n }\n\n async function navigate(path: string, replace: boolean, redirectDepth = 0): Promise<void> {\n if (redirectDepth > 10) return\n\n const gen = ++_navGen\n loadingSignal.update((n) => n + 1)\n\n const to = resolveRoute(path, routes)\n const from = currentRoute()\n\n const redirectTarget = resolveRedirect(to)\n if (redirectTarget !== null) {\n loadingSignal.update((n) => n - 1)\n return navigate(redirectTarget, replace, redirectDepth + 1)\n }\n\n const guardOutcome = await runAllGuards(to, from, gen)\n if (guardOutcome.action !== \"continue\") {\n loadingSignal.update((n) => n - 1)\n if (guardOutcome.action === \"redirect\") {\n return navigate(sanitizePath(guardOutcome.target), replace, redirectDepth + 1)\n }\n return\n }\n\n router._abortController?.abort()\n const ac = new AbortController()\n router._abortController = ac\n\n const loadersOk = await runLoaders(to, gen, ac)\n if (!loadersOk) {\n loadingSignal.update((n) => n - 1)\n return\n }\n\n commitNavigation(path, replace, to, from)\n loadingSignal.update((n) => n - 1)\n }\n\n // ── Public router object ──────────────────────────────────────────────────\n\n const router: RouterInstance = {\n routes,\n mode,\n currentRoute,\n _currentPath: currentPath,\n _currentRoute: currentRoute,\n _componentCache: componentCache,\n _loadingSignal: loadingSignal,\n _scrollPositions: new Map(),\n _scrollBehavior: scrollBehavior,\n _viewDepth: 0,\n _erroredChunks: new Set(),\n _loaderData: new Map(),\n _abortController: null,\n _onError: onError,\n _maxCacheSize: maxCacheSize,\n\n async push(\n location:\n | string\n | { name: string; params?: Record<string, string>; query?: Record<string, string> },\n ) {\n if (typeof location === \"string\") return navigate(sanitizePath(location), false)\n const path = resolveNamedPath(\n location.name,\n location.params ?? {},\n location.query ?? {},\n nameIndex,\n )\n return navigate(path, false)\n },\n\n async replace(path: string) {\n return navigate(sanitizePath(path), true)\n },\n\n back() {\n if (_isBrowser) window.history.back()\n },\n\n beforeEach(guard: NavigationGuard) {\n guards.push(guard)\n return () => {\n const idx = guards.indexOf(guard)\n if (idx >= 0) guards.splice(idx, 1)\n }\n },\n\n afterEach(hook: AfterEachHook) {\n afterHooks.push(hook)\n return () => {\n const idx = afterHooks.indexOf(hook)\n if (idx >= 0) afterHooks.splice(idx, 1)\n }\n },\n\n loading: () => loadingSignal() > 0,\n\n destroy() {\n if (_popstateHandler) {\n window.removeEventListener(\"popstate\", _popstateHandler)\n _popstateHandler = null\n }\n if (_hashchangeHandler) {\n window.removeEventListener(\"hashchange\", _hashchangeHandler)\n _hashchangeHandler = null\n }\n guards.length = 0\n afterHooks.length = 0\n componentCache.clear()\n router._loaderData.clear()\n router._abortController?.abort()\n router._abortController = null\n },\n\n _resolve: (rawPath: string) => resolveRoute(rawPath, routes),\n }\n\n return router\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function runGuard(\n guard: NavigationGuard,\n to: ResolvedRoute,\n from: ResolvedRoute,\n): Promise<NavigationGuardResult> {\n try {\n return await guard(to, from)\n } catch (_err) {\n return false\n }\n}\n\nfunction resolveNamedPath(\n name: string,\n params: Record<string, string>,\n query: Record<string, string>,\n index: Map<string, RouteRecord>,\n): string {\n const record = index.get(name)\n if (!record) {\n return \"/\"\n }\n let path = buildPath(record.path, params)\n const qs = Object.entries(query)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\")\n if (qs) path += `?${qs}`\n return path\n}\n\n/** Block unsafe navigation targets: javascript/data/vbscript URIs and absolute URLs. */\nfunction sanitizePath(path: string): string {\n const trimmed = path.trim()\n if (/^(?:javascript|data|vbscript):/i.test(trimmed)) {\n return \"/\"\n }\n // Block absolute URLs and protocol-relative URLs — router only handles same-origin paths\n if (/^\\/\\/|^https?:/i.test(trimmed)) {\n return \"/\"\n }\n return path\n}\n\nexport { isLazy }\n","import type { ComponentFn, Props, VNode, VNodeChild } from \"@pyreon/core\"\nimport { createRef, h, onUnmount, popContext, pushContext, useContext } from \"@pyreon/core\"\nimport { LoaderDataContext, prefetchLoaderData } from \"./loader\"\nimport { isLazy, RouterContext, setActiveRouter } from \"./router\"\nimport type { LazyComponent, ResolvedRoute, RouteRecord, Router, RouterInstance } from \"./types\"\n\n// Track prefetched paths per router to avoid duplicate fetches\nconst _prefetched = new WeakMap<RouterInstance, Set<string>>()\n\n// ─── RouterProvider ───────────────────────────────────────────────────────────\n\nexport interface RouterProviderProps extends Props {\n router: Router\n children?: VNode | VNodeChild | null\n}\n\nexport const RouterProvider: ComponentFn<RouterProviderProps> = (props) => {\n const router = props.router as RouterInstance\n // Push router into the context stack — isolated per request in SSR via ALS,\n // isolated per component tree in CSR.\n const frame = new Map([[RouterContext.id, router]])\n pushContext(frame)\n onUnmount(() => {\n popContext()\n // Clean up event listeners, caches, abort in-flight navigations.\n // Safe to call multiple times (destroy is idempotent).\n router.destroy()\n setActiveRouter(null)\n })\n // Also set the module fallback so programmatic useRouter() outside a component\n // tree (e.g. navigation guards in event handlers) still works in CSR.\n setActiveRouter(router)\n return (props.children ?? null) as VNode | null\n}\n\n// ─── RouterView ───────────────────────────────────────────────────────────────\n\nexport interface RouterViewProps extends Props {\n /** Explicitly pass a router (optional — uses the active router by default) */\n router?: Router\n}\n\n/**\n * Renders the matched route component at this nesting level.\n *\n * Nested layouts work by placing a second `<RouterView />` inside the layout\n * component — it automatically renders the next level of the matched route.\n *\n * How depth tracking works:\n * Pyreon components run once in depth-first tree order. Each `RouterView`\n * captures `router._viewDepth` at setup time and immediately increments it,\n * so sibling and child views get the correct index. `onUnmount` decrements\n * the counter so dynamic route swaps work correctly.\n *\n * @example\n * // Route config:\n * { path: \"/admin\", component: AdminLayout, children: [\n * { path: \"users\", component: AdminUsers },\n * ]}\n *\n * // AdminLayout renders a nested RouterView:\n * function AdminLayout() {\n * return <div><Sidebar /><RouterView /></div>\n * }\n */\nexport const RouterView: ComponentFn<RouterViewProps> = (props) => {\n const router = ((props.router as RouterInstance | undefined) ??\n useContext(RouterContext)) as RouterInstance | null\n if (!router) return null\n\n // Claim this view's depth at setup time (depth-first component init order)\n const depth = router._viewDepth\n router._viewDepth++\n\n onUnmount(() => {\n router._viewDepth--\n })\n\n const child = (): VNodeChild => {\n router._loadingSignal() // reactive — re-renders after lazy load completes\n\n const route = router.currentRoute()\n\n if (route.matched.length === 0) return null\n\n // Render the matched record at this view's depth level\n const record = route.matched[depth]\n if (!record) return null // no component at this nesting level\n\n const cached = router._componentCache.get(record)\n if (cached) {\n return renderWithLoader(router, record, cached, route)\n }\n\n const raw = record.component\n\n if (!isLazy(raw)) {\n cacheSet(router, record, raw)\n return renderWithLoader(router, record, raw, route)\n }\n\n return renderLazyRoute(router, record, raw)\n }\n\n return h(\"div\", { \"data-pyreon-router-view\": true }, child as unknown as VNodeChild)\n}\n\n// ─── RouterLink ───────────────────────────────────────────────────────────────\n\nexport interface RouterLinkProps extends Props {\n to: string\n /** If true, uses router.replace() instead of router.push() */\n replace?: boolean\n /** CSS class applied when this link is active (default: \"router-link-active\") */\n activeClass?: string\n /** CSS class for exact-match active state (default: \"router-link-exact-active\") */\n exactActiveClass?: string\n /** If true, only applies activeClass on exact match */\n exact?: boolean\n /**\n * Prefetch strategy for loader data:\n * - \"hover\" (default) — prefetch when the user hovers over the link\n * - \"viewport\" — prefetch when the link scrolls into the viewport\n * - \"none\" — no prefetching\n */\n prefetch?: \"hover\" | \"viewport\" | \"none\"\n children?: VNodeChild | null\n}\n\nexport const RouterLink: ComponentFn<RouterLinkProps> = (props) => {\n const router = useContext(RouterContext)\n const prefetchMode = props.prefetch ?? \"hover\"\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault()\n if (!router) return\n if (props.replace) {\n router.replace(props.to)\n } else {\n router.push(props.to)\n }\n }\n\n const handleMouseEnter = () => {\n if (prefetchMode !== \"hover\" || !router) return\n prefetchRoute(router as RouterInstance, props.to)\n }\n\n const href = router?.mode === \"history\" ? props.to : `#${props.to}`\n\n const activeClass = (): string => {\n if (!router) return \"\"\n const current = router.currentRoute().path\n const target = props.to\n const isExact = current === target\n const isActive = isExact || (!props.exact && isSegmentPrefix(current, target))\n\n const classes: string[] = []\n if (isActive) classes.push(props.activeClass ?? \"router-link-active\")\n if (isExact) classes.push(props.exactActiveClass ?? \"router-link-exact-active\")\n return classes.join(\" \").trim()\n }\n\n // Viewport prefetching — observe link visibility with IntersectionObserver\n const ref = createRef<Element>()\n if (prefetchMode === \"viewport\" && router && typeof IntersectionObserver !== \"undefined\") {\n const observer = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n prefetchRoute(router as RouterInstance, props.to)\n observer.disconnect()\n break\n }\n }\n })\n // Observe after mount — the ref will be populated once the element is in the DOM\n queueMicrotask(() => {\n observer.observe(ref.current as Element)\n })\n onUnmount(() => observer.disconnect())\n }\n\n return h(\n \"a\",\n { ref, href, class: activeClass, onClick: handleClick, onMouseEnter: handleMouseEnter },\n props.children ?? props.to,\n )\n}\n\n/** Prefetch loader data for a route (only once per router + path). */\nfunction prefetchRoute(router: RouterInstance, path: string): void {\n let set = _prefetched.get(router)\n if (!set) {\n set = new Set()\n _prefetched.set(router, set)\n }\n if (set.has(path)) return\n set.add(path)\n prefetchLoaderData(router, path).catch(() => {\n // Silently ignore — prefetch is best-effort\n set?.delete(path)\n })\n}\n\nfunction renderLazyRoute(\n router: RouterInstance,\n record: RouteRecord,\n raw: LazyComponent,\n): VNodeChild {\n if (router._erroredChunks.has(record)) {\n return raw.errorComponent ? h(raw.errorComponent, {}) : null\n }\n\n const tryLoad = (attempt: number): Promise<void> =>\n raw\n .loader()\n .then((mod) => {\n const resolved = typeof mod === \"function\" ? mod : mod.default\n cacheSet(router, record, resolved)\n router._loadingSignal.update((n) => n + 1)\n })\n .catch((err: unknown) => {\n if (attempt < 3) {\n return new Promise<void>((res) => setTimeout(res, 500 * 2 ** attempt)).then(() =>\n tryLoad(attempt + 1),\n )\n }\n if (typeof window !== \"undefined\" && isStaleChunk(err)) {\n window.location.reload()\n return\n }\n\n router._erroredChunks.add(record)\n router._loadingSignal.update((n) => n + 1)\n })\n\n tryLoad(0)\n return raw.loadingComponent ? h(raw.loadingComponent, {}) : null\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Wraps the route component with a LoaderDataProvider so `useLoaderData()` works\n * inside the component. If the record has no loader, renders the component directly.\n */\nfunction renderWithLoader(\n router: RouterInstance,\n record: RouteRecord,\n Comp: ComponentFn,\n route: Pick<ResolvedRoute, \"params\" | \"query\" | \"meta\">,\n): VNodeChild {\n const routeProps = { params: route.params, query: route.query, meta: route.meta }\n if (!record.loader) {\n return h(Comp, routeProps)\n }\n const data = router._loaderData.get(record)\n // If loader data is undefined and route has an errorComponent, render it\n if (data === undefined && record.errorComponent) {\n return h(record.errorComponent, routeProps)\n }\n return h(LoaderDataProvider, { data, children: h(Comp, routeProps) })\n}\n\n/**\n * Thin provider component that pushes LoaderDataContext before children mount.\n * Uses Pyreon's context stack so useLoaderData() reads it during child setup.\n */\nfunction LoaderDataProvider(props: { data: unknown; children: VNode | null }): VNode | null {\n const frame = new Map([[LoaderDataContext.id, props.data]])\n pushContext(frame)\n onUnmount(() => popContext())\n return props.children\n}\n\n/** Evict oldest cache entries when the component cache exceeds maxCacheSize. */\nfunction cacheSet(router: RouterInstance, record: RouteRecord, comp: ComponentFn): void {\n router._componentCache.set(record, comp)\n if (router._componentCache.size > router._maxCacheSize) {\n // Map iterates in insertion order — first key is oldest\n const oldest = router._componentCache.keys().next().value as RouteRecord\n router._componentCache.delete(oldest)\n }\n}\n\n/**\n * Segment-aware prefix check for active link matching.\n * `/admin` is a prefix of `/admin/users` but NOT of `/admin-panel`.\n */\nfunction isSegmentPrefix(current: string, target: string): boolean {\n if (target === \"/\") return false\n const cs = current.split(\"/\").filter(Boolean)\n const ts = target.split(\"/\").filter(Boolean)\n if (ts.length > cs.length) return false\n return ts.every((seg, i) => seg === cs[i])\n}\n\n/**\n * Detect a stale chunk error — happens post-deploy when the browser requests\n * a hashed filename that no longer exists on the server. Trigger a full reload\n * so the user gets the new bundle instead of a broken loading state.\n */\nfunction isStaleChunk(err: unknown): boolean {\n if (err instanceof TypeError && String(err.message).includes(\"Failed to fetch\")) return true\n if (err instanceof SyntaxError) return true\n return false\n}\n"],"mappings":"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"}