@torpor/build 0.0.1

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 (71) hide show
  1. package/LICENSE.md +5 -0
  2. package/README.md +55 -0
  3. package/dist/bin/index.cjs +137 -0
  4. package/dist/bin/index.cjs.map +1 -0
  5. package/dist/bin/index.d.cts +1 -0
  6. package/dist/bin/index.d.ts +1 -0
  7. package/dist/bin/index.js +114 -0
  8. package/dist/bin/index.js.map +1 -0
  9. package/dist/index.cjs +55 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.cts +150 -0
  12. package/dist/index.d.ts +150 -0
  13. package/dist/index.js +27 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/metafile-cjs.json +1 -0
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/response.cjs +150 -0
  18. package/dist/response.cjs.map +1 -0
  19. package/dist/response.d.cts +162 -0
  20. package/dist/response.d.ts +162 -0
  21. package/dist/response.js +109 -0
  22. package/dist/response.js.map +1 -0
  23. package/dist/state.cjs +41 -0
  24. package/dist/state.cjs.map +1 -0
  25. package/dist/state.d.cts +6 -0
  26. package/dist/state.d.ts +6 -0
  27. package/dist/state.js +14 -0
  28. package/dist/state.js.map +1 -0
  29. package/package.json +49 -0
  30. package/src/bin/index.ts +41 -0
  31. package/src/index.ts +24 -0
  32. package/src/response/badRequest.ts +14 -0
  33. package/src/response/created.ts +13 -0
  34. package/src/response/forbidden.ts +14 -0
  35. package/src/response/methodNotAllowed.ts +14 -0
  36. package/src/response/moved.ts +16 -0
  37. package/src/response/notFound.ts +17 -0
  38. package/src/response/notModified.ts +14 -0
  39. package/src/response/ok.ts +20 -0
  40. package/src/response/redirect.ts +17 -0
  41. package/src/response/response.ts +22 -0
  42. package/src/response/seeOther.ts +13 -0
  43. package/src/response/serverError.ts +10 -0
  44. package/src/response/transfer.ts +14 -0
  45. package/src/response/unauthorized.ts +12 -0
  46. package/src/response/unprocessable.ts +12 -0
  47. package/src/response.ts +33 -0
  48. package/src/site/FileSystemRouter.ts +24 -0
  49. package/src/site/buildRoutePath.ts +25 -0
  50. package/src/site/clientEntry.ts +191 -0
  51. package/src/site/defineConfig.ts +5 -0
  52. package/src/site/defineSite.ts +74 -0
  53. package/src/site/lazyRoute.ts +72 -0
  54. package/src/site/routeHandlers.ts +113 -0
  55. package/src/site/serverEntry.ts +384 -0
  56. package/src/state/$page.ts +11 -0
  57. package/src/state.ts +3 -0
  58. package/src/types/CookieHelper.ts +7 -0
  59. package/src/types/PageEndPoint.ts +37 -0
  60. package/src/types/PageEvent.ts +14 -0
  61. package/src/types/PageServerEndPoint.ts +18 -0
  62. package/src/types/RouteHandler.ts +12 -0
  63. package/src/types/RouteHandlerCollection.ts +7 -0
  64. package/src/types/RouteLayoutHandler.ts +5 -0
  65. package/src/types/RouteMatchResult.ts +7 -0
  66. package/src/types/ServerEndPoint.ts +37 -0
  67. package/src/types/ServerEvent.ts +27 -0
  68. package/src/types/ServerHook.ts +13 -0
  69. package/src/types/UserConfig.ts +3 -0
  70. package/tsconfig.json +4 -0
  71. package/tsup.config.ts +10 -0
@@ -0,0 +1,13 @@
1
+ import transfer from "./transfer";
2
+
3
+ /**
4
+ * 303 See Other
5
+ *
6
+ * The server sent this response to direct the client to get the requested
7
+ * resource at another URI with a GET request.
8
+ *
9
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
10
+ */
11
+ export default function seeOther(location: string): Response {
12
+ return transfer(303, location);
13
+ }
@@ -0,0 +1,10 @@
1
+ import response from "./response";
2
+
3
+ /**
4
+ * The server has encountered a situation it does not know how to handle.
5
+ *
6
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
7
+ */
8
+ export default function serverError(body?: object | string): Response {
9
+ return response(500, body);
10
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Creates a redirect response with the supplied status code.
3
+ */
4
+ export default function transfer(
5
+ status: 300 | 301 | 302 | 303 | 304 | 307 | 308,
6
+ location: string,
7
+ ): Response {
8
+ return new Response(null, {
9
+ status,
10
+ headers: {
11
+ location,
12
+ },
13
+ });
14
+ }
@@ -0,0 +1,12 @@
1
+ import response from "./response";
2
+
3
+ /**
4
+ * Although the HTTP standard specifies "unauthorized", semantically this
5
+ * response means "unauthenticated". That is, the client must authenticate
6
+ * itself to get the requested response.
7
+ *
8
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
9
+ */
10
+ export default function unauthorized(body?: object | string): Response {
11
+ return response(401, body);
12
+ }
@@ -0,0 +1,12 @@
1
+ import response from "./response";
2
+
3
+ /**
4
+ * 422 Unprocessable Content
5
+ *
6
+ * The request was well-formed but was unable to be followed due to semantic errors.
7
+ *
8
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
9
+ */
10
+ export default function unprocessable(body?: object | string): Response {
11
+ return response(422, body);
12
+ }
@@ -0,0 +1,33 @@
1
+ import badRequest from "./response/badRequest";
2
+ import created from "./response/created";
3
+ import forbidden from "./response/forbidden";
4
+ import methodNotAllowed from "./response/methodNotAllowed";
5
+ import moved from "./response/moved";
6
+ import notFound from "./response/notFound";
7
+ import notModified from "./response/notModified";
8
+ import ok from "./response/ok";
9
+ import redirect from "./response/redirect";
10
+ import response from "./response/response";
11
+ import seeOther from "./response/seeOther";
12
+ import serverError from "./response/serverError";
13
+ import transfer from "./response/transfer";
14
+ import unauthorized from "./response/unauthorized";
15
+ import unprocessable from "./response/unprocessable";
16
+
17
+ export {
18
+ response,
19
+ ok,
20
+ created,
21
+ transfer,
22
+ seeOther,
23
+ notModified,
24
+ redirect,
25
+ moved,
26
+ badRequest,
27
+ unauthorized,
28
+ forbidden,
29
+ notFound,
30
+ methodNotAllowed,
31
+ unprocessable,
32
+ serverError,
33
+ };
@@ -0,0 +1,24 @@
1
+ import { BaseFileSystemRouter, cleanPath } from "vinxi/fs-router";
2
+
3
+ export default class FileSystemRouter extends BaseFileSystemRouter {
4
+ toPath(src: string) {
5
+ let routePath = cleanPath(src, this.config)
6
+ .replace(/^\//, "")
7
+ .replace(/_layout.server$/, "_layout/~server")
8
+ .replace(/\+page.server$/, "~server")
9
+ .replace(/\+page$/, "")
10
+ .replace(/\+server$/, "")
11
+ .replace(/\/$/, "");
12
+ return routePath?.length > 0 ? `/${routePath}` : "/";
13
+ }
14
+
15
+ toRoute(filePath: string) {
16
+ return {
17
+ path: this.toPath(filePath),
18
+ $handler: {
19
+ src: filePath,
20
+ pick: ["default"],
21
+ },
22
+ };
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+ type ParamsType = Record<PropertyKey, any>;
2
+
3
+ export default function buildRoutePath<RP extends ParamsType, UP extends ParamsType>(
4
+ path: string,
5
+ routeParams: RP,
6
+ urlParams?: UP,
7
+ ) {
8
+ for (let [key, value] of Object.entries(routeParams)) {
9
+ path = path.replace(`[${key}]`, value);
10
+ }
11
+ if (urlParams) {
12
+ // TODO: More sophisticated url params serialization
13
+ let params = Object.entries(urlParams)
14
+ .map(([key, value]) => {
15
+ if (value != null) {
16
+ return `${key}=${value}`;
17
+ }
18
+ })
19
+ .filter(Boolean);
20
+ if (params.length) {
21
+ path += `?${params.join("&")}`;
22
+ }
23
+ }
24
+ return path;
25
+ }
@@ -0,0 +1,191 @@
1
+ import { hydrate, mount } from "@torpor/view";
2
+ import type { Component, SlotRender } from "@torpor/view";
3
+ import "vinxi/client";
4
+ import $page from "../state/$page";
5
+ import type PageEndPoint from "../types/PageEndPoint";
6
+ import type PageServerEndPoint from "../types/PageServerEndPoint";
7
+ import routeHandlers from "./routeHandlers";
8
+
9
+ // Intercept clicks on links
10
+ window.addEventListener("click", async (e) => {
11
+ if (e.target && (e.target as HTMLElement).tagName === "A") {
12
+ e.preventDefault();
13
+ const href = (e.target as HTMLLinkElement).href;
14
+ const url = new URL(href);
15
+
16
+ // Update $page before building the components
17
+ // TODO: Find somewhere better to put this
18
+ $page.url = url;
19
+
20
+ if (await navigate(url.pathname, url.searchParams)) {
21
+ window.history.pushState(null, "", url);
22
+ } else {
23
+ window.location.href = href;
24
+ }
25
+ }
26
+ });
27
+
28
+ // Listen for changes to the URL that occur when the user navigates using the
29
+ // back or forward buttons
30
+ window.addEventListener("popstate", async () => {
31
+ await navigateToLocation(document.location);
32
+ });
33
+
34
+ // Do the initial navigation and hydration
35
+ navigateToLocation(document.location, true);
36
+
37
+ async function navigateToLocation(location: Location, firstTime = false) {
38
+ return await navigate(location.pathname, new URLSearchParams(location.search), firstTime);
39
+ }
40
+
41
+ async function navigate(
42
+ path: string,
43
+ urlParams: URLSearchParams,
44
+ firstTime = false,
45
+ ): Promise<boolean> {
46
+ console.log("navigating client to", path, "with", urlParams.toString());
47
+
48
+ const parent = document.getElementById("app");
49
+ if (!parent) {
50
+ // TODO: 500
51
+ console.log("500");
52
+ return false;
53
+ }
54
+
55
+ const route = routeHandlers.match(path, urlParams);
56
+ if (!route) {
57
+ // TODO: 404
58
+ console.log("404");
59
+ return false;
60
+ }
61
+
62
+ // Update $page before building the components
63
+ // TODO: Find somewhere better to put this
64
+ //$page.url = new URL(document.location.href);
65
+
66
+ const handler = route.handler;
67
+ const params = route.routeParams || {};
68
+
69
+ // There must be a client endpoint with a component
70
+ const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint).default;
71
+ if (!clientEndPoint?.component) {
72
+ // TODO: 404
73
+ console.log("404");
74
+ return false;
75
+ }
76
+
77
+ // There may be a server endpoint
78
+ const serverEndPoint: PageServerEndPoint | undefined = (await handler.serverEndPoint)?.default;
79
+
80
+ // Pass the data into $props
81
+ // TODO: Don't load if this is the first time -- it should have been passed to us, somehow...
82
+ let data = {};
83
+ if (handler.layouts) {
84
+ for (let layout of handler.layouts) {
85
+ let layoutPath = layout.path;
86
+ for (let key in params) {
87
+ layoutPath = layoutPath.replace(`[${key}]`, params[key]);
88
+ }
89
+ const layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint)?.default;
90
+ const layoutServerEndPoint: PageServerEndPoint | undefined = (await layout.serverEndPoint)
91
+ ?.default;
92
+ const layoutData = await loadClientAndServerData(
93
+ data,
94
+ document.location.origin + layoutPath,
95
+ params,
96
+ layoutEndPoint,
97
+ layoutServerEndPoint,
98
+ );
99
+ Object.assign(data, layoutData);
100
+ }
101
+ }
102
+ let endPointData = await loadClientAndServerData(
103
+ data,
104
+ document.location.origin + path,
105
+ params,
106
+ clientEndPoint,
107
+ serverEndPoint,
108
+ );
109
+ Object.assign(data, endPointData);
110
+ let $props: Record<string, any> = { data };
111
+
112
+ // If there are layouts, work our way upwards, pushing each component into
113
+ // the default slot of its parent
114
+ // TODO: Also handle layout server data
115
+ // TODO: There's probably a nicer way to do this with reducers or something
116
+ let component = clientEndPoint.component as Component;
117
+ let slots: Record<string, SlotRender> | undefined = undefined;
118
+ if (handler.layouts) {
119
+ let slotFunctions: SlotRender[] = [];
120
+ slotFunctions[handler.layouts.length] = (parent, anchor, _, context) =>
121
+ clientEndPoint.component!(parent, anchor, $props, context);
122
+ for (let i = handler.layouts.length - 1; i >= 0; i--) {
123
+ const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint)?.default;
124
+ if (layoutEndPoint?.component) {
125
+ if (i === 0) {
126
+ component = layoutEndPoint.component as Component;
127
+ slots = { _: slotFunctions[i + 1] };
128
+ } else {
129
+ slotFunctions[i] = (parent, anchor, _, context) =>
130
+ layoutEndPoint.component!(parent, anchor, $props, context, {
131
+ _: slotFunctions[i + 1],
132
+ });
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ if (firstTime) {
139
+ hydrate(parent, component, $props, slots);
140
+ } else {
141
+ // TODO: Clear ranges, reuse layouts etc
142
+ parent.textContent = "";
143
+
144
+ try {
145
+ mount(parent, component, $props, slots);
146
+ } catch (error) {
147
+ // TODO: Show a proper Error component
148
+ parent.innerHTML = '<span style="color: red">Script syntax error</span><p>' + error + "</p>";
149
+ }
150
+ }
151
+
152
+ return true;
153
+ }
154
+
155
+ async function loadClientAndServerData(
156
+ data: Record<string, any>,
157
+ location: string,
158
+ params: Record<string, string>,
159
+ clientEndPoint?: PageEndPoint,
160
+ serverEndPoint?: PageServerEndPoint,
161
+ ) {
162
+ let newData = {};
163
+ if (clientEndPoint?.load) {
164
+ const clientUrl = new URL(document.location.href);
165
+ const clientParams = buildClientParams(clientUrl, params, data);
166
+ const clientData = await clientEndPoint.load(clientParams);
167
+ if (clientData) {
168
+ Object.assign(newData, clientData);
169
+ }
170
+ }
171
+ if (serverEndPoint?.load) {
172
+ const serverUrl = location.replace(/\/$/, "") + "/~server";
173
+ const serverResponse = await fetch(serverUrl);
174
+ if (
175
+ serverResponse?.ok &&
176
+ serverResponse.headers.get("content-type")?.includes("application/json")
177
+ ) {
178
+ const serverData = await serverResponse.json();
179
+ Object.assign(newData, serverData);
180
+ }
181
+ }
182
+ return newData;
183
+ }
184
+
185
+ function buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {
186
+ return {
187
+ url,
188
+ params,
189
+ data,
190
+ };
191
+ }
@@ -0,0 +1,5 @@
1
+ import type UserConfig from "../types/UserConfig";
2
+
3
+ export default function defineConfig(config: UserConfig): UserConfig {
4
+ return config;
5
+ }
@@ -0,0 +1,74 @@
1
+ import torpor from "@torpor/unplugin/vite";
2
+ import path from "path";
3
+ import { AppOptions, RouterSchemaInput, createApp } from "vinxi";
4
+ import { config } from "vinxi/plugins/config";
5
+ import type UserConfig from "../types/UserConfig";
6
+ import FileSystemRouter from "./FileSystemRouter";
7
+
8
+ export default function defineSite(options?: UserConfig) {
9
+ // Set the port
10
+ process.env.PORT = (options?.port || 7059).toString();
11
+
12
+ // Get Vinxi to create and serve the app
13
+ return createApp({
14
+ routers: [
15
+ {
16
+ name: "assets",
17
+ type: "static",
18
+ dir: "./src/assets",
19
+ },
20
+ // Doesn't look like we can have two server routes?
21
+ // Have to put api in /routes
22
+ // Maybe we can support having /routes/api and routes/site and stripping /site from the start?
23
+ //{
24
+ // name: "api",
25
+ // type: "http",
26
+ // target: "server",
27
+ // // TODO: change this when published
28
+ // handler: "node_modules/@torpor/build/src/site/serverEntry.ts",
29
+ // routes: apiRoutes,
30
+ // plugins: () => [config("custom", viteOptions())],
31
+ //},
32
+ {
33
+ name: "server",
34
+ type: "http",
35
+ target: "server",
36
+ // TODO: change this when published
37
+ handler: "node_modules/@torpor/build/src/site/serverEntry.ts",
38
+ routes,
39
+ plugins: () => [config("custom", viteOptions()), torpor({ server: true })],
40
+ },
41
+ {
42
+ name: "client",
43
+ type: "client",
44
+ target: "browser",
45
+ // TODO: change this when published
46
+ handler: "node_modules/@torpor/build/src/site/clientEntry.ts",
47
+ base: "/_build",
48
+ routes,
49
+ plugins: () => [config("custom", viteOptions()), torpor()],
50
+ },
51
+ ],
52
+ });
53
+ }
54
+
55
+ function routes(router: RouterSchemaInput, app: AppOptions) {
56
+ return new FileSystemRouter(
57
+ {
58
+ dir: path.resolve("src/routes"),
59
+ extensions: ["js", "ts"],
60
+ },
61
+ router,
62
+ app,
63
+ );
64
+ }
65
+
66
+ function viteOptions() {
67
+ return {
68
+ resolve: {
69
+ alias: {
70
+ "@": path.resolve("src"),
71
+ },
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,72 @@
1
+ export default async function lazyRoute(
2
+ component: { src: string; import(): Promise<any> },
3
+ clientManifest: any,
4
+ serverManifest: any,
5
+ //exported = "default",
6
+ ) {
7
+ if (import.meta.env.DEV) {
8
+ const manifest = import.meta.env.SSR ? serverManifest : clientManifest;
9
+ const mod = await manifest.inputs[component.src].import();
10
+ return mod;
11
+ } else {
12
+ const mod = await component.import();
13
+ return mod;
14
+ }
15
+ /*
16
+ return lazy(async () => {
17
+ if (import.meta.env.DEV) {
18
+ let manifest = import.meta.env.SSR ? serverManifest : clientManifest;
19
+
20
+ const mod = await manifest.inputs[component.src].import();
21
+ invariant(mod[exported], `Module ${component.src} does not export ${exported}`);
22
+ const Component = mod[exported];
23
+ let assets = await clientManifest.inputs?.[component.src].assets();
24
+ const styles = assets.filter((asset) => asset.tag === "style");
25
+
26
+ if (typeof window !== "undefined" && import.meta.hot) {
27
+ import.meta.hot.on("css-update", (data) => {
28
+ updateStyles(styles, data);
29
+ });
30
+ }
31
+
32
+ const Comp = forwardRef((props, ref) => {
33
+ if (typeof window !== "undefined") {
34
+ useLayoutEffect(() => {
35
+ return () => {
36
+ // remove style tags added by vite when a CSS file is imported
37
+ cleanupStyles(styles);
38
+ };
39
+ }, []);
40
+ }
41
+ return createElement(
42
+ Fragment,
43
+ null,
44
+ ...assets.map((asset) => renderAsset(asset)),
45
+ createElement(Component, { ...props, ref: ref }),
46
+ );
47
+ });
48
+ return { default: Comp };
49
+ } else {
50
+ const mod = await component.import();
51
+
52
+ const Component = mod[exported];
53
+ let assets = await clientManifest.inputs?.[component.src].assets();
54
+
55
+ if (typeof window !== "undefined") {
56
+ const styles = assets.filter((asset) => asset.attrs.rel === "stylesheet");
57
+ preloadStyles(styles);
58
+ }
59
+
60
+ const Comp = forwardRef((props, ref) => {
61
+ return createElement(
62
+ Fragment,
63
+ null,
64
+ ...assets.map((asset) => renderAsset(asset)),
65
+ createElement(Component, { ...props, ref: ref }),
66
+ );
67
+ });
68
+ return { default: Comp };
69
+ }
70
+ });
71
+ */
72
+ }
@@ -0,0 +1,113 @@
1
+ import { getManifest } from "vinxi/manifest";
2
+ import fileRoutes, { RouteModule } from "vinxi/routes";
3
+ import type RouteHandler from "../types/RouteHandler";
4
+ import type RouteHandlerCollection from "../types/RouteHandlerCollection";
5
+ import type RouteLayoutHandler from "../types/RouteLayoutHandler";
6
+ import lazyRoute from "./lazyRoute";
7
+
8
+ const clientManifest = getManifest("client");
9
+ const serverManifest = getManifest("server");
10
+
11
+ const routeHandlers: RouteHandlerCollection = {
12
+ handlers: fileRoutes
13
+ // HACK: Is there a better way to filter? glob in the config?
14
+ .filter((route) => !route.path.startsWith("_"))
15
+ .map((route) => {
16
+ return {
17
+ ...route,
18
+ regex: pathToRegExp(route.path),
19
+ // TODO: Implement the rest of the lazyRoute stuff from vinxi/react or vinxi/solid
20
+ endPoint: lazyRoute(route.$handler, clientManifest, serverManifest),
21
+ loaded: false,
22
+ };
23
+ }),
24
+ match(path, urlParams) {
25
+ console.log("matching", path);
26
+
27
+ // TODO: Lots of testing etc
28
+ for (let handler of (this as RouteHandlerCollection).handlers) {
29
+ let match = path.match(handler.regex);
30
+ if (match) {
31
+ // Lazy load server endpoints and layouts
32
+ // Partly because it makes sense to do it here, but also because
33
+ // it doesn't work if we try to do it upfront for some reason
34
+ if (!handler.loaded) {
35
+ loadHandler(handler);
36
+ handler.loaded = true;
37
+ }
38
+ return {
39
+ handler,
40
+ routeParams: match.groups,
41
+ urlParams: urlParams ? Object.fromEntries(urlParams) : undefined,
42
+ };
43
+ }
44
+ }
45
+
46
+ console.log("not found");
47
+ //for (let handler of (this as RouteHandlerCollection).handlers) {
48
+ // console.log(" have", handler.path, handler.regex);
49
+ //}
50
+ },
51
+ };
52
+
53
+ function pathToRegExp(path: string): RegExp {
54
+ const pattern = path
55
+ .split("/")
56
+ .map((p) => {
57
+ return p.replace(/\[([^\/]+?)\]/, "(?<$1>[^\\/]+?)");
58
+ })
59
+ .join("\\/");
60
+ return new RegExp(`^${pattern}$`);
61
+ }
62
+
63
+ function loadHandler(handler: RouteHandler) {
64
+ handler.layouts = findLayouts(handler.path, fileRoutes);
65
+ handler.serverEndPoint = findServer(handler.path, fileRoutes);
66
+ handler.serverHook = findServerHook(handler.path, fileRoutes);
67
+ }
68
+
69
+ function findLayouts(path: string, fileRoutes: RouteModule[]): RouteLayoutHandler[] | undefined {
70
+ let layouts: RouteLayoutHandler[] = [];
71
+ let parts = path
72
+ // Strip the trailing`~/server` (so we don't look for `~/server/_layout`
73
+ // which will never exist)
74
+ .replace(/\/~server$/, "")
75
+ // The path will always start with / so splitting e.g. `/first/second`
76
+ // will result in ['', 'first', 'second'] and checks for `/_layout`,
77
+ // `/first/_layout` and `/second/_layout`
78
+ .split("/");
79
+ let basePath = "";
80
+ for (let i = 0; i < parts.length; i++) {
81
+ if (i > 0) {
82
+ basePath += "/" + parts[i];
83
+ }
84
+ const layoutPath = basePath + "/_layout";
85
+ const layoutRoute = fileRoutes.find((r) => r.path === layoutPath);
86
+ if (layoutRoute) {
87
+ layouts.push({
88
+ path: layoutPath,
89
+ endPoint: lazyRoute(layoutRoute.$handler, clientManifest, serverManifest),
90
+ serverEndPoint: findServer(layoutPath, fileRoutes),
91
+ });
92
+ }
93
+ }
94
+ return layouts.length ? layouts : undefined;
95
+ }
96
+
97
+ function findServer(path: string, fileRoutes: RouteModule[]): Promise<any> | undefined {
98
+ const serverPath = path.replace(/\/$/, "") + "/~server";
99
+ const serverRoute = fileRoutes.find((r) => r.path === serverPath);
100
+ return serverRoute && lazyRoute(serverRoute.$handler, clientManifest, serverManifest);
101
+ }
102
+
103
+ function findServerHook(path: string, fileRoutes: RouteModule[]): Promise<any> | undefined {
104
+ // TODO: Should this be a collection, like layouts?
105
+ let hookPath = "/_hook.server";
106
+ if (path.startsWith("/api/")) {
107
+ hookPath = "/api" + hookPath;
108
+ }
109
+ const hookRoute = fileRoutes.find((r) => r.path === hookPath);
110
+ return hookRoute && lazyRoute(hookRoute.$handler, clientManifest, serverManifest);
111
+ }
112
+
113
+ export default routeHandlers;