@bractjs/bractjs 0.1.27 → 0.1.28

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.
Files changed (95) hide show
  1. package/README.md +242 -36
  2. package/bin/cli.ts +18 -1
  3. package/package.json +1 -1
  4. package/src/__tests__/codegen-write.test.ts +67 -0
  5. package/src/__tests__/codegen.test.ts +29 -2
  6. package/src/__tests__/compile-safety.test.ts +4 -0
  7. package/src/__tests__/csp.test.ts +10 -0
  8. package/src/__tests__/define-actions.test.ts +69 -0
  9. package/src/__tests__/env.test.ts +18 -0
  10. package/src/__tests__/fetcher-store.test.ts +67 -0
  11. package/src/__tests__/fixtures/app/root.tsx +7 -2
  12. package/src/__tests__/fixtures/app/routes/boom.tsx +9 -0
  13. package/src/__tests__/fixtures/app/routes/client-only.tsx +16 -0
  14. package/src/__tests__/fixtures/app/routes/counter.tsx +16 -0
  15. package/src/__tests__/fixtures/app/routes/data-only.tsx +16 -0
  16. package/src/__tests__/fixtures/app/routes/intent-demo.tsx +46 -0
  17. package/src/__tests__/fixtures/app/routes/protected-client-only.tsx +15 -0
  18. package/src/__tests__/fixtures/app/routes/search-demo.tsx +39 -0
  19. package/src/__tests__/form-data-helpers.test.ts +43 -0
  20. package/src/__tests__/integration.test.ts +56 -0
  21. package/src/__tests__/loader.test.ts +32 -1
  22. package/src/__tests__/nav-utils.test.ts +46 -0
  23. package/src/__tests__/prerender.test.ts +102 -0
  24. package/src/__tests__/programmatic-api.test.ts +20 -1
  25. package/src/__tests__/revalidation.test.ts +65 -0
  26. package/src/__tests__/route-lint.test.ts +74 -0
  27. package/src/__tests__/route-table.test.ts +33 -0
  28. package/src/__tests__/safe-validate.test.ts +96 -0
  29. package/src/__tests__/scroll-restoration.test.ts +66 -0
  30. package/src/__tests__/search-serializer.test.ts +42 -0
  31. package/src/__tests__/search-validation.test.ts +125 -0
  32. package/src/__tests__/security.test.ts +110 -1
  33. package/src/__tests__/selective-ssr.test.ts +85 -0
  34. package/src/__tests__/spa-mode.test.ts +77 -0
  35. package/src/__tests__/typed-routing.test.ts +51 -1
  36. package/src/build/bundler.ts +33 -0
  37. package/src/build/prerender.ts +88 -0
  38. package/src/build/route-lint.ts +49 -0
  39. package/src/client/ClientRouter.tsx +239 -47
  40. package/src/client/cache.ts +8 -0
  41. package/src/client/components/Await.tsx +9 -2
  42. package/src/client/components/Form.tsx +23 -34
  43. package/src/client/components/Link.tsx +80 -9
  44. package/src/client/components/Outlet.tsx +8 -2
  45. package/src/client/components/ScrollRestoration.tsx +125 -0
  46. package/src/client/entry.tsx +39 -2
  47. package/src/client/fetcher-store.ts +61 -0
  48. package/src/client/form-utils.ts +3 -0
  49. package/src/client/hooks/useActionData.ts +7 -3
  50. package/src/client/hooks/useFetcher.ts +116 -33
  51. package/src/client/hooks/useFetchers.ts +23 -0
  52. package/src/client/hooks/useLoaderData.ts +8 -4
  53. package/src/client/hooks/useLocation.ts +27 -0
  54. package/src/client/hooks/useNavigate.ts +11 -6
  55. package/src/client/hooks/useRevalidator.ts +26 -0
  56. package/src/client/hooks/useSearch.ts +73 -0
  57. package/src/client/hooks/useSearchParams.ts +7 -2
  58. package/src/client/nav-utils.ts +26 -0
  59. package/src/client/prefetch.ts +110 -15
  60. package/src/client/registry.ts +24 -0
  61. package/src/client/revalidation.ts +25 -0
  62. package/src/client/router.tsx +28 -1
  63. package/src/client/scroll-restoration.ts +48 -0
  64. package/src/client/search-serializer.ts +40 -0
  65. package/src/client/types.ts +6 -0
  66. package/src/codegen/route-codegen.ts +141 -8
  67. package/src/config/load.ts +21 -0
  68. package/src/dev/hmr-client.ts +3 -1
  69. package/src/dev/route-table.ts +27 -0
  70. package/src/dev/server.ts +106 -8
  71. package/src/dev/watcher.ts +25 -3
  72. package/src/index.ts +27 -3
  73. package/src/server/action-handler.ts +12 -3
  74. package/src/server/action-registry.ts +35 -0
  75. package/src/server/csp.ts +10 -1
  76. package/src/server/csrf.ts +26 -0
  77. package/src/server/env.ts +26 -5
  78. package/src/server/layout.ts +31 -1
  79. package/src/server/loader.ts +14 -8
  80. package/src/server/render.ts +18 -3
  81. package/src/server/request-handler.ts +50 -8
  82. package/src/server/search.ts +43 -0
  83. package/src/server/serve.ts +88 -1
  84. package/src/server/spa.ts +62 -0
  85. package/src/server/stream-handler.ts +10 -1
  86. package/src/server/validate.ts +85 -13
  87. package/src/shared/context.ts +5 -0
  88. package/src/shared/define-actions.ts +39 -0
  89. package/src/shared/form-data.ts +34 -0
  90. package/src/shared/route-types.ts +83 -2
  91. package/templates/new-app/app/root.tsx +2 -1
  92. package/templates/new-app/bractjs.config.ts +7 -12
  93. package/types/config.d.ts +21 -0
  94. package/types/index.d.ts +165 -9
  95. package/types/route.d.ts +62 -2
package/types/index.d.ts CHANGED
@@ -4,8 +4,11 @@ import type { ReactNode, Context, CSSProperties } from "react";
4
4
  export type {
5
5
  LoaderArgs, ActionArgs, MetaDescriptor, MetaArgs,
6
6
  LoaderFunction, ActionFunction, MetaFunction, RouteModule,
7
- RouteFile, Segment,
7
+ RouteFile, Segment, RouterLocation,
8
+ ShouldRevalidateArgs, ShouldRevalidateFunction,
9
+ LoaderData, ActionData,
8
10
  } from "./route.d.ts";
11
+ import type { RouterLocation, LoaderData, ActionData, ActionArgs } from "./route.d.ts";
9
12
 
10
13
  // ── Config + Server ───────────────────────────────────────────────────────
11
14
  export type { BractJSConfig, ServerManifest, BuildConfig } from "./config.d.ts";
@@ -18,9 +21,15 @@ export interface RenderOptions {
18
21
  actionData: unknown;
19
22
  params: Record<string, string>;
20
23
  pathname: string;
24
+ /** Validated search params — hydrates `useSearch()` on the client. */
25
+ search?: Record<string, unknown>;
21
26
  manifest: ServerManifest;
22
27
  meta: MetaDescriptor[];
23
28
  status?: number;
29
+ routeFile?: string;
30
+ nonce?: string;
31
+ /** Set when the document did not SSR the route component (selective SSR / SPA shell). */
32
+ ssrMode?: "client-only" | "data-only" | "spa";
24
33
  }
25
34
 
26
35
  export type OnErrorHook = (err: unknown, request?: Request) => Promise<void> | void;
@@ -64,6 +73,10 @@ export interface BractJSContextValue {
64
73
  params: Record<string, string>;
65
74
  pathname: string;
66
75
  manifest: RouteManifest;
76
+ /** The request's location, so `useLocation()` works during SSR (hash is always ""). */
77
+ location?: RouterLocation;
78
+ /** Validated search params, so `useSearch()` works during SSR. */
79
+ search?: Record<string, unknown>;
67
80
  }
68
81
  export declare const BractJSContext: Context<BractJSContextValue>;
69
82
  export declare function BractJSProvider(props: { value: BractJSContextValue; children: ReactNode }): ReactNode;
@@ -129,6 +142,38 @@ export declare function validate<T>(
129
142
  input: FormData | Record<string, unknown>,
130
143
  ): Promise<T>;
131
144
 
145
+ export type SafeValidateResult<T> =
146
+ | { ok: true; data: T }
147
+ | { ok: false; fieldErrors: FieldErrors; firstError: string };
148
+ /** Non-throwing validate(): returns a result instead of throwing a 400. */
149
+ export declare function safeValidate<T>(
150
+ schema: { safeParse?(i: unknown): unknown } | { parse(i: unknown): T },
151
+ input: FormData | Record<string, unknown>,
152
+ ): Promise<SafeValidateResult<T>>;
153
+ /** True for the 400 Response thrown by validate()/searchSchema validation. */
154
+ export declare function isValidationResponse(value: unknown): value is Response;
155
+ /** Parse the `{ errors }` body of a validation 400 into field errors + first message. */
156
+ export declare function readValidationError(
157
+ res: Response,
158
+ ): Promise<{ fieldErrors: FieldErrors; firstError: string }>;
159
+
160
+ // ── FormData helpers ──────────────────────────────────────────────────────
161
+ /** String field from FormData; "" when missing or a File. */
162
+ export declare function formText(formData: FormData, key: string): string;
163
+ /** Collect string fields from FormData (all, or a named subset). */
164
+ export declare function formValues(formData: FormData, keys?: string[]): Record<string, string>;
165
+
166
+ // ── Search-param validation ───────────────────────────────────────────────
167
+ /** URLSearchParams → plain object; repeated keys collapse into arrays. */
168
+ export declare function searchParamsToObject(sp: URLSearchParams): Record<string, string | string[]>;
169
+ /**
170
+ * Validate a URL's search params against a route's `searchSchema`. No schema →
171
+ * the raw string record; failure → throws a 400 Response with field errors.
172
+ */
173
+ export declare function validateSearch(schema: unknown, url: URL): Promise<Record<string, unknown>>;
174
+ /** Serialize a search object back into a query string (leading `?`, or ""). */
175
+ export declare function serializeSearch(search: Record<string, unknown>): string;
176
+
132
177
  // ── Typed-routing registration seam ───────────────────────────────────────
133
178
  // Mirror of src/client/registry.ts. Augment `Register` (done by `bractjs codegen`
134
179
  // in app/route-types.gen.ts) to make <Link>/useNavigate/useParams/useSearchParams
@@ -151,10 +196,21 @@ export type RegisteredParamsMap =
151
196
  Register extends { routes: { params: infer P } } ? P : Record<string, Record<string, string>>;
152
197
  export type RegisteredSearchMap =
153
198
  Register extends { routes: { search: infer S } } ? S : Record<string, Record<string, string>>;
199
+ export type RegisteredSearchOutputMap =
200
+ Register extends { routes: { searchOutput: infer S } } ? S : Record<string, Record<string, unknown>>;
154
201
  export type ParamsFor<TTo> =
155
202
  TTo extends keyof RegisteredParamsMap ? RegisteredParamsMap[TTo] : Record<string, string>;
156
203
  export type SearchFor<TTo> =
157
204
  TTo extends keyof RegisteredSearchMap ? RegisteredSearchMap[TTo] : Record<string, string>;
205
+ /** Validated (schema-output) search object for a specific route literal. */
206
+ export type SearchOutputFor<TTo> =
207
+ TTo extends keyof RegisteredSearchOutputMap ? RegisteredSearchOutputMap[TTo] : Record<string, unknown>;
208
+ /** Infer the output type of a Zod/Valibot-compatible schema (duck-typed z.infer). */
209
+ export type InferSchemaOutput<S> =
210
+ S extends { parse(input: unknown): infer T } ? T :
211
+ S extends { safeParse(input: unknown): infer R }
212
+ ? (Awaited<R> extends { data?: infer T } ? NonNullable<T> : Record<string, unknown>)
213
+ : Record<string, unknown>;
158
214
  export declare function buildPath(pattern: string, params: Record<string, string | number>): string;
159
215
 
160
216
  // ── Client components ─────────────────────────────────────────────────────
@@ -165,18 +221,37 @@ export declare function Outlet(): ReactNode;
165
221
  export type LinkProps<TTo extends RegisteredRoutes = RegisteredRoutes> = {
166
222
  to: TTo | (string & {});
167
223
  params?: ParamsFor<TTo>;
168
- prefetch?: "hover" | "none";
224
+ /** Search params for the target, typed by its `searchSchema` (replaces any query in `to`). */
225
+ search?: Partial<SearchOutputFor<TTo>>;
226
+ /** When to prefetch the target's chunk + loader data. Default "none". */
227
+ prefetch?: "none" | "intent" | "hover" | "viewport" | "render";
169
228
  viewTransition?: boolean;
229
+ /** Replace the current history entry instead of pushing. */
230
+ replace?: boolean;
170
231
  children?: ReactNode;
171
232
  className?: string;
172
233
  [key: string]: unknown;
173
234
  };
174
235
  export declare function Link<TTo extends RegisteredRoutes = RegisteredRoutes>(props: LinkProps<TTo>): ReactNode;
175
236
 
176
- export interface FormProps { method?: "post" | "put" | "delete"; action?: string; children?: ReactNode; [key: string]: unknown; }
237
+ export interface ScrollRestorationProps {
238
+ /** Derive the storage key for a location. Default: `location.key`. */
239
+ getKey?: (location: RouterLocation) => string;
240
+ storageKey?: string;
241
+ }
242
+ /** Restores scroll on back/forward, scrolls to top (or `#hash`) on new navigations. Render once in root.tsx. */
243
+ export declare function ScrollRestoration(props?: ScrollRestorationProps): null;
244
+
245
+ export interface FormProps { method?: "post" | "put" | "delete"; action?: string; /** Renders a hidden `intent` input (pairs with defineActions()). */ intent?: string; children?: ReactNode; [key: string]: unknown; }
177
246
  export declare function Form(props: FormProps): ReactNode;
178
247
 
179
- export interface AwaitProps<T> { resolve: Promise<T>; fallback: ReactNode; children: (data: T) => ReactNode; }
248
+ // ── defineActions (intent dispatch) ───────────────────────────────────────
249
+ /** Compose a route action from per-intent handlers, dispatching on the form's `intent` field. */
250
+ export declare function defineActions<M extends Record<string, (args: ActionArgs) => unknown>>(
251
+ handlers: M,
252
+ ): (args: ActionArgs) => Promise<Awaited<ReturnType<M[keyof M]>> | Response>;
253
+
254
+ export interface AwaitProps<T> { resolve: Promise<T> | Deferred<T>; fallback: ReactNode; children: (data: T) => ReactNode; }
180
255
  export declare function Await<T>(props: AwaitProps<T>): ReactNode;
181
256
 
182
257
  export type ImageFormat = "webp" | "avif" | "jpeg" | "png";
@@ -197,30 +272,90 @@ export interface ImageProps {
197
272
  export declare function Image(props: ImageProps): ReactNode;
198
273
 
199
274
  // ── Client hooks ──────────────────────────────────────────────────────────
200
- export declare function useLoaderData<T = unknown>(): T;
201
- export declare function useActionData<T = unknown>(): T | null;
275
+ // Pass the loader/action function type to infer the data — useLoaderData<typeof loader>().
276
+ export declare function useLoaderData<T = unknown>(): LoaderData<T>;
277
+ export declare function useActionData<T = unknown>(): ActionData<T> | null;
278
+ /** The current location — reactive on the client, request-derived during SSR. */
279
+ export declare function useLocation(): RouterLocation;
202
280
  export declare function useParams<TTo extends string>(): ParamsFor<TTo>;
203
281
  export declare function useParams<T extends Record<string, string> = Record<string, string>>(): T;
204
282
  export type NavigationState = "idle" | "loading" | "submitting";
205
283
  export declare function useNavigation(): { state: NavigationState };
206
284
 
207
- export interface NavigateOptions<TTo extends RegisteredRoutes = RegisteredRoutes> { params?: ParamsFor<TTo>; }
285
+ export interface NavigateOptions<TTo extends RegisteredRoutes = RegisteredRoutes> {
286
+ params?: ParamsFor<TTo>;
287
+ /** Search params for the target, typed by its `searchSchema` (replaces any query in `to`). */
288
+ search?: Partial<SearchOutputFor<TTo>>;
289
+ /** Replace the current history entry instead of pushing a new one. */
290
+ replace?: boolean;
291
+ /** Arbitrary history state, readable via `useLocation().state` after navigating. */
292
+ state?: unknown;
293
+ }
208
294
  export interface NavigateFn {
209
295
  <TTo extends RegisteredRoutes>(to: TTo | (string & {}), options?: NavigateOptions<TTo>): Promise<void>;
210
296
  }
211
297
  export declare function useNavigate(): NavigateFn;
298
+
299
+ // ── Fetchers ──────────────────────────────────────────────────────────────
300
+ export type FetcherState = "idle" | "loading" | "submitting";
301
+ export interface FetcherEntry {
302
+ key: string;
303
+ state: FetcherState;
304
+ data: unknown;
305
+ /** The submitted form data while a submission is in flight — the optimistic-UI source. */
306
+ formData?: FormData;
307
+ formMethod?: string;
308
+ }
309
+ export interface FetcherFormProps {
310
+ method?: "post" | "put" | "delete";
311
+ action?: string;
312
+ /** Renders a hidden `intent` input (pairs with defineActions()). */
313
+ intent?: string;
314
+ children?: ReactNode;
315
+ [key: string]: unknown;
316
+ }
212
317
  export interface FetcherResult {
213
318
  data: unknown;
214
- state: NavigationState;
319
+ state: FetcherState;
320
+ formData?: FormData;
321
+ formMethod?: string;
322
+ key: string;
215
323
  load(path: string): Promise<void>;
216
324
  submit(path: string, opts: { method: string; body: FormData | Record<string, string> }): Promise<void>;
325
+ /** A form that submits through this fetcher (no navigation, no history). */
326
+ Form: (props: FetcherFormProps) => ReactNode;
217
327
  }
218
328
  export interface StreamFetcherResult<T = unknown> {
329
+ /** @deprecated Never emitted — call `connect(actionId)` instead. Removed in 0.2. */
219
330
  events: AsyncGenerator<T>;
220
331
  connect(actionId: string): AsyncGenerator<T>;
221
332
  }
222
- export declare function useFetcher(): FetcherResult;
333
+ export interface UseFetcherOptions { key?: string; stream?: boolean }
334
+ export declare function useFetcher(opts?: { key?: string }): FetcherResult;
223
335
  export declare function useFetcher<T>(opts: { stream: true }): StreamFetcherResult<T>;
336
+ /** Every active fetcher — the cross-component view for optimistic UI. */
337
+ export declare function useFetchers(): FetcherEntry[];
338
+
339
+ // ── Revalidation ──────────────────────────────────────────────────────────
340
+ export interface Revalidator {
341
+ revalidate(): Promise<void>;
342
+ state: "idle" | "loading";
343
+ }
344
+ /** Manually re-run the active route's loaders (respects `shouldRevalidate`). */
345
+ export declare function useRevalidator(): Revalidator;
346
+
347
+ // ── Typed search ──────────────────────────────────────────────────────────
348
+ /** The current route's VALIDATED search params (its `searchSchema` output). */
349
+ export declare function useSearch<TTo extends string>(): SearchOutputFor<TTo>;
350
+ export declare function useSearch<T extends Record<string, unknown>>(): T;
351
+ export interface SetSearchOptions { replace?: boolean }
352
+ export type SetSearchFn<T extends Record<string, unknown>> = (
353
+ updater: Partial<T> | ((prev: T) => Partial<T>),
354
+ options?: SetSearchOptions,
355
+ ) => Promise<void>;
356
+ /** Merge a patch into the current search params and soft-navigate (loaders re-run). */
357
+ export declare function useSetSearch<TTo extends string>(): SetSearchFn<SearchOutputFor<TTo>>;
358
+ export declare function useSetSearch<T extends Record<string, unknown>>(): SetSearchFn<T>;
224
359
 
225
360
  export interface SearchParamsResult<T extends Record<string, string> = Record<string, string>> {
226
361
  searchParams: URLSearchParams;
@@ -316,3 +451,24 @@ export interface DevServer {
316
451
  export declare function createDevServer(options?: DevServerOptions): Promise<DevServer>;
317
452
 
318
453
  export declare function loadUserConfig(): Promise<Partial<BractJSConfig>>;
454
+
455
+ /** Identity helper for bractjs.config.ts — wrap your default export for autocomplete + type-checking. */
456
+ export declare function defineConfig(config: Partial<BractJSConfig>): Partial<BractJSConfig>;
457
+
458
+ // ── Prerendering / SPA shell ──────────────────────────────────────────────
459
+ export interface PrerenderOptions {
460
+ prerender: string[] | (() => string[] | Promise<string[]>);
461
+ appDir?: string;
462
+ publicDir?: string;
463
+ buildDir?: string;
464
+ manifest?: ServerManifest;
465
+ }
466
+ export interface PrerenderResult { written: string[] }
467
+ /** Build-time prerendering (SSG): write HTML + /_data payloads under `<buildDir>/client/_prerender/`. */
468
+ export declare function runPrerender(options: PrerenderOptions): Promise<PrerenderResult>;
469
+ /** Render the SPA-mode document shell (config `ssr: false`). */
470
+ export declare function renderSpaShell(
471
+ appDir: string,
472
+ manifest: ServerManifest,
473
+ registry?: ModuleRegistry,
474
+ ): Promise<string>;
package/types/route.d.ts CHANGED
@@ -1,15 +1,47 @@
1
1
  import type { ComponentType } from "react";
2
2
 
3
- export interface LoaderArgs {
3
+ /** A parsed navigation location (see `useLocation`). `hash` is always "" during SSR. */
4
+ export interface RouterLocation {
5
+ pathname: string;
6
+ /** Raw query string including the leading `?`, or `""`. */
7
+ search: string;
8
+ /** Fragment including the leading `#`, or `""`. */
9
+ hash: string;
10
+ state: unknown;
11
+ key: string;
12
+ }
13
+
14
+ export interface LoaderArgs<TSearch extends Record<string, unknown> = Record<string, unknown>> {
4
15
  request: Request;
5
16
  params: Record<string, string>;
6
17
  context: Record<string, unknown>;
18
+ /**
19
+ * The request's search params, validated/coerced by the route's
20
+ * `searchSchema` export when present; otherwise the raw string record
21
+ * (repeated keys become arrays). Parameterize to skip the cast:
22
+ * `loader({ search }: LoaderArgs<BoardSearch>)`.
23
+ */
24
+ search: TSearch;
7
25
  }
8
26
 
9
- export interface ActionArgs extends LoaderArgs {
27
+ export interface ActionArgs<TSearch extends Record<string, unknown> = Record<string, unknown>>
28
+ extends LoaderArgs<TSearch> {
10
29
  formData: FormData;
11
30
  }
12
31
 
32
+ /**
33
+ * The data a route's loader resolves to, for typing `useLoaderData`. Pass the
34
+ * loader function type to infer it (`useLoaderData<typeof loader>()` →
35
+ * awaited return, `Response` excluded, `Deferred` fields preserved). A plain
36
+ * object type is returned as-is (back-compat).
37
+ */
38
+ export type LoaderData<T> = T extends (...args: never[]) => unknown
39
+ ? Exclude<Awaited<ReturnType<T>>, Response>
40
+ : T;
41
+
42
+ /** The data a route's action resolves to, for typing `useActionData`. See {@link LoaderData}. */
43
+ export type ActionData<T> = LoaderData<T>;
44
+
13
45
  export type MetaDescriptor =
14
46
  | { title: string }
15
47
  | { name: string; content: string }
@@ -35,17 +67,45 @@ export interface BeforeLoadArgs {
35
67
  params: Record<string, string>;
36
68
  context: Record<string, unknown>;
37
69
  location: { pathname: string; search: string };
70
+ /** Validated search params (server-side only; absent in the client-side guard). */
71
+ search?: Record<string, unknown>;
38
72
  }
39
73
 
40
74
  export type BeforeLoadFunction = (
41
75
  args: BeforeLoadArgs,
42
76
  ) => void | Response | Promise<void | Response>;
43
77
 
78
+ /**
79
+ * Decide whether loader data should be refetched (SWR background refetch and
80
+ * post-mutation revalidation). Return `args.defaultShouldRevalidate` (true)
81
+ * for the default behavior.
82
+ */
83
+ export interface ShouldRevalidateArgs {
84
+ currentUrl: URL;
85
+ nextUrl: URL;
86
+ formMethod?: string;
87
+ actionStatus?: number;
88
+ defaultShouldRevalidate: boolean;
89
+ }
90
+
91
+ export type ShouldRevalidateFunction = (args: ShouldRevalidateArgs) => boolean;
92
+
44
93
  export interface RouteModule<TLoader = unknown, TAction = unknown> {
45
94
  loader?: LoaderFunction<TLoader>;
46
95
  action?: ActionFunction<TAction>;
47
96
  meta?: MetaFunction<TLoader>;
48
97
  beforeLoad?: BeforeLoadFunction;
98
+ shouldRevalidate?: ShouldRevalidateFunction;
99
+ /** Zod/Valibot-compatible schema validating search params before loaders run (400 on failure). */
100
+ searchSchema?: unknown;
101
+ /**
102
+ * Selective SSR: `true` (default) full SSR; `"data-only"` loaders run on the
103
+ * server but the component renders client-only; `false` neither the route
104
+ * loader nor the component runs during document SSR (beforeLoad still does).
105
+ */
106
+ ssr?: boolean | "data-only";
107
+ /** SSR'd in the component's place for `ssr: false` / `"data-only"` routes. */
108
+ Fallback?: ComponentType;
49
109
  handle?: Record<string, unknown>;
50
110
  ErrorBoundary?: ComponentType<{ error: unknown }>;
51
111
  default?: ComponentType;