Skip to content

API

@ogygia/router

The server router, with routes, pages, layouts, loads, actions and endpoints.

On this page

This page is generated from the package’s own .d.ts files on every build, so the signatures, descriptions and examples below always match the source.

import { … } from '@ogygia/router';

Exports action · anonymousVisitor · api · compile · DELETE · error · fail · GET · is_http_error · is_redirect · layout · load · match_path · page · PATCH · POST · PUT · redirect · routes · when · ApiError · ActionDef · ActionFailure · AnonymousVisitorOptions · ApiClient · ApiClientOptions · CompiledPattern · ComponentPick · Ctx · Endpoint · LayoutDef · LoadDef · PageDef · PageServer · Router · RoutesOptions · StandardSchemaV1 · VerbEntry · Action · AnyComponent · Handler · HrefArgs · HrefParams · InferMap · InferOutput · Load · Params · RouteTable · Simplify

action

function

action(fn) / action(pattern, fn) — one form action. Called once on POST (no memoization). In a page's actions, action(fn) takes the page's params like load(fn) does.

function action<Ret, Prm = LooseParams, Srch = Record<string, string>>(run: (c: Ctx<Prm, Srch>) => Ret | Promise<Ret>): ActionDef<Awaited<Ret>, string, Prm, Srch>;
function action<Ret, P extends string, Srch = Record<string, string>>(pattern: P, run: (c: Ctx<Params<P>, Srch>) => Ret | Promise<Ret>): ActionDef<Awaited<Ret>, P, Params<P>, Srch>;

anonymousVisitor

function

function anonymousVisitor(opts?: AnonymousVisitorOptions): (c: Ctx) => Visitor | undefined;

api

function

function api<App>(base: string, opts?: ApiClientOptions): ApiClient<App>;

compile

function

function compile(pattern: string): CompiledPattern;

DELETE

function

DELETE(handler) — a verb wrapper for a DELETE slot (no body schema).

function DELETE<P = LooseParams, R = unknown>(handler: (c: Ctx<P>) => R): VerbEntry<Awaited<R>, undefined, P>;

error

function

Throw an HTTP error from a load / action / handler — Kit's error(404, 'Not found'), verbatim. On a page branch the nearest error boundary renders it; on an endpoint it becomes JSON.

function error(status: number, message?: string): never;

fail

function

Return an action validation failure — Kit's fail(400, { incorrect: true }). The router re-renders the page with form = data and the given status. RETURNED, not thrown (Kit's contract).

function fail<T extends Record<string, unknown>>(status: number, data: T): ActionFailure<T>;

GET

function

GET(handler) — a verb wrapper for a GET slot (no body, so no schema). Inside a table its c.params are the key's; a bare { GET: (c) => … } is the same thing — the wrapper exists for symmetry with the body verbs.

function GET<P = LooseParams, R = unknown>(handler: (c: Ctx<P>) => R): VerbEntry<Awaited<R>, undefined, P>;

is_http_error

function

const is_http_error: (x: unknown) => x is HttpError

is_redirect

function

const is_redirect: (x: unknown) => x is Redirect

layout

function

layout(name, Component, { load?, error?, config? }) — a named table→table wrapper. Applying it PREPENDS this layout to every entry's chain and returns a table with the SAME KEYS, so ...admin({...}) spreads the tagged entries into the parent (a wrapped table nested in another accumulates the chain — Kit's nested layouts). load may be a load() wrapper or a bare typed fn. The name keys the layout's { data, children } type as App['(name)']; duplicate names are a build error.

function layout<Name extends string, S extends {
  load?: LoadInput;
  error?: AnyComponent;
  config?: RouteConfig;
}>(name: Name, component: AnyComponent, opts?: S): LayoutFn<LayoutDef<Name, S["load"]>>;

load

function

load(fn) / load(pattern, fn). The returned function memoizes on c (run once per request); the dispatcher's concurrent branch-load pass and any await other_load(c) share the same promise. In a page's load slot, load(fn) takes the page's params through its contextual return type (like a verb wrapper); anywhere else they are loose.

function load<Data, Prm = LooseParams, Srch = Record<string, string>>(run: (c: Ctx<Prm, Srch>) => Data | Promise<Data>): LoadDef<Awaited<Data>, string, Prm, Srch>;
function load<Data, P extends string, Srch = Record<string, string>>(pattern: P, run: (c: Ctx<Params<P>, Srch>) => Data | Promise<Data>): LoadDef<Awaited<Data>, P, Params<P>, Srch>;

match_path

function

First (most-specific) match in a pre-sorted list, or null.

function match_path(sorted: CompiledPattern[], pathname: string): PathMatch | null;

page

function

page(Component) / page(Component, { params?, search?, load?, actions? }). The second arg mirrors +page.server.ts; params/search are input schemas (typed into c + $infer), load/actions are behaviors (wrappers or bare typed functions — see Load / Action). The component slot also takes a flag.pick(), a PageSlotResolver, or a streamed-page generator (StreamSlot).

Inside a table, c.params in load and actions is the KEY's: P comes from the contextual return type (PageFor), like a verb wrapper's. A params schema wins over the key, and a search schema types c.search. Outside a table P stays loose. Ld/A lead with NO default: a slot's contextual type is then its constraint (the typed c) — a default would win outside a table and leave c untyped. An unwritten slot infers its constraint, which keeps the | undefined $infer reads as "no load" / "no actions".

function page<Ld extends PageLoad<PageParams<P, PS>, PageSearch<SS>> | undefined, A extends Record<string, PageAction<PageParams<P, PS>, PageSearch<SS>>> | undefined, P = LooseParams, PS extends StandardSchemaV1 | undefined = undefined, SS extends StandardSchemaV1 | undefined = undefined>(component: AnyComponent | ComponentPick | PageSlotResolver | StreamSlot, server?: PageServer<PS, SS, Ld, A>): PageDef<readonly [], Ld, A, PS, SS, P>;

PATCH

function

PATCH(handler) / PATCH(bodySchema, handler).

function PATCH<P = LooseParams, R = unknown>(handler: (c: Ctx<P>) => R): VerbEntry<Awaited<R>, undefined, P>;
function PATCH<Sch extends StandardSchemaV1, P = LooseParams, R = unknown>(schema: Sch, handler: (c: Ctx<P, Record<string, string>, InferOutput<Sch>>) => R): VerbEntry<Awaited<R>, InferOutput<Sch>, P>;

POST

function

POST(handler) / POST(bodySchema, handler) — a POST slot. The schema validates the JSON body into c.input (400 on failure), like a command RF's typed argument.

function POST<P = LooseParams, R = unknown>(handler: (c: Ctx<P>) => R): VerbEntry<Awaited<R>, undefined, P>;
function POST<Sch extends StandardSchemaV1, P = LooseParams, R = unknown>(schema: Sch, handler: (c: Ctx<P, Record<string, string>, InferOutput<Sch>>) => R): VerbEntry<Awaited<R>, InferOutput<Sch>, P>;

PUT

function

PUT(handler) / PUT(bodySchema, handler).

function PUT<P = LooseParams, R = unknown>(handler: (c: Ctx<P>) => R): VerbEntry<Awaited<R>, undefined, P>;
function PUT<Sch extends StandardSchemaV1, P = LooseParams, R = unknown>(schema: Sch, handler: (c: Ctx<P, Record<string, string>, InferOutput<Sch>>) => R): VerbEntry<Awaited<R>, InferOutput<Sch>, P>;

redirect

function

Throw a redirect from a load / action / handler — Kit's redirect(303, '/login'), verbatim.

function redirect(status: number, location: string | URL): never;

routes

function

function routes<const T extends KeyedTable<T>>(typed: T, opts?: RoutesOptions): Router<T>;

when

function

Flag-gate a table entry — page or endpoint. OFF means the route DOES NOT EXIST for that request: 404 (the app's error page) under an owned base, fall-through to the rest of the app without one. The gate is any (c) => boolean — a boolean flag() slots in directly, so a staged rollout of a whole page, a beta route, or an endpoint kill switch is one wrapper:

'/checkout-v2': when(checkoutV2, page(NewCheckout, { load })),
'/api/export':  when(exportsFlag, { GET: GET(export_csv) }),
'/cms/[...rest]': when(cmsRollout, mount(cms)),          // per-cohort fragment rollout

Decided AFTER decide({ source }) primes, so a vendor kill switch gates routes too; the decision self-registers for federation carry like any flag read. Type-transparent: $infer sees the entry exactly as if unwrapped.

function when<T extends object>(gate: (c: Ctx) => boolean, entry: T): T;

ApiError

class

class ApiError extends Error {/*…*/}

ApiError.status

readonly status: number;

ApiError.body

readonly body: unknown;

ActionDef

interface

A form action def (symmetric with load): action(fn) / action(pattern, fn). Returns fail(...) for validation, throws redirect() on success, or returns data for the form prop — Kit's action contract. pattern types c.params; $infer reads Ret into the form prop. Prm/Srch are the params and search its c was typed with (see LoadDef).

interface ActionDef<Ret = unknown, P extends string = string, Prm = LooseParams, Srch = Record<string, string>> {/*…*/}

ActionDef.pattern

readonly pattern: P | undefined;

ActionFailure

interface

interface ActionFailure<T = Record<string, unknown>> {/*…*/}

ActionFailure.[ACTION_FAILURE]

[ACTION_FAILURE]: true;

ActionFailure.status

status: number;

ActionFailure.data

data: T;

AnonymousVisitorOptions

interface

interface AnonymousVisitorOptions {/*…*/}

AnonymousVisitorOptions.cookie

Cookie name (default og-vid — the same one experiment()'s fallback chain reads).

cookie?: string;

AnonymousVisitorOptions.days

Cookie lifetime in days (default 365).

days?: number;

ApiClient

interface

interface ApiClient<App> {/*…*/}

ApiClient.get

get<K extends KeysWith<App, "get">>(path: K, ...args: CallArgs<K>): Promise<OutOf<App, K, "get">>;

ApiClient.delete

delete<K extends KeysWith<App, "delete">>(path: K, ...args: CallArgs<K>): Promise<OutOf<App, K, "delete">>;

ApiClient.post

post<K extends KeysWith<App, "post">>(path: K, ...args: CallArgs<K, InOf<App, K, "post">>): Promise<OutOf<App, K, "post">>;

ApiClient.put

put<K extends KeysWith<App, "put">>(path: K, ...args: CallArgs<K, InOf<App, K, "put">>): Promise<OutOf<App, K, "put">>;

ApiClient.patch

patch<K extends KeysWith<App, "patch">>(path: K, ...args: CallArgs<K, InOf<App, K, "patch">>): Promise<OutOf<App, K, "patch">>;

ApiClientOptions

interface

interface ApiClientOptions {/*…*/}

ApiClientOptions.headers

Extra headers on every call (auth tokens, signed hops — compose with sign_headers).

headers?: Record<string, string>;

ApiClientOptions.fetch

Custom fetch (a Kit event's cookie-forwarding fetch, a test stub). Default: global.

fetch?: typeof fetch;

CompiledPattern

interface

interface CompiledPattern {/*…*/}

CompiledPattern.pattern

the original table key, e.g. '/docs/[slug]' — also the route.id we expose

pattern: string;

CompiledPattern.regex

regex: RegExp;

CompiledPattern.params

params: ParamSpec[];

CompiledPattern.score

higher = more specific; used to order overlapping matches (static beats dynamic)

score: number;

ComponentPick

interface

A per-request component chooser for page() — what flag.pick({ … }) returns (page(hero.pick({ control: A, bold: B }))). Branded, not a bare function, because Svelte 5 components ARE functions. Structural: the flags package builds the same shape without importing the router.

interface ComponentPick {/*…*/}

Ctx

interface

interface Ctx<P = Record<string, string | undefined>, S = Record<string, string>, I = undefined> {/*…*/}

Ctx.params

Path params — pattern-typed, or the params schema's coerced output (404 on bad input).

params: Simplify<P>;

Ctx.search

Query params — raw { [k]: string }, or the search schema's typed output (400 on bad input).

search: Simplify<S>;

Ctx.input

A body-verb endpoint's validated JSON body (POST/PUT/PATCH schema); else undefined.

input: I;

Ctx.url

url: URL;

Ctx.request

request: Request;

Ctx.route

The matched route pattern, e.g. /docs/[slug] (Kit's route.id).

route: {
    id: string;
  };

Ctx.fetch

fetch: typeof fetch;

Ctx.cookies

cookies?: RequestEvent["cookies"];

Ctx.locals

locals?: RequestEvent["locals"];

Ctx.setHeaders

Set response headers from a load/action/handler. ALWAYS works: the router applies these to the Response IT builds — Kit's own event.setHeaders only affects resolve()-built responses, and a router-rendered document bypasses resolve (found when a mount load's Server-Timing silently vanished).

setHeaders?: RequestEvent["setHeaders"];

Ctx.collected_headers

collected_headers?: Map<string, string>;

Ctx.visitor

WHO is this request? THE identity — derived ONCE. Precedence: signature-bound claims from an upstream shell (fragment federation — unforgeable, they arrived inside an Ed25519 signature) → the table's visitor resolver (routes(table, { visitor })) → undefined. Everything downstream READS this instead of re-deriving: experiments stick on it, mounts sign it onward, loads personalize with it.

readonly visitor?: Visitor;

Ctx.platform

platform?: Readonly<RequestEvent["platform"]>;

Ctx.getClientAddress

getClientAddress?: RequestEvent["getClientAddress"];

Ctx.event

The raw Kit event, when mounted in Kit.

event?: RequestEvent;

Ctx.json

JSON response (default application/json, no-store).

json(data: unknown, init?: ResponseInit): Response;

Ctx.redirect

Redirect Response (default 303). Prefer the throwable redirect() inside loads/actions.

redirect(location: string, status?: number): Response;

Ctx.text

Plain-text response.

text(body: string, init?: ResponseInit): Response;

Ctx.href

A rename-safe URL to a route in this router: c.href('/report/[id]', { id }).

href(pattern: string, params?: Record<string, string | number>): string;

Ctx.state

A per-request scratch bag (a guard writes, a handler reads).

state: Record<string, unknown>;

Endpoint

interface

An endpoint route entry — the +server.ts shape, as an object of uppercase verb slots. Each verb is a bare handler or a verb wrapper. Distinguished from a page by having no __ogkind.

interface Endpoint {/*…*/}

Endpoint.GET

GET?: VerbSlot;

Endpoint.DELETE

DELETE?: VerbSlot;

Endpoint.HEAD

HEAD?: VerbSlot;

Endpoint.OPTIONS

OPTIONS?: VerbSlot;

Endpoint.POST

POST?: VerbSlot;

Endpoint.PUT

PUT?: VerbSlot;

Endpoint.PATCH

PATCH?: VerbSlot;

LayoutDef

interface

A layout def, generic over its NAME and own LOAD (authored form) so $infer can key App['(name)'] and merge its data into the chains that contain it.

interface LayoutDef<Name extends string = string, Load = unknown> {/*…*/}

LayoutDef.name

readonly name: Name;

LayoutDef.component

readonly component: AnyComponent;

LayoutDef.load

readonly load: LoadDef | undefined;

LayoutDef.error

readonly error?: AnyComponent;

LayoutDef.config

config for every page under this layout (a deeper layout's or the page's own key wins)

readonly config?: RouteConfig;

LoadDef

interface

A load is a CALLABLE, memoized per request: await session_load(c) runs .run(c) once per request (the memo lives on c), so a layout and its child sharing a load never double-fetch — Kit's parent(), generalized. pattern (optional) binds c.params AND is checked against the table key at assembly. $infer reads Data. Prm/Srch are the params and search its c was typed with: the page's (its key's params or its schemas' output) when written in a page's load slot, loose elsewhere. They type the call, so a loose load (a shared one) fits any page, and its call signature matches a bare load's — which is what lets a bare (c) => … in the same slot keep its contextual type.

interface LoadDef<Data = unknown, P extends string = string, Prm = LooseParams, Srch = Record<string, string>> {/*…*/}

LoadDef.pattern

readonly pattern: P | undefined;

LoadDef.run

readonly run: (c: Ctx) => Data | Promise<Data>;

PageDef

interface

A page route def, generic over its layout chain / load / actions so $infer reads them. L is the OUTERMOST-first layout chain (empty = no chrome); the runtime prepends to it as layout(...)() wraps this entry's table. Load/Actions keep the AUTHORED form (wrapper or bare fn) for infer. KeyParams is what page() took from its table key (loose outside a table) — see PageFor.

interface PageDef<L extends readonly LayoutDef[] = readonly LayoutDef[], Load = unknown, Actions = unknown, ParamsSchema = undefined, SearchSchema = undefined, KeyParams = LooseParams> {/*…*/}

PageDef.component

The page component, a per-request ComponentPick (page(flag.pick({...}))), or a STREAMED page (page(async function* (c, data) { … }) — see StreamSlot); the dispatcher resolves it with the ctx.

readonly component: AnyComponent | ComponentPick | StreamSlot;

PageDef.load

readonly load: LoadDef | undefined;

PageDef.actions

readonly actions: Record<string, ActionDef> | undefined;

PageDef.params_schema

readonly params_schema?: StandardSchemaV1;

PageDef.search_schema

readonly search_schema?: StandardSchemaV1;

PageDef.fallback

a deferred page's loading placeholder — the component's reserved ogygiaFallback slot

readonly fallback?: AnyComponent;

PageDef.entries

prerender param sets for a dynamic pattern — see PageServer.entries

readonly entries?: PageServer["entries"];

PageDef.config

the page's own config — see PageServer.config

readonly config?: RouteConfig;

PageDef.layouts

readonly layouts: L;

PageServer

interface

The +page.server.ts-shaped second argument to page(). Always an object (ruled). Route input is declared here (params/search schemas — coerced, typed into c, shown in $infer); load and actions are the behaviors (each a wrapper or a bare typed fn). Actions read FormData themselves (Kit-identical) — no action schema; typed mutation input is a command RF, not a form action. page() instantiates it with what it infers; the bare PageServer is the loose shape.

interface PageServer<PS = StandardSchemaV1, SS = StandardSchemaV1, Ld = LoadInput, A = Record<string, ActionInput>> {/*…*/}

PageServer.params

Coerce + validate path params (garbage → 404); c.params and App['/p']['params'] = its output.

params?: PS;

PageServer.search

Coerce + validate query (bad → 400, or lenient via .default()); c.search + App['/p']['search'].

search?: SS;

PageServer.load

load?: Ld;

PageServer.actions

actions?: A;

PageServer.entries

PRERENDER param sets for a DYNAMIC pattern — Kit's per-route entries export. Each item fills the pattern (/posts/[id] + { id: '1' } → /posts/1) and joins the router's entries() crawl list; a dynamic page without this is skipped (crawled or SSR'd live).

entries?: () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;

PageServer.config

Kit's config page option, the programmatic twin of export const config = {…} in a +page.server.ts: plugins read their keys from it ({ postrender: false }). Merged like Kit's: routes(table, { config }), then each layout outermost → innermost, then the page; a later level replaces a key, top level only.

config?: RouteConfig;

Router

interface

The router value. $infer is a type-only phantom read as typeof app.$infer.

interface Router<T extends TableShape = RouteTable> {/*…*/}

Router.fetch

(request) => Response | null — null on no-match (a catchall 404s its own way).

fetch(request: Request, event?: RequestEvent): Promise<Response | null>;

Router.handle

Kit handle: dispatches, else falls through to the rest of the app.

handle(input: {
    event: RequestEvent;
    resolve: (e: RequestEvent) => Response | Promise<Response>;
  }): Promise<Response>;

Router.href

Rename-safe URL to a route in this table.

href<P extends keyof T & string>(pattern: P, ...args: HrefArgs<P>): string;

Router.entries

The catchall crawl list (static page routes) — export const entries = app.entries.

entries(): Promise<Array<{
    path: string;
  }>>;

Router.$infer

Type-only phantom map: export type App = typeof app.$infer.

readonly $infer: InferMap<T>;

RoutesOptions

interface

interface RoutesOptions {/*…*/}

RoutesOptions.base

Mount prefix, stripped before matching. Required for a library that owns a subtree.

base?: string;

RoutesOptions.slash

Trailing-slash policy. 'ignore' (default) matches both; 'never'/'always' 308-canonicalize.

slash?: "ignore" | "never" | "always";

RoutesOptions.miss

Response for an unmatched path UNDER base (default: 404). Ignored without base.

miss?: (c: Ctx) => unknown;

RoutesOptions.error

Root error boundary component (Kit's top +error.svelte) for thrown error() with no nearer layout boundary.

error?: AnyComponent;

RoutesOptions.load

A table-wide load run before EVERY route (pages and endpoints) — Kit's root +layout.server.ts load without a component. The place for a guard with no shared chrome: it throws redirect/error to gate, and its returned data merges into every page's data (outermost).

load?: LoadInput;

RoutesOptions.visitor

WHO is this request? Derived ONCE here, read everywhere as c.visitor — experiments stick on it, mounts sign it into claims, loads personalize with it. Signature-bound claims from an upstream shell (fragment federation) take precedence over this resolver.

visitor?: (c: Ctx) => Visitor | undefined;

RoutesOptions.flags

Flags to PRE-DECIDE at the table, so mounts carry their buckets even when no page on this server reads them (a shell that only ROUTES to teams still wants the visitor's world to travel). Any flag a page DOES read auto-carries without being listed here — this is just for the decide-but-don't-render case. flags: [csr].

flags?: ReadonlyArray<(c: Ctx) => unknown>;

RoutesOptions.config

Table-wide Kit config page option for every page (a layout's or a page's own key wins): plugins read their keys from it ({ postrender: false }). The handle consults this table for any request Kit's file router did not claim — keep ogygia.handle() ahead of the table in the sequence() so a stored page is served before the table runs.

config?: RouteConfig;

StandardSchemaV1

interface

The Standard Schema interface (standardschema.dev): the ~standard spec Zod / Valibot / ArkType all implement, the very thing Kit's remote functions consume. Validate a value with any schema library. Types only.

interface StandardSchemaV1<Output = unknown> {/*…*/}

StandardSchemaV1."~standard"

readonly "~standard": {
    readonly version: 1;
    readonly vendor: string;
    readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>;
    readonly types?: {
      readonly output: Output;
    };
  };

VerbEntry

interface

A verb entry produced by the GET/POST/… value wrappers — a handler plus (for body verbs) a body schema (POST(schema, fn)). Symmetric with load()/action(). Out/In/P are PHANTOM types for $infer + the typed api() client: a PLAIN return is the endpoint's JSON payload (finalize() serializes it), a Response return erases to unknown; In is the body schema's output; P the params the handler was typed with (its table key's, inside a table).

interface VerbEntry<Out = unknown, In = undefined, P = unknown> {/*…*/}

VerbEntry.handler

readonly handler: (c: Ctx) => unknown;

VerbEntry.bodySchema

readonly bodySchema?: StandardSchemaV1;

Action

type

TYPE helper for a pulled-out or inline action (mirrors Load).

type Action<P extends string = string> = (c: Ctx<Params<P>>) => unknown;

AnyComponent

type

Any component. Props are a component's INPUT, so never props is the one shape every component's props can stand in for: every Component<Props> is assignable to this.

type AnyComponent = Component<never>;

Handler

type

TYPE helper for an endpoint handler written OUTSIDE its table. In (a schema output) types c.input — the validated JSON body of a POST/PUT/PATCH. Verb-agnostic (the method doesn't change the ctx). Check against it with satisfies, which keeps the handler's own return type (the payload $infer and api() read): const getPost = ((c) => …) satisfies Handler<'/p/[id]'>. Inside a table nothing is needed: the key types the handler.

type Handler<P extends string = string, In = undefined> = (c: Ctx<Params<P>, Record<string, string>, In>) => unknown;

HrefArgs

type

href args: params are REQUIRED when the pattern has any, omittable when it has none.

type HrefArgs<P extends string> = {} extends Params<P> ? [params?: HrefParams<P>] : [params: HrefParams<P>];

HrefParams

type

The params object href accepts for a pattern — every param, as a string or number.

type HrefParams<P extends string> = { [K in keyof Params<P>]: string | number; };

InferMap

type

The full $infer map: page paths ⊕ endpoint paths ⊕ layout (name)s.

type InferMap<T extends TableShape> = Simplify<PageEntries<T> & EndpointEntries<T> & AllLayoutEntries<T>>;

InferOutput

type

type InferOutput<S> = S extends StandardSchemaV1$1<infer O> ? O : never;

Load

type

TYPE helper for a pulled-out or inline load — annotate a bare function so its c (and c.params) are typed without the load() value wrapper: const l: Load<'/docs/[slug]'> = async (c) => …. Pass it straight to page(Comp, { load: l }). Use the load() VALUE wrapper only when a load must be SHARED across loads (await other(c) — memoized per request).

type Load<P extends string = string> = (c: Ctx<Params<P>>) => unknown;

Params

type

type Params<P extends string> = Simplify<ParamsAcc<P>>;

RouteTable

type

type RouteTable = Record<string, RouteDef>;

Simplify

type

Route params derived from the pattern string via template literals — /docs/[slug] → { slug: string }, /[[lang]] → { lang?: string }, /[...rest] → { rest: string }, /[id].json → { id: string }. No codegen.

type Simplify<T> = { [K in keyof T]: T[K]; } & {};