@funstack/router 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/skill-installer.mjs +2 -3
- package/dist/bin/skill-installer.mjs.map +1 -1
- package/dist/{bindRoute-C7JBYje-.mjs → bindRoute-CQ2-ruTp.mjs} +2 -3
- package/dist/{bindRoute-C7JBYje-.mjs.map → bindRoute-CQ2-ruTp.mjs.map} +1 -1
- package/dist/{bindRoute-BtT4qPKI.d.mts → bindRoute-DulMzi5X.d.mts} +1 -1
- package/dist/{bindRoute-BtT4qPKI.d.mts.map → bindRoute-DulMzi5X.d.mts.map} +1 -1
- package/dist/docs/ApiHooksPage.tsx +9 -0
- package/dist/docs/ApiTypesPage.tsx +17 -0
- package/dist/docs/LearnLoadersPage.tsx +320 -0
- package/dist/docs/index.md +1 -0
- package/dist/index.d.mts +21 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +59 -34
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +1 -1
- package/dist/server.mjs +2 -3
- package/package.json +4 -3
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
import { install } from "@funstack/skill-installer";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
|
-
|
|
6
5
|
//#region src/bin/skill-installer.ts
|
|
7
6
|
const skillDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../skills/funstack-router-knowledge");
|
|
8
7
|
console.log("Installing skill from:", skillDir);
|
|
9
8
|
await install(skillDir);
|
|
10
|
-
|
|
11
9
|
//#endregion
|
|
12
|
-
export {
|
|
10
|
+
export {};
|
|
11
|
+
|
|
13
12
|
//# sourceMappingURL=skill-installer.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-installer.mjs","names":[],"sources":["../../src/bin/skill-installer.ts"],"sourcesContent":["#! /usr/bin/env node\n\nimport { install } from \"@funstack/skill-installer\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolve, dirname } from \"node:path\";\n\n// Resolve the skill directory relative to this script's location.\n// This script is at dist/bin/skill-installer.mjs, so go up two levels\n// to reach the package root, then into skills/.\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst skillDir = resolve(__dirname, \"../../skills/funstack-router-knowledge\");\n\nconsole.log(\"Installing skill from:\", skillDir);\n\nawait install(skillDir);\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"skill-installer.mjs","names":[],"sources":["../../src/bin/skill-installer.ts"],"sourcesContent":["#! /usr/bin/env node\n\nimport { install } from \"@funstack/skill-installer\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolve, dirname } from \"node:path\";\n\n// Resolve the skill directory relative to this script's location.\n// This script is at dist/bin/skill-installer.mjs, so go up two levels\n// to reach the package root, then into skills/.\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst skillDir = resolve(__dirname, \"../../skills/funstack-router-knowledge\");\n\nconsole.log(\"Installing skill from:\", skillDir);\n\nawait install(skillDir);\n"],"mappings":";;;;;AAUA,MAAM,WAAW,QADC,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACrB,yCAAyC;AAE7E,QAAQ,IAAI,0BAA0B,SAAS;AAE/C,MAAM,QAAQ,SAAS"}
|
|
@@ -31,7 +31,6 @@ function routeState() {
|
|
|
31
31
|
return definition;
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
|
|
35
34
|
//#endregion
|
|
36
35
|
//#region src/bindRoute.ts
|
|
37
36
|
function bindRoute(partialRoute, binding) {
|
|
@@ -40,7 +39,7 @@ function bindRoute(partialRoute, binding) {
|
|
|
40
39
|
...binding
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
|
-
|
|
44
42
|
//#endregion
|
|
45
43
|
export { route as n, routeState as r, bindRoute as t };
|
|
46
|
-
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=bindRoute-CQ2-ruTp.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindRoute-C7JBYje-.mjs","names":[],"sources":["../src/route.ts","../src/bindRoute.ts"],"sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\nconst routeDefinitionSymbol = Symbol();\nconst partialRouteDefinitionSymbol = Symbol();\n\n/**\n * Extracts parameter names from a path pattern.\n * E.g., \"/users/:id/posts/:postId\" -> \"id\" | \"postId\"\n */\ntype ExtractParams<T extends string> =\n T extends `${string}:${infer Param}/${infer Rest}`\n ? Param | ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? Param\n : never;\n\n/**\n * Creates a params object type from a path pattern.\n * E.g., \"/users/:id\" -> { id: string }\n */\nexport type PathParams<T extends string> = [ExtractParams<T>] extends [never]\n ? Record<string, never>\n : { [K in ExtractParams<T>]: string };\n\n/**\n * Arguments passed to action functions.\n * The request carries the POST method and FormData body.\n */\nexport type ActionArgs<Params extends Record<string, string>> = {\n /** Extracted path parameters */\n params: Params;\n /** Request object with method POST and FormData body */\n request: Request;\n /** AbortSignal for cancellation */\n signal: AbortSignal;\n};\n\n/**\n * Arguments passed to loader functions.\n */\nexport type LoaderArgs<\n Params extends Record<string, string>,\n ActionResult = undefined,\n> = {\n /** Extracted path parameters */\n params: Params;\n /** Request object with URL and headers */\n request: Request;\n /** AbortSignal for cancellation on navigation */\n signal: AbortSignal;\n /** Result from the action, if this load was triggered by a form submission */\n actionResult: ActionResult | undefined;\n};\n\n/**\n * Props for route components without loader.\n * Includes navigation state management props.\n */\nexport interface RouteComponentProps<\n TParams extends Record<string, string>,\n TState = undefined,\n> {\n /** Extracted path parameters */\n params: TParams;\n /** Current navigation state for this route (undefined on first visit) */\n state: TState | undefined;\n /** Update navigation state for this route asynchronously via replace navigation */\n setState: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => Promise<void>;\n /** Update navigation state for this route synchronously via updateCurrentEntry */\n setStateSync: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => void;\n /** Reset navigation state to undefined asynchronously via replace navigation */\n resetState: () => Promise<void>;\n /** Reset navigation state to undefined synchronously via updateCurrentEntry */\n resetStateSync: () => void;\n /** Ephemeral navigation info (only available during navigation, not persisted) */\n info: unknown;\n /** Whether a navigation transition is pending */\n isPending: boolean;\n}\n\n/**\n * Props for route components with loader.\n * Includes data from loader and navigation state management props.\n */\nexport interface RouteComponentPropsWithData<\n TParams extends Record<string, string>,\n TData,\n TState = undefined,\n> extends RouteComponentProps<TParams, TState> {\n /** Data returned from the loader */\n data: TData;\n}\n\n/**\n * Route definition created by the `route` helper function.\n */\nexport interface OpaqueRouteDefinition {\n [routeDefinitionSymbol]: unknown;\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/**\n * Type-carrying route definition created by the `route` helper function when an `id` is provided.\n * This type carries type information for params, state, and data, enabling type-safe hooks in the future.\n */\nexport interface TypefulOpaqueRouteDefinition<\n Id extends string,\n Params extends Record<string, string>,\n State,\n Data,\n> {\n [routeDefinitionSymbol]: {\n id: Id;\n params: Params;\n state: State;\n data: Data;\n };\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/**\n * Partial route definition created by the `route` helper function when `id` is provided but `component` is not.\n * Used for two-phase route definition in RSC: Phase 1 defines id, path, loader, action;\n * Phase 2 uses `bindRoute()` to attach the component.\n * This type carries type information for params, state, and data, enabling type-safe hooks.\n */\nexport interface PartialRouteDefinition<\n Id extends string,\n Params extends Record<string, string>,\n State,\n Data,\n> {\n [partialRouteDefinitionSymbol]: {\n id: Id;\n params: Params;\n state: State;\n data: Data;\n };\n path?: string;\n}\n\n/** Extract the Id type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteId<T> =\n T extends PartialRouteDefinition<\n infer Id,\n infer _Params,\n infer _State,\n infer _Data\n >\n ? Id\n : T extends TypefulOpaqueRouteDefinition<\n infer Id,\n infer _Params,\n infer _State,\n infer _Data\n >\n ? Id\n : never;\n\n/** Extract the Params type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteParams<T> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer Params,\n infer _State,\n infer _Data\n >\n ? Params\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer _State,\n infer _Data\n >\n ? Params\n : never;\n\n/** Extract the State type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteState<T> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer _Params,\n infer State,\n infer _Data\n >\n ? State\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer State,\n infer _Data\n >\n ? State\n : never;\n\n/** Extract the Data type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteData<T> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer _Params,\n infer _State,\n infer Data\n >\n ? Data\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer _State,\n infer Data\n >\n ? Data\n : never;\n\n/** Extract the component props type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type RouteComponentPropsOf<\n T extends\n | TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >\n | PartialRouteDefinition<string, Record<string, string>, unknown, unknown>,\n> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer Params,\n infer State,\n infer Data\n >\n ? Data extends undefined\n ? RouteComponentProps<Params, State>\n : RouteComponentPropsWithData<Params, Data, State>\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer State,\n infer Data\n >\n ? Data extends undefined\n ? RouteComponentProps<Params, State>\n : RouteComponentPropsWithData<Params, Data, State>\n : never;\n\n/**\n * Any route definition defined by user.\n */\nexport type RouteDefinition =\n | OpaqueRouteDefinition\n | TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >\n | {\n path?: string;\n component?: ComponentType<object> | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n };\n\n/**\n * Route definition with action and loader.\n * Action result flows to loader via actionResult parameter.\n */\ntype RouteWithActionAndLoader<\n TPath extends string,\n TActionResult,\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => TActionResult;\n loader: (\n args: LoaderArgs<PathParams<TPath>, Awaited<TActionResult>>,\n ) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<PathParams<TPath>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition with action only (no loader).\n * Action executes as a pure side effect.\n */\ntype RouteWithActionOnly<\n TPath extends string,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => unknown;\n component?:\n | ComponentType<RouteComponentProps<PathParams<TPath>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition with loader - infers TData from loader return type.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithLoader<\n TPath extends string,\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n loader: (args: LoaderArgs<PathParams<TPath>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<PathParams<TPath>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition without loader.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithoutLoader<\n TPath extends string,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n component?:\n | ComponentType<RouteComponentProps<PathParams<TPath>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition with loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithLoader<\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n loader: (args: LoaderArgs<Record<string, never>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<Record<string, never>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition without loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithoutLoader<\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n component?:\n | ComponentType<RouteComponentProps<Record<string, never>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Partial route definition with action and loader (no component).\n * Used for two-phase route definition.\n */\ntype PartialRouteWithActionAndLoader<\n TPath extends string,\n TActionResult,\n TData,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => TActionResult;\n loader: (\n args: LoaderArgs<PathParams<TPath>, Awaited<TActionResult>>,\n ) => TData;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial route definition with action only (no component, no loader).\n */\ntype PartialRouteWithActionOnly<\n TPath extends string,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => unknown;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial route definition with loader (no component).\n */\ntype PartialRouteWithLoader<\n TPath extends string,\n TData,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n loader: (args: LoaderArgs<PathParams<TPath>>) => TData;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial route definition without loader or component.\n */\ntype PartialRouteWithoutLoader<\n TPath extends string,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial pathless route definition with loader (no component).\n */\ntype PartialPathlessRouteWithLoader<\n TData,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n loader: (args: LoaderArgs<Record<string, never>>) => TData;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial pathless route definition without loader or component.\n */\ntype PartialPathlessRouteWithoutLoader<\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Helper function for creating type-safe route definitions.\n *\n * When a loader is provided, TypeScript infers the return type and ensures\n * the component accepts a `data` prop of that type. Components always receive\n * a `params` prop with types inferred from the path pattern.\n *\n * For routes with navigation state, use `routeState<TState>()({ ... })` instead.\n *\n * @example\n * ```typescript\n * // Route with async loader\n * route({\n * path: \"users/:userId\",\n * loader: async ({ params, signal }) => {\n * const res = await fetch(`/api/users/${params.userId}`, { signal });\n * return res.json() as Promise<User>;\n * },\n * component: UserDetail, // Must accept { data: Promise<User>, params: { userId: string }, state, setState, resetState }\n * });\n *\n * // Route without loader\n * route({\n * path: \"about\",\n * component: AboutPage, // Must accept { params: {}, state, setState, resetState }\n * });\n * ```\n */\n// Partial overload: id + action + loader (no component) → PartialRouteDefinition\nexport function route<\n TId extends string,\n const TPath extends string,\n TActionResult,\n TData,\n>(\n definition: PartialRouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n TId\n > & {\n id: TId;\n },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Partial overload: id + action only (no component) → PartialRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: PartialRouteWithActionOnly<TPath, TId> & { id: TId },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Partial overload: id + pathless + loader (no component) → PartialRouteDefinition\nexport function route<TId extends string, TData>(\n definition: PartialPathlessRouteWithLoader<TData, TId> & { id: TId },\n): PartialRouteDefinition<TId, Record<string, never>, undefined, TData>;\n// Partial overload: id + pathless + no loader (no component) → PartialRouteDefinition\nexport function route<TId extends string>(\n definition: PartialPathlessRouteWithoutLoader<TId> & { id: TId },\n): PartialRouteDefinition<TId, Record<string, never>, undefined, undefined>;\n// Partial overload: id + loader (no component) → PartialRouteDefinition\nexport function route<TId extends string, const TPath extends string, TData>(\n definition: PartialRouteWithLoader<TPath, TData, TId> & { id: TId },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Partial overload: id + no loader (no component) → PartialRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: PartialRouteWithoutLoader<TPath, TId> & { id: TId },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with id + action + loader → TypefulOpaqueRouteDefinition\nexport function route<\n TId extends string,\n const TPath extends string,\n TActionResult,\n TData,\n>(\n definition: RouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n undefined,\n TId\n > & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Overload with id + action only → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: RouteWithActionOnly<TPath, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with action + loader (no id)\nexport function route<const TPath extends string, TActionResult, TData>(\n definition: RouteWithActionAndLoader<TPath, TActionResult, TData, undefined>,\n): OpaqueRouteDefinition;\n// Overload with action only (no id)\nexport function route<const TPath extends string>(\n definition: RouteWithActionOnly<TPath, undefined>,\n): OpaqueRouteDefinition;\n// Pathless overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, Record<string, never>, undefined, TData>;\n// Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string>(\n definition: PathlessRouteWithoutLoader<undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n undefined,\n undefined\n>;\n// Pathless overload with loader (no id)\nexport function route<TData>(\n definition: PathlessRouteWithLoader<TData, undefined>,\n): OpaqueRouteDefinition;\n// Pathless overload without loader (no id)\nexport function route(\n definition: PathlessRouteWithoutLoader<undefined>,\n): OpaqueRouteDefinition;\n// Overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with loader (no id)\nexport function route<const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined>,\n): OpaqueRouteDefinition;\n// Overload without loader (no id)\nexport function route<const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined>,\n): OpaqueRouteDefinition;\n// Implementation\nexport function route(\n definition: object,\n):\n | OpaqueRouteDefinition\n | PartialRouteDefinition<string, Record<string, string>, unknown, unknown> {\n return definition as unknown as OpaqueRouteDefinition;\n}\n\n/**\n * Helper function for creating type-safe route definitions with navigation state.\n *\n * Use this curried function when your route component needs to manage navigation state.\n * The state is tied to the navigation history entry and persists across back/forward navigation.\n *\n * @example\n * ```typescript\n * // Route with navigation state\n * type MyState = { scrollPosition: number };\n * routeState<MyState>()({\n * path: \"users/:userId\",\n * component: UserPage, // Receives { params, state, setState, resetState }\n * });\n *\n * // Route with both loader and navigation state\n * type FilterState = { filter: string };\n * routeState<FilterState>()({\n * path: \"products\",\n * loader: async () => fetchProducts(),\n * component: ProductList, // Receives { data, params, state, setState, resetState }\n * });\n * ```\n */\nexport function routeState<TState>(): {\n // Partial overload: id + action + loader (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string, TActionResult, TData>(\n definition: PartialRouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n TId\n > & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Partial overload: id + action only (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string>(\n definition: PartialRouteWithActionOnly<TPath, TId> & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Partial overload: id + pathless + loader (no component) → PartialRouteDefinition\n <TId extends string, TData>(\n definition: PartialPathlessRouteWithLoader<TData, TId> & { id: TId },\n ): PartialRouteDefinition<TId, Record<string, never>, TState, TData>;\n // Partial overload: id + pathless + no loader (no component) → PartialRouteDefinition\n <TId extends string>(\n definition: PartialPathlessRouteWithoutLoader<TId> & { id: TId },\n ): PartialRouteDefinition<TId, Record<string, never>, TState, undefined>;\n // Partial overload: id + loader (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string, TData>(\n definition: PartialRouteWithLoader<TPath, TData, TId> & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Partial overload: id + no loader (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string>(\n definition: PartialRouteWithoutLoader<TPath, TId> & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with id + action + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string, TActionResult, TData>(\n definition: RouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n TState,\n TId\n > & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Overload with id + action only → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string>(\n definition: RouteWithActionOnly<TPath, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with action + loader (no id)\n <TPath extends string, TActionResult, TData>(\n definition: RouteWithActionAndLoader<TPath, TActionResult, TData, TState>,\n ): OpaqueRouteDefinition;\n // Overload with action only (no id)\n <TPath extends string>(\n definition: RouteWithActionOnly<TPath, TState>,\n ): OpaqueRouteDefinition;\n // Pathless overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, Record<string, never>, TState, TData>;\n // Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string>(\n definition: PathlessRouteWithoutLoader<TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n TState,\n undefined\n >;\n // Pathless overload with loader (no id)\n <TData>(\n definition: PathlessRouteWithLoader<TData, TState>,\n ): OpaqueRouteDefinition;\n // Pathless overload without loader (no id)\n (definition: PathlessRouteWithoutLoader<TState>): OpaqueRouteDefinition;\n // Overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with loader (no id)\n <TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState>,\n ): OpaqueRouteDefinition;\n // Overload without loader (no id)\n <TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState>,\n ): OpaqueRouteDefinition;\n} {\n return ((definition: object) => {\n return definition as unknown as OpaqueRouteDefinition;\n }) as never;\n}\n","import type { ComponentType, ReactNode } from \"react\";\nimport type {\n OpaqueRouteDefinition,\n PartialRouteDefinition,\n RouteDefinition,\n TypefulOpaqueRouteDefinition,\n} from \"./route.js\";\n\ntype BindRouteOptions = {\n component: ComponentType<any> | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Binds a component (and optionally children) to a partial route definition,\n * producing a full route definition for use with `<Router />`.\n *\n * This is the Phase 2 of two-phase route definition for RSC:\n * - Phase 1: `route()` without component → `PartialRouteDefinition` (shared module)\n * - Phase 2: `bindRoute()` with component → full route definition (server module)\n *\n * @example\n * ```tsx\n * // Phase 1 (shared module)\n * export const userRoute = route({\n * id: \"user\",\n * path: \"/:userId\",\n * loader: fetchUser,\n * });\n *\n * // Phase 2 (server module)\n * const routes = [\n * bindRoute(userRoute, { component: <UserProfile /> }),\n * ];\n * ```\n */\nexport function bindRoute<\n TId extends string,\n TParams extends Record<string, string>,\n TState,\n TData,\n>(\n partialRoute: PartialRouteDefinition<TId, TParams, TState, TData>,\n binding: BindRouteOptions,\n): TypefulOpaqueRouteDefinition<TId, TParams, TState, TData>;\nexport function bindRoute(\n partialRoute: OpaqueRouteDefinition,\n binding: BindRouteOptions,\n): OpaqueRouteDefinition;\nexport function bindRoute(\n partialRoute: object,\n binding: BindRouteOptions,\n): object {\n return { ...partialRoute, ...binding };\n}\n"],"mappings":";AAynBA,SAAgB,MACd,YAG2E;AAC3E,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,aAuFd;AACA,UAAS,eAAuB;AAC9B,SAAO;;;;;;AC/rBX,SAAgB,UACd,cACA,SACQ;AACR,QAAO;EAAE,GAAG;EAAc,GAAG;EAAS"}
|
|
1
|
+
{"version":3,"file":"bindRoute-CQ2-ruTp.mjs","names":[],"sources":["../src/route.ts","../src/bindRoute.ts"],"sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\nconst routeDefinitionSymbol = Symbol();\nconst partialRouteDefinitionSymbol = Symbol();\n\n/**\n * Extracts parameter names from a path pattern.\n * E.g., \"/users/:id/posts/:postId\" -> \"id\" | \"postId\"\n */\ntype ExtractParams<T extends string> =\n T extends `${string}:${infer Param}/${infer Rest}`\n ? Param | ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? Param\n : never;\n\n/**\n * Creates a params object type from a path pattern.\n * E.g., \"/users/:id\" -> { id: string }\n */\nexport type PathParams<T extends string> = [ExtractParams<T>] extends [never]\n ? Record<string, never>\n : { [K in ExtractParams<T>]: string };\n\n/**\n * Arguments passed to action functions.\n * The request carries the POST method and FormData body.\n */\nexport type ActionArgs<Params extends Record<string, string>> = {\n /** Extracted path parameters */\n params: Params;\n /** Request object with method POST and FormData body */\n request: Request;\n /** AbortSignal for cancellation */\n signal: AbortSignal;\n};\n\n/**\n * Arguments passed to loader functions.\n */\nexport type LoaderArgs<\n Params extends Record<string, string>,\n ActionResult = undefined,\n> = {\n /** Extracted path parameters */\n params: Params;\n /** Request object with URL and headers */\n request: Request;\n /** AbortSignal for cancellation on navigation */\n signal: AbortSignal;\n /** Result from the action, if this load was triggered by a form submission */\n actionResult: ActionResult | undefined;\n};\n\n/**\n * Props for route components without loader.\n * Includes navigation state management props.\n */\nexport interface RouteComponentProps<\n TParams extends Record<string, string>,\n TState = undefined,\n> {\n /** Extracted path parameters */\n params: TParams;\n /** Current navigation state for this route (undefined on first visit) */\n state: TState | undefined;\n /** Update navigation state for this route asynchronously via replace navigation */\n setState: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => Promise<void>;\n /** Update navigation state for this route synchronously via updateCurrentEntry */\n setStateSync: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => void;\n /** Reset navigation state to undefined asynchronously via replace navigation */\n resetState: () => Promise<void>;\n /** Reset navigation state to undefined synchronously via updateCurrentEntry */\n resetStateSync: () => void;\n /** Ephemeral navigation info (only available during navigation, not persisted) */\n info: unknown;\n /** Whether a navigation transition is pending */\n isPending: boolean;\n}\n\n/**\n * Props for route components with loader.\n * Includes data from loader and navigation state management props.\n */\nexport interface RouteComponentPropsWithData<\n TParams extends Record<string, string>,\n TData,\n TState = undefined,\n> extends RouteComponentProps<TParams, TState> {\n /** Data returned from the loader */\n data: TData;\n}\n\n/**\n * Route definition created by the `route` helper function.\n */\nexport interface OpaqueRouteDefinition {\n [routeDefinitionSymbol]: unknown;\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/**\n * Type-carrying route definition created by the `route` helper function when an `id` is provided.\n * This type carries type information for params, state, and data, enabling type-safe hooks in the future.\n */\nexport interface TypefulOpaqueRouteDefinition<\n Id extends string,\n Params extends Record<string, string>,\n State,\n Data,\n> {\n [routeDefinitionSymbol]: {\n id: Id;\n params: Params;\n state: State;\n data: Data;\n };\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/**\n * Partial route definition created by the `route` helper function when `id` is provided but `component` is not.\n * Used for two-phase route definition in RSC: Phase 1 defines id, path, loader, action;\n * Phase 2 uses `bindRoute()` to attach the component.\n * This type carries type information for params, state, and data, enabling type-safe hooks.\n */\nexport interface PartialRouteDefinition<\n Id extends string,\n Params extends Record<string, string>,\n State,\n Data,\n> {\n [partialRouteDefinitionSymbol]: {\n id: Id;\n params: Params;\n state: State;\n data: Data;\n };\n path?: string;\n}\n\n/** Extract the Id type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteId<T> =\n T extends PartialRouteDefinition<\n infer Id,\n infer _Params,\n infer _State,\n infer _Data\n >\n ? Id\n : T extends TypefulOpaqueRouteDefinition<\n infer Id,\n infer _Params,\n infer _State,\n infer _Data\n >\n ? Id\n : never;\n\n/** Extract the Params type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteParams<T> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer Params,\n infer _State,\n infer _Data\n >\n ? Params\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer _State,\n infer _Data\n >\n ? Params\n : never;\n\n/** Extract the State type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteState<T> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer _Params,\n infer State,\n infer _Data\n >\n ? State\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer State,\n infer _Data\n >\n ? State\n : never;\n\n/** Extract the Data type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type ExtractRouteData<T> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer _Params,\n infer _State,\n infer Data\n >\n ? Data\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer _State,\n infer Data\n >\n ? Data\n : never;\n\n/** Extract the component props type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */\nexport type RouteComponentPropsOf<\n T extends\n | TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >\n | PartialRouteDefinition<string, Record<string, string>, unknown, unknown>,\n> =\n T extends PartialRouteDefinition<\n infer _Id,\n infer Params,\n infer State,\n infer Data\n >\n ? Data extends undefined\n ? RouteComponentProps<Params, State>\n : RouteComponentPropsWithData<Params, Data, State>\n : T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer State,\n infer Data\n >\n ? Data extends undefined\n ? RouteComponentProps<Params, State>\n : RouteComponentPropsWithData<Params, Data, State>\n : never;\n\n/**\n * Any route definition defined by user.\n */\nexport type RouteDefinition =\n | OpaqueRouteDefinition\n | TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >\n | {\n path?: string;\n component?: ComponentType<object> | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n };\n\n/**\n * Route definition with action and loader.\n * Action result flows to loader via actionResult parameter.\n */\ntype RouteWithActionAndLoader<\n TPath extends string,\n TActionResult,\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => TActionResult;\n loader: (\n args: LoaderArgs<PathParams<TPath>, Awaited<TActionResult>>,\n ) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<PathParams<TPath>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition with action only (no loader).\n * Action executes as a pure side effect.\n */\ntype RouteWithActionOnly<\n TPath extends string,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => unknown;\n component?:\n | ComponentType<RouteComponentProps<PathParams<TPath>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition with loader - infers TData from loader return type.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithLoader<\n TPath extends string,\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n loader: (args: LoaderArgs<PathParams<TPath>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<PathParams<TPath>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition without loader.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithoutLoader<\n TPath extends string,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n component?:\n | ComponentType<RouteComponentProps<PathParams<TPath>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition with loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithLoader<\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n loader: (args: LoaderArgs<Record<string, never>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<Record<string, never>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition without loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithoutLoader<\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n component?:\n | ComponentType<RouteComponentProps<Record<string, never>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Partial route definition with action and loader (no component).\n * Used for two-phase route definition.\n */\ntype PartialRouteWithActionAndLoader<\n TPath extends string,\n TActionResult,\n TData,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => TActionResult;\n loader: (\n args: LoaderArgs<PathParams<TPath>, Awaited<TActionResult>>,\n ) => TData;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial route definition with action only (no component, no loader).\n */\ntype PartialRouteWithActionOnly<\n TPath extends string,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n action: (args: ActionArgs<PathParams<TPath>>) => unknown;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial route definition with loader (no component).\n */\ntype PartialRouteWithLoader<\n TPath extends string,\n TData,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n loader: (args: LoaderArgs<PathParams<TPath>>) => TData;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial route definition without loader or component.\n */\ntype PartialRouteWithoutLoader<\n TPath extends string,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial pathless route definition with loader (no component).\n */\ntype PartialPathlessRouteWithLoader<\n TData,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n loader: (args: LoaderArgs<Record<string, never>>) => TData;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Partial pathless route definition without loader or component.\n */\ntype PartialPathlessRouteWithoutLoader<\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n component?: never;\n children?: never;\n exact?: never;\n requireChildren?: never;\n};\n\n/**\n * Helper function for creating type-safe route definitions.\n *\n * When a loader is provided, TypeScript infers the return type and ensures\n * the component accepts a `data` prop of that type. Components always receive\n * a `params` prop with types inferred from the path pattern.\n *\n * For routes with navigation state, use `routeState<TState>()({ ... })` instead.\n *\n * @example\n * ```typescript\n * // Route with async loader\n * route({\n * path: \"users/:userId\",\n * loader: async ({ params, signal }) => {\n * const res = await fetch(`/api/users/${params.userId}`, { signal });\n * return res.json() as Promise<User>;\n * },\n * component: UserDetail, // Must accept { data: Promise<User>, params: { userId: string }, state, setState, resetState }\n * });\n *\n * // Route without loader\n * route({\n * path: \"about\",\n * component: AboutPage, // Must accept { params: {}, state, setState, resetState }\n * });\n * ```\n */\n// Partial overload: id + action + loader (no component) → PartialRouteDefinition\nexport function route<\n TId extends string,\n const TPath extends string,\n TActionResult,\n TData,\n>(\n definition: PartialRouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n TId\n > & {\n id: TId;\n },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Partial overload: id + action only (no component) → PartialRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: PartialRouteWithActionOnly<TPath, TId> & { id: TId },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Partial overload: id + pathless + loader (no component) → PartialRouteDefinition\nexport function route<TId extends string, TData>(\n definition: PartialPathlessRouteWithLoader<TData, TId> & { id: TId },\n): PartialRouteDefinition<TId, Record<string, never>, undefined, TData>;\n// Partial overload: id + pathless + no loader (no component) → PartialRouteDefinition\nexport function route<TId extends string>(\n definition: PartialPathlessRouteWithoutLoader<TId> & { id: TId },\n): PartialRouteDefinition<TId, Record<string, never>, undefined, undefined>;\n// Partial overload: id + loader (no component) → PartialRouteDefinition\nexport function route<TId extends string, const TPath extends string, TData>(\n definition: PartialRouteWithLoader<TPath, TData, TId> & { id: TId },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Partial overload: id + no loader (no component) → PartialRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: PartialRouteWithoutLoader<TPath, TId> & { id: TId },\n): PartialRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with id + action + loader → TypefulOpaqueRouteDefinition\nexport function route<\n TId extends string,\n const TPath extends string,\n TActionResult,\n TData,\n>(\n definition: RouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n undefined,\n TId\n > & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Overload with id + action only → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: RouteWithActionOnly<TPath, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with action + loader (no id)\nexport function route<const TPath extends string, TActionResult, TData>(\n definition: RouteWithActionAndLoader<TPath, TActionResult, TData, undefined>,\n): OpaqueRouteDefinition;\n// Overload with action only (no id)\nexport function route<const TPath extends string>(\n definition: RouteWithActionOnly<TPath, undefined>,\n): OpaqueRouteDefinition;\n// Pathless overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, Record<string, never>, undefined, TData>;\n// Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string>(\n definition: PathlessRouteWithoutLoader<undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n undefined,\n undefined\n>;\n// Pathless overload with loader (no id)\nexport function route<TData>(\n definition: PathlessRouteWithLoader<TData, undefined>,\n): OpaqueRouteDefinition;\n// Pathless overload without loader (no id)\nexport function route(\n definition: PathlessRouteWithoutLoader<undefined>,\n): OpaqueRouteDefinition;\n// Overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with loader (no id)\nexport function route<const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined>,\n): OpaqueRouteDefinition;\n// Overload without loader (no id)\nexport function route<const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined>,\n): OpaqueRouteDefinition;\n// Implementation\nexport function route(\n definition: object,\n):\n | OpaqueRouteDefinition\n | PartialRouteDefinition<string, Record<string, string>, unknown, unknown> {\n return definition as unknown as OpaqueRouteDefinition;\n}\n\n/**\n * Helper function for creating type-safe route definitions with navigation state.\n *\n * Use this curried function when your route component needs to manage navigation state.\n * The state is tied to the navigation history entry and persists across back/forward navigation.\n *\n * @example\n * ```typescript\n * // Route with navigation state\n * type MyState = { scrollPosition: number };\n * routeState<MyState>()({\n * path: \"users/:userId\",\n * component: UserPage, // Receives { params, state, setState, resetState }\n * });\n *\n * // Route with both loader and navigation state\n * type FilterState = { filter: string };\n * routeState<FilterState>()({\n * path: \"products\",\n * loader: async () => fetchProducts(),\n * component: ProductList, // Receives { data, params, state, setState, resetState }\n * });\n * ```\n */\nexport function routeState<TState>(): {\n // Partial overload: id + action + loader (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string, TActionResult, TData>(\n definition: PartialRouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n TId\n > & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Partial overload: id + action only (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string>(\n definition: PartialRouteWithActionOnly<TPath, TId> & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Partial overload: id + pathless + loader (no component) → PartialRouteDefinition\n <TId extends string, TData>(\n definition: PartialPathlessRouteWithLoader<TData, TId> & { id: TId },\n ): PartialRouteDefinition<TId, Record<string, never>, TState, TData>;\n // Partial overload: id + pathless + no loader (no component) → PartialRouteDefinition\n <TId extends string>(\n definition: PartialPathlessRouteWithoutLoader<TId> & { id: TId },\n ): PartialRouteDefinition<TId, Record<string, never>, TState, undefined>;\n // Partial overload: id + loader (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string, TData>(\n definition: PartialRouteWithLoader<TPath, TData, TId> & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Partial overload: id + no loader (no component) → PartialRouteDefinition\n <TId extends string, TPath extends string>(\n definition: PartialRouteWithoutLoader<TPath, TId> & { id: TId },\n ): PartialRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with id + action + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string, TActionResult, TData>(\n definition: RouteWithActionAndLoader<\n TPath,\n TActionResult,\n TData,\n TState,\n TId\n > & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Overload with id + action only → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string>(\n definition: RouteWithActionOnly<TPath, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with action + loader (no id)\n <TPath extends string, TActionResult, TData>(\n definition: RouteWithActionAndLoader<TPath, TActionResult, TData, TState>,\n ): OpaqueRouteDefinition;\n // Overload with action only (no id)\n <TPath extends string>(\n definition: RouteWithActionOnly<TPath, TState>,\n ): OpaqueRouteDefinition;\n // Pathless overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, Record<string, never>, TState, TData>;\n // Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string>(\n definition: PathlessRouteWithoutLoader<TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n TState,\n undefined\n >;\n // Pathless overload with loader (no id)\n <TData>(\n definition: PathlessRouteWithLoader<TData, TState>,\n ): OpaqueRouteDefinition;\n // Pathless overload without loader (no id)\n (definition: PathlessRouteWithoutLoader<TState>): OpaqueRouteDefinition;\n // Overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with loader (no id)\n <TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState>,\n ): OpaqueRouteDefinition;\n // Overload without loader (no id)\n <TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState>,\n ): OpaqueRouteDefinition;\n} {\n return ((definition: object) => {\n return definition as unknown as OpaqueRouteDefinition;\n }) as never;\n}\n","import type { ComponentType, ReactNode } from \"react\";\nimport type {\n OpaqueRouteDefinition,\n PartialRouteDefinition,\n RouteDefinition,\n TypefulOpaqueRouteDefinition,\n} from \"./route.js\";\n\ntype BindRouteOptions = {\n component: ComponentType<any> | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Binds a component (and optionally children) to a partial route definition,\n * producing a full route definition for use with `<Router />`.\n *\n * This is the Phase 2 of two-phase route definition for RSC:\n * - Phase 1: `route()` without component → `PartialRouteDefinition` (shared module)\n * - Phase 2: `bindRoute()` with component → full route definition (server module)\n *\n * @example\n * ```tsx\n * // Phase 1 (shared module)\n * export const userRoute = route({\n * id: \"user\",\n * path: \"/:userId\",\n * loader: fetchUser,\n * });\n *\n * // Phase 2 (server module)\n * const routes = [\n * bindRoute(userRoute, { component: <UserProfile /> }),\n * ];\n * ```\n */\nexport function bindRoute<\n TId extends string,\n TParams extends Record<string, string>,\n TState,\n TData,\n>(\n partialRoute: PartialRouteDefinition<TId, TParams, TState, TData>,\n binding: BindRouteOptions,\n): TypefulOpaqueRouteDefinition<TId, TParams, TState, TData>;\nexport function bindRoute(\n partialRoute: OpaqueRouteDefinition,\n binding: BindRouteOptions,\n): OpaqueRouteDefinition;\nexport function bindRoute(\n partialRoute: object,\n binding: BindRouteOptions,\n): object {\n return { ...partialRoute, ...binding };\n}\n"],"mappings":";AAynBA,SAAgB,MACd,YAG2E;AAC3E,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,aAuFd;AACA,UAAS,eAAuB;AAC9B,SAAO;;;;;AC/rBX,SAAgB,UACd,cACA,SACQ;AACR,QAAO;EAAE,GAAG;EAAc,GAAG;EAAS"}
|
|
@@ -446,4 +446,4 @@ declare function bindRoute<TId extends string, TParams extends Record<string, st
|
|
|
446
446
|
declare function bindRoute(partialRoute: OpaqueRouteDefinition, binding: BindRouteOptions): OpaqueRouteDefinition;
|
|
447
447
|
//#endregion
|
|
448
448
|
export { routeState as _, ExtractRouteParams as a, OpaqueRouteDefinition as c, RouteComponentProps as d, RouteComponentPropsOf as f, route as g, TypefulOpaqueRouteDefinition as h, ExtractRouteId as i, PartialRouteDefinition as l, RouteDefinition as m, ActionArgs as n, ExtractRouteState as o, RouteComponentPropsWithData as p, ExtractRouteData as r, LoaderArgs as s, bindRoute as t, PathParams as u };
|
|
449
|
-
//# sourceMappingURL=bindRoute-
|
|
449
|
+
//# sourceMappingURL=bindRoute-DulMzi5X.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindRoute-
|
|
1
|
+
{"version":3,"file":"bindRoute-DulMzi5X.d.mts","names":[],"sources":["../src/route.ts","../src/bindRoute.ts"],"mappings":"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"}
|
|
@@ -21,7 +21,16 @@ function MyComponent() {
|
|
|
21
21
|
console.log(location.pathname); // "/users/123"
|
|
22
22
|
console.log(location.search); // "?tab=profile"
|
|
23
23
|
console.log(location.hash); // "#section"
|
|
24
|
+
console.log(location.entryId); // NavigationHistoryEntry.id or null
|
|
25
|
+
console.log(location.entryKey); // NavigationHistoryEntry.key or null
|
|
24
26
|
}`}</CodeBlock>
|
|
27
|
+
<p>
|
|
28
|
+
<code>entryId</code> and <code>entryKey</code> are <code>null</code>{" "}
|
|
29
|
+
when the Navigation API is unavailable. Do not render them directly in
|
|
30
|
+
DOM — they are not available during SSR and will cause a hydration
|
|
31
|
+
mismatch. Use them as a React <code>key</code> or in effects/callbacks
|
|
32
|
+
instead.
|
|
33
|
+
</p>
|
|
25
34
|
</article>
|
|
26
35
|
|
|
27
36
|
<article className="api-item">
|
|
@@ -335,7 +335,24 @@ routeState<{ tab: string }>()({
|
|
|
335
335
|
pathname: string;
|
|
336
336
|
search: string;
|
|
337
337
|
hash: string;
|
|
338
|
+
entryId: string | null; // NavigationHistoryEntry.id
|
|
339
|
+
entryKey: string | null; // NavigationHistoryEntry.key
|
|
338
340
|
}`}</CodeBlock>
|
|
341
|
+
<p>
|
|
342
|
+
<code>entryId</code> and <code>entryKey</code> expose the
|
|
343
|
+
corresponding properties from the Navigation API's{" "}
|
|
344
|
+
<code>NavigationHistoryEntry</code>. <code>entryId</code> is a unique
|
|
345
|
+
identifier for the entry — a new id is assigned when the entry is
|
|
346
|
+
replaced. <code>entryKey</code> represents the slot in the entry list
|
|
347
|
+
and is stable across replacements. Both are <code>null</code> when the
|
|
348
|
+
Navigation API is unavailable (e.g., in static fallback mode).
|
|
349
|
+
</p>
|
|
350
|
+
<p>
|
|
351
|
+
<strong>Warning:</strong> Do not render these values directly in DOM,
|
|
352
|
+
as they are not available during SSR and will cause a hydration
|
|
353
|
+
mismatch. They are best suited for use as a React <code>key</code> or
|
|
354
|
+
in effects/callbacks.
|
|
355
|
+
</p>
|
|
339
356
|
</article>
|
|
340
357
|
|
|
341
358
|
<article className="api-item">
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { CodeBlock } from "../components/CodeBlock.js";
|
|
2
|
+
|
|
3
|
+
export function LearnLoadersPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="learn-content">
|
|
6
|
+
<h2>How Loaders Run</h2>
|
|
7
|
+
|
|
8
|
+
<p className="page-intro">
|
|
9
|
+
Loaders fetch data for a route before the UI renders. This page explains
|
|
10
|
+
when loaders execute, how results are cached, and how different types of
|
|
11
|
+
navigation affect loader behavior.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<section>
|
|
15
|
+
<h3>Defining a Loader</h3>
|
|
16
|
+
<p>
|
|
17
|
+
A loader is a function on a route definition that receives the route
|
|
18
|
+
params, a <code>Request</code>, and an <code>AbortSignal</code>. It
|
|
19
|
+
can return any value — typically a Promise from a fetch call:
|
|
20
|
+
</p>
|
|
21
|
+
<CodeBlock language="tsx">{`import { route } from "@funstack/router";
|
|
22
|
+
|
|
23
|
+
const userRoute = route({
|
|
24
|
+
path: "/users/:id",
|
|
25
|
+
loader: async ({ params, request, signal }) => {
|
|
26
|
+
const res = await fetch(\`/api/users/\${params.id}\`, { signal });
|
|
27
|
+
return res.json();
|
|
28
|
+
},
|
|
29
|
+
component: UserPage,
|
|
30
|
+
});`}</CodeBlock>
|
|
31
|
+
<p>
|
|
32
|
+
The component receives the loader’s return value as the{" "}
|
|
33
|
+
<code>data</code> prop. For async loaders this is a{" "}
|
|
34
|
+
<code>Promise</code>, which you unwrap with React’s{" "}
|
|
35
|
+
<code>use()</code> hook inside a <code>Suspense</code> boundary:
|
|
36
|
+
</p>
|
|
37
|
+
<CodeBlock language="tsx">{`import { use, Suspense } from "react";
|
|
38
|
+
|
|
39
|
+
function UserPage({ data }: { data: Promise<User> }) {
|
|
40
|
+
return (
|
|
41
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
42
|
+
<UserDetail data={data} />
|
|
43
|
+
</Suspense>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function UserDetail({ data }: { data: Promise<User> }) {
|
|
48
|
+
const user = use(data);
|
|
49
|
+
return <h1>{user.name}</h1>;
|
|
50
|
+
}`}</CodeBlock>
|
|
51
|
+
</section>
|
|
52
|
+
|
|
53
|
+
<section>
|
|
54
|
+
<h3>When Loaders Execute</h3>
|
|
55
|
+
<p>Loaders run at two points in the lifecycle:</p>
|
|
56
|
+
<ol>
|
|
57
|
+
<li>
|
|
58
|
+
<strong>Initial page load</strong> — When the Router first
|
|
59
|
+
renders, it matches the current URL against the route definitions
|
|
60
|
+
and executes all matching loaders immediately.
|
|
61
|
+
</li>
|
|
62
|
+
<li>
|
|
63
|
+
<strong>Navigation events</strong> — When the user navigates
|
|
64
|
+
(by clicking a link, submitting a form, or calling{" "}
|
|
65
|
+
<code>navigate()</code>), the Router matches the destination URL and
|
|
66
|
+
executes loaders for the matched routes.
|
|
67
|
+
</li>
|
|
68
|
+
</ol>
|
|
69
|
+
<p>
|
|
70
|
+
In both cases, all loaders in the matched route stack (parent and
|
|
71
|
+
child) run <strong>in parallel</strong>. The navigation completes once
|
|
72
|
+
every loader’s Promise has resolved.
|
|
73
|
+
</p>
|
|
74
|
+
</section>
|
|
75
|
+
|
|
76
|
+
<section>
|
|
77
|
+
<h3>Caching by Navigation Entry</h3>
|
|
78
|
+
<p>
|
|
79
|
+
Loader results are cached using the{" "}
|
|
80
|
+
<strong>navigation entry ID</strong> from the Navigation API. Each
|
|
81
|
+
time you navigate to a new URL, the browser creates a new navigation
|
|
82
|
+
entry with a unique ID. The Router uses this ID as the cache key, so:
|
|
83
|
+
</p>
|
|
84
|
+
<ul>
|
|
85
|
+
<li>
|
|
86
|
+
Re-renders of the same page <strong>do not</strong> re-execute
|
|
87
|
+
loaders — the cached result is returned.
|
|
88
|
+
</li>
|
|
89
|
+
<li>
|
|
90
|
+
Navigating to a new URL always creates a new entry and{" "}
|
|
91
|
+
<strong>always</strong> executes loaders, even if the URL is the
|
|
92
|
+
same as a previous navigation.
|
|
93
|
+
</li>
|
|
94
|
+
</ul>
|
|
95
|
+
<p>
|
|
96
|
+
This design ensures that loaders run exactly once per navigation while
|
|
97
|
+
preventing unnecessary re-fetches during React re-renders.
|
|
98
|
+
</p>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<section>
|
|
102
|
+
<h3>Navigation Types and Loader Behavior</h3>
|
|
103
|
+
<p>
|
|
104
|
+
Different types of navigation have different effects on whether
|
|
105
|
+
loaders run:
|
|
106
|
+
</p>
|
|
107
|
+
|
|
108
|
+
<h4>Push and Replace</h4>
|
|
109
|
+
<p>
|
|
110
|
+
A <strong>push</strong> navigation (the default when clicking a link
|
|
111
|
+
or calling <code>navigate()</code>) creates a new navigation entry.
|
|
112
|
+
Since the entry is new, loaders always execute. A{" "}
|
|
113
|
+
<strong>replace</strong> navigation behaves the same way — it
|
|
114
|
+
creates a new entry that replaces the current one, so loaders execute
|
|
115
|
+
fresh.
|
|
116
|
+
</p>
|
|
117
|
+
|
|
118
|
+
<h4>Traverse (Back / Forward)</h4>
|
|
119
|
+
<p>
|
|
120
|
+
When the user goes back or forward in history, the browser revisits an{" "}
|
|
121
|
+
<strong>existing</strong> navigation entry. Because the entry ID is
|
|
122
|
+
the same as when the page was originally visited, the cached loader
|
|
123
|
+
results are returned <strong>without re-executing</strong> the
|
|
124
|
+
loaders. This makes back/forward navigation instant.
|
|
125
|
+
</p>
|
|
126
|
+
|
|
127
|
+
<h4>Reload</h4>
|
|
128
|
+
<p>
|
|
129
|
+
A reload navigation stays on the same navigation entry, but the Router
|
|
130
|
+
generates a <strong>fresh cache key</strong> so that all loaders{" "}
|
|
131
|
+
<strong>re-execute</strong>. This is useful when you want to refresh
|
|
132
|
+
data without navigating away from the current page.
|
|
133
|
+
</p>
|
|
134
|
+
<p>
|
|
135
|
+
You can trigger a reload programmatically using the Navigation
|
|
136
|
+
API’s <code>navigation.reload()</code> method:
|
|
137
|
+
</p>
|
|
138
|
+
<CodeBlock language="tsx">{`function RefreshButton() {
|
|
139
|
+
return (
|
|
140
|
+
<button onClick={() => navigation.reload()}>
|
|
141
|
+
Refresh Data
|
|
142
|
+
</button>
|
|
143
|
+
);
|
|
144
|
+
}`}</CodeBlock>
|
|
145
|
+
<p>
|
|
146
|
+
During a reload, the old cached data remains available for the{" "}
|
|
147
|
+
<strong>pending UI</strong>. Because the Router wraps navigations in a
|
|
148
|
+
React transition, the previous UI stays on screen while the new data
|
|
149
|
+
loads. Once the new loaders resolve, the UI updates. This means users
|
|
150
|
+
see the existing content while the refresh is in progress, rather than
|
|
151
|
+
a blank screen or loading spinner.
|
|
152
|
+
</p>
|
|
153
|
+
<p>
|
|
154
|
+
Consecutive reloads work correctly — each reload increments an
|
|
155
|
+
internal counter to produce a unique cache key, and stale caches are
|
|
156
|
+
pruned automatically.
|
|
157
|
+
</p>
|
|
158
|
+
|
|
159
|
+
<h4>Form Submissions</h4>
|
|
160
|
+
<p>
|
|
161
|
+
When a <code>{'<form method="post">'}</code> is submitted, the Router
|
|
162
|
+
runs the matched route’s <code>action</code> first, then clears
|
|
163
|
+
the loader cache for the current entry and re-executes all loaders.
|
|
164
|
+
The action’s return value is passed to each loader as{" "}
|
|
165
|
+
<code>actionResult</code>. See the{" "}
|
|
166
|
+
<a href="/learn/actions">Form Actions</a> page for details.
|
|
167
|
+
</p>
|
|
168
|
+
</section>
|
|
169
|
+
|
|
170
|
+
<section>
|
|
171
|
+
<h3>Nested Route Loaders</h3>
|
|
172
|
+
<p>
|
|
173
|
+
When routes are nested, each route in the matched stack can define its
|
|
174
|
+
own loader. All loaders in the stack execute in parallel, and each
|
|
175
|
+
component receives its own loader’s result:
|
|
176
|
+
</p>
|
|
177
|
+
<CodeBlock language="tsx">{`const routes = [
|
|
178
|
+
route({
|
|
179
|
+
path: "/dashboard",
|
|
180
|
+
loader: () => fetchDashboardLayout(),
|
|
181
|
+
component: DashboardLayout,
|
|
182
|
+
children: [
|
|
183
|
+
route({
|
|
184
|
+
path: "/stats",
|
|
185
|
+
loader: () => fetchStats(),
|
|
186
|
+
component: StatsPage,
|
|
187
|
+
}),
|
|
188
|
+
],
|
|
189
|
+
}),
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
// When navigating to /dashboard/stats:
|
|
193
|
+
// → fetchDashboardLayout() and fetchStats() run in parallel
|
|
194
|
+
// → DashboardLayout receives the layout data
|
|
195
|
+
// → StatsPage receives the stats data`}</CodeBlock>
|
|
196
|
+
<p>
|
|
197
|
+
On reload, <strong>all</strong> loaders in the matched stack
|
|
198
|
+
re-execute, not just the deepest one.
|
|
199
|
+
</p>
|
|
200
|
+
</section>
|
|
201
|
+
|
|
202
|
+
<section>
|
|
203
|
+
<h3>Cache Cleanup</h3>
|
|
204
|
+
<p>
|
|
205
|
+
Cached loader results are automatically cleaned up when a navigation
|
|
206
|
+
entry is <strong>disposed</strong>. The browser disposes entries when
|
|
207
|
+
they are removed from the history stack (for example, when the user
|
|
208
|
+
navigates forward from a point in the middle of the history stack, the
|
|
209
|
+
entries ahead are discarded). The Router listens for these dispose
|
|
210
|
+
events and removes the corresponding cached data.
|
|
211
|
+
</p>
|
|
212
|
+
</section>
|
|
213
|
+
|
|
214
|
+
<section>
|
|
215
|
+
<h3>Error Handling</h3>
|
|
216
|
+
<p>
|
|
217
|
+
When a loader throws an error, the router catches it and re-throws it
|
|
218
|
+
during rendering of that route’s component. This means the error
|
|
219
|
+
can be caught by a React{" "}
|
|
220
|
+
<a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary">
|
|
221
|
+
Error Boundary
|
|
222
|
+
</a>{" "}
|
|
223
|
+
placed above the route in the component tree. For async loaders that
|
|
224
|
+
return a rejected promise, the error is surfaced when{" "}
|
|
225
|
+
<code>use(data)</code> is called, which is also caught by Error
|
|
226
|
+
Boundaries.
|
|
227
|
+
</p>
|
|
228
|
+
<p>
|
|
229
|
+
The recommended pattern is to place an error boundary in your{" "}
|
|
230
|
+
<strong>root layout route</strong>, wrapping the{" "}
|
|
231
|
+
<code>{"<Outlet />"}</code>. This catches errors from any loader in
|
|
232
|
+
the route tree while keeping the root layout (header, navigation,
|
|
233
|
+
etc.) intact:
|
|
234
|
+
</p>
|
|
235
|
+
<CodeBlock language="tsx">{`import { Router, route, Outlet } from "@funstack/router";
|
|
236
|
+
import { ErrorBoundary } from "./ErrorBoundary";
|
|
237
|
+
|
|
238
|
+
function RootLayout() {
|
|
239
|
+
return (
|
|
240
|
+
<div>
|
|
241
|
+
<header>My App</header>
|
|
242
|
+
<ErrorBoundary fallback={<div>Something went wrong.</div>}>
|
|
243
|
+
<Outlet />
|
|
244
|
+
</ErrorBoundary>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const routes = [
|
|
250
|
+
route({
|
|
251
|
+
path: "/",
|
|
252
|
+
component: RootLayout,
|
|
253
|
+
children: [
|
|
254
|
+
route({
|
|
255
|
+
path: "/",
|
|
256
|
+
component: HomePage,
|
|
257
|
+
}),
|
|
258
|
+
route({
|
|
259
|
+
path: "/users/:id",
|
|
260
|
+
component: UserPage,
|
|
261
|
+
loader: async ({ params }) => {
|
|
262
|
+
const res = await fetch(\`/api/users/\${params.id}\`);
|
|
263
|
+
if (!res.ok) throw new Error("Failed to load user");
|
|
264
|
+
return res.json();
|
|
265
|
+
},
|
|
266
|
+
}),
|
|
267
|
+
],
|
|
268
|
+
}),
|
|
269
|
+
];`}</CodeBlock>
|
|
270
|
+
<p>
|
|
271
|
+
This works for both synchronous and asynchronous loaders. For sync
|
|
272
|
+
loaders, the router catches the error and re-throws it during route
|
|
273
|
+
rendering. For async loaders, the rejected promise naturally surfaces
|
|
274
|
+
through <code>use()</code>. Either way, Error Boundaries catch the
|
|
275
|
+
error.
|
|
276
|
+
</p>
|
|
277
|
+
<p>
|
|
278
|
+
You can also place error boundaries at more granular levels (e.g.,
|
|
279
|
+
wrapping a specific route’s <code>{"<Outlet />"}</code> or{" "}
|
|
280
|
+
<code>{"<Suspense>"}</code> boundary) for fine-grained error handling.
|
|
281
|
+
</p>
|
|
282
|
+
</section>
|
|
283
|
+
|
|
284
|
+
<section>
|
|
285
|
+
<h3>Summary</h3>
|
|
286
|
+
<table className="summary-table">
|
|
287
|
+
<thead>
|
|
288
|
+
<tr>
|
|
289
|
+
<th>Navigation type</th>
|
|
290
|
+
<th>Loaders run?</th>
|
|
291
|
+
<th>Why</th>
|
|
292
|
+
</tr>
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody>
|
|
295
|
+
<tr>
|
|
296
|
+
<td>Push / Replace</td>
|
|
297
|
+
<td>Yes</td>
|
|
298
|
+
<td>New navigation entry, no cache</td>
|
|
299
|
+
</tr>
|
|
300
|
+
<tr>
|
|
301
|
+
<td>Traverse (Back / Forward)</td>
|
|
302
|
+
<td>No</td>
|
|
303
|
+
<td>Existing entry, cached results returned</td>
|
|
304
|
+
</tr>
|
|
305
|
+
<tr>
|
|
306
|
+
<td>Reload</td>
|
|
307
|
+
<td>Yes</td>
|
|
308
|
+
<td>Fresh cache key generated</td>
|
|
309
|
+
</tr>
|
|
310
|
+
<tr>
|
|
311
|
+
<td>Form submission (POST)</td>
|
|
312
|
+
<td>Yes</td>
|
|
313
|
+
<td>Cache cleared after action runs</td>
|
|
314
|
+
</tr>
|
|
315
|
+
</tbody>
|
|
316
|
+
</table>
|
|
317
|
+
</section>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|
package/dist/docs/index.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
### Learn
|
|
17
17
|
|
|
18
18
|
- [Form Actions](./LearnActionsPage.tsx) - FUNSTACK Router can intercept submissions and run an action function on the client before navigation occurs. This guide explains how actions work, when to use them, and important considerations for progressive enhancement.
|
|
19
|
+
- [How Loaders Run](./LearnLoadersPage.tsx) - Loaders fetch data for a route before the UI renders. This page explains when loaders execute, how results are cached, and how different types of navigation affect loader behavior.
|
|
19
20
|
- [Navigation API](./LearnNavigationApiPage.tsx) - FUNSTACK Router is built on the Navigation API , a modern browser API that provides a unified way to handle navigation. This guide explains the key differences from the older History API and the benefits this brings to your application.
|
|
20
21
|
- [Nested Routes](./LearnNestedRoutesPage.tsx) - Nested routes let you build complex page layouts where parts of the UI persist across navigation while other parts change. Think of a dashboard with a sidebar that stays in place while the main content area updates—that's nested routing in action.
|
|
21
22
|
- [RSC with Route Features](./LearnRouteDefinitionsPage.tsx) - When using React Server Components as route components, you may also want route features like loaders, typed hooks ( useRouteParams, useRouteData), and navigation state. The challenge is that route definitions referencing server components cannot be imported from client modules. This guide shows how to split a route definition into a shared part (importable by client components for type safety) and a server part (where the component is attached), enabling full route features alongside RSC.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as routeState, a as ExtractRouteParams, c as OpaqueRouteDefinition, d as RouteComponentProps, f as RouteComponentPropsOf, g as route, h as TypefulOpaqueRouteDefinition, i as ExtractRouteId, l as PartialRouteDefinition, m as RouteDefinition, n as ActionArgs, o as ExtractRouteState, p as RouteComponentPropsWithData, r as ExtractRouteData, s as LoaderArgs, t as bindRoute, u as PathParams } from "./bindRoute-
|
|
1
|
+
import { _ as routeState, a as ExtractRouteParams, c as OpaqueRouteDefinition, d as RouteComponentProps, f as RouteComponentPropsOf, g as route, h as TypefulOpaqueRouteDefinition, i as ExtractRouteId, l as PartialRouteDefinition, m as RouteDefinition, n as ActionArgs, o as ExtractRouteState, p as RouteComponentPropsWithData, r as ExtractRouteData, s as LoaderArgs, t as bindRoute, u as PathParams } from "./bindRoute-DulMzi5X.mjs";
|
|
2
2
|
import { ComponentType, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
@@ -72,6 +72,26 @@ type Location = {
|
|
|
72
72
|
pathname: string;
|
|
73
73
|
search: string;
|
|
74
74
|
hash: string;
|
|
75
|
+
/**
|
|
76
|
+
* NavigationHistoryEntry.id — unique identifier for this entry.
|
|
77
|
+
* A new id is assigned when the entry is replaced.
|
|
78
|
+
* Null when Navigation API is unavailable.
|
|
79
|
+
*
|
|
80
|
+
* **Warning:** Do not render this value directly in DOM, as it is not
|
|
81
|
+
* available during SSR and will cause a hydration mismatch. Use it as a
|
|
82
|
+
* React `key` or in effects/callbacks instead.
|
|
83
|
+
*/
|
|
84
|
+
entryId: string | null;
|
|
85
|
+
/**
|
|
86
|
+
* NavigationHistoryEntry.key — represents the slot in the entry list.
|
|
87
|
+
* Stable across replacements.
|
|
88
|
+
* Null when Navigation API is unavailable.
|
|
89
|
+
*
|
|
90
|
+
* **Warning:** Do not render this value directly in DOM, as it is not
|
|
91
|
+
* available during SSR and will cause a hydration mismatch. Use it as a
|
|
92
|
+
* React `key` or in effects/callbacks instead.
|
|
93
|
+
*/
|
|
94
|
+
entryKey: string | null;
|
|
75
95
|
};
|
|
76
96
|
/**
|
|
77
97
|
* Callback invoked before navigation is intercepted.
|