File-based router for the aihu meta-framework.
__router_registerAfterGuardfunction __router_registerAfterGuard( fn: Parameters<Router['registerAfterGuard']>[0], ): () => void__router_registerBeforeGuardfunction __router_registerBeforeGuard( fn: Parameters<Router['registerBeforeGuard']>[0], ): () => voidbindRouteSignalWriterfunction bindRouteSignalWriter( value: RouteContextValue, write: (m: MatchResult | null) => void, ): voidcomponentTagForfunction componentTagFor(name: string): stringNormalize a component name to its custom-element tag (O1b).
composeRouterMiddlewarefunction composeRouterMiddleware(...middlewares: RouterMiddleware[]): RouterMiddlewarecreatePrefetcherfunction createPrefetcher(mode: PrefetchMode): { attach: (a: HTMLAnchorElement, getMatch: () => MatchResult | null) => void detach: (a: HTMLAnchorElement) => void }Build the DOM hooks needed for a prefetch-enabled `<a>`.
createRouterfunction createRouter(routes: RouteDefinition[]): RoutercreateRouteSignalfunction createRouteSignal( router: Router, initialPathname?: string, ): { read: () => MatchResult | null write: (m: MatchResult | null) => void dispose: () => void }Wrap a `Router` in a reactive `Signal<MatchResult | null>` that updates on `popstate` events and on calls to `navigate()`.
createServerRouterfunction createServerRouter( routes: RouteDefinition[], options?: ServerRouterOptions, ): ServerRouterConstruct a router with the server-side `handle(req)` request handler wired in.
defineRouterMiddlewarefunction defineRouterMiddleware(fn: RouterMiddleware): RouterMiddlewarelayoutTagForfunction layoutTagFor(name: string): stringRuntime layout namespace convention (v0.7.5).
navigateasync function navigate( href: string, opts: NavigateOptions = {}, ): Promise<'navigated' | 'cancelled' | 'no-router' | 'no-match'>Programmatically navigate to `href` within the active router.
provideRouteContextfunction provideRouteContext(value: RouteContextValue): voidProvide the route context (used by `<router>` SFC).
readAihuComponentTagfunction readAihuComponentTag(f: string): stringO1b: Resolve the custom-element tag a component file registers under.
readRouteSidecarfunction readRouteSidecar(f: string): RouteSidecar | nullv0.6.3: Read sibling .route.json sidecar.
scanComponentsfunction scanComponents(d: string): Record<string, string>O1b: Recursively scan a components dir for `.aihu` files, mapping each file's normalized custom-element tag → absolute POSIX path.
scanLayoutsfunction scanLayouts(d: string): LayoutMapv0.6.8: Scan layouts dir for .aihu files.
scanPagesfunction scanPages(root: string, pd: string): MiddlewareScanuseRoutefunction useRoute(): MatchResult | nullRead the current matched route from the nearest `<router>` context.
useRouterfunction useRouter(): Router | nullGet the active `Router` instance from the nearest `<router>` context.
viteRouterPluginfunction viteRouterPlugin(opts?: RouterPluginOptions): VitePluginRouteContextconst RouteContextContext token for the active routing context.
viteRouterIntegrationconst viteRouterIntegrationLayoutMapinterface LayoutMap {
[name: string]: string
}Layout name → absolute file path (v0.6.8).
MiddlewareScaninterface MiddlewareScan {
/** Route files (non-underscore page files). */
routes: string[]
/**
* Map from directory (absolute path) to its `_middleware.(ts|js)` file.
* When the runtime composes a route, all middleware files from the route
* file's ancestor directories (innermost first) should be applied.
*/
middlewareByDir: Record<string, string>
}v0.7.2: File-convention middleware discovered alongside routes.
NavigateOptionsinterface NavigateOptions {
/** Use `history.replaceState` instead of `pushState`. */
replace?: boolean
/** Wrap navigation in `document.startViewTransition()` when supported. */
viewTransitions?: boolean
}RouteContextValueinterface RouteContextValue {
router: Router
/** Reactive read accessor for the current match. */
current: () => MatchResult | null
/** @internal — view transitions opt-in (read by `<a>`). */
viewTransitions?: boolean
}Value carried by `RouteContext`.
RouteMatchContextinterface RouteMatchContext {
url: URL
params: Record<string, string>
route: RouteDefinition
signal: AbortSignal
env?: Record<string, string>
}RouterPluginOptionsinterface RouterPluginOptions {
pagesDir?: string
/** Directory to scan for layout files. Default: 'src/layouts' */
layoutsDir?: string
/** Directory to scan for component files. Default: 'src/components' */
componentsDir?: string
/**
* Build-time route-metadata extractor — `@aihu/compiler`'s `compileRouteMeta`.
* When provided, `genR` uses it to recover FULL `@route` metadata
* (`head`/`middleware`/`params`/`ssr`/`layout`) for `.aihu` pages, since the
* stdin compile path writes no `.route.json` sidecar to disk. Wired by
* `@aihu/app` (which pairs the compiler + router plugins). When absent (e.g.
* standalone `viteRouterIntegration` without the compiler), `genR` falls back
* to reading `name`+`layout` from the `@route` block via regex.
*/
compileRouteMeta?: (source: string, id: string) => RouteSidecar | null
}RouteSidecarinterface RouteSidecar {
name?: string
middleware?: string[]
ssr?: boolean
layout?: string
/** Declared route param names, e.g. ["slug"]. Emitted by the Rust compiler from $prop declarations. */
params?: string[]
/**
* B2: per-route `<head>` metadata (compiler `head:` block). Omitted entirely
* when a route declares no `head:`. Threaded through to RouteDefinition.head
* and the generated `virtual:aihu-routes` module.
*/
head?: RouteHead
/**
* O1a: normalized custom-element tags this route's template references
* (e.g. `["hn-comment", "user-card"]`). Emitted by the compiler's
* `route.json`; threaded through to RouteDefinition.components so O1c can
* register a route's components on navigation instead of eager imports.
*/
components?: string[]
/**
* GX Phase 3 (#437-GX): the compiled `extract` policy (Phase 1 fan-out —
* always present in a v0.1.12+ `.route.json`, the default recorded, never
* implied by absence). Values stay `unknown` here: the compiler validated
* them (C483), but sidecars can be hand-edited, so consumers normalize
* fail-closed via `@aihu/server`'s `deriveReadPolicy`.
*/
extract?: { read?: unknown; call?: unknown }
/**
* GX Phase 4 (#466): the compiled `data:` declaration — the governed
* resource type binding (`type` keys the provider registry) and the
* declared locked-state `preview:` fields. INTEGRATION SEAM (Builder A):
* the Rust compiler parses the `@route` `data:` block and fans it into
* `.route.json` beside `extract`; this field threads it (sidecar or
* `compileRouteMeta`) into `RouteDefinition.data`, where
* `createServerRouter` normalizes it fail-closed (`normalizeGovernedData`).
*/
data?: { type?: string; preview?: string[] }
}Fields from a .route.json compiler sidecar (v0.6.3).
ServerRouterOptionsinterface ServerRouterOptions {
/**
* The governed registry (`createGovernedRegistry()` from `@aihu/server`).
* Passing it activates the governed pipeline for every route with a
* compiled `data:` declaration, boot-validates the registry against the
* route census (spec §2.3 — a typo is a boot refusal, not a 500 at first
* request), and enables the E3 governed-data endpoint. Pass the SAME
* instance to `AgentServiceOptions.entitlements` so both axes share one
* live meaning per scope (§4.6).
*/
readonly governed?: GovernedRegistry
/**
* Credential material for principal resolution on the SSR path: the
* verifying auth plugin (Bearer JWTs), the anonymous-UA classifier, and a
* host-verified session resolver (cookie path). Same injection posture as
* `AgentServiceOptions.resolveAuth` / `PrincipalGateDeps`.
*/
readonly auth?: GovernedRequestAuth
}GX Phase 4 (#466, 70-governed-data-access): options for the server router.
AfterGuardtype AfterGuard = (to: MatchResult, from: MatchResult | null) => voidAfter-navigation callback.
BeforeGuardtype BeforeGuard = (
to: MatchResult,
from: MatchResult | null,
next: NextFn,
) => void | Promise<void>Before-navigation guard.
MatchResulttype MatchResult = {
route: RouteDefinition
params: Record<string, string>
/** The matched pathname, e.g. '/posts/hello' */
pathname: string
}NextFntype NextFn = (decision?: void | false | string) => voidNavigation guard `next` callback (RFC-A5-015).
PrefetchModetype PrefetchMode = 'none' | 'hover' | 'visible'RouteDefinitiontype RouteDefinition = {
pattern: string
segments: RouteSegment[]
module: () => Promise<RouteModule>
// v0.6.3: fields from .route.json compiler sidecars
name?: string
middleware?: string[]
ssr?: boolean
layout?: string
// B2: per-route <head> metadata from the .route.json `head:` block
head?: RouteHead
/** Normalized custom-element tags this route references; used for route-scoped registration (O1). */
components?: readonly string[]
/**
* GX Phase 3 (#437-GX): the route's compiled `extract` policy from the
* `.route.json` sidecar (`{ read, call }`, the Phase 1 fan-out). Values are
* `unknown` — consumers normalize fail-closed (`deriveReadPolicy` in
* `@aihu/server`). Drives the compliance-tier noindex signal in
* `createServerRouter.handle` and the derived robots/discovery listings in
* `@aihu-plugin/agent-readiness`. Absent on hand-built routes → the
* resolved default (`read: 'agents'`, `call: 'anonymous'`) applies.
*/
extract?: { readonly read?: unknown; readonly call?: unknown }
/**
* GX Phase 4 (#466): the route's compiled `data:` declaration from the
* `.route.json` sidecar — `{ type, preview? }`, naming the governed
* resource type (the provider key) and the fields renderable in the locked
* state. Values are `unknown`: consumers normalize FAIL-CLOSED
* (`normalizeGovernedData` in `@aihu/server` — malformed is a boot
* refusal, never rounded to ungoverned). Presence makes the route governed:
* `createServerRouter.handle` replaces the ungated `mod.loader` path with
* the generated loader (70-governed-data-access §3).
*/
data?: unknown
}RouteHeadtype RouteHead = {
title?: string
description?: string
canonical?: string
og?: {
title?: string
description?: string
image?: string
type?: string
url?: string
}
twitter?: {
card?: string
title?: string
description?: string
image?: string
site?: string
}
jsonld?: unknown
}Per-route `<head>` metadata, threaded from the compiler's `.route.json` sidecar (`head:` block).
RouteModuletype RouteModule = {
default: unknown
/**
* The route's data loader. Either a plain server loader (the shipped,
* ungoverned contract — called with the matched params), or — GX Phase 4
* (#466) — the `defineGovernedFetch` escape hatch (structurally typed here
* so this browser-eligible file keeps zero `@aihu/server` imports): a
* route-local provider the generated loader gates. On a `data:`-declared
* route a PLAIN loader is a C486 build error (one data source per route).
*/
loader?:
| ((params: Record<string, string>) => Promise<unknown>)
| { readonly _brand: 'DefinedGovernedFetch' }
}Routertype Router = {
match(pathname: string): MatchResult | null
/**
* Register a guard that runs before each navigation. Multiple guards run
* in registration order. The first `next(false)` cancels navigation; the
* first `next('/x')` redirects. Returns a dispose fn.
*/
registerBeforeGuard(fn: BeforeGuard): () => void
/**
* Register a callback that runs after each navigation completes (after the
* outlet has updated). Returns a dispose fn.
*/
registerAfterGuard(fn: AfterGuard): () => void
/**
* Run the registered before-guard chain. Returns the final navigation
* decision: 'continue', 'cancel', or { redirect: string }.
* @internal — used by `<a>` and `<navigate>`.
*/
runBeforeGuards(
to: MatchResult,
from: MatchResult | null,
): Promise<'continue' | 'cancel' | { redirect: string }>
/**
* Run the registered after-guard chain.
* @internal — used by `<router>`.
*/
runAfterGuards(to: MatchResult, from: MatchResult | null): void
}RouterMiddlewaretype RouterMiddleware = (
ctx: RouteMatchContext,
next: () => Promise<RouterResult>,
) => Promise<RouterResult>RouterResulttype RouterResult =
| { kind: 'continue' }
| { kind: 'redirect'; location: string; status?: number }
| { kind: 'cancel'; status?: number; body?: string }RouteSegmenttype RouteSegment =
| { kind: 'static'; path: string }
| { kind: 'param'; name: string }
| { kind: 'catchall' }ServerRoutertype ServerRouter = Router & {
handle(req: Request): Promise<Response>
}