/** * Route manifest - the fs-free heart of file-based routing. Maps route file paths to nifra * router patterns + their nested layout chain. `discoverRoutes` (in `@nifrajs/web/fs`) scans * the filesystem and feeds `buildManifest`; everything here is pure logic, so it stays * portable (no fs, no DOM) and fully unit-testable. Edge deploys pre-build the manifest. */ import type { StandardSchemaV1 } from "@nifrajs/core/server" import type { BoundaryDescriptor, BoundaryRegistration } from "./boundary.ts" /** Context passed to a route `loader`. The `api` + `env` are injected by `createWebApp` and typed * per-route via `@nifrajs/client`'s `LoaderArgs` (here they are opaque to the agnostic core). */ export interface LoaderContext { readonly params: Record readonly request: Request /** Alias of {@link request} - mirrors a route handler's `c.req` so the same name works in both. */ readonly req: Request readonly api: unknown /** Platform bindings forwarded from the request `c.env` (Workers env/KV/D1). Opaque here. */ readonly env: unknown /** `true` when the request carries a valid draft/preview cookie (only when `createWebApp` is given a * `draftSecret`; otherwise always `false`). Branch on it to load unpublished content for editors. */ readonly draft: boolean /** The URL search params, validated against the route's `searchSchema` (a Standard Schema) when it * declares one - failing closed to the schema's defaults - else the raw parsed query. Typed per-route * via `@nifrajs/client`'s `LoaderArgs`. */ readonly search: Record } /** A route's optional data loader: params/request in, data out. */ export type Loader = (ctx: LoaderContext) => unknown | Promise /** * A route's optional mutation, run on POST. Shares the loader context (params/request/api); * read the form/JSON body off `request`. Returns either a control-flow value (a `redirect()`, a * `status(...)` render, or a hand-rolled `Response` - all passed straight through) or data, * surfaced to the page component as `actionData`. */ export type Action = (ctx: LoaderContext) => unknown | Promise /** The body a client action may prepare for the server action. The server must validate it again. */ export type ClientRequestBody = NonNullable /** Safe, client-visible context for a client loader. It intentionally exposes no Request or headers. */ export interface ClientLoaderArgs { readonly url: string readonly params: Readonly> readonly signal: AbortSignal /** Lazily obtains the server loader result; repeated calls share one per-navigation request. */ readonly serverLoader: () => Promise } /** A client-only post-hydration data loader. Its return value replaces the route's rendered data. */ export type ClientLoader = (args: ClientLoaderArgs) => unknown | Promise /** Safe, client-visible context for a client action. No secrets or raw request headers cross this seam. */ export interface ClientActionArgs extends ClientLoaderArgs { readonly body: ClientRequestBody } /** Client action preparation. `body` is sent as untrusted input; `optimisticData` is never sent. */ export interface ClientActionResult { readonly body?: ClientRequestBody readonly optimisticData?: unknown } /** A client-only action wrapper; it never replaces the server action. */ export type ClientAction = ( args: ClientActionArgs, ) => ClientActionResult | void | Promise /** Client hooks populated by the generated route entry after a route chunk loads. */ export interface ClientRouteHooks { readonly clientLoader?: ClientLoader readonly clientAction?: ClientAction /** Neutral boundary descriptors needed for soft-navigation interception; no server loader crosses. */ readonly boundaries?: readonly BoundaryDescriptor[] } /** * One `` tag's attributes for a route/layout's `meta.link`. The common HTML `` attributes * are spelled out and **optional** so a typed partial like `{ rel, href, hreflang }` is assignable - * the previous `Record` required *every* value to be a present string, which rejected * exactly that idiomatic shape (the bug this fixes). Standard attributes are explicit and the template * index signature admits inert `data-*` metadata without opening executable `on*` attributes. `boolean` * covers `disabled` (rendered bare when `true`, omitted when `false`), and `undefined` lets a caller * spread in a conditionally absent attribute. SSR and soft navigation apply one runtime allowlist too, * so a cast or untyped route cannot widen the injection surface. */ export interface LinkDescriptor { readonly rel?: string readonly href?: string readonly hreflang?: string readonly crossorigin?: string readonly media?: string readonly nonce?: string readonly sizes?: string readonly type?: string readonly as?: string readonly integrity?: string readonly referrerpolicy?: string readonly fetchpriority?: string readonly title?: string readonly imagesrcset?: string readonly imagesizes?: string readonly color?: string readonly disabled?: boolean readonly [attr: `data-${string}`]: string | undefined } /** One managed `` tag. Standard attributes and inert `data-*` metadata only. */ export interface MetaDescriptor { readonly charset?: string readonly content?: string readonly "http-equiv"?: string readonly itemprop?: string readonly media?: string readonly name?: string readonly property?: string readonly scheme?: string readonly [attr: `data-${string}`]: string | undefined } /** One `