@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.
- package/README.md +242 -36
- package/bin/cli.ts +18 -1
- package/package.json +1 -1
- package/src/__tests__/codegen-write.test.ts +67 -0
- package/src/__tests__/codegen.test.ts +29 -2
- package/src/__tests__/compile-safety.test.ts +4 -0
- package/src/__tests__/csp.test.ts +10 -0
- package/src/__tests__/define-actions.test.ts +69 -0
- package/src/__tests__/env.test.ts +18 -0
- package/src/__tests__/fetcher-store.test.ts +67 -0
- package/src/__tests__/fixtures/app/root.tsx +7 -2
- package/src/__tests__/fixtures/app/routes/boom.tsx +9 -0
- package/src/__tests__/fixtures/app/routes/client-only.tsx +16 -0
- package/src/__tests__/fixtures/app/routes/counter.tsx +16 -0
- package/src/__tests__/fixtures/app/routes/data-only.tsx +16 -0
- package/src/__tests__/fixtures/app/routes/intent-demo.tsx +46 -0
- package/src/__tests__/fixtures/app/routes/protected-client-only.tsx +15 -0
- package/src/__tests__/fixtures/app/routes/search-demo.tsx +39 -0
- package/src/__tests__/form-data-helpers.test.ts +43 -0
- package/src/__tests__/integration.test.ts +56 -0
- package/src/__tests__/loader.test.ts +32 -1
- package/src/__tests__/nav-utils.test.ts +46 -0
- package/src/__tests__/prerender.test.ts +102 -0
- package/src/__tests__/programmatic-api.test.ts +20 -1
- package/src/__tests__/revalidation.test.ts +65 -0
- package/src/__tests__/route-lint.test.ts +74 -0
- package/src/__tests__/route-table.test.ts +33 -0
- package/src/__tests__/safe-validate.test.ts +96 -0
- package/src/__tests__/scroll-restoration.test.ts +66 -0
- package/src/__tests__/search-serializer.test.ts +42 -0
- package/src/__tests__/search-validation.test.ts +125 -0
- package/src/__tests__/security.test.ts +110 -1
- package/src/__tests__/selective-ssr.test.ts +85 -0
- package/src/__tests__/spa-mode.test.ts +77 -0
- package/src/__tests__/typed-routing.test.ts +51 -1
- package/src/build/bundler.ts +33 -0
- package/src/build/prerender.ts +88 -0
- package/src/build/route-lint.ts +49 -0
- package/src/client/ClientRouter.tsx +239 -47
- package/src/client/cache.ts +8 -0
- package/src/client/components/Await.tsx +9 -2
- package/src/client/components/Form.tsx +23 -34
- package/src/client/components/Link.tsx +80 -9
- package/src/client/components/Outlet.tsx +8 -2
- package/src/client/components/ScrollRestoration.tsx +125 -0
- package/src/client/entry.tsx +39 -2
- package/src/client/fetcher-store.ts +61 -0
- package/src/client/form-utils.ts +3 -0
- package/src/client/hooks/useActionData.ts +7 -3
- package/src/client/hooks/useFetcher.ts +116 -33
- package/src/client/hooks/useFetchers.ts +23 -0
- package/src/client/hooks/useLoaderData.ts +8 -4
- package/src/client/hooks/useLocation.ts +27 -0
- package/src/client/hooks/useNavigate.ts +11 -6
- package/src/client/hooks/useRevalidator.ts +26 -0
- package/src/client/hooks/useSearch.ts +73 -0
- package/src/client/hooks/useSearchParams.ts +7 -2
- package/src/client/nav-utils.ts +26 -0
- package/src/client/prefetch.ts +110 -15
- package/src/client/registry.ts +24 -0
- package/src/client/revalidation.ts +25 -0
- package/src/client/router.tsx +28 -1
- package/src/client/scroll-restoration.ts +48 -0
- package/src/client/search-serializer.ts +40 -0
- package/src/client/types.ts +6 -0
- package/src/codegen/route-codegen.ts +141 -8
- package/src/config/load.ts +21 -0
- package/src/dev/hmr-client.ts +3 -1
- package/src/dev/route-table.ts +27 -0
- package/src/dev/server.ts +106 -8
- package/src/dev/watcher.ts +25 -3
- package/src/index.ts +27 -3
- package/src/server/action-handler.ts +12 -3
- package/src/server/action-registry.ts +35 -0
- package/src/server/csp.ts +10 -1
- package/src/server/csrf.ts +26 -0
- package/src/server/env.ts +26 -5
- package/src/server/layout.ts +31 -1
- package/src/server/loader.ts +14 -8
- package/src/server/render.ts +18 -3
- package/src/server/request-handler.ts +50 -8
- package/src/server/search.ts +43 -0
- package/src/server/serve.ts +88 -1
- package/src/server/spa.ts +62 -0
- package/src/server/stream-handler.ts +10 -1
- package/src/server/validate.ts +85 -13
- package/src/shared/context.ts +5 -0
- package/src/shared/define-actions.ts +39 -0
- package/src/shared/form-data.ts +34 -0
- package/src/shared/route-types.ts +83 -2
- package/templates/new-app/app/root.tsx +2 -1
- package/templates/new-app/bractjs.config.ts +7 -12
- package/types/config.d.ts +21 -0
- package/types/index.d.ts +165 -9
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
export declare function
|
|
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> {
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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;
|