@torpor/build 1.4.6 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/{Site-CQ29Opvm.d.mts → Site-Blz32JRD.d.mts} +4 -3
- package/dist/{Site-CQ29Opvm.d.mts.map → Site-Blz32JRD.d.mts.map} +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/clientEntry.mjs +4 -4
- package/dist/clientEntry.mjs.map +1 -1
- package/dist/dataRequest-JosDB_ig.mjs +50 -0
- package/dist/dataRequest-JosDB_ig.mjs.map +1 -0
- package/dist/dev.mjs +1 -1
- package/dist/devPlugin-V689J6l6.mjs +144 -0
- package/dist/devPlugin-V689J6l6.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/load-LWQoFWh-.mjs +349 -0
- package/dist/load-LWQoFWh-.mjs.map +1 -0
- package/dist/nav.mjs +1 -1
- package/dist/nodeDev.d.mts +16 -0
- package/dist/nodeDev.d.mts.map +1 -0
- package/dist/nodeDev.mjs +2 -0
- package/dist/openapi.d.mts +1 -1
- package/dist/{response-CuNPcxsY.mjs → response-sth6FQsj.mjs} +33 -3
- package/dist/response-sth6FQsj.mjs.map +1 -0
- package/dist/response.mjs +2 -2
- package/dist/run.d.mts +1 -1
- package/dist/run.mjs +1 -1
- package/dist/{runOpenApi-CbbrPi4E.mjs → runOpenApi-tnflDjZV.mjs} +10 -143
- package/dist/runOpenApi-tnflDjZV.mjs.map +1 -0
- package/dist/serverEntry-fj0XfTs4.d.mts.map +1 -1
- package/dist/{serverEntry-D3uwudkX.mjs → serverEntry-o6kk_-Wo.mjs} +2 -2
- package/dist/{serverEntry-D3uwudkX.mjs.map → serverEntry-o6kk_-Wo.mjs.map} +1 -1
- package/dist/serverEntry.mjs +1 -1
- package/dist/{serverHandlers-C8qzOSX-.mjs → serverHandlers-F1gJ1lrj.mjs} +194 -30
- package/dist/serverHandlers-F1gJ1lrj.mjs.map +1 -0
- package/dist/test.d.mts +1 -1
- package/dist/test.mjs +1 -1
- package/dist/{unprocessable-D6RcYLh6.mjs → unprocessable-CoMN2zFa.mjs} +2 -32
- package/dist/unprocessable-CoMN2zFa.mjs.map +1 -0
- package/package.json +8 -3
- package/dist/load-Uyj9I60p.mjs +0 -246
- package/dist/load-Uyj9I60p.mjs.map +0 -1
- package/dist/response-CuNPcxsY.mjs.map +0 -1
- package/dist/runOpenApi-CbbrPi4E.mjs.map +0 -1
- package/dist/serverHandlers-C8qzOSX-.mjs.map +0 -1
- package/dist/unprocessable-D6RcYLh6.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -27,8 +27,9 @@ npm run dev
|
|
|
27
27
|
- `tb --preview` -- preview the production build
|
|
28
28
|
- `tb --openapi [outFile]` -- generate an OpenAPI document
|
|
29
29
|
- Adapters for deployment targets -- see
|
|
30
|
-
[@torpor/adapter-node](../adapters/adapter-node)
|
|
31
|
-
[@torpor/adapter-cloudflare](../adapters/adapter-cloudflare)
|
|
30
|
+
[@torpor/adapter-node](../adapters/adapter-node),
|
|
31
|
+
[@torpor/adapter-cloudflare](../adapters/adapter-cloudflare) and
|
|
32
|
+
[@torpor/adapter-electron](../adapters/adapter-electron)
|
|
32
33
|
- Schema validation for endpoints (`@torpor/build/schema`)
|
|
33
34
|
- Form helpers (`@torpor/build/form`) and client side navigation helpers
|
|
34
35
|
(`@torpor/build/nav`)
|
|
@@ -322,8 +322,9 @@ declare class Site {
|
|
|
322
322
|
*/
|
|
323
323
|
vitePlugins: Plugin[];
|
|
324
324
|
/**
|
|
325
|
-
* The site's adapter, e.g. from `@torpor/adapter-node
|
|
326
|
-
* `@torpor/adapter-cloudflare`. When not set,
|
|
325
|
+
* The site's adapter, e.g. from `@torpor/adapter-node`,
|
|
326
|
+
* `@torpor/adapter-cloudflare` or `@torpor/adapter-electron`. When not set,
|
|
327
|
+
* one is resolved when it's
|
|
327
328
|
* needed: a deployment environment (e.g. `CF_PAGES`) picks the platform's
|
|
328
329
|
* adapter when installed, an installed adapter is used otherwise, and
|
|
329
330
|
* preview falls back to serving on the current runtime. See
|
|
@@ -505,4 +506,4 @@ declare class Site {
|
|
|
505
506
|
}
|
|
506
507
|
//#endregion
|
|
507
508
|
export { ServerEndPoint as a, ActionBody as c, PageServerActionSchemas as d, PageServerEndPoint as f, Adapter as g, SitePlugin as h, SchemaQuery as i, LoadParams as l, PageServerAction as m, ParamsOf as n, ServerEndPointSchemas as o, PageServerLoad as p, SchemaBody as r, ServerRequest as s, Site as t, LoadQuery as u };
|
|
508
|
-
//# sourceMappingURL=Site-
|
|
509
|
+
//# sourceMappingURL=Site-Blz32JRD.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Site-
|
|
1
|
+
{"version":3,"file":"Site-Blz32JRD.d.mts","names":[],"sources":["../src/types/Adapter.ts","../src/types/SitePlugin.ts","../src/types/PageServerAction.ts","../src/types/PageServerLoad.ts","../src/types/PageServerEndPoint.ts","../src/types/ServerRequest.ts","../src/types/ServerEndPoint.ts","../src/types/RouteType.ts","../src/types/Route.ts","../src/site/Site.ts"],"mappings":"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"}
|
package/dist/bin/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
import { n as loadSite, r as run, t as runOpenApi } from "../runOpenApi-
|
|
2
|
+
import { n as loadSite, r as run, t as runOpenApi } from "../runOpenApi-tnflDjZV.mjs";
|
|
3
3
|
//#region src/bin/index.ts
|
|
4
4
|
const workingDir = process.cwd();
|
|
5
5
|
if (process.argv.includes("--openapi")) {
|
package/dist/clientEntry.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as Router } from "./Router-C4ADUfhv.mjs";
|
|
2
2
|
import { o as setBasePath } from "./basePath-DQnbHc7o.mjs";
|
|
3
3
|
import { t as $page } from "./_page-CW6SLuT4.mjs";
|
|
4
|
-
import { a as client, n as navigate, r as loadData } from "./load-
|
|
4
|
+
import { a as client, n as navigate, r as loadData } from "./load-LWQoFWh-.mjs";
|
|
5
5
|
import manifest from "@torpor/build/manifest";
|
|
6
6
|
//#region src/site/clientEntry.ts
|
|
7
7
|
setBasePath(manifest.base);
|
|
@@ -36,9 +36,9 @@ async function maybePrefetch(e) {
|
|
|
36
36
|
if (route) {
|
|
37
37
|
const handler = route.handler;
|
|
38
38
|
const params = route.params || {};
|
|
39
|
-
const clientEndPoint = (await handler.endPoint())
|
|
40
|
-
|
|
41
|
-
await loadData(handler, params, path, query, [], clientEndPoint,
|
|
39
|
+
const clientEndPoint = handler.endPoint ? (await handler.endPoint())?.default : void 0;
|
|
40
|
+
if (!clientEndPoint?.component) return;
|
|
41
|
+
await loadData(handler, params, path, query, [], clientEndPoint, true);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
package/dist/clientEntry.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientEntry.mjs","names":[],"sources":["../src/site/clientEntry.ts"],"sourcesContent":["import manifest from \"@torpor/build/manifest\";\nimport loadData from \"../nav/loadData.ts\";\nimport navigate from \"../nav/navigate.ts\";\nimport $page from \"../state/$page.ts\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport
|
|
1
|
+
{"version":3,"file":"clientEntry.mjs","names":[],"sources":["../src/site/clientEntry.ts"],"sourcesContent":["import manifest from \"@torpor/build/manifest\";\nimport loadData from \"../nav/loadData.ts\";\nimport navigate from \"../nav/navigate.ts\";\nimport $page from \"../state/$page.ts\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport Router from \"./Router.ts\";\nimport { setBasePath } from \"./basePath.ts\";\n\nsetBasePath(manifest.base);\n\n// Build the router from the Site object created by the user\nconst router = new Router();\nrouter.addPages(manifest.routes);\n\n// Put it in client state so that it can be accessed from navigate. We can't\n// just import it to navigate because that gets built by tsdown, without Vite\n// and the manifest\nclient.router = router;\nclient.viewTransitions = manifest.viewTransitions ?? false;\n\n// Intercept clicks on links\nwindow.addEventListener(\"click\", async (e) => {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\te.preventDefault();\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tif (await navigate(url)) {\n\t\t\t// Save the scroll position of the outgoing page on the current\n\t\t\t// history entry, so that going (or coming) back can restore it\n\t\t\twindow.history.replaceState({ scroll: window.scrollY }, \"\", document.location.href);\n\t\t\twindow.history.pushState({ scroll: 0 }, \"\", url);\n\t\t} else {\n\t\t\twindow.location.href = href;\n\t\t}\n\t}\n});\n\n// Intercept mouseover/focus/touchstart on links for prefetching\n// TODO: settings e.g. <a href=\"\" data-tb-prefetch=\"eager\"/\"visible\"/\"hover\"/\"touch\">\nwindow.addEventListener(\"mouseover\", maybePrefetch);\nwindow.addEventListener(\"touchstart\", maybePrefetch);\n\nasync function maybePrefetch(e: MouseEvent | TouchEvent) {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tconst path = url.pathname;\n\t\tconst query = url.searchParams;\n\t\tconst route = router.match(path, query);\n\t\tif (route) {\n\t\t\tconst handler = route.handler;\n\t\t\tconst params = route.params || {};\n\n\t\t\t// A route with no client endpoint (a pure `+server` route) has\n\t\t\t// nothing to prefetch\n\t\t\tconst clientEndPoint: PageEndPoint | undefined = handler.endPoint\n\t\t\t\t? (await handler.endPoint())?.default\n\t\t\t\t: undefined;\n\t\t\tif (!clientEndPoint?.component) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Just dummy this up\n\t\t\tlet newStack: LayoutPath[] = [];\n\n\t\t\tawait loadData(handler, params, path, query, newStack, clientEndPoint, true);\n\t\t}\n\t}\n}\n\nfunction getLink(target: EventTarget | null): HTMLAnchorElement | undefined {\n\t// The target may be a child element of the link (e.g. an icon inside it),\n\t// so walk up to the nearest actual link. Fragment-only links (href=\"#...\")\n\t// are left to the browser's native jump-to-anchor behavior\n\tif (!(target instanceof Element)) return;\n\tconst link = target.closest(\"a[href]\");\n\tif (link?.getAttribute(\"href\")?.startsWith(\"#\")) return;\n\treturn (link as HTMLAnchorElement) ?? undefined;\n}\n\nfunction ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {\n\t// Ignore the event if it was already handled (e.g. a user handler calling\n\t// preventDefault), a modifier key is pressed, the target is not \"_self\"\n\t// (e.g. \"_blank\"), rel=\"external\", it's a download link, or it's in a\n\t// contenteditable region\n\treturn (\n\t\te.defaultPrevented ||\n\t\te.altKey ||\n\t\te.ctrlKey ||\n\t\te.metaKey ||\n\t\te.shiftKey ||\n\t\t(link.target && link.target !== \"_self\") ||\n\t\tlink.rel === \"external\" ||\n\t\tlink.hasAttribute(\"download\") ||\n\t\tlink.isContentEditable\n\t);\n}\n\n// Listen for changes to the URL that occur when the user navigates using the\n// back or forward buttons\nwindow.addEventListener(\"popstate\", async () => {\n\tconst navigated = await navigateToLocation(document.location);\n\tif (!navigated) {\n\t\twindow.location.reload();\n\t\treturn;\n\t}\n\t// Restore the scroll position that was saved on the history entry\n\trequestAnimationFrame(() => {\n\t\tconst state = window.history.state as { scroll?: number } | null;\n\t\twindow.scrollTo(0, state?.scroll ?? 0);\n\t});\n});\n\n// Do the initial navigation and hydration\nawait navigateToLocation(document.location, true);\n\nasync function navigateToLocation(location: Location, firstTime = false) {\n\t// Reset $page on submit and link navigation\n\t// TODO: Should we put data in $page.data??\n\t$page.form = undefined;\n\n\treturn await navigate(new URL(location.toString()), firstTime);\n}\n"],"mappings":";;;;;;AAUA,YAAY,SAAS,IAAI;AAGzB,MAAM,SAAS,IAAI,OAAO;AAC1B,OAAO,SAAS,SAAS,MAAM;AAK/B,OAAO,SAAS;AAChB,OAAO,kBAAkB,SAAS,mBAAmB;AAGrD,OAAO,iBAAiB,SAAS,OAAO,MAAM;CAC7C,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,EAAE,eAAe;EAEjB,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,IAAI,MAAM,SAAS,GAAG,GAAG;GAGxB,OAAO,QAAQ,aAAa,EAAE,QAAQ,OAAO,QAAQ,GAAG,IAAI,SAAS,SAAS,IAAI;GAClF,OAAO,QAAQ,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG;EAChD,OACC,OAAO,SAAS,OAAO;CAEzB;AACD,CAAC;AAID,OAAO,iBAAiB,aAAa,aAAa;AAClD,OAAO,iBAAiB,cAAc,aAAa;AAEnD,eAAe,cAAc,GAA4B;CACxD,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,IAAI;EAClB,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK;EACtC,IAAI,OAAO;GACV,MAAM,UAAU,MAAM;GACtB,MAAM,SAAS,MAAM,UAAU,CAAC;GAIhC,MAAM,iBAA2C,QAAQ,YACrD,MAAM,QAAQ,SAAS,EAAA,EAAI,UAC5B,KAAA;GACH,IAAI,CAAC,gBAAgB,WACpB;GAMD,MAAM,SAAS,SAAS,QAAQ,MAAM,OAAO,CAAO,GAAG,gBAAgB,IAAI;EAC5E;CACD;AACD;AAEA,SAAS,QAAQ,QAA2D;CAI3E,IAAI,EAAE,kBAAkB,UAAU;CAClC,MAAM,OAAO,OAAO,QAAQ,SAAS;CACrC,IAAI,MAAM,aAAa,MAAM,CAAC,EAAE,WAAW,GAAG,GAAG;CACjD,OAAQ,QAA8B,KAAA;AACvC;AAEA,SAAS,uBAAuB,MAAyB,GAA4B;CAKpF,OACC,EAAE,oBACF,EAAE,UACF,EAAE,WACF,EAAE,WACF,EAAE,YACD,KAAK,UAAU,KAAK,WAAW,WAChC,KAAK,QAAQ,cACb,KAAK,aAAa,UAAU,KAC5B,KAAK;AAEP;AAIA,OAAO,iBAAiB,YAAY,YAAY;CAE/C,IAAI,CAAC,MADmB,mBAAmB,SAAS,QAAQ,GAC5C;EACf,OAAO,SAAS,OAAO;EACvB;CACD;CAEA,4BAA4B;EAC3B,MAAM,QAAQ,OAAO,QAAQ;EAC7B,OAAO,SAAS,GAAG,OAAO,UAAU,CAAC;CACtC,CAAC;AACF,CAAC;AAGD,MAAM,mBAAmB,SAAS,UAAU,IAAI;AAEhD,eAAe,mBAAmB,UAAoB,YAAY,OAAO;CAGxE,MAAM,OAAO,KAAA;CAEb,OAAO,MAAM,SAAS,IAAI,IAAI,SAAS,SAAS,CAAC,GAAG,SAAS;AAC9D"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/site/findErrorRoute.ts
|
|
2
|
+
/**
|
|
3
|
+
* Finds the path of the nearest error route for a request path: the error
|
|
4
|
+
* route whose base (its path with the `/_error` suffix stripped) is the
|
|
5
|
+
* longest prefix of the request path. E.g. an error route added with
|
|
6
|
+
* `addRoute("/admin", { error })` handles `/admin/nope`, while everything
|
|
7
|
+
* else falls back to the root `/_error`.
|
|
8
|
+
*
|
|
9
|
+
* Returns undefined when the site has no matching error route.
|
|
10
|
+
*/
|
|
11
|
+
function findErrorRoute(router, path) {
|
|
12
|
+
let best = void 0;
|
|
13
|
+
for (const route of router.routes) {
|
|
14
|
+
if (route.handler.type !== 8) continue;
|
|
15
|
+
const base = route.handler.path.replace(/\/_error$/, "");
|
|
16
|
+
if ((base === "" || path === base || path.startsWith(base + "/")) && (best === void 0 || base.length > best.length)) best = {
|
|
17
|
+
path: route.handler.path,
|
|
18
|
+
length: base.length
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return best?.path;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/dataRequest.ts
|
|
25
|
+
/**
|
|
26
|
+
* The request header a client data request sets.
|
|
27
|
+
*
|
|
28
|
+
* A GET to `<page url>/~server` carrying this header is handled specially:
|
|
29
|
+
* the server runs the folder hooks once against the page's url and returns the
|
|
30
|
+
* server data for the page and all of its layouts in a single JSON response
|
|
31
|
+
* (`{ loads: [...] }`). This mirrors the server render, where hooks run once
|
|
32
|
+
* with the page url and every layout and the page load in the same request --
|
|
33
|
+
* fetching each `~server` separately would run hooks with internal paths like
|
|
34
|
+
* `/_layout`.
|
|
35
|
+
*
|
|
36
|
+
* The response's `loads` array lines up with the page's layouts (root to leaf)
|
|
37
|
+
* followed by the page itself, so the client can merge each layout's cached
|
|
38
|
+
* client data with its fresh server data.
|
|
39
|
+
*/
|
|
40
|
+
const DATA_REQUEST_HEADER = "X-Torpor-Data";
|
|
41
|
+
/**
|
|
42
|
+
* A JSON array of the layout paths a data request is reusing. Reused layouts
|
|
43
|
+
* keep their already-loaded data, so the server skips their loads (their slot
|
|
44
|
+
* in `loads` is `null`), avoiding redundant work and repeated side effects.
|
|
45
|
+
*/
|
|
46
|
+
const DATA_REUSE_HEADER = "X-Torpor-Reuse";
|
|
47
|
+
//#endregion
|
|
48
|
+
export { DATA_REUSE_HEADER as n, findErrorRoute as r, DATA_REQUEST_HEADER as t };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=dataRequest-JosDB_ig.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataRequest-JosDB_ig.mjs","names":[],"sources":["../src/site/findErrorRoute.ts","../src/dataRequest.ts"],"sourcesContent":["import { ERROR_ROUTE } from \"../types/RouteType\";\nimport type Router from \"./Router\";\n\n/**\n * Finds the path of the nearest error route for a request path: the error\n * route whose base (its path with the `/_error` suffix stripped) is the\n * longest prefix of the request path. E.g. an error route added with\n * `addRoute(\"/admin\", { error })` handles `/admin/nope`, while everything\n * else falls back to the root `/_error`.\n *\n * Returns undefined when the site has no matching error route.\n */\nexport default function findErrorRoute(router: Router, path: string): string | undefined {\n\tlet best: { path: string; length: number } | undefined = undefined;\n\tfor (const route of router.routes) {\n\t\tif (route.handler.type !== ERROR_ROUTE) continue;\n\t\tconst base = route.handler.path.replace(/\\/_error$/, \"\");\n\t\tconst matches = base === \"\" || path === base || path.startsWith(base + \"/\");\n\t\tif (matches && (best === undefined || base.length > best.length)) {\n\t\t\tbest = { path: route.handler.path, length: base.length };\n\t\t}\n\t}\n\treturn best?.path;\n}\n","/**\n * The request header a client data request sets.\n *\n * A GET to `<page url>/~server` carrying this header is handled specially:\n * the server runs the folder hooks once against the page's url and returns the\n * server data for the page and all of its layouts in a single JSON response\n * (`{ loads: [...] }`). This mirrors the server render, where hooks run once\n * with the page url and every layout and the page load in the same request --\n * fetching each `~server` separately would run hooks with internal paths like\n * `/_layout`.\n *\n * The response's `loads` array lines up with the page's layouts (root to leaf)\n * followed by the page itself, so the client can merge each layout's cached\n * client data with its fresh server data.\n */\nexport const DATA_REQUEST_HEADER = \"X-Torpor-Data\";\n\n/**\n * A JSON array of the layout paths a data request is reusing. Reused layouts\n * keep their already-loaded data, so the server skips their loads (their slot\n * in `loads` is `null`), avoiding redundant work and repeated side effects.\n */\nexport const DATA_REUSE_HEADER = \"X-Torpor-Reuse\";\n"],"mappings":";;;;;;;;;;AAYA,SAAwB,eAAe,QAAgB,MAAkC;CACxF,IAAI,OAAqD,KAAA;CACzD,KAAK,MAAM,SAAS,OAAO,QAAQ;EAClC,IAAI,MAAM,QAAQ,SAAA,GAAsB;EACxC,MAAM,OAAO,MAAM,QAAQ,KAAK,QAAQ,aAAa,EAAE;EAEvD,KADgB,SAAS,MAAM,SAAS,QAAQ,KAAK,WAAW,OAAO,GAAG,OAC1D,SAAS,KAAA,KAAa,KAAK,SAAS,KAAK,SACxD,OAAO;GAAE,MAAM,MAAM,QAAQ;GAAM,QAAQ,KAAK;EAAO;CAEzD;CACA,OAAO,MAAM;AACd;;;;;;;;;;;;;;;;;;ACRA,MAAa,sBAAsB;;;;;;AAOnC,MAAa,oBAAoB"}
|
package/dist/dev.mjs
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { r as serverError } from "./response-sth6FQsj.mjs";
|
|
2
|
+
import { t as Server } from "./Server-C0PGzSQO.mjs";
|
|
3
|
+
import { t as flattenHeaders } from "./flattenHeaders-DM5qsSqO.mjs";
|
|
4
|
+
import { existsSync, promises, realpathSync } from "node:fs";
|
|
5
|
+
import fpath from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
//#region src/utils/entryPaths.ts
|
|
8
|
+
/**
|
|
9
|
+
* Whether tb should run the framework from its TypeScript source instead of
|
|
10
|
+
* its compiled dist. This is a framework-development convenience: registry
|
|
11
|
+
* installs always run dist. Source mode is enabled explicitly via the
|
|
12
|
+
* TORPOR_SOURCE_DEV env var, or automatically when both `@torpor/build` and
|
|
13
|
+
* `@torpor/view` are symlinked into the app from outside node_modules (as
|
|
14
|
+
* workspace links and `link:` overrides do). Both packages must be linked,
|
|
15
|
+
* because source mode resolves their `torpor:source` export conditions, which
|
|
16
|
+
* point at src files that registry installs don't ship.
|
|
17
|
+
*/
|
|
18
|
+
function detectSourceMode(siteRoot) {
|
|
19
|
+
if (process.env.TORPOR_SOURCE_DEV) return true;
|
|
20
|
+
return ["@torpor/build", "@torpor/view"].every((pkg) => {
|
|
21
|
+
try {
|
|
22
|
+
const dist = fpath.resolve(siteRoot, "node_modules", pkg, "dist/index.mjs");
|
|
23
|
+
return !realpathSync(dist).includes("node_modules");
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The site entry files for the given mode: raw TypeScript sources when the
|
|
31
|
+
* framework is linked (Vite transforms them, and edits show up without a
|
|
32
|
+
* rebuild), compiled `.mjs` bundles for registry installs.
|
|
33
|
+
*/
|
|
34
|
+
function siteEntryPaths(siteRoot, sourceMode) {
|
|
35
|
+
const folder = fpath.resolve(siteRoot, "node_modules/@torpor/build", sourceMode ? "src/site" : "dist");
|
|
36
|
+
const ext = sourceMode ? ".ts" : ".mjs";
|
|
37
|
+
return {
|
|
38
|
+
serverEntry: fpath.join(folder, `serverEntry${ext}`),
|
|
39
|
+
clientEntry: fpath.join(folder, `clientEntry${ext}`),
|
|
40
|
+
clientDevEntry: fpath.join(folder, `clientEntryDev${ext}`)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/run/prepareTemplate.ts
|
|
45
|
+
function prepareTemplate(template, clientScript, clientDevScript) {
|
|
46
|
+
let result = template;
|
|
47
|
+
let headStart = result.indexOf("</head>");
|
|
48
|
+
if (headStart === -1) throw new Error(`Couldn't find <head> end tag`);
|
|
49
|
+
result = result.substring(0, headStart) + "%COMPONENT_HEAD%" + result.substring(headStart);
|
|
50
|
+
let divMatchStart = regexIndexOf(result, /<div\s+id=("app"|'app'|app)[\s>]/);
|
|
51
|
+
if (divMatchStart === -1) throw new Error(`Couldn't find <div id="app"></div>`);
|
|
52
|
+
let bodyStart = result.indexOf(">", divMatchStart) + 1;
|
|
53
|
+
let bodyEnd = result.indexOf("</div>", bodyStart);
|
|
54
|
+
if (bodyEnd === -1) throw new Error(`Couldn't find <div id="app"></div>`);
|
|
55
|
+
result = result.substring(0, bodyStart) + "%COMPONENT_BODY%" + result.substring(bodyEnd);
|
|
56
|
+
if (clientDevScript) result += `<script type="module" src="${clientDevScript}"><\/script>`;
|
|
57
|
+
result += `<script type="module" src="${clientScript}"><\/script>`;
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function regexIndexOf(string, regex, position) {
|
|
61
|
+
var indexOf = string.substring(position || 0).search(regex);
|
|
62
|
+
return indexOf >= 0 ? indexOf + (position || 0) : indexOf;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/run/devPlugin.ts
|
|
66
|
+
/**
|
|
67
|
+
* The default Node-runtime dev plugin. Reads `src/site.html`, sets up a torpor
|
|
68
|
+
* `Server` whose catch-all route renders via `vite.ssrLoadModule`, and registers
|
|
69
|
+
* a Connect middleware (after Vite's own middlewares) that bridges Node
|
|
70
|
+
* req/res to the torpor Server's fetch handler.
|
|
71
|
+
*
|
|
72
|
+
* Adapters that need a different runtime (e.g. workerd) provide their own
|
|
73
|
+
* `dev()` plugin instead.
|
|
74
|
+
*/
|
|
75
|
+
function devPlugin(site, sourceMode = false) {
|
|
76
|
+
return {
|
|
77
|
+
name: "torpor-dev",
|
|
78
|
+
configureServer(vite) {
|
|
79
|
+
const handlerPromise = createDevHandler(vite, site, sourceMode);
|
|
80
|
+
return () => {
|
|
81
|
+
vite.middlewares.use(async (req, res, next) => {
|
|
82
|
+
try {
|
|
83
|
+
await (await handlerPromise)(req, res);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
next(e);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function createDevHandler(vite, site, sourceMode) {
|
|
93
|
+
const entries = siteEntryPaths(site.root, sourceMode);
|
|
94
|
+
const serverScript = entries.serverEntry;
|
|
95
|
+
let template;
|
|
96
|
+
const templateFile = fpath.resolve(site.root, "src/site.html");
|
|
97
|
+
if (existsSync(templateFile)) {
|
|
98
|
+
template = await promises.readFile(templateFile, "utf-8");
|
|
99
|
+
template = await vite.transformIndexHtml("", template);
|
|
100
|
+
template = prepareTemplate(template, entries.clientEntry, entries.clientDevEntry);
|
|
101
|
+
}
|
|
102
|
+
const server = new Server();
|
|
103
|
+
server.add("*", async (ev) => {
|
|
104
|
+
try {
|
|
105
|
+
const { load } = await vite.ssrLoadModule(serverScript);
|
|
106
|
+
return await load(ev, template);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
vite.ssrFixStacktrace(e);
|
|
109
|
+
return serverError(e);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
globalThis.adapter = { env: process.env };
|
|
113
|
+
return async (req, res) => {
|
|
114
|
+
const request = await nodeReqToRequest(req);
|
|
115
|
+
await sendResponse(await server.fetch(request), res);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function nodeReqToRequest(req) {
|
|
119
|
+
const url = req.url ?? "/";
|
|
120
|
+
const host = req.headers.host ?? "localhost";
|
|
121
|
+
const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
|
|
122
|
+
const init = {
|
|
123
|
+
method: req.method,
|
|
124
|
+
headers: flattenHeaders(req.headers)
|
|
125
|
+
};
|
|
126
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
127
|
+
init.body = Readable.toWeb(req);
|
|
128
|
+
init.duplex = "half";
|
|
129
|
+
}
|
|
130
|
+
return new Request(`${protocol}://${host}${url}`, init);
|
|
131
|
+
}
|
|
132
|
+
async function sendResponse(response, res) {
|
|
133
|
+
res.statusCode = response.status;
|
|
134
|
+
response.headers.forEach((value, key) => res.setHeader(key, value));
|
|
135
|
+
if (!response.body) {
|
|
136
|
+
res.end();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
Readable.fromWeb(response.body).pipe(res);
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
export { siteEntryPaths as i, prepareTemplate as n, detectSourceMode as r, devPlugin as t };
|
|
143
|
+
|
|
144
|
+
//# sourceMappingURL=devPlugin-V689J6l6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devPlugin-V689J6l6.mjs","names":["path","path","fs"],"sources":["../src/utils/entryPaths.ts","../src/run/prepareTemplate.ts","../src/run/devPlugin.ts"],"sourcesContent":["import { realpathSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Paths to the site entry files that `tb` loads from the installed\n * `@torpor/build` package, in either source (linked framework) or dist\n * (registry install) mode.\n */\nexport type SiteEntryPaths = {\n\tserverEntry: string;\n\tclientEntry: string;\n\tclientDevEntry: string;\n};\n\n/**\n * Whether tb should run the framework from its TypeScript source instead of\n * its compiled dist. This is a framework-development convenience: registry\n * installs always run dist. Source mode is enabled explicitly via the\n * TORPOR_SOURCE_DEV env var, or automatically when both `@torpor/build` and\n * `@torpor/view` are symlinked into the app from outside node_modules (as\n * workspace links and `link:` overrides do). Both packages must be linked,\n * because source mode resolves their `torpor:source` export conditions, which\n * point at src files that registry installs don't ship.\n */\nexport function detectSourceMode(siteRoot: string): boolean {\n\tif (process.env.TORPOR_SOURCE_DEV) return true;\n\tconst packages = [\"@torpor/build\", \"@torpor/view\"];\n\treturn packages.every((pkg) => {\n\t\ttry {\n\t\t\tconst dist = path.resolve(siteRoot, \"node_modules\", pkg, \"dist/index.mjs\");\n\t\t\tconst real = realpathSync(dist);\n\t\t\treturn !real.includes(\"node_modules\");\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\n/**\n * The site entry files for the given mode: raw TypeScript sources when the\n * framework is linked (Vite transforms them, and edits show up without a\n * rebuild), compiled `.mjs` bundles for registry installs.\n */\nexport function siteEntryPaths(siteRoot: string, sourceMode: boolean): SiteEntryPaths {\n\tconst folder = path.resolve(\n\t\tsiteRoot,\n\t\t\"node_modules/@torpor/build\",\n\t\tsourceMode ? \"src/site\" : \"dist\",\n\t);\n\tconst ext = sourceMode ? \".ts\" : \".mjs\";\n\treturn {\n\t\tserverEntry: path.join(folder, `serverEntry${ext}`),\n\t\tclientEntry: path.join(folder, `clientEntry${ext}`),\n\t\tclientDevEntry: path.join(folder, `clientEntryDev${ext}`),\n\t};\n}\n\n/**\n * The torpor `Server` class module that a Cloudflare `_worker.ts` imports,\n * for the given mode.\n */\nexport function serverClassPath(siteRoot: string, sourceMode: boolean): string {\n\treturn path.resolve(\n\t\tsiteRoot,\n\t\t\"node_modules/@torpor/build\",\n\t\tsourceMode ? \"src/server/Server.ts\" : \"dist/server/Server.mjs\",\n\t);\n}\n","export default function prepareTemplate(\n\ttemplate: string,\n\tclientScript: string,\n\tclientDevScript?: string,\n): string {\n\tlet result = template;\n\n\t// Put %COMPONENT_HEAD% before </head>\n\t// This is where styles will go\n\tlet headStart = result.indexOf(\"</head>\");\n\tif (headStart === -1) {\n\t\tthrow new Error(`Couldn't find <head> end tag`);\n\t}\n\tresult = result.substring(0, headStart) + \"%COMPONENT_HEAD%\" + result.substring(headStart);\n\n\t// Put %COMPONENT_BODY% inside <div id=\"app\"></div>\n\t// This is where the component's HTML will go\n\tlet divMatchStart = regexIndexOf(result, /<div\\s+id=(\"app\"|'app'|app)[\\s>]/);\n\tif (divMatchStart === -1) {\n\t\tthrow new Error(`Couldn't find <div id=\"app\"></div>`);\n\t}\n\tlet bodyStart = result.indexOf(\">\", divMatchStart) + 1;\n\tlet bodyEnd = result.indexOf(\"</div>\", bodyStart);\n\tif (bodyEnd === -1) {\n\t\tthrow new Error(`Couldn't find <div id=\"app\"></div>`);\n\t}\n\tresult = result.substring(0, bodyStart) + \"%COMPONENT_BODY%\" + result.substring(bodyEnd);\n\n\t// Add the clientEntry script at the very end, for hydrating and navigating\n\tif (clientDevScript) {\n\t\tresult += `<script type=\"module\" src=\"${clientDevScript}\"></script>`;\n\t}\n\tresult += `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\n\treturn result;\n}\n\n// From https://stackoverflow.com/a/274094\nfunction regexIndexOf(string: string, regex: RegExp, position?: number) {\n\tvar indexOf = string.substring(position || 0).search(regex);\n\treturn indexOf >= 0 ? indexOf + (position || 0) : indexOf;\n}\n","import { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { existsSync, promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { type Plugin, type ViteDevServer } from \"vite\";\nimport { serverError } from \"../response.ts\";\nimport Server from \"../server/Server.ts\";\nimport flattenHeaders from \"../server/connect/flattenHeaders.ts\";\nimport type Site from \"../site/Site.ts\";\nimport { siteEntryPaths } from \"../utils/entryPaths\";\nimport prepareTemplate from \"./prepareTemplate.ts\";\n\ntype ReqHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\n/**\n * The default Node-runtime dev plugin. Reads `src/site.html`, sets up a torpor\n * `Server` whose catch-all route renders via `vite.ssrLoadModule`, and registers\n * a Connect middleware (after Vite's own middlewares) that bridges Node\n * req/res to the torpor Server's fetch handler.\n *\n * Adapters that need a different runtime (e.g. workerd) provide their own\n * `dev()` plugin instead.\n */\nexport default function devPlugin(site: Site, sourceMode = false): Plugin {\n\treturn {\n\t\tname: \"torpor-dev\",\n\t\tconfigureServer(vite) {\n\t\t\t// Kick off async setup immediately; the middleware awaits it so we\n\t\t\t// don't depend on whether Vite awaits configureServer itself.\n\t\t\tconst handlerPromise = createDevHandler(vite, site, sourceMode);\n\t\t\t// Return a post-hook so our middleware runs AFTER Vite's internal\n\t\t\t// middlewares (static serving, transforms, HMR), acting as the SSR\n\t\t\t// fallback.\n\t\t\treturn () => {\n\t\t\t\tvite.middlewares.use(async (req, res, next) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst handler = await handlerPromise;\n\t\t\t\t\t\tawait handler(req, res);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tnext(e as Error);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t};\n\t\t},\n\t};\n}\n\nasync function createDevHandler(\n\tvite: ViteDevServer,\n\tsite: Site,\n\tsourceMode: boolean,\n): Promise<ReqHandler> {\n\tconst entries = siteEntryPaths(site.root, sourceMode);\n\tconst serverScript = entries.serverEntry;\n\n\t// Read site.html if present\n\t// It's called site.html because @torpor/build builds the site (html, routes\n\t// etc) while the user builds the app (components etc). An endpoints-only\n\t// site has no site.html, and no template is needed\n\tlet template: string | undefined;\n\tconst templateFile = path.resolve(site.root, \"src/site.html\");\n\tif (existsSync(templateFile)) {\n\t\ttemplate = await fs.readFile(templateFile, \"utf-8\");\n\n\t\t// Apply Vite HTML transforms. This injects the Vite HMR client, and also\n\t\t// applies HTML transforms from Vite plugins. Note that we only support a\n\t\t// universal transform, not individual transforms for each route\n\t\ttemplate = await vite.transformIndexHtml(\"\", template);\n\n\t\t// Prepare site.html so that we can just splice components into it\n\t\ttemplate = prepareTemplate(template, entries.clientEntry, entries.clientDevEntry);\n\t}\n\n\tconst server = new Server();\n\n\t// Every request (GET, POST, etc) goes through loadEndPoint\n\tserver.add(\"*\", async (ev) => {\n\t\ttry {\n\t\t\t// Load the server entry. ssrLoadModule automatically transforms ESM\n\t\t\t// source code to be usable in Node.js. No bundling is required, and\n\t\t\t// it provides efficient invalidation similar to HMR\n\t\t\tconst { load } = await vite.ssrLoadModule(serverScript);\n\n\t\t\t// Render the app HTML (or fetch server data etc) via serverEntry's\n\t\t\t// exported `load` function\n\t\t\treturn await load(ev, template);\n\t\t} catch (e: any) {\n\t\t\t// If an error is caught, let Vite fix the stack trace so it maps\n\t\t\t// back to your actual source code\n\t\t\tvite.ssrFixStacktrace(e);\n\t\t\treturn serverError(e);\n\t\t}\n\t});\n\n\t// Put adapter-specific functionality in the `adapter` property of\n\t// globalThis for now\n\t// @ts-ignore\n\tglobalThis.adapter = { env: process.env };\n\n\treturn async (req, res) => {\n\t\tconst request = await nodeReqToRequest(req);\n\t\tconst response = await server.fetch(request);\n\t\tawait sendResponse(response, res);\n\t};\n}\n\nasync function nodeReqToRequest(req: IncomingMessage): Promise<Request> {\n\tconst url = req.url ?? \"/\";\n\tconst host = req.headers.host ?? \"localhost\";\n\tconst protocol = \"encrypted\" in req.socket && req.socket.encrypted ? \"https\" : \"http\";\n\tconst init: RequestInit = {\n\t\tmethod: req.method,\n\t\theaders: flattenHeaders(req.headers),\n\t};\n\tif (req.method !== \"GET\" && req.method !== \"HEAD\") {\n\t\tinit.body = Readable.toWeb(req) as ReadableStream;\n\t\t// @ts-ignore duplex is required when streaming a body\n\t\tinit.duplex = \"half\";\n\t}\n\treturn new Request(`${protocol}://${host}${url}`, init);\n}\n\nasync function sendResponse(response: Response, res: ServerResponse): Promise<void> {\n\tres.statusCode = response.status;\n\tresponse.headers.forEach((value, key) => res.setHeader(key, value));\n\tif (!response.body) {\n\t\tres.end();\n\t\treturn;\n\t}\n\tReadable.fromWeb(response.body as unknown as import(\"node:stream/web\").ReadableStream).pipe(res);\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -5,7 +5,7 @@ import { t as ServerHook } from "./ServerHook-BTSGfLiJ.mjs";
|
|
|
5
5
|
import { a as PageLoadReturn, i as PageEndPoint, o as PageLoadEvent } from "./sitemap-BjqN4d4W.mjs";
|
|
6
6
|
import { n as UntypedResponse, t as TypedResponse } from "./TypedResponse-6lznqN4U.mjs";
|
|
7
7
|
import { t as StandardSchemaV1 } from "./StandardSchema-D28L8_zZ.mjs";
|
|
8
|
-
import { a as ServerEndPoint, c as ActionBody, d as PageServerActionSchemas, f as PageServerEndPoint, g as Adapter, h as SitePlugin, i as SchemaQuery, l as LoadParams, m as PageServerAction, n as ParamsOf, o as ServerEndPointSchemas, p as PageServerLoad, r as SchemaBody, s as ServerRequest, t as Site, u as LoadQuery } from "./Site-
|
|
8
|
+
import { a as ServerEndPoint, c as ActionBody, d as PageServerActionSchemas, f as PageServerEndPoint, g as Adapter, h as SitePlugin, i as SchemaQuery, l as LoadParams, m as PageServerAction, n as ParamsOf, o as ServerEndPointSchemas, p as PageServerLoad, r as SchemaBody, s as ServerRequest, t as Site, u as LoadQuery } from "./Site-Blz32JRD.mjs";
|
|
9
9
|
import { t as Jsonify } from "./Jsonify-Dg6gChd3.mjs";
|
|
10
10
|
//#region src/types/PageData.d.ts
|
|
11
11
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -168,8 +168,9 @@ var Site = class {
|
|
|
168
168
|
*/
|
|
169
169
|
vitePlugins = [];
|
|
170
170
|
/**
|
|
171
|
-
* The site's adapter, e.g. from `@torpor/adapter-node
|
|
172
|
-
* `@torpor/adapter-cloudflare`. When not set,
|
|
171
|
+
* The site's adapter, e.g. from `@torpor/adapter-node`,
|
|
172
|
+
* `@torpor/adapter-cloudflare` or `@torpor/adapter-electron`. When not set,
|
|
173
|
+
* one is resolved when it's
|
|
173
174
|
* needed: a deployment environment (e.g. `CF_PAGES`) picks the platform's
|
|
174
175
|
* adapter when installed, an installed adapter is used otherwise, and
|
|
175
176
|
* preview falls back to serving on the current runtime. See
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","#routePath","#routeType","#sortRoutes","#routeTypeFromString","#pushRoute"],"sources":["../src/site/defaultAdapter.ts","../src/site/Site.ts"],"sourcesContent":["import type Server from \"../server/Server\";\nimport type Adapter from \"../types/Adapter\";\n\n/**\n * The known adapter packages, in preference order, with the platform env\n * markers that select them when the site is built in a deployment\n * environment. Only packages that are actually installed (somewhere in the\n * site's node_modules) can be resolved.\n */\nconst ADAPTER_PACKAGES = [\n\t{\n\t\t// The preferred fallback: a node server runs anywhere\n\t\tpkg: \"@torpor/adapter-node\",\n\t\tplatforms: [] as string[],\n\t},\n\t{\n\t\tpkg: \"@torpor/adapter-cloudflare\",\n\t\t// Set when building in a Cloudflare Pages environment. More markers\n\t\t// can be added as adapters are (e.g. `VERCEL` for an adapter-vercel)\n\t\tplatforms: [\"CF_PAGES\"],\n\t},\n];\n\n/**\n * Loads an adapter package, returning undefined when it isn't installed.\n * The import deliberately uses a variable specifier so that bundlers never\n * try to resolve adapter packages from @torpor/build.\n */\nasync function loadAdapterPackage(pkg: string): Promise<Adapter | undefined> {\n\ttry {\n\t\treturn (await import(/* @vite-ignore */ pkg)).default;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport type AdapterResolution = {\n\t/** The resolved adapter, or undefined when none could be found */\n\tadapter: Adapter | undefined;\n\t/** The package the adapter was resolved from */\n\tpkg: string | undefined;\n\t/** Why the adapter was chosen, for logging */\n\treason: string;\n};\n\nexport type ResolveAdapterOptions = {\n\t/** The environment to check for platform markers (defaults to process.env) */\n\tenv?: Record<string, string | undefined>;\n\t/** Loads an adapter package by name (defaults to a dynamic import) */\n\tload?: (pkg: string) => Promise<Adapter | undefined>;\n};\n\n/**\n * Finds an adapter to use when the site doesn't set one explicitly:\n *\n * 1. A platform build environment (e.g. `CF_PAGES` for Cloudflare Pages)\n * selects its adapter, when the adapter package is installed.\n * 2. Otherwise an installed adapter is used, preferring `adapter-node`\n * (a node server runs anywhere).\n * 3. Otherwise nothing is found, and the caller decides what to do (warn,\n * or serve on the current runtime).\n */\nexport async function resolveAdapter(\n\toptions: ResolveAdapterOptions = {},\n): Promise<AdapterResolution> {\n\tconst env = options.env ?? process.env;\n\tconst load = options.load ?? loadAdapterPackage;\n\n\t// Import each package at most once\n\tconst loaded = new Map<string, Adapter | undefined>();\n\tconst loadOnce = async (pkg: string) => {\n\t\tif (!loaded.has(pkg)) loaded.set(pkg, await load(pkg));\n\t\treturn loaded.get(pkg);\n\t};\n\n\t// 1. A deployment environment selects its adapter\n\tfor (const candidate of ADAPTER_PACKAGES) {\n\t\tfor (const marker of candidate.platforms) {\n\t\t\tif (env[marker] === undefined) continue;\n\t\t\tconst adapter = await loadOnce(candidate.pkg);\n\t\t\tif (adapter) {\n\t\t\t\treturn { adapter, pkg: candidate.pkg, reason: `${marker} detected` };\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. An installed adapter (the list is in preference order)\n\tfor (const candidate of ADAPTER_PACKAGES) {\n\t\tconst adapter = await loadOnce(candidate.pkg);\n\t\tif (adapter) {\n\t\t\treturn { adapter, pkg: candidate.pkg, reason: \"installed\" };\n\t\t}\n\t}\n\n\treturn { adapter: undefined, pkg: undefined, reason: \"none found\" };\n}\n\n/**\n * The current runtime, when preview can serve a fetch handler without an\n * adapter package. (Node's request conversion lives in adapter-node, so\n * node is deliberately not handled here.)\n */\nfunction currentRuntime(): \"bun\" | \"deno\" | undefined {\n\tconst global = globalThis as any;\n\tif (global.Bun) return \"bun\";\n\tif (global.Deno) return \"deno\";\n\treturn undefined;\n}\n\n/**\n * Serves the built output on the current runtime, for when no adapter\n * package could be found. Returns false when the runtime isn't supported.\n */\nfunction serveOnCurrentRuntime(server: Server): boolean {\n\tconst runtime = currentRuntime();\n\tif (!runtime) return false;\n\n\tconst global = globalThis as any;\n\tconst port = parseInt(process.env.PORT ?? \"7059\");\n\tconst hostname = process.env.HOST ?? \"localhost\";\n\tconsole.log(`[torpor] Serving on the ${runtime} runtime at ${hostname}:${port}`);\n\n\tif (runtime === \"bun\") {\n\t\tglobal.Bun.serve({ port, hostname, fetch: (req: Request) => server.fetch(req) });\n\t} else {\n\t\tglobal.Deno.serve({ port, hostname }, (req: Request) => server.fetch(req));\n\t}\n\treturn true;\n}\n\n// The resolution is cached per process, and the \"Using ...\" message is only\n// logged once\nlet cache: Promise<AdapterResolution> | undefined;\nlet loggedPkg: string | undefined;\n\nfunction getCachedResolution(): Promise<AdapterResolution> {\n\treturn (cache ??= resolveAdapter());\n}\n\nfunction logAdapter(pkg: string, reason: string): void {\n\tif (loggedPkg === pkg) return;\n\tloggedPkg = pkg;\n\tconsole.log(`[torpor] Using the ${pkg.replace(\"@torpor/adapter-\", \"\")} adapter (${reason})`);\n}\n\n/**\n * The adapter used when the site doesn't set one explicitly. Rather than\n * being a real adapter, it resolves one at the time it's needed (see\n * `resolveAdapter`), and as a last resort serves the built output on the\n * current runtime -- Bun and Deno can serve a fetch handler natively, while\n * node needs `@torpor/adapter-node`.\n *\n * Dev mode is not affected: with no `dev` hook, the framework's default\n * Node-runtime dev plugin is used.\n */\nconst defaultAdapter: Adapter = {\n\tasync serve(server, site) {\n\t\tconst { adapter, pkg, reason } = await getCachedResolution();\n\t\tif (adapter && pkg) {\n\t\t\tlogAdapter(pkg, reason);\n\t\t\tawait adapter.serve(server, site);\n\t\t\treturn;\n\t\t}\n\t\tif (serveOnCurrentRuntime(server)) return;\n\t\tconsole.log(\n\t\t\t\"[torpor] No adapter found -- install one (e.g. `pnpm add @torpor/adapter-node`) or set `site.adapter` in site.config.ts\",\n\t\t);\n\t},\n\tasync prebuild(site) {\n\t\tconst { adapter, pkg, reason } = await getCachedResolution();\n\t\tif (!adapter || !pkg) return;\n\t\tlogAdapter(pkg, reason);\n\t\tawait adapter.prebuild?.(site);\n\t},\n\tasync postbuild(site) {\n\t\tconst { adapter, pkg, reason } = await getCachedResolution();\n\t\tif (!adapter || !pkg) return;\n\t\tlogAdapter(pkg, reason);\n\t\tawait adapter.postbuild?.(site);\n\t},\n};\n\nexport default defaultAdapter;\n","import { promises as fs } from \"node:fs\";\nimport fpath from \"node:path\";\nimport { type Plugin, type UserConfig } from \"vite\";\nimport type Adapter from \"../types/Adapter\";\nimport type { SitemapOptions } from \"../discovery/sitemap\";\nimport type MiddlewareFunction from \"../server/types/MiddlewareFunction\";\nimport type { StandardSchemaV1 } from \"../types/StandardSchema\";\nimport type SitePlugin from \"../types/SitePlugin\";\nimport type { InlineEndPoint } from \"../types/Route\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport type Route from \"../types/Route\";\nimport type ServerEndPoint from \"../types/ServerEndPoint\";\nimport type ServerHook from \"../types/ServerHook\";\nimport {\n\tERROR_ROUTE,\n\tHOOK_ROUTE,\n\tHOOK_SERVER_ROUTE,\n\tLAYOUT_ROUTE,\n\tLAYOUT_SERVER_ROUTE,\n\tPAGE_ROUTE,\n\tPAGE_SERVER_ROUTE,\n\tRouteType,\n\tSERVER_ROUTE,\n} from \"../types/RouteType\";\nimport defaultAdapter from \"./defaultAdapter\";\n\n// 1. The user sets this up\n// 2. It is passed into serverEntry and clientEntry through a Vite plugin\n// (defined in manifest.ts)\n// 3. In those files, it is used to build a Router\n\n/**\n * The Site class contains the information necessary to setup a @torpor/build\n * site, including routes, plugins, and options.\n */\nexport default class Site {\n\troot: string;\n\troutes: Route[] = [];\n\t/**\n\t * Torpor site plugins, run when the site config is loaded (for dev and\n\t * build) and when the server starts up. Use these to register extra\n\t * routes (e.g. `openApi()` from `@torpor/build/openapi`) or add other\n\t * framework-level functionality\n\t */\n\tplugins: SitePlugin[] = [];\n\t/**\n\t * Vite plugins to add to the client and server builds\n\t */\n\tvitePlugins: Plugin[] = [];\n\t/**\n\t * The site's adapter, e.g. from `@torpor/adapter-node` or\n\t * `@torpor/adapter-cloudflare`. When not set, one is resolved when it's\n\t * needed: a deployment environment (e.g. `CF_PAGES`) picks the platform's\n\t * adapter when installed, an installed adapter is used otherwise, and\n\t * preview falls back to serving on the current runtime. See\n\t * defaultAdapter.ts\n\t */\n\tadapter: Adapter = defaultAdapter;\n\t/**\n\t * Extra inputs, as absolute file paths, that will be compiled with the\n\t * output. What you do with them after that is up to you. File paths ending\n\t * with server.ts/js will only be compiled with the server output\n\t */\n\tinputs: string[] = [];\n\t/**\n\t * Vite config options to merge with the standard Torpor build options.\n\t * `plugins` and `build.rollupOptions.input` will be overridden, so you\n\t * should set `Site.vitePlugins` and `Site.inputs` instead. Other options may be\n\t * overridden or incompatible, so experimentation may be required\n\t */\n\tviteConfig?: UserConfig;\n\t/**\n\t * The path to the site.config file. Set automatically by the CLI; used by\n\t * the manifest plugin to import inline endpoints for server builds.\n\t */\n\tconfigFile?: string;\n\t/**\n\t * A state map for plugins to store their configuration and state on the\n\t * site, so that framework internals (e.g. the manifest plugin) and other\n\t * plugins can read them back:\n\t *\n\t * ```ts\n\t * const myKey = Symbol.for(\"my-plugin\");\n\t * site.pluginState.set(myKey, options);\n\t * ```\n\t *\n\t * Prefer `Symbol.for(\"...\")` keys (or plain strings) over `Symbol()`,\n\t * because the user's config file and framework internals can be loaded as\n\t * separate module instances -- registry symbols are shared across\n\t * instances, `Symbol()` ones are not.\n\t */\n\tpluginState: Map<PropertyKey, unknown> = new Map();\n\t/**\n\t * The path the site is mounted under, e.g. \"/app\". Incoming server\n\t * requests that don't carry it are not found; everything the framework\n\t * generates (HTML attributes, redirect locations, paths from `route()`)\n\t * gets it added. Server code and markup stay base-free.\n\t */\n\tbasePath: string = \"\";\n\t/**\n\t * Wraps client-side navigations in the View Transitions API -- where the\n\t * browser supports it -- so pages cross-fade by default and can be\n\t * animated with `::view-transition-old/new` CSS. Opt-in: off unless set\n\t * to `true`. The initial hydration never transitions.\n\t */\n\tviewTransitions?: boolean;\n\t/**\n\t * Prerendering, applied to every page route that doesn't have (or\n\t * inherit) a `prerender` flag of its own. `true` prerenders everything;\n\t * a `Record<string, boolean>` maps route paths -- exact (`/about`) or\n\t * prefix (`/blog/**` for a path and everything below it) -- to flags,\n\t * with the most specific matching key winning. Route endpoint and\n\t * layout flags always take precedence over this.\n\t */\n\tprerender?: boolean | Record<string, boolean>;\n\t/**\n\t * The site's absolute origin, e.g. \"https://example.com\". Required for\n\t * a build-time sitemap (see `sitemap`), since there is no request to\n\t * take the host from.\n\t */\n\torigin?: string;\n\t/**\n\t * Global middleware, run for every request before routing -- including\n\t * requests that won't match a route (maintenance mode, legacy url\n\t * redirects, etc). Each middleware's `enter` runs in order and may\n\t * return a Response to short-circuit the request; `exit` hooks run in\n\t * reverse after the handler. Middleware see the raw request url.\n\t *\n\t * Route middleware (scoped to a single route) is set on the route's\n\t * server endpoint instead, e.g. `middleware: [...]` in\n\t * `+page.server.ts` or `+server.ts`.\n\t */\n\tmiddleware: MiddlewareFunction[] = [];\n\t/**\n\t * Writes a sitemap.xml listing the prerendered pages at build time\n\t * (see `prerender`). Pass a custom path (e.g. \"/blog/sitemap.xml\") or\n\t * `true` for \"/sitemap.xml\"; requires `origin` to be set.\n\t *\n\t * For a runtime sitemap -- one that reads the database and keeps up with\n\t * content that isn't prerendered -- pass a config instead, e.g.\n\t * `sitemap = { get: async () => [\"/posts/a\", \"/posts/b\"] }`, which serves\n\t * the XML live at that path (customizable with `path`).\n\t */\n\tsitemap?: SitemapOptions | boolean | string;\n\t/**\n\t * A Standard Schema (zod, valibot, arktype, etc) used to validate (and\n\t * parse) the server environment. When set, `env()` from\n\t * `@torpor/build/env` checks the environment on first use per request and\n\t * throws with the schema's issues -- so a missing or invalid key fails\n\t * immediately instead of passing `undefined` along. The schema must\n\t * validate synchronously.\n\t */\n\tenv?: StandardSchemaV1<unknown, TorporEnv>;\n\t/**\n\t * Inline endpoints keyed by `\"path:type\"`. Populated by `addRoute` when\n\t * the user passes an inline endpoint object instead of a file path.\n\t */\n\tinlineEndPoints: Record<string, InlineEndPoint> = {};\n\n\tconstructor() {\n\t\tthis.root = process.cwd();\n\t}\n\n\t/**\n\t * Adds a folder of routes, based on the names of the files in the folder\n\t * @param folder The folder containing the routes\n\t * @param subFolder If a subFolder is set, all routes in the folder will be\n\t * placed under the subFolder, and only hooks, layouts etc will be used if\n\t * they have the same subFolder. This can be used to create e.g. an `api`\n\t * set of routes with their own hook for authorization from various clients\n\t */\n\tasync addRouteFolder(folder: string, subFolder?: string): Promise<void> {\n\t\tconst routeFolder = fpath.join(this.root, folder);\n\t\tconst routeFiles = await fs.readdir(routeFolder, { recursive: true });\n\t\tif (subFolder !== undefined && !subFolder.startsWith(\"/\")) {\n\t\t\tsubFolder = \"/\" + subFolder;\n\t\t}\n\t\tfor (let file of routeFiles) {\n\t\t\t// The file must start with `+` or `_` and end with `.js` or `.ts`\n\t\t\tif (/^(\\+|_).+(\\.js|\\.ts)$/.test(fpath.basename(file))) {\n\t\t\t\tlet routePath = this.#routePath(file);\n\t\t\t\tif (subFolder !== undefined) {\n\t\t\t\t\troutePath = subFolder + routePath;\n\t\t\t\t}\n\t\t\t\tlet type = this.#routeType(file);\n\t\t\t\tfile = fpath.relative(this.root, fpath.resolve(routeFolder, file));\n\t\t\t\tthis.routes.push({\n\t\t\t\t\tpath: routePath,\n\t\t\t\t\tfile,\n\t\t\t\t\ttype,\n\t\t\t\t\tsubFolder: subFolder,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.#sortRoutes();\n\t}\n\n\t/**\n\t * Adds a file route, which can have a standard name (e.g. +page.ts) or any\n\t * name if you pass the type\n\t * @param path The route path\n\t * @param file The file\n\t * @param type The file type (if the file name is non-standard)\n\t */\n\taddRouteFile(\n\t\tpath: string,\n\t\tfile: string,\n\t\ttype?:\n\t\t\t| \"+page\"\n\t\t\t| \"+page.server\"\n\t\t\t| \"+server\"\n\t\t\t| \"_layout\"\n\t\t\t| \"_layout.server\"\n\t\t\t| \"_hook\"\n\t\t\t| \"_hook.server\"\n\t\t\t| \"_error\",\n\t\tsubFolder?: string,\n\t): void {\n\t\tlet routePath = this.#routePath(fpath.join(path, type ?? fpath.basename(file)));\n\t\tlet routeType = type ? this.#routeTypeFromString(type) : this.#routeType(file);\n\t\tif (subFolder !== undefined && !subFolder.startsWith(\"/\")) {\n\t\t\tsubFolder = \"/\" + subFolder;\n\t\t}\n\t\tthis.routes.push({\n\t\t\tpath: routePath,\n\t\t\tfile,\n\t\t\ttype: routeType,\n\t\t\tsubFolder: subFolder,\n\t\t});\n\t\tthis.#sortRoutes();\n\t}\n\n\t#routePath(file: string): string {\n\t\tlet routePath = file\n\t\t\t.replace(/^\\//, \"\")\n\t\t\t.replace(/(\\.ts|\\.js)$/, \"\")\n\t\t\t.replace(/_hook.server$/, \"_hook/~server\")\n\t\t\t.replace(/_layout.server$/, \"_layout/~server\")\n\t\t\t.replace(/\\+page.server$/, \"~server\")\n\t\t\t.replace(/\\+server$/, \"\")\n\t\t\t.replace(/\\+page$/, \"\")\n\t\t\t.replace(/\\/$/, \"\");\n\t\treturn routePath.length > 0 ? `/${routePath}` : \"/\";\n\t}\n\n\t#routeType(file: string): RouteType {\n\t\tlet routePath = file.replace(/^\\//, \"\").replace(/(\\.ts|\\.js)$/, \"\");\n\t\tif (routePath.endsWith(\"+page\")) {\n\t\t\treturn PAGE_ROUTE;\n\t\t} else if (routePath.endsWith(\"+page.server\")) {\n\t\t\treturn PAGE_SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"+server\")) {\n\t\t\treturn SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"_layout\")) {\n\t\t\treturn LAYOUT_ROUTE;\n\t\t} else if (routePath.endsWith(\"_layout.server\")) {\n\t\t\treturn LAYOUT_SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"_hook\")) {\n\t\t\treturn HOOK_ROUTE;\n\t\t} else if (routePath.endsWith(\"_hook.server\")) {\n\t\t\treturn HOOK_SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"_error\")) {\n\t\t\treturn ERROR_ROUTE;\n\t\t}\n\t\treturn -1;\n\t}\n\n\t#routeTypeFromString(\n\t\ttype:\n\t\t\t| \"+page\"\n\t\t\t| \"+page.server\"\n\t\t\t| \"+server\"\n\t\t\t| \"_layout\"\n\t\t\t| \"_layout.server\"\n\t\t\t| \"_hook\"\n\t\t\t| \"_hook.server\"\n\t\t\t| \"_error\",\n\t) {\n\t\tswitch (type) {\n\t\t\tcase \"+page\":\n\t\t\t\treturn PAGE_ROUTE;\n\t\t\tcase \"+page.server\":\n\t\t\t\treturn PAGE_SERVER_ROUTE;\n\t\t\tcase \"+server\":\n\t\t\t\treturn SERVER_ROUTE;\n\t\t\tcase \"_layout\":\n\t\t\t\treturn LAYOUT_ROUTE;\n\t\t\tcase \"_layout.server\":\n\t\t\t\treturn LAYOUT_SERVER_ROUTE;\n\t\t\tcase \"_hook\":\n\t\t\t\treturn HOOK_ROUTE;\n\t\t\tcase \"_hook.server\":\n\t\t\t\treturn HOOK_SERVER_ROUTE;\n\t\t\tcase \"_error\":\n\t\t\t\treturn ERROR_ROUTE;\n\t\t}\n\t}\n\n\t/**\n\t * Adds a route in code, without needing the standard file naming\n\t * conventions (+page.ts, +page.server.ts, etc). This is useful for small\n\t * sites with one or two pages.\n\t *\n\t * Any combination of options may be provided; each maps to the\n\t * corresponding file-route type:\n\t *\n\t * - `page` — a `+page` (`.torp` component or `.ts`/`.js` `PageEndPoint`)\n\t * - `pageServer` — a `+page.server` (`PageServerEndPoint`, file or inline)\n\t * - `server` — a `+server` HTTP endpoint (`ServerEndPoint`, file or inline)\n\t * - `layout` — a `_layout` (`.torp` component or `.ts`/`.js` `PageEndPoint`)\n\t * - `layoutServer` — a `_layout.server` (`PageServerEndPoint`, file or inline)\n\t * - `hookServer` — a `_hook.server` (`ServerHook`, file or inline)\n\t * - `error` — an `_error` page (`.torp` component or `.ts`/`.js` `PageEndPoint`)\n\t *\n\t * Options that need a component (`page`, `layout`, `error`) must be file\n\t * paths so the component is included in the client bundle. Server-only\n\t * options (`pageServer`, `server`, `layoutServer`, `hookServer`) accept\n\t * either a file path or an inline object; inline server code is kept out\n\t * of the client bundle automatically.\n\t *\n\t * @example\n\t * // A page with an inline server action\n\t * site.addRoute(\"/\", {\n\t * page: \"./src/Counter.torp\",\n\t * pageServer: { actions: { set: async ({ request }) => ok() } },\n\t * });\n\t * @example\n\t * // A JSON API endpoint\n\t * site.addRoute(\"/api/time\", {\n\t * server: { get: async () => ok({ time: Date.now() }) },\n\t * });\n\t * @example\n\t * // A root layout, server hook, and error page\n\t * site.addRoute(\"/\", {\n\t * layout: \"./src/Layout.torp\",\n\t * hookServer: { enter: async (event) => {} },\n\t * error: \"./src/ErrorPage.torp\",\n\t * });\n\t *\n\t * @param path The route path, e.g. `/` or `/about`\n\t * @param options File paths and/or inline endpoints for the route\n\t * @param subFolder An optional subFolder for grouping routes\n\t */\n\taddRoute(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tpage?: string;\n\t\t\tpageServer?: string | PageServerEndPoint;\n\t\t\tserver?: string | ServerEndPoint;\n\t\t\tlayout?: string;\n\t\t\tlayoutServer?: string | PageServerEndPoint;\n\t\t\thookServer?: string | ServerHook;\n\t\t\terror?: string;\n\t\t},\n\t\tsubFolder?: string,\n\t): void {\n\t\tlet normalizedSubFolder = subFolder;\n\t\tif (normalizedSubFolder !== undefined && !normalizedSubFolder.startsWith(\"/\")) {\n\t\t\tnormalizedSubFolder = \"/\" + normalizedSubFolder;\n\t\t}\n\n\t\t// The base path with any trailing slash stripped, so that we can\n\t\t// append \"/_layout\", \"/~server\" etc. For the root \"/\" this is \"\".\n\t\tlet base = path.replace(/\\/$/, \"\");\n\n\t\tif (options.page !== undefined) {\n\t\t\tthis.#pushRoute(path, PAGE_ROUTE, options.page, normalizedSubFolder);\n\t\t}\n\t\tif (options.pageServer !== undefined) {\n\t\t\tthis.#pushRoute(\n\t\t\t\t`${base}/~server`,\n\t\t\t\tPAGE_SERVER_ROUTE,\n\t\t\t\toptions.pageServer,\n\t\t\t\tnormalizedSubFolder,\n\t\t\t);\n\t\t}\n\t\tif (options.server !== undefined) {\n\t\t\tthis.#pushRoute(path, SERVER_ROUTE, options.server, normalizedSubFolder);\n\t\t}\n\t\tif (options.layout !== undefined) {\n\t\t\tthis.#pushRoute(`${base}/_layout`, LAYOUT_ROUTE, options.layout, normalizedSubFolder);\n\t\t}\n\t\tif (options.layoutServer !== undefined) {\n\t\t\tthis.#pushRoute(\n\t\t\t\t`${base}/_layout/~server`,\n\t\t\t\tLAYOUT_SERVER_ROUTE,\n\t\t\t\toptions.layoutServer,\n\t\t\t\tnormalizedSubFolder,\n\t\t\t);\n\t\t}\n\t\tif (options.hookServer !== undefined) {\n\t\t\tthis.#pushRoute(\n\t\t\t\t`${base}/_hook/~server`,\n\t\t\t\tHOOK_SERVER_ROUTE,\n\t\t\t\toptions.hookServer,\n\t\t\t\tnormalizedSubFolder,\n\t\t\t);\n\t\t}\n\t\tif (options.error !== undefined) {\n\t\t\tthis.#pushRoute(`${base}/_error`, ERROR_ROUTE, options.error, normalizedSubFolder);\n\t\t}\n\n\t\tthis.#sortRoutes();\n\t}\n\n\t/**\n\t * Pushes a single route entry, handling both file-path and inline-endpoint\n\t * targets. File paths are resolved relative to the site root; inline\n\t * endpoints are also stored in `inlineEndPoints` keyed by `\"path:type\"`\n\t * so the manifest plugin can resolve them at build time.\n\t *\n\t * If a route with the same path and type was already added, it is\n\t * replaced, so that running a plugin twice (config load and server\n\t * startup) doesn't add duplicate routes.\n\t */\n\t#pushRoute(\n\t\troutePath: string,\n\t\ttype: RouteType,\n\t\ttarget: string | InlineEndPoint,\n\t\tsubFolder?: string,\n\t): void {\n\t\tconst index = this.routes.findIndex((r) => r.path === routePath && r.type === type);\n\t\tif (typeof target === \"string\") {\n\t\t\tlet file = fpath.relative(this.root, fpath.resolve(this.root, target));\n\t\t\tif (index >= 0) {\n\t\t\t\tthis.routes[index] = { path: routePath, file, type, subFolder };\n\t\t\t} else {\n\t\t\t\tthis.routes.push({ path: routePath, file, type, subFolder });\n\t\t\t}\n\t\t} else {\n\t\t\tlet key = `${routePath}:${type}`;\n\t\t\tthis.inlineEndPoints[key] = target;\n\t\t\tif (index >= 0) {\n\t\t\t\tthis.routes[index] = { path: routePath, endPoint: target, type, subFolder };\n\t\t\t} else {\n\t\t\t\tthis.routes.push({ path: routePath, endPoint: target, type, subFolder });\n\t\t\t}\n\t\t}\n\t}\n\n\t#sortRoutes() {\n\t\tthis.routes = this.routes.sort((a, b) => {\n\t\t\t// Sort [param]s after paths\n\t\t\t// There might be a quicker/easier way to do this\n\t\t\tfor (let i = 0; i < Math.min(a.path.length, b.path.length); i++) {\n\t\t\t\tif (a.path[i] === \"[\" && b.path[i] !== \"[\") {\n\t\t\t\t\treturn 1;\n\t\t\t\t} else if (b.path[i] === \"[\" && a.path[i] !== \"[\") {\n\t\t\t\t\treturn -1;\n\t\t\t\t} else if (a.path[i] === b.path[i]) {\n\t\t\t\t\t// Keep going...\n\t\t\t\t} else {\n\t\t\t\t\treturn a.path[i].localeCompare(b.path[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn a.path.length - b.path.length;\n\t\t});\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","#routePath","#routeType","#sortRoutes","#routeTypeFromString","#pushRoute"],"sources":["../src/site/defaultAdapter.ts","../src/site/Site.ts"],"sourcesContent":["import type Server from \"../server/Server\";\nimport type Adapter from \"../types/Adapter\";\n\n/**\n * The known adapter packages, in preference order, with the platform env\n * markers that select them when the site is built in a deployment\n * environment. Only packages that are actually installed (somewhere in the\n * site's node_modules) can be resolved.\n */\nconst ADAPTER_PACKAGES = [\n\t{\n\t\t// The preferred fallback: a node server runs anywhere\n\t\tpkg: \"@torpor/adapter-node\",\n\t\tplatforms: [] as string[],\n\t},\n\t{\n\t\tpkg: \"@torpor/adapter-cloudflare\",\n\t\t// Set when building in a Cloudflare Pages environment. More markers\n\t\t// can be added as adapters are (e.g. `VERCEL` for an adapter-vercel)\n\t\tplatforms: [\"CF_PAGES\"],\n\t},\n];\n\n/**\n * Loads an adapter package, returning undefined when it isn't installed.\n * The import deliberately uses a variable specifier so that bundlers never\n * try to resolve adapter packages from @torpor/build.\n */\nasync function loadAdapterPackage(pkg: string): Promise<Adapter | undefined> {\n\ttry {\n\t\treturn (await import(/* @vite-ignore */ pkg)).default;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport type AdapterResolution = {\n\t/** The resolved adapter, or undefined when none could be found */\n\tadapter: Adapter | undefined;\n\t/** The package the adapter was resolved from */\n\tpkg: string | undefined;\n\t/** Why the adapter was chosen, for logging */\n\treason: string;\n};\n\nexport type ResolveAdapterOptions = {\n\t/** The environment to check for platform markers (defaults to process.env) */\n\tenv?: Record<string, string | undefined>;\n\t/** Loads an adapter package by name (defaults to a dynamic import) */\n\tload?: (pkg: string) => Promise<Adapter | undefined>;\n};\n\n/**\n * Finds an adapter to use when the site doesn't set one explicitly:\n *\n * 1. A platform build environment (e.g. `CF_PAGES` for Cloudflare Pages)\n * selects its adapter, when the adapter package is installed.\n * 2. Otherwise an installed adapter is used, preferring `adapter-node`\n * (a node server runs anywhere).\n * 3. Otherwise nothing is found, and the caller decides what to do (warn,\n * or serve on the current runtime).\n */\nexport async function resolveAdapter(\n\toptions: ResolveAdapterOptions = {},\n): Promise<AdapterResolution> {\n\tconst env = options.env ?? process.env;\n\tconst load = options.load ?? loadAdapterPackage;\n\n\t// Import each package at most once\n\tconst loaded = new Map<string, Adapter | undefined>();\n\tconst loadOnce = async (pkg: string) => {\n\t\tif (!loaded.has(pkg)) loaded.set(pkg, await load(pkg));\n\t\treturn loaded.get(pkg);\n\t};\n\n\t// 1. A deployment environment selects its adapter\n\tfor (const candidate of ADAPTER_PACKAGES) {\n\t\tfor (const marker of candidate.platforms) {\n\t\t\tif (env[marker] === undefined) continue;\n\t\t\tconst adapter = await loadOnce(candidate.pkg);\n\t\t\tif (adapter) {\n\t\t\t\treturn { adapter, pkg: candidate.pkg, reason: `${marker} detected` };\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. An installed adapter (the list is in preference order)\n\tfor (const candidate of ADAPTER_PACKAGES) {\n\t\tconst adapter = await loadOnce(candidate.pkg);\n\t\tif (adapter) {\n\t\t\treturn { adapter, pkg: candidate.pkg, reason: \"installed\" };\n\t\t}\n\t}\n\n\treturn { adapter: undefined, pkg: undefined, reason: \"none found\" };\n}\n\n/**\n * The current runtime, when preview can serve a fetch handler without an\n * adapter package. (Node's request conversion lives in adapter-node, so\n * node is deliberately not handled here.)\n */\nfunction currentRuntime(): \"bun\" | \"deno\" | undefined {\n\tconst global = globalThis as any;\n\tif (global.Bun) return \"bun\";\n\tif (global.Deno) return \"deno\";\n\treturn undefined;\n}\n\n/**\n * Serves the built output on the current runtime, for when no adapter\n * package could be found. Returns false when the runtime isn't supported.\n */\nfunction serveOnCurrentRuntime(server: Server): boolean {\n\tconst runtime = currentRuntime();\n\tif (!runtime) return false;\n\n\tconst global = globalThis as any;\n\tconst port = parseInt(process.env.PORT ?? \"7059\");\n\tconst hostname = process.env.HOST ?? \"localhost\";\n\tconsole.log(`[torpor] Serving on the ${runtime} runtime at ${hostname}:${port}`);\n\n\tif (runtime === \"bun\") {\n\t\tglobal.Bun.serve({ port, hostname, fetch: (req: Request) => server.fetch(req) });\n\t} else {\n\t\tglobal.Deno.serve({ port, hostname }, (req: Request) => server.fetch(req));\n\t}\n\treturn true;\n}\n\n// The resolution is cached per process, and the \"Using ...\" message is only\n// logged once\nlet cache: Promise<AdapterResolution> | undefined;\nlet loggedPkg: string | undefined;\n\nfunction getCachedResolution(): Promise<AdapterResolution> {\n\treturn (cache ??= resolveAdapter());\n}\n\nfunction logAdapter(pkg: string, reason: string): void {\n\tif (loggedPkg === pkg) return;\n\tloggedPkg = pkg;\n\tconsole.log(`[torpor] Using the ${pkg.replace(\"@torpor/adapter-\", \"\")} adapter (${reason})`);\n}\n\n/**\n * The adapter used when the site doesn't set one explicitly. Rather than\n * being a real adapter, it resolves one at the time it's needed (see\n * `resolveAdapter`), and as a last resort serves the built output on the\n * current runtime -- Bun and Deno can serve a fetch handler natively, while\n * node needs `@torpor/adapter-node`.\n *\n * Dev mode is not affected: with no `dev` hook, the framework's default\n * Node-runtime dev plugin is used.\n */\nconst defaultAdapter: Adapter = {\n\tasync serve(server, site) {\n\t\tconst { adapter, pkg, reason } = await getCachedResolution();\n\t\tif (adapter && pkg) {\n\t\t\tlogAdapter(pkg, reason);\n\t\t\tawait adapter.serve(server, site);\n\t\t\treturn;\n\t\t}\n\t\tif (serveOnCurrentRuntime(server)) return;\n\t\tconsole.log(\n\t\t\t\"[torpor] No adapter found -- install one (e.g. `pnpm add @torpor/adapter-node`) or set `site.adapter` in site.config.ts\",\n\t\t);\n\t},\n\tasync prebuild(site) {\n\t\tconst { adapter, pkg, reason } = await getCachedResolution();\n\t\tif (!adapter || !pkg) return;\n\t\tlogAdapter(pkg, reason);\n\t\tawait adapter.prebuild?.(site);\n\t},\n\tasync postbuild(site) {\n\t\tconst { adapter, pkg, reason } = await getCachedResolution();\n\t\tif (!adapter || !pkg) return;\n\t\tlogAdapter(pkg, reason);\n\t\tawait adapter.postbuild?.(site);\n\t},\n};\n\nexport default defaultAdapter;\n","import { promises as fs } from \"node:fs\";\nimport fpath from \"node:path\";\nimport { type Plugin, type UserConfig } from \"vite\";\nimport type Adapter from \"../types/Adapter\";\nimport type { SitemapOptions } from \"../discovery/sitemap\";\nimport type MiddlewareFunction from \"../server/types/MiddlewareFunction\";\nimport type { StandardSchemaV1 } from \"../types/StandardSchema\";\nimport type SitePlugin from \"../types/SitePlugin\";\nimport type { InlineEndPoint } from \"../types/Route\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport type Route from \"../types/Route\";\nimport type ServerEndPoint from \"../types/ServerEndPoint\";\nimport type ServerHook from \"../types/ServerHook\";\nimport {\n\tERROR_ROUTE,\n\tHOOK_ROUTE,\n\tHOOK_SERVER_ROUTE,\n\tLAYOUT_ROUTE,\n\tLAYOUT_SERVER_ROUTE,\n\tPAGE_ROUTE,\n\tPAGE_SERVER_ROUTE,\n\tRouteType,\n\tSERVER_ROUTE,\n} from \"../types/RouteType\";\nimport defaultAdapter from \"./defaultAdapter\";\n\n// 1. The user sets this up\n// 2. It is passed into serverEntry and clientEntry through a Vite plugin\n// (defined in manifest.ts)\n// 3. In those files, it is used to build a Router\n\n/**\n * The Site class contains the information necessary to setup a @torpor/build\n * site, including routes, plugins, and options.\n */\nexport default class Site {\n\troot: string;\n\troutes: Route[] = [];\n\t/**\n\t * Torpor site plugins, run when the site config is loaded (for dev and\n\t * build) and when the server starts up. Use these to register extra\n\t * routes (e.g. `openApi()` from `@torpor/build/openapi`) or add other\n\t * framework-level functionality\n\t */\n\tplugins: SitePlugin[] = [];\n\t/**\n\t * Vite plugins to add to the client and server builds\n\t */\n\tvitePlugins: Plugin[] = [];\n\t/**\n\t * The site's adapter, e.g. from `@torpor/adapter-node`,\n\t * `@torpor/adapter-cloudflare` or `@torpor/adapter-electron`. When not set,\n\t * one is resolved when it's\n\t * needed: a deployment environment (e.g. `CF_PAGES`) picks the platform's\n\t * adapter when installed, an installed adapter is used otherwise, and\n\t * preview falls back to serving on the current runtime. See\n\t * defaultAdapter.ts\n\t */\n\tadapter: Adapter = defaultAdapter;\n\t/**\n\t * Extra inputs, as absolute file paths, that will be compiled with the\n\t * output. What you do with them after that is up to you. File paths ending\n\t * with server.ts/js will only be compiled with the server output\n\t */\n\tinputs: string[] = [];\n\t/**\n\t * Vite config options to merge with the standard Torpor build options.\n\t * `plugins` and `build.rollupOptions.input` will be overridden, so you\n\t * should set `Site.vitePlugins` and `Site.inputs` instead. Other options may be\n\t * overridden or incompatible, so experimentation may be required\n\t */\n\tviteConfig?: UserConfig;\n\t/**\n\t * The path to the site.config file. Set automatically by the CLI; used by\n\t * the manifest plugin to import inline endpoints for server builds.\n\t */\n\tconfigFile?: string;\n\t/**\n\t * A state map for plugins to store their configuration and state on the\n\t * site, so that framework internals (e.g. the manifest plugin) and other\n\t * plugins can read them back:\n\t *\n\t * ```ts\n\t * const myKey = Symbol.for(\"my-plugin\");\n\t * site.pluginState.set(myKey, options);\n\t * ```\n\t *\n\t * Prefer `Symbol.for(\"...\")` keys (or plain strings) over `Symbol()`,\n\t * because the user's config file and framework internals can be loaded as\n\t * separate module instances -- registry symbols are shared across\n\t * instances, `Symbol()` ones are not.\n\t */\n\tpluginState: Map<PropertyKey, unknown> = new Map();\n\t/**\n\t * The path the site is mounted under, e.g. \"/app\". Incoming server\n\t * requests that don't carry it are not found; everything the framework\n\t * generates (HTML attributes, redirect locations, paths from `route()`)\n\t * gets it added. Server code and markup stay base-free.\n\t */\n\tbasePath: string = \"\";\n\t/**\n\t * Wraps client-side navigations in the View Transitions API -- where the\n\t * browser supports it -- so pages cross-fade by default and can be\n\t * animated with `::view-transition-old/new` CSS. Opt-in: off unless set\n\t * to `true`. The initial hydration never transitions.\n\t */\n\tviewTransitions?: boolean;\n\t/**\n\t * Prerendering, applied to every page route that doesn't have (or\n\t * inherit) a `prerender` flag of its own. `true` prerenders everything;\n\t * a `Record<string, boolean>` maps route paths -- exact (`/about`) or\n\t * prefix (`/blog/**` for a path and everything below it) -- to flags,\n\t * with the most specific matching key winning. Route endpoint and\n\t * layout flags always take precedence over this.\n\t */\n\tprerender?: boolean | Record<string, boolean>;\n\t/**\n\t * The site's absolute origin, e.g. \"https://example.com\". Required for\n\t * a build-time sitemap (see `sitemap`), since there is no request to\n\t * take the host from.\n\t */\n\torigin?: string;\n\t/**\n\t * Global middleware, run for every request before routing -- including\n\t * requests that won't match a route (maintenance mode, legacy url\n\t * redirects, etc). Each middleware's `enter` runs in order and may\n\t * return a Response to short-circuit the request; `exit` hooks run in\n\t * reverse after the handler. Middleware see the raw request url.\n\t *\n\t * Route middleware (scoped to a single route) is set on the route's\n\t * server endpoint instead, e.g. `middleware: [...]` in\n\t * `+page.server.ts` or `+server.ts`.\n\t */\n\tmiddleware: MiddlewareFunction[] = [];\n\t/**\n\t * Writes a sitemap.xml listing the prerendered pages at build time\n\t * (see `prerender`). Pass a custom path (e.g. \"/blog/sitemap.xml\") or\n\t * `true` for \"/sitemap.xml\"; requires `origin` to be set.\n\t *\n\t * For a runtime sitemap -- one that reads the database and keeps up with\n\t * content that isn't prerendered -- pass a config instead, e.g.\n\t * `sitemap = { get: async () => [\"/posts/a\", \"/posts/b\"] }`, which serves\n\t * the XML live at that path (customizable with `path`).\n\t */\n\tsitemap?: SitemapOptions | boolean | string;\n\t/**\n\t * A Standard Schema (zod, valibot, arktype, etc) used to validate (and\n\t * parse) the server environment. When set, `env()` from\n\t * `@torpor/build/env` checks the environment on first use per request and\n\t * throws with the schema's issues -- so a missing or invalid key fails\n\t * immediately instead of passing `undefined` along. The schema must\n\t * validate synchronously.\n\t */\n\tenv?: StandardSchemaV1<unknown, TorporEnv>;\n\t/**\n\t * Inline endpoints keyed by `\"path:type\"`. Populated by `addRoute` when\n\t * the user passes an inline endpoint object instead of a file path.\n\t */\n\tinlineEndPoints: Record<string, InlineEndPoint> = {};\n\n\tconstructor() {\n\t\tthis.root = process.cwd();\n\t}\n\n\t/**\n\t * Adds a folder of routes, based on the names of the files in the folder\n\t * @param folder The folder containing the routes\n\t * @param subFolder If a subFolder is set, all routes in the folder will be\n\t * placed under the subFolder, and only hooks, layouts etc will be used if\n\t * they have the same subFolder. This can be used to create e.g. an `api`\n\t * set of routes with their own hook for authorization from various clients\n\t */\n\tasync addRouteFolder(folder: string, subFolder?: string): Promise<void> {\n\t\tconst routeFolder = fpath.join(this.root, folder);\n\t\tconst routeFiles = await fs.readdir(routeFolder, { recursive: true });\n\t\tif (subFolder !== undefined && !subFolder.startsWith(\"/\")) {\n\t\t\tsubFolder = \"/\" + subFolder;\n\t\t}\n\t\tfor (let file of routeFiles) {\n\t\t\t// The file must start with `+` or `_` and end with `.js` or `.ts`\n\t\t\tif (/^(\\+|_).+(\\.js|\\.ts)$/.test(fpath.basename(file))) {\n\t\t\t\tlet routePath = this.#routePath(file);\n\t\t\t\tif (subFolder !== undefined) {\n\t\t\t\t\troutePath = subFolder + routePath;\n\t\t\t\t}\n\t\t\t\tlet type = this.#routeType(file);\n\t\t\t\tfile = fpath.relative(this.root, fpath.resolve(routeFolder, file));\n\t\t\t\tthis.routes.push({\n\t\t\t\t\tpath: routePath,\n\t\t\t\t\tfile,\n\t\t\t\t\ttype,\n\t\t\t\t\tsubFolder: subFolder,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.#sortRoutes();\n\t}\n\n\t/**\n\t * Adds a file route, which can have a standard name (e.g. +page.ts) or any\n\t * name if you pass the type\n\t * @param path The route path\n\t * @param file The file\n\t * @param type The file type (if the file name is non-standard)\n\t */\n\taddRouteFile(\n\t\tpath: string,\n\t\tfile: string,\n\t\ttype?:\n\t\t\t| \"+page\"\n\t\t\t| \"+page.server\"\n\t\t\t| \"+server\"\n\t\t\t| \"_layout\"\n\t\t\t| \"_layout.server\"\n\t\t\t| \"_hook\"\n\t\t\t| \"_hook.server\"\n\t\t\t| \"_error\",\n\t\tsubFolder?: string,\n\t): void {\n\t\tlet routePath = this.#routePath(fpath.join(path, type ?? fpath.basename(file)));\n\t\tlet routeType = type ? this.#routeTypeFromString(type) : this.#routeType(file);\n\t\tif (subFolder !== undefined && !subFolder.startsWith(\"/\")) {\n\t\t\tsubFolder = \"/\" + subFolder;\n\t\t}\n\t\tthis.routes.push({\n\t\t\tpath: routePath,\n\t\t\tfile,\n\t\t\ttype: routeType,\n\t\t\tsubFolder: subFolder,\n\t\t});\n\t\tthis.#sortRoutes();\n\t}\n\n\t#routePath(file: string): string {\n\t\tlet routePath = file\n\t\t\t.replace(/^\\//, \"\")\n\t\t\t.replace(/(\\.ts|\\.js)$/, \"\")\n\t\t\t.replace(/_hook.server$/, \"_hook/~server\")\n\t\t\t.replace(/_layout.server$/, \"_layout/~server\")\n\t\t\t.replace(/\\+page.server$/, \"~server\")\n\t\t\t.replace(/\\+server$/, \"\")\n\t\t\t.replace(/\\+page$/, \"\")\n\t\t\t.replace(/\\/$/, \"\");\n\t\treturn routePath.length > 0 ? `/${routePath}` : \"/\";\n\t}\n\n\t#routeType(file: string): RouteType {\n\t\tlet routePath = file.replace(/^\\//, \"\").replace(/(\\.ts|\\.js)$/, \"\");\n\t\tif (routePath.endsWith(\"+page\")) {\n\t\t\treturn PAGE_ROUTE;\n\t\t} else if (routePath.endsWith(\"+page.server\")) {\n\t\t\treturn PAGE_SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"+server\")) {\n\t\t\treturn SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"_layout\")) {\n\t\t\treturn LAYOUT_ROUTE;\n\t\t} else if (routePath.endsWith(\"_layout.server\")) {\n\t\t\treturn LAYOUT_SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"_hook\")) {\n\t\t\treturn HOOK_ROUTE;\n\t\t} else if (routePath.endsWith(\"_hook.server\")) {\n\t\t\treturn HOOK_SERVER_ROUTE;\n\t\t} else if (routePath.endsWith(\"_error\")) {\n\t\t\treturn ERROR_ROUTE;\n\t\t}\n\t\treturn -1;\n\t}\n\n\t#routeTypeFromString(\n\t\ttype:\n\t\t\t| \"+page\"\n\t\t\t| \"+page.server\"\n\t\t\t| \"+server\"\n\t\t\t| \"_layout\"\n\t\t\t| \"_layout.server\"\n\t\t\t| \"_hook\"\n\t\t\t| \"_hook.server\"\n\t\t\t| \"_error\",\n\t) {\n\t\tswitch (type) {\n\t\t\tcase \"+page\":\n\t\t\t\treturn PAGE_ROUTE;\n\t\t\tcase \"+page.server\":\n\t\t\t\treturn PAGE_SERVER_ROUTE;\n\t\t\tcase \"+server\":\n\t\t\t\treturn SERVER_ROUTE;\n\t\t\tcase \"_layout\":\n\t\t\t\treturn LAYOUT_ROUTE;\n\t\t\tcase \"_layout.server\":\n\t\t\t\treturn LAYOUT_SERVER_ROUTE;\n\t\t\tcase \"_hook\":\n\t\t\t\treturn HOOK_ROUTE;\n\t\t\tcase \"_hook.server\":\n\t\t\t\treturn HOOK_SERVER_ROUTE;\n\t\t\tcase \"_error\":\n\t\t\t\treturn ERROR_ROUTE;\n\t\t}\n\t}\n\n\t/**\n\t * Adds a route in code, without needing the standard file naming\n\t * conventions (+page.ts, +page.server.ts, etc). This is useful for small\n\t * sites with one or two pages.\n\t *\n\t * Any combination of options may be provided; each maps to the\n\t * corresponding file-route type:\n\t *\n\t * - `page` — a `+page` (`.torp` component or `.ts`/`.js` `PageEndPoint`)\n\t * - `pageServer` — a `+page.server` (`PageServerEndPoint`, file or inline)\n\t * - `server` — a `+server` HTTP endpoint (`ServerEndPoint`, file or inline)\n\t * - `layout` — a `_layout` (`.torp` component or `.ts`/`.js` `PageEndPoint`)\n\t * - `layoutServer` — a `_layout.server` (`PageServerEndPoint`, file or inline)\n\t * - `hookServer` — a `_hook.server` (`ServerHook`, file or inline)\n\t * - `error` — an `_error` page (`.torp` component or `.ts`/`.js` `PageEndPoint`)\n\t *\n\t * Options that need a component (`page`, `layout`, `error`) must be file\n\t * paths so the component is included in the client bundle. Server-only\n\t * options (`pageServer`, `server`, `layoutServer`, `hookServer`) accept\n\t * either a file path or an inline object; inline server code is kept out\n\t * of the client bundle automatically.\n\t *\n\t * @example\n\t * // A page with an inline server action\n\t * site.addRoute(\"/\", {\n\t * page: \"./src/Counter.torp\",\n\t * pageServer: { actions: { set: async ({ request }) => ok() } },\n\t * });\n\t * @example\n\t * // A JSON API endpoint\n\t * site.addRoute(\"/api/time\", {\n\t * server: { get: async () => ok({ time: Date.now() }) },\n\t * });\n\t * @example\n\t * // A root layout, server hook, and error page\n\t * site.addRoute(\"/\", {\n\t * layout: \"./src/Layout.torp\",\n\t * hookServer: { enter: async (event) => {} },\n\t * error: \"./src/ErrorPage.torp\",\n\t * });\n\t *\n\t * @param path The route path, e.g. `/` or `/about`\n\t * @param options File paths and/or inline endpoints for the route\n\t * @param subFolder An optional subFolder for grouping routes\n\t */\n\taddRoute(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tpage?: string;\n\t\t\tpageServer?: string | PageServerEndPoint;\n\t\t\tserver?: string | ServerEndPoint;\n\t\t\tlayout?: string;\n\t\t\tlayoutServer?: string | PageServerEndPoint;\n\t\t\thookServer?: string | ServerHook;\n\t\t\terror?: string;\n\t\t},\n\t\tsubFolder?: string,\n\t): void {\n\t\tlet normalizedSubFolder = subFolder;\n\t\tif (normalizedSubFolder !== undefined && !normalizedSubFolder.startsWith(\"/\")) {\n\t\t\tnormalizedSubFolder = \"/\" + normalizedSubFolder;\n\t\t}\n\n\t\t// The base path with any trailing slash stripped, so that we can\n\t\t// append \"/_layout\", \"/~server\" etc. For the root \"/\" this is \"\".\n\t\tlet base = path.replace(/\\/$/, \"\");\n\n\t\tif (options.page !== undefined) {\n\t\t\tthis.#pushRoute(path, PAGE_ROUTE, options.page, normalizedSubFolder);\n\t\t}\n\t\tif (options.pageServer !== undefined) {\n\t\t\tthis.#pushRoute(\n\t\t\t\t`${base}/~server`,\n\t\t\t\tPAGE_SERVER_ROUTE,\n\t\t\t\toptions.pageServer,\n\t\t\t\tnormalizedSubFolder,\n\t\t\t);\n\t\t}\n\t\tif (options.server !== undefined) {\n\t\t\tthis.#pushRoute(path, SERVER_ROUTE, options.server, normalizedSubFolder);\n\t\t}\n\t\tif (options.layout !== undefined) {\n\t\t\tthis.#pushRoute(`${base}/_layout`, LAYOUT_ROUTE, options.layout, normalizedSubFolder);\n\t\t}\n\t\tif (options.layoutServer !== undefined) {\n\t\t\tthis.#pushRoute(\n\t\t\t\t`${base}/_layout/~server`,\n\t\t\t\tLAYOUT_SERVER_ROUTE,\n\t\t\t\toptions.layoutServer,\n\t\t\t\tnormalizedSubFolder,\n\t\t\t);\n\t\t}\n\t\tif (options.hookServer !== undefined) {\n\t\t\tthis.#pushRoute(\n\t\t\t\t`${base}/_hook/~server`,\n\t\t\t\tHOOK_SERVER_ROUTE,\n\t\t\t\toptions.hookServer,\n\t\t\t\tnormalizedSubFolder,\n\t\t\t);\n\t\t}\n\t\tif (options.error !== undefined) {\n\t\t\tthis.#pushRoute(`${base}/_error`, ERROR_ROUTE, options.error, normalizedSubFolder);\n\t\t}\n\n\t\tthis.#sortRoutes();\n\t}\n\n\t/**\n\t * Pushes a single route entry, handling both file-path and inline-endpoint\n\t * targets. File paths are resolved relative to the site root; inline\n\t * endpoints are also stored in `inlineEndPoints` keyed by `\"path:type\"`\n\t * so the manifest plugin can resolve them at build time.\n\t *\n\t * If a route with the same path and type was already added, it is\n\t * replaced, so that running a plugin twice (config load and server\n\t * startup) doesn't add duplicate routes.\n\t */\n\t#pushRoute(\n\t\troutePath: string,\n\t\ttype: RouteType,\n\t\ttarget: string | InlineEndPoint,\n\t\tsubFolder?: string,\n\t): void {\n\t\tconst index = this.routes.findIndex((r) => r.path === routePath && r.type === type);\n\t\tif (typeof target === \"string\") {\n\t\t\tlet file = fpath.relative(this.root, fpath.resolve(this.root, target));\n\t\t\tif (index >= 0) {\n\t\t\t\tthis.routes[index] = { path: routePath, file, type, subFolder };\n\t\t\t} else {\n\t\t\t\tthis.routes.push({ path: routePath, file, type, subFolder });\n\t\t\t}\n\t\t} else {\n\t\t\tlet key = `${routePath}:${type}`;\n\t\t\tthis.inlineEndPoints[key] = target;\n\t\t\tif (index >= 0) {\n\t\t\t\tthis.routes[index] = { path: routePath, endPoint: target, type, subFolder };\n\t\t\t} else {\n\t\t\t\tthis.routes.push({ path: routePath, endPoint: target, type, subFolder });\n\t\t\t}\n\t\t}\n\t}\n\n\t#sortRoutes() {\n\t\tthis.routes = this.routes.sort((a, b) => {\n\t\t\t// Sort [param]s after paths\n\t\t\t// There might be a quicker/easier way to do this\n\t\t\tfor (let i = 0; i < Math.min(a.path.length, b.path.length); i++) {\n\t\t\t\tif (a.path[i] === \"[\" && b.path[i] !== \"[\") {\n\t\t\t\t\treturn 1;\n\t\t\t\t} else if (b.path[i] === \"[\" && a.path[i] !== \"[\") {\n\t\t\t\t\treturn -1;\n\t\t\t\t} else if (a.path[i] === b.path[i]) {\n\t\t\t\t\t// Keep going...\n\t\t\t\t} else {\n\t\t\t\t\treturn a.path[i].localeCompare(b.path[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn a.path.length - b.path.length;\n\t\t});\n\t}\n}\n"],"mappings":"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"}
|