rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
181 lines (180 loc) • 8.44 kB
TypeScript
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 {};