@kasperrt/smolter 0.4.1 → 0.4.3

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 (86) hide show
  1. package/.generated/client/deployment.ts +40 -25
  2. package/.generated/core/environment.ts +1 -0
  3. package/.generated/core/navigation.ts +1 -0
  4. package/.generated/core/resource-types.ts +4 -0
  5. package/.generated/core/resources.ts +2 -0
  6. package/.generated/core/responses.ts +12 -0
  7. package/.generated/core/utils.ts +5 -2
  8. package/.generated/files/index.ts +5 -3
  9. package/.generated/preact/await-fallback.tsx +33 -0
  10. package/.generated/preact/await.tsx +23 -5
  11. package/.generated/preact/client.tsx +15 -4
  12. package/.generated/preact/deferred-hydration.ts +14 -1
  13. package/.generated/preact/document.tsx +10 -0
  14. package/.generated/preact/index.ts +6 -2
  15. package/.generated/preact/lazy-route.ts +7 -0
  16. package/.generated/preact/navigation.tsx +6 -0
  17. package/.generated/preact/root-context.ts +4 -0
  18. package/.generated/preact/route-hooks.tsx +12 -2
  19. package/.generated/preact/route-types.ts +14 -0
  20. package/.generated/preact/router.tsx +3 -0
  21. package/.generated/react/data.ts +5 -1
  22. package/.generated/react/index.ts +11 -1
  23. package/.generated/react/outlet.ts +48 -0
  24. package/.generated/react/resources/components.tsx +12 -0
  25. package/.generated/server/application.tsx +13 -0
  26. package/.generated/server/assets.ts +7 -0
  27. package/.generated/server/handler.ts +3 -0
  28. package/.generated/server/http.ts +2 -0
  29. package/.generated/server/node.ts +5 -2
  30. package/.generated/server/preact.ts +5 -2
  31. package/.generated/vite/application/lazy-files.ts +10 -17
  32. package/.generated/vite/index.ts +14 -0
  33. package/CHANGELOG.md +18 -0
  34. package/README.md +38 -18
  35. package/dist/client/deployment.js +30 -22
  36. package/dist/core/environment.d.ts +1 -0
  37. package/dist/core/navigation.d.ts +1 -0
  38. package/dist/core/resource-types.d.ts +4 -0
  39. package/dist/core/resources.d.ts +2 -0
  40. package/dist/core/resources.js +2 -0
  41. package/dist/core/responses.d.ts +12 -0
  42. package/dist/core/responses.js +5 -0
  43. package/dist/core/utils.d.ts +5 -2
  44. package/dist/core/utils.js +1 -2
  45. package/dist/files/index.js +4 -3
  46. package/dist/preact/await-fallback.d.ts +4 -0
  47. package/dist/preact/await-fallback.js +29 -0
  48. package/dist/preact/await.d.ts +5 -1
  49. package/dist/preact/await.js +22 -3
  50. package/dist/preact/client.d.ts +9 -1
  51. package/dist/preact/client.js +8 -3
  52. package/dist/preact/deferred-hydration.d.ts +1 -1
  53. package/dist/preact/deferred-hydration.js +7 -1
  54. package/dist/preact/document.d.ts +10 -0
  55. package/dist/preact/document.js +10 -1
  56. package/dist/preact/index.d.ts +6 -2
  57. package/dist/preact/lazy-route.d.ts +2 -0
  58. package/dist/preact/lazy-route.js +6 -0
  59. package/dist/preact/navigation.d.ts +6 -0
  60. package/dist/preact/navigation.js +2 -0
  61. package/dist/preact/root-context.d.ts +1 -0
  62. package/dist/preact/route-hooks.d.ts +10 -0
  63. package/dist/preact/route-hooks.js +13 -3
  64. package/dist/preact/route-types.d.ts +14 -0
  65. package/dist/preact/router.d.ts +3 -0
  66. package/dist/preact/router.js +3 -1
  67. package/dist/react/data.d.ts +5 -1
  68. package/dist/react/index.d.ts +6 -1
  69. package/dist/react/index.js +2 -1
  70. package/dist/react/outlet.d.ts +16 -0
  71. package/dist/react/outlet.js +26 -0
  72. package/dist/react/resources/components.d.ts +12 -0
  73. package/dist/react/resources/components.js +11 -0
  74. package/dist/server/application.d.ts +3 -0
  75. package/dist/server/application.js +10 -0
  76. package/dist/server/assets.d.ts +2 -0
  77. package/dist/server/assets.js +6 -0
  78. package/dist/server/handler.d.ts +3 -0
  79. package/dist/server/http.d.ts +2 -0
  80. package/dist/server/http.js +2 -0
  81. package/dist/server/node.d.ts +5 -1
  82. package/dist/server/preact.d.ts +5 -1
  83. package/dist/vite/application/lazy-files.js +5 -11
  84. package/dist/vite/index.js +6 -0
  85. package/jsr.json +1 -1
  86. package/package.json +6 -5
@@ -1,51 +1,56 @@
1
1
  import { deploymentCheck } from "./deployment-check.js";
2
2
  //#region .generated/client/deployment.ts
3
+ var watchers = /* @__PURE__ */ new WeakMap();
3
4
  /** Check for deployment changes before navigation. Install before creating fetchers. Returns a cleanup function. */
4
5
  function watchDeployment(router, options) {
5
6
  if (!options || typeof window === "undefined") return () => {};
7
+ const state = watchers.get(router) ?? {
8
+ sequence: 0,
9
+ mutations: 0
10
+ };
11
+ state.cleanup?.();
12
+ watchers.set(router, state);
6
13
  const check = deploymentCheck(options);
7
14
  const { changed, reload } = check;
8
15
  const read = router.getSnapshot;
9
16
  const navigate = router.navigate;
10
17
  const dispose = router.dispose;
11
- let sequence = 0;
12
18
  let stopped = false;
13
19
  let replay = false;
14
- let mutations = 0;
15
20
  const dataRouter = router;
16
21
  const createFetcher = dataRouter.createFetcher;
22
+ const submit = dataRouter.submit;
23
+ const trackSubmission = (submit) => (...args) => {
24
+ state.sequence++;
25
+ state.mutations++;
26
+ return submit(...args).finally(() => {
27
+ state.mutations--;
28
+ });
29
+ };
30
+ const guardedSubmit = submit && trackSubmission(submit);
17
31
  const guardedFetcher = createFetcher && (() => {
18
32
  const fetcher = createFetcher();
19
- const submit = fetcher.submit;
20
- fetcher.submit = (...args) => {
21
- sequence++;
22
- mutations++;
23
- return submit(...args).finally(() => {
24
- mutations--;
25
- });
26
- };
33
+ fetcher.submit = trackSubmission(fetcher.submit);
27
34
  return fetcher;
28
35
  });
36
+ if (guardedSubmit) dataRouter.submit = guardedSubmit;
29
37
  if (guardedFetcher) dataRouter.createFetcher = guardedFetcher;
30
- const valid = (current) => current === sequence && !stopped;
31
- const canReload = (current) => valid(current) && mutations === 0;
32
- const unsubscribe = router.subscribe(() => {
33
- if (read().state === "submitting") sequence++;
34
- });
38
+ const valid = (current) => current === state.sequence && !stopped;
39
+ const canReload = (current) => valid(current) && state.mutations === 0;
35
40
  const guarded = async (href, operation) => {
36
41
  const target = new URL(href, router.getSnapshot().url);
37
42
  const currentURL = new URL(router.getSnapshot().url);
38
- const current = ++sequence;
43
+ const current = ++state.sequence;
39
44
  if (target.origin === location.origin && (target.pathname !== currentURL.pathname || target.search !== currentURL.search) && await changed()) {
40
45
  if (!valid(current) || await reload(target, () => canReload(current))) return;
41
46
  }
42
- if (current === sequence && !stopped) await navigate(href, operation);
47
+ if (current === state.sequence && !stopped) await navigate(href, operation);
43
48
  };
44
49
  const pop = (event) => {
45
50
  if (replay) return;
46
51
  const target = new URL(location.href);
47
52
  const previous = new URL(read().url);
48
- const current = ++sequence;
53
+ const current = ++state.sequence;
49
54
  if (target.pathname === previous.pathname && target.search === previous.search) return;
50
55
  event.stopImmediatePropagation();
51
56
  changed().then(async (updated) => {
@@ -59,20 +64,22 @@ function watchDeployment(router, options) {
59
64
  };
60
65
  const preloadError = () => {
61
66
  if (read().state !== "loading") return;
62
- const current = sequence;
67
+ const current = state.sequence;
63
68
  const target = new URL(read().url);
64
69
  changed().then((updated) => {
65
70
  if (updated) return reload(target, () => canReload(current) && read().state !== "submitting" && read().url === target.href);
66
71
  });
67
72
  };
68
73
  function cleanup() {
74
+ if (stopped) return;
75
+ if (state.cleanup === cleanup) state.cleanup = void 0;
69
76
  stopped = true;
70
- sequence++;
77
+ state.sequence++;
71
78
  check.dispose();
72
- unsubscribe();
73
79
  window.removeEventListener("popstate", pop, true);
74
80
  window.removeEventListener("vite:preloadError", preloadError);
75
- if (dataRouter.createFetcher === guardedFetcher) dataRouter.createFetcher = createFetcher;
81
+ if (guardedSubmit && dataRouter.submit === guardedSubmit) dataRouter.submit = submit;
82
+ if (guardedFetcher && dataRouter.createFetcher === guardedFetcher) dataRouter.createFetcher = createFetcher;
76
83
  if (router.navigate === guarded) router.navigate = navigate;
77
84
  if (router.dispose === guardedDispose) router.dispose = dispose;
78
85
  }
@@ -80,6 +87,7 @@ function watchDeployment(router, options) {
80
87
  cleanup();
81
88
  dispose();
82
89
  }
90
+ state.cleanup = cleanup;
83
91
  router.navigate = guarded;
84
92
  router.dispose = guardedDispose;
85
93
  window.addEventListener("popstate", pop, true);
@@ -1,3 +1,4 @@
1
+ /** Application base URLs, navigation handoff and document-transition settings shared by browser integrations. */
1
2
  export interface ApplicationEnvironment {
2
3
  resolveURL?: (url: URL) => URL;
3
4
  isDocumentHandoff?: (from: URL, to: URL) => boolean;
@@ -1,4 +1,5 @@
1
1
  import type { Router } from "./types.ts";
2
+ /** Browser history operation: POP, PUSH or REPLACE. */
2
3
  export type NavigationType = "POP" | "PUSH" | "REPLACE";
3
4
  export interface NavigationSnapshot {
4
5
  type: NavigationType;
@@ -1,13 +1,16 @@
1
1
  export type Params = Readonly<Record<string, string>>;
2
+ /** Resource parameters and cancellation signal supplied to a loader. */
2
3
  export interface LoaderFunctionArgs {
3
4
  params: Params;
4
5
  signal: AbortSignal;
5
6
  }
7
+ /** Submitted input, resource parameters and cancellation signal supplied to an action. */
6
8
  export interface ActionFunctionArgs<Input = unknown> {
7
9
  input: Input;
8
10
  params: Params;
9
11
  signal: AbortSignal;
10
12
  }
13
+ /** Successful resource data or a normalized resource failure. */
11
14
  export interface DataResult<Data> {
12
15
  data: Data | null;
13
16
  error: string;
@@ -18,6 +21,7 @@ export interface ResourceModule {
18
21
  loader?: (args: LoaderFunctionArgs) => Promise<DataResult<unknown>>;
19
22
  shouldRevalidate?: (args: ShouldRevalidateFunctionArgs) => boolean;
20
23
  }
24
+ /** Navigation or mutation context used to decide whether loader data should refresh. */
21
25
  export interface ShouldRevalidateFunctionArgs {
22
26
  actionResult: DataResult<unknown>;
23
27
  defaultShouldRevalidate: boolean;
@@ -1,4 +1,5 @@
1
1
  import type { DataResult, Params, ResourceModule, RouteState } from "./resource-types.ts";
2
+ /** Manage keyed resource reads, submissions, subscriptions and cancellation independently of navigation. */
2
3
  export declare class ResourceRouter {
3
4
  #private;
4
5
  constructor(routes: ReadonlyMap<string, ResourceModule>);
@@ -16,4 +17,5 @@ export declare class ResourceRouter {
16
17
  retry(routeId: string, params: Params): Promise<DataResult<unknown>>;
17
18
  reportUnexpected(routeId: string, params: Params): void;
18
19
  }
20
+ /** Create an independent resource-data router from registered modules; this does not manage URL history. */
19
21
  export declare function createResourceRouter(routes: ReadonlyMap<string, ResourceModule>): ResourceRouter;
@@ -11,6 +11,7 @@ var IDLE_ROUTE_STATE = {
11
11
  navigationState: "idle",
12
12
  unexpectedError: false
13
13
  };
14
+ /** Manage keyed resource reads, submissions, subscriptions and cancellation independently of navigation. */
14
15
  var ResourceRouter = class {
15
16
  #cleanupTimers = /* @__PURE__ */ new Map();
16
17
  #controllers = /* @__PURE__ */ new Map();
@@ -333,6 +334,7 @@ var ResourceRouter = class {
333
334
  for (const listener of this.#listeners.get(routeKey) ?? []) listener();
334
335
  }
335
336
  };
337
+ /** Create an independent resource-data router from registered modules; this does not manage URL history. */
336
338
  function createResourceRouter(routes) {
337
339
  return new ResourceRouter(routes);
338
340
  }
@@ -1,15 +1,27 @@
1
+ /**
2
+ * HTTP response helpers and loader result normalization.
3
+ *
4
+ * @module
5
+ */
6
+ /** Normalized route failure with an HTTP status, status text and optional response data. */
1
7
  export interface RouteError extends Error {
2
8
  status: number;
3
9
  statusText: string;
4
10
  data?: unknown;
5
11
  }
12
+ /** Loader value paired with HTTP response metadata. */
6
13
  export interface DataResponse<T> {
7
14
  value: T;
8
15
  init: ResponseInit;
9
16
  __routeData: true;
10
17
  }
18
+ /** Wrap loader data with HTTP status and headers without creating a Response body. */
11
19
  export declare function data<T>(value: T, init?: ResponseInit): DataResponse<T>;
20
+ /** Create an empty redirect Response with a Location header; defaults to HTTP 302. */
12
21
  export declare function redirect(location: string, init?: number | ResponseInit): Response;
22
+ /** Identify normalized Error objects carrying an HTTP status. */
13
23
  export declare function isRouteErrorResponse(value: unknown): value is RouteError;
24
+ /** Normalize transport responses and wrapped data, optionally resolving nested deferred values. */
14
25
  export declare function normalizeResult(value: unknown, resolveDeferred?: boolean): Promise<unknown>;
26
+ /** Recursively resolve promises in arrays and plain objects before JSON serialization. */
15
27
  export declare function resolveData(value: unknown): Promise<unknown>;
@@ -1,4 +1,5 @@
1
1
  //#region .generated/core/responses.ts
2
+ /** Wrap loader data with HTTP status and headers without creating a Response body. */
2
3
  function data(value, init = {}) {
3
4
  return {
4
5
  value,
@@ -6,6 +7,7 @@ function data(value, init = {}) {
6
7
  __routeData: true
7
8
  };
8
9
  }
10
+ /** Create an empty redirect Response with a Location header; defaults to HTTP 302. */
9
11
  function redirect(location, init = 302) {
10
12
  const options = typeof init === "number" ? { status: init } : init;
11
13
  const headers = new Headers(options.headers);
@@ -16,9 +18,11 @@ function redirect(location, init = 302) {
16
18
  headers
17
19
  });
18
20
  }
21
+ /** Identify normalized Error objects carrying an HTTP status. */
19
22
  function isRouteErrorResponse(value) {
20
23
  return value instanceof Error && "status" in value;
21
24
  }
25
+ /** Normalize transport responses and wrapped data, optionally resolving nested deferred values. */
22
26
  async function normalizeResult(value, resolveDeferred = true) {
23
27
  if (value && typeof value === "object" && "__routeError" in value) {
24
28
  const error = value.__routeError;
@@ -35,6 +39,7 @@ async function normalizeResult(value, resolveDeferred = true) {
35
39
  if (value && typeof value === "object" && "__routeData" in value) return resolveDeferred ? resolveData(value.value) : value.value;
36
40
  return resolveDeferred ? resolveData(value) : value;
37
41
  }
42
+ /** Recursively resolve promises in arrays and plain objects before JSON serialization. */
38
43
  async function resolveData(value) {
39
44
  const resolved = await value;
40
45
  if (Array.isArray(resolved)) return Promise.all(resolved.map(resolveData));
@@ -1,5 +1,8 @@
1
- /** Framework-independent, browser-safe utilities. @module */
2
- export * from "./errors.ts";
1
+ /**
2
+ * Routing patterns, query strings and serialized data utilities.
3
+ *
4
+ * @module
5
+ */
3
6
  export * from "./match.ts";
4
7
  export * from "./serialize.ts";
5
8
  export * from "./url.ts";
@@ -1,5 +1,4 @@
1
- import { safeWrapAsync } from "./errors.js";
2
1
  import { defineRoute, matchRoutes } from "./match.js";
3
2
  import { internalURL, navigationClick } from "./url.js";
4
3
  import { serialize, serializeData } from "./serialize.js";
5
- export { defineRoute, internalURL, matchRoutes, navigationClick, safeWrapAsync, serialize, serializeData };
4
+ export { defineRoute, internalURL, matchRoutes, navigationClick, serialize, serializeData };
@@ -26,15 +26,16 @@ async function flatRoutes(directory) {
26
26
  if (!entry.isFile() || !flatFile.test(name) || name.endsWith(".d.ts")) continue;
27
27
  name = name.replace(flatFile, "");
28
28
  }
29
- if (/[()[\]:*?]/.test(name)) return /* @__PURE__ */ new Error(`Unsupported flat route name: ${name}`);
30
- const segments = name.split(".");
29
+ const escaped = name.replaceAll("[.]", "\0");
30
+ if (/[()[\]:*?]/.test(escaped)) return /* @__PURE__ */ new Error(`Unsupported flat route name: ${name}`);
31
+ const segments = escaped.split(".");
31
32
  const parts = [];
32
33
  for (const [index, segment] of segments.entries()) {
33
34
  if (!segment) return /* @__PURE__ */ new Error(`Empty route segment: ${name}`);
34
35
  if (segment === "_index" && index !== segments.length - 1) return /* @__PURE__ */ new Error(`_index must be last: ${name}`);
35
36
  if (segment === "$" && index !== segments.length - 1) return /* @__PURE__ */ new Error(`Splat must be last: ${name}`);
36
37
  if (segment.startsWith("_")) continue;
37
- const part = segment.replace(/_$/, "");
38
+ const part = segment.replace(/_$/, "").replaceAll("\0", ".");
38
39
  parts.push(part === "$" ? "*" : part.replace(/^\$/, ":"));
39
40
  }
40
41
  if (segments.at(-1)?.startsWith("_") && segments.at(-1) !== "_index") continue;
@@ -0,0 +1,4 @@
1
+ import type { AwaitProps } from "./await.tsx";
2
+ export declare function AwaitFallback<T>({ resolve, fallback, errorElement, children, }: AwaitProps<T> & {
3
+ resolve: Promise<T>;
4
+ }): import("preact").ComponentChildren;
@@ -0,0 +1,29 @@
1
+ import { useEffect, useState } from "preact/hooks";
2
+ //#region .generated/preact/await-fallback.tsx
3
+ /** @jsxImportSource preact */
4
+ function AwaitFallback({ resolve, fallback, errorElement, children }) {
5
+ const [result, setResult] = useState();
6
+ useEffect(() => {
7
+ let current = true;
8
+ resolve.then((value) => {
9
+ if (current) setResult({
10
+ source: resolve,
11
+ value
12
+ });
13
+ }, (error) => {
14
+ if (current) setResult({
15
+ source: resolve,
16
+ error
17
+ });
18
+ });
19
+ return () => {
20
+ current = false;
21
+ };
22
+ }, [resolve]);
23
+ if (!result || result.source !== resolve) return fallback;
24
+ if ("value" in result) return children(result.value);
25
+ if (errorElement !== void 0) return errorElement;
26
+ throw result.error;
27
+ }
28
+ //#endregion
29
+ export { AwaitFallback };
@@ -1,7 +1,11 @@
1
+ /** @jsxImportSource preact */
1
2
  import type { ComponentChildren } from "preact";
2
3
  export interface AwaitProps<T> {
3
4
  resolve: T | Promise<T>;
4
5
  children: (value: Awaited<T>) => ComponentChildren;
5
6
  errorElement?: ComponentChildren;
7
+ /** Component-owned pending content, safe during early hydration. Pass null to render nothing. */
8
+ fallback?: ComponentChildren;
6
9
  }
7
- export declare function Await<T>({ resolve, children, errorElement, }: AwaitProps<T>): ComponentChildren;
10
+ /** Render a value or promise. An explicit fallback owns pending content during early hydration; errorElement handles rejection. */
11
+ export declare function Await<T>(props: AwaitProps<T>): ComponentChildren;
@@ -1,8 +1,27 @@
1
- import { use } from "preact/compat";
1
+ import { AwaitFallback } from "./await-fallback.js";
2
+ import { Application } from "./root-context.js";
3
+ import { use, useContext } from "preact/compat";
4
+ import { jsx } from "preact/jsx-runtime";
2
5
  //#region .generated/preact/await.tsx
3
6
  var resolutions = /* @__PURE__ */ new WeakMap();
4
- function Await({ resolve, children, errorElement }) {
5
- if (!(resolve instanceof Promise)) return children(resolve);
7
+ /** Render a value or promise. An explicit fallback owns pending content during early hydration; errorElement handles rejection. */
8
+ function Await(props) {
9
+ const app = useContext(Application);
10
+ if (!(props.resolve instanceof Promise)) return props.children(props.resolve);
11
+ if (!("fallback" in props)) return /* @__PURE__ */ jsx(SuspendedAwait, {
12
+ ...props,
13
+ resolve: props.resolve
14
+ });
15
+ if (app?.deferredBoundary) return app.deferredBoundary(/* @__PURE__ */ jsx(SuspendedAwait, {
16
+ ...props,
17
+ resolve: props.resolve
18
+ }), props.fallback);
19
+ return /* @__PURE__ */ jsx(AwaitFallback, {
20
+ ...props,
21
+ resolve: props.resolve
22
+ });
23
+ }
24
+ function SuspendedAwait({ resolve, children, errorElement }) {
6
25
  let pending = resolutions.get(resolve);
7
26
  if (!pending) {
8
27
  pending = resolve.then((value) => ({ value }), (error) => ({ error }));
@@ -1,12 +1,20 @@
1
- /** Browser mounting and hydration for Preact applications. @module */
1
+ /**
2
+ * Preact document hydration, browser history and deployment recovery.
3
+ *
4
+ * @module
5
+ */
2
6
  /** @jsxImportSource preact */
3
7
  import { type DeploymentOptions } from "../client/index.ts";
4
8
  import { type Router } from "../core/index.ts";
5
9
  import type { AppRoute, RootModule } from "./route-types.ts";
6
10
  export * from "../client/index.ts";
7
11
  export { configureApplication } from "../core/environment.ts";
12
+ /** Browser hydration settings for hash history, deployment recovery and deferred-data timeout. */
8
13
  export interface MountApplicationOptions {
9
14
  hash?: boolean;
10
15
  deployment?: DeploymentOptions | null;
16
+ /** Total initial deferred-data budget in integer milliseconds; not an inactivity timeout. Defaults to 30,000. */
17
+ deferredTimeout?: number;
11
18
  }
19
+ /** Create the browser application router and hydrate the document, installing recovery before initial lazy loading. */
12
20
  export declare function mountApplication(routes: AppRoute[], rootModule?: RootModule, options?: boolean | MountApplicationOptions): Promise<Router>;
@@ -13,10 +13,15 @@ import { flushSync } from "preact/compat";
13
13
  import { jsx } from "preact/jsx-runtime";
14
14
  import { hydrate, render } from "preact";
15
15
  //#region .generated/preact/client.tsx
16
- /** Browser mounting and hydration for Preact applications. @module */
16
+ /**
17
+ * Preact document hydration, browser history and deployment recovery.
18
+ *
19
+ * @module
20
+ */
17
21
  /** @jsxImportSource preact */
22
+ /** Create the browser application router and hydrate the document, installing recovery before initial lazy loading. */
18
23
  async function mountApplication(routes, rootModule, options = false) {
19
- const { hash = false, deployment = null } = typeof options === "boolean" ? { hash: options } : options;
24
+ const { hash = false, deployment = null, deferredTimeout } = typeof options === "boolean" ? { hash: options } : options;
20
25
  const initialURL = new URL(hash ? location.hash.slice(1) || "/" : location.href, location.origin);
21
26
  await (matchRoutes(routes, initialURL.pathname)?.route)?.preload?.().catch(async (error) => {
22
27
  await recoverDeployment(deployment, initialURL);
@@ -24,7 +29,7 @@ async function mountApplication(routes, rootModule, options = false) {
24
29
  });
25
30
  routes = prepareRoutes(routes);
26
31
  const element = document.getElementById("smolter-data");
27
- const initial = element?.textContent ? readHydration(element.textContent) : void 0;
32
+ const initial = element?.textContent ? readHydration(element.textContent, deferredTimeout) : void 0;
28
33
  const hydration = initial?.hydration;
29
34
  if (hydration?.snapshot.error) hydration.snapshot.error = Object.assign(new Error(hydration.snapshot.error.message), hydration.snapshot.error);
30
35
  const router = createDataRouter(routes, {
@@ -3,7 +3,7 @@ export interface DeferredWindow extends Window {
3
3
  __smolterDeferred?: Settlement[];
4
4
  }
5
5
  /** Restore promise identity before hydrating Suspense boundaries. */
6
- export declare function readHydration(json: string): {
6
+ export declare function readHydration(json: string, timeout?: number): {
7
7
  hydration: any;
8
8
  dispose: () => void;
9
9
  };
@@ -1,6 +1,7 @@
1
1
  //#region .generated/preact/deferred-hydration.ts
2
2
  /** Restore promise identity before hydrating Suspense boundaries. */
3
- function readHydration(json) {
3
+ function readHydration(json, timeout = 3e4) {
4
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > 2147483647) throw new RangeError("deferredTimeout must be an integer between 1 and 2147483647 ms");
4
5
  const pending = /* @__PURE__ */ new Map();
5
6
  const queue = window.__smolterDeferred ??= [];
6
7
  function revive(_key, value) {
@@ -34,11 +35,16 @@ function readHydration(json) {
34
35
  }
35
36
  const hydration = JSON.parse(json, revive);
36
37
  const dispose = () => {
38
+ clearTimeout(deadline);
37
39
  window.removeEventListener("DOMContentLoaded", dispose);
38
40
  for (const target of pending.values()) target.reject(/* @__PURE__ */ new Error("Hydration stream closed"));
39
41
  pending.clear();
40
42
  queue.push = () => 0;
41
43
  };
44
+ const deadline = pending.size ? setTimeout(() => {
45
+ for (const target of pending.values()) target.reject(/* @__PURE__ */ new Error("Hydration stream timed out"));
46
+ dispose();
47
+ }, timeout) : void 0;
42
48
  const receive = (packet) => {
43
49
  const [id, value, error] = packet;
44
50
  if (id === -1) return dispose();
@@ -1,11 +1,21 @@
1
+ /**
2
+ * Preact document metadata, resource links and hydration scripts.
3
+ *
4
+ * @module
5
+ */
1
6
  /** @jsxImportSource preact */
2
7
  import type { ComponentProps } from "preact/compat";
8
+ /** Render root or active-route metadata in the document head. */
3
9
  export declare function Meta(): import("preact").JSX.Element;
10
+ /** Render stylesheet and other link descriptors from the application root. */
4
11
  export declare function Links(): import("preact").JSX.Element;
12
+ /** Nonce and browser entry options for the document hydration scripts. */
5
13
  export interface ScriptProps {
6
14
  nonce?: string;
7
15
  }
16
+ /** Render the hydration snapshot and browser entry script with an optional CSP nonce. */
8
17
  export declare function Scripts({ nonce }: ScriptProps): import("preact").JSX.Element;
18
+ /** Reset scroll after navigation unless the navigation requests preservation. */
9
19
  export declare function ScrollRestoration(_props: ScriptProps): null;
10
20
  /** HTML attributes for server markup and client document reconciliation. */
11
21
  export interface DocumentProps extends ComponentProps<"html"> {
@@ -1,13 +1,18 @@
1
1
  import { serializeData } from "../core/serialize.js";
2
+ import { useApplication } from "./root-context.js";
2
3
  import { useSnapshot } from "./context.js";
3
4
  import { applicationEnvironment } from "../core/environment.js";
4
5
  import { scrollPositions } from "../core/navigation.js";
5
6
  import { useNavigationSnapshot } from "./navigation-state.js";
6
- import { useApplication } from "./root-context.js";
7
7
  import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
8
8
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
9
9
  import { Fragment as Fragment$1, createElement } from "preact";
10
10
  //#region .generated/preact/document.tsx
11
+ /**
12
+ * Preact document metadata, resource links and hydration scripts.
13
+ *
14
+ * @module
15
+ */
11
16
  /** @jsxImportSource preact */
12
17
  function renderMeta(item, index) {
13
18
  if (typeof item.title === "string") return /* @__PURE__ */ jsx("title", { children: item.title }, index);
@@ -21,6 +26,7 @@ function renderMeta(item, index) {
21
26
  key: index
22
27
  });
23
28
  }
29
+ /** Render root or active-route metadata in the document head. */
24
30
  function Meta() {
25
31
  const { routes, rootModule } = useApplication();
26
32
  const snapshot = useSnapshot();
@@ -39,10 +45,12 @@ function Meta() {
39
45
  }, [values]);
40
46
  return /* @__PURE__ */ jsx(Fragment$1, { children: values.map(renderMeta) });
41
47
  }
48
+ /** Render stylesheet and other link descriptors from the application root. */
42
49
  function Links() {
43
50
  const { rootModule } = useApplication();
44
51
  return /* @__PURE__ */ jsx(Fragment$1, { children: rootModule?.links?.().map((props, index) => /* @__PURE__ */ jsx("link", { ...props }, index)) });
45
52
  }
53
+ /** Render the hydration snapshot and browser entry script with an optional CSP nonce. */
46
54
  function Scripts({ nonce }) {
47
55
  const { root, rootError, router, entry, streaming, initialData } = useApplication();
48
56
  const [json] = useState(() => {
@@ -82,6 +90,7 @@ function Scripts({ nonce }) {
82
90
  })
83
91
  ] });
84
92
  }
93
+ /** Reset scroll after navigation unless the navigation requests preservation. */
85
94
  function ScrollRestoration(_props) {
86
95
  const { router } = useApplication();
87
96
  const { url, state } = useSnapshot();
@@ -1,4 +1,8 @@
1
- /** Framework components and typed data hooks. @module */
1
+ /**
2
+ * Preact routing components, typed data hooks and application integration.
3
+ *
4
+ * @module
5
+ */
2
6
  export type * from "../core/index.ts";
3
7
  export { createDataRouter, createRouter, defineRoute, } from "../core/index.ts";
4
8
  export type { ApplicationEnvironment } from "../core/environment.ts";
@@ -14,5 +18,5 @@ export type { NavigateOptions } from "./navigation.tsx";
14
18
  export * from "./navigation.tsx";
15
19
  export { useMatches, useNavigation, useOutletContext, useRootData, useRouteError, useRouteLoaderData, useSubmit, } from "./route-hooks.tsx";
16
20
  export * from "./route-types.ts";
17
- export { Outlet, RouterApplication } from "./router.tsx";
21
+ export { Outlet, type OutletProps, RouterApplication } from "./router.tsx";
18
22
  export * from "./routes.ts";
@@ -1,6 +1,7 @@
1
1
  import type { RouteModule } from "../core/index.ts";
2
2
  import { type ComponentType } from "preact";
3
3
  import type { AppRoute } from "./route-types.ts";
4
+ /** Lazy route path, server transport endpoint and available handler capabilities. */
4
5
  export interface LazyRouteOptions {
5
6
  path: string;
6
7
  dataEndpoint?: string;
@@ -16,5 +17,6 @@ type LoadedRoute = RouteModule & Pick<AppRoute, "meta">;
16
17
  interface LazyAppRoute extends AppRoute {
17
18
  resolveComponent: () => ComponentType | undefined;
18
19
  }
20
+ /** Create a route that imports its module on demand and shares the import with intent prefetching. */
19
21
  export declare function lazyRoute(options: LazyRouteOptions, importer: () => Promise<LoadedRoute>): LazyAppRoute;
20
22
  export {};
@@ -3,12 +3,18 @@ import { useRouter } from "./context.js";
3
3
  import { serverRoute } from "../client/index.js";
4
4
  import { createElement } from "preact";
5
5
  //#region .generated/preact/lazy-route.ts
6
+ /**
7
+ * On-demand Preact route modules with shared intent prefetching.
8
+ *
9
+ * @module
10
+ */
6
11
  function LazyView() {
7
12
  const router = useRouter();
8
13
  const { path } = useSyncExternalStore(router.subscribe, router.getSnapshot);
9
14
  const Component = router.routes.find((item) => item.path === path)?.resolveComponent();
10
15
  return Component && createElement(Component, {});
11
16
  }
17
+ /** Create a route that imports its module on demand and shares the import with intent prefetching. */
12
18
  function lazyRoute(options, importer) {
13
19
  let module;
14
20
  let pending;
@@ -2,7 +2,9 @@ import { type NavigateOptions } from "../core/index.ts";
2
2
  import type { ComponentProps, ReactNode } from "preact/compat";
3
3
  export type { NavigateOptions } from "../core/index.ts";
4
4
  export type { NavigationType } from "../core/navigation.ts";
5
+ /** Navigate to a URL or move through browser history using a numeric offset. */
5
6
  export type NavigateFunction = (to: string | number | Partial<Pick<URL, "pathname" | "search" | "hash">>, options?: NavigateOptions) => void;
7
+ /** Anchor properties and router navigation options, including document reload and prefetch controls. */
6
8
  export interface LinkProps extends Omit<ComponentProps<"a">, "href" | "role"> {
7
9
  to?: string;
8
10
  href?: string;
@@ -14,15 +16,19 @@ export interface LinkProps extends Omit<ComponentProps<"a">, "href" | "role"> {
14
16
  viewTransition?: boolean;
15
17
  reloadDocument?: boolean;
16
18
  }
19
+ /** Active and pending flags supplied to NavLink render callbacks. */
17
20
  export interface NavLinkRenderProps {
18
21
  isActive: boolean;
19
22
  isPending: boolean;
20
23
  }
24
+ /** Link options with matching rules and state-aware class or children callbacks. */
21
25
  export interface NavLinkProps extends Omit<LinkProps, "className" | "children"> {
22
26
  end?: boolean;
23
27
  className?: string | ((state: NavLinkRenderProps) => string);
24
28
  children?: ReactNode | ((state: NavLinkRenderProps) => ReactNode);
25
29
  }
30
+ /** Render an anchor with same-origin router navigation and optional intent prefetching. */
26
31
  export declare function Link(options: LinkProps): ReactNode;
32
+ /** Render a Link with active-route state and aria-current for the matching destination. */
27
33
  export declare function NavLink({ href, to, end, className, children, ...props }: NavLinkProps): ReactNode;
28
34
  export { useNavigate, useNavigationType, useSearchParams, useURLLocation, } from "./route-hooks.tsx";
@@ -5,6 +5,7 @@ import { useRouter, useSnapshot } from "./context.js";
5
5
  import { handoffDocument, resolveApplicationURL } from "../core/environment.js";
6
6
  import { useNavigate, useNavigationType, useSearchParams, useURLLocation } from "./route-hooks.js";
7
7
  //#region .generated/preact/navigation.tsx
8
+ /** Render an anchor with same-origin router navigation and optional intent prefetching. */
8
9
  function Link(options) {
9
10
  const { href: hrefProp, to = hrefProp ?? "/", replace, preventScrollReset, viewTransition, prefetch, state, reloadDocument, ...props } = options;
10
11
  const router = useRouter();
@@ -40,6 +41,7 @@ function Link(options) {
40
41
  }
41
42
  });
42
43
  }
44
+ /** Render a Link with active-route state and aria-current for the matching destination. */
43
45
  function NavLink({ href, to = href ?? "/", end, className, children, ...props }) {
44
46
  const location = useURLLocation();
45
47
  const target = new URL(to, location.href).pathname;
@@ -6,6 +6,7 @@ export interface ApplicationContext {
6
6
  root: unknown;
7
7
  rootError?: boolean;
8
8
  streaming?: boolean;
9
+ deferredBoundary?: (children: import("preact").ComponentChildren, fallback: import("preact").ComponentChildren) => import("preact").ComponentChildren;
9
10
  initialData?: string;
10
11
  rootModule?: RootModule;
11
12
  hydration?: Hydration;