@funstack/router 0.0.10 → 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.
@@ -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":";;;;;;AAUA,MAAM,WAAW,QADC,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACrB,yCAAyC;AAE7E,QAAQ,IAAI,0BAA0B,SAAS;AAE/C,MAAM,QAAQ,SAAS"}
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,15 @@ function routeState() {
31
31
  return definition;
32
32
  });
33
33
  }
34
-
35
34
  //#endregion
36
- export { routeState as n, route as t };
37
- //# sourceMappingURL=route-p_gr5yPI.mjs.map
35
+ //#region src/bindRoute.ts
36
+ function bindRoute(partialRoute, binding) {
37
+ return {
38
+ ...partialRoute,
39
+ ...binding
40
+ };
41
+ }
42
+ //#endregion
43
+ export { route as n, routeState as r, bindRoute as t };
44
+
45
+ //# sourceMappingURL=bindRoute-CQ2-ruTp.mjs.map
@@ -0,0 +1 @@
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"}
@@ -2,6 +2,7 @@ import { ComponentType, ReactNode } from "react";
2
2
 
3
3
  //#region src/route.d.ts
4
4
  declare const routeDefinitionSymbol: unique symbol;
5
+ declare const partialRouteDefinitionSymbol: unique symbol;
5
6
  /**
6
7
  * Extracts parameter names from a path pattern.
7
8
  * E.g., "/users/:id/posts/:postId" -> "id" | "postId"
@@ -86,16 +87,31 @@ interface TypefulOpaqueRouteDefinition<Id extends string, Params extends Record<
86
87
  exact?: boolean;
87
88
  requireChildren?: boolean;
88
89
  }
89
- /** Extract the Id type from a TypefulOpaqueRouteDefinition */
90
- type ExtractRouteId<T> = T extends TypefulOpaqueRouteDefinition<infer Id, infer _Params, infer _State, infer _Data> ? Id : never;
91
- /** Extract the Params type from a TypefulOpaqueRouteDefinition */
92
- type ExtractRouteParams<T> = T extends TypefulOpaqueRouteDefinition<infer _Id, infer Params, infer _State, infer _Data> ? Params : never;
93
- /** Extract the State type from a TypefulOpaqueRouteDefinition */
94
- type ExtractRouteState<T> = T extends TypefulOpaqueRouteDefinition<infer _Id, infer _Params, infer State, infer _Data> ? State : never;
95
- /** Extract the Data type from a TypefulOpaqueRouteDefinition */
96
- type ExtractRouteData<T> = T extends TypefulOpaqueRouteDefinition<infer _Id, infer _Params, infer _State, infer Data> ? Data : never;
97
- /** Extract the component props type from a TypefulOpaqueRouteDefinition */
98
- type RouteComponentPropsOf<T extends TypefulOpaqueRouteDefinition<string, Record<string, string>, unknown, unknown>> = T extends TypefulOpaqueRouteDefinition<infer _Id, infer Params, infer State, infer Data> ? Data extends undefined ? RouteComponentProps<Params, State> : RouteComponentPropsWithData<Params, Data, State> : never;
90
+ /**
91
+ * Partial route definition created by the `route` helper function when `id` is provided but `component` is not.
92
+ * Used for two-phase route definition in RSC: Phase 1 defines id, path, loader, action;
93
+ * Phase 2 uses `bindRoute()` to attach the component.
94
+ * This type carries type information for params, state, and data, enabling type-safe hooks.
95
+ */
96
+ interface PartialRouteDefinition<Id extends string, Params extends Record<string, string>, State, Data> {
97
+ [partialRouteDefinitionSymbol]: {
98
+ id: Id;
99
+ params: Params;
100
+ state: State;
101
+ data: Data;
102
+ };
103
+ path?: string;
104
+ }
105
+ /** Extract the Id type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */
106
+ type ExtractRouteId<T> = T extends PartialRouteDefinition<infer Id, infer _Params, infer _State, infer _Data> ? Id : T extends TypefulOpaqueRouteDefinition<infer Id, infer _Params, infer _State, infer _Data> ? Id : never;
107
+ /** Extract the Params type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */
108
+ type ExtractRouteParams<T> = T extends PartialRouteDefinition<infer _Id, infer Params, infer _State, infer _Data> ? Params : T extends TypefulOpaqueRouteDefinition<infer _Id, infer Params, infer _State, infer _Data> ? Params : never;
109
+ /** Extract the State type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */
110
+ type ExtractRouteState<T> = T extends PartialRouteDefinition<infer _Id, infer _Params, infer State, infer _Data> ? State : T extends TypefulOpaqueRouteDefinition<infer _Id, infer _Params, infer State, infer _Data> ? State : never;
111
+ /** Extract the Data type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */
112
+ type ExtractRouteData<T> = T extends PartialRouteDefinition<infer _Id, infer _Params, infer _State, infer Data> ? Data : T extends TypefulOpaqueRouteDefinition<infer _Id, infer _Params, infer _State, infer Data> ? Data : never;
113
+ /** Extract the component props type from a TypefulOpaqueRouteDefinition or PartialRouteDefinition */
114
+ type RouteComponentPropsOf<T extends TypefulOpaqueRouteDefinition<string, Record<string, string>, unknown, unknown> | PartialRouteDefinition<string, Record<string, string>, unknown, unknown>> = T extends PartialRouteDefinition<infer _Id, infer Params, infer State, infer Data> ? Data extends undefined ? RouteComponentProps<Params, State> : RouteComponentPropsWithData<Params, Data, State> : T extends TypefulOpaqueRouteDefinition<infer _Id, infer Params, infer State, infer Data> ? Data extends undefined ? RouteComponentProps<Params, State> : RouteComponentPropsWithData<Params, Data, State> : never;
99
115
  /**
100
116
  * Any route definition defined by user.
101
117
  */
@@ -185,6 +201,78 @@ type PathlessRouteWithoutLoader<TState, TId extends string | undefined = undefin
185
201
  children?: RouteDefinition[];
186
202
  requireChildren?: boolean;
187
203
  };
204
+ /**
205
+ * Partial route definition with action and loader (no component).
206
+ * Used for two-phase route definition.
207
+ */
208
+ type PartialRouteWithActionAndLoader<TPath extends string, TActionResult, TData, TId extends string | undefined = undefined> = {
209
+ id?: TId;
210
+ path: TPath;
211
+ action: (args: ActionArgs<PathParams<TPath>>) => TActionResult;
212
+ loader: (args: LoaderArgs<PathParams<TPath>, Awaited<TActionResult>>) => TData;
213
+ component?: never;
214
+ children?: never;
215
+ exact?: never;
216
+ requireChildren?: never;
217
+ };
218
+ /**
219
+ * Partial route definition with action only (no component, no loader).
220
+ */
221
+ type PartialRouteWithActionOnly<TPath extends string, TId extends string | undefined = undefined> = {
222
+ id?: TId;
223
+ path: TPath;
224
+ action: (args: ActionArgs<PathParams<TPath>>) => unknown;
225
+ component?: never;
226
+ children?: never;
227
+ exact?: never;
228
+ requireChildren?: never;
229
+ };
230
+ /**
231
+ * Partial route definition with loader (no component).
232
+ */
233
+ type PartialRouteWithLoader<TPath extends string, TData, TId extends string | undefined = undefined> = {
234
+ id?: TId;
235
+ path: TPath;
236
+ loader: (args: LoaderArgs<PathParams<TPath>>) => TData;
237
+ component?: never;
238
+ children?: never;
239
+ exact?: never;
240
+ requireChildren?: never;
241
+ };
242
+ /**
243
+ * Partial route definition without loader or component.
244
+ */
245
+ type PartialRouteWithoutLoader<TPath extends string, TId extends string | undefined = undefined> = {
246
+ id?: TId;
247
+ path: TPath;
248
+ component?: never;
249
+ children?: never;
250
+ exact?: never;
251
+ requireChildren?: never;
252
+ };
253
+ /**
254
+ * Partial pathless route definition with loader (no component).
255
+ */
256
+ type PartialPathlessRouteWithLoader<TData, TId extends string | undefined = undefined> = {
257
+ id?: TId;
258
+ path?: undefined;
259
+ loader: (args: LoaderArgs<Record<string, never>>) => TData;
260
+ component?: never;
261
+ children?: never;
262
+ exact?: never;
263
+ requireChildren?: never;
264
+ };
265
+ /**
266
+ * Partial pathless route definition without loader or component.
267
+ */
268
+ type PartialPathlessRouteWithoutLoader<TId extends string | undefined = undefined> = {
269
+ id?: TId;
270
+ path?: undefined;
271
+ component?: never;
272
+ children?: never;
273
+ exact?: never;
274
+ requireChildren?: never;
275
+ };
188
276
  /**
189
277
  * Helper function for creating type-safe route definitions.
190
278
  *
@@ -213,6 +301,24 @@ type PathlessRouteWithoutLoader<TState, TId extends string | undefined = undefin
213
301
  * });
214
302
  * ```
215
303
  */
304
+ declare function route<TId extends string, const TPath extends string, TActionResult, TData>(definition: PartialRouteWithActionAndLoader<TPath, TActionResult, TData, TId> & {
305
+ id: TId;
306
+ }): PartialRouteDefinition<TId, PathParams<TPath>, undefined, TData>;
307
+ declare function route<TId extends string, const TPath extends string>(definition: PartialRouteWithActionOnly<TPath, TId> & {
308
+ id: TId;
309
+ }): PartialRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;
310
+ declare function route<TId extends string, TData>(definition: PartialPathlessRouteWithLoader<TData, TId> & {
311
+ id: TId;
312
+ }): PartialRouteDefinition<TId, Record<string, never>, undefined, TData>;
313
+ declare function route<TId extends string>(definition: PartialPathlessRouteWithoutLoader<TId> & {
314
+ id: TId;
315
+ }): PartialRouteDefinition<TId, Record<string, never>, undefined, undefined>;
316
+ declare function route<TId extends string, const TPath extends string, TData>(definition: PartialRouteWithLoader<TPath, TData, TId> & {
317
+ id: TId;
318
+ }): PartialRouteDefinition<TId, PathParams<TPath>, undefined, TData>;
319
+ declare function route<TId extends string, const TPath extends string>(definition: PartialRouteWithoutLoader<TPath, TId> & {
320
+ id: TId;
321
+ }): PartialRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;
216
322
  declare function route<TId extends string, const TPath extends string, TActionResult, TData>(definition: RouteWithActionAndLoader<TPath, TActionResult, TData, undefined, TId> & {
217
323
  id: TId;
218
324
  }): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;
@@ -262,6 +368,24 @@ declare function route<const TPath extends string>(definition: RouteWithoutLoade
262
368
  * ```
263
369
  */
264
370
  declare function routeState<TState>(): {
371
+ <TId extends string, TPath extends string, TActionResult, TData>(definition: PartialRouteWithActionAndLoader<TPath, TActionResult, TData, TId> & {
372
+ id: TId;
373
+ }): PartialRouteDefinition<TId, PathParams<TPath>, TState, TData>;
374
+ <TId extends string, TPath extends string>(definition: PartialRouteWithActionOnly<TPath, TId> & {
375
+ id: TId;
376
+ }): PartialRouteDefinition<TId, PathParams<TPath>, TState, undefined>;
377
+ <TId extends string, TData>(definition: PartialPathlessRouteWithLoader<TData, TId> & {
378
+ id: TId;
379
+ }): PartialRouteDefinition<TId, Record<string, never>, TState, TData>;
380
+ <TId extends string>(definition: PartialPathlessRouteWithoutLoader<TId> & {
381
+ id: TId;
382
+ }): PartialRouteDefinition<TId, Record<string, never>, TState, undefined>;
383
+ <TId extends string, TPath extends string, TData>(definition: PartialRouteWithLoader<TPath, TData, TId> & {
384
+ id: TId;
385
+ }): PartialRouteDefinition<TId, PathParams<TPath>, TState, TData>;
386
+ <TId extends string, TPath extends string>(definition: PartialRouteWithoutLoader<TPath, TId> & {
387
+ id: TId;
388
+ }): PartialRouteDefinition<TId, PathParams<TPath>, TState, undefined>;
265
389
  <TId extends string, TPath extends string, TActionResult, TData>(definition: RouteWithActionAndLoader<TPath, TActionResult, TData, TState, TId> & {
266
390
  id: TId;
267
391
  }): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;
@@ -288,5 +412,38 @@ declare function routeState<TState>(): {
288
412
  <TPath extends string>(definition: RouteWithoutLoader<TPath, TState>): OpaqueRouteDefinition;
289
413
  };
290
414
  //#endregion
291
- export { ExtractRouteState as a, PathParams as c, RouteComponentPropsWithData as d, RouteDefinition as f, routeState as h, ExtractRouteParams as i, RouteComponentProps as l, route as m, ExtractRouteData as n, LoaderArgs as o, TypefulOpaqueRouteDefinition as p, ExtractRouteId as r, OpaqueRouteDefinition as s, ActionArgs as t, RouteComponentPropsOf as u };
292
- //# sourceMappingURL=route-DRcgs0Pt.d.mts.map
415
+ //#region src/bindRoute.d.ts
416
+ type BindRouteOptions = {
417
+ component: ComponentType<any> | ReactNode;
418
+ children?: RouteDefinition[];
419
+ exact?: boolean;
420
+ requireChildren?: boolean;
421
+ };
422
+ /**
423
+ * Binds a component (and optionally children) to a partial route definition,
424
+ * producing a full route definition for use with `<Router />`.
425
+ *
426
+ * This is the Phase 2 of two-phase route definition for RSC:
427
+ * - Phase 1: `route()` without component → `PartialRouteDefinition` (shared module)
428
+ * - Phase 2: `bindRoute()` with component → full route definition (server module)
429
+ *
430
+ * @example
431
+ * ```tsx
432
+ * // Phase 1 (shared module)
433
+ * export const userRoute = route({
434
+ * id: "user",
435
+ * path: "/:userId",
436
+ * loader: fetchUser,
437
+ * });
438
+ *
439
+ * // Phase 2 (server module)
440
+ * const routes = [
441
+ * bindRoute(userRoute, { component: <UserProfile /> }),
442
+ * ];
443
+ * ```
444
+ */
445
+ declare function bindRoute<TId extends string, TParams extends Record<string, string>, TState, TData>(partialRoute: PartialRouteDefinition<TId, TParams, TState, TData>, binding: BindRouteOptions): TypefulOpaqueRouteDefinition<TId, TParams, TState, TData>;
446
+ declare function bindRoute(partialRoute: OpaqueRouteDefinition, binding: BindRouteOptions): OpaqueRouteDefinition;
447
+ //#endregion
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-DulMzi5X.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindRoute-DulMzi5X.d.mts","names":[],"sources":["../src/route.ts","../src/bindRoute.ts"],"mappings":"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"}
@@ -8,28 +8,6 @@ export function ApiHooksPage() {
8
8
  React hooks for accessing router state and navigation.
9
9
  </p>
10
10
 
11
- <article className="api-item">
12
- <h3>
13
- <code>useNavigate()</code>
14
- </h3>
15
- <p>Returns a function to programmatically navigate.</p>
16
- <CodeBlock language="tsx">{`import { useNavigate } from "@funstack/router";
17
-
18
- function MyComponent() {
19
- const navigate = useNavigate();
20
-
21
- // Navigate to a path
22
- navigate("/about");
23
-
24
- // Navigate with options
25
- navigate("/users/123", {
26
- replace: true, // Replace current history entry
27
- state: { from: "home" }, // Persistent state (survives back/forward)
28
- info: { referrer: "home" }, // Ephemeral info (only for this navigation)
29
- });
30
- }`}</CodeBlock>
31
- </article>
32
-
33
11
  <article className="api-item">
34
12
  <h3>
35
13
  <code>useLocation()</code>
@@ -43,7 +21,16 @@ function MyComponent() {
43
21
  console.log(location.pathname); // "/users/123"
44
22
  console.log(location.search); // "?tab=profile"
45
23
  console.log(location.hash); // "#section"
24
+ console.log(location.entryId); // NavigationHistoryEntry.id or null
25
+ console.log(location.entryKey); // NavigationHistoryEntry.key or null
46
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>
47
34
  </article>
48
35
 
49
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">
@@ -254,6 +254,58 @@ const productRoute = routeState<{ filter: string }>()({
254
254
  }`}</CodeBlock>
255
255
  </article>
256
256
 
257
+ <article className="api-item">
258
+ <h3>
259
+ <code>hardReload()</code>
260
+ </h3>
261
+ <p>
262
+ Performs a full page reload, bypassing the router's navigation
263
+ interception entirely. This is useful when you need a true browser
264
+ reload that the router should not intercept.
265
+ </p>
266
+ <CodeBlock language="tsx">{`import { hardReload } from "@funstack/router";
267
+
268
+ // Full page reload — bypasses the router and all blockers
269
+ hardReload();`}</CodeBlock>
270
+ </article>
271
+
272
+ <article className="api-item">
273
+ <h3>
274
+ <code>hardNavigate(url)</code>
275
+ </h3>
276
+ <p>
277
+ Performs a full page navigation to the given URL, bypassing the
278
+ router's navigation interception entirely. This triggers a real
279
+ browser navigation instead of a client-side route change, and skips
280
+ all blockers.
281
+ </p>
282
+ <CodeBlock language="tsx">{`import { hardNavigate } from "@funstack/router";
283
+
284
+ // Full page navigation — bypasses the router and all blockers
285
+ hardNavigate("/other-page");`}</CodeBlock>
286
+ <h4>Parameters</h4>
287
+ <table className="props-table">
288
+ <thead>
289
+ <tr>
290
+ <th>Parameter</th>
291
+ <th>Type</th>
292
+ <th>Description</th>
293
+ </tr>
294
+ </thead>
295
+ <tbody>
296
+ <tr>
297
+ <td>
298
+ <code>url</code>
299
+ </td>
300
+ <td>
301
+ <code>string</code>
302
+ </td>
303
+ <td>The URL to navigate to</td>
304
+ </tr>
305
+ </tbody>
306
+ </table>
307
+ </article>
308
+
257
309
  <article className="api-item">
258
310
  <h3>Server Entry Point</h3>
259
311
  <p>
@@ -294,6 +346,11 @@ const routes = [
294
346
  <li>
295
347
  <code>routeState</code> - Route definition helper with typed state
296
348
  </li>
349
+ <li>
350
+ <code>bindRoute</code> - Binds a component to a partial route
351
+ definition (see{" "}
352
+ <a href="/learn/rsc/route-features">Two-Phase Route Definitions</a>)
353
+ </li>
297
354
  <li>
298
355
  Types: <code>ActionArgs</code>, <code>LoaderArgs</code>,{" "}
299
356
  <code>RouteDefinition</code>, <code>PathParams</code>,{" "}
@@ -306,9 +363,11 @@ const routes = [
306
363
  , and hooks, use the main <code>@funstack/router</code> entry point.
307
364
  </p>
308
365
  <p>
309
- See the{" "}
310
- <a href="/learn/react-server-components">React Server Components</a>{" "}
311
- guide for a full walkthrough of using the server entry point.
366
+ See the <a href="/learn/rsc">React Server Components</a> guide for a
367
+ full walkthrough of using the server entry point, and the{" "}
368
+ <a href="/learn/rsc/route-features">Two-Phase Route Definitions</a>{" "}
369
+ guide for using <code>bindRoute()</code> to split route definitions
370
+ across the RSC boundary.
312
371
  </p>
313
372
  </article>
314
373
  </div>
@@ -8,7 +8,7 @@ export function ExamplesPage() {
8
8
  <section>
9
9
  <h2>Basic Routing</h2>
10
10
  <p>A simple example with home and about pages:</p>
11
- <CodeBlock language="tsx">{`import { Router, route, Outlet, useNavigate } from "@funstack/router";
11
+ <CodeBlock language="tsx">{`import { Router, route, Outlet } from "@funstack/router";
12
12
 
13
13
  function Home() {
14
14
  return <h1>Home Page</h1>;
@@ -19,13 +19,11 @@ function About() {
19
19
  }
20
20
 
21
21
  function Layout() {
22
- const navigate = useNavigate();
23
-
24
22
  return (
25
23
  <div>
26
24
  <nav>
27
- <button onClick={() => navigate("/")}>Home</button>
28
- <button onClick={() => navigate("/about")}>About</button>
25
+ <button onClick={() => navigation.navigate("/")}>Home</button>
26
+ <button onClick={() => navigation.navigate("/about")}>About</button>
29
27
  </nav>
30
28
  <Outlet />
31
29
  </div>
@@ -0,0 +1,84 @@
1
+ import { CodeBlock } from "../components/CodeBlock.js";
2
+
3
+ export function FaqPage() {
4
+ return (
5
+ <div className="page docs-page">
6
+ <h1>FAQ</h1>
7
+
8
+ <section>
9
+ <h2>Browser doesn't scroll to top after navigation</h2>
10
+ <p>
11
+ According to the Navigation API specification, the browser should
12
+ automatically scroll to the top of the page after a same-document
13
+ navigation. However, as of now, Chrome and Safari do not follow this
14
+ part of the spec.
15
+ </p>
16
+ <p>
17
+ As a workaround, you can listen to the <code>navigatesuccess</code>{" "}
18
+ event and scroll to the top manually:
19
+ </p>
20
+ <CodeBlock language="tsx">{`import { useEffect } from "react";
21
+
22
+ function App() {
23
+ useEffect(() => {
24
+ const navigation = window.navigation;
25
+ if (!navigation) {
26
+ return;
27
+ }
28
+ const controller = new AbortController();
29
+ navigation.addEventListener(
30
+ "navigatesuccess",
31
+ () => {
32
+ const transition = navigation.transition;
33
+ if (
34
+ transition.navigationType === "push" ||
35
+ transition.navigationType === "replace"
36
+ ) {
37
+ // Safari ignores scrolling immediately after navigation,
38
+ // so we wait a bit before scrolling
39
+ setTimeout(() => {
40
+ window.scrollTo(0, -1);
41
+ }, 10);
42
+ }
43
+ },
44
+ { signal: controller.signal },
45
+ );
46
+ return () => {
47
+ controller.abort();
48
+ };
49
+ }, []);
50
+
51
+ return <Router routes={routes} />;
52
+ }`}</CodeBlock>
53
+ </section>
54
+
55
+ <section>
56
+ <h2>
57
+ <code>location.href</code> and <code>location.reload()</code> doesn't
58
+ hard navigate
59
+ </h2>
60
+ <p>
61
+ When you use <code>location.href = "..."</code> or{" "}
62
+ <code>location.reload()</code> in an app with FUNSTACK Router, the
63
+ router intercepts the navigation and handles it as a client-side route
64
+ change instead of performing a full page reload.
65
+ </p>
66
+ <p>
67
+ This is because the Navigation API, which FUNSTACK Router is built on,
68
+ intercepts these navigations by default.
69
+ </p>
70
+ <p>
71
+ If you need a true hard navigation or reload that bypasses the router,
72
+ use <code>hardNavigate</code> or <code>hardReload</code>:
73
+ </p>
74
+ <CodeBlock language="tsx">{`import { hardReload, hardNavigate } from "@funstack/router";
75
+
76
+ // Full page reload — bypasses the router and all blockers
77
+ hardReload();
78
+
79
+ // Full page navigation — bypasses the router and all blockers
80
+ hardNavigate("/other-page");`}</CodeBlock>
81
+ </section>
82
+ </div>
83
+ );
84
+ }
@@ -20,10 +20,13 @@ yarn add @funstack/router`}</CodeBlock>
20
20
  <p>
21
21
  <code>@funstack/router</code> ships with an Agent skill that gives
22
22
  your coding assistant (Claude Code, Cursor, GitHub Copilot, etc.)
23
- knowledge about the router's API and best practices. After installing
24
- the package, run:
23
+ knowledge about the router's API and best practices. Run:
25
24
  </p>
26
- <CodeBlock language="bash">{`npx funstack-router-skill-installer`}</CodeBlock>
25
+ <CodeBlock language="bash">{`npx -p @funstack/router funstack-router-skill-installer
26
+ # or
27
+ pnpm dlx --package @funstack/router funstack-router-skill-installer
28
+ # or
29
+ yarn dlx -p @funstack/router funstack-router-skill-installer`}</CodeBlock>
27
30
  <p>
28
31
  The installer will guide you through setting up the skill for your
29
32
  preferred AI agent. Alternatively, if you prefer{" "}