@torpor/build 0.1.33 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/{Server-BC_dixZQ.d.cts → Server-CNZWh5ca.d.cts} +2 -2
  3. package/dist/{Server-BC_dixZQ.d.ts → Server-CNZWh5ca.d.ts} +2 -2
  4. package/dist/{Site-4tGiw2-I.d.ts → Site-C2020GV_.d.ts} +1 -1
  5. package/dist/{Site-BXX-S541.d.cts → Site-DQojGpE-.d.cts} +1 -1
  6. package/dist/bin/index.cjs +30 -7
  7. package/dist/bin/index.cjs.map +1 -1
  8. package/dist/bin/index.js +3 -3
  9. package/dist/{chunk-LO7P2LJE.js → chunk-CZHJQCPI.js} +11 -3
  10. package/dist/chunk-CZHJQCPI.js.map +1 -0
  11. package/dist/{chunk-KKFND4HK.js → chunk-TDOXEXG6.js} +1 -1
  12. package/dist/chunk-TDOXEXG6.js.map +1 -0
  13. package/dist/{chunk-RHDSM5GS.js → chunk-YRBEYKQM.js} +23 -8
  14. package/dist/chunk-YRBEYKQM.js.map +1 -0
  15. package/dist/index.d.cts +6 -6
  16. package/dist/index.d.ts +6 -6
  17. package/dist/metafile-cjs.json +1 -1
  18. package/dist/metafile-esm.json +1 -1
  19. package/dist/run.cjs +30 -7
  20. package/dist/run.cjs.map +1 -1
  21. package/dist/run.d.cts +2 -2
  22. package/dist/run.d.ts +2 -2
  23. package/dist/run.js +3 -3
  24. package/dist/server.cjs +9 -1
  25. package/dist/server.cjs.map +1 -1
  26. package/dist/server.d.cts +2 -2
  27. package/dist/server.d.ts +2 -2
  28. package/dist/server.js +2 -2
  29. package/dist/test.cjs +3 -3
  30. package/dist/test.cjs.map +1 -1
  31. package/dist/test.d.cts +2 -2
  32. package/dist/test.d.ts +2 -2
  33. package/dist/test.js +4 -4
  34. package/dist/test.js.map +1 -1
  35. package/package.json +6 -6
  36. package/src/server/ServerEvent.ts +1 -1
  37. package/src/server/connect/bufferToArrayBuffer.ts +8 -0
  38. package/src/server/connect/connectMiddleware.ts +2 -1
  39. package/src/server/types/ServerFunction.ts +3 -1
  40. package/src/site/clientEntry.ts +51 -30
  41. package/src/site/prepareTemplate.ts +22 -10
  42. package/src/site/runPreview.ts +11 -1
  43. package/src/site/serverEntry.ts +23 -10
  44. package/src/test/runTest.ts +3 -3
  45. package/src/types/PageEndPoint.ts +3 -1
  46. package/src/types/PageServerAction.ts +1 -1
  47. package/src/types/PageServerLoad.ts +1 -1
  48. package/src/types/ServerRequest.ts +1 -1
  49. package/dist/chunk-KKFND4HK.js.map +0 -1
  50. package/dist/chunk-LO7P2LJE.js.map +0 -1
  51. package/dist/chunk-RHDSM5GS.js.map +0 -1
package/dist/test.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/test.ts","../src/test/runTest.ts","../src/response/response.ts","../src/response/notFound.ts","../src/response/ok.ts","../src/response/transfer.ts","../src/response/seeOther.ts","../src/server/CookieHelper.ts","../src/server/HeaderHelper.ts","../src/server/ServerEvent.ts","../src/site/pathToRegex.ts","../src/site/Router.ts","../src/state/$page.ts","../src/types/RouteType.ts"],"sourcesContent":["import runTest from \"./test/runTest\";\n\nexport { runTest };\n","import { type ServerComponent, type ServerSlotRender } from \"@torpor/view\";\nimport path from \"node:path\";\nimport notFound from \"../response/notFound.ts\";\nimport ok from \"../response/ok.ts\";\nimport seeOther from \"../response/seeOther.ts\";\nimport ServerEvent from \"../server/ServerEvent.ts\";\nimport Router from \"../site/Router.ts\";\nimport Site from \"../site/Site\";\nimport $page from \"../state/$page.ts\";\nimport type PageEndPoint from \"../types/PageEndPoint.ts\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint.ts\";\nimport RouteHandler from \"../types/RouteHandler.ts\";\nimport {\n\tHOOK_ROUTE,\n\tHOOK_SERVER_ROUTE,\n\tLAYOUT_ROUTE,\n\tLAYOUT_SERVER_ROUTE,\n\tPAGE_ROUTE,\n\tPAGE_SERVER_ROUTE,\n\tSERVER_ROUTE,\n} from \"../types/RouteType\";\nimport type ServerEndPoint from \"../types/ServerEndPoint.ts\";\nimport type ServerHook from \"../types/ServerHook.ts\";\nimport type ServerLoadEvent from \"../types/ServerLoadEvent.ts\";\n\n/**\n * Runs a site route in test mode\n * @param site The site configuration, with routing configured\n * @param route The route path e.g. `/home`\n * @param ev An optional ServerEvent, which you can use to set test cookies and headers\n * @returns An HTTP response containing the requested data, HTML, or error status\n */\nexport default async function runTest(\n\tsite: Site,\n\troute: string,\n\tev?: ServerEvent,\n): Promise<Response> {\n\tif (!route.startsWith(\"/\")) route += \"/\";\n\tif (!ev) {\n\t\tconst req = new Request(`http://localhost${route}`);\n\t\tev = new ServerEvent(req);\n\t}\n\n\t// Build the router from the Site object created by the user\n\tconst router = new Router();\n\trouter.addPages(\n\t\tsite.routes.map((r) => ({\n\t\t\tpath: r.path,\n\t\t\ttype: r.type,\n\t\t\tendPoint: () => import(/* @vite-ignore */ path.join(site.root, r.file)),\n\t\t})),\n\t);\n\n\treturn await load(router, ev, \"%COMPONENT_HTML%\");\n}\n\n// HACK: This is a copy of serverEntry\n// TODO: Make it shared instead!\nasync function load(router: Router, ev: ServerEvent, template: string): Promise<Response> {\n\t//const url = new URL(`http://${process.env.HOST ?? \"localhost\"}${ev.request.url}`);\n\tconst url = new URL(ev.request.url);\n\tconst path = url.pathname;\n\tconst query = url.searchParams;\n\n\t//console.log(`handling ${ev.request.method} for '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\tconst route = router.match(path, query);\n\tif (!route) {\n\t\treturn notFound();\n\t}\n\n\t// Update $page before building the components\n\t// TODO: Find somewhere better to put this\n\t$page.url = url;\n\n\tconst handler = route.handler;\n\tconst params = route.params || {};\n\n\t// TODO: Hit the server hook out here\n\t// We could maybe set data loading up as middleware on a route??????\n\n\tswitch (handler.type) {\n\t\tcase PAGE_ROUTE:\n\t\tcase LAYOUT_ROUTE: {\n\t\t\t// It's a /+page.ts or /_layout.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadView(ev, url, handler, params, template);\n\t\t\t} else if (ev.request.method === \"POST\") {\n\t\t\t\treturn await runAction(ev, url, handler, params, query);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase PAGE_SERVER_ROUTE:\n\t\tcase LAYOUT_SERVER_ROUTE: {\n\t\t\t// It's a /+page.server.ts or /_layout.server.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadData(ev, url, handler, \"load\", params);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase SERVER_ROUTE: {\n\t\t\t// It's a /+server.ts endpoint\n\t\t\tconst functionName = ev.request.method.toLowerCase().replace(\"delete\", \"del\");\n\t\t\treturn await loadData(ev, url, handler, functionName, params);\n\t\t}\n\t\tcase HOOK_ROUTE:\n\t\tcase HOOK_SERVER_ROUTE: {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadData(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tfunctionName: string,\n\tparams: Record<string, string>,\n) {\n\tconst serverEndPoint: ServerEndPoint | undefined = (await handler.endPoint()).default;\n\tif (serverEndPoint && serverEndPoint[functionName]) {\n\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\tif (handler.serverHook) {\n\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\tif (serverHook?.handle) {\n\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t}\n\t\t}\n\n\t\tconst result = await serverEndPoint[functionName](serverParams);\n\n\t\t// If there was no response returned from load (such as errors or a\n\t\t// redirect), send an ok response\n\t\treturn result || ok();\n\t}\n\n\treturn notFound();\n}\n\nasync function loadView(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\ttemplate: string,\n) {\n\t// There must be a client endpoint with a component\n\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\tif (!clientEndPoint?.component) {\n\t\treturn notFound();\n\t}\n\n\t// There may be a server endpoint\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t// Maybe hit the server hook\n\tconst serverParams = buildServerParams(ev, url, params);\n\tif (handler.serverHook) {\n\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\tif (serverHook?.handle) {\n\t\t\tawait serverHook.handle(serverParams);\n\t\t}\n\t}\n\n\t// Pass the data into $props\n\t// TODO: Promise.all\n\t// NOTE: We're loading data from top to bottom, overriding as we go, and I'm not sure if this is the best way to go\n\tlet data = {};\n\tif (handler.layouts) {\n\t\tfor (let layout of handler.layouts) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;\n\t\t\tconst layoutServerEndPoint: PageServerEndPoint | undefined =\n\t\t\t\tlayout.serverEndPoint && (await layout.serverEndPoint())?.default;\n\t\t\tconst layoutResponse = await loadClientAndServerData(\n\t\t\t\turl,\n\t\t\t\tparams,\n\t\t\t\tserverParams,\n\t\t\t\tdata,\n\t\t\t\tlayoutEndPoint,\n\t\t\t\tlayoutServerEndPoint,\n\t\t\t);\n\t\t\tif (layoutResponse?.ok === false) {\n\t\t\t\treturn layoutResponse;\n\t\t\t}\n\t\t}\n\t}\n\tlet endPointResponse = await loadClientAndServerData(\n\t\turl,\n\t\tparams,\n\t\tserverParams,\n\t\tdata,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\tif (endPointResponse?.ok === false) {\n\t\treturn endPointResponse;\n\t}\n\tlet $props: Record<string, any> = { data };\n\n\t// If there are layouts, work our way upwards, pushing each component into\n\t// the default slot of its parent\n\t// TODO: Also handle layout server data\n\t// TODO: There's probably a nicer way to do this with reducers or something\n\t// TODO: Re-use parent layouts\n\tlet component = clientEndPoint.component as ServerComponent;\n\tlet slots: Record<string, ServerSlotRender> | undefined = undefined;\n\tif (handler.layouts) {\n\t\tlet slotFunctions: ServerSlotRender[] = [];\n\t\tslotFunctions[handler.layouts.length] = (_, context) =>\n\t\t\t(clientEndPoint.component as ServerComponent)($props, context);\n\t\tfor (let i = handler.layouts.length - 1; i >= 0; i--) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())\n\t\t\t\t?.default;\n\t\t\tif (layoutEndPoint?.component) {\n\t\t\t\tif (i === 0) {\n\t\t\t\t\tcomponent = layoutEndPoint.component as ServerComponent;\n\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t} else {\n\t\t\t\t\tslotFunctions[i] = (_, context) =>\n\t\t\t\t\t\t(layoutEndPoint.component as ServerComponent)($props, context, {\n\t\t\t\t\t\t\t_: slotFunctions[i + 1],\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet html;\n\ttry {\n\t\thtml = component($props, undefined, slots);\n\t\thtml = template.replace(\"%COMPONENT_HTML%\", html);\n\t} catch (error) {\n\t\t// TODO: Show a proper Error component\n\t\thtml = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\tconsole.log(error);\n\t}\n\n\treturn new Response(html, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"text/html\",\n\t\t},\n\t});\n}\n\nasync function runAction(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\tquery: URLSearchParams,\n) {\n\tconst actionName = (Array.from(query.keys())[0] || \"default\").replace(/^\\//, \"\");\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint()).default;\n\tif (serverEndPoint?.actions) {\n\t\tconst action = serverEndPoint.actions[actionName];\n\t\tif (action) {\n\t\t\t// TODO: form.errors etc\n\t\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\t\tif (handler.serverHook) {\n\t\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\t\tif (serverHook?.handle) {\n\t\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await action(serverParams); // <--\n\n\t\t\t// If there was no response returned from the action (such as errors\n\t\t\t// or a redirect), reload the page by redirecting\n\t\t\treturn result || seeOther(url.pathname);\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadClientAndServerData(\n\turl: URL,\n\tparams: Record<string, string>,\n\tserverParams: ServerLoadEvent,\n\tdata: Record<string, any>,\n\tclientEndPoint?: PageEndPoint,\n\tserverEndPoint?: PageServerEndPoint,\n) {\n\tif (clientEndPoint?.load) {\n\t\tconst clientParams = buildClientParams(url, data, params);\n\t\tconst clientResponse = await clientEndPoint.load(clientParams);\n\t\tif (clientResponse) {\n\t\t\tif (clientResponse.ok) {\n\t\t\t\tif (clientResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await clientResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn clientResponse;\n\t\t\t}\n\t\t}\n\t}\n\tif (serverEndPoint?.load) {\n\t\tconst serverResponse = await serverEndPoint.load(serverParams);\n\t\tif (serverResponse) {\n\t\t\tif (serverResponse.ok) {\n\t\t\t\tif (serverResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await serverResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn serverResponse;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tdata,\n\t};\n}\n\nfunction buildServerParams(\n\tev: ServerEvent,\n\turl: URL,\n\tparams: Record<string, string>,\n): ServerLoadEvent {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tappData: {},\n\t\trequest: ev.request,\n\t\tcookies: ev.cookies,\n\t\theaders: ev.headers,\n\t\tadapter: ev.adapter,\n\t};\n}\n","/**\n * Creates a response with the supplied status code and optional body.\n *\n * If the body is an object, it will be converted to JSON and the content-type\n * header set to \"application/json\". Otherwise, if the body is a string, the\n * content-type header will be set to \"text/plain\".\n */\nexport default function response(status: number, body?: object | string): Response {\n\tlet headers: Record<string, string> | undefined = undefined;\n\tif (body) {\n\t\tif (typeof body === \"object\") {\n\t\t\theaders = { \"content-type\": \"application/json\" };\n\t\t\tbody = JSON.stringify(body);\n\t\t} else {\n\t\t\theaders = { \"content-type\": \"text/plain\" };\n\t\t}\n\t}\n\treturn new Response(body, {\n\t\tstatus,\n\t\theaders,\n\t});\n}\n","import response from \"./response\";\n\n/**\n * 404 Not Found\n *\n * The server cannot find the requested resource. In the browser, this means the\n * URL is not recognized. In an API, this can also mean that the endpoint is\n * valid but the resource itself does not exist. Servers may also send this\n * response instead of 403 Forbidden to hide the existence of a resource from an\n * unauthorized client. This response code is probably the most well known due\n * to its frequent occurrence on the web.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\n */\nexport default function notFound(body?: object | string): Response {\n\treturn response(404, body ?? \"Not found\");\n}\n","import response from \"./response\";\n\n/**\n * 200 OK\n *\n * The request succeeded. The result meaning of \"success\" depends on the HTTP\n * method:\n * - GET: The resource has been fetched and transmitted in the message body.\n * - HEAD: The representation headers are included in the response without any\n * message body.\n * - PUT or POST: The resource describing the result of the action is\n * transmitted in the message body.\n * - TRACE: The message body contains the request message as received by the\n * server.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200\n */\nexport default function ok(body?: object | string): Response {\n\treturn response(200, body);\n}\n","/**\n * Creates a redirect response with the supplied status code.\n */\nexport default function transfer(\n\tstatus: 300 | 301 | 302 | 303 | 304 | 307 | 308,\n\tlocation: string,\n): Response {\n\treturn new Response(null, {\n\t\tstatus,\n\t\theaders: {\n\t\t\tlocation,\n\t\t},\n\t});\n}\n","import transfer from \"./transfer\";\n\n/**\n * 303 See Other\n *\n * The server sent this response to direct the client to get the requested\n * resource at another URI with a GET request.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303\n */\nexport default function seeOther(location: string): Response {\n\treturn transfer(303, location);\n}\n","import { SerializeOptions, parse, serialize } from \"cookie\";\n\nexport default class CookieHelper {\n\trequest: Request;\n\tcookies: Map<string, string> = new Map();\n\n\tconstructor(request: Request) {\n\t\tthis.request = request;\n\t}\n\n\tget(name: string): string | undefined {\n\t\t// If it's been set, return the value from the map\n\t\tif (this.cookies.has(name)) {\n\t\t\treturn parse(this.cookies.get(name)!)[name];\n\t\t}\n\t\t// Otherwise, return the value from the `cookie` header\n\t\tconst header = this.request.headers.get(\"cookie\");\n\t\treturn parse(header ?? \"\")[name];\n\t}\n\n\tset(name: string, value: string, options?: SerializeOptions): void {\n\t\t// Set a cookie header with some default values for security\n\t\toptions ??= {};\n\t\toptions.httpOnly ??= true;\n\t\toptions.secure ??= true;\n\t\toptions.sameSite ??= \"lax\";\n\t\tthis.cookies.set(name, serialize(name, value, options));\n\t}\n\n\tdelete(name: string, options?: SerializeOptions): void {\n\t\t// Set a cookie header with an immediate expiry date\n\t\toptions ??= {};\n\t\toptions.expires = new Date(0);\n\t\tthis.cookies.set(name, serialize(name, \"\", options));\n\t}\n}\n","export default class HeaderHelper {\n\trequest: Request;\n\theaders: Map<string, string> = new Map();\n\n\tconstructor(request: Request) {\n\t\tthis.request = request;\n\t}\n\n\tget(name: string): string | undefined {\n\t\t// If it's been set, return the updated value\n\t\tif (this.headers.has(name)) {\n\t\t\treturn this.headers.get(name);\n\t\t}\n\t\t// Otherwise, return the value from the header\n\t\treturn this.request.headers.get(name) ?? undefined;\n\t}\n\n\tset(name: string, value: string): void {\n\t\tthis.headers.set(name, value);\n\t}\n\n\tdelete(name: string): void {\n\t\tthis.headers.delete(name);\n\t}\n}\n","import CookieHelper from \"./CookieHelper\";\nimport HeaderHelper from \"./HeaderHelper\";\n\nexport default class ServerEvent {\n\trequest: Request;\n\tresponse?: Response;\n\t// TODO: Should we cast types??\n\tparams?: Record<string, string>;\n\n\tadapter: any;\n\n\tcookies: CookieHelper;\n\theaders: HeaderHelper;\n\n\tconstructor(request: Request, params?: Record<string, string>) {\n\t\tthis.request = request;\n\t\tthis.params = params;\n\t\tthis.cookies = new CookieHelper(request);\n\t\tthis.headers = new HeaderHelper(request);\n\n\t\t// Adapters may have added the `adapter` property to globalThis\n\t\t// @ts-ignore\n\t\tthis.adapter = globalThis.adapter;\n\t}\n\n\taddHeaders(): void {\n\t\tif (!this.response) {\n\t\t\tthrow new Error(\"Response not created yet\");\n\t\t}\n\n\t\tfor (let cookie of this.cookies.cookies.values()) {\n\t\t\tthis.response.headers.append(\"set-cookie\", cookie);\n\t\t}\n\n\t\tfor (let header of this.headers.headers.entries()) {\n\t\t\tthis.response.headers.append(header[0], header[1]);\n\t\t}\n\t}\n}\n","export default function pathToRegex(path: string): RegExp {\n\tconst pattern =\n\t\tpath\n\t\t\t.split(\"/\")\n\t\t\t// Replace `[slug]` with a grouped match so we can pull out params\n\t\t\t.map((p) => {\n\t\t\t\treturn p.replace(/\\[([^\\/]+?)\\]/, \"(?<$1>[^\\\\/]+?)\");\n\t\t\t})\n\t\t\t.join(\"\\\\/\")\n\t\t\t// Replace globs with regex syntax\n\t\t\t.replaceAll(\"*\", \".*\") +\n\t\t// Ignore trailing slashes -- `/path` should be treated the same as `/path/`\n\t\t\"/*\";\n\treturn new RegExp(`^${pattern}/*$`);\n}\n","import type LayoutHandler from \"../types/LayoutHandler\";\nimport type ManifestRoute from \"../types/ManifestRoute\";\nimport type RouteHandler from \"../types/RouteHandler\";\nimport pathToRegex from \"./pathToRegex\";\n\n/**\n * A router that handles file routes with layouts, hooks, etc.\n */\nexport default class Router {\n\troutes: Route[] = [];\n\n\tconstructor() {}\n\n\t//add(path: string, file: string) {\n\t//\t// TODO: Call the specific addX method based on the file path\n\t//\tthis.addPage(route, file);\n\t//}\n\n\taddPages(routes: ManifestRoute[]): this {\n\t\tfor (let r of routes) {\n\t\t\tthis.routes.push(\n\t\t\t\tnew Route(r.path, {\n\t\t\t\t\tpath: r.path,\n\t\t\t\t\ttype: r.type,\n\t\t\t\t\tendPoint: r.endPoint,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tthis.#sortRoutes();\n\t\treturn this;\n\t}\n\n\t// TODO: Allow calling with the endpoint itself, so that you can setup an app with no scaffold\n\taddPage(path: string, type: number, endPoint: () => Promise<any>): this {\n\t\tthis.routes.push(\n\t\t\tnew Route(path, {\n\t\t\t\tpath,\n\t\t\t\ttype,\n\t\t\t\tendPoint,\n\t\t\t}),\n\t\t);\n\t\tthis.#sortRoutes();\n\t\treturn this;\n\t}\n\n\t/*\n\tloadPage(ev: ServerEvent, file: string): Response {\n\t\tlet page = this.pages.get(file);\n\t\tif (!page) {\n\t\t\tpage = {};\n\t\t\tthis.pages.set(file, page);\n\t\t}\n\t}\n\t*/\n\n\tmatch(path: string, query: URLSearchParams): Match | undefined {\n\t\tfor (let route of this.routes) {\n\t\t\tlet match = path.match(route.regex);\n\t\t\tif (match) {\n\t\t\t\t// Lazy load server endpoints and layouts\n\t\t\t\tif (!route.handler.loaded) {\n\t\t\t\t\tthis.#loadHandler(route.handler, route.path);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\thandler: route.handler,\n\t\t\t\t\tparams: match.groups,\n\t\t\t\t\tquery,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`not found: '${path}'`);\n\t}\n\n\t#loadHandler(handler: RouteHandler, path: string) {\n\t\thandler.layouts = this.#findLayouts(path);\n\t\thandler.serverEndPoint = this.#findServer(path);\n\t\thandler.serverHook = this.#findServerHook(path);\n\t}\n\n\t#findLayouts(path: string): LayoutHandler[] | undefined {\n\t\tlet layouts: LayoutHandler[] = [];\n\t\tlet parts = path\n\t\t\t// Strip the trailing`~/server` (so we don't look for `~/server/_layout`\n\t\t\t// which will never exist)\n\t\t\t.replace(/\\/~server$/, \"\")\n\t\t\t// The path will always start with / so splitting e.g. `/first/second`\n\t\t\t// will result in ['', 'first', 'second'] and checks for `/_layout`,\n\t\t\t// `/first/_layout` and `/second/_layout`\n\t\t\t.split(\"/\");\n\t\tlet basePath = \"\";\n\t\tfor (let i = 0; i < parts.length; i++) {\n\t\t\tif (i > 0) {\n\t\t\t\tbasePath += \"/\" + parts[i];\n\t\t\t}\n\t\t\tconst layoutPath = basePath + \"/_layout\";\n\t\t\tconst layoutRoute = this.routes.find((r) => r.path === layoutPath);\n\t\t\tif (layoutRoute) {\n\t\t\t\tlayouts.push({\n\t\t\t\t\tpath: layoutPath,\n\t\t\t\t\tendPoint: layoutRoute.handler.endPoint,\n\t\t\t\t\tserverEndPoint: this.#findServer(layoutPath),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn layouts.length ? layouts : undefined;\n\t}\n\n\t#findServer(path: string): (() => Promise<any>) | undefined {\n\t\tconst serverPath = path.replace(/\\/$/, \"\") + \"/~server\";\n\t\tconst serverRoute = this.routes.find((r) => r.path === serverPath);\n\t\treturn serverRoute && serverRoute.handler.endPoint;\n\t}\n\n\t#findServerHook(path: string): (() => Promise<any>) | undefined {\n\t\t// TODO: Should this be a collection, like layouts?\n\t\tlet serverHookPath = \"/_hook/~server\";\n\t\t// HACK: Generalise this\n\t\tif (path.startsWith(\"/api/\")) {\n\t\t\tserverHookPath = \"/api\" + serverHookPath;\n\t\t}\n\t\tconst serverHookRoute = this.routes.find((r) => r.path === serverHookPath);\n\t\treturn serverHookRoute && serverHookRoute.handler.endPoint;\n\t}\n\n\t// HACK: We're doing this here as well as in Site, so that you can use the\n\t// Router externally without Site\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\nclass Route {\n\tpath: string;\n\tregex: RegExp;\n\thandler: RouteHandler;\n\n\tconstructor(path: string, handler: RouteHandler) {\n\t\tthis.path = path;\n\t\tthis.regex = pathToRegex(path);\n\t\tthis.handler = handler;\n\t}\n}\n\ntype Match = {\n\thandler: RouteHandler;\n\tparams?: Record<string, string>;\n\tquery: URLSearchParams;\n};\n","import { $watch } from \"@torpor/view\";\nimport Page from \"./Page\";\n\nconst $page: Page = $watch(\n\t{\n\t\tstatus: 404,\n\t\turl: new URL(\"http://localhost\"),\n\t\terror: {\n\t\t\tmessage: \"\",\n\t\t},\n\t},\n\t{ shallow: true },\n);\n\nexport default $page;\n","export const PAGE_ROUTE = 0;\nexport const PAGE_SERVER_ROUTE = 1;\nexport const SERVER_ROUTE = 3;\nexport const LAYOUT_ROUTE = 4;\nexport const LAYOUT_SERVER_ROUTE = 5;\nexport const HOOK_ROUTE = 6;\nexport const HOOK_SERVER_ROUTE = 7;\nexport const ERROR_ROUTE = 8;\n\nexport type RouteType =\n\t| typeof PAGE_ROUTE\n\t| typeof PAGE_SERVER_ROUTE\n\t| typeof SERVER_ROUTE\n\t| typeof LAYOUT_ROUTE\n\t| typeof LAYOUT_SERVER_ROUTE\n\t| typeof HOOK_ROUTE\n\t| typeof HOOK_SERVER_ROUTE\n\t| typeof ERROR_ROUTE\n\t| -1;\n"],"mappings":"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","names":["path","path","path"]}
1
+ {"version":3,"sources":["../src/test.ts","../src/test/runTest.ts","../src/response/response.ts","../src/response/notFound.ts","../src/response/ok.ts","../src/response/transfer.ts","../src/response/seeOther.ts","../src/server/CookieHelper.ts","../src/server/HeaderHelper.ts","../src/server/ServerEvent.ts","../src/site/pathToRegex.ts","../src/site/Router.ts","../src/state/$page.ts","../src/types/RouteType.ts"],"sourcesContent":["import runTest from \"./test/runTest\";\n\nexport { runTest };\n","import { type ServerComponent, type ServerSlotRender } from \"@torpor/view\";\nimport path from \"node:path\";\nimport notFound from \"../response/notFound.ts\";\nimport ok from \"../response/ok.ts\";\nimport seeOther from \"../response/seeOther.ts\";\nimport ServerEvent from \"../server/ServerEvent.ts\";\nimport Router from \"../site/Router.ts\";\nimport Site from \"../site/Site\";\nimport $page from \"../state/$page.ts\";\nimport type PageEndPoint from \"../types/PageEndPoint.ts\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint.ts\";\nimport RouteHandler from \"../types/RouteHandler.ts\";\nimport {\n\tHOOK_ROUTE,\n\tHOOK_SERVER_ROUTE,\n\tLAYOUT_ROUTE,\n\tLAYOUT_SERVER_ROUTE,\n\tPAGE_ROUTE,\n\tPAGE_SERVER_ROUTE,\n\tSERVER_ROUTE,\n} from \"../types/RouteType\";\nimport type ServerEndPoint from \"../types/ServerEndPoint.ts\";\nimport type ServerHook from \"../types/ServerHook.ts\";\nimport type ServerLoadEvent from \"../types/ServerLoadEvent.ts\";\n\n/**\n * Runs a site route in test mode\n * @param site The site configuration, with routing configured\n * @param route The route path e.g. `/home`\n * @param ev An optional ServerEvent, which you can use to set test cookies and headers\n * @returns An HTTP response containing the requested data, HTML, or error status\n */\nexport default async function runTest(\n\tsite: Site,\n\troute: string,\n\tev?: ServerEvent,\n): Promise<Response> {\n\tif (!route.startsWith(\"/\")) route += \"/\";\n\tif (!ev) {\n\t\tconst req = new Request(`http://localhost${route}`);\n\t\tev = new ServerEvent(req);\n\t}\n\n\t// Build the router from the Site object created by the user\n\tconst router = new Router();\n\trouter.addPages(\n\t\tsite.routes.map((r) => ({\n\t\t\tpath: r.path,\n\t\t\ttype: r.type,\n\t\t\tendPoint: () => import(/* @vite-ignore */ path.join(site.root, r.file)),\n\t\t})),\n\t);\n\n\treturn await load(router, ev, \"%COMPONENT_BODY%\");\n}\n\n// HACK: This is a copy of serverEntry\n// TODO: Make it shared instead!\nasync function load(router: Router, ev: ServerEvent, template: string): Promise<Response> {\n\t//const url = new URL(`http://${process.env.HOST ?? \"localhost\"}${ev.request.url}`);\n\tconst url = new URL(ev.request.url);\n\tconst path = url.pathname;\n\tconst query = url.searchParams;\n\n\t//console.log(`handling ${ev.request.method} for '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\tconst route = router.match(path, query);\n\tif (!route) {\n\t\treturn notFound();\n\t}\n\n\t// Update $page before building the components\n\t// TODO: Find somewhere better to put this\n\t$page.url = url;\n\n\tconst handler = route.handler;\n\tconst params = route.params || {};\n\n\t// TODO: Hit the server hook out here\n\t// We could maybe set data loading up as middleware on a route??????\n\n\tswitch (handler.type) {\n\t\tcase PAGE_ROUTE:\n\t\tcase LAYOUT_ROUTE: {\n\t\t\t// It's a /+page.ts or /_layout.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadView(ev, url, handler, params, template);\n\t\t\t} else if (ev.request.method === \"POST\") {\n\t\t\t\treturn await runAction(ev, url, handler, params, query);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase PAGE_SERVER_ROUTE:\n\t\tcase LAYOUT_SERVER_ROUTE: {\n\t\t\t// It's a /+page.server.ts or /_layout.server.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadData(ev, url, handler, \"load\", params);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase SERVER_ROUTE: {\n\t\t\t// It's a /+server.ts endpoint\n\t\t\tconst functionName = ev.request.method.toLowerCase().replace(\"delete\", \"del\");\n\t\t\treturn await loadData(ev, url, handler, functionName, params);\n\t\t}\n\t\tcase HOOK_ROUTE:\n\t\tcase HOOK_SERVER_ROUTE: {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadData(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tfunctionName: string,\n\tparams: Record<string, string>,\n) {\n\tconst serverEndPoint: ServerEndPoint | undefined = (await handler.endPoint()).default;\n\tif (serverEndPoint && serverEndPoint[functionName]) {\n\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\tif (handler.serverHook) {\n\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\tif (serverHook?.handle) {\n\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t}\n\t\t}\n\n\t\tconst result = await serverEndPoint[functionName](serverParams);\n\n\t\t// If there was no response returned from load (such as errors or a\n\t\t// redirect), send an ok response\n\t\treturn result || ok();\n\t}\n\n\treturn notFound();\n}\n\nasync function loadView(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\ttemplate: string,\n) {\n\t// There must be a client endpoint with a component\n\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\tif (!clientEndPoint?.component) {\n\t\treturn notFound();\n\t}\n\n\t// There may be a server endpoint\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t// Maybe hit the server hook\n\tconst serverParams = buildServerParams(ev, url, params);\n\tif (handler.serverHook) {\n\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\tif (serverHook?.handle) {\n\t\t\tawait serverHook.handle(serverParams);\n\t\t}\n\t}\n\n\t// Pass the data into $props\n\t// TODO: Promise.all\n\t// NOTE: We're loading data from top to bottom, overriding as we go, and I'm not sure if this is the best way to go\n\tlet data = {};\n\tif (handler.layouts) {\n\t\tfor (let layout of handler.layouts) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;\n\t\t\tconst layoutServerEndPoint: PageServerEndPoint | undefined =\n\t\t\t\tlayout.serverEndPoint && (await layout.serverEndPoint())?.default;\n\t\t\tconst layoutResponse = await loadClientAndServerData(\n\t\t\t\turl,\n\t\t\t\tparams,\n\t\t\t\tserverParams,\n\t\t\t\tdata,\n\t\t\t\tlayoutEndPoint,\n\t\t\t\tlayoutServerEndPoint,\n\t\t\t);\n\t\t\tif (layoutResponse?.ok === false) {\n\t\t\t\treturn layoutResponse;\n\t\t\t}\n\t\t}\n\t}\n\tlet endPointResponse = await loadClientAndServerData(\n\t\turl,\n\t\tparams,\n\t\tserverParams,\n\t\tdata,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\tif (endPointResponse?.ok === false) {\n\t\treturn endPointResponse;\n\t}\n\tlet $props: Record<string, any> = { data };\n\n\t// If there are layouts, work our way upwards, pushing each component into\n\t// the default slot of its parent\n\t// TODO: Also handle layout server data\n\t// TODO: There's probably a nicer way to do this with reducers or something\n\t// TODO: Re-use parent layouts\n\tlet component = clientEndPoint.component as ServerComponent;\n\tlet slots: Record<string, ServerSlotRender> | undefined = undefined;\n\tif (handler.layouts) {\n\t\tlet slotFunctions: ServerSlotRender[] = [];\n\t\tslotFunctions[handler.layouts.length] = (_, context) =>\n\t\t\t(clientEndPoint.component as ServerComponent)($props, context);\n\t\tfor (let i = handler.layouts.length - 1; i >= 0; i--) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())\n\t\t\t\t?.default;\n\t\t\tif (layoutEndPoint?.component) {\n\t\t\t\tif (i === 0) {\n\t\t\t\t\tcomponent = layoutEndPoint.component as ServerComponent;\n\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t} else {\n\t\t\t\t\tslotFunctions[i] = (_, context) =>\n\t\t\t\t\t\t(layoutEndPoint.component as ServerComponent)($props, context, {\n\t\t\t\t\t\t\t_: slotFunctions[i + 1],\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet html;\n\ttry {\n\t\tlet { body, head } = component($props, undefined, slots);\n\t\thtml = template.replace(\"%COMPONENT_BODY%\", body).replace(\"%COMPONENT_HEAD\", head);\n\t} catch (error) {\n\t\t// TODO: Show a proper Error component\n\t\thtml = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\tconsole.log(error);\n\t}\n\n\treturn new Response(html, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"text/html\",\n\t\t},\n\t});\n}\n\nasync function runAction(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\tquery: URLSearchParams,\n) {\n\tconst actionName = (Array.from(query.keys())[0] || \"default\").replace(/^\\//, \"\");\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint()).default;\n\tif (serverEndPoint?.actions) {\n\t\tconst action = serverEndPoint.actions[actionName];\n\t\tif (action) {\n\t\t\t// TODO: form.errors etc\n\t\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\t\tif (handler.serverHook) {\n\t\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\t\tif (serverHook?.handle) {\n\t\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await action(serverParams); // <--\n\n\t\t\t// If there was no response returned from the action (such as errors\n\t\t\t// or a redirect), reload the page by redirecting\n\t\t\treturn result || seeOther(url.pathname);\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadClientAndServerData(\n\turl: URL,\n\tparams: Record<string, string>,\n\tserverParams: ServerLoadEvent,\n\tdata: Record<string, any>,\n\tclientEndPoint?: PageEndPoint,\n\tserverEndPoint?: PageServerEndPoint,\n) {\n\tif (clientEndPoint?.load) {\n\t\tconst clientParams = buildClientParams(url, data, params);\n\t\tconst clientResponse = await clientEndPoint.load(clientParams);\n\t\tif (clientResponse) {\n\t\t\tif (clientResponse.ok) {\n\t\t\t\tif (clientResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await clientResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn clientResponse;\n\t\t\t}\n\t\t}\n\t}\n\tif (serverEndPoint?.load) {\n\t\tconst serverResponse = await serverEndPoint.load(serverParams);\n\t\tif (serverResponse) {\n\t\t\tif (serverResponse.ok) {\n\t\t\t\tif (serverResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await serverResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn serverResponse;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tdata,\n\t};\n}\n\nfunction buildServerParams(\n\tev: ServerEvent,\n\turl: URL,\n\tparams: Record<string, string>,\n): ServerLoadEvent {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tappData: {},\n\t\trequest: ev.request,\n\t\tcookies: ev.cookies,\n\t\theaders: ev.headers,\n\t\tadapter: ev.adapter,\n\t};\n}\n","/**\n * Creates a response with the supplied status code and optional body.\n *\n * If the body is an object, it will be converted to JSON and the content-type\n * header set to \"application/json\". Otherwise, if the body is a string, the\n * content-type header will be set to \"text/plain\".\n */\nexport default function response(status: number, body?: object | string): Response {\n\tlet headers: Record<string, string> | undefined = undefined;\n\tif (body) {\n\t\tif (typeof body === \"object\") {\n\t\t\theaders = { \"content-type\": \"application/json\" };\n\t\t\tbody = JSON.stringify(body);\n\t\t} else {\n\t\t\theaders = { \"content-type\": \"text/plain\" };\n\t\t}\n\t}\n\treturn new Response(body, {\n\t\tstatus,\n\t\theaders,\n\t});\n}\n","import response from \"./response\";\n\n/**\n * 404 Not Found\n *\n * The server cannot find the requested resource. In the browser, this means the\n * URL is not recognized. In an API, this can also mean that the endpoint is\n * valid but the resource itself does not exist. Servers may also send this\n * response instead of 403 Forbidden to hide the existence of a resource from an\n * unauthorized client. This response code is probably the most well known due\n * to its frequent occurrence on the web.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\n */\nexport default function notFound(body?: object | string): Response {\n\treturn response(404, body ?? \"Not found\");\n}\n","import response from \"./response\";\n\n/**\n * 200 OK\n *\n * The request succeeded. The result meaning of \"success\" depends on the HTTP\n * method:\n * - GET: The resource has been fetched and transmitted in the message body.\n * - HEAD: The representation headers are included in the response without any\n * message body.\n * - PUT or POST: The resource describing the result of the action is\n * transmitted in the message body.\n * - TRACE: The message body contains the request message as received by the\n * server.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200\n */\nexport default function ok(body?: object | string): Response {\n\treturn response(200, body);\n}\n","/**\n * Creates a redirect response with the supplied status code.\n */\nexport default function transfer(\n\tstatus: 300 | 301 | 302 | 303 | 304 | 307 | 308,\n\tlocation: string,\n): Response {\n\treturn new Response(null, {\n\t\tstatus,\n\t\theaders: {\n\t\t\tlocation,\n\t\t},\n\t});\n}\n","import transfer from \"./transfer\";\n\n/**\n * 303 See Other\n *\n * The server sent this response to direct the client to get the requested\n * resource at another URI with a GET request.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303\n */\nexport default function seeOther(location: string): Response {\n\treturn transfer(303, location);\n}\n","import { SerializeOptions, parse, serialize } from \"cookie\";\n\nexport default class CookieHelper {\n\trequest: Request;\n\tcookies: Map<string, string> = new Map();\n\n\tconstructor(request: Request) {\n\t\tthis.request = request;\n\t}\n\n\tget(name: string): string | undefined {\n\t\t// If it's been set, return the value from the map\n\t\tif (this.cookies.has(name)) {\n\t\t\treturn parse(this.cookies.get(name)!)[name];\n\t\t}\n\t\t// Otherwise, return the value from the `cookie` header\n\t\tconst header = this.request.headers.get(\"cookie\");\n\t\treturn parse(header ?? \"\")[name];\n\t}\n\n\tset(name: string, value: string, options?: SerializeOptions): void {\n\t\t// Set a cookie header with some default values for security\n\t\toptions ??= {};\n\t\toptions.httpOnly ??= true;\n\t\toptions.secure ??= true;\n\t\toptions.sameSite ??= \"lax\";\n\t\tthis.cookies.set(name, serialize(name, value, options));\n\t}\n\n\tdelete(name: string, options?: SerializeOptions): void {\n\t\t// Set a cookie header with an immediate expiry date\n\t\toptions ??= {};\n\t\toptions.expires = new Date(0);\n\t\tthis.cookies.set(name, serialize(name, \"\", options));\n\t}\n}\n","export default class HeaderHelper {\n\trequest: Request;\n\theaders: Map<string, string> = new Map();\n\n\tconstructor(request: Request) {\n\t\tthis.request = request;\n\t}\n\n\tget(name: string): string | undefined {\n\t\t// If it's been set, return the updated value\n\t\tif (this.headers.has(name)) {\n\t\t\treturn this.headers.get(name);\n\t\t}\n\t\t// Otherwise, return the value from the header\n\t\treturn this.request.headers.get(name) ?? undefined;\n\t}\n\n\tset(name: string, value: string): void {\n\t\tthis.headers.set(name, value);\n\t}\n\n\tdelete(name: string): void {\n\t\tthis.headers.delete(name);\n\t}\n}\n","import CookieHelper from \"./CookieHelper\";\nimport HeaderHelper from \"./HeaderHelper\";\n\nexport default class ServerEvent {\n\trequest: Request;\n\tresponse: Response | undefined | void;\n\t// TODO: Should we cast types??\n\tparams?: Record<string, string>;\n\n\tadapter: any;\n\n\tcookies: CookieHelper;\n\theaders: HeaderHelper;\n\n\tconstructor(request: Request, params?: Record<string, string>) {\n\t\tthis.request = request;\n\t\tthis.params = params;\n\t\tthis.cookies = new CookieHelper(request);\n\t\tthis.headers = new HeaderHelper(request);\n\n\t\t// Adapters may have added the `adapter` property to globalThis\n\t\t// @ts-ignore\n\t\tthis.adapter = globalThis.adapter;\n\t}\n\n\taddHeaders(): void {\n\t\tif (!this.response) {\n\t\t\tthrow new Error(\"Response not created yet\");\n\t\t}\n\n\t\tfor (let cookie of this.cookies.cookies.values()) {\n\t\t\tthis.response.headers.append(\"set-cookie\", cookie);\n\t\t}\n\n\t\tfor (let header of this.headers.headers.entries()) {\n\t\t\tthis.response.headers.append(header[0], header[1]);\n\t\t}\n\t}\n}\n","export default function pathToRegex(path: string): RegExp {\n\tconst pattern =\n\t\tpath\n\t\t\t.split(\"/\")\n\t\t\t// Replace `[slug]` with a grouped match so we can pull out params\n\t\t\t.map((p) => {\n\t\t\t\treturn p.replace(/\\[([^\\/]+?)\\]/, \"(?<$1>[^\\\\/]+?)\");\n\t\t\t})\n\t\t\t.join(\"\\\\/\")\n\t\t\t// Replace globs with regex syntax\n\t\t\t.replaceAll(\"*\", \".*\") +\n\t\t// Ignore trailing slashes -- `/path` should be treated the same as `/path/`\n\t\t\"/*\";\n\treturn new RegExp(`^${pattern}/*$`);\n}\n","import type LayoutHandler from \"../types/LayoutHandler\";\nimport type ManifestRoute from \"../types/ManifestRoute\";\nimport type RouteHandler from \"../types/RouteHandler\";\nimport pathToRegex from \"./pathToRegex\";\n\n/**\n * A router that handles file routes with layouts, hooks, etc.\n */\nexport default class Router {\n\troutes: Route[] = [];\n\n\tconstructor() {}\n\n\t//add(path: string, file: string) {\n\t//\t// TODO: Call the specific addX method based on the file path\n\t//\tthis.addPage(route, file);\n\t//}\n\n\taddPages(routes: ManifestRoute[]): this {\n\t\tfor (let r of routes) {\n\t\t\tthis.routes.push(\n\t\t\t\tnew Route(r.path, {\n\t\t\t\t\tpath: r.path,\n\t\t\t\t\ttype: r.type,\n\t\t\t\t\tendPoint: r.endPoint,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tthis.#sortRoutes();\n\t\treturn this;\n\t}\n\n\t// TODO: Allow calling with the endpoint itself, so that you can setup an app with no scaffold\n\taddPage(path: string, type: number, endPoint: () => Promise<any>): this {\n\t\tthis.routes.push(\n\t\t\tnew Route(path, {\n\t\t\t\tpath,\n\t\t\t\ttype,\n\t\t\t\tendPoint,\n\t\t\t}),\n\t\t);\n\t\tthis.#sortRoutes();\n\t\treturn this;\n\t}\n\n\t/*\n\tloadPage(ev: ServerEvent, file: string): Response {\n\t\tlet page = this.pages.get(file);\n\t\tif (!page) {\n\t\t\tpage = {};\n\t\t\tthis.pages.set(file, page);\n\t\t}\n\t}\n\t*/\n\n\tmatch(path: string, query: URLSearchParams): Match | undefined {\n\t\tfor (let route of this.routes) {\n\t\t\tlet match = path.match(route.regex);\n\t\t\tif (match) {\n\t\t\t\t// Lazy load server endpoints and layouts\n\t\t\t\tif (!route.handler.loaded) {\n\t\t\t\t\tthis.#loadHandler(route.handler, route.path);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\thandler: route.handler,\n\t\t\t\t\tparams: match.groups,\n\t\t\t\t\tquery,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`not found: '${path}'`);\n\t}\n\n\t#loadHandler(handler: RouteHandler, path: string) {\n\t\thandler.layouts = this.#findLayouts(path);\n\t\thandler.serverEndPoint = this.#findServer(path);\n\t\thandler.serverHook = this.#findServerHook(path);\n\t}\n\n\t#findLayouts(path: string): LayoutHandler[] | undefined {\n\t\tlet layouts: LayoutHandler[] = [];\n\t\tlet parts = path\n\t\t\t// Strip the trailing`~/server` (so we don't look for `~/server/_layout`\n\t\t\t// which will never exist)\n\t\t\t.replace(/\\/~server$/, \"\")\n\t\t\t// The path will always start with / so splitting e.g. `/first/second`\n\t\t\t// will result in ['', 'first', 'second'] and checks for `/_layout`,\n\t\t\t// `/first/_layout` and `/second/_layout`\n\t\t\t.split(\"/\");\n\t\tlet basePath = \"\";\n\t\tfor (let i = 0; i < parts.length; i++) {\n\t\t\tif (i > 0) {\n\t\t\t\tbasePath += \"/\" + parts[i];\n\t\t\t}\n\t\t\tconst layoutPath = basePath + \"/_layout\";\n\t\t\tconst layoutRoute = this.routes.find((r) => r.path === layoutPath);\n\t\t\tif (layoutRoute) {\n\t\t\t\tlayouts.push({\n\t\t\t\t\tpath: layoutPath,\n\t\t\t\t\tendPoint: layoutRoute.handler.endPoint,\n\t\t\t\t\tserverEndPoint: this.#findServer(layoutPath),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn layouts.length ? layouts : undefined;\n\t}\n\n\t#findServer(path: string): (() => Promise<any>) | undefined {\n\t\tconst serverPath = path.replace(/\\/$/, \"\") + \"/~server\";\n\t\tconst serverRoute = this.routes.find((r) => r.path === serverPath);\n\t\treturn serverRoute && serverRoute.handler.endPoint;\n\t}\n\n\t#findServerHook(path: string): (() => Promise<any>) | undefined {\n\t\t// TODO: Should this be a collection, like layouts?\n\t\tlet serverHookPath = \"/_hook/~server\";\n\t\t// HACK: Generalise this\n\t\tif (path.startsWith(\"/api/\")) {\n\t\t\tserverHookPath = \"/api\" + serverHookPath;\n\t\t}\n\t\tconst serverHookRoute = this.routes.find((r) => r.path === serverHookPath);\n\t\treturn serverHookRoute && serverHookRoute.handler.endPoint;\n\t}\n\n\t// HACK: We're doing this here as well as in Site, so that you can use the\n\t// Router externally without Site\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\nclass Route {\n\tpath: string;\n\tregex: RegExp;\n\thandler: RouteHandler;\n\n\tconstructor(path: string, handler: RouteHandler) {\n\t\tthis.path = path;\n\t\tthis.regex = pathToRegex(path);\n\t\tthis.handler = handler;\n\t}\n}\n\ntype Match = {\n\thandler: RouteHandler;\n\tparams?: Record<string, string>;\n\tquery: URLSearchParams;\n};\n","import { $watch } from \"@torpor/view\";\nimport Page from \"./Page\";\n\nconst $page: Page = $watch(\n\t{\n\t\tstatus: 404,\n\t\turl: new URL(\"http://localhost\"),\n\t\terror: {\n\t\t\tmessage: \"\",\n\t\t},\n\t},\n\t{ shallow: true },\n);\n\nexport default $page;\n","export const PAGE_ROUTE = 0;\nexport const PAGE_SERVER_ROUTE = 1;\nexport const SERVER_ROUTE = 3;\nexport const LAYOUT_ROUTE = 4;\nexport const LAYOUT_SERVER_ROUTE = 5;\nexport const HOOK_ROUTE = 6;\nexport const HOOK_SERVER_ROUTE = 7;\nexport const ERROR_ROUTE = 8;\n\nexport type RouteType =\n\t| typeof PAGE_ROUTE\n\t| typeof PAGE_SERVER_ROUTE\n\t| typeof SERVER_ROUTE\n\t| typeof LAYOUT_ROUTE\n\t| typeof LAYOUT_SERVER_ROUTE\n\t| typeof HOOK_ROUTE\n\t| typeof HOOK_SERVER_ROUTE\n\t| typeof ERROR_ROUTE\n\t| -1;\n"],"mappings":"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","names":["path","path","path"]}
package/dist/test.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as ServerEvent } from './Server-BC_dixZQ.cjs';
2
- import { S as Site } from './Site-BXX-S541.cjs';
1
+ import { a as ServerEvent } from './Server-CNZWh5ca.cjs';
2
+ import { S as Site } from './Site-DQojGpE-.cjs';
3
3
  import 'cookie';
4
4
  import 'vite';
5
5
 
package/dist/test.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as ServerEvent } from './Server-BC_dixZQ.js';
2
- import { S as Site } from './Site-4tGiw2-I.js';
1
+ import { a as ServerEvent } from './Server-CNZWh5ca.js';
2
+ import { S as Site } from './Site-C2020GV_.js';
3
3
  import 'cookie';
4
4
  import 'vite';
5
5
 
package/dist/test.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  } from "./chunk-WVXAN4IV.js";
19
19
  import {
20
20
  ServerEvent
21
- } from "./chunk-KKFND4HK.js";
21
+ } from "./chunk-TDOXEXG6.js";
22
22
  import "./chunk-SJVHHGVW.js";
23
23
 
24
24
  // src/test/runTest.ts
@@ -40,7 +40,7 @@ async function runTest(site, route, ev) {
40
40
  )
41
41
  }))
42
42
  );
43
- return await load(router, ev, "%COMPONENT_HTML%");
43
+ return await load(router, ev, "%COMPONENT_BODY%");
44
44
  }
45
45
  async function load(router, ev, template) {
46
46
  const url = new URL(ev.request.url);
@@ -160,8 +160,8 @@ async function loadView(ev, url, handler, params, template) {
160
160
  }
161
161
  let html;
162
162
  try {
163
- html = component($props, void 0, slots);
164
- html = template.replace("%COMPONENT_HTML%", html);
163
+ let { body, head } = component($props, void 0, slots);
164
+ html = template.replace("%COMPONENT_BODY%", body).replace("%COMPONENT_HEAD", head);
165
165
  } catch (error) {
166
166
  html = '<span style="color: red">Script syntax error</span><p>' + error + "</p>";
167
167
  console.log(error);
package/dist/test.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/test/runTest.ts"],"sourcesContent":["import { type ServerComponent, type ServerSlotRender } from \"@torpor/view\";\nimport path from \"node:path\";\nimport notFound from \"../response/notFound.ts\";\nimport ok from \"../response/ok.ts\";\nimport seeOther from \"../response/seeOther.ts\";\nimport ServerEvent from \"../server/ServerEvent.ts\";\nimport Router from \"../site/Router.ts\";\nimport Site from \"../site/Site\";\nimport $page from \"../state/$page.ts\";\nimport type PageEndPoint from \"../types/PageEndPoint.ts\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint.ts\";\nimport RouteHandler from \"../types/RouteHandler.ts\";\nimport {\n\tHOOK_ROUTE,\n\tHOOK_SERVER_ROUTE,\n\tLAYOUT_ROUTE,\n\tLAYOUT_SERVER_ROUTE,\n\tPAGE_ROUTE,\n\tPAGE_SERVER_ROUTE,\n\tSERVER_ROUTE,\n} from \"../types/RouteType\";\nimport type ServerEndPoint from \"../types/ServerEndPoint.ts\";\nimport type ServerHook from \"../types/ServerHook.ts\";\nimport type ServerLoadEvent from \"../types/ServerLoadEvent.ts\";\n\n/**\n * Runs a site route in test mode\n * @param site The site configuration, with routing configured\n * @param route The route path e.g. `/home`\n * @param ev An optional ServerEvent, which you can use to set test cookies and headers\n * @returns An HTTP response containing the requested data, HTML, or error status\n */\nexport default async function runTest(\n\tsite: Site,\n\troute: string,\n\tev?: ServerEvent,\n): Promise<Response> {\n\tif (!route.startsWith(\"/\")) route += \"/\";\n\tif (!ev) {\n\t\tconst req = new Request(`http://localhost${route}`);\n\t\tev = new ServerEvent(req);\n\t}\n\n\t// Build the router from the Site object created by the user\n\tconst router = new Router();\n\trouter.addPages(\n\t\tsite.routes.map((r) => ({\n\t\t\tpath: r.path,\n\t\t\ttype: r.type,\n\t\t\tendPoint: () => import(/* @vite-ignore */ path.join(site.root, r.file)),\n\t\t})),\n\t);\n\n\treturn await load(router, ev, \"%COMPONENT_HTML%\");\n}\n\n// HACK: This is a copy of serverEntry\n// TODO: Make it shared instead!\nasync function load(router: Router, ev: ServerEvent, template: string): Promise<Response> {\n\t//const url = new URL(`http://${process.env.HOST ?? \"localhost\"}${ev.request.url}`);\n\tconst url = new URL(ev.request.url);\n\tconst path = url.pathname;\n\tconst query = url.searchParams;\n\n\t//console.log(`handling ${ev.request.method} for '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\tconst route = router.match(path, query);\n\tif (!route) {\n\t\treturn notFound();\n\t}\n\n\t// Update $page before building the components\n\t// TODO: Find somewhere better to put this\n\t$page.url = url;\n\n\tconst handler = route.handler;\n\tconst params = route.params || {};\n\n\t// TODO: Hit the server hook out here\n\t// We could maybe set data loading up as middleware on a route??????\n\n\tswitch (handler.type) {\n\t\tcase PAGE_ROUTE:\n\t\tcase LAYOUT_ROUTE: {\n\t\t\t// It's a /+page.ts or /_layout.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadView(ev, url, handler, params, template);\n\t\t\t} else if (ev.request.method === \"POST\") {\n\t\t\t\treturn await runAction(ev, url, handler, params, query);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase PAGE_SERVER_ROUTE:\n\t\tcase LAYOUT_SERVER_ROUTE: {\n\t\t\t// It's a /+page.server.ts or /_layout.server.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadData(ev, url, handler, \"load\", params);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase SERVER_ROUTE: {\n\t\t\t// It's a /+server.ts endpoint\n\t\t\tconst functionName = ev.request.method.toLowerCase().replace(\"delete\", \"del\");\n\t\t\treturn await loadData(ev, url, handler, functionName, params);\n\t\t}\n\t\tcase HOOK_ROUTE:\n\t\tcase HOOK_SERVER_ROUTE: {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadData(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tfunctionName: string,\n\tparams: Record<string, string>,\n) {\n\tconst serverEndPoint: ServerEndPoint | undefined = (await handler.endPoint()).default;\n\tif (serverEndPoint && serverEndPoint[functionName]) {\n\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\tif (handler.serverHook) {\n\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\tif (serverHook?.handle) {\n\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t}\n\t\t}\n\n\t\tconst result = await serverEndPoint[functionName](serverParams);\n\n\t\t// If there was no response returned from load (such as errors or a\n\t\t// redirect), send an ok response\n\t\treturn result || ok();\n\t}\n\n\treturn notFound();\n}\n\nasync function loadView(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\ttemplate: string,\n) {\n\t// There must be a client endpoint with a component\n\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\tif (!clientEndPoint?.component) {\n\t\treturn notFound();\n\t}\n\n\t// There may be a server endpoint\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t// Maybe hit the server hook\n\tconst serverParams = buildServerParams(ev, url, params);\n\tif (handler.serverHook) {\n\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\tif (serverHook?.handle) {\n\t\t\tawait serverHook.handle(serverParams);\n\t\t}\n\t}\n\n\t// Pass the data into $props\n\t// TODO: Promise.all\n\t// NOTE: We're loading data from top to bottom, overriding as we go, and I'm not sure if this is the best way to go\n\tlet data = {};\n\tif (handler.layouts) {\n\t\tfor (let layout of handler.layouts) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;\n\t\t\tconst layoutServerEndPoint: PageServerEndPoint | undefined =\n\t\t\t\tlayout.serverEndPoint && (await layout.serverEndPoint())?.default;\n\t\t\tconst layoutResponse = await loadClientAndServerData(\n\t\t\t\turl,\n\t\t\t\tparams,\n\t\t\t\tserverParams,\n\t\t\t\tdata,\n\t\t\t\tlayoutEndPoint,\n\t\t\t\tlayoutServerEndPoint,\n\t\t\t);\n\t\t\tif (layoutResponse?.ok === false) {\n\t\t\t\treturn layoutResponse;\n\t\t\t}\n\t\t}\n\t}\n\tlet endPointResponse = await loadClientAndServerData(\n\t\turl,\n\t\tparams,\n\t\tserverParams,\n\t\tdata,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\tif (endPointResponse?.ok === false) {\n\t\treturn endPointResponse;\n\t}\n\tlet $props: Record<string, any> = { data };\n\n\t// If there are layouts, work our way upwards, pushing each component into\n\t// the default slot of its parent\n\t// TODO: Also handle layout server data\n\t// TODO: There's probably a nicer way to do this with reducers or something\n\t// TODO: Re-use parent layouts\n\tlet component = clientEndPoint.component as ServerComponent;\n\tlet slots: Record<string, ServerSlotRender> | undefined = undefined;\n\tif (handler.layouts) {\n\t\tlet slotFunctions: ServerSlotRender[] = [];\n\t\tslotFunctions[handler.layouts.length] = (_, context) =>\n\t\t\t(clientEndPoint.component as ServerComponent)($props, context);\n\t\tfor (let i = handler.layouts.length - 1; i >= 0; i--) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())\n\t\t\t\t?.default;\n\t\t\tif (layoutEndPoint?.component) {\n\t\t\t\tif (i === 0) {\n\t\t\t\t\tcomponent = layoutEndPoint.component as ServerComponent;\n\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t} else {\n\t\t\t\t\tslotFunctions[i] = (_, context) =>\n\t\t\t\t\t\t(layoutEndPoint.component as ServerComponent)($props, context, {\n\t\t\t\t\t\t\t_: slotFunctions[i + 1],\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet html;\n\ttry {\n\t\thtml = component($props, undefined, slots);\n\t\thtml = template.replace(\"%COMPONENT_HTML%\", html);\n\t} catch (error) {\n\t\t// TODO: Show a proper Error component\n\t\thtml = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\tconsole.log(error);\n\t}\n\n\treturn new Response(html, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"text/html\",\n\t\t},\n\t});\n}\n\nasync function runAction(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\tquery: URLSearchParams,\n) {\n\tconst actionName = (Array.from(query.keys())[0] || \"default\").replace(/^\\//, \"\");\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint()).default;\n\tif (serverEndPoint?.actions) {\n\t\tconst action = serverEndPoint.actions[actionName];\n\t\tif (action) {\n\t\t\t// TODO: form.errors etc\n\t\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\t\tif (handler.serverHook) {\n\t\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\t\tif (serverHook?.handle) {\n\t\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await action(serverParams); // <--\n\n\t\t\t// If there was no response returned from the action (such as errors\n\t\t\t// or a redirect), reload the page by redirecting\n\t\t\treturn result || seeOther(url.pathname);\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadClientAndServerData(\n\turl: URL,\n\tparams: Record<string, string>,\n\tserverParams: ServerLoadEvent,\n\tdata: Record<string, any>,\n\tclientEndPoint?: PageEndPoint,\n\tserverEndPoint?: PageServerEndPoint,\n) {\n\tif (clientEndPoint?.load) {\n\t\tconst clientParams = buildClientParams(url, data, params);\n\t\tconst clientResponse = await clientEndPoint.load(clientParams);\n\t\tif (clientResponse) {\n\t\t\tif (clientResponse.ok) {\n\t\t\t\tif (clientResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await clientResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn clientResponse;\n\t\t\t}\n\t\t}\n\t}\n\tif (serverEndPoint?.load) {\n\t\tconst serverResponse = await serverEndPoint.load(serverParams);\n\t\tif (serverResponse) {\n\t\t\tif (serverResponse.ok) {\n\t\t\t\tif (serverResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await serverResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn serverResponse;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tdata,\n\t};\n}\n\nfunction buildServerParams(\n\tev: ServerEvent,\n\turl: URL,\n\tparams: Record<string, string>,\n): ServerLoadEvent {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tappData: {},\n\t\trequest: ev.request,\n\t\tcookies: ev.cookies,\n\t\theaders: ev.headers,\n\t\tadapter: ev.adapter,\n\t};\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../src/test/runTest.ts"],"sourcesContent":["import { type ServerComponent, type ServerSlotRender } from \"@torpor/view\";\nimport path from \"node:path\";\nimport notFound from \"../response/notFound.ts\";\nimport ok from \"../response/ok.ts\";\nimport seeOther from \"../response/seeOther.ts\";\nimport ServerEvent from \"../server/ServerEvent.ts\";\nimport Router from \"../site/Router.ts\";\nimport Site from \"../site/Site\";\nimport $page from \"../state/$page.ts\";\nimport type PageEndPoint from \"../types/PageEndPoint.ts\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint.ts\";\nimport RouteHandler from \"../types/RouteHandler.ts\";\nimport {\n\tHOOK_ROUTE,\n\tHOOK_SERVER_ROUTE,\n\tLAYOUT_ROUTE,\n\tLAYOUT_SERVER_ROUTE,\n\tPAGE_ROUTE,\n\tPAGE_SERVER_ROUTE,\n\tSERVER_ROUTE,\n} from \"../types/RouteType\";\nimport type ServerEndPoint from \"../types/ServerEndPoint.ts\";\nimport type ServerHook from \"../types/ServerHook.ts\";\nimport type ServerLoadEvent from \"../types/ServerLoadEvent.ts\";\n\n/**\n * Runs a site route in test mode\n * @param site The site configuration, with routing configured\n * @param route The route path e.g. `/home`\n * @param ev An optional ServerEvent, which you can use to set test cookies and headers\n * @returns An HTTP response containing the requested data, HTML, or error status\n */\nexport default async function runTest(\n\tsite: Site,\n\troute: string,\n\tev?: ServerEvent,\n): Promise<Response> {\n\tif (!route.startsWith(\"/\")) route += \"/\";\n\tif (!ev) {\n\t\tconst req = new Request(`http://localhost${route}`);\n\t\tev = new ServerEvent(req);\n\t}\n\n\t// Build the router from the Site object created by the user\n\tconst router = new Router();\n\trouter.addPages(\n\t\tsite.routes.map((r) => ({\n\t\t\tpath: r.path,\n\t\t\ttype: r.type,\n\t\t\tendPoint: () => import(/* @vite-ignore */ path.join(site.root, r.file)),\n\t\t})),\n\t);\n\n\treturn await load(router, ev, \"%COMPONENT_BODY%\");\n}\n\n// HACK: This is a copy of serverEntry\n// TODO: Make it shared instead!\nasync function load(router: Router, ev: ServerEvent, template: string): Promise<Response> {\n\t//const url = new URL(`http://${process.env.HOST ?? \"localhost\"}${ev.request.url}`);\n\tconst url = new URL(ev.request.url);\n\tconst path = url.pathname;\n\tconst query = url.searchParams;\n\n\t//console.log(`handling ${ev.request.method} for '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\tconst route = router.match(path, query);\n\tif (!route) {\n\t\treturn notFound();\n\t}\n\n\t// Update $page before building the components\n\t// TODO: Find somewhere better to put this\n\t$page.url = url;\n\n\tconst handler = route.handler;\n\tconst params = route.params || {};\n\n\t// TODO: Hit the server hook out here\n\t// We could maybe set data loading up as middleware on a route??????\n\n\tswitch (handler.type) {\n\t\tcase PAGE_ROUTE:\n\t\tcase LAYOUT_ROUTE: {\n\t\t\t// It's a /+page.ts or /_layout.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadView(ev, url, handler, params, template);\n\t\t\t} else if (ev.request.method === \"POST\") {\n\t\t\t\treturn await runAction(ev, url, handler, params, query);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase PAGE_SERVER_ROUTE:\n\t\tcase LAYOUT_SERVER_ROUTE: {\n\t\t\t// It's a /+page.server.ts or /_layout.server.ts endpoint\n\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\treturn await loadData(ev, url, handler, \"load\", params);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase SERVER_ROUTE: {\n\t\t\t// It's a /+server.ts endpoint\n\t\t\tconst functionName = ev.request.method.toLowerCase().replace(\"delete\", \"del\");\n\t\t\treturn await loadData(ev, url, handler, functionName, params);\n\t\t}\n\t\tcase HOOK_ROUTE:\n\t\tcase HOOK_SERVER_ROUTE: {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadData(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tfunctionName: string,\n\tparams: Record<string, string>,\n) {\n\tconst serverEndPoint: ServerEndPoint | undefined = (await handler.endPoint()).default;\n\tif (serverEndPoint && serverEndPoint[functionName]) {\n\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\tif (handler.serverHook) {\n\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\tif (serverHook?.handle) {\n\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t}\n\t\t}\n\n\t\tconst result = await serverEndPoint[functionName](serverParams);\n\n\t\t// If there was no response returned from load (such as errors or a\n\t\t// redirect), send an ok response\n\t\treturn result || ok();\n\t}\n\n\treturn notFound();\n}\n\nasync function loadView(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\ttemplate: string,\n) {\n\t// There must be a client endpoint with a component\n\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\tif (!clientEndPoint?.component) {\n\t\treturn notFound();\n\t}\n\n\t// There may be a server endpoint\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t// Maybe hit the server hook\n\tconst serverParams = buildServerParams(ev, url, params);\n\tif (handler.serverHook) {\n\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\tif (serverHook?.handle) {\n\t\t\tawait serverHook.handle(serverParams);\n\t\t}\n\t}\n\n\t// Pass the data into $props\n\t// TODO: Promise.all\n\t// NOTE: We're loading data from top to bottom, overriding as we go, and I'm not sure if this is the best way to go\n\tlet data = {};\n\tif (handler.layouts) {\n\t\tfor (let layout of handler.layouts) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;\n\t\t\tconst layoutServerEndPoint: PageServerEndPoint | undefined =\n\t\t\t\tlayout.serverEndPoint && (await layout.serverEndPoint())?.default;\n\t\t\tconst layoutResponse = await loadClientAndServerData(\n\t\t\t\turl,\n\t\t\t\tparams,\n\t\t\t\tserverParams,\n\t\t\t\tdata,\n\t\t\t\tlayoutEndPoint,\n\t\t\t\tlayoutServerEndPoint,\n\t\t\t);\n\t\t\tif (layoutResponse?.ok === false) {\n\t\t\t\treturn layoutResponse;\n\t\t\t}\n\t\t}\n\t}\n\tlet endPointResponse = await loadClientAndServerData(\n\t\turl,\n\t\tparams,\n\t\tserverParams,\n\t\tdata,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\tif (endPointResponse?.ok === false) {\n\t\treturn endPointResponse;\n\t}\n\tlet $props: Record<string, any> = { data };\n\n\t// If there are layouts, work our way upwards, pushing each component into\n\t// the default slot of its parent\n\t// TODO: Also handle layout server data\n\t// TODO: There's probably a nicer way to do this with reducers or something\n\t// TODO: Re-use parent layouts\n\tlet component = clientEndPoint.component as ServerComponent;\n\tlet slots: Record<string, ServerSlotRender> | undefined = undefined;\n\tif (handler.layouts) {\n\t\tlet slotFunctions: ServerSlotRender[] = [];\n\t\tslotFunctions[handler.layouts.length] = (_, context) =>\n\t\t\t(clientEndPoint.component as ServerComponent)($props, context);\n\t\tfor (let i = handler.layouts.length - 1; i >= 0; i--) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())\n\t\t\t\t?.default;\n\t\t\tif (layoutEndPoint?.component) {\n\t\t\t\tif (i === 0) {\n\t\t\t\t\tcomponent = layoutEndPoint.component as ServerComponent;\n\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t} else {\n\t\t\t\t\tslotFunctions[i] = (_, context) =>\n\t\t\t\t\t\t(layoutEndPoint.component as ServerComponent)($props, context, {\n\t\t\t\t\t\t\t_: slotFunctions[i + 1],\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet html;\n\ttry {\n\t\tlet { body, head } = component($props, undefined, slots);\n\t\thtml = template.replace(\"%COMPONENT_BODY%\", body).replace(\"%COMPONENT_HEAD\", head);\n\t} catch (error) {\n\t\t// TODO: Show a proper Error component\n\t\thtml = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\tconsole.log(error);\n\t}\n\n\treturn new Response(html, {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"text/html\",\n\t\t},\n\t});\n}\n\nasync function runAction(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\tquery: URLSearchParams,\n) {\n\tconst actionName = (Array.from(query.keys())[0] || \"default\").replace(/^\\//, \"\");\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint()).default;\n\tif (serverEndPoint?.actions) {\n\t\tconst action = serverEndPoint.actions[actionName];\n\t\tif (action) {\n\t\t\t// TODO: form.errors etc\n\t\t\tconst serverParams = buildServerParams(ev, url, params);\n\n\t\t\tif (handler.serverHook) {\n\t\t\t\tconst serverHook: ServerHook | undefined = (await handler.serverHook()).default;\n\t\t\t\tif (serverHook?.handle) {\n\t\t\t\t\tawait serverHook.handle(serverParams);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await action(serverParams); // <--\n\n\t\t\t// If there was no response returned from the action (such as errors\n\t\t\t// or a redirect), reload the page by redirecting\n\t\t\treturn result || seeOther(url.pathname);\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadClientAndServerData(\n\turl: URL,\n\tparams: Record<string, string>,\n\tserverParams: ServerLoadEvent,\n\tdata: Record<string, any>,\n\tclientEndPoint?: PageEndPoint,\n\tserverEndPoint?: PageServerEndPoint,\n) {\n\tif (clientEndPoint?.load) {\n\t\tconst clientParams = buildClientParams(url, data, params);\n\t\tconst clientResponse = await clientEndPoint.load(clientParams);\n\t\tif (clientResponse) {\n\t\t\tif (clientResponse.ok) {\n\t\t\t\tif (clientResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await clientResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn clientResponse;\n\t\t\t}\n\t\t}\n\t}\n\tif (serverEndPoint?.load) {\n\t\tconst serverResponse = await serverEndPoint.load(serverParams);\n\t\tif (serverResponse) {\n\t\t\tif (serverResponse.ok) {\n\t\t\t\tif (serverResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\tObject.assign(data, await serverResponse.json());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn serverResponse;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tdata,\n\t};\n}\n\nfunction buildServerParams(\n\tev: ServerEvent,\n\turl: URL,\n\tparams: Record<string, string>,\n): ServerLoadEvent {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tappData: {},\n\t\trequest: ev.request,\n\t\tcookies: ev.cookies,\n\t\theaders: ev.headers,\n\t\tadapter: ev.adapter,\n\t};\n}\n"],"mappings":"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","names":["path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torpor/build",
3
- "version": "0.1.33",
3
+ "version": "0.2.0",
4
4
  "description": "A framework for building sites and apps with Torpor",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -52,17 +52,17 @@
52
52
  "license": "ISC",
53
53
  "dependencies": {
54
54
  "cookie": "^1.0.2",
55
- "dotenv": "^16.5.0",
56
- "vite": "^7.0.0",
55
+ "dotenv": "^17.2.1",
56
+ "vite": "^7.1.1",
57
57
  "vite-tsconfig-paths": "^5.1.4",
58
- "@torpor/view": "^0.1.10",
59
- "@torpor/unplugin": "^0.1.16"
58
+ "@torpor/view": "^0.2.0",
59
+ "@torpor/unplugin": "^0.1.17"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@trivago/prettier-plugin-sort-imports": "^5.2.2",
63
63
  "tsup": "^8.5.0",
64
64
  "tsx": "^4.20.3",
65
- "typescript": "^5.8.3",
65
+ "typescript": "^5.9.2",
66
66
  "vitest": "^3.2.4"
67
67
  },
68
68
  "scripts": {
@@ -3,7 +3,7 @@ import HeaderHelper from "./HeaderHelper";
3
3
 
4
4
  export default class ServerEvent {
5
5
  request: Request;
6
- response?: Response;
6
+ response: Response | undefined | void;
7
7
  // TODO: Should we cast types??
8
8
  params?: Record<string, string>;
9
9
 
@@ -0,0 +1,8 @@
1
+ // From https://stackoverflow.com/a/79345620
2
+ // No idea why it is needed
3
+ export default function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
4
+ return buffer.buffer.slice(
5
+ buffer.byteOffset,
6
+ buffer.byteOffset + buffer.byteLength,
7
+ ) as ArrayBuffer;
8
+ }
@@ -1,5 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
2
  import type MiddlewareFunction from "../types/MiddlewareFunction";
3
+ import bufferToArrayBuffer from "./bufferToArrayBuffer";
3
4
  import flattenHeaders from "./flattenHeaders";
4
5
  import nodeMessageToNodeResponse from "./nodeMessageToNodeResponse";
5
6
  import readableToBuffer from "./readableToBuffer";
@@ -27,7 +28,7 @@ export default function connectMiddleware(
27
28
  : //(Readable.toWeb(readable) as ReadableStream);
28
29
  // HACK: Chunked bodies are not being
29
30
  // read correctly somewhere...
30
- await readableToBuffer(readable);
31
+ bufferToArrayBuffer(await readableToBuffer(readable));
31
32
  ev.response = new Response(responseBody, {
32
33
  status,
33
34
  headers: flattenHeaders(headers),
@@ -1,5 +1,7 @@
1
1
  import ServerEvent from "../ServerEvent";
2
2
 
3
- type ServerFunction = (e: ServerEvent) => Response | Promise<Response | undefined> | undefined;
3
+ type ServerFunction = (
4
+ e: ServerEvent,
5
+ ) => Response | undefined | void | Promise<Response | undefined | void>;
4
6
 
5
7
  export default ServerFunction;
@@ -1,7 +1,7 @@
1
1
  import manifest from "@torpor/build/manifest";
2
2
  // @ts-ignore This errors in the Cloudflare build?
3
3
  import { $page } from "@torpor/build/state";
4
- import { clearLayoutSlot, hydrate, runLayoutSlot } from "@torpor/view";
4
+ import { clearLayoutSlot, fillLayoutSlot, hydrate } from "@torpor/view";
5
5
  import { type Component, type SlotRender } from "@torpor/view";
6
6
  import { mount } from "@torpor/view";
7
7
  import type PageEndPoint from "../types/PageEndPoint.ts";
@@ -31,8 +31,15 @@ let prefetchedData: Record<string, any> = {};
31
31
  // Intercept clicks on links
32
32
  window.addEventListener("click", async (e) => {
33
33
  if (e.target && (e.target as HTMLElement).tagName === "A") {
34
+ const link = e.target as HTMLAnchorElement;
35
+
36
+ if (ignoreAnchorMouseEvent(link, e)) {
37
+ return;
38
+ }
39
+
34
40
  e.preventDefault();
35
- const href = (e.target as HTMLLinkElement).href;
41
+
42
+ const href = link.href;
36
43
  const url = new URL(href);
37
44
 
38
45
  if (await navigate(url)) {
@@ -46,12 +53,17 @@ window.addEventListener("click", async (e) => {
46
53
  // Intercept mouseover/focus/touchstart on links for prefetching
47
54
  // TODO: settings e.g. <a href="" data-tb-prefetch="eager"/"visible"/"hover"/"touch">
48
55
  window.addEventListener("mouseover", maybePrefetch);
49
- window.addEventListener("focus", maybePrefetch);
50
56
  window.addEventListener("touchstart", maybePrefetch);
51
57
 
52
- async function maybePrefetch(e: Event) {
58
+ async function maybePrefetch(e: MouseEvent | TouchEvent) {
53
59
  if (e.target && (e.target as HTMLElement).tagName === "A") {
54
- const href = (e.target as HTMLLinkElement).href;
60
+ const link = e.target as HTMLAnchorElement;
61
+
62
+ if (ignoreAnchorMouseEvent(link, e)) {
63
+ return;
64
+ }
65
+
66
+ const href = link.href;
55
67
  const url = new URL(href);
56
68
 
57
69
  const path = url.pathname;
@@ -76,6 +88,19 @@ async function maybePrefetch(e: Event) {
76
88
  }
77
89
  }
78
90
 
91
+ function ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {
92
+ // Ignore the event if a modifier key is pressed, or target is not
93
+ // "_self", or if rel="external"
94
+ return (
95
+ e.altKey ||
96
+ e.ctrlKey ||
97
+ e.metaKey ||
98
+ e.shiftKey ||
99
+ (link.target && link.target !== "_self") ||
100
+ link.rel === "external"
101
+ );
102
+ }
103
+
79
104
  // Listen for changes to the URL that occur when the user navigates using the
80
105
  // back or forward buttons
81
106
  window.addEventListener("popstate", async () => {
@@ -161,17 +186,15 @@ async function navigate(url: URL, firstTime = false): Promise<boolean> {
161
186
  let slotFunctions: SlotRender[] = [];
162
187
  // The last slot function will render the client component
163
188
  slotFunctions[handler.layouts.length] = function clientComponent(parent, anchor, _, $context) {
164
- if (clientEndPoint.component) {
165
- let i = layoutStack.length - 1;
166
- layoutStack[i].slotRange = runLayoutSlot(
167
- clientEndPoint.component,
168
- slotFunctions[i + 1],
169
- parent,
170
- anchor,
171
- $props,
172
- $context,
173
- );
174
- }
189
+ let i = layoutStack.length - 1;
190
+ layoutStack[i].slotRange = fillLayoutSlot(
191
+ clientEndPoint.component!,
192
+ slotFunctions[i + 1],
193
+ parent,
194
+ anchor,
195
+ $props,
196
+ $context,
197
+ );
175
198
  };
176
199
 
177
200
  // Each earlier slot function will render a layout
@@ -192,16 +215,14 @@ async function navigate(url: URL, firstTime = false): Promise<boolean> {
192
215
  slots = { _: slotFunctions[i + 1] };
193
216
  } else {
194
217
  slotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {
195
- if (layoutEndPoint.component) {
196
- layoutStack[i - 1].slotRange = runLayoutSlot(
197
- layoutEndPoint.component,
198
- slotFunctions[i + 1],
199
- parent,
200
- anchor,
201
- $props,
202
- $context,
203
- );
204
- }
218
+ layoutStack[i - 1].slotRange = fillLayoutSlot(
219
+ layoutEndPoint.component!,
220
+ slotFunctions[i + 1],
221
+ parent,
222
+ anchor,
223
+ $props,
224
+ $context,
225
+ );
205
226
  };
206
227
  }
207
228
  }
@@ -239,6 +260,9 @@ async function loadData(
239
260
  if (handler.layouts) {
240
261
  for (let [i, layout] of handler.layouts.entries()) {
241
262
  let layoutPath = layout.path;
263
+ for (let key in params) {
264
+ layoutPath = layoutPath.replace(`[${key}]`, params[key]);
265
+ }
242
266
  if (layoutStack.at(i)?.path === layoutPath) {
243
267
  // We've already loaded this layout, we can just re-use its data and UI
244
268
  if (!prefetch) {
@@ -248,9 +272,6 @@ async function loadData(
248
272
  Object.assign(data, layoutStack[i].data);
249
273
  } else {
250
274
  const stackLayout = { path: layoutPath, data: {}, reuse: false, slotRange: null };
251
- for (let key in params) {
252
- layoutPath = layoutPath.replace(`[${key}]`, params[key]);
253
- }
254
275
  const layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;
255
276
  const layoutServerEndPoint: PageServerEndPoint | undefined =
256
277
  layout.serverEndPoint && (await layout.serverEndPoint())?.default;
@@ -288,7 +309,7 @@ async function loadClientAndServerData(
288
309
  params: Record<string, string>,
289
310
  clientEndPoint?: PageEndPoint,
290
311
  serverEndPoint?: PageServerEndPoint,
291
- ): Promise<Response | void> {
312
+ ): Promise<Response | undefined | void> {
292
313
  if (clientEndPoint?.load) {
293
314
  if (prefetchedData[location]) {
294
315
  Object.assign(data, prefetchedData[location]);
@@ -1,16 +1,28 @@
1
1
  export default function prepareTemplate(template: string, clientScript: string): string {
2
- let contentStart = regexIndexOf(template, /\<div\s+id=("app"|'app'|app)\s+/);
3
- contentStart = template.indexOf(">", contentStart) + 1;
4
- let contentEnd = template.indexOf("</div>", contentStart);
5
- if (contentStart === -1 || contentEnd === -1) {
2
+ let result = template;
3
+
4
+ // Put %COMPONENT_HEAD% before </head>
5
+ // This is where styles will go
6
+ let headStart = result.indexOf("</head>");
7
+ if (headStart === -1) {
8
+ throw new Error(`Couldn't find <head> end tag`);
9
+ }
10
+ result = result.substring(0, headStart) + "%COMPONENT_HEAD%" + result.substring(headStart);
11
+
12
+ // Put %COMPONENT_BODY% inside <div id="app"></div>
13
+ // This is where the component's HTML will go
14
+ let bodyStart = regexIndexOf(result, /\<div\s+id=("app"|'app'|app)\s+/);
15
+ bodyStart = result.indexOf(">", bodyStart) + 1;
16
+ let bodyEnd = result.indexOf("</div>", bodyStart);
17
+ if (bodyStart === -1 || bodyEnd === -1) {
6
18
  throw new Error(`Couldn't find <div id="app"></div>`);
7
19
  }
8
- return (
9
- template.substring(0, contentStart) +
10
- "%COMPONENT_HTML%" +
11
- template.substring(contentEnd) +
12
- `<script type="module" src="${clientScript}"></script>`
13
- );
20
+ result = result.substring(0, bodyStart) + "%COMPONENT_BODY%" + result.substring(bodyEnd);
21
+
22
+ // Add the clientEntry script at the very end, for hydrating and navigating
23
+ result += `<script type="module" src="${clientScript}"></script>`;
24
+
25
+ return result;
14
26
  }
15
27
 
16
28
  // From https://stackoverflow.com/a/274094
@@ -44,7 +44,8 @@ export default async function runPreview(site: Site): Promise<void> {
44
44
  const file = path.join(site.root, "dist", "client", url.pathname);
45
45
  if (existsSync(file)) {
46
46
  // TODO: Stream the data?
47
- return new Response(await fs.readFile(file), {
47
+ const body = bufferToArrayBuffer(await fs.readFile(file));
48
+ return new Response(body, {
48
49
  status: 200,
49
50
  headers: {
50
51
  "Content-Type": contentType(path.extname(file)),
@@ -81,3 +82,12 @@ export default async function runPreview(site: Site): Promise<void> {
81
82
  // Serve the site
82
83
  site.adapter.serve(server, site);
83
84
  }
85
+
86
+ // From https://stackoverflow.com/a/79345620
87
+ // No idea why it is needed
88
+ function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
89
+ return buffer.buffer.slice(
90
+ buffer.byteOffset,
91
+ buffer.byteOffset + buffer.byteLength,
92
+ ) as ArrayBuffer;
93
+ }