@kasperrt/smolter 0.4.3 → 0.4.4

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 (112) hide show
  1. package/.generated/client/hash-history.ts +27 -0
  2. package/.generated/client/hydration.ts +98 -0
  3. package/.generated/client/root-data.ts +52 -0
  4. package/.generated/core/prepare-routes.ts +50 -0
  5. package/.generated/core/presentation.ts +1 -1
  6. package/.generated/preact/client.tsx +1 -26
  7. package/.generated/preact/deferred-hydration.ts +1 -98
  8. package/.generated/preact/index.ts +6 -1
  9. package/.generated/preact/navigation.tsx +13 -5
  10. package/.generated/preact/root-data.ts +1 -45
  11. package/.generated/preact/router.tsx +2 -48
  12. package/.generated/react/client.tsx +130 -0
  13. package/.generated/react/document.tsx +235 -0
  14. package/.generated/react/fetcher.ts +29 -7
  15. package/.generated/react/hooks.ts +20 -18
  16. package/.generated/react/index.ts +17 -1
  17. package/.generated/react/lazy-route.ts +116 -0
  18. package/.generated/react/link.ts +1 -38
  19. package/.generated/react/navigation-state.ts +13 -0
  20. package/.generated/react/navigation.ts +145 -0
  21. package/.generated/react/root-context.ts +22 -0
  22. package/.generated/react/route-hooks.ts +159 -0
  23. package/.generated/react/route-types.ts +91 -0
  24. package/.generated/react/router.ts +50 -0
  25. package/.generated/react/runtime.ts +1 -0
  26. package/.generated/server/application.tsx +18 -253
  27. package/.generated/server/dispatch.ts +87 -0
  28. package/.generated/server/index.ts +2 -0
  29. package/.generated/server/react.ts +34 -0
  30. package/.generated/server/render-application.ts +203 -0
  31. package/.generated/vite/application/lazy-files.ts +2 -1
  32. package/.generated/vite/application/vite.ts +12 -4
  33. package/.generated/vite/react.ts +11 -0
  34. package/CHANGELOG.md +10 -0
  35. package/README.md +41 -41
  36. package/dist/client/hash-history.d.ts +2 -0
  37. package/dist/client/hash-history.js +26 -0
  38. package/dist/client/hydration.d.ts +10 -0
  39. package/dist/{preact/deferred-hydration.js → client/hydration.js} +1 -1
  40. package/dist/client/root-data.d.ts +14 -0
  41. package/dist/{preact → client}/root-data.js +1 -1
  42. package/dist/core/prepare-routes.d.ts +7 -0
  43. package/dist/core/prepare-routes.js +28 -0
  44. package/dist/core/presentation.js +1 -1
  45. package/dist/preact/client.js +5 -26
  46. package/dist/preact/deferred-hydration.d.ts +1 -10
  47. package/dist/preact/document.js +2 -2
  48. package/dist/preact/index.d.ts +1 -1
  49. package/dist/preact/index.js +1 -1
  50. package/dist/preact/navigation-state.js +1 -1
  51. package/dist/preact/navigation.js +7 -5
  52. package/dist/preact/root-data.d.ts +1 -8
  53. package/dist/preact/route-hooks.js +1 -1
  54. package/dist/preact/router.d.ts +2 -2
  55. package/dist/preact/router.js +2 -26
  56. package/dist/react/client.d.ts +20 -0
  57. package/dist/react/client.js +86 -0
  58. package/dist/react/document.d.ts +25 -0
  59. package/dist/react/document.js +169 -0
  60. package/dist/react/fetcher.d.ts +8 -4
  61. package/dist/react/fetcher.js +12 -1
  62. package/dist/react/form.js +1 -1
  63. package/dist/react/hooks.d.ts +7 -6
  64. package/dist/react/index.d.ts +7 -1
  65. package/dist/react/index.js +6 -2
  66. package/dist/react/lazy-route.d.ts +22 -0
  67. package/dist/react/lazy-route.js +57 -0
  68. package/dist/react/link.d.ts +1 -12
  69. package/dist/react/navigation-state.d.ts +2 -0
  70. package/dist/react/navigation-state.js +9 -0
  71. package/dist/react/navigation.d.ts +33 -0
  72. package/dist/react/navigation.js +64 -0
  73. package/dist/react/root-context.d.ts +14 -0
  74. package/dist/react/root-context.js +10 -0
  75. package/dist/react/route-hooks.d.ts +34 -0
  76. package/dist/react/route-hooks.js +89 -0
  77. package/dist/react/route-types.d.ts +69 -0
  78. package/dist/react/router.d.ts +10 -0
  79. package/dist/react/router.js +22 -0
  80. package/dist/react/runtime.d.ts +1 -1
  81. package/dist/react/runtime.js +1 -1
  82. package/dist/server/application.d.ts +4 -8
  83. package/dist/server/application.js +9 -156
  84. package/dist/server/dispatch.d.ts +5 -0
  85. package/dist/server/dispatch.js +50 -0
  86. package/dist/server/index.d.ts +1 -0
  87. package/dist/server/index.js +2 -1
  88. package/dist/server/preact.js +2 -1
  89. package/dist/server/react.d.ts +10 -0
  90. package/dist/server/react.js +22 -0
  91. package/dist/server/render-application.d.ts +31 -0
  92. package/dist/server/render-application.js +118 -0
  93. package/dist/vite/application/lazy-files.d.ts +1 -1
  94. package/dist/vite/application/lazy-files.js +2 -2
  95. package/dist/vite/application/vite.d.ts +2 -1
  96. package/dist/vite/application/vite.js +12 -5
  97. package/dist/vite/react.d.ts +4 -0
  98. package/dist/vite/react.js +9 -1
  99. package/jsr.json +10 -6
  100. package/package.json +18 -10
  101. package/.generated/core/resource-types.ts +0 -48
  102. package/.generated/core/resources.ts +0 -446
  103. package/.generated/react/data.ts +0 -29
  104. package/.generated/react/resources/components.tsx +0 -416
  105. package/dist/core/resource-types.d.ts +0 -41
  106. package/dist/core/resources.d.ts +0 -21
  107. package/dist/core/resources.js +0 -347
  108. package/dist/react/data.d.ts +0 -9
  109. package/dist/react/data.js +0 -3
  110. package/dist/react/link.js +0 -27
  111. package/dist/react/resources/components.d.ts +0 -76
  112. package/dist/react/resources/components.js +0 -250
@@ -0,0 +1,69 @@
1
+ import type { DataArgs, RevalidationArgs, Route, Snapshot } from "../core/index.ts";
2
+ import type { DataResponse } from "../core/responses.ts";
3
+ import type { ComponentType, ReactNode } from "./runtime.ts";
4
+ /** Request, route parameters and cancellation signal supplied to a loader. */
5
+ export interface LoaderFunctionArgs extends DataArgs {
6
+ context?: unknown;
7
+ }
8
+ /** Request, route parameters and cancellation signal supplied to an action. */
9
+ export type ActionFunctionArgs = LoaderFunctionArgs;
10
+ /** Loader arguments with a serverLoader callback for the server transport. */
11
+ export interface ClientLoaderFunctionArgs extends LoaderFunctionArgs {
12
+ serverLoader: <T = unknown>() => Promise<T>;
13
+ }
14
+ /** Action arguments with a serverAction callback for the server transport. */
15
+ export interface ClientActionFunctionArgs extends LoaderFunctionArgs {
16
+ serverAction: <T = unknown>() => Promise<T>;
17
+ }
18
+ /** Navigation or mutation context used to decide whether loader data should refresh. */
19
+ export type ShouldRevalidateFunctionArgs = RevalidationArgs;
20
+ type UnwrapData<T> = T extends DataResponse<infer V> ? V : T;
21
+ /** Infer successful loader data, unwrapping promises and data responses. */
22
+ export type RouteData<T> = T extends (...args: never[]) => infer R ? UnwrapData<Exclude<Awaited<R>, Response | Error>> : T;
23
+ /** A document metadata descriptor, such as a title, meta tag or structured-data record. */
24
+ export type MetaDescriptor = Record<string, unknown>;
25
+ /** Loader data, error, parameters and location supplied to a route metadata function. */
26
+ export interface MetaArgs<T> {
27
+ data: RouteData<T> | undefined;
28
+ loaderData: RouteData<T> | undefined;
29
+ error?: unknown;
30
+ params: Record<string, string>;
31
+ location: URL;
32
+ }
33
+ /** Derive document metadata descriptors from the current route data and location. */
34
+ export type MetaFunction<T = unknown> = (args: MetaArgs<T>) => MetaDescriptor[];
35
+ /** Return link descriptors rendered in the document head. */
36
+ export type LinksFunction = () => Record<string, string>[];
37
+ /** Data route with optional lazy component, preload and document metadata integration. */
38
+ export interface AppRoute extends Route {
39
+ resolveComponent?: () => ComponentType | undefined;
40
+ preload?: () => Promise<void>;
41
+ id?: string;
42
+ routePath?: string;
43
+ meta?: MetaFunction;
44
+ }
45
+ /** Root document components, loader, metadata and revalidation policy for a React application. */
46
+ export interface RootModule {
47
+ /** Route identity used by targeted revalidation. Defaults to root. */
48
+ id?: string;
49
+ /** Optional registered route URL used to revalidate root data after mutations. */
50
+ loaderPath?: string;
51
+ ErrorBoundary?: ComponentType;
52
+ default: ComponentType;
53
+ Layout: ComponentType<{
54
+ children: ReactNode;
55
+ }>;
56
+ loader?: (args: LoaderFunctionArgs) => unknown;
57
+ links?: LinksFunction;
58
+ meta?: MetaFunction;
59
+ shouldRevalidate?: (args: RevalidationArgs) => boolean;
60
+ }
61
+ /** Serialized root data and route snapshot used to hydrate without repeating initial loaders. */
62
+ export interface Hydration {
63
+ rootError?: boolean;
64
+ root: unknown;
65
+ snapshot: Snapshot;
66
+ }
67
+ /** Transform a data transport response using the current request context. */
68
+ export type HandleDataRequestFunction = (response: Response, args: LoaderFunctionArgs) => Response | Promise<Response>;
69
+ export {};
@@ -0,0 +1,10 @@
1
+ import { createStore } from "../core/store.ts";
2
+ import { type ApplicationContext } from "./root-context.ts";
3
+ import { type ReactNode } from "./runtime.ts";
4
+ /** Root data, document components and request-scoped router for an application. */
5
+ export interface ApplicationProps extends ApplicationContext {
6
+ document?: boolean;
7
+ rootStore?: ReturnType<typeof createStore>;
8
+ }
9
+ /** Compose a persistent root layout with route views and observable root loader data. */
10
+ export declare function RouterApplication(props: ApplicationProps): ReactNode;
@@ -0,0 +1,22 @@
1
+ import { createElement, useMemo, useSyncExternalStore } from "./runtime.js";
2
+ import { RouterProvider } from "./context.js";
3
+ import { createStore } from "../core/store.js";
4
+ import { Application } from "./root-context.js";
5
+ import { Outlet } from "./outlet.js";
6
+ //#region .generated/react/router.ts
7
+ /** Compose a persistent root layout with route views and observable root loader data. */
8
+ function RouterApplication(props) {
9
+ const store = useMemo(() => props.rootStore ?? createStore(props.root), [props.rootStore, props.root]);
10
+ const root = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
11
+ const snapshot = useSyncExternalStore(props.router.subscribe, props.router.getSnapshot, props.router.getSnapshot);
12
+ const route = props.routes.find((item) => item.path === snapshot.path);
13
+ const Root = snapshot.error && (props.rootError || !route?.ErrorBoundary) ? props.rootModule?.ErrorBoundary ?? Outlet : props.rootModule?.default ?? Outlet;
14
+ const Layout = props.rootModule?.Layout;
15
+ const children = createElement(Root);
16
+ return createElement(RouterProvider, { router: props.router }, createElement(Application.Provider, { value: {
17
+ ...props,
18
+ root
19
+ } }, props.document && Layout ? createElement(Layout, { children }) : children));
20
+ }
21
+ //#endregion
22
+ export { RouterApplication };
@@ -1,2 +1,2 @@
1
1
  export type { AnchorHTMLAttributes, ComponentType, FormHTMLAttributes, MouseEvent, ReactNode, } from "react";
2
- export { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore, } from "react";
2
+ export { createContext, createElement, useCallback, useContext, useEffect, useMemo, useSyncExternalStore, } from "react";
@@ -1 +1 @@
1
- export { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
1
+ export { createContext, createElement, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
@@ -1,11 +1,7 @@
1
1
  /** @jsxImportSource preact */
2
2
  import type { AppRoute, RootModule } from "../preact/route-types.ts";
3
- /** SSR settings for CSP nonce and early deferred-data hydration. */
4
- export interface RenderApplicationOptions {
5
- nonce?: string;
6
- earlyHydration?: boolean;
7
- }
8
- /** Render a Preact application response with root and route data, streaming deferred content when enabled. */
3
+ import { type RenderApplicationOptions } from "./render-application.ts";
4
+ export { dispatchRoute } from "./dispatch.ts";
5
+ export type { RenderApplicationOptions } from "./render-application.ts";
6
+ /** Render a Preact application response with root data and optional early deferred hydration. */
9
7
  export declare function renderApplication(request: Request, routes: AppRoute[], rootModule: RootModule, entry: string, options?: RenderApplicationOptions): Promise<Response>;
10
- /** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
11
- export declare function dispatchRoute(request: Request, routes: AppRoute[], document?: boolean): Promise<Response>;
@@ -1,120 +1,17 @@
1
- import { safeWrapAsync } from "../core/errors.js";
2
- import { matchRoutes } from "../core/match.js";
3
- import { createDataRouter } from "../core/router.js";
4
- import { normalizeResult, resolveData } from "../core/responses.js";
5
- import { RouterApplication, prepareRoutes } from "../preact/router.js";
6
- import { privateResponse } from "./private-response.js";
7
- import { loadRouter } from "./index.js";
8
- import { deferredHydration } from "./deferred-hydration.js";
9
- import { progressiveDocument } from "./progressive-document.js";
10
- import { resolveHydration, streamDocument } from "./stream-document.js";
1
+ import { RouterApplication } from "../preact/router.js";
2
+ import { dispatchRoute } from "./dispatch.js";
3
+ import { renderDocument } from "./render-application.js";
11
4
  import { Suspense } from "preact/compat";
12
5
  import { jsx } from "preact/jsx-runtime";
13
6
  import { renderToReadableStream } from "preact-render-to-string/stream";
14
7
  //#region .generated/server/application.tsx
15
- /** @jsxImportSource preact */
16
- /** Render a Preact application response with root and route data, streaming deferred content when enabled. */
17
- async function renderApplication(request, routes, rootModule, entry, options = {}) {
18
- routes = routes.map((route) => ({
19
- ...route,
20
- path: route.routePath ?? route.path
21
- }));
22
- const match = matchRoutes(routes, new URL(request.url).pathname);
23
- if (match && !match.route.default) return dispatchRoute(request, routes, true);
24
- let actionResponse;
25
- let actionData;
26
- if (request.method !== "GET" && request.method !== "HEAD") {
27
- actionResponse = await dispatchRoute(request, routes, true);
28
- if (actionResponse.headers.has("Location") || !actionResponse.headers.get("Content-Type")?.includes("application/json")) return actionResponse;
29
- actionData = await normalizeResult(await actionResponse.json());
30
- }
31
- const rootResult = safeWrapAsync(() => Promise.resolve().then(() => rootModule.loader?.({
32
- request,
33
- params: {},
34
- signal: request.signal
35
- })).then((value) => normalizeResult(value, false), (error) => error instanceof Response ? normalizeResult(error, false) : error instanceof Error ? error : new Error(String(error), { cause: error })));
36
- let redirectResponse;
37
- const prepared = prepareRoutes(routes.map((route) => ({
38
- ...route,
39
- ErrorBoundary: route.ErrorBoundary ?? rootModule.ErrorBoundary
40
- })), true).map((route) => {
41
- if (!route.loader) return route;
42
- const loader = route.loader;
43
- return {
44
- ...route,
45
- loader: async (args) => {
46
- const value = await loader(args);
47
- if (value instanceof Response && value.headers.has("Location")) redirectResponse = value;
48
- return value;
49
- }
50
- };
51
- });
52
- const [rootState, loadedRouter] = await Promise.all([rootResult, loadRouter(prepared, request)]);
53
- let router = loadedRouter;
54
- const rootError = rootState.error ?? (rootState.data instanceof Error ? rootState.data : void 0);
55
- const root = rootError ? void 0 : rootState.data;
56
- if (root instanceof Response && root.headers.has("Location")) {
57
- router.dispose();
58
- return root;
59
- }
60
- if (redirectResponse) {
61
- router.dispose();
62
- return redirectResponse;
63
- }
64
- if (actionResponse || rootError) {
65
- const hydrationData = {
66
- ...router.getSnapshot(),
67
- actionData: actionData instanceof Error ? void 0 : actionData,
68
- ...actionData instanceof Error && { error: actionData },
69
- ...rootError && { error: rootError }
70
- };
71
- router.dispose();
72
- router = createDataRouter(router.routes, { hydrationData });
73
- }
74
- const snapshot = router.getSnapshot();
75
- const hydrationData = {
76
- root,
77
- snapshot: {
78
- ...snapshot,
79
- error: snapshot.error && {
80
- ...snapshot.error,
81
- name: snapshot.error.name,
82
- message: snapshot.error.message
83
- }
84
- },
85
- ...rootError && { rootError: true }
86
- };
87
- const deferred = options.earlyHydration ? deferredHydration(hydrationData, options.nonce) : void 0;
88
- const stream = renderToReadableStream(/* @__PURE__ */ jsx(RouterApplication, {
89
- router,
90
- routes,
91
- root,
8
+ /** Render a Preact application response with root data and optional early deferred hydration. */
9
+ function renderApplication(request, routes, rootModule, entry, options = {}) {
10
+ return renderDocument(request, routes, rootModule, entry, options, (props) => renderToReadableStream(/* @__PURE__ */ jsx(RouterApplication, {
11
+ ...props,
92
12
  rootModule,
93
- entry,
94
- rootError: Boolean(rootError),
95
- streaming: true,
96
- deferredBoundary: options.earlyHydration ? void 0 : deferredBoundary,
97
- initialData: deferred?.initial,
98
- document: true
99
- }), options);
100
- stream.allReady.catch(() => {});
101
- const headers = new Headers(actionResponse?.headers);
102
- headers.delete("Content-Length");
103
- headers.delete("Content-Encoding");
104
- headers.set("Content-Type", "text/html; charset=utf-8");
105
- headers.set("Cache-Control", "no-store");
106
- return new Response(deferred ? progressiveDocument(stream, deferred.stream, {
107
- signal: request.signal,
108
- dispose: () => router.dispose()
109
- }) : streamDocument(stream, {
110
- hydration: resolveHydration(hydrationData),
111
- signal: request.signal,
112
- nonce: options.nonce,
113
- dispose: () => router.dispose()
114
- }), {
115
- status: snapshot.error ? Number("status" in snapshot.error ? snapshot.error.status : 500) : actionResponse?.status ?? 200,
116
- headers
117
- });
13
+ deferredBoundary: options.earlyHydration ? void 0 : deferredBoundary
14
+ }), options));
118
15
  }
119
16
  function deferredBoundary(children, fallback) {
120
17
  return /* @__PURE__ */ jsx(Suspense, {
@@ -122,49 +19,5 @@ function deferredBoundary(children, fallback) {
122
19
  children
123
20
  });
124
21
  }
125
- async function dispatch(request, routes, document = false) {
126
- const match = matchRoutes(routes.map((route) => ({
127
- ...route,
128
- path: route.routePath ?? route.path
129
- })), new URL(request.url).pathname);
130
- if (!match) return Response.json({ error: "No route" }, { status: 404 });
131
- const handler = request.method === "GET" || request.method === "HEAD" ? match.route.loader : match.route.action;
132
- if (!handler) return Response.json({ error: "No handler" }, { status: 405 });
133
- const value = await Promise.resolve().then(() => handler({
134
- request,
135
- params: match.params,
136
- signal: request.signal
137
- })).then((result) => result, (error) => error);
138
- if (value instanceof Response) {
139
- const location = value.headers.get("Location");
140
- if (document) return value;
141
- if (!location) {
142
- if (value.ok) return value;
143
- return Response.json({ __routeError: {
144
- message: await value.text(),
145
- status: value.status,
146
- statusText: value.statusText
147
- } });
148
- }
149
- const headers = new Headers(value.headers);
150
- headers.delete("Location");
151
- headers.set("X-Smolter-Redirect", location);
152
- return new Response(null, { headers });
153
- }
154
- if (value instanceof Error) return Response.json({ __routeError: {
155
- ...value,
156
- message: value.message
157
- } });
158
- if (value && typeof value === "object" && "__routeData" in value) {
159
- const result = value;
160
- return Response.json(await resolveData(result.value), document ? result.init : { headers: result.init.headers });
161
- }
162
- return Response.json(await normalizeResult(value) ?? null);
163
- }
164
- /** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
165
- async function dispatchRoute(request, routes, document = false) {
166
- const response = await dispatch(request, routes, document);
167
- return document ? response : privateResponse(response);
168
- }
169
22
  //#endregion
170
23
  export { dispatchRoute, renderApplication };
@@ -0,0 +1,5 @@
1
+ import { type Route } from "../core/index.ts";
2
+ /** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
3
+ export declare function dispatchRoute(request: Request, routes: readonly (Route & {
4
+ routePath?: string;
5
+ })[], document?: boolean): Promise<Response>;
@@ -0,0 +1,50 @@
1
+ import { matchRoutes } from "../core/match.js";
2
+ import { normalizeResult, resolveData } from "../core/responses.js";
3
+ import { privateResponse } from "./private-response.js";
4
+ //#region .generated/server/dispatch.ts
5
+ async function dispatch(request, routes, document = false) {
6
+ const match = matchRoutes(routes.map((route) => ({
7
+ ...route,
8
+ path: route.routePath ?? route.path
9
+ })), new URL(request.url).pathname);
10
+ if (!match) return Response.json({ error: "No route" }, { status: 404 });
11
+ const handler = request.method === "GET" || request.method === "HEAD" ? match.route.loader : match.route.action;
12
+ if (!handler) return Response.json({ error: "No handler" }, { status: 405 });
13
+ const value = await Promise.resolve().then(() => handler({
14
+ request,
15
+ params: match.params,
16
+ signal: request.signal
17
+ })).then((result) => result, (error) => error);
18
+ if (value instanceof Response) {
19
+ const location = value.headers.get("Location");
20
+ if (document) return value;
21
+ if (!location) {
22
+ if (value.ok) return value;
23
+ return Response.json({ __routeError: {
24
+ message: await value.text(),
25
+ status: value.status,
26
+ statusText: value.statusText
27
+ } });
28
+ }
29
+ const headers = new Headers(value.headers);
30
+ headers.delete("Location");
31
+ headers.set("X-Smolter-Redirect", location);
32
+ return new Response(null, { headers });
33
+ }
34
+ if (value instanceof Error) return Response.json({ __routeError: {
35
+ ...value,
36
+ message: value.message
37
+ } });
38
+ if (value && typeof value === "object" && "__routeData" in value) {
39
+ const result = value;
40
+ return Response.json(await resolveData(result.value), document ? result.init : { headers: result.init.headers });
41
+ }
42
+ return Response.json(await normalizeResult(value) ?? null);
43
+ }
44
+ /** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
45
+ async function dispatchRoute(request, routes, document = false) {
46
+ const response = await dispatch(request, routes, document);
47
+ return document ? response : privateResponse(response);
48
+ }
49
+ //#endregion
50
+ export { dispatchRoute };
@@ -8,3 +8,4 @@ export declare function loadRouter(routes: readonly Route[], input: string | Req
8
8
  export { serialize } from "../core/index.ts";
9
9
  /** Dispatch a server data request with an explicit private cache policy. */
10
10
  export declare function handleRequest(routes: readonly Route[], request: Request): Promise<Response>;
11
+ export { dispatchRoute } from "./dispatch.ts";
@@ -3,6 +3,7 @@ import { matchRoutes } from "../core/match.js";
3
3
  import { createDataRouter } from "../core/router.js";
4
4
  import { serialize } from "../core/serialize.js";
5
5
  import { privateResponse } from "./private-response.js";
6
+ import { dispatchRoute } from "./dispatch.js";
6
7
  //#region .generated/server/index.ts
7
8
  /**
8
9
  * Request-scoped server rendering and HTTP data dispatch.
@@ -83,4 +84,4 @@ async function handleRequest(routes, request) {
83
84
  return privateResponse(await dispatchRequest(routes, request));
84
85
  }
85
86
  //#endregion
86
- export { handleRequest, loadRouter, serialize };
87
+ export { dispatchRoute, handleRequest, loadRouter, serialize };
@@ -1,4 +1,5 @@
1
1
  import { serialize } from "../core/serialize.js";
2
+ import { dispatchRoute } from "./dispatch.js";
2
3
  import { handleRequest, loadRouter } from "./index.js";
3
- import { dispatchRoute, renderApplication } from "./application.js";
4
+ import { renderApplication } from "./application.js";
4
5
  export { dispatchRoute, handleRequest, loadRouter, renderApplication, serialize };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * React SSR rendering and route data dispatch.
3
+ * @module
4
+ */
5
+ import type { AppRoute, RootModule } from "../react/route-types.ts";
6
+ import { type RenderApplicationOptions } from "./render-application.ts";
7
+ export * from "./index.ts";
8
+ export type { RenderApplicationOptions } from "./render-application.ts";
9
+ /** Render a React document with root and route data using React's streaming renderer. */
10
+ export declare function renderApplication(request: Request, routes: AppRoute[], rootModule: RootModule, entry: string, options?: RenderApplicationOptions): Promise<Response>;
@@ -0,0 +1,22 @@
1
+ import { serialize } from "../core/serialize.js";
2
+ import { RouterApplication } from "../react/router.js";
3
+ import { dispatchRoute } from "./dispatch.js";
4
+ import { handleRequest, loadRouter } from "./index.js";
5
+ import { renderDocument } from "./render-application.js";
6
+ import { createElement } from "react";
7
+ import * as ReactDOMServer from "react-dom/server";
8
+ //#region .generated/server/react.ts
9
+ /** Render a React document with root and route data using React's streaming renderer. */
10
+ async function renderApplication(request, routes, rootModule, entry, options = {}) {
11
+ const { renderToReadableStream } = ReactDOMServer;
12
+ if (!renderToReadableStream) throw new Error("renderApplication requires React DOM 19 or newer.");
13
+ return renderDocument(request, routes, rootModule, entry, options, (props) => renderToReadableStream(createElement(RouterApplication, {
14
+ ...props,
15
+ rootModule
16
+ }), {
17
+ nonce: options.nonce,
18
+ signal: request.signal
19
+ }));
20
+ }
21
+ //#endregion
22
+ export { dispatchRoute, handleRequest, loadRouter, renderApplication, serialize };
@@ -0,0 +1,31 @@
1
+ import type { DataArgs, Route, Router } from "../core/index.ts";
2
+ interface ServerRoot {
3
+ loader?: (args: DataArgs) => unknown;
4
+ ErrorBoundary?: unknown;
5
+ }
6
+ export interface RenderProps {
7
+ router: Router;
8
+ routes: (Route & {
9
+ routePath?: string;
10
+ })[];
11
+ root: unknown;
12
+ entry: string;
13
+ rootError: boolean;
14
+ streaming: boolean;
15
+ initialData?: string;
16
+ document: boolean;
17
+ }
18
+ /** SSR settings for CSP nonce and early deferred-data hydration. */
19
+ export interface RenderApplicationOptions {
20
+ nonce?: string;
21
+ earlyHydration?: boolean;
22
+ }
23
+ /** Render a Preact application response with root and route data, streaming deferred content when enabled. */
24
+ export declare function renderDocument(request: Request, routes: (Route & {
25
+ routePath?: string;
26
+ })[], rootModule: ServerRoot, entry: string, options: RenderApplicationOptions, render: (props: RenderProps) => (ReadableStream<Uint8Array> & {
27
+ allReady: Promise<unknown>;
28
+ }) | Promise<ReadableStream<Uint8Array> & {
29
+ allReady: Promise<unknown>;
30
+ }>): Promise<Response>;
31
+ export {};
@@ -0,0 +1,118 @@
1
+ import { safeWrapAsync } from "../core/errors.js";
2
+ import { matchRoutes } from "../core/match.js";
3
+ import { createDataRouter } from "../core/router.js";
4
+ import { normalizeResult } from "../core/responses.js";
5
+ import { prepareRoutes } from "../core/prepare-routes.js";
6
+ import { dispatchRoute } from "./dispatch.js";
7
+ import { loadRouter } from "./index.js";
8
+ import { deferredHydration } from "./deferred-hydration.js";
9
+ import { progressiveDocument } from "./progressive-document.js";
10
+ import { resolveHydration, streamDocument } from "./stream-document.js";
11
+ //#region .generated/server/render-application.ts
12
+ /** Render a Preact application response with root and route data, streaming deferred content when enabled. */
13
+ async function renderDocument(request, routes, rootModule, entry, options, render) {
14
+ routes = routes.map((route) => ({
15
+ ...route,
16
+ path: route.routePath ?? route.path
17
+ }));
18
+ const match = matchRoutes(routes, new URL(request.url).pathname);
19
+ if (match && !match.route.default) return dispatchRoute(request, routes, true);
20
+ let actionResponse;
21
+ let actionData;
22
+ if (request.method !== "GET" && request.method !== "HEAD") {
23
+ actionResponse = await dispatchRoute(request, routes, true);
24
+ if (actionResponse.headers.has("Location") || !actionResponse.headers.get("Content-Type")?.includes("application/json")) return actionResponse;
25
+ actionData = await normalizeResult(await actionResponse.json());
26
+ }
27
+ const rootResult = safeWrapAsync(() => Promise.resolve().then(() => rootModule.loader?.({
28
+ request,
29
+ params: {},
30
+ signal: request.signal
31
+ })).then((value) => normalizeResult(value, false), (error) => error instanceof Response ? normalizeResult(error, false) : error instanceof Error ? error : new Error(String(error), { cause: error })));
32
+ let redirectResponse;
33
+ const prepared = prepareRoutes(routes.map((route) => ({
34
+ ...route,
35
+ ErrorBoundary: route.ErrorBoundary ?? rootModule.ErrorBoundary
36
+ })), true).map((route) => {
37
+ if (!route.loader) return route;
38
+ const loader = route.loader;
39
+ return {
40
+ ...route,
41
+ loader: async (args) => {
42
+ const value = await loader(args);
43
+ if (value instanceof Response && value.headers.has("Location")) redirectResponse = value;
44
+ return value;
45
+ }
46
+ };
47
+ });
48
+ const [rootState, loadedRouter] = await Promise.all([rootResult, loadRouter(prepared, request)]);
49
+ let router = loadedRouter;
50
+ const rootError = rootState.error ?? (rootState.data instanceof Error ? rootState.data : void 0);
51
+ const root = rootError ? void 0 : rootState.data;
52
+ if (root instanceof Response && root.headers.has("Location")) {
53
+ router.dispose();
54
+ return root;
55
+ }
56
+ if (redirectResponse) {
57
+ router.dispose();
58
+ return redirectResponse;
59
+ }
60
+ if (actionResponse || rootError) {
61
+ const hydrationData = {
62
+ ...router.getSnapshot(),
63
+ actionData: actionData instanceof Error ? void 0 : actionData,
64
+ ...actionData instanceof Error && { error: actionData },
65
+ ...rootError && { error: rootError }
66
+ };
67
+ router.dispose();
68
+ router = createDataRouter(router.routes, { hydrationData });
69
+ }
70
+ const snapshot = router.getSnapshot();
71
+ const hydrationData = {
72
+ root,
73
+ snapshot: {
74
+ ...snapshot,
75
+ error: snapshot.error && {
76
+ ...snapshot.error,
77
+ name: snapshot.error.name,
78
+ message: snapshot.error.message
79
+ }
80
+ },
81
+ ...rootError && { rootError: true }
82
+ };
83
+ const deferred = options.earlyHydration ? deferredHydration(hydrationData, options.nonce) : void 0;
84
+ const stream = await Promise.resolve().then(() => render({
85
+ router,
86
+ routes,
87
+ root,
88
+ entry,
89
+ rootError: Boolean(rootError),
90
+ streaming: true,
91
+ initialData: deferred?.initial,
92
+ document: true
93
+ })).catch(async (error) => {
94
+ router.dispose();
95
+ await deferred?.stream.cancel();
96
+ throw error;
97
+ });
98
+ stream.allReady.catch(() => {});
99
+ const headers = new Headers(actionResponse?.headers);
100
+ headers.delete("Content-Length");
101
+ headers.delete("Content-Encoding");
102
+ headers.set("Content-Type", "text/html; charset=utf-8");
103
+ headers.set("Cache-Control", "no-store");
104
+ return new Response(deferred ? progressiveDocument(stream, deferred.stream, {
105
+ signal: request.signal,
106
+ dispose: () => router.dispose()
107
+ }) : streamDocument(stream, {
108
+ hydration: resolveHydration(hydrationData),
109
+ signal: request.signal,
110
+ nonce: options.nonce,
111
+ dispose: () => router.dispose()
112
+ }), {
113
+ status: snapshot.error ? Number("status" in snapshot.error ? snapshot.error.status : 500) : actionResponse?.status ?? 200,
114
+ headers
115
+ });
116
+ }
117
+ //#endregion
118
+ export { renderDocument };
@@ -1,2 +1,2 @@
1
1
  import { type Plugin } from "vite";
2
- export declare function lazyFiles(directory: string, dataEndpoint?: string): Plugin;
2
+ export declare function lazyFiles(directory: string, dataEndpoint?: string, framework?: "react" | "preact"): Plugin;
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { transformWithOxc } from "vite";
5
5
  //#region .generated/vite/application/lazy-files.ts
6
- function lazyFiles(directory, dataEndpoint = "/_smolter") {
6
+ function lazyFiles(directory, dataEndpoint = "/_smolter", framework = "preact") {
7
7
  let root;
8
8
  const modules = /* @__PURE__ */ new Map();
9
9
  return {
@@ -81,7 +81,7 @@ function lazyFiles(directory, dataEndpoint = "/_smolter") {
81
81
  if (fields.default) modules.set(file, String(fields.path));
82
82
  definitions.push(`lazyRoute(${JSON.stringify(fields)}, () => import(${JSON.stringify(file)}))`);
83
83
  }
84
- return ["import { lazyRoute } from \"@kasperrt/smolter/preact/lazy-route\";", `export default [${definitions.join(",\n")}];`].join("\n");
84
+ return [`import { lazyRoute } from "@kasperrt/smolter/${framework}/lazy-route";`, `export default [${definitions.join(",\n")}];`].join("\n");
85
85
  }
86
86
  };
87
87
  }
@@ -1,10 +1,11 @@
1
1
  import type { Plugin, UserConfig } from "vite";
2
2
  export interface ApplicationOptions {
3
3
  directory?: string;
4
+ framework?: "react" | "preact";
4
5
  dataEndpoint?: string;
5
6
  ssr?: boolean;
6
7
  splitRoutes?: boolean;
7
8
  resourceRequest?: (url: string) => boolean;
8
9
  }
9
10
  export declare const preactConfig: UserConfig;
10
- export declare function applicationPlugins({ directory, dataEndpoint, ssr, splitRoutes, resourceRequest, }?: ApplicationOptions): Plugin[];
11
+ export declare function applicationPlugins({ directory, framework, dataEndpoint, ssr, splitRoutes, resourceRequest, }?: ApplicationOptions): Plugin[];