@ilha/router 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -246,7 +246,7 @@ If the active island is not found in the registry, falls back to plain SSR and e
246
246
 
247
247
  | Option | Type | Default | Description |
248
248
  | ---------- | --------- | ------- | ----------------------------------------------------- |
249
- | `snapshot` | `boolean` | `true` | Embed island state as `data-ilha-state` for hydration |
249
+ | `snapshot` | `boolean` | `false` | Embed island state as `data-ilha-state` for hydration |
250
250
 
251
251
  ---
252
252
 
@@ -396,10 +396,17 @@ router()
396
396
  Attaches the route's `+error` boundary so **loader** errors render through it — on the server (`renderResponse` returns the boundary's HTML with the error status) and on client navigations. Render errors are already handled by `wrapError` inside the island; this closes the gap for errors thrown before rendering starts. The FS-routing codegen wires the nearest `+error.ts` automatically; manual routers can call it directly. A throwing boundary falls back to the minimal inline error.
397
397
 
398
398
  ```ts
399
+ import { router } from "@ilha/router";
400
+ import { ilha, html } from "ilha";
401
+
399
402
  router()
400
403
  .route("/user/:id", userPage, userLoader)
401
404
  .errorBoundary("/user/:id", (err, route) =>
402
- ilha.render(() => `<h1>${err.status ?? 500}</h1><p>${err.message}</p>`),
405
+ ilha(
406
+ () =>
407
+ html`<h1>${err.status ?? 500}</h1>
408
+ <p>${err.message}</p>`,
409
+ ),
403
410
  );
404
411
  ```
405
412
 
@@ -443,8 +450,9 @@ Reactive — `true` while a client navigation (loader fetch + view swap) is in f
443
450
 
444
451
  ```ts
445
452
  import { navigating } from "@ilha/router";
453
+ import { ilha, html } from "ilha";
446
454
 
447
- const Spinner = ilha.render(() => (navigating() ? `<div class="bar" />` : ""));
455
+ const Spinner = ilha(() => (navigating() ? html`<div class="bar" />` : ""));
448
456
  ```
449
457
 
450
458
  ---
@@ -574,10 +582,11 @@ Returns reactive signal accessors for the current route state. Safe to call insi
574
582
 
575
583
  ```ts
576
584
  import { useRoute } from "@ilha/router";
585
+ import { ilha, html } from "ilha";
577
586
 
578
- const MyPage = ilha.render(() => {
587
+ const MyPage = ilha(() => {
579
588
  const { path, params, search, hash } = useRoute();
580
- return `<p>user id: ${params().id}</p>`;
589
+ return html`<p>user id: ${params().id}</p>`;
581
590
  });
582
591
  ```
583
592
 
@@ -685,10 +694,10 @@ A typed helper that returns the layout function as-is. Use it instead of the `sa
685
694
  ```ts
686
695
  // src/pages/+layout.ts
687
696
  import { defineLayout } from "@ilha/router";
688
- import ilha, { html } from "ilha";
697
+ import { ilha, html } from "ilha";
689
698
 
690
699
  export default defineLayout((children) =>
691
- ilha.render(
700
+ ilha(
692
701
  () => html`
693
702
  <nav>
694
703
  <a href="/">Home</a>
@@ -714,7 +723,7 @@ import { wrapError } from "@ilha/router";
714
723
  const safe = wrapError(myErrorHandler, myPage);
715
724
  ```
716
725
 
717
- > **Note:** Error boundaries wrap the _page island's render_, not the loader. Loader errors (thrown via `error()`) are surfaced through `.renderResponse()` — they do not currently route through `+error.ts` boundaries. Use `.renderResponse()` to handle loader errors at the HTTP layer.
726
+ > **Note:** Error boundaries wrap the _page island's render_, not the loader. Loader errors (thrown via `error()`) route through the nearest `+error.ts` / `.errorBoundary()` boundary on the server `renderResponse` returns the boundary's HTML with the error status, and on client navigations the boundary renders in place. `wrapError` covers render/mount throws inside the island only.
718
727
 
719
728
  ---
720
729
 
@@ -743,8 +752,16 @@ interface LoaderContext {
743
752
 
744
753
  type Loader<T> = (ctx: LoaderContext) => Promise<T> | T;
745
754
 
746
- // Extract the return type of a loader
747
- type InferLoader<L> = L extends Loader<infer T> ? Awaited<T> : never;
755
+ // Infer the complete page props produced by a loader
756
+ type InferLoader<L> = L extends Loader<infer T>
757
+ ? {
758
+ load: {
759
+ loading: boolean;
760
+ value: Awaited<T>;
761
+ error: Error | undefined;
762
+ };
763
+ }
764
+ : never;
748
765
 
749
766
  // Merge multiple loader return types — later loaders win on key collision
750
767
  type MergeLoaders<Ls extends readonly Loader<any>[]> = /* … */;
@@ -900,10 +917,10 @@ A `+layout.ts` wraps every page in its directory and all subdirectories. Layouts
900
917
  ```ts
901
918
  // src/pages/+layout.ts
902
919
  import { defineLayout } from "@ilha/router";
903
- import ilha, { html } from "ilha";
920
+ import { ilha, html } from "ilha";
904
921
 
905
922
  export default defineLayout((children) =>
906
- ilha.render(
923
+ ilha(
907
924
  () => html`
908
925
  <nav>
909
926
  <a href="/">Home</a>
@@ -920,10 +937,10 @@ Alternatively, using the explicit type annotation:
920
937
  ```ts
921
938
  // src/pages/+layout.ts — using satisfies (equivalent)
922
939
  import type { LayoutHandler } from "@ilha/router/vite";
923
- import ilha, { html } from "ilha";
940
+ import { ilha, html } from "ilha";
924
941
 
925
942
  export default ((children) =>
926
- ilha.render(
943
+ ilha(
927
944
  () => html`
928
945
  <nav>
929
946
  <a href="/">Home</a>
@@ -953,14 +970,14 @@ A page file can export a `load` function declared with the `loader()` helper. Th
953
970
  ```ts
954
971
  // src/pages/user/[id].ts
955
972
  import { loader } from "@ilha/router";
956
- import ilha from "ilha";
973
+ import { ilha, html } from "ilha";
957
974
 
958
975
  export const load = loader(async ({ params }) => {
959
976
  const user = await fetchUser(params.id);
960
977
  return { user };
961
978
  });
962
979
 
963
- export default ilha.input<{ user: User }>().render((input) => `<h1>${input.user.name}</h1>`);
980
+ export default ilha<{ user: User }>(({ user }) => html`<h1>${user.name}</h1>`);
964
981
  ```
965
982
 
966
983
  The `load` export must be declared with the `loader()` helper so the Vite plugin can identify it via export name.
@@ -989,14 +1006,14 @@ A page or layout can export a `clientLoad` function that runs **in the browser**
989
1006
  ```ts
990
1007
  // src/pages/dashboard.ts
991
1008
  import { loader } from "@ilha/router";
992
- import ilha from "ilha";
1009
+ import { ilha } from "ilha";
993
1010
 
994
1011
  export const clientLoad = loader(async ({ signal }) => {
995
1012
  const stats = await fetch("/api/stats", { signal }).then((r) => r.json());
996
1013
  return { stats };
997
1014
  });
998
1015
 
999
- export default ilha.input<{ stats: Stats }>().render(/* */);
1016
+ export default ilha<{ stats: Stats }>(({ stats }) => html`<pre>${JSON.stringify(stats)}</pre>`);
1000
1017
  ```
1001
1018
 
1002
1019
  Rules and caveats:
@@ -1014,17 +1031,17 @@ A `+error.ts` catches any error thrown during rendering of pages in its director
1014
1031
  ```ts
1015
1032
  // src/pages/+error.ts
1016
1033
  import type { ErrorHandler } from "@ilha/router/vite";
1017
- import ilha from "ilha";
1034
+ import { ilha, html } from "ilha";
1018
1035
 
1019
1036
  export default ((error, route) =>
1020
- ilha.render(
1021
- () => `
1022
- <div class="error">
1023
- <h1>${error.status ?? 500}</h1>
1024
- <p>${error.message}</p>
1025
- <p>Path: ${route.path}</p>
1026
- </div>
1027
- `,
1037
+ ilha(
1038
+ () => html`
1039
+ <div class="error">
1040
+ <h1>${error.status ?? 500}</h1>
1041
+ <p>${error.message}</p>
1042
+ <p>Path: ${route.path}</p>
1043
+ </div>
1044
+ `,
1028
1045
  )) satisfies ErrorHandler;
1029
1046
  ```
1030
1047
 
package/dist/head.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Render-scoped head collection and serialization.
3
+ *
4
+ * Loaders and render-time `head()` calls push `HeadInput` entries into a
5
+ * store scoped to the current render (`withHeadStore`); `serializeHead`
6
+ * turns the collected entries into document-shell fragments.
7
+ */
8
+ /**
9
+ * Serializable description of `<head>` (and html/body attributes) contributed
10
+ * by a loader or a render-time `head()` call. Deliberately a plain POJO — Tier
11
+ * 1 head management is SSR-only, so there is no reactive wrapper. Dedup keys
12
+ * mirror unhead so a later move to a runtime head manager stays a drop-in.
13
+ */
14
+ export interface HeadInput {
15
+ title?: string;
16
+ /** Wrap the resolved title. The last template in merge order wins. */
17
+ titleTemplate?: string | ((title?: string) => string);
18
+ meta?: Array<Record<string, string>>;
19
+ link?: Array<Record<string, string>>;
20
+ /**
21
+ * Inline script bodies are emitted raw in SSR (`serializeHead`). Must be trusted
22
+ * app code and must not contain a literal `</script>` sequence.
23
+ */
24
+ script?: Array<Record<string, string> & {
25
+ children?: string;
26
+ }>;
27
+ htmlAttrs?: Record<string, string>;
28
+ bodyAttrs?: Record<string, string>;
29
+ }
30
+ /** Serialized head fragments ready to inject into a document shell. */
31
+ export interface SerializedHead {
32
+ /** Markup for inside `<head>` (title, meta, link, script). */
33
+ headTags: string;
34
+ /** Attribute string for the `<html>` tag (leading space included). */
35
+ htmlAttrs: string;
36
+ /** Attribute string for the `<body>` tag (leading space included). */
37
+ bodyAttrs: string;
38
+ }
39
+ export interface HeadStore {
40
+ entries: HeadInput[];
41
+ }
42
+ /**
43
+ * Contribute `<head>` data from inside an island's `.render()` body or a
44
+ * layout. During SSR this collects into the active render window; on the
45
+ * client, entries are collected when the router re-renders a route inside
46
+ * `withHeadStore` and then applied to `document`. Prefer a loader's `ctx.head`
47
+ * for data that depends on the request.
48
+ */
49
+ export declare function head(input: HeadInput): void;
50
+ export declare function cssEscapeAttr(value: string): string;
51
+ /**
52
+ * Apply merged head entries on client navigations. Updates `document.title` and
53
+ * managed meta/link nodes (`data-ilha-head`). Script tags from HeadInput are
54
+ * SSR-only and are not re-injected here. Removes managed tags from the previous
55
+ * route that are not part of this navigation's set.
56
+ */
57
+ export declare function applyHeadEntriesToDocument(entries: HeadInput[]): void;
58
+ export declare function withHeadStore<T>(store: HeadStore, fn: () => T | Promise<T>): Promise<T>;
59
+ export declare function escapeHeadAttr(value: unknown): string;
60
+ /** Escape text content for inline HTML (loader error messages etc.). */
61
+ export declare function escapeHtml(value: unknown): string;
62
+ /**
63
+ * Merge head entries in contribution order (loader first as the base, then
64
+ * render-time outer→inner layouts, then the page) and serialize. Later entries
65
+ * win on collision; the last `titleTemplate` wraps the resolved title.
66
+ */
67
+ export declare function serializeHead(entries: HeadInput[]): SerializedHead;
package/dist/http.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Standalone HTTP response helper for custom server handlers.
3
+ */
4
+ import type { SerializedHead } from "./head";
5
+ export interface HttpResponseOptions {
6
+ status?: number;
7
+ headers?: HeadersInit;
8
+ /**
9
+ * CSP nonce for inline scripts. When set (and `contentSecurityPolicy` is
10
+ * not), a conservative default CSP is emitted with `'nonce-${nonce}'` for
11
+ * `script-src` — pass the same nonce to head `<script nonce=…>` tags.
12
+ */
13
+ cspNonce?: string;
14
+ /** Full `Content-Security-Policy` string; overrides the nonce-derived default. */
15
+ contentSecurityPolicy?: string;
16
+ }
17
+ /**
18
+ * Build an HTTP `Response` for SSR output with sensible security headers:
19
+ * `Content-Type: text/html`, `X-Content-Type-Options: nosniff`,
20
+ * `Referrer-Policy`, `Cache-Control: no-store`, and an optional CSP. This is
21
+ * a low-level helper — prefer {@link RouterBuilder.respond} for the full
22
+ * render+head+headers pipeline.
23
+ */
24
+ export declare function httpResponse(body: string | null, options?: HttpResponseOptions): Response;
25
+ export declare const EMPTY_HEAD: SerializedHead;
26
+ /**
27
+ * Options for {@link RouterBuilder.respond}.
28
+ */
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import type { Island, HydratableOptions } from "ilha";
2
+ import { head, type HeadInput, type SerializedHead } from "./head";
3
+ import { type HttpResponseOptions } from "./http";
2
4
  export { setHistoryMode, getHistoryMode } from "./hash";
3
5
  export type { HistoryMode } from "./hash";
4
6
  export interface RouteRecord {
5
7
  pattern: string;
6
- island: Island<any, any>;
8
+ island: Island<any>;
7
9
  /** Merged loader chain (layouts outer→inner, then page) — `undefined` if no loaders. */
8
10
  loader?: Loader<any>;
9
11
  /** True when the route has a server-side loader, even if the client only has a marker. */
@@ -20,39 +22,8 @@ export interface AppError {
20
22
  status?: number;
21
23
  stack?: string;
22
24
  }
23
- export type LayoutHandler = (children: Island<any, any>) => Island<any, any>;
24
- export type ErrorHandler = (error: AppError, route: RouteSnapshot) => Island<any, any>;
25
- /**
26
- * Serializable description of `<head>` (and html/body attributes) contributed
27
- * by a loader or a render-time `head()` call. Deliberately a plain POJO — Tier
28
- * 1 head management is SSR-only, so there is no reactive wrapper. Dedup keys
29
- * mirror unhead so a later move to a runtime head manager stays a drop-in.
30
- */
31
- export interface HeadInput {
32
- title?: string;
33
- /** Wrap the resolved title. The last template in merge order wins. */
34
- titleTemplate?: string | ((title?: string) => string);
35
- meta?: Array<Record<string, string>>;
36
- link?: Array<Record<string, string>>;
37
- /**
38
- * Inline script bodies are emitted raw in SSR (`serializeHead`). Must be trusted
39
- * app code and must not contain a literal `</script>` sequence.
40
- */
41
- script?: Array<Record<string, string> & {
42
- children?: string;
43
- }>;
44
- htmlAttrs?: Record<string, string>;
45
- bodyAttrs?: Record<string, string>;
46
- }
47
- /** Serialized head fragments ready to inject into a document shell. */
48
- export interface SerializedHead {
49
- /** Markup for inside `<head>` (title, meta, link, script). */
50
- headTags: string;
51
- /** Attribute string for the `<html>` tag (leading space included). */
52
- htmlAttrs: string;
53
- /** Attribute string for the `<body>` tag (leading space included). */
54
- bodyAttrs: string;
55
- }
25
+ export type LayoutHandler = (children: Island<any>) => Island<any>;
26
+ export type ErrorHandler = (error: AppError, route: RouteSnapshot) => Island<any>;
56
27
  export interface LoaderContext {
57
28
  params: Record<string, string>;
58
29
  request: Request;
@@ -74,8 +45,15 @@ export declare function loader<T>(fn: Loader<T>): Loader<T>;
74
45
  export declare namespace loader {
75
46
  var client: <T>(fn: Loader<T>) => Loader<T>;
76
47
  }
77
- /** Extract the return type of a loader. */
78
- export type InferLoader<L> = L extends Loader<infer T> ? Awaited<T> : never;
48
+ /** Infer the page props produced by a loader. */
49
+ export type InferLoader<L> = L extends Loader<infer T> ? {
50
+ load: {
51
+ loading: boolean;
52
+ value: Awaited<T>;
53
+ error: Error | undefined;
54
+ };
55
+ } : never;
56
+ type InferLoaderValue<L> = L extends Loader<infer T> ? Awaited<T> : never;
79
57
  /**
80
58
  * Merge multiple loader return types into a single object type.
81
59
  * Later loaders override earlier ones on key collision — matching runtime merge.
@@ -86,7 +64,7 @@ export type InferLoader<L> = L extends Loader<infer T> ? Awaited<T> : never;
86
64
  export type MergeLoaders<Ls extends readonly Loader<any>[]> = Ls extends readonly [
87
65
  infer First extends Loader<any>,
88
66
  ...infer Rest extends readonly Loader<any>[]
89
- ] ? Rest extends readonly [] ? InferLoader<First> : Omit<InferLoader<First>, keyof MergeLoaders<Rest>> & MergeLoaders<Rest> : {};
67
+ ] ? Rest extends readonly [] ? InferLoaderValue<First> : Omit<InferLoaderValue<First>, keyof MergeLoaders<Rest>> & MergeLoaders<Rest> : {};
90
68
  export declare class Redirect {
91
69
  readonly __ilhaRedirect: true;
92
70
  readonly to: string;
@@ -111,8 +89,8 @@ export declare function error(status: number, message: string): never;
111
89
  * `Redirect` or `LoaderError`, the composed loader re-throws it unchanged.
112
90
  */
113
91
  export declare function composeLoaders<Ls extends readonly Loader<any>[]>(loaders: Ls): Loader<MergeLoaders<Ls>>;
114
- export declare function wrapLayout(layout: LayoutHandler, page: Island<any, any>): Island<any, any>;
115
- export declare function wrapError(handler: ErrorHandler, page: Island<any, any>): Island<any, any>;
92
+ export declare function wrapLayout(layout: LayoutHandler, page: Island<any>): Island<any>;
93
+ export declare function wrapError(handler: ErrorHandler, page: Island<any>): Island<any>;
116
94
  export declare function defineLayout(layout: LayoutHandler): LayoutHandler;
117
95
  export interface NavigateOptions {
118
96
  replace?: boolean;
@@ -144,7 +122,7 @@ export interface RouterOptions {
144
122
  * Island rendered when no route matches the current URL — both on the
145
123
  * server (with a 404 status) and in the client `RouterView`.
146
124
  */
147
- notFound?: Island<any, any>;
125
+ notFound?: Island<any>;
148
126
  /**
149
127
  * Allow loader `redirect()` targets pointing at other origins. When `false`
150
128
  * (default), absolute cross-origin redirect targets are rejected with a 500
@@ -184,7 +162,7 @@ export interface HydrateOptions {
184
162
  }
185
163
  export interface MountOptions {
186
164
  hydrate?: boolean;
187
- registry?: Record<string, Island<any, any>>;
165
+ registry?: Record<string, Island<any>>;
188
166
  /**
189
167
  * When `true` (default), internal `<a>` clicks are intercepted for
190
168
  * client-side navigation. Set to `false` for MPA-style full-page navigations.
@@ -214,7 +192,7 @@ export interface RouterBuilder {
214
192
  * (layout loaders outer→inner followed by the page loader) produced by
215
193
  * the FS-routing codegen.
216
194
  */
217
- route(pattern: string, island: Island<any, any>, loader?: Loader<any>): RouterBuilder;
195
+ route(pattern: string, island: Island<any>, loader?: Loader<any>): RouterBuilder;
218
196
  /**
219
197
  * Attach (or replace) a loader on an already-registered route pattern.
220
198
  * Used by the `ilha:loaders` virtual module to wire server-only loaders
@@ -253,13 +231,13 @@ export interface RouterBuilder {
253
231
  prime(): void;
254
232
  mount(target: string | Element, options?: MountOptions): () => void;
255
233
  render(url: string | URL): string;
256
- renderHydratable(urlOrRequest: string | URL | Request, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<string>;
234
+ renderHydratable(urlOrRequest: string | URL | Request, registry: Record<string, Island<any>>, options?: HydratableRenderOptions, request?: Request): Promise<string>;
257
235
  /**
258
236
  * Like `renderHydratable` but surfaces loader redirects and errors as
259
237
  * structured responses instead of baking them into HTML. Prefer this from
260
238
  * host server code so you can emit proper 302 / 4xx responses.
261
239
  */
262
- renderResponse(urlOrRequest: string | URL | Request, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<RenderResponse>;
240
+ renderResponse(urlOrRequest: string | URL | Request, registry: Record<string, Island<any>>, options?: HydratableRenderOptions, request?: Request): Promise<RenderResponse>;
263
241
  /**
264
242
  * Run the loader chain for a given URL without rendering. Backs the
265
243
  * `/__ilha/loader` endpoint that the host server handler
@@ -286,20 +264,20 @@ export interface RouterBuilder {
286
264
  * loader errors, and security headers. `request` (or a URL string) selects
287
265
  * the route; the optional `shell` injects `head` tags into a document.
288
266
  */
289
- respond(urlOrRequest: string | URL | Request, registry: Record<string, Island<any, any>>, options?: RespondOptions): Promise<Response>;
267
+ respond(urlOrRequest: string | URL | Request, registry: Record<string, Island<any>>, options?: RespondOptions): Promise<Response>;
290
268
  /**
291
269
  * Hydrate the application - combines prime(), mount(), and router.mount() into one call.
292
270
  * @param registry - The island registry from ilha:registry
293
271
  * @param options - Optional root element (defaults to document.body) and router target (defaults to root)
294
272
  * @returns Cleanup function
295
273
  */
296
- hydrate(registry: Record<string, Island<any, any>>, options?: HydrateOptions): () => void;
274
+ hydrate(registry: Record<string, Island<any>>, options?: HydrateOptions): () => void;
297
275
  /**
298
276
  * Hydrate islands on the current pre-rendered page without mounting a route
299
277
  * view or enabling client navigation. Intended for `static` mode: each page
300
278
  * is a self-contained HTML file; only interactive islands need activation.
301
279
  */
302
- hydrateStatic(registry: Record<string, Island<any, any>>, options?: {
280
+ hydrateStatic(registry: Record<string, Island<any>>, options?: {
303
281
  root?: Element;
304
282
  }): () => void;
305
283
  }
@@ -311,9 +289,13 @@ export declare const LOADER_ENDPOINT = "/__ilha/loader";
311
289
  * navigation) or is superseded by another prefetch.
312
290
  */
313
291
  export declare function prefetch(pathWithSearch: string): void;
292
+ /** @internal Internal reactive route-state accessor; use the aggregate `useRoute()` for the public surface. */
314
293
  export declare function routePath(value?: string): string;
294
+ /** @internal Internal reactive route-state accessor; use the aggregate `useRoute()` for the public surface. */
315
295
  export declare function routeParams(value?: Record<string, string>): Record<string, string>;
296
+ /** @internal Internal reactive route-state accessor; use the aggregate `useRoute()` for the public surface. */
316
297
  export declare function routeSearch(value?: string): string;
298
+ /** @internal Internal reactive route-state accessor; use the aggregate `useRoute()` for the public surface. */
317
299
  export declare function routeHash(value?: string): string;
318
300
  /** Reactive: `true` while a client navigation (loader fetch + view swap) is in flight. */
319
301
  export declare function navigating(): boolean;
@@ -390,12 +372,11 @@ export interface LinkInterceptionOptions {
390
372
  prefetch?: boolean;
391
373
  }
392
374
  export declare function enableLinkInterception(root?: Element | Document, options?: LinkInterceptionOptions): () => void;
393
- export declare const RouterView: Island<{
394
- [x: string]: unknown;
395
- }, {}>;
375
+ export declare const RouterView: Island<unknown>;
396
376
  export declare const RouterLink: Island<{
397
- [x: string]: unknown;
398
- }, Omit<Omit<{}, K> & Record<"href", string>, "label"> & Record<"label", string>>;
377
+ href?: string;
378
+ label?: string;
379
+ }>;
399
380
  export interface IsActiveOptions {
400
381
  /**
401
382
  * When `false`, `isActive("/docs")` also matches nested paths like
@@ -405,43 +386,6 @@ export interface IsActiveOptions {
405
386
  exact?: boolean;
406
387
  }
407
388
  export declare function isActive(pattern: string, options?: IsActiveOptions): boolean;
408
- /**
409
- * Contribute `<head>` data from inside an island's `.render()` body or a
410
- * layout. During SSR this collects into the active render window; on the
411
- * client, entries are collected when the router re-renders a route inside
412
- * `withHeadStore` and then applied to `document`. Prefer a loader's `ctx.head`
413
- * for data that depends on the request.
414
- */
415
- export declare function head(input: HeadInput): void;
416
- /**
417
- * Merge head entries in contribution order (loader first as the base, then
418
- * render-time outer→inner layouts, then the page) and serialize. Later entries
419
- * win on collision; the last `titleTemplate` wraps the resolved title.
420
- */
421
- export declare function serializeHead(entries: HeadInput[]): SerializedHead;
422
- export interface HttpResponseOptions {
423
- status?: number;
424
- headers?: HeadersInit;
425
- /**
426
- * CSP nonce for inline scripts. When set (and `contentSecurityPolicy` is
427
- * not), a conservative default CSP is emitted with `'nonce-${nonce}'` for
428
- * `script-src` — pass the same nonce to head `<script nonce=…>` tags.
429
- */
430
- cspNonce?: string;
431
- /** Full `Content-Security-Policy` string; overrides the nonce-derived default. */
432
- contentSecurityPolicy?: string;
433
- }
434
- /**
435
- * Build an HTTP `Response` for SSR output with sensible security headers:
436
- * `Content-Type: text/html`, `X-Content-Type-Options: nosniff`,
437
- * `Referrer-Policy`, `Cache-Control: no-store`, and an optional CSP. This is
438
- * a low-level helper — prefer {@link RouterBuilder.respond} for the full
439
- * render+head+headers pipeline.
440
- */
441
- export declare function httpResponse(body: string | null, options?: HttpResponseOptions): Response;
442
- /**
443
- * Options for {@link RouterBuilder.respond}.
444
- */
445
389
  export interface RespondOptions extends HydratableRenderOptions, HttpResponseOptions {
446
390
  /**
447
391
  * Wrap the rendered body with a document shell. Receives the serialized
@@ -452,9 +396,10 @@ export interface RespondOptions extends HydratableRenderOptions, HttpResponseOpt
452
396
  }
453
397
  /**
454
398
  * Validate a loader redirect target. Relative paths always pass; same-origin
455
- * absolute URLs collapse to a path; cross-origin targets are rejected unless
456
- * `allowExternal`. Protocol-relative (`//host`) and unparsable targets are
457
- * always rejected.
399
+ * absolute URLs collapse to a path; cross-origin targets (including
400
+ * protocol-relative `//host` URLs) are rejected unless `allowExternal` is set.
401
+ * Non-http(s) schemes, backslash/control-char smuggling, and unparsable
402
+ * targets are always rejected.
458
403
  */
459
404
  export declare function resolveRedirectTarget(to: string, base: URL, allowExternal: boolean): {
460
405
  ok: true;
@@ -473,12 +418,11 @@ declare const _default: {
473
418
  prefetch: typeof prefetch;
474
419
  beforeNavigate: typeof beforeNavigate;
475
420
  afterNavigate: typeof afterNavigate;
476
- RouterView: Island<{
477
- [x: string]: unknown;
478
- }, {}>;
421
+ RouterView: Island<unknown>;
479
422
  RouterLink: Island<{
480
- [x: string]: unknown;
481
- }, Omit<Omit<{}, K> & Record<"href", string>, "label"> & Record<"label", string>>;
423
+ href?: string;
424
+ label?: string;
425
+ }>;
482
426
  loader: typeof loader;
483
427
  redirect: typeof redirect;
484
428
  error: typeof error;
@@ -486,3 +430,5 @@ declare const _default: {
486
430
  head: typeof head;
487
431
  };
488
432
  export default _default;
433
+ export { head, serializeHead, type HeadInput, type SerializedHead } from "./head";
434
+ export { httpResponse, type HttpResponseOptions } from "./http";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import { A as useContext, C as routeHash, D as router, E as routeSearch, L as getHistoryMode, M as wrapError, N as wrapLayout, O as serializeHead, R as setHistoryMode, S as resolveRedirectTarget, T as routePath, _ as navigate, a as RouterView, b as prime, c as composeLoaders, d as error, f as head, g as loader, h as isActive, i as RouterLink, j as useRoute, k as src_default, l as defineLayout, m as invalidate, n as LoaderError, o as afterNavigate, p as httpResponse, r as Redirect, s as beforeNavigate, t as LOADER_ENDPOINT, u as enableLinkInterception, v as navigating, w as routeParams, x as redirect, y as prefetch } from "./src-BBsbD5vU.js";
1
+ import { A as wrapLayout, C as routePath, D as useContext, E as src_default, F as getHistoryMode, I as setHistoryMode, O as useRoute, P as httpResponse, S as routeParams, T as router, _ as prefetch, a as RouterView, b as resolveRedirectTarget, c as composeLoaders, d as error, f as invalidate, g as navigating, h as navigate, i as RouterLink, k as wrapError, l as defineLayout, m as loader, n as LoaderError, o as afterNavigate, p as isActive, r as Redirect, s as beforeNavigate, t as LOADER_ENDPOINT, u as enableLinkInterception, v as prime, w as routeSearch, x as routeHash, y as redirect } from "./src-B5dHU24f.js";
2
+ import { o as head, s as serializeHead } from "./snapshot-CsEaY6h_.js";
2
3
 
3
4
  export { LOADER_ENDPOINT, LoaderError, Redirect, RouterLink, RouterView, afterNavigate, beforeNavigate, composeLoaders, src_default as default, defineLayout, enableLinkInterception, error, getHistoryMode, head, httpResponse, invalidate, isActive, loader, navigate, navigating, prefetch, prime, redirect, resolveRedirectTarget, routeHash, routeParams, routePath, routeSearch, router, serializeHead, setHistoryMode, useContext, useRoute, wrapError, wrapLayout };