cross-router-core 1.0.1
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/LICENSE.md +7 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +1217 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/history.ts","../src/middleware.ts","../src/logger.ts","../src/registry.ts","../src/matcher.ts","../src/loader.ts","../src/types.ts","../src/action.ts","../src/router.ts"],"sourcesContent":["// ============================================================\n// CONTEXT PROVIDER\n// ============================================================\ninterface RouterContext<T = unknown> {\n defaultValue?: T\n}\n\nexport function createContext<T>(defaultValue?: T): RouterContext<T> {\n return { defaultValue }\n}\n\nexport class ContextError extends Error {\n public name: string = 'CONTEXT_ERROR'\n}\n\nexport class RouterContextProvider {\n private map = new Map<RouterContext, unknown>()\n\n /**\n * Create a new `RouterContextProvider` instance\n * @param init An optional initial context map to populate the provider with\n */\n constructor(init?: Map<RouterContext, unknown>) {\n if (init) {\n for (const [context, value] of init) {\n this.set(context, value)\n }\n }\n }\n\n /**\n * Access a value from the context. If no value has been set for the context,\n * it will return the context's `defaultValue` if provided, or throw an error\n * if no `defaultValue` was set.\n * @param context The context to get the value for\n * @returns The value for the context, or the context's `defaultValue` if no\n * value was set\n */\n get<T>(context: RouterContext<T>): T {\n if (this.map.has(context)) {\n return this.map.get(context) as T\n }\n\n if (context.defaultValue !== undefined) {\n return context.defaultValue\n }\n\n throw new ContextError('No value found for context')\n }\n\n /**\n * Set a value for the context. If the context already has a value set, this\n * will overwrite it.\n *\n * @param context The context to set the value for\n * @param value The value to set for the context\n * @returns {void}\n */\n set<C extends RouterContext>(\n context: C,\n value: C extends RouterContext<infer T> ? T : never,\n ): void {\n this.map.set(context, value)\n }\n}\n","// ============================================================\n// HISTORY\n// A thin, framework-agnostic wrapper around the browser History API.\n// Produces Location objects and notifies subscribers on changes.\n// ============================================================\n\nimport type { Location } from './types'\n\nexport type HistoryListener = (location: Location) => void\n\nexport interface History {\n // Current location\n readonly location: Location\n\n // Navigate forward — adds a new entry to the stack\n push: (to: string, state?: unknown) => void\n\n // Navigate without adding to the stack\n replace: (to: string, state?: unknown) => void\n\n // Move through the stack\n back: () => void\n forward: () => void\n go: (delta: number) => void\n\n // Subscribe to location changes — returns unsubscribe fn\n listen: (listener: HistoryListener) => () => void\n\n // Teardown — removes the popstate listener\n destroy: () => void\n}\n\n// Generates a random short key for each navigation entry\n// Used to uniquely identify entries (like React Router's key)\nfunction createKey(): string {\n return Math.random().toString(36).slice(2, 8)\n}\n\nfunction parseLocation(\n href: string,\n state: unknown,\n key: string,\n): Location {\n const url = new URL(href, window.location.origin)\n return {\n pathname: url.pathname,\n search: url.search,\n hash: url.hash,\n state,\n key,\n }\n}\n\nfunction currentLocation(key: string): Location {\n return parseLocation(window.location.href, window.history.state, key)\n}\n\n// ============================================================\n// createBrowserHistory\n// Standard HTML5 history — uses pushState/replaceState.\n// URL changes are visible in the address bar.\n// ============================================================\n\nexport function createBrowserHistory(): History {\n const listeners = new Set<HistoryListener>()\n\n // We store the key in history.state alongside user state\n // so we can restore it on popstate\n interface HistoryState { __key: string, __userState: unknown }\n\n function getKey(nativeState: unknown): string {\n if (\n nativeState\n && typeof nativeState === 'object'\n && '__key' in nativeState\n ) {\n return (nativeState as HistoryState).__key\n }\n return createKey()\n }\n\n let currentKey = getKey(window.history.state)\n let location = currentLocation(currentKey)\n\n function notify() {\n listeners.forEach(l => l(location))\n }\n\n function handlePopState() {\n currentKey = getKey(window.history.state)\n location = currentLocation(currentKey)\n notify()\n }\n\n window.addEventListener('popstate', handlePopState)\n\n return {\n get location() {\n return location\n },\n\n push(to, state) {\n const key = createKey()\n const historyState: HistoryState = { __key: key, __userState: state ?? null }\n window.history.pushState(historyState, '', to)\n currentKey = key\n location = parseLocation(\n new URL(to, window.location.origin).href,\n state,\n key,\n )\n notify()\n },\n\n replace(to, state) {\n const key = createKey()\n const historyState: HistoryState = { __key: key, __userState: state ?? null }\n window.history.replaceState(historyState, '', to)\n currentKey = key\n location = parseLocation(\n new URL(to, window.location.origin).href,\n state,\n key,\n )\n notify()\n },\n\n back() {\n window.history.back()\n },\n\n forward() {\n window.history.forward()\n },\n\n go(delta) {\n window.history.go(delta)\n },\n\n listen(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n destroy() {\n window.removeEventListener('popstate', handlePopState)\n listeners.clear()\n },\n }\n}\n\n// ============================================================\n// createMemoryHistory\n// No browser dependency — useful for SSR, tests, and non-browser envs.\n// ============================================================\n\nexport function createMemoryHistory(\n initialPath = '/',\n): History {\n const listeners = new Set<HistoryListener>()\n\n const initialKey = createKey()\n const stack: Array<{ location: Location }> = [\n {\n location: parseLocation(\n new URL(initialPath, 'http://localhost').href,\n null,\n initialKey,\n ),\n },\n ]\n let cursor = 0\n\n function notify() {\n listeners.forEach(l => l(stack[cursor]!.location))\n }\n\n return {\n get location() {\n return stack[cursor]!.location\n },\n\n push(to, state) {\n // Discard any forward entries when pushing\n stack.splice(cursor + 1)\n const key = createKey()\n stack.push({\n location: parseLocation(\n new URL(to, 'http://localhost').href,\n state ?? null,\n key,\n ),\n })\n cursor = stack.length - 1\n notify()\n },\n\n replace(to, state) {\n const key = createKey()\n stack[cursor] = {\n location: parseLocation(\n new URL(to, 'http://localhost').href,\n state ?? null,\n key,\n ),\n }\n notify()\n },\n\n back() {\n if (cursor > 0) {\n cursor--\n notify()\n }\n },\n\n forward() {\n if (cursor < stack.length - 1) {\n cursor++\n notify()\n }\n },\n\n go(delta) {\n const next = cursor + delta\n if (next >= 0 && next < stack.length) {\n cursor = next\n notify()\n }\n },\n\n listen(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n destroy() {\n listeners.clear()\n },\n }\n}\n","import type { RouterContextProvider } from './context'\nimport type {\n AnyMiddleware,\n AnyRouteMatch,\n NavigationRequest,\n} from './types'\n\n// ============================================================\n// MIDDLEWARE CHAIN RUNNER\n//\n// Builds and executes a flat middleware chain from:\n// [global middlewares, ...ancestor middlewares, ...leaf middlewares]\n//\n// Each middleware receives:\n// - context: the accumulated context so far (immutable — passed via next())\n// - request: the current navigation request\n//\n// Each middleware MUST call next() exactly once with the new context.\n// Forgetting to call next() throws a clear error.\n// Calling next() twice throws a clear error.\n//\n// Returns the final accumulated context, which is then passed to the loader.\n// Does NOT call the loader itself — that's the loader module's job.\n//\n// Throws:\n// - Redirect (caught by router → triggers navigation)\n// - Any error thrown by middleware (caught by router → errorComponent)\n// - MiddlewareError for chain misuse (developer mistakes)\n// ============================================================\n\nexport class MiddlewareError extends Error {\n constructor(message: string, public readonly middlewareIndex: number) {\n super(`[middleware #${middlewareIndex}] ${message}`)\n this.name = 'MiddlewareError'\n }\n}\n\n// ============================================================\n// COLLECT MIDDLEWARE\n// Flattens global + per-route-hierarchy middleware into one ordered array.\n// Order: global → root match → ... → leaf match\n// ============================================================\n\nexport function collectMiddleware(\n globalMiddleware: AnyMiddleware[],\n matches: AnyRouteMatch[],\n): AnyMiddleware[] {\n const routeMiddleware = matches.flatMap(\n match => match.route.middleware ?? [],\n )\n return [...globalMiddleware, ...routeMiddleware]\n}\n\n// ============================================================\n// RUN MIDDLEWARE CHAIN\n// Executes the middleware array as a next()-based chain.\n// Returns the final context after all middleware have run.\n// ============================================================\n\nexport async function runMiddlewareChain(\n middlewares: AnyMiddleware[],\n initialContext: RouterContextProvider,\n request: NavigationRequest,\n): Promise<RouterContextProvider> {\n // Nothing to run — return context as-is\n if (middlewares.length === 0)\n return initialContext\n\n // We use a promise that resolves when the innermost next() is called,\n // carrying the final context out of the chain.\n let finalContext: RouterContextProvider = initialContext\n\n // Build the chain recursively from the end\n // Each step wraps the next step in a closure\n async function execute(index: number, context: RouterContextProvider): Promise<void> {\n // All middleware have run — capture final context and return\n if (index >= middlewares.length) {\n finalContext = context\n return\n }\n\n const middleware = middlewares[index] as AnyMiddleware // We are SURE there is something since we previously checked the array size\n let nextCallCount = 0\n\n const next = async (): Promise<void> => {\n nextCallCount++\n\n if (nextCallCount > 1) {\n throw new MiddlewareError(\n 'next() was called more than once. A middleware must call next() exactly once.',\n index,\n )\n }\n\n await execute(index + 1, context)\n }\n\n let nextCalled = false\n await middleware.handler({ context, request }, () => {\n nextCalled = true\n return next()\n })\n\n if (!nextCalled) {\n await next()\n }\n\n // After the middleware's handler resolved, check next() was called\n if (nextCallCount === 0) {\n throw new MiddlewareError(\n 'next() was never called. Either call next() to continue the chain, '\n + 'or throw redirect() / an Error to short-circuit it.',\n index,\n )\n }\n }\n\n await execute(0, initialContext)\n\n return finalContext\n}\n\n// ============================================================\n// CONVENIENCE: RUN FULL CHAIN FOR A NAVIGATION\n// Combines collectMiddleware + runMiddlewareChain.\n// This is what the router calls on every navigation.\n// ============================================================\n\nexport async function runRouteMiddleware(\n globalMiddleware: AnyMiddleware[],\n matches: AnyRouteMatch[],\n initialContext: RouterContextProvider,\n request: NavigationRequest,\n): Promise<RouterContextProvider> {\n const middlewares = collectMiddleware(globalMiddleware, matches)\n return runMiddlewareChain(middlewares, initialContext, request)\n}\n","/* eslint-disable no-console */\n// ============================================================\n// ROUTER LOGGER\n//\n// Lightweight structured logger for debugging the router internals.\n// Enable via: localStorage.setItem('cross-router:debug', 'true')\n// Filter by scope: localStorage.setItem('cross-router:debug', 'router,registry')\n// ============================================================\n\ntype LogScope = 'router' | 'registry' | 'matcher' | 'loader' | 'middleware'\n\nfunction isEnabled(scope: LogScope): boolean {\n try {\n const val = localStorage.getItem('cross-router:debug')\n if (!val)\n return false\n if (val === 'true' || val === '*')\n return true\n return val.split(',').map(s => s.trim()).includes(scope)\n }\n catch {\n return false\n }\n}\n\nconst COLORS: Record<LogScope, string> = {\n router: 'color:#6366f1;font-weight:bold',\n registry: 'color:#f59e0b;font-weight:bold',\n matcher: 'color:#10b981;font-weight:bold',\n loader: 'color:#3b82f6;font-weight:bold',\n middleware: 'color:#ec4899;font-weight:bold',\n}\n\nexport function createLogger(scope: LogScope) {\n return {\n log(message: string, data?: unknown) {\n if (!isEnabled(scope))\n return\n if (data !== undefined) {\n console.groupCollapsed(`%c[${scope}] ${message}`, COLORS[scope])\n console.log(data)\n console.groupEnd()\n }\n else {\n console.log(`%c[${scope}] ${message}`, COLORS[scope])\n }\n },\n warn(message: string, data?: unknown) {\n if (!isEnabled(scope))\n return\n console.warn(`%c[${scope}] ${message}`, COLORS[scope], data ?? '')\n },\n error(message: string, data?: unknown) {\n // errors always log regardless of debug flag\n console.error(`%c[${scope}] ${message}`, COLORS[scope], data ?? '')\n },\n }\n}\n","import type {\n AnyRouteDefinition,\n RouteRegistry,\n} from './types'\nimport { createLogger } from './logger'\n\n// ============================================================\n// REGISTRY\n//\n// Owns the canonical route tree.\n// Plugins call patch() to insert their routes at any point in the tree.\n// The router subscribes to tree changes and re-matches the current URL.\n//\n// Rules:\n// - Route ids must be unique across the entire tree (throws on violation)\n// - patch() appends — never reorders existing routes\n// - unpatch() removes the target node and its entire subtree\n// - The live tree is never mutated — all changes produce a new tree copy\n// - patch(routes) with no parentId appends at the top level\n// - index routes (index: true) and pathless layouts (path: '') are stored as-is\n// ============================================================\n\nexport class RegistryError extends Error {\n constructor(message: string) {\n super(`[registry] ${message}`)\n this.name = 'RegistryError'\n }\n}\n\nexport class Registry implements RouteRegistry {\n private tree: AnyRouteDefinition[]\n private readonly listeners = new Set<(tree: AnyRouteDefinition[]) => void>()\n private readonly logger = createLogger('registry')\n\n constructor(initialRoutes: AnyRouteDefinition[]) {\n this.tree = this.clone(initialRoutes)\n }\n\n // ── Public API ───────────────────────────────────────────────\n\n /**\n * Patch Routes\n *\n * Appends new routes to the tree at the specified parent location.\n * If no parentId is provided, routes are appended to the root level.\n * Validates that all incoming route ids are unique before applying the patch.\n *\n * @param routes - new routes to add\n * @param parentId - optional parent route id where routes should be appended\n * @throws {RegistryError} if parentId is not found or if route ids are duplicated\n */\n public patch(routes: AnyRouteDefinition[], parentId?: string): void {\n if (routes.length === 0)\n return\n\n this.validateIncoming(routes)\n\n const patched = this.findAndPatch(this.tree, routes, parentId)\n if (!patched) {\n throw new RegistryError(\n `Cannot patch: no route with id \"${parentId}\" found in the tree.`,\n )\n }\n\n this.tree = patched\n this.logger.log(`patch complete${parentId ? ` under \"${parentId}\"` : ' at root'}`, {\n added: routes.map(r => r.id),\n treeSize: this.collectIds(this.tree).size,\n })\n this.notify()\n }\n\n public unpatch(routeId: string): void {\n const { tree, found } = this.findAndUnpatch(this.tree, routeId)\n if (!found) {\n throw new RegistryError(\n `Cannot unpatch: no route with id \"${routeId}\" found in the tree.`,\n )\n }\n\n this.tree = tree\n this.logger.log(`unpatch \"${routeId}\" complete`, { treeSize: this.collectIds(this.tree).size })\n this.notify()\n }\n\n public getTree(): AnyRouteDefinition[] {\n return this.clone(this.tree)\n }\n\n public subscribe(listener: (tree: AnyRouteDefinition[]) => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n // ── Private helpers ──────────────────────────────────────────\n\n private notify(): void {\n const snapshot = this.clone(this.tree)\n this.listeners.forEach(l => l(snapshot))\n }\n\n /**\n * Deep Clone Tree\n *\n * Produces a shallow-enough clone that route objects themselves\n * are not mutated (lazy resolution mutates them in place,\n * which is intentional — but tree structure changes must not\n * affect the previous tree snapshot).\n *\n * @param routes - routes to clone\n */\n private clone(routes: AnyRouteDefinition[]): AnyRouteDefinition[] {\n return routes.map(r => ({\n ...r,\n children: r.children ? this.clone(r.children) : undefined,\n }))\n }\n\n /**\n * Collect All IDs\n *\n * Walks a route array recursively and returns all route ids found.\n * Used for duplicate detection before any patch is committed.\n *\n * @param routes - routes to walk through\n * @returns collected IDs\n */\n private collectIds(routes: AnyRouteDefinition[]): Set<string> {\n const ids = new Set<string>()\n const walk = (nodes: AnyRouteDefinition[]) => {\n for (const node of nodes) {\n ids.add(node.id)\n if (node.children?.length)\n walk(node.children)\n }\n }\n walk(routes)\n return ids\n }\n\n /**\n * Validate Incoming Routes\n *\n * Checks that:\n * 1. No incoming route id already exists in the current tree\n * 2. No incoming routes have duplicate ids among themselves\n *\n * Throws RegistryError immediately — never silently accepts bad state.\n *\n * @param incoming - new routes\n */\n private validateIncoming(incoming: AnyRouteDefinition[]): void {\n const existingIds = this.collectIds(this.tree)\n const incomingIds = new Set<string>()\n\n const walk = (nodes: AnyRouteDefinition[]) => {\n for (const node of nodes) {\n if (incomingIds.has(node.id))\n throw new RegistryError(`Duplicate route id \"${node.id}\" in the routes being registered.`)\n if (existingIds.has(node.id))\n throw new RegistryError(`Route id \"${node.id}\" is already registered.`)\n\n incomingIds.add(node.id)\n if (node.children?.length)\n walk(node.children)\n }\n }\n walk(incoming)\n }\n\n /**\n * Find and Patch\n *\n * Recursively searches for parentId and appends routes to its children.\n * Returns a new tree with the patch applied, or null if parentId not found.\n *\n * @param tree - routes tree\n * @param incoming - new routes to add\n * @param parentId - parent route id\n *\n * @returns patched routes tree\n */\n private findAndPatch(\n tree: AnyRouteDefinition[],\n incoming: AnyRouteDefinition[],\n parentId?: string,\n ): AnyRouteDefinition[] | null {\n const result: AnyRouteDefinition[] = []\n let found = false\n\n for (const node of tree) {\n if (node.id === parentId) {\n result.push({ ...node, children: [...(node.children ?? []), ...incoming] })\n found = true\n }\n else if (node.children?.length) {\n const patched = this.findAndPatch(node.children, incoming, parentId)\n result.push(patched ? { ...node, children: patched } : node)\n if (patched)\n found = true\n }\n else {\n result.push(node)\n }\n }\n\n if (!found && parentId === undefined) {\n result.push(...incoming)\n found = true\n }\n\n return found ? result : null\n }\n\n /**\n * Find and Unpatch\n *\n * Recursively searches for routeId and removes it from the tree.\n * Returns a new tree with the node removed. The entire subtree under\n * the removed node is discarded.\n *\n * @param tree - routes tree to search\n * @param routeId - id of the route to remove\n * @returns object with patched tree and found flag\n */\n private findAndUnpatch(\n tree: AnyRouteDefinition[],\n routeId: string,\n ): { tree: AnyRouteDefinition[], found: boolean } {\n const result: AnyRouteDefinition[] = []\n let found = false\n\n for (const node of tree) {\n if (node.id === routeId) {\n found = true\n continue\n }\n if (node.children?.length) {\n const { tree: children, found: foundInChildren } = this.findAndUnpatch(node.children, routeId)\n result.push(foundInChildren ? { ...node, children } : node)\n if (foundInChildren)\n found = true\n }\n else {\n result.push(node)\n }\n }\n\n return { tree: result, found }\n }\n}\n\n// ============================================================\n// CREATE REGISTRY\n// ============================================================\n\nexport function createRegistry(initialRoutes: AnyRouteDefinition[] = []): RouteRegistry {\n return new Registry(initialRoutes)\n}\n","import type { GenericSchema } from 'valibot'\nimport type {\n AnyRouteDefinition,\n AnyRouteMatch,\n Location,\n} from './types'\nimport { safeParse } from 'valibot'\n\n// ============================================================\n// MATCHER\n// Turns a Location into an ordered array of RouteMatches,\n// walking the route tree from root to leaf.\n//\n// Supports:\n// - static segments: /about\n// - named params: /users/:id\n// - nested params: /users/:userId/posts/:postId\n// - layout routes: path: '' (pathless — just wraps children)\n// - splat/catch-all: path: '*'\n// - query parsing + valibot validation per route\n// ============================================================\n\nimport { createLogger } from './logger'\n\nconst matcherLog = createLogger('matcher')\n\nconst RE_TRAILING_SLASH = /\\/$/\nconst RE_SPLAT_ROUTE_END = /^(.*)$/\nconst RE_SPLAT_ROUTE = /^(.*)/\nconst RE_ROUTE_SEGMENTS = /[.*+?^${}()|[\\]\\\\]/g\nconst RE_PATH_START = /^\\//\n\n/**\n * Compiles a route path string into a RegExp pattern and extracts parameter names.\n *\n * @param path - The route path to compile (e.g., '/users/:id', '*')\n * @param end - If true, pattern must match to end of string (leaf route); if false, prefix match (parent route)\n * @returns An object containing the compiled RegExp pattern and array of parameter names\n */\nexport function compilePath(\n path: string,\n end: boolean, // true = must match to end of string (leaf), false = prefix match (parent)\n): { pattern: RegExp, paramNames: string[] } {\n const paramNames: string[] = []\n\n // Normalize: remove trailing slash (except root)\n const normalized = path === '/' ? '/' : path.replace(RE_TRAILING_SLASH, '')\n\n // Splat / catch-all\n if (normalized === '*') {\n return {\n pattern: end ? RE_SPLAT_ROUTE_END : RE_SPLAT_ROUTE,\n paramNames: ['*'],\n }\n }\n\n // Build regex segment by segment\n const regexStr = normalized\n .split('/')\n .map((segment) => {\n if (segment.startsWith(':')) {\n paramNames.push(segment.slice(1))\n return '([^/]+)'\n }\n if (segment === '*') {\n paramNames.push('*')\n return '(.*)'\n }\n // Escape special regex chars in static segments\n return segment.replace(RE_ROUTE_SEGMENTS, '\\\\$&')\n })\n .join('/')\n\n // Root path in prefix mode must match any pathname — a plain ^\\/\n // is correct. The lookahead (?=/|$) would reject '/admin' because\n // after the slash there is neither another slash nor end-of-string.\n const pattern = end\n ? new RegExp(`^${regexStr}$`)\n : normalized === '/'\n ? RE_PATH_START\n : new RegExp(`^${regexStr}(?=/|$)`)\n\n return { pattern, paramNames }\n}\n\n/**\n * Parse the search string and optionally validates with a valibot schema.\n * This method will never throw, caller should decide how to handle the result.\n *\n * @param search - search term\n * @param schema - validation schema\n * @returns extracted data\n */\nexport function parseSearch(\n search: string,\n schema?: GenericSchema<Record<string, unknown>>,\n): { data: Record<string, unknown>, error: unknown } {\n const raw: Record<string, unknown> = {}\n\n if (search) {\n const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search)\n params.forEach((value, key) => {\n // Handle multiple values for the same key as array\n if (key in raw) {\n const existing = raw[key]\n raw[key] = Array.isArray(existing) ? [...existing, value] : [existing, value]\n }\n else {\n raw[key] = value\n }\n })\n }\n\n if (!schema)\n return { data: raw, error: null }\n\n const result = safeParse(schema, raw)\n if (result.success) {\n return { data: result.output, error: null }\n }\n return { data: raw, error: result.issues }\n}\n\n/**\n * Merge a base path with a segment\n * @param base Base path to merge\n * @param segment current segment\n * @returns Sanitized and merge path\n */\nfunction joinPaths(base: string, segment: string): string {\n if (!segment || segment === '/')\n return base || '/'\n\n if (segment.startsWith('/')) {\n matcherLog.warn(`Segment \"${segment}\" on path \"${base}\" shouldn't be prefixed by a slash.`)\n segment = segment.slice(1)\n }\n\n const rel = segment.replace(RE_PATH_START, '')\n if (!base || base === '/')\n return `/${rel}`\n return `${base.replace(RE_TRAILING_SLASH, '')}/${rel}`\n}\n\n/**\n * Matches a location against a route tree and returns an ordered array of route matches.\n *\n * @param routes - The root route definitions to match against\n * @param location - The current location containing pathname and search string\n * @returns An array of route matches from root to leaf, or null if no match found\n */\nexport function matchRoutes(\n routes: AnyRouteDefinition[],\n location: Location,\n): AnyRouteMatch[] | null {\n const { pathname, search } = location\n\n // We do a recursive tree walk rather than a flat list,\n // so we get proper ancestor → descendant match arrays\n const matches = walkTree(routes, pathname, '')\n if (!matches)\n return null\n\n // Deep-walk route ids for the snapshot so we can see nested routes\n function collectIds(rs: AnyRouteDefinition[]): string[] {\n return rs.flatMap(r => [r.id, ...(r.children ? collectIds(r.children) : [])])\n }\n\n if (!matches) {\n matcherLog.log(`no match for \"${pathname}\"`, {\n allRouteIds: collectIds(routes),\n })\n }\n else {\n matcherLog.log(`matched \"${pathname}\"`, {\n matches: matches.map(m => ({ id: m.route.id, path: m.route.path ?? '(index)' })),\n allRouteIds: collectIds(routes),\n })\n }\n\n // Attach search params (validated per route) to each match\n return matches?.map(match => ({\n ...match,\n search: (() => {\n const schema = match.route.search\n const { data } = parseSearch(search, schema)\n return data\n })(),\n loaderData: undefined,\n status: 'idle' as const,\n }))\n}\n\ntype PartialMatch = Omit<AnyRouteMatch, 'search' | 'loaderData' | 'status' | 'error'>\n\n/**\n * Calculates a route's priority score for matching precedence.\n * Higher scores match first. Static segments score higher than dynamic params.\n *\n * @param route - The route definition to score\n * @returns A numeric score where higher values indicate higher priority\n */\nfunction scoreRoute(route: AnyRouteDefinition): number {\n if (route.index || route.path === '')\n return 1\n const path = route.path ?? '' as string\n if (path === '/')\n return 100 // root static\n const segments = path.split('/').filter(Boolean) as string[]\n let score = 0\n for (const seg of segments) {\n if (seg === '*')\n return score\n\n score += seg.startsWith(':') ? 10 : 100\n }\n return score\n}\n\n/**\n * Recursively walks the route tree to find the first matching route path.\n * Routes are sorted by priority score (static segments first) and tested in order.\n *\n * @param routes - Array of route definitions to search through\n * @param fullPathname - The complete URL pathname to match against (never a slice)\n * @param matchedPath - The path consumed so far by parent routes\n * @returns An array of route matches from root to leaf, or null if no match found\n */\nfunction walkTree(\n routes: AnyRouteDefinition[],\n fullPathname: string, // always the complete URL pathname — never a slice\n matchedPath: string,\n): PartialMatch[] | null {\n const sorted = routes.toSorted((a, b) => scoreRoute(b) - scoreRoute(a))\n for (const route of sorted) {\n const result = tryMatch(route, fullPathname, matchedPath)\n if (result)\n return result\n }\n return null\n}\n\n/**\n * Attempts to match a single route against the given pathname.\n * Handles index routes, pathless layouts, and recursive child matching.\n *\n * @param route - The route definition to attempt matching against\n * @param fullPathname - The complete URL pathname to match (never a slice)\n * @param matchedPath - The path consumed so far by parent routes\n * @returns An array of route matches from this route to leaf routes, or null if no match\n */\nfunction tryMatch(\n route: AnyRouteDefinition,\n fullPathname: string, // always the complete URL pathname\n matchedPath: string,\n): PartialMatch[] | null {\n // Index route — matches the parent's exact pathname, no extra segment.\n // Behaves like path: '' but only matches when the URL is exactly the parent path.\n if (route.index) {\n const selfMatch: PartialMatch = {\n route,\n params: {},\n pathname: matchedPath,\n }\n // Only match if the full pathname is already fully consumed by the parent\n if (fullPathname === matchedPath || fullPathname === `${matchedPath}/`)\n return [selfMatch]\n return null\n }\n\n // Pathless layout route — doesn't consume any URL segment,\n // just contributes middleware/layout to its children\n if (route.path === '') {\n if (!route.children?.length)\n return null\n\n const childMatches = walkTree(route.children, fullPathname, matchedPath)\n if (!childMatches)\n return null\n\n const selfMatch: PartialMatch = {\n route,\n params: {},\n pathname: matchedPath,\n }\n return [selfMatch, ...childMatches]\n }\n\n const fullPath = joinPaths(matchedPath, route.path)\n const hasChildren = !!route.children?.length\n\n // Always compile and test against the full pathname.\n // Prefix match when has children (don't require end),\n // exact match when leaf.\n const { pattern, paramNames } = compilePath(fullPath, !hasChildren)\n const match = pattern.exec(fullPathname)\n\n if (!match)\n return null\n\n const params: Record<string, string> = {}\n paramNames.forEach((name, i) => {\n params[name] = decodeURIComponent(match[i + 1] ?? '')\n })\n\n const selfMatch: PartialMatch = {\n route,\n params,\n pathname: fullPath,\n }\n\n // Leaf route — full pathname must be fully consumed\n if (!hasChildren) {\n if (match[0] !== fullPathname && route.path !== '*') {\n return null\n }\n return [selfMatch]\n }\n\n // Has children — recurse with the same full pathname.\n // Children compile their own full paths and test against it.\n const childMatches = walkTree(route.children!, fullPathname, fullPath)\n\n // If no child matched, this parent is the leaf (index route scenario).\n // Valid only if the prefix match consumed the entire pathname.\n if (!childMatches) {\n if (match[0] === fullPathname) {\n return [selfMatch]\n }\n return null\n }\n\n return [selfMatch, ...childMatches]\n}\n\n/**\n * Utility used by the loader to decide whether to revalidate.\n *\n * @param prev previous route matching data to compare\n * @param next new route matching data\n * @returns true if any param value changed between two match arrays.\n */\nexport function haveParamsChanged(\n prev: AnyRouteMatch[],\n next: AnyRouteMatch[],\n): Record<string, boolean> {\n const changed: Record<string, boolean> = {}\n\n const nextById = new Map(next.map(m => [m.route.id, m]))\n\n for (const prevMatch of prev) {\n const nextMatch = nextById.get(prevMatch.route.id)\n if (!nextMatch) {\n changed[prevMatch.route.id] = true\n continue\n }\n\n const paramsChanged\n = JSON.stringify(prevMatch.params) !== JSON.stringify(nextMatch.params)\n const searchChanged\n = JSON.stringify(prevMatch.search) !== JSON.stringify(nextMatch.search)\n\n changed[prevMatch.route.id] = paramsChanged || searchChanged\n }\n\n return changed\n}\n","import type { RouterContextProvider } from './context'\nimport type {\n AnyRouteDefinition,\n AnyRouteMatch,\n NavigationRequest,\n RevalidateArgs,\n} from './types'\nimport { haveParamsChanged } from './matcher'\n\n// ============================================================\n// LOADER\n//\n// Responsibilities:\n// 1. Resolve lazy route modules (once, then cache)\n// 2. Decide which loaders need to re-run (revalidation logic)\n// 3. Run loaders in parallel, each with its own error boundary\n// 4. Return an updated match array with loaderData + status per match\n//\n// Does NOT throw — errors are captured per match as status: 'error'.\n// The router decides what to do with them (find nearest errorComponent).\n// ============================================================\n\n// ============================================================\n// LAZY RESOLUTION CACHE\n// Keyed by route id — once resolved, we never call lazy() again.\n// The cache lives for the lifetime of the router instance.\n// ============================================================\n\nexport type ResolvedLazy = Partial<Pick<\n AnyRouteDefinition,\n 'loader' | 'action' | 'component' | 'errorComponent'\n>>\n\nexport function createLazyCache(): Map<string, ResolvedLazy> {\n return new Map()\n}\n\n// Resolves a route's lazy() if not yet cached.\n// Merges the resolved fields onto the route object in-place\n// so subsequent accesses just use route.loader directly.\nexport async function resolveLazy(\n route: AnyRouteDefinition,\n cache: Map<string, ResolvedLazy>,\n): Promise<void> {\n if (!route.lazy)\n return\n if (cache.has(route.id)) {\n // Already resolved — merge cached result onto route\n const resolved = cache.get(route.id)!\n Object.assign(route, resolved)\n return\n }\n\n const resolved = await route.lazy()\n cache.set(route.id, resolved)\n Object.assign(route, resolved)\n}\n\n// ============================================================\n// REVALIDATION DECISION\n// Returns true if a loader should re-run for this navigation.\n// ============================================================\n\ntype RevalidationReason\n = | 'new-route' // route wasn't in the previous match array\n | 'params-changed' // params or search changed\n | 'post-action' // an action just completed\n | 'forced' // no previous data at all\n\nexport function shouldRunLoader(\n match: AnyRouteMatch,\n prevMatches: AnyRouteMatch[],\n paramsChanged: Record<string, boolean>,\n isPostAction: boolean,\n postActionFormData?: FormData,\n): { run: boolean, reason: RevalidationReason | 'skip' } {\n const prevMatch = prevMatches.find(m => m.route.id === match.route.id)\n\n // Route wasn't matched before — always run\n if (!prevMatch) {\n return { run: true, reason: 'new-route' }\n }\n\n // No loader data yet — always run\n if (prevMatch.loaderData === undefined) {\n return { run: true, reason: 'forced' }\n }\n\n // Params or search changed — run unless shouldRevalidate says no\n if (paramsChanged[match.route.id]) {\n if (match.route.shouldRevalidate) {\n const args: RevalidateArgs<Record<string, string>> = {\n currentParams: prevMatch.params,\n nextParams: match.params,\n currentSearch: prevMatch.search,\n nextSearch: match.search,\n }\n const should = match.route.shouldRevalidate(args)\n return { run: should, reason: should ? 'params-changed' : 'skip' }\n }\n return { run: true, reason: 'params-changed' }\n }\n\n // Post-action full revalidation — run unless shouldRevalidate says no\n if (isPostAction) {\n if (match.route.shouldRevalidate) {\n const args: RevalidateArgs<Record<string, string>> = {\n currentParams: prevMatch.params,\n nextParams: match.params,\n currentSearch: prevMatch.search,\n nextSearch: match.search,\n formData: postActionFormData,\n }\n const should = match.route.shouldRevalidate(args)\n return { run: should, reason: should ? 'post-action' : 'skip' }\n }\n return { run: true, reason: 'post-action' }\n }\n\n // Nothing changed — reuse previous data\n return { run: false, reason: 'skip' }\n}\n\n// ============================================================\n// RUN ONE LOADER\n// Executes a single route's loader, capturing errors.\n// Returns an updated RouteMatch.\n// ============================================================\n\nasync function runOneLoader(\n match: AnyRouteMatch,\n prevMatch: AnyRouteMatch | undefined,\n context: RouterContextProvider,\n request: NavigationRequest,\n lazyCache: Map<string, ResolvedLazy>,\n prevMatches: AnyRouteMatch[],\n paramsChanged: Record<string, boolean>,\n isPostAction: boolean,\n postActionFormData?: FormData,\n): Promise<AnyRouteMatch> {\n // Step 1 — resolve lazy module if needed\n try {\n await resolveLazy(match.route, lazyCache)\n }\n catch (error) {\n console.error(`[loader] resolveLazy failed for route \"${match.route.id}\"`, error)\n return { ...match, loaderData: undefined, status: 'error', error }\n }\n\n // Step 2 — decide whether to re-run\n const { run } = shouldRunLoader(match, prevMatches, paramsChanged, isPostAction, postActionFormData)\n\n if (!run) {\n // Reuse previous loader data\n return {\n ...match,\n loaderData: prevMatch?.loaderData,\n status: 'success',\n }\n }\n\n // Step 3 — no loader defined → resolve with undefined\n if (!match.route.loader) {\n return {\n ...match,\n loaderData: undefined,\n status: 'success',\n }\n }\n\n // Step 4 — run the loader, capture errors per-match\n try {\n const loaderData = await match.route.loader({\n context,\n params: match.params,\n search: match.search,\n request,\n })\n\n return {\n ...match,\n loaderData,\n status: 'success',\n }\n }\n catch (error) {\n return {\n ...match,\n loaderData: undefined,\n status: 'error',\n error,\n }\n }\n}\n\n// ============================================================\n// RUN ALL LOADERS\n// Main entry point — runs all matched route loaders in parallel.\n// Returns the full updated match array.\n// ============================================================\n\nexport interface RunLoadersOptions {\n matches: AnyRouteMatch[]\n prevMatches: AnyRouteMatch[]\n context: RouterContextProvider\n request: NavigationRequest\n lazyCache: Map<string, ResolvedLazy>\n isPostAction?: boolean\n // Present when isPostAction=true — passed to shouldRevalidate so\n // routes can make fine-grained revalidation decisions\n postActionFormData?: FormData\n}\n\nexport async function runLoaders(options: RunLoadersOptions): Promise<AnyRouteMatch[]> {\n const {\n matches,\n prevMatches,\n context,\n request,\n lazyCache,\n isPostAction = false,\n postActionFormData,\n } = options\n\n // Pre-compute which routes have changed params/search\n // so we don't recompute it per-loader\n const paramsChanged = haveParamsChanged(prevMatches, matches)\n\n // Run all loaders concurrently — errors are captured per match\n const updatedMatches = await Promise.all(\n matches.map((match) => {\n const prevMatch = prevMatches.find(m => m.route.id === match.route.id)\n return runOneLoader(\n match,\n prevMatch,\n context,\n request,\n lazyCache,\n prevMatches,\n paramsChanged,\n isPostAction,\n postActionFormData,\n )\n }),\n )\n\n return updatedMatches\n}\n\n// ============================================================\n// EXTRACT LOADER DATA\n// Produces the flat loaderData map (keyed by route id)\n// that goes into NavigationState.\n// ============================================================\n\nexport function extractLoaderData(\n matches: AnyRouteMatch[],\n): Record<string, unknown> {\n return Object.fromEntries(\n matches.map(match => [match.route.id, match.loaderData]),\n )\n}\n\n// ============================================================\n// FIND ERROR MATCH\n// Finds the deepest match with status: 'error'.\n// The router uses this to find the nearest errorComponent ancestor.\n// ============================================================\n\nexport function findErrorMatch(\n matches: AnyRouteMatch[],\n): AnyRouteMatch | undefined {\n // Reverse to find deepest (leaf-first)\n return matches.toReversed().find(m => m.status === 'error')\n}\n\n// ============================================================\n// FIND NEAREST ERROR BOUNDARY\n// Given a failed match, walks up the match array to find\n// the nearest ancestor (or self) that has an errorComponent.\n// Returns the index in the matches array, or -1 if none found.\n// ============================================================\n\nexport function findNearestErrorBoundary(\n matches: AnyRouteMatch[],\n failedMatch: AnyRouteMatch,\n): number {\n const failedIndex = matches.findIndex(m => m.route.id === failedMatch.route.id)\n if (failedIndex === -1)\n return -1\n\n // Walk from failed match upward toward root\n for (let i = failedIndex; i >= 0; i--) {\n if (matches[i]?.route.errorComponent)\n return i\n }\n\n return -1\n}\n","import type { GenericSchema, InferOutput } from 'valibot'\nimport type { RouterContextProvider } from './context'\n\n// ============================================================\n// PARAMS & SEARCH\n// ============================================================\n\n// Extracts param names from a path string, e.g. '/users/:id' → 'id'\nexport type ExtractParams<TPath extends string>\n = TPath extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Param | ExtractParams<`/${Rest}`>\n : TPath extends `${infer _Start}:${infer Param}`\n ? Param\n : never\n\n// Record of extracted param names → string values\nexport type ParamsFromPath<TPath extends string> = {\n [K in ExtractParams<TPath>]: string\n}\n\n// Search schema is always a zod object schema\nexport type SearchSchema = GenericSchema<Record<string, unknown>>\nexport type InferSearch<TSchema extends SearchSchema> = InferOutput<TSchema>\n\n// ============================================================\n// MIDDLEWARE\n// ============================================================\n\nexport interface MiddlewareArgs {\n context: RouterContextProvider\n request: NavigationRequest\n}\n\nexport type NextFn = () => Promise<void>\n\nexport interface Middleware {\n handler: (\n args: MiddlewareArgs,\n next: NextFn,\n ) => Promise<void>\n}\n\nexport type AnyMiddleware = Middleware\n\n// Helper to define a middleware with full type inference\n// Usage:\n// const requireAuth = defineMiddleware(async ({ context, request }, next) => {\n// const user = await getUser(request)\n// await next({ ...context, user })\n// })\nexport function defineMiddleware(\n handler: (args: MiddlewareArgs, next: NextFn) => Promise<void>,\n): Middleware {\n return { handler }\n}\n\n// ============================================================\n// LOADER & ACTION\n// ============================================================\n\nexport type MaybePromise<T> = Promise<T> | T\n\nexport interface LoaderArgs<\n TParams extends Record<string, string>,\n TSearch,\n> {\n context: RouterContextProvider\n params: TParams\n search: TSearch\n request: NavigationRequest\n}\n\nexport interface ActionArgs<\n TParams extends Record<string, string>,\n> {\n context: RouterContextProvider\n params: TParams\n request: NavigationRequest\n formData: FormData\n}\n\nexport type LoaderFn<\n TParams extends Record<string, string>,\n TSearch,\n TData,\n> = (args: LoaderArgs<TParams, TSearch>) => MaybePromise<TData>\n\nexport type ActionFn<\n TParams extends Record<string, string>,\n> = (args: ActionArgs<TParams>) => MaybePromise<unknown>\n\n// ============================================================\n// SHOULDREVALIDATE\n// ============================================================\n\nexport interface RevalidateArgs<TParams extends Record<string, string>> {\n currentParams: TParams\n nextParams: TParams\n currentSearch: Record<string, unknown>\n nextSearch: Record<string, unknown>\n formData?: FormData | undefined // present if revalidating after an action\n}\n\n// ============================================================\n// ROUTE DEFINITION\n// ============================================================\n\nexport interface RouteDefinition<\n TPath extends string = string,\n TMiddlewares extends AnyMiddleware[] = AnyMiddleware[],\n TSearchSchema extends SearchSchema = SearchSchema,\n TLoaderData = unknown,\n TComponent = unknown,\n> {\n // Identity\n id: string\n path?: TPath\n index?: true\n\n // Middleware chain — context accumulates left to right\n middleware?: TMiddlewares\n\n // Search params — valibot schema for validation + inference\n search?: TSearchSchema\n\n // Loader — receives fully typed context from middleware chain\n loader?: LoaderFn<\n ParamsFromPath<TPath>,\n TSearchSchema extends SearchSchema ? InferSearch<TSearchSchema> : Record<string, unknown>,\n TLoaderData\n >\n\n // Action — POST only\n action?: ActionFn<ParamsFromPath<TPath>>\n\n // Revalidation control — return false to skip re-running after action/navigation\n shouldRevalidate?: (args: RevalidateArgs<ParamsFromPath<TPath>>) => boolean\n\n // Component — typed as unknown here, narrowed by framework adapter\n component?: TComponent\n\n // Error boundary — bubbles to nearest ancestor that declares this\n errorComponent?: TComponent\n\n // Lazy loading — resolves loader and/or component on first navigation\n lazy?: () => Promise<\n Partial<Pick<\n RouteDefinition<TPath, TMiddlewares, TSearchSchema, TLoaderData, TComponent>,\n 'loader' | 'action' | 'component' | 'errorComponent'\n >>\n >\n\n // Children — nested routes\n children?: AnyRouteDefinition[]\n\n // Foreign framework renderer — set automatically by adapter `routes()` helpers.\n // When present, RouterView delegates mounting to this renderer instead of\n // treating the component as a native component.\n __renderer?: RouteRenderer\n}\n\nexport type AnyRouteDefinition = RouteDefinition<any, any, any, any, any>\n\n// ============================================================\n// ROUTE RENDERER — foreign framework adapter interface\n// ============================================================\n\nexport interface RouteRenderer {\n /** Mount component into target element. Returns cleanup fn. */\n mount: (component: unknown, target: HTMLElement) => () => void\n}\n\n// ============================================================\n// ROUTE MATCH — what the router produces after URL matching\n// ============================================================\n\nexport interface RouteMatch<TLoaderData = unknown> {\n route: AnyRouteDefinition\n params: Record<string, string>\n search: Record<string, unknown>\n pathname: string // the portion of the URL this route matched\n loaderData: TLoaderData\n status: 'idle' | 'loading' | 'success' | 'error'\n error?: unknown\n}\n\nexport type AnyRouteMatch = RouteMatch<unknown>\n\n// ============================================================\n// NAVIGATION REQUEST\n// ============================================================\n\nexport interface NavigationRequest {\n url: URL\n method: 'GET' | 'POST'\n formData?: FormData\n}\n\n// ============================================================\n// NAVIGATION STATE MACHINE\n// ============================================================\n\nexport type NavigationStatus\n = | 'idle'\n | 'loading' // navigating, loaders running\n | 'submitting' // action running\n | 'redirecting' // redirect triggered inside middleware or loader\n | 'error' // unhandled error (no errorComponent caught it)\n\nexport interface NavigationState {\n location: Location\n status: NavigationStatus\n matches: AnyRouteMatch[]\n loaderData: Record<string, unknown> // keyed by route id\n actionData: unknown\n error: unknown\n isTransitioning: boolean\n viewTransition: boolean\n}\n\n// ============================================================\n// LOCATION\n// ============================================================\n\nexport interface Location {\n pathname: string\n search: string\n hash: string\n state: unknown\n key: string // unique per navigation entry\n}\n\n// ============================================================\n// ROUTER OPTIONS\n// ============================================================\n\nexport interface RouterOptions {\n // Initial context — available to ALL middleware and loaders\n // This is where you'd put things like { apiClient, i18n, featureFlags }\n context?: () => RouterContextProvider\n\n // Global middleware — runs before every route's own middleware\n middleware?: AnyMiddleware[]\n\n // Called when no route matches\n notFoundComponent?: unknown\n\n // Root error fallback — required, catches anything not caught by route errorComponents\n errorComponent?: unknown\n}\n\n// ============================================================\n// REDIRECT — thrown inside middleware/loader to short-circuit\n// ============================================================\n\nexport class Redirect {\n readonly __type = 'redirect' as const\n constructor(\n public readonly to: string,\n public readonly status: 301 | 302 | 307 | 308 = 302,\n ) {}\n}\n\nexport function redirect(to: string, status: 301 | 302 | 307 | 308 = 302): never {\n throw new Redirect(to, status)\n}\n\nexport function isRedirect(value: unknown): value is Redirect {\n return value instanceof Redirect\n}\n\n// ============================================================\n// ROUTER INSTANCE — public API exposed to framework adapters\n// ============================================================\n\nexport interface RouterInstance {\n /** Current state */\n readonly state: NavigationState\n\n /** Subscribe to state changes — returns unsubscribe fn */\n subscribe: (listener: (state: NavigationState) => void) => () => void\n\n // Programmatic navigation\n navigate: (to: string, options?: NavigateOptions) => Promise<void>\n replace: (to: string, options?: NavigateOptions) => Promise<void>\n back: () => void\n forward: () => void\n\n /** Form submission trigger (called by <Form> component in adapters) */\n submit: (formData: FormData, options: SubmitOptions) => Promise<void>\n\n /** Must be called once by the framework adapter before first render */\n initialize: () => Promise<void>\n\n /** Add routes under a parent node (by id). Use 'root' for top level */\n patch: (routes: AnyRouteDefinition[], parentId?: string) => void\n /** Remove a route subtree by id */\n unpatch: (routeId: string) => void\n\n /** Cleanup — removes history listener */\n destroy: () => void\n}\n\nexport interface NavigateOptions {\n state?: unknown\n replace?: boolean\n resetScroll?: boolean\n viewTransition?: boolean\n}\n\nexport interface SubmitOptions {\n action: string // the route path that handles this submission\n method?: 'POST'\n}\n\n// ============================================================\n// REGISTRY — public API for route patching\n// ============================================================\n\nexport interface RouteRegistry {\n // Add routes under a parent node (by id). Use 'root' for top level\n patch: (routes: AnyRouteDefinition[], parentId?: string) => void\n\n // Remove a route subtree by id\n unpatch: (routeId: string) => void\n\n // Get the current full route tree\n getTree: () => AnyRouteDefinition[]\n\n // Subscribe to tree changes (router uses this to recompute match tables)\n subscribe: (listener: (tree: AnyRouteDefinition[]) => void) => () => void\n}\n","import type { ResolvedLazy } from './loader'\nimport type {\n AnyRouteMatch,\n ContextMap,\n NavigationRequest,\n Redirect,\n} from './types'\nimport { resolveLazy } from './loader'\nimport { isRedirect } from './types'\n\n// ============================================================\n// ACTION\n//\n// Responsibilities:\n// 1. Find the deepest matched route that declares an action\n// 2. Resolve lazy module if needed\n// 3. Run the action with context + formData\n// 4. Return a typed result the router uses to decide next steps\n//\n// Three possible outcomes:\n// - ActionSuccess → has actionData, router then revalidates loaders\n// - ActionRedirect → router navigates to the new location\n// - ActionError → has error, router finds nearest errorComponent\n// loaders still revalidate after an error\n//\n// Does NOT run loaders — that's the router's job after it gets the result.\n// Does NOT run middleware — the router runs middleware first, then calls here.\n// ============================================================\n\n// ============================================================\n// RESULT TYPES\n// Discriminated union so the router can switch on .type cleanly\n// ============================================================\n\nexport interface ActionSuccess {\n type: 'success'\n actionData: unknown\n formData: FormData // passed through to shouldRevalidate\n}\n\nexport interface ActionRedirect {\n type: 'redirect'\n redirect: Redirect\n formData: FormData\n}\n\nexport interface ActionError {\n type: 'error'\n error: unknown\n formData: FormData // loaders still revalidate after error\n}\n\nexport type ActionResult = ActionSuccess | ActionRedirect | ActionError\n\n// ============================================================\n// FIND ACTION ROUTE\n// Walks the match array leaf → root to find the deepest\n// route that has an action defined.\n// Returns undefined if no route in the match array has an action.\n// ============================================================\n\nexport function findActionMatch(\n matches: AnyRouteMatch[],\n): AnyRouteMatch | undefined {\n // Leaf-first search — deepest action wins.\n // A route with lazy() is also a candidate — its action\n // may only exist after the lazy module resolves.\n return matches.toReversed().find(\n m => m.route.action || m.route.lazy,\n )\n}\n\n// ============================================================\n// RUN ACTION\n// Executes the action for the given match.\n// Expects middleware to have already run — context is the\n// fully accumulated context from the middleware chain.\n// ============================================================\n\nexport async function runAction(\n matches: AnyRouteMatch[],\n context: ContextMap,\n request: NavigationRequest,\n formData: FormData,\n lazyCache: Map<string, ResolvedLazy>,\n): Promise<ActionResult> {\n // Step 1 — find the action route\n const actionMatch = findActionMatch(matches)\n\n if (!actionMatch) {\n // No action defined on any matched route — developer error\n // We treat it as an error result so the router surfaces it\n // via the nearest errorComponent rather than crashing silently\n return {\n type: 'error',\n error: new Error(\n `No action found for \"${request.url.pathname}\". `\n + `Define an action on the route or remove the form submission.`,\n ),\n formData,\n }\n }\n\n // Step 2 — resolve lazy module (may expose the action fn)\n await resolveLazy(actionMatch.route, lazyCache)\n\n // Step 3 — re-check after lazy resolution\n // (lazy() may be the thing that provides the action)\n if (!actionMatch.route.action) {\n return {\n type: 'error',\n error: new Error(\n `Route \"${actionMatch.route.id}\" resolved its lazy module but `\n + `no action was found. Make sure your lazy() export includes an action.`,\n ),\n formData,\n }\n }\n\n // Step 4 — run the action\n try {\n const actionData = await actionMatch.route.action({\n context,\n params: actionMatch.params,\n request,\n formData,\n })\n\n return {\n type: 'success',\n actionData,\n formData,\n }\n }\n catch (error) {\n // Redirect thrown inside action — not an error, just navigation\n if (isRedirect(error)) {\n return {\n type: 'redirect',\n redirect: error,\n formData,\n }\n }\n\n // Genuine error — capture it, loaders still revalidate after\n return {\n type: 'error',\n error,\n formData,\n }\n }\n}\n\n// ============================================================\n// BUILD POST-ACTION REVALIDATION REQUEST\n// After an action completes, loaders need to revalidate.\n// We reconstruct a GET-like NavigationRequest for them,\n// and attach the formData so shouldRevalidate can inspect it.\n// ============================================================\n\nexport function buildRevalidationRequest(\n originalRequest: NavigationRequest,\n formData: FormData,\n): NavigationRequest & { formData: FormData } {\n return {\n url: originalRequest.url,\n method: 'GET',\n formData, // available in shouldRevalidate, not in loader args\n }\n}\n\n// ============================================================\n// PATCH SHOULD-REVALIDATE ARGS WITH FORM DATA\n// After an action, shouldRevalidate receives formData so a route\n// can make smart decisions: \"only revalidate if this form\n// touched data I care about\".\n//\n// This is called in loader.ts's shouldRunLoader when isPostAction=true.\n// We export it here since it's logically tied to action flow.\n// ============================================================\n\nexport function buildPostActionRevalidateArgs(\n prevMatch: AnyRouteMatch,\n nextMatch: AnyRouteMatch,\n formData: FormData,\n) {\n return {\n currentParams: prevMatch.params,\n nextParams: nextMatch.params,\n currentSearch: prevMatch.search,\n nextSearch: nextMatch.search,\n formData,\n }\n}\n","import type { History } from './history'\nimport type {\n AnyMiddleware,\n AnyRouteDefinition,\n AnyRouteMatch,\n Location,\n NavigateOptions,\n NavigationRequest,\n NavigationState,\n RouteRegistry,\n RouteRenderer,\n RouterInstance,\n RouterOptions,\n SubmitOptions,\n} from './types'\nimport { runAction } from './action'\nimport { RouterContextProvider } from './context'\nimport { createBrowserHistory } from './history'\nimport { createLazyCache, extractLoaderData, findErrorMatch, findNearestErrorBoundary, runLoaders } from './loader'\nimport { createLogger } from './logger'\nimport { matchRoutes } from './matcher'\nimport { runRouteMiddleware } from './middleware'\nimport { createRegistry } from './registry'\nimport { isRedirect } from './types'\n\n// ============================================================\n// ROUTER\n//\n// The state machine that wires everything together.\n//\n// Owns:\n// - NavigationState (current state, emitted to subscribers)\n// - History (pushState / popstate)\n// - LazyCache (persists across navigations)\n// - Navigation cancellation (via navigationId)\n//\n// Delegates to:\n// - matcher → URL → RouteMatch[]\n// - middleware → context accumulation\n// - loader → parallel data fetching\n// - action → POST handling\n// ============================================================\n\nexport class Router implements RouterInstance {\n private readonly history: History\n private readonly options: RouterOptions\n private registry: RouteRegistry\n\n private readonly logger = createLogger('router')\n private readonly lazyCache = createLazyCache()\n private readonly listeners = new Set<(state: NavigationState) => void>()\n private currentNavigationId: number = 0\n\n private _state: NavigationState\n private unlisten: () => void\n private unlistenRegistry?: () => void\n\n constructor(history: History, options: RouterOptions, registry: RouteRegistry) {\n this.history = history\n this.options = options\n this.registry = registry\n\n this._state = {\n location: history.location,\n status: 'idle',\n matches: [],\n loaderData: {},\n actionData: undefined,\n error: undefined,\n isTransitioning: false,\n viewTransition: false,\n }\n\n this.unlisten = history.listen(async (location) => {\n const navigationId = ++this.currentNavigationId\n this.setState({ status: 'loading', isTransitioning: true })\n await this.performNavigation(location, navigationId)\n })\n\n this.unlistenRegistry = registry?.subscribe(async () => {\n this.logger.log('registry changed — re-navigating', this.state.location.pathname)\n\n if (this._state.status === 'loading' || this._state.status === 'submitting') {\n this.logger.log('registry changed but navigation in-flight — skipping re-nav, in-flight nav will use updated tree')\n return\n }\n\n const navigationId = ++this.currentNavigationId\n this.setState({ status: 'loading', isTransitioning: true })\n await this.performNavigation(this.state.location, navigationId)\n })\n }\n\n // ── Public API ───────────────────────────────────────────────\n\n public get state(): NavigationState {\n return this._state\n }\n\n public subscribe(listener: (state: NavigationState) => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n public async navigate(to: string, opts: NavigateOptions = {}): Promise<void> {\n if (opts.replace) {\n this.history.replace(to, opts.state)\n }\n else {\n this.history.push(to, opts.state)\n }\n\n const navigationId = ++this.currentNavigationId\n const location = this.history.location\n\n this.setState({\n status: 'loading',\n isTransitioning: true,\n viewTransition: opts.viewTransition === true && 'startViewTransition' in document,\n })\n\n await this.performNavigation(location, navigationId)\n }\n\n public async replace(to: string, opts: NavigateOptions = {}): Promise<void> {\n return this.navigate(to, { ...opts, replace: true })\n }\n\n public async submit(formData: FormData, opts: SubmitOptions): Promise<void> {\n const navigationId = ++this.currentNavigationId\n\n const actionLocation: Location = {\n pathname: opts.action,\n search: '',\n hash: '',\n state: null,\n key: 'action',\n }\n\n this.setState({ status: 'submitting', isTransitioning: true })\n await this.performSubmission(actionLocation, formData, navigationId)\n }\n\n public back(): void { this.history.back() }\n public forward(): void { this.history.forward() }\n\n public async initialize(): Promise<void> {\n const navigationId = ++this.currentNavigationId\n const location = this.history.location\n this.setState({ status: 'loading', location, isTransitioning: true })\n await this.performNavigation(location, navigationId)\n }\n\n public patch(routes: AnyRouteDefinition[], parentId?: string) {\n this.registry.patch(routes, parentId)\n }\n\n public unpatch(routeId: string) {\n this.registry.unpatch(routeId)\n }\n\n public destroy(): void {\n this.unlisten()\n this.unlistenRegistry?.()\n this.listeners.clear()\n }\n\n // ── Private helpers ──────────────────────────────────────────\n\n private setState(partial: Partial<NavigationState>): void {\n this._state = { ...this._state, ...partial }\n this.listeners.forEach(l => l(this.state))\n }\n\n private buildRequest(location: Location, method: 'GET' | 'POST' = 'GET', formData?: FormData): NavigationRequest {\n return {\n url: new URL(location.pathname + location.search + location.hash, window.location.origin),\n method,\n formData,\n }\n }\n\n private getRoutes() {\n return this.registry.getTree()\n }\n\n private handleNotFound(location: Location): void {\n this.setState({\n status: 'error',\n location,\n matches: [],\n loaderData: {},\n error: new Error(`No route matched \"${location.pathname}\"`),\n isTransitioning: false,\n })\n }\n\n private commit(navigationId: number, location: Location, matches: AnyRouteMatch[], actionData?: unknown): boolean {\n if (navigationId !== this.currentNavigationId)\n return false\n\n const loaderData = extractLoaderData(matches)\n const errorMatch = findErrorMatch(matches)\n\n if (errorMatch) {\n const boundaryIndex = findNearestErrorBoundary(matches, errorMatch)\n this.setState({\n status: boundaryIndex === -1 ? 'error' : 'idle',\n location,\n matches,\n loaderData,\n actionData,\n error: boundaryIndex === -1 ? errorMatch.error : undefined,\n isTransitioning: false,\n })\n }\n else {\n this.setState({\n status: 'idle',\n location,\n matches,\n loaderData,\n actionData,\n error: undefined,\n isTransitioning: false,\n viewTransition: false,\n })\n }\n\n return true\n }\n\n private async runMiddleware(matches: AnyRouteMatch[], request: NavigationRequest): Promise<RouterContextProvider> {\n const ctx = this.options.context?.() ?? new RouterContextProvider()\n\n return runRouteMiddleware(\n (this.options.middleware ?? []) as AnyMiddleware[],\n matches,\n ctx,\n request,\n )\n }\n\n private async performNavigation(location: Location, navigationId: number): Promise<void> {\n this.logger.log(`navigate → ${location.pathname}${location.search}`, { navigationId })\n const request = this.buildRequest(location)\n const matches = matchRoutes(this.getRoutes(), location)\n\n this.logger.log('matched routes', matches\n ? matches.map(m => ({ id: m.route.id, path: m.route.path, params: m.params }))\n : 'NO MATCH')\n\n if (!matches?.length) {\n this.logger.warn(`no match for \"${location.pathname}\" — setting error state`)\n if (navigationId === this.currentNavigationId)\n this.handleNotFound(location)\n return\n }\n\n let context: RouterContextProvider\n try {\n context = await this.runMiddleware(matches, request)\n this.logger.log('middleware chain complete', context)\n }\n catch (error) {\n if (navigationId !== this.currentNavigationId)\n return\n if (isRedirect(error)) {\n this.logger.log(`middleware redirect → ${(error as any).to}`)\n await this.navigate(error.to, { replace: true })\n return\n }\n this.logger.error('middleware threw', error)\n this.setState({ status: 'error', location, matches, loaderData: {}, error, isTransitioning: false })\n return\n }\n\n if (navigationId !== this.currentNavigationId) {\n this.logger.log(`stale navigation ${navigationId} cancelled (current: ${this.currentNavigationId})`)\n return\n }\n\n const updatedMatches = await runLoaders({\n matches,\n prevMatches: this._state.matches,\n context,\n request,\n lazyCache: this.lazyCache,\n isPostAction: false,\n })\n\n this.logger.log('loaders complete', updatedMatches.map(m => ({\n id: m.route.id,\n status: m.status,\n loaderData: m.loaderData,\n error: m.error,\n })))\n\n this.commit(navigationId, location, updatedMatches)\n }\n\n private async performSubmission(location: Location, formData: FormData, navigationId: number): Promise<void> {\n const request = this.buildRequest(location, 'POST', formData)\n const matches = matchRoutes(this.getRoutes(), location)\n\n if (!matches?.length) {\n if (navigationId === this.currentNavigationId)\n this.handleNotFound(location)\n return\n }\n\n let context: RouterContextProvider\n try {\n context = await this.runMiddleware(matches, request)\n }\n catch (error) {\n if (navigationId !== this.currentNavigationId)\n return\n if (isRedirect(error)) {\n await this.navigate(error.to, { replace: true })\n return\n }\n this.setState({ status: 'error', location, matches, loaderData: this._state.loaderData, error, isTransitioning: false })\n return\n }\n\n if (navigationId !== this.currentNavigationId)\n return\n\n const actionResult = await runAction(matches, context, request, formData, this.lazyCache)\n\n if (navigationId !== this.currentNavigationId)\n return\n\n if (actionResult.type === 'redirect') {\n await this.navigate(actionResult.redirect.to, { replace: true })\n return\n }\n\n const revalidationRequest = this.buildRequest(location)\n const updatedMatches = await runLoaders({\n matches,\n prevMatches: this._state.matches,\n context,\n request: revalidationRequest,\n lazyCache: this.lazyCache,\n isPostAction: true,\n postActionFormData: formData,\n })\n\n if (navigationId !== this.currentNavigationId)\n return\n\n if (actionResult.type === 'error') {\n const errorMatch = findErrorMatch(updatedMatches)\n const boundaryIndex = errorMatch ? findNearestErrorBoundary(updatedMatches, errorMatch) : -1\n this.setState({\n status: boundaryIndex === -1 ? 'error' : 'idle',\n location,\n matches: updatedMatches,\n loaderData: extractLoaderData(updatedMatches),\n actionData: undefined,\n error: actionResult.error,\n isTransitioning: false,\n })\n return\n }\n\n this.commit(navigationId, location, updatedMatches, actionResult.actionData)\n }\n}\n\nexport function createRouter(\n history: History,\n options: RouterOptions,\n registry: RouteRegistry,\n): RouterInstance {\n return new Router(history, options, registry)\n}\n\nexport function createBrowserRouter(\n options: RouterOptions,\n initialRoutes?: AnyRouteDefinition[],\n) {\n const history = createBrowserHistory()\n const registry = createRegistry(initialRoutes)\n\n return createRouter(history, options, registry)\n}\n\n/**\n * Recursively tag all routes (and their children) with a renderer.\n * This method is used by renderers\n */\nexport function tag(renderer: RouteRenderer, defs: AnyRouteDefinition[]): AnyRouteDefinition[] {\n return defs.map(route => ({\n __renderer: renderer, // Passed first to allow being override if this a renderer is already configured\n ...route,\n children: route.children ? tag(renderer, route.children) : undefined,\n }))\n}\n"],"mappings":"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","names":["selfMatch","childMatches","resolved"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cross-router-core",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"description": "",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"valibot": "^1.2.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@vitest/ui": "4.0.18",
|
|
26
|
+
"jsdom": "^28.1.0",
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"valibot": "^1.2.0",
|
|
30
|
+
"vitest": "^4.0.18"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"linkDirectory": true
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"lint": "eslint",
|
|
38
|
+
"test": "vitest",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|