import type { FrameworkAdapter } from "./framework.js"; import type { ContentSnapshot } from "./snapshot.js"; /** * Heading metadata emitted by the MDX `headings` export (T4). Cross-ref: * `crates/zfb-content/src/mdx_jsx_emit.rs`. Optional on the page-module * shape — non-MDX pages won't carry it. */ export interface PageHeading { readonly depth: number; readonly slug: string; readonly text: string; } /** * The shape every page module must export. * * - `default`: the JSX page component. Called with the props returned by * `getStaticProps` (if exported) or the `props` from the matching * `paths()` entry (for dynamic routes). The return value is fed straight * to the framework adapter's `renderToString`. A dynamic route with no * `paths()` export is called with `{ params: urlParams }` instead of * `{}` — see `createPageRouter`'s JSDoc for the full componentInput * derivation table. * - `prerender`: literal `false` opts a route OUT of build-time SSG (T5 * contract). The page router still serves it under the embedded V8 host * so dev mode behaves identically; SSG callers filter the route list before * driving the renderer. * - `contentType`: optional override for non-HTML routes (e.g. * `application/xml` for `rss.xml.tsx`). Default is * `text/html; charset=utf-8`. Cross-ref shipped #49. * - `headings`: optional list emitted by MDX (T4). * - `paths`: optional dynamic-route enumerator. Called at build time by * the `__paths__` synthetic endpoint to produce the concrete URL list * for this route template. May be async. Returns an array of * `{ params, props? }` objects identical in shape to the Astro/zfb * `paths()` contract. * **Evaluated once per router instance** — the result is memoised and * shared across the `/__paths__` handler and all per-page render * requests. This matches the build-time-enumerator contract: every * entry rendered within a single build sees the same paths() snapshot. * Dev mode is safe because each file-save triggers a fresh bundle, * which creates a new router instance with a clean memo. * - `getStaticProps`: optional async function for static routes that need * to fetch data at build/render time. Called once per request (before * `default`). Must return `{ props: Record }`. The * returned `props` are spread into the `default` component's props. */ export interface PageModule { readonly default: (props: Record) => unknown; readonly prerender?: boolean; readonly contentType?: string; readonly headings?: readonly PageHeading[]; readonly paths?: () => unknown[] | Promise; readonly getStaticProps?: () => Promise<{ props: Record; }>; } /** * One page registered with the router. * * `route` is a Hono path pattern (e.g. `/`, `/blog/:slug`, * `/blog/page/:page`). `module` is a thunk so the bundle can use code * splitting if it wants to — today the bundler emits everything as one * ESM file and the thunk simply returns the already-loaded module. */ export interface PageDefinition { readonly route: string; readonly module: () => Promise; } /** Options accepted by [`createPageRouter`]. */ export interface CreatePageRouterOptions { /** Pages to register. Order does not affect routing — Hono dispatches by path. */ readonly pages: readonly PageDefinition[]; /** * In-memory content snapshot. Embedded into the bundle by T3; the * router hands it to `zfb/content` so user pages reading content via * `getCollection(...)` resolve synchronously from memory. */ readonly contentSnapshot: ContentSnapshot; /** Framework adapter pinning the SSR call. */ readonly framework: FrameworkAdapter; /** * When `true`, the 500 body for render errors includes the full JS stack * trace. When `false`, only the message + route are included. When * omitted, the runtime checks `globalThis.__zfb.ssrDebug` at request time: * that flag is set by the embedded V8 build/dev host (`globals_shim.js`) * and is absent on the production Cloudflare Workers runtime. * * Default is effectively OFF for production (no flag ⇒ message + route only). * Useful for explicit injection in unit tests without global mutation. */ readonly includeErrorStack?: boolean; } /** * Fetch-handler shape returned by [`createPageRouter`]. Shaped as a plain * function (not a Hono `app`) so the consumer's contract is exactly * "Worker-style fetch handler" with no leaked framework types. */ export type PageRouter = (request: Request) => Promise; /** * Build a page router for the SSG-first architecture (ADR-005). * * Side effects: * 1. Registers `opts.contentSnapshot` with the `zfb/content` module so * `getCollection(name)` resolves from memory. Idempotent across * calls — the latest snapshot wins (matches the documented dev-mode * live-reload contract). * 2. Constructs an internal Hono app and registers an **all-methods** * handler (`app.all`, not `app.get`) per `pages[i].route`, so an SSR * route that dispatches on `request.method` — e.g. a POST endpoint * under `pages/api/` — actually reaches its handler instead of being * 404'd by the inner router. See the comment at the `app.all` call * site below. The handler imports the page module, calls * `framework.renderToString(module.default(componentInput))`, and * returns the string in a `Response` with the appropriate * `Content-Type`. (`componentInput` is the page's props object — see * the derivation table below; it is never the incoming `Request`.) * * Per-route `componentInput` (the object passed to `module.default(...)`) is * derived from the route pattern and the page module's exports: * - Dynamic route + `paths()` export: match the URL params against the * `paths()` entries and pass `{ params, ...props }` (404 on no match). * - Static route + `getStaticProps()` export: pass the returned `props`. * - Dynamic route with NO `paths()` export (e.g. a per-request SSR page * whose slugs can't be enumerated ahead of time): pass `{ params: * urlParams }` so the component still knows which URL params it is * serving, rather than being invoked with `{}`. * - Anything else (static route, no `getStaticProps`): pass `{}`. * * The returned function is a plain `(request) => Promise` so a * Worker entry point can `export default { fetch: createPageRouter(...) }` * directly. */ export declare function createPageRouter(opts: CreatePageRouterOptions): PageRouter;