import type { ReactNode } from "react"; import type { ErrorBoundaryHandler, ExtractParams, Handler, HandlerContext, LoaderDefinition, MiddlewareFn, NotFoundBoundaryHandler, PartialCacheOptions, ShouldRevalidateFn, TransitionConfig, } from "../types.js"; import type { AllUseItems, TypedLayoutItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ResponseRouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, WhenItem, TypedCacheItem, TransitionItem, TypedTransitionItem, TypedRouteItem, TypedIncludeItem, UseItems, } from "../route-types.js"; import type { SearchSchema } from "../search-params.js"; import type { PrerenderHandlerDefinition, PassthroughHandlerDefinition, } from "../prerender.js"; import type { StaticHandlerDefinition } from "../static-handler.js"; import type { InterceptWhenFn } from "../server/context"; import type { ResponseHandler, ResponseHandlerContext, TextResponseHandler, } from "./response-types.js"; import type { UnnamedRoute, LocalOnlyInclude, PathOptions, UrlPatterns, IncludeOptions, } from "./pattern-types.js"; import type { ExtractRoutes, ExtractResponses } from "./type-extraction.js"; /** * Base path function signature for defining routes with URL patterns. */ export type PathFn = < const TPattern extends string, const TName extends string = UnnamedRoute, const TSearch extends SearchSchema = {}, TParams extends Record = ExtractParams, >( pattern: TPattern, handler: | ReactNode | (( ctx: HandlerContext, ) => ReactNode | Promise | Response | Promise) | PrerenderHandlerDefinition | PassthroughHandlerDefinition | StaticHandlerDefinition, optionsOrUse?: PathOptions | (() => UseItems), use?: () => UseItems, // Generic handler bypass: when handler uses index-signature params // (e.g. Handler>), skip the biconditional. // `string extends keyof TParams` is true for index signatures, // false for concrete params ({id: string}) and empty ({}). // // Subset check: pattern params must be assignable to handler params, // but handler can have MORE params (e.g. from parent include() prefix). // This allows Prerender<"locale.detail"> with {locale, slug} to mount // on path("/blog/:slug") where the pattern only declares {slug}. ) => string extends keyof TParams ? TypedRouteItem : TParams extends ExtractParams ? TypedRouteItem : { __error: `Handler params do not match pattern "${TPattern}"` }; /** * Path function for response routes that must return Response (image, stream, any). * Handler must return Response, not ReactNode. Uses lighter ResponseHandlerContext. * Use items restricted to middleware() and cache() only. */ export type ResponsePathFn = < const TPattern extends string, const TName extends string = UnnamedRoute, const TSearch extends SearchSchema = {}, >( pattern: TPattern, handler: ResponseHandler, TEnv>, optionsOrUse?: | PathOptions | (() => UseItems), use?: () => UseItems, ) => TypedRouteItem; /** * Path function for JSON response routes (path.json()). * Handler can return plain JSON-serializable values or Response. * TData is inferred from the handler's return type (excluding Response/Promise wrappers). */ export type JsonResponsePathFn = < const TPattern extends string, const TName extends string = UnnamedRoute, const TSearch extends SearchSchema = {}, TData = unknown, >( pattern: TPattern, handler: ( ctx: ResponseHandlerContext, TEnv>, ) => TData | Response | Promise, optionsOrUse?: | PathOptions | (() => UseItems), use?: () => UseItems, ) => TypedRouteItem; /** * Path function for text-based response routes (path.text(), path.html(), path.xml()). * Handler can return a string or Response. TData is always `string`. */ export type TextResponsePathFn = < const TPattern extends string, const TName extends string = UnnamedRoute, const TSearch extends SearchSchema = {}, >( pattern: TPattern, handler: TextResponseHandler, TEnv>, optionsOrUse?: | PathOptions | (() => UseItems), use?: () => UseItems, ) => TypedRouteItem; /** * Base include function signature. */ export type IncludeFn = < TRoutes extends Record, const TUrlPrefix extends string, const TNamePrefix extends string = LocalOnlyInclude, TResponses extends Record = Record, >( prefix: TUrlPrefix, patterns: UrlPatterns, options?: IncludeOptions, ) => TypedIncludeItem; export type PathHelpers = { /** * Define a route with URL pattern at definition site * * @example * ```typescript * // Pattern and component only * path("/about", AboutPage) * * // With options * path("/:slug", PostPage, { name: "post" }) * * // With children (loaders, middleware, etc.) * path("/:slug", PostPage, { name: "post" }, () => [ * loader(PostLoader), * ]) * ``` */ path: PathFn & { json: JsonResponsePathFn; text: TextResponsePathFn; html: TextResponsePathFn; xml: TextResponsePathFn; md: TextResponsePathFn; image: ResponsePathFn; stream: ResponsePathFn; any: ResponsePathFn; }; /** * Define a layout that wraps child routes */ layout: { ( component: ReactNode | Handler | StaticHandlerDefinition, ): TypedLayoutItem<{}, {}>; < const TChildren extends readonly ( | LayoutUseItem | readonly LayoutUseItem[] )[], >( component: ReactNode | Handler | StaticHandlerDefinition, use: () => TChildren, ): TypedLayoutItem, ExtractResponses>; }; /** * Include nested URL patterns under a URL prefix. * * The `name` option controls how child route names appear in the * global route map and generated types: * * ```typescript * // Named — children become "blog.index", "blog.post", etc. * // Visible in generated types and globally reversible. * include("/blog", blogPatterns, { name: "blog" }) * * // Flattened — children merge into the parent namespace as-is. * // Equivalent to defining those routes inline at the include site. * include("/blog", blogPatterns, { name: "" }) * * // Local-only (default) — children are scoped privately. * // Hidden from generated types and global reverse resolution. * // Only dot-local reverse (reverse(".child")) works inside. * include("/blog", blogPatterns) * ``` */ include: IncludeFn; /** * Define parallel routes that render simultaneously in named slots. * * A slot value can be a Handler / ReactNode / StaticHandlerDefinition * (legacy form, broadcast use applies to every slot) or a slot descriptor * `{ handler, use? }` whose `use` is scoped to that slot only. Per-slot * merge order is `handler.use` → shared `use` → slot-local `use`, with * narrowest scope winning for last-write-wins items like `loading()`. */ parallel: < TSlots extends Record< `@${string}`, | Handler | ReactNode | StaticHandlerDefinition | { handler: | Handler | ReactNode | StaticHandlerDefinition; use?: () => ParallelUseItem[]; } >, >( slots: TSlots, use?: () => ParallelUseItem[], ) => ParallelItem; /** * Define an intercepting route for soft navigation * Note: routeName must match a named path() in this urlpatterns */ intercept: keyof Rango.GeneratedRouteMap extends never ? ( slotName: `@${string}`, routeName: string, handler: ReactNode | Handler, use?: () => InterceptUseItem[], ) => InterceptItem : ( slotName: `@${string}`, routeName: (keyof Rango.GeneratedRouteMap & string) | `.${string}`, handler: ReactNode | Handler, use?: () => InterceptUseItem[], ) => InterceptItem; /** * Attach middleware to the current route/layout, or wrap child segments */ middleware: { (fn: MiddlewareFn): MiddlewareItem; ( fn: MiddlewareFn, children: () => UseItems, ): MiddlewareItem; (fns: MiddlewareFn[]): MiddlewareItem; ( fns: MiddlewareFn[], children: () => UseItems, ): MiddlewareItem; }; /** * Control when a segment should revalidate during navigation */ revalidate: (fn: ShouldRevalidateFn) => RevalidateItem; /** * Attach a data loader to the current route/layout */ loader: ( loaderDef: LoaderDefinition, use?: () => LoaderUseItem[], ) => LoaderItem; /** * Attach a loading component to the current route/layout */ loading: ( component: ReactNode | (() => ReactNode), options?: { ssr?: boolean }, ) => LoadingItem; /** * Attach an error boundary to catch errors in this segment */ errorBoundary: ( fallback: ReactNode | ErrorBoundaryHandler, ) => ErrorBoundaryItem; /** * Attach a not-found boundary to handle notFound() calls */ notFoundBoundary: ( fallback: ReactNode | NotFoundBoundaryHandler, ) => NotFoundBoundaryItem; /** * Define a condition for when an intercept should activate */ when: (fn: InterceptWhenFn) => WhenItem; /** * Define cache configuration for segments */ cache: { (): TypedCacheItem<{}, {}>; ( children: () => TChildren, ): TypedCacheItem, ExtractResponses>; (options: PartialCacheOptions | false): TypedCacheItem<{}, {}>; ( options: PartialCacheOptions | false, use: () => TChildren, ): TypedCacheItem, ExtractResponses>; }; /** * Attach a ViewTransition boundary to the current segment or a group of routes */ transition: { (): TransitionItem; (config: TransitionConfig): TransitionItem; ( children: () => TChildren, ): TypedTransitionItem< ExtractRoutes, ExtractResponses >; ( config: TransitionConfig, children: () => TChildren, ): TypedTransitionItem< ExtractRoutes, ExtractResponses >; }; };