UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

181 lines (180 loc) 8.44 kB
import React from "react"; import { RequestInfo } from "../requestInfo/types"; import type { DocumentProps, LayoutProps } from "./types.js"; type MaybePromise<T> = T | Promise<T>; type BivariantRouteHandler<T extends RequestInfo, R> = { bivarianceHack(requestInfo: T): R; }["bivarianceHack"]; export type RouteMiddleware<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<React.JSX.Element | Response | void>>; export type ExceptHandler<T extends RequestInfo = RequestInfo> = { __rwExcept: true; handler: (error: unknown, requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>; }; type RouteFunction<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<Response>>; type RouteComponent<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<React.JSX.Element | Response | void>>; type RouteHandler<T extends RequestInfo = RequestInfo> = RouteFunction<T> | RouteComponent<T> | readonly [...RouteMiddleware<T>[], RouteFunction<T> | RouteComponent<T>]; declare const METHOD_VERBS: readonly ["delete", "get", "head", "patch", "post", "put"]; export type MethodVerb = (typeof METHOD_VERBS)[number]; export type MethodHandlers<T extends RequestInfo = RequestInfo> = { [K in MethodVerb]?: RouteHandler<T>; } & { config?: { disable405?: true; disableOptions?: true; }; custom?: { [method: string]: RouteHandler<T>; }; }; export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<string, T> | ExceptHandler<T> | readonly Route<T>[]; type NormalizedRouteDefinition<T extends RequestInfo = RequestInfo> = { path: string; handler: RouteHandler<T> | MethodHandlers<T>; layouts?: React.FC<LayoutProps<T>>[]; }; export type RouteDefinition<Path extends string = string, T extends RequestInfo = RequestInfo> = NormalizedRouteDefinition<T> & { readonly __rwPath?: Path; }; type TrimTrailingSlash<S extends string> = S extends `${infer Head}/` ? TrimTrailingSlash<Head> : S; type TrimLeadingSlash<S extends string> = S extends `/${infer Rest}` ? TrimLeadingSlash<Rest> : S; type NormalizePrefix<Prefix extends string> = TrimTrailingSlash<TrimLeadingSlash<Prefix>> extends "" ? "" : `/${TrimTrailingSlash<TrimLeadingSlash<Prefix>>}`; type NormalizePath<Path extends string> = TrimTrailingSlash<Path> extends "/" ? "/" : `/${TrimTrailingSlash<TrimLeadingSlash<Path>>}`; type JoinPaths<Prefix extends string, Path extends string> = NormalizePrefix<Prefix> extends "" ? NormalizePath<Path> : Path extends "/" ? NormalizePrefix<Prefix> : `${NormalizePrefix<Prefix>}${NormalizePath<Path>}`; type PrefixedRouteValue<Prefix extends string, Value> = Value extends RouteDefinition<infer Path, infer Req> ? RouteDefinition<JoinPaths<Prefix, Path>, Req> : Value extends ExceptHandler<any> ? Value : Value extends readonly Route<any>[] ? PrefixedRouteArray<Prefix, Value> : Value; type PrefixedRouteArray<Prefix extends string, Routes extends readonly Route<any>[]> = Routes extends readonly [] ? [] : Routes extends readonly [infer Head, ...infer Tail] ? readonly [ PrefixedRouteValue<Prefix, Head>, ...PrefixedRouteArray<Prefix, Tail extends readonly Route<any>[] ? Tail : []> ] : ReadonlyArray<PrefixedRouteValue<Prefix, Routes[number]>>; export declare function matchPath<T extends RequestInfo = RequestInfo>(routePath: string, requestPath: string): T["params"] | null; export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: readonly Route<T>[]): { routes: Route<T>[]; handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }: { request: Request; renderPage: (requestInfo: T, Page: React.FC, onError: (error: unknown) => void) => Promise<Response>; getRequestInfo: () => T; onError: (error: unknown) => void; runWithRequestInfoOverrides: <Result>(overrides: Partial<T>, fn: () => Promise<Result>) => Promise<Result>; rscActionHandler: (request: Request) => Promise<unknown>; }) => Response | Promise<Response>; }; /** * Defines a route handler for a path pattern. * * Supports three types of path patterns: * - Static: /about, /contact * - Parameters: /users/:id, /posts/:postId/edit * - Wildcards: /files/\*, /api/\*\/download * * @example * // Static route * route("/about", () => <AboutPage />) * * @example * // Route with parameters * route("/users/:id", ({ params }) => { * return <UserProfile userId={params.id} /> * }) * * @example * // Route with wildcards * route("/files/*", ({ params }) => { * const filePath = params.$0 * return <FileViewer path={filePath} /> * }) * * @example * // Method-based routing * route("/api/users", { * get: () => Response.json(users), * post: ({ request }) => Response.json({ status: "created" }, { status: 201 }), * delete: () => new Response(null, { status: 204 }), * }) * * @example * // Route with middleware array * route("/admin", [isAuthenticated, isAdmin, () => <AdminDashboard />]) */ export declare function route<Path extends string, T extends RequestInfo = RequestInfo>(path: Path, handler: RouteHandler<T> | MethodHandlers<T>): RouteDefinition<NormalizePath<Path>, T>; /** * Defines a route handler for the root path "/". * * @example * // Homepage * index(() => <HomePage />) * * @example * // With middleware * index([logRequest, () => <HomePage />]) */ export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<"/", T>; /** * Defines an error handler that catches errors from routes, middleware, and RSC actions. * * @example * // Global error handler * except((error, requestInfo) => { * console.error(error); * return new Response("Internal Server Error", { status: 500 }); * }) * * @example * // Error handler that returns a React component * except((error) => { * return <ErrorPage error={error} />; * }) */ export declare function except<T extends RequestInfo = RequestInfo>(handler: (error: unknown, requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>): ExceptHandler<T>; export declare function prefix<Prefix extends string, T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(prefixPath: Prefix, routes: Routes): PrefixedRouteArray<Prefix, Routes>; export declare const wrapHandlerToThrowResponses: <T extends RequestInfo = RequestInfo>(handler: RouteFunction<T> | RouteComponent<T>) => RouteHandler<T>; /** * Wraps routes with a layout component. * * @example * // Define a layout component * function BlogLayout({ children }: { children?: React.ReactNode }) { * return ( * <div> * <nav>Blog Navigation</nav> * <main>{children}</main> * </div> * ) * } * * // Apply layout to routes * const blogRoutes = layout(BlogLayout, [ * route("/", () => <BlogIndex />), * route("/post/:id", ({ params }) => <BlogPost id={params.id} />), * ]) */ export declare function layout<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(LayoutComponent: React.FC<LayoutProps<T>>, routes: Routes): Routes; /** * Wraps routes with a Document component and configures rendering options. * * @param options.rscPayload - Toggle the RSC payload that's appended to the Document. Disabling this will mean that interactivity no longer works. * @param options.ssr - Disable sever side rendering for all these routes. This only allow client side rendering, which requires `rscPayload` to be enabled. * * @example * // Basic usage * defineApp([ * render(Document, [ * route("/", () => <HomePage />), * route("/about", () => <AboutPage />), * ]), * ]) * * @example * // With custom rendering options * render(Document, [ * route("/", () => <HomePage />), * ], { * rscPayload: true, * ssr: true, * }) */ type RenderedRoutes<T extends RequestInfo, Routes extends readonly Route<T>[]> = readonly [RouteMiddleware<T>, ...Routes]; export declare function render<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(Document: React.FC<DocumentProps<T>>, routes: Routes, options?: { rscPayload?: boolean; ssr?: boolean; }): RenderedRoutes<T, Routes>; export declare const isClientReference: (value: any) => boolean; export {};