@kasperrt/smolter 0.0.1 → 0.3.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.
Files changed (246) hide show
  1. package/{src/internal → .generated}/client/index.ts +1 -0
  2. package/.generated/core/environment.ts +43 -0
  3. package/{src/internal → .generated}/core/errors.ts +1 -1
  4. package/{src/internal → .generated}/core/index.ts +2 -0
  5. package/.generated/core/navigation-router.ts +107 -0
  6. package/.generated/core/navigation.ts +147 -0
  7. package/.generated/core/presentation.ts +108 -0
  8. package/.generated/core/resource-types.ts +44 -0
  9. package/.generated/core/resources.ts +444 -0
  10. package/.generated/core/responses.ts +86 -0
  11. package/{src/internal → .generated}/core/router.ts +136 -63
  12. package/.generated/core/serialize.ts +20 -0
  13. package/.generated/core/store.ts +25 -0
  14. package/.generated/core/submission.ts +116 -0
  15. package/{src/internal → .generated}/core/types.ts +18 -1
  16. package/.generated/core/url.ts +29 -0
  17. package/.generated/core/utils.ts +5 -0
  18. package/.generated/preact/await.tsx +41 -0
  19. package/.generated/preact/client.tsx +93 -0
  20. package/.generated/preact/context.ts +59 -0
  21. package/.generated/preact/document.tsx +198 -0
  22. package/{src/internal → .generated}/preact/fetcher.ts +43 -23
  23. package/.generated/preact/form.ts +58 -0
  24. package/{src/internal → .generated}/preact/hooks.ts +25 -28
  25. package/.generated/preact/index.ts +34 -0
  26. package/.generated/preact/lazy-route.ts +100 -0
  27. package/.generated/preact/navigation-state.ts +9 -0
  28. package/.generated/preact/navigation.tsx +147 -0
  29. package/.generated/preact/root-context.ts +21 -0
  30. package/.generated/preact/root-data.ts +45 -0
  31. package/.generated/preact/route-hooks.tsx +170 -0
  32. package/.generated/preact/route-types.ts +76 -0
  33. package/.generated/preact/router.tsx +107 -0
  34. package/{src/internal → .generated}/preact/routes.ts +17 -9
  35. package/.generated/react/context.ts +59 -0
  36. package/.generated/react/data.ts +25 -0
  37. package/{src/internal → .generated}/react/fetcher.ts +13 -11
  38. package/.generated/react/form.ts +58 -0
  39. package/{src/internal → .generated}/react/hooks.ts +9 -14
  40. package/{src/internal/preact → .generated/react}/index.ts +5 -1
  41. package/{src/internal → .generated}/react/link.ts +13 -21
  42. package/.generated/react/resources/components.tsx +404 -0
  43. package/{src/internal → .generated}/react/routes.ts +17 -9
  44. package/.generated/server/application.tsx +185 -0
  45. package/.generated/server/assets.ts +19 -0
  46. package/.generated/server/handler.ts +69 -0
  47. package/.generated/server/http.ts +55 -0
  48. package/{src/internal → .generated}/server/index.ts +4 -17
  49. package/.generated/server/node.ts +4 -0
  50. package/.generated/server/preact.ts +7 -0
  51. package/.generated/vite/application/browser-boundary.ts +42 -0
  52. package/.generated/vite/application/lazy-files.ts +136 -0
  53. package/.generated/vite/application/vite.ts +144 -0
  54. package/{src/internal → .generated}/vite/index.ts +4 -2
  55. package/.generated/vite/preact.ts +24 -0
  56. package/.generated/vite/react.ts +18 -0
  57. package/{src/internal → .generated}/vite/transport.ts +2 -1
  58. package/CHANGELOG.md +54 -0
  59. package/README.md +269 -55
  60. package/dist/client/deserialize.js +10 -0
  61. package/dist/{internal/client → client}/index.d.ts +1 -0
  62. package/dist/client/index.js +46 -0
  63. package/dist/core/environment.d.ts +12 -0
  64. package/dist/core/environment.js +23 -0
  65. package/dist/core/errors.js +7 -0
  66. package/dist/{internal/core → core}/index.d.ts +2 -0
  67. package/dist/core/match.js +33 -0
  68. package/dist/core/navigation-router.d.ts +15 -0
  69. package/dist/core/navigation-router.js +64 -0
  70. package/dist/core/navigation.d.ts +15 -0
  71. package/dist/core/navigation.js +92 -0
  72. package/dist/core/presentation.d.ts +5 -0
  73. package/dist/core/presentation.js +69 -0
  74. package/dist/core/resource-types.d.ts +37 -0
  75. package/dist/core/resources.d.ts +19 -0
  76. package/dist/core/resources.js +345 -0
  77. package/dist/core/responses.d.ts +15 -0
  78. package/dist/core/responses.js +48 -0
  79. package/dist/{internal/core → core}/router.d.ts +1 -1
  80. package/dist/core/router.js +204 -0
  81. package/dist/core/serialize.d.ts +5 -0
  82. package/dist/core/serialize.js +17 -0
  83. package/dist/core/store.d.ts +6 -0
  84. package/dist/core/store.js +21 -0
  85. package/dist/core/submission.d.ts +30 -0
  86. package/dist/core/submission.js +41 -0
  87. package/dist/{internal/core → core}/types.d.ts +16 -1
  88. package/dist/core/url.d.ts +7 -0
  89. package/dist/core/url.js +12 -0
  90. package/dist/core/utils.d.ts +5 -0
  91. package/dist/core/utils.js +5 -0
  92. package/dist/files/index.js +64 -0
  93. package/dist/preact/await.d.ts +7 -0
  94. package/dist/preact/await.js +19 -0
  95. package/dist/preact/client.d.ts +7 -0
  96. package/dist/preact/client.js +67 -0
  97. package/dist/preact/context.d.ts +18 -0
  98. package/dist/preact/context.js +23 -0
  99. package/dist/preact/document.d.ts +15 -0
  100. package/dist/preact/document.js +155 -0
  101. package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
  102. package/dist/preact/fetcher.js +45 -0
  103. package/dist/{internal/preact → preact}/form.d.ts +5 -6
  104. package/dist/preact/form.js +21 -0
  105. package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
  106. package/dist/preact/hooks.js +25 -0
  107. package/dist/preact/index.d.ts +18 -0
  108. package/dist/preact/index.js +15 -0
  109. package/dist/preact/lazy-route.d.ts +19 -0
  110. package/dist/preact/lazy-route.js +51 -0
  111. package/dist/preact/navigation-state.d.ts +2 -0
  112. package/dist/preact/navigation-state.js +9 -0
  113. package/dist/preact/navigation.d.ts +28 -0
  114. package/dist/preact/navigation.js +60 -0
  115. package/dist/preact/root-context.d.ts +13 -0
  116. package/dist/preact/root-context.js +9 -0
  117. package/dist/preact/root-data.d.ts +8 -0
  118. package/dist/preact/root-data.js +28 -0
  119. package/dist/preact/route-hooks.d.ts +32 -0
  120. package/dist/preact/route-hooks.js +94 -0
  121. package/dist/preact/route-types.d.ts +54 -0
  122. package/dist/preact/router.d.ts +16 -0
  123. package/dist/preact/router.js +63 -0
  124. package/dist/{internal/preact → preact}/routes.d.ts +1 -1
  125. package/dist/preact/routes.js +12 -0
  126. package/dist/{internal/preact → preact}/runtime.js +1 -1
  127. package/dist/react/context.d.ts +18 -0
  128. package/dist/react/context.js +23 -0
  129. package/dist/react/data.d.ts +5 -0
  130. package/dist/react/data.js +3 -0
  131. package/dist/{internal/react → react}/fetcher.d.ts +2 -2
  132. package/dist/react/fetcher.js +34 -0
  133. package/dist/{internal/react → react}/form.d.ts +5 -6
  134. package/dist/react/form.js +21 -0
  135. package/dist/react/hooks.js +25 -0
  136. package/dist/{internal/react → react}/index.d.ts +1 -1
  137. package/dist/react/index.js +10 -0
  138. package/dist/{internal/react → react}/link.d.ts +2 -2
  139. package/dist/react/link.js +27 -0
  140. package/dist/react/resources/components.d.ts +64 -0
  141. package/dist/react/resources/components.js +239 -0
  142. package/dist/{internal/react → react}/routes.d.ts +1 -1
  143. package/dist/react/routes.js +12 -0
  144. package/dist/react/runtime.js +1 -0
  145. package/dist/server/application.d.ts +4 -0
  146. package/dist/server/application.js +119 -0
  147. package/dist/server/assets.d.ts +5 -0
  148. package/dist/server/assets.js +7 -0
  149. package/dist/server/handler.d.ts +19 -0
  150. package/dist/server/handler.js +22 -0
  151. package/dist/server/http.d.ts +4 -0
  152. package/dist/server/http.js +27 -0
  153. package/dist/{internal/server → server}/index.d.ts +2 -3
  154. package/dist/server/index.js +76 -0
  155. package/dist/server/node.d.ts +3 -0
  156. package/dist/server/node.js +3 -0
  157. package/dist/server/preact.d.ts +3 -0
  158. package/dist/server/preact.js +4 -0
  159. package/dist/vite/application/browser-boundary.d.ts +3 -0
  160. package/dist/vite/application/browser-boundary.js +35 -0
  161. package/dist/vite/application/lazy-files.d.ts +2 -0
  162. package/dist/vite/application/lazy-files.js +87 -0
  163. package/dist/vite/application/vite.d.ts +9 -0
  164. package/dist/vite/application/vite.js +101 -0
  165. package/dist/{internal/vite → vite}/index.d.ts +2 -1
  166. package/dist/vite/index.js +104 -0
  167. package/dist/vite/preact.d.ts +11 -0
  168. package/dist/vite/preact.js +15 -0
  169. package/dist/vite/react.d.ts +9 -0
  170. package/dist/vite/react.js +14 -0
  171. package/dist/{internal/vite → vite}/transport.d.ts +1 -1
  172. package/dist/vite/transport.js +35 -0
  173. package/jsr.json +28 -11
  174. package/package.json +119 -38
  175. package/dist/client.d.ts +0 -2
  176. package/dist/client.js +0 -2
  177. package/dist/core.d.ts +0 -2
  178. package/dist/core.js +0 -2
  179. package/dist/files.d.ts +0 -2
  180. package/dist/files.js +0 -2
  181. package/dist/internal/client/deserialize.js +0 -11
  182. package/dist/internal/client/index.js +0 -44
  183. package/dist/internal/core/errors.js +0 -6
  184. package/dist/internal/core/index.js +0 -6
  185. package/dist/internal/core/match.js +0 -27
  186. package/dist/internal/core/router.js +0 -189
  187. package/dist/internal/core/types.js +0 -5
  188. package/dist/internal/core/url.d.ts +0 -2
  189. package/dist/internal/core/url.js +0 -6
  190. package/dist/internal/files/index.js +0 -77
  191. package/dist/internal/preact/context.d.ts +0 -18
  192. package/dist/internal/preact/context.js +0 -20
  193. package/dist/internal/preact/fetcher.js +0 -28
  194. package/dist/internal/preact/form.js +0 -41
  195. package/dist/internal/preact/hooks.js +0 -35
  196. package/dist/internal/preact/index.d.ts +0 -9
  197. package/dist/internal/preact/index.js +0 -7
  198. package/dist/internal/preact/link.d.ts +0 -12
  199. package/dist/internal/preact/link.js +0 -28
  200. package/dist/internal/preact/routes.js +0 -22
  201. package/dist/internal/react/context.d.ts +0 -18
  202. package/dist/internal/react/context.js +0 -20
  203. package/dist/internal/react/fetcher.js +0 -28
  204. package/dist/internal/react/form.js +0 -41
  205. package/dist/internal/react/hooks.js +0 -35
  206. package/dist/internal/react/index.js +0 -7
  207. package/dist/internal/react/link.js +0 -28
  208. package/dist/internal/react/routes.js +0 -22
  209. package/dist/internal/react/runtime.js +0 -2
  210. package/dist/internal/server/index.js +0 -81
  211. package/dist/internal/vite/index.js +0 -132
  212. package/dist/internal/vite/transport.js +0 -42
  213. package/dist/preact.d.ts +0 -2
  214. package/dist/preact.js +0 -2
  215. package/dist/react.d.ts +0 -2
  216. package/dist/react.js +0 -2
  217. package/dist/server.d.ts +0 -2
  218. package/dist/server.js +0 -2
  219. package/dist/vite.d.ts +0 -2
  220. package/dist/vite.js +0 -2
  221. package/src/client.ts +0 -2
  222. package/src/core.ts +0 -2
  223. package/src/files.ts +0 -2
  224. package/src/internal/core/url.ts +0 -6
  225. package/src/internal/preact/context.ts +0 -46
  226. package/src/internal/preact/form.ts +0 -79
  227. package/src/internal/preact/link.ts +0 -46
  228. package/src/internal/react/context.ts +0 -46
  229. package/src/internal/react/form.ts +0 -79
  230. package/src/internal/react/index.ts +0 -13
  231. package/src/preact.ts +0 -2
  232. package/src/react.ts +0 -2
  233. package/src/server.ts +0 -2
  234. package/src/vite.ts +0 -2
  235. /package/{src/internal → .generated}/client/deserialize.ts +0 -0
  236. /package/{src/internal → .generated}/core/match.ts +0 -0
  237. /package/{src/internal → .generated}/files/index.ts +0 -0
  238. /package/{src/internal → .generated}/preact/runtime.ts +0 -0
  239. /package/{src/internal → .generated}/react/runtime.ts +0 -0
  240. /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
  241. /package/dist/{internal/core → core}/errors.d.ts +0 -0
  242. /package/dist/{internal/core → core}/match.d.ts +0 -0
  243. /package/dist/{internal/files → files}/index.d.ts +0 -0
  244. /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
  245. /package/dist/{internal/react → react}/hooks.d.ts +0 -0
  246. /package/dist/{internal/react → react}/runtime.d.ts +0 -0
@@ -0,0 +1,119 @@
1
+ import { matchRoutes } from "../core/match.js";
2
+ import { createDataRouter } from "../core/router.js";
3
+ import { normalizeResult, resolveData } from "../core/responses.js";
4
+ import { RouterApplication, prepareRoutes } from "../preact/router.js";
5
+ import { loadRouter } from "./index.js";
6
+ import { jsx } from "preact/jsx-runtime";
7
+ import { renderToStringAsync } from "preact-render-to-string";
8
+ //#region .generated/server/application.tsx
9
+ /** @jsxImportSource preact */
10
+ async function renderApplication(request, routes, rootModule, entry) {
11
+ routes = routes.map((route) => ({
12
+ ...route,
13
+ path: route.routePath ?? route.path
14
+ }));
15
+ const match = matchRoutes(routes, new URL(request.url).pathname);
16
+ if (match && !match.route.default) return dispatchRoute(request, routes, true);
17
+ let actionResponse;
18
+ let actionData;
19
+ if (request.method !== "GET" && request.method !== "HEAD") {
20
+ actionResponse = await dispatchRoute(request, routes, true);
21
+ if (actionResponse.headers.has("Location") || !actionResponse.headers.get("Content-Type")?.includes("application/json")) return actionResponse;
22
+ actionData = await normalizeResult(await actionResponse.json());
23
+ }
24
+ const root = await resolveData(await rootModule.loader?.({
25
+ request,
26
+ params: {},
27
+ signal: request.signal
28
+ }));
29
+ let redirectResponse;
30
+ const prepared = prepareRoutes(routes.map((route) => ({
31
+ ...route,
32
+ ErrorBoundary: route.ErrorBoundary ?? rootModule.ErrorBoundary
33
+ }))).map((route) => {
34
+ if (!route.loader) return route;
35
+ const loader = route.loader;
36
+ return {
37
+ ...route,
38
+ loader: async (args) => {
39
+ const value = await loader(args);
40
+ if (value instanceof Response && value.headers.has("Location")) redirectResponse = value;
41
+ return value;
42
+ }
43
+ };
44
+ });
45
+ let router = await loadRouter(prepared, request);
46
+ if (redirectResponse) {
47
+ router.dispose();
48
+ return redirectResponse;
49
+ }
50
+ if (actionResponse) {
51
+ const hydrationData = {
52
+ ...router.getSnapshot(),
53
+ actionData: actionData instanceof Error ? void 0 : actionData,
54
+ ...actionData instanceof Error && { error: actionData }
55
+ };
56
+ router.dispose();
57
+ router = createDataRouter(router.routes, { hydrationData });
58
+ }
59
+ const snapshot = router.getSnapshot();
60
+ const html = await renderToStringAsync(/* @__PURE__ */ jsx(RouterApplication, {
61
+ router,
62
+ routes,
63
+ root,
64
+ rootModule,
65
+ entry,
66
+ document: true
67
+ }));
68
+ router.dispose();
69
+ const headers = new Headers(actionResponse?.headers);
70
+ headers.delete("Content-Length");
71
+ headers.delete("Content-Encoding");
72
+ headers.set("Content-Type", "text/html; charset=utf-8");
73
+ headers.set("Cache-Control", "no-store");
74
+ return new Response(`<!doctype html>${html}`, {
75
+ status: snapshot.error ? Number("status" in snapshot.error ? snapshot.error.status : 500) : actionResponse?.status ?? 200,
76
+ headers
77
+ });
78
+ }
79
+ async function dispatchRoute(request, routes, document = false) {
80
+ const match = matchRoutes(routes.map((route) => ({
81
+ ...route,
82
+ path: route.routePath ?? route.path
83
+ })), new URL(request.url).pathname);
84
+ if (!match) return Response.json({ error: "No route" }, { status: 404 });
85
+ const handler = request.method === "GET" || request.method === "HEAD" ? match.route.loader : match.route.action;
86
+ if (!handler) return Response.json({ error: "No handler" }, { status: 405 });
87
+ const value = await Promise.resolve().then(() => handler({
88
+ request,
89
+ params: match.params,
90
+ signal: request.signal
91
+ })).then((result) => result, (error) => error);
92
+ if (value instanceof Response) {
93
+ const location = value.headers.get("Location");
94
+ if (document) return value;
95
+ if (!location) {
96
+ if (value.ok) return value;
97
+ return Response.json({ __routeError: {
98
+ message: await value.text(),
99
+ status: value.status,
100
+ statusText: value.statusText
101
+ } });
102
+ }
103
+ const headers = new Headers(value.headers);
104
+ headers.delete("Location");
105
+ headers.set("X-Smolter-Redirect", location);
106
+ return new Response(null, { headers });
107
+ }
108
+ if (value instanceof Error) return Response.json({ __routeError: {
109
+ ...value,
110
+ message: value.message
111
+ } });
112
+ if (value && typeof value === "object" && "__routeData" in value) {
113
+ const result = value;
114
+ return Response.json(await resolveData(result.value), document ? result.init : { headers: result.init.headers });
115
+ }
116
+ return Response.json(await normalizeResult(value) ?? null);
117
+ }
118
+ //#endregion
119
+ export { dispatchRoute, renderApplication };
@@ -0,0 +1,5 @@
1
+ export interface RouteAssets {
2
+ path: string;
3
+ files: string[];
4
+ }
5
+ export declare function routePreloads(routes: RouteAssets[], pathname: string, base: string): string;
@@ -0,0 +1,7 @@
1
+ import { matchRoutes } from "../core/match.js";
2
+ //#region .generated/server/assets.ts
3
+ function routePreloads(routes, pathname, base) {
4
+ return ((matchRoutes(routes, pathname)?.route)?.files ?? []).map((file) => `<${base.replace(/\/$/, "")}/${file}>; rel=modulepreload`).join(", ");
5
+ }
6
+ //#endregion
7
+ export { routePreloads };
@@ -0,0 +1,19 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ /** Server module produced by an application's SSR entrypoint. */
3
+ export interface ServerBuild {
4
+ default: (request: Request, entry: string) => Response | Promise<Response>;
5
+ handleDataRequest: (request: Request) => Response | Promise<Response>;
6
+ }
7
+ export interface RequestHandlerOptions {
8
+ build: ServerBuild;
9
+ /** Browser entry URL resolved from Vite's production manifest. */
10
+ entry: string;
11
+ }
12
+ export interface ServerIncomingMessage extends IncomingMessage {
13
+ /** Express supplies this using the application's trust proxy policy. */
14
+ protocol?: string;
15
+ originalUrl?: string;
16
+ }
17
+ export type NextHandler = (error?: unknown) => void;
18
+ /** Node/Express middleware; mount after metrics, tracing and static assets. */
19
+ export declare function createRequestHandler({ build, entry, }: RequestHandlerOptions): (incoming: ServerIncomingMessage, outgoing: ServerResponse, next: NextHandler) => Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { send, webRequest } from "./http.js";
2
+ //#region .generated/server/handler.ts
3
+ /** Node/Express middleware; mount after metrics, tracing and static assets. */
4
+ function createRequestHandler({ build, entry }) {
5
+ return (incoming, outgoing, next) => {
6
+ const handle = async () => {
7
+ const url = new URL(incoming.originalUrl ?? incoming.url ?? "/", "http://local");
8
+ const dataRequest = url.pathname === "/_smolter";
9
+ const target = dataRequest ? url.searchParams.get("url") : void 0;
10
+ if (dataRequest && (!target?.startsWith("/") || new URL(target, "http://local").origin !== "http://local")) {
11
+ await send(new Response("Invalid route URL", { status: 400 }), outgoing);
12
+ return;
13
+ }
14
+ const request = await webRequest(incoming, outgoing, target ?? void 0);
15
+ const response = dataRequest ? await build.handleDataRequest(request) : await build.default(request, entry);
16
+ await send(response, outgoing);
17
+ };
18
+ return handle().catch(next);
19
+ };
20
+ }
21
+ //#endregion
22
+ export { createRequestHandler };
@@ -0,0 +1,4 @@
1
+ import type { ServerResponse } from "node:http";
2
+ import type { ServerIncomingMessage } from "./handler.ts";
3
+ export declare function webRequest(incoming: ServerIncomingMessage, outgoing: ServerResponse, target?: string): Promise<Request>;
4
+ export declare function send(response: Response, outgoing: ServerResponse): Promise<void>;
@@ -0,0 +1,27 @@
1
+ //#region .generated/server/http.ts
2
+ async function webRequest(incoming, outgoing, target) {
3
+ const origin = `${incoming.protocol ?? "http"}://${incoming.headers.host}`;
4
+ const headers = new Headers();
5
+ for (const [name, value] of Object.entries(incoming.headers)) if (value) headers.set(name, Array.isArray(value) ? value.join(", ") : value);
6
+ const controller = new AbortController();
7
+ outgoing.once("close", () => controller.abort());
8
+ const chunks = [];
9
+ for await (const chunk of incoming) chunks.push(chunk);
10
+ return new Request(new URL(target ?? incoming.originalUrl ?? incoming.url ?? "/", origin), {
11
+ method: incoming.method,
12
+ headers,
13
+ signal: controller.signal,
14
+ ...incoming.method !== "GET" && incoming.method !== "HEAD" && { body: new Uint8Array(Buffer.concat(chunks)) }
15
+ });
16
+ }
17
+ async function send(response, outgoing) {
18
+ outgoing.statusCode = response.status;
19
+ response.headers.forEach((value, name) => {
20
+ if (name !== "set-cookie") outgoing.setHeader(name, value);
21
+ });
22
+ const cookies = response.headers.getSetCookie();
23
+ if (cookies.length) outgoing.setHeader("set-cookie", cookies.map((cookie) => process.env.NODE_ENV === "production" ? cookie : cookie.replace(/; Secure/gi, "")));
24
+ outgoing.end(new Uint8Array(await response.arrayBuffer()));
25
+ }
26
+ //#endregion
27
+ export { send, webRequest };
@@ -2,10 +2,9 @@
2
2
  * Request-scoped server rendering and HTTP data dispatch.
3
3
  * @module
4
4
  */
5
- import { type Route, type Router, type Snapshot } from "../core/index.ts";
5
+ import { type Route, type Router } from "../core/index.ts";
6
6
  /** Create one router per server request, await this, then render the provider. */
7
7
  export declare function loadRouter(routes: readonly Route[], input: string | Request): Promise<Router>;
8
- /** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
9
- export declare function serialize(snapshot: Snapshot): string;
8
+ export { serialize } from "../core/index.ts";
10
9
  /** Dispatch an HTTP data request to a registered server loader or action. */
11
10
  export declare function handleRequest(routes: readonly Route[], request: Request): Promise<Response>;
@@ -0,0 +1,76 @@
1
+ import { safeWrapAsync } from "../core/errors.js";
2
+ import { matchRoutes } from "../core/match.js";
3
+ import { createDataRouter } from "../core/router.js";
4
+ import { serialize } from "../core/serialize.js";
5
+ //#region .generated/server/index.ts
6
+ /**
7
+ * Request-scoped server rendering and HTTP data dispatch.
8
+ * @module
9
+ */
10
+ /** Create one router per server request, await this, then render the provider. */
11
+ async function loadRouter(routes, input) {
12
+ const url = typeof input === "string" ? input : input.url;
13
+ const wrap = (handler) => {
14
+ if (!handler || typeof input === "string") return handler;
15
+ return (args) => {
16
+ const signal = AbortSignal.any([input.signal, args.signal]);
17
+ const request = new Request(args.request, { signal });
18
+ input.headers.forEach((value, name) => {
19
+ if (!request.headers.has(name)) request.headers.set(name, value);
20
+ });
21
+ return handler({
22
+ ...args,
23
+ request,
24
+ signal
25
+ });
26
+ };
27
+ };
28
+ const serverRoutes = routes.map((route) => ({
29
+ ...route,
30
+ clientLoader: void 0,
31
+ clientAction: void 0,
32
+ loader: wrap(route.loader),
33
+ action: wrap(route.action)
34
+ }));
35
+ const router = createDataRouter(serverRoutes, { url });
36
+ await router.navigate(url);
37
+ const snapshot = router.getSnapshot();
38
+ if (!snapshot.error && snapshot.data === void 0 && routes.some((route) => route.path === snapshot.path && route.clientLoader && !route.loader)) return createDataRouter(serverRoutes, {
39
+ url,
40
+ hydrationData: {
41
+ ...snapshot,
42
+ loaded: false,
43
+ state: "loading"
44
+ }
45
+ });
46
+ return router;
47
+ }
48
+ /** Dispatch an HTTP data request to a registered server loader or action. */
49
+ async function handleRequest(routes, request) {
50
+ const match = matchRoutes(routes, new URL(request.url).pathname);
51
+ const reading = request.method === "GET";
52
+ if (!match) return Response.json({ error: "No route" }, { status: 404 });
53
+ if (!reading && ![
54
+ "POST",
55
+ "PUT",
56
+ "PATCH",
57
+ "DELETE"
58
+ ].includes(request.method)) return Response.json({ error: "Method not allowed" }, { status: 405 });
59
+ const handler = reading ? match.route.loader : match.route.action;
60
+ if (!handler) return Response.json({ error: `No ${reading ? "loader" : "action"}` }, { status: 405 });
61
+ const result = await safeWrapAsync(() => handler({
62
+ request,
63
+ params: match.params,
64
+ signal: request.signal
65
+ }));
66
+ const error = result.error ?? (result.data instanceof Error && result.data);
67
+ if (error) return Response.json({ error: error.message }, { status: 500 });
68
+ if (result.data instanceof Response) {
69
+ const location = result.data.headers.get("Location");
70
+ if (location) return new Response(null, { headers: { "X-Smolter-Redirect": location } });
71
+ return result.data;
72
+ }
73
+ return Response.json(result.data ?? null, { headers: { "Cache-Control": "no-store" } });
74
+ }
75
+ //#endregion
76
+ export { handleRequest, loadRouter, serialize };
@@ -0,0 +1,3 @@
1
+ /** Optional application integration. @module */
2
+ export * from "./handler.ts";
3
+ export * from "./http.ts";
@@ -0,0 +1,3 @@
1
+ import { send, webRequest } from "./http.js";
2
+ import { createRequestHandler } from "./handler.js";
3
+ export { createRequestHandler, send, webRequest };
@@ -0,0 +1,3 @@
1
+ /** Preact server rendering and HTTP data dispatch. @module */
2
+ export { dispatchRoute, renderApplication, } from "./application.tsx";
3
+ export * from "./index.ts";
@@ -0,0 +1,4 @@
1
+ import { serialize } from "../core/serialize.js";
2
+ import { handleRequest, loadRouter } from "./index.js";
3
+ import { dispatchRoute, renderApplication } from "./application.js";
4
+ export { dispatchRoute, handleRequest, loadRouter, renderApplication, serialize };
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "vite";
2
+ /** Check emitted modules, after tree shaking, rather than import strings. */
3
+ export declare function browserBoundary(): Plugin;
@@ -0,0 +1,35 @@
1
+ import { builtinModules } from "node:module";
2
+ //#region .generated/vite/application/browser-boundary.ts
3
+ var builtins = new Set(builtinModules.flatMap((id) => [id, `node:${id}`]));
4
+ var forbiddenModules = [
5
+ /(?:vite-browser-external|__vite-browser-external)/,
6
+ /\/node_modules\/(?:react|react-dom|react-router|react-router-dom)\//,
7
+ /\/smolter\/dist\/(?:internal\/)?(?:server|vite|files)\//,
8
+ /\/smolter\/packages\/(?:server|vite)\/src\//,
9
+ /\/vite\/dist\/node\//,
10
+ /\/src\/[^?]*\.server\.[cm]?[jt]sx?(?:\?|$)/
11
+ ];
12
+ /** Check emitted modules, after tree shaking, rather than import strings. */
13
+ function browserBoundary() {
14
+ return {
15
+ name: "smolter-browser-boundary",
16
+ apply: "build",
17
+ applyToEnvironment: (environment) => environment.name === "client",
18
+ generateBundle(_options, bundle) {
19
+ for (const output of Object.values(bundle)) {
20
+ if (output.type !== "chunk") continue;
21
+ for (const id of [...output.imports, ...output.dynamicImports]) {
22
+ if (!builtins.has(id)) continue;
23
+ this.error(`Node builtin in browser chunk ${output.fileName}: ${id}`);
24
+ }
25
+ for (const id of Object.keys(output.modules)) {
26
+ const normalized = id.replaceAll("\\", "/");
27
+ if (!forbiddenModules.some((pattern) => pattern.test(normalized))) continue;
28
+ this.error(`Unexpected browser module in ${output.fileName}: ${id}`);
29
+ }
30
+ }
31
+ }
32
+ };
33
+ }
34
+ //#endregion
35
+ export { browserBoundary };
@@ -0,0 +1,2 @@
1
+ import { type Plugin } from "vite";
2
+ export declare function lazyFiles(directory: string): Plugin;
@@ -0,0 +1,87 @@
1
+ import { flatRoutes } from "../../files/index.js";
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { transformWithOxc } from "vite";
5
+ //#region .generated/vite/application/lazy-files.ts
6
+ function lazyFiles(directory) {
7
+ let root;
8
+ const modules = /* @__PURE__ */ new Map();
9
+ return {
10
+ name: "smolter-lazy-routes",
11
+ enforce: "pre",
12
+ configResolved(config) {
13
+ root = resolve(config.root, directory);
14
+ },
15
+ generateBundle(_options, bundle) {
16
+ if (!modules.size) return;
17
+ const routes = [];
18
+ for (const chunk of Object.values(bundle)) {
19
+ if (chunk.type !== "chunk" || !chunk.facadeModuleId) continue;
20
+ const path = modules.get(chunk.facadeModuleId);
21
+ if (!path) continue;
22
+ const files = /* @__PURE__ */ new Set();
23
+ function visit(file) {
24
+ if (files.has(file)) return;
25
+ files.add(file);
26
+ const dependency = bundle[file];
27
+ if (dependency?.type !== "chunk") return;
28
+ for (const imported of dependency.imports) visit(imported);
29
+ }
30
+ visit(chunk.fileName);
31
+ routes.push({
32
+ path,
33
+ files: [...files]
34
+ });
35
+ }
36
+ const paths = [...modules.values()];
37
+ routes.sort((left, right) => paths.indexOf(left.path) - paths.indexOf(right.path));
38
+ this.emitFile({
39
+ type: "asset",
40
+ fileName: ".vite/route-assets.json",
41
+ source: JSON.stringify(routes)
42
+ });
43
+ },
44
+ async load(id, options) {
45
+ if (id !== "\0virtual:smolter/routes" || options?.ssr) return;
46
+ const routes = await flatRoutes(root);
47
+ if (routes instanceof Error) return this.error(routes.message);
48
+ const definitions = [];
49
+ for (const route of routes) {
50
+ this.addWatchFile(route.file);
51
+ const source = await readFile(route.file, "utf8");
52
+ const transformed = await transformWithOxc(source, route.file);
53
+ const ast = this.parse(transformed.code);
54
+ const fields = { path: route.path };
55
+ for (const statement of ast.body) {
56
+ if (statement.type === "ExportDefaultDeclaration") {
57
+ fields.default = true;
58
+ continue;
59
+ }
60
+ if (statement.type !== "ExportNamedDeclaration") continue;
61
+ if (statement.declaration?.type === "FunctionDeclaration") {
62
+ const name = statement.declaration.id?.name;
63
+ if (name) fields[name] = true;
64
+ }
65
+ for (const specifier of statement.specifiers) {
66
+ const name = specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value;
67
+ fields[name] = true;
68
+ }
69
+ if (statement.declaration?.type !== "VariableDeclaration") continue;
70
+ for (const declaration of statement.declaration.declarations) {
71
+ if (declaration.id.type === "Identifier") fields[declaration.id.name] = true;
72
+ if (declaration.id.type !== "Identifier" || declaration.init?.type !== "Literal" || typeof declaration.init.value !== "string") continue;
73
+ const name = declaration.id.name;
74
+ if (name === "id") fields.id = declaration.init.value;
75
+ if (name === "routePath") fields.path = declaration.init.value;
76
+ }
77
+ }
78
+ const file = `${route.file.replaceAll("\\", "/")}?smolter-client`;
79
+ if (fields.default) modules.set(file, String(fields.path));
80
+ definitions.push(`lazyRoute(${JSON.stringify(fields)}, () => import(${JSON.stringify(file)}))`);
81
+ }
82
+ return ["import { lazyRoute } from \"@kasperrt/smolter/preact/lazy-route\";", `export default [${definitions.join(",\n")}];`].join("\n");
83
+ }
84
+ };
85
+ }
86
+ //#endregion
87
+ export { lazyFiles };
@@ -0,0 +1,9 @@
1
+ import type { Plugin, UserConfig } from "vite";
2
+ export interface ApplicationOptions {
3
+ directory?: string;
4
+ ssr?: boolean;
5
+ splitRoutes?: boolean;
6
+ resourceRequest?: (url: string) => boolean;
7
+ }
8
+ export declare const preactConfig: UserConfig;
9
+ export declare function applicationPlugins({ directory, ssr, splitRoutes, resourceRequest, }?: ApplicationOptions): Plugin[];
@@ -0,0 +1,101 @@
1
+ import { routerPlugin } from "../index.js";
2
+ import { send, webRequest } from "../../server/http.js";
3
+ import { browserBoundary } from "./browser-boundary.js";
4
+ import { lazyFiles } from "./lazy-files.js";
5
+ //#region .generated/vite/application/vite.ts
6
+ var preactConfig = {
7
+ resolve: {
8
+ alias: [
9
+ {
10
+ find: /^react\/jsx-dev-runtime$/,
11
+ replacement: "preact/jsx-dev-runtime"
12
+ },
13
+ {
14
+ find: /^react\/jsx-runtime$/,
15
+ replacement: "preact/jsx-runtime"
16
+ },
17
+ {
18
+ find: /^react-dom\/client$/,
19
+ replacement: "preact/compat/client"
20
+ },
21
+ {
22
+ find: /^react-dom\/server$/,
23
+ replacement: "preact-render-to-string"
24
+ },
25
+ {
26
+ find: /^react-dom$/,
27
+ replacement: "preact/compat"
28
+ },
29
+ {
30
+ find: /^react$/,
31
+ replacement: "preact/compat"
32
+ }
33
+ ],
34
+ dedupe: ["preact", "@kasperrt/smolter"]
35
+ },
36
+ ssr: { noExternal: ["@kasperrt/smolter"] },
37
+ optimizeDeps: { include: [
38
+ "preact",
39
+ "preact/hooks",
40
+ "preact/compat",
41
+ "preact/jsx-runtime",
42
+ "@kasperrt/smolter/preact",
43
+ "@kasperrt/smolter/preact/lazy-route"
44
+ ] }
45
+ };
46
+ function applicationPlugins({ directory = "src/routes", ssr = true, splitRoutes = true, resourceRequest } = {}) {
47
+ return [
48
+ {
49
+ name: "smolter-application",
50
+ enforce: "pre",
51
+ config: () => preactConfig,
52
+ transformIndexHtml: {
53
+ order: "post",
54
+ handler(html, context) {
55
+ if (!context.server || !ssr) return html;
56
+ const client = html.match(/\s*<script\b[^>]*src="[^"]*\/@vite\/client"[^>]*><\/script>\s*/)?.[0];
57
+ if (!client) return html;
58
+ return html.replace(client, "").replace("</body>", `${client.trim()}</body>`);
59
+ }
60
+ },
61
+ configureServer(server) {
62
+ server.middlewares.use((incoming, outgoing, next) => {
63
+ if (!incoming.url?.startsWith("/_smolter?")) return next();
64
+ const run = async () => {
65
+ const target = new URL(incoming.url, "http://local").searchParams.get("url");
66
+ if (!target?.startsWith("/") || target.startsWith("//")) return send(new Response("Invalid route URL", { status: 400 }), outgoing);
67
+ const request = await webRequest(incoming, outgoing, target);
68
+ const [{ default: routes }, { dispatchRoute }] = await Promise.all([server.ssrLoadModule("virtual:smolter/routes"), server.ssrLoadModule("@kasperrt/smolter/preact/server")]);
69
+ await send(await dispatchRoute(request, routes), outgoing);
70
+ };
71
+ run().catch(next);
72
+ });
73
+ if (!ssr) return;
74
+ return () => {
75
+ server.middlewares.use((incoming, outgoing, next) => {
76
+ if (!incoming.headers.accept?.includes("text/html") && !resourceRequest?.(incoming.originalUrl ?? incoming.url ?? "")) return next();
77
+ const run = async () => {
78
+ const request = await webRequest(incoming, outgoing);
79
+ const response = await (await server.ssrLoadModule("/src/entry.server.tsx")).default(request, "/src/entry.client.tsx");
80
+ if (!response.headers.get("Content-Type")?.includes("text/html")) return send(response, outgoing);
81
+ const html = await server.transformIndexHtml(incoming.url, await response.text());
82
+ await send(new Response(html, {
83
+ status: response.status,
84
+ headers: response.headers
85
+ }), outgoing);
86
+ };
87
+ run().catch(next);
88
+ });
89
+ };
90
+ }
91
+ },
92
+ browserBoundary(),
93
+ ...splitRoutes ? [lazyFiles(directory)] : [],
94
+ routerPlugin({
95
+ directory,
96
+ framework: "preact"
97
+ })
98
+ ];
99
+ }
100
+ //#endregion
101
+ export { applicationPlugins, preactConfig };
@@ -3,8 +3,9 @@ export interface FileRouterOptions {
3
3
  directory?: string;
4
4
  clientRoutes?: string;
5
5
  serverRoutes?: string;
6
+ framework?: "react" | "preact";
6
7
  }
7
8
  /** Build-time flat route discovery and the optional server data transport. */
8
- export declare function routerPlugin({ directory, clientRoutes, serverRoutes, }?: FileRouterOptions): Plugin;
9
+ export declare function routerPlugin({ directory, clientRoutes, serverRoutes, framework, }?: FileRouterOptions): Plugin;
9
10
  /** File routing is the default; this alias keeps configuration explicit. */
10
11
  export declare const fileRouter: typeof routerPlugin;