@torpor/build 1.4.1 → 1.4.3
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/dist/MiddlewareFunction-BZilnEei.d.mts.map +1 -1
- package/dist/{Server-CSti9cvM.mjs → Server-C0PGzSQO.mjs} +2 -2
- package/dist/{Server-CSti9cvM.mjs.map → Server-C0PGzSQO.mjs.map} +1 -1
- package/dist/{ServerEvent-Bh9cEpro.mjs → ServerEvent-BY4f3RVF.mjs} +10 -1
- package/dist/ServerEvent-BY4f3RVF.mjs.map +1 -0
- package/dist/bin/index.js +1 -1
- package/dist/dev.mjs +2 -2
- package/dist/run.mjs +1 -1
- package/dist/{runOpenApi-D3dWfh5a.mjs → runOpenApi-CbbrPi4E.mjs} +30 -5
- package/dist/runOpenApi-CbbrPi4E.mjs.map +1 -0
- package/dist/server/Server.mjs +1 -1
- package/dist/server.mjs +2 -2
- package/dist/{serverEntry-CnCVNNjp.mjs → serverEntry-D3uwudkX.mjs} +2 -2
- package/dist/{serverEntry-CnCVNNjp.mjs.map → serverEntry-D3uwudkX.mjs.map} +1 -1
- package/dist/serverEntry.mjs +1 -1
- package/dist/{serverHandlers-B6EcNfBP.mjs → serverHandlers-C8qzOSX-.mjs} +8 -7
- package/dist/serverHandlers-C8qzOSX-.mjs.map +1 -0
- package/dist/test.mjs +2 -2
- package/package.json +1 -1
- package/dist/ServerEvent-Bh9cEpro.mjs.map +0 -1
- package/dist/runOpenApi-D3dWfh5a.mjs.map +0 -1
- package/dist/serverHandlers-B6EcNfBP.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Router } from "./Router-C4ADUfhv.mjs";
|
|
2
2
|
import { o as setBasePath } from "./basePath-DQnbHc7o.mjs";
|
|
3
|
-
import { t as createServerLoad } from "./serverHandlers-
|
|
3
|
+
import { t as createServerLoad } from "./serverHandlers-C8qzOSX-.mjs";
|
|
4
4
|
import manifest from "@torpor/build/manifest";
|
|
5
5
|
//#region src/site/serverEntry.ts
|
|
6
6
|
setBasePath(manifest.base);
|
|
@@ -10,4 +10,4 @@ const load = createServerLoad(router, manifest.base, manifest.middleware ?? []);
|
|
|
10
10
|
//#endregion
|
|
11
11
|
export { router as n, load as t };
|
|
12
12
|
|
|
13
|
-
//# sourceMappingURL=serverEntry-
|
|
13
|
+
//# sourceMappingURL=serverEntry-D3uwudkX.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverEntry-
|
|
1
|
+
{"version":3,"file":"serverEntry-D3uwudkX.mjs","names":[],"sources":["../src/site/serverEntry.ts"],"sourcesContent":["import manifest from \"@torpor/build/manifest\";\nimport Router from \"./Router.ts\";\nimport { setBasePath } from \"./basePath.ts\";\nimport { type ServerLoad, createServerLoad } from \"./serverHandlers.ts\";\n\n// Put adapter-specific functionality in the `adapter` property of\n// globalThis for now\nsetBasePath(manifest.base);\n\n// Build the router from the Site object created by the user\nexport const router: Router = new Router();\nrouter.addPages(manifest.routes);\n\n//console.log(`routes:\\n ${router.routes.map((r) => r.path).join(\"\\n \")}`);\n\nexport const load: ServerLoad = createServerLoad(router, manifest.base, manifest.middleware ?? []);\n"],"mappings":";;;;;AAOA,YAAY,SAAS,IAAI;AAGzB,MAAa,SAAiB,IAAI,OAAO;AACzC,OAAO,SAAS,SAAS,MAAM;AAI/B,MAAa,OAAmB,iBAAiB,QAAQ,SAAS,MAAM,SAAS,cAAc,CAAC,CAAC"}
|
package/dist/serverEntry.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as router, t as load } from "./serverEntry-
|
|
1
|
+
import { n as router, t as load } from "./serverEntry-D3uwudkX.mjs";
|
|
2
2
|
export { load, router };
|
|
@@ -309,7 +309,7 @@ async function loadData(ev, url, handler, functionName, params) {
|
|
|
309
309
|
throw error;
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
-
async function loadView(ev, url, handler, params, template, formStatus, form, fromForm) {
|
|
312
|
+
async function loadView(ev, url, handler, params, template, formStatus, form, fromForm, appData) {
|
|
313
313
|
const clientEndPoint = (await resolveModule(handler)).default;
|
|
314
314
|
if (!clientEndPoint?.component) return notFound();
|
|
315
315
|
$page.flash = ev.flash.get();
|
|
@@ -338,7 +338,7 @@ async function loadView(ev, url, handler, params, template, formStatus, form, fr
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
-
const serverParams = buildServerParams(ev, url, params, query ? { query } : {});
|
|
341
|
+
const serverParams = buildServerParams(ev, url, params, query ? { query } : {}, appData);
|
|
342
342
|
const serverHooks = fromForm ? [] : await loadServerHooks(handler);
|
|
343
343
|
let entered = 0;
|
|
344
344
|
let enterResponse = void 0;
|
|
@@ -442,10 +442,11 @@ async function runAction(ev, url, handler, serverEndPoint, params, query, templa
|
|
|
442
442
|
throw error;
|
|
443
443
|
}
|
|
444
444
|
let result;
|
|
445
|
+
let serverParams = void 0;
|
|
445
446
|
try {
|
|
446
447
|
const schema = endpointSchema(serverEndPoint, actionName);
|
|
447
448
|
const form = schema ? await validate(schema, await formDataToRecord(ev.request)) : void 0;
|
|
448
|
-
|
|
449
|
+
serverParams = buildServerParams(ev, url, params, form ? { form } : {});
|
|
449
450
|
const serverHooks = await loadServerHooks(handler);
|
|
450
451
|
let entered = 0;
|
|
451
452
|
let enterResponse = void 0;
|
|
@@ -485,7 +486,7 @@ async function runAction(ev, url, handler, serverEndPoint, params, query, templa
|
|
|
485
486
|
} else $page.form = {};
|
|
486
487
|
$page.form.status ??= formStatus ?? 0;
|
|
487
488
|
$page.form.message ??= formMessage ?? "";
|
|
488
|
-
return await loadView(ev, newUrl, handler, params, template, formStatus, $page.form, true);
|
|
489
|
+
return await loadView(ev, newUrl, handler, params, template, formStatus, $page.form, true, serverParams?.appData);
|
|
489
490
|
} else return result;
|
|
490
491
|
}
|
|
491
492
|
}
|
|
@@ -517,11 +518,11 @@ function buildClientParams(url, params, data) {
|
|
|
517
518
|
data
|
|
518
519
|
};
|
|
519
520
|
}
|
|
520
|
-
function buildServerParams(ev, url, params, values = {}) {
|
|
521
|
+
function buildServerParams(ev, url, params, values = {}, appData = {}) {
|
|
521
522
|
return {
|
|
522
523
|
url,
|
|
523
524
|
params,
|
|
524
|
-
appData
|
|
525
|
+
appData,
|
|
525
526
|
request: ev.request,
|
|
526
527
|
json: () => values.json !== void 0 ? Promise.resolve(values.json) : ev.json(),
|
|
527
528
|
form: () => values.form !== void 0 ? Promise.resolve(values.form) : formDataToRecord(ev.request),
|
|
@@ -548,4 +549,4 @@ function escapeHtml(value) {
|
|
|
548
549
|
//#endregion
|
|
549
550
|
export { createServerLoad as t };
|
|
550
551
|
|
|
551
|
-
//# sourceMappingURL=serverHandlers-
|
|
552
|
+
//# sourceMappingURL=serverHandlers-C8qzOSX-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverHandlers-C8qzOSX-.mjs","names":[],"sources":["../src/form/formDataToRecord.ts","../src/state/$serverPage.ts","../src/utils/searchParamsToRecord.ts","../src/validation/endpoint.ts","../src/site/layoutSlots.ts","../src/site/mergeHead.ts","../src/site/serverHandlers.ts"],"sourcesContent":["import type { FormDataRecord } from \"../types/ServerLoadEvent\";\n\n/**\n * Converts the submitted fields of a form request into a plain record: one\n * value per field, or an array when a field was submitted with multiple\n * values (e.g. a multi select). File inputs pass through as `File` values;\n * coercing them is left to the schema or the action.\n *\n * @param request The request (or FormData directly)\n * @returns The field values\n */\nexport default async function formDataToRecord(\n\trequest: Request | FormData,\n): Promise<FormDataRecord> {\n\tconst formData = request instanceof FormData ? request : await request.formData();\n\tconst result: FormDataRecord = {};\n\tfor (const key of formData.keys()) {\n\t\tif (key in result) continue;\n\t\tconst values = formData.getAll(key);\n\t\tresult[key] = values.length > 1 ? values : values[0];\n\t}\n\treturn result;\n}\n","import { $watch } from \"@torpor/view/ssr\";\nimport type PageState from \"../types/PageState\";\n\nconst PAGE_SYMBOL: unique symbol = Symbol.for(\"torp.Page\");\n\n// The server request path has no reactive subscribers, so the state is\n// stored plainly ($watch from `@torpor/view/ssr` is a no-op that returns\n// the object itself) instead of through a reactive proxy. Shares the same\n// `globalThis` slot as the client `$page`, so both variants always see the\n// same state object whichever loads first.\nconst $page: PageState =\n\t// @ts-ignore\n\t(globalThis[PAGE_SYMBOL] ??= $watch({\n\t\tstatus: 404,\n\t\turl: new URL(\"http://localhost\"),\n\t\tform: undefined,\n\t\tflash: undefined,\n\t\terror: {\n\t\t\tmessage: \"\",\n\t\t},\n\t}));\n\nexport default $page;\n","import type { QueryRecord } from \"../types/ServerLoadEvent\";\n\n/**\n * Converts a URL's query string into a plain record: one string per param,\n * or an array when a param was repeated.\n *\n * @param searchParams The URL's search params\n * @returns The param values\n */\nexport default function searchParamsToRecord(searchParams: URLSearchParams): QueryRecord {\n\tconst result: QueryRecord = {};\n\tfor (const key of searchParams.keys()) {\n\t\tif (key in result) continue;\n\t\tconst values = searchParams.getAll(key);\n\t\tresult[key] = values.length > 1 ? values : values[0];\n\t}\n\treturn result;\n}\n","import type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport type ServerEndPoint from \"../types/ServerEndPoint\";\nimport type { StandardSchemaV1 } from \"../types/StandardSchema\";\nimport type ServerEvent from \"../server/ServerEvent\";\nimport searchParamsToRecord from \"../utils/searchParamsToRecord\";\nimport unprocessable from \"../response/unprocessable\";\nimport ValidationError from \"./ValidationError\";\nimport validate from \"./validate\";\n\n/**\n * Loose views of an endpoint's schema map, keyed by handler/action name (plus\n * the reserved `params` key). The type-level constraint differs per endpoint\n * type, but the runtime treats them the same.\n */\ntype Schemas = Record<string, StandardSchemaV1 | undefined>;\n\ntype EndPoint = ServerEndPoint | PageServerEndPoint;\n\n/**\n * Gets the standard schema declared for a handler or action, if any. The\n * schemas map is keyed by name; the runtime treats it as a loose record.\n */\nexport function endpointSchema(endPoint: EndPoint, name: string): StandardSchemaV1 | undefined {\n\treturn (endPoint.schema as Schemas | undefined)?.[name];\n}\n\n/**\n * Validates the route params against the endpoint's `params` schema, if it\n * declares one, returning the coerced params. Throws a ValidationError when\n * they don't match, which callers turn into a 404 (params come from the URL,\n * so a failed validation means the resource doesn't exist).\n *\n * @param endPoint The endpoint declaring the (optional) `params` schema\n * @param params The route params from the matched route\n * @returns The validated params\n */\nexport async function validateEndpointParams(\n\tendPoint: EndPoint,\n\tparams: Record<string, any>,\n): Promise<Record<string, any>> {\n\tconst schema = endpointSchema(endPoint, \"params\");\n\treturn schema ? ((await validate(schema, params)) as Record<string, any>) : params;\n}\n\n/**\n * Validates a handler's input against its schema: get/head handlers and load\n * functions validate the URL's query string, other handlers validate the JSON\n * request body. Returns the validated values (an empty object when no schema\n * is declared), which callers pass through to the handler's event.\n *\n * @param schema The handler's (optional) schema\n * @param functionName The handler name (`get`, `post`, `load`, etc)\n * @param url The request URL, for the query string\n * @param ev The server event, for the request body\n * @returns The validated values\n */\nexport async function validateHandlerInput(\n\tschema: StandardSchemaV1 | undefined,\n\tfunctionName: string,\n\turl: URL,\n\tev: ServerEvent,\n): Promise<{ json?: unknown; form?: unknown; query?: unknown }> {\n\tif (!schema) return {};\n\tif (functionName === \"get\" || functionName === \"head\" || functionName === \"load\") {\n\t\treturn { query: await validate(schema, searchParamsToRecord(url.searchParams)) };\n\t}\n\treturn { json: await validate(schema, await ev.json()) };\n}\n\n/**\n * Turns a ValidationError thrown during endpoint validation into a 422\n * response with the schema's issues. Returns undefined for any other error,\n * which callers should rethrow.\n *\n * @param error The error thrown by a handler or validation\n * @returns A 422 response, or undefined when the error isn't a validation failure\n */\nexport function validationErrorResponse(error: unknown): Response | undefined {\n\tif (error instanceof ValidationError) {\n\t\treturn unprocessable({ message: error.message, issues: error.issues });\n\t}\n\treturn undefined;\n}\n","/**\n * Layouts are composed by the layout engine passing each layout (and finally\n * the page) as the default slot of its parent — so every `_layout` component\n * must render `<slot />`, or pass the default slot down to a child component\n * it renders. A layout that doesn't drops its entire content: the server\n * renders the layout's own markup with an empty content area (no error), and\n * the first client-side navigation that reuses the layout crashes on a null\n * slot region.\n *\n * The static `checkLayoutSlots` check catches this at build/dev time; these\n * helpers report it at request time for the cases static analysis can't see\n * (e.g. a `<slot />` rendered conditionally inside `@if`). They are\n * server-side only — nothing here is imported into the client bundle.\n */\n\n/**\n * Returns the path of the outermost layout that never rendered its\n * `<slot />`, or undefined when every level rendered.\n *\n * Slot level j (1-based) is the slot render that layout j - 1 receives: the\n * root layout (layouts[0]) renders level 1, layout i renders level i + 1,\n * and level layouts.length renders the page. A broken level makes every\n * deeper level unreachable, so the first level that never ran identifies\n * the offending layout.\n */\nexport function findMissingSlotLayout(\n\tlayouts: readonly { path: string }[],\n\tinvokedLevels: ReadonlySet<number>,\n): string | undefined {\n\tfor (let level = 1; level <= layouts.length; level++) {\n\t\tif (!invokedLevels.has(level)) {\n\t\t\treturn layouts[level - 1].path;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst warnedLayouts = new Set<string>();\n\n/**\n * Reports a layout that dropped its slot content. The condition is a genuine\n * rendering bug (the page content is missing from the output), so it is not\n * dev-gated — but it warns only once per layout path to avoid per-request\n * noise.\n */ export function warnMissingSlotContent(layoutPath: string): void {\n\tif (warnedLayouts.has(layoutPath)) return;\n\twarnedLayouts.add(layoutPath);\n\tconsole.warn(\n\t\t`[torpor] The layout at \"${layoutPath}\" never rendered <slot />, so the page content ` +\n\t\t\t`was not included in the render. Every _layout component must render <slot /> (or ` +\n\t\t\t`pass the default slot down to a child component it renders).`,\n\t);\n}\n","const TITLE_REGEX = /<title(?:\\s[^>]*)?>[\\s\\S]*?<\\/title>/g;\nconst FIRST_TITLE_REGEX = /<title(?:\\s[^>]*)?>[\\s\\S]*?<\\/title>/;\n\n/**\n * Merges the head string rendered by a page (and its layouts) into the site\n * template. The first <title> in the head string -- the page's, since pages\n * render before the layouts around them -- wins, overriding both any later\n * component title (e.g. from a child component) and a static <title> in the\n * template itself. All other head content (styles, meta elements, etc) is\n * appended where %COMPONENT_HEAD% sits in the template, before </head>.\n */\nexport default function mergeHead(template: string, head: string): string {\n\tconst titles = head.match(TITLE_REGEX);\n\tlet html = template;\n\tlet headHtml = head;\n\tif (titles?.length) {\n\t\theadHtml = head.replace(TITLE_REGEX, \"\");\n\t\thtml = html.replace(FIRST_TITLE_REGEX, \"\");\n\t\theadHtml = titles[0] + headHtml;\n\t}\n\treturn html.replace(\"%COMPONENT_HEAD%\", headHtml);\n}\n","import { type ServerComponent, type ServerSlotRender } from \"@torpor/view/ssr\";\nimport formDataToRecord from \"../form/formDataToRecord.ts\";\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 type MiddlewareFunction from \"../server/types/MiddlewareFunction\";\nimport $page from \"../state/$serverPage.ts\";\nimport type PageEndPoint from \"../types/PageEndPoint.ts\";\nimport type { HeadElement } from \"../types/PageEndPoint.ts\";\nimport type PageLoadEvent from \"../types/PageLoadEvent.ts\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint.ts\";\nimport type RouteHandler from \"../types/RouteHandler.ts\";\nimport type ServerRequest from \"../types/ServerRequest.ts\";\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\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\";\nimport searchParamsToRecord from \"../utils/searchParamsToRecord.ts\";\nimport validate from \"../validation/validate.ts\";\nimport ValidationError from \"../validation/ValidationError.ts\";\nimport {\n\tendpointSchema,\n\tvalidateEndpointParams,\n\tvalidationErrorResponse,\n} from \"../validation/endpoint.ts\";\nimport { findMissingSlotLayout, warnMissingSlotContent } from \"./layoutSlots.ts\";\nimport { getBasePath, rewriteBaseInHtml, setBasePath, stripBaseFromUrl } from \"./basePath.ts\";\nimport mergeHead from \"./mergeHead.ts\";\nimport type Router from \"./Router.ts\";\n\n/**\n * The site's request handler, as created by `createServerLoad`.\n */\nexport type ServerLoad = (ev: ServerEvent, template?: string) => Promise<Response>;\n\n/**\n * Creates the site's request handler for the given router. This is kept\n * separate from serverEntry, which imports the virtual\n * `@torpor/build/manifest` module (it only exists when a site runs or\n * builds), so that tests can exercise the handlers with their own routes.\n *\n * @param router The router, built from the site's routes\n * @param basePath The site's base path, stripped from incoming URLs before\n * routing (see basePath.ts)\n * @param middleware Global middleware (from `site.middleware`), run for\n * every request before routing -- including requests that won't match a\n * route\n * @returns The request handler, which takes the site template\n */\nexport function createServerLoad(\n\trouter: Router,\n\tbasePath = \"\",\n\tmiddleware: MiddlewareFunction[] = [],\n): ServerLoad {\n\t// The base path is shared through module state, so that everything that\n\t// generates URLs (HTML attributes, redirect locations) sees the same\n\t// value the router strips\n\tsetBasePath(basePath);\n\n\tconst handle = async (ev: ServerEvent, template?: string): Promise<Response> => {\n\t\t// The base is stripped before routing, so that everything below (route\n\t\t// matching, params, user code reading `event.url`) is base-free.\n\t\t// Requests that don't carry the base aren't ours\n\t\tconst url = stripBaseFromUrl(ev.url, basePath);\n\t\tif (!url) {\n\t\t\treturn handleResponse(notFound());\n\t\t}\n\t\tconst path = url.pathname;\n\t\tconst query = url.searchParams;\n\n\t\t//console.log(`handling ${ev.request.method} for '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\t\tlet route = router.match(path, query);\n\t\tif (!route) {\n\t\t\t// If the route wasn't found, the user may be posting a form to a\n\t\t\t// +page.server.ts route that doesn't have a corresponding +page.ts\n\t\t\t// route, which is allowed, so check for that as well\n\t\t\tif (ev.request.method === \"POST\" && !path.endsWith(\"/~server\")) {\n\t\t\t\tconst serverPath =\n\t\t\t\t\t(path.endsWith(\"/\") ? path.substring(0, path.length - 1) : path) + \"/~server\";\n\t\t\t\troute = router.match(serverPath, query);\n\t\t\t}\n\t\t}\n\t\tif (!route) {\n\t\t\treturn handleResponse(notFound());\n\t\t}\n\t\tconst handler = route.handler;\n\t\tconst params = route.params || {};\n\n\t\t// Route middleware, declared on the route's server endpoint\n\t\t// (+page.server.ts / +server.ts). Runs after the global middleware and\n\t\t// before folder hooks, so a guard can skip data loading entirely\n\t\tconst routeMiddleware: MiddlewareFunction[] = [];\n\t\tconst endPointModule = handler.serverEndPoint\n\t\t\t? (await handler.serverEndPoint())?.default\n\t\t\t: handler.type === PAGE_SERVER_ROUTE || handler.type === SERVER_ROUTE\n\t\t\t\t? (await resolveModule(handler))?.default\n\t\t\t\t: undefined;\n\t\tif (Array.isArray(endPointModule?.middleware)) {\n\t\t\trouteMiddleware.push(...endPointModule.middleware);\n\t\t}\n\n\t\t// Update $page before building the components\n\t\t$page.url = url;\n\t\tif (path.endsWith(\"/_error\")) {\n\t\t\t$page.status = parseInt(query.get(\"status\") ?? \"404\");\n\t\t\t$page.error = { message: query.get(\"message\") ?? \"\" };\n\t\t} else {\n\t\t\t$page.status = 200;\n\t\t}\n\n\t\t// TODO: Hit the server hook out here\n\t\t// We could maybe set up data loading as middleware on a route??????\n\n\t\t// Run the route's middleware around the handler: enter hooks in order\n\t\t// (a Response short-circuits the request), exit hooks in reverse\n\t\tlet enteredRoute = 0;\n\t\tlet routeResponse: Response | undefined = undefined;\n\t\ttry {\n\t\t\tfor (; enteredRoute < routeMiddleware.length && !routeResponse; enteredRoute++) {\n\t\t\t\tconst result = await routeMiddleware[enteredRoute].enter?.(ev);\n\t\t\t\tif (result) {\n\t\t\t\t\trouteResponse = result;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (routeResponse) {\n\t\t\t\treturn routeResponse;\n\t\t\t}\n\n\t\t\tswitch (handler.type) {\n\t\t\t\tcase PAGE_ROUTE:\n\t\t\t\tcase LAYOUT_ROUTE: {\n\t\t\t\t\t// It's a /+page.ts or /_layout.ts endpoint\n\t\t\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\t\t\treturn handleResponse(await loadView(ev, url, handler, params, template));\n\t\t\t\t\t} else if (ev.request.method === \"POST\") {\n\t\t\t\t\t\t// The server end point comes from the sibling /+page.server.ts, if applicable\n\t\t\t\t\t\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\t\t\t\t\t\thandler.serverEndPoint && (await handler.serverEndPoint()).default;\n\t\t\t\t\t\treturn handleResponse(\n\t\t\t\t\t\t\tawait runAction(ev, url, handler, serverEndPoint, params, query, template),\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase PAGE_SERVER_ROUTE:\n\t\t\t\tcase LAYOUT_SERVER_ROUTE: {\n\t\t\t\t\t// It's a /+page.server.ts or /_layout.server.ts endpoint\n\t\t\t\t\tif (ev.request.method === \"GET\") {\n\t\t\t\t\t\treturn await loadData(ev, url, handler, \"load\", params);\n\t\t\t\t\t} else if (ev.request.method === \"POST\") {\n\t\t\t\t\t\tconst serverEndPoint: PageServerEndPoint | undefined = (await resolveModule(handler))\n\t\t\t\t\t\t\t.default;\n\t\t\t\t\t\treturn handleResponse(\n\t\t\t\t\t\t\tawait runAction(ev, url, handler, serverEndPoint, params, query, template),\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase SERVER_ROUTE: {\n\t\t\t\t\t// It's a /+server.ts endpoint\n\t\t\t\t\tconst functionName = ev.request.method.toLowerCase().replace(\"delete\", \"del\");\n\t\t\t\t\treturn await loadData(ev, url, handler, functionName, params);\n\t\t\t\t}\n\t\t\t\tcase HOOK_ROUTE:\n\t\t\t\tcase HOOK_SERVER_ROUTE: {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase ERROR_ROUTE: {\n\t\t\t\t\treturn await loadView(ev, url, handler, params, template);\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\twhile (enteredRoute > 0) {\n\t\t\t\tenteredRoute--;\n\t\t\t\tawait routeMiddleware[enteredRoute].exit?.(ev);\n\t\t\t}\n\t\t}\n\n\t\treturn notFound();\n\t};\n\n\treturn async function load(ev: ServerEvent, template?: string): Promise<Response> {\n\t\t// Global middleware, from `site.middleware`: runs for every request\n\t\t// before routing, including ones that won't match a route\n\t\t// (maintenance mode, legacy url redirects, etc)\n\t\tlet entered = 0;\n\t\ttry {\n\t\t\tlet response: Response | undefined = undefined;\n\t\t\tfor (; entered < middleware.length && !response; entered++) {\n\t\t\t\tconst result = await middleware[entered].enter?.(ev);\n\t\t\t\tif (result) {\n\t\t\t\t\tresponse = result;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (response) {\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\treturn await handle(ev, template);\n\t\t} catch (error) {\n\t\t\t// Middleware exits can inspect (and replace, in Server.fetch) the\n\t\t\t// error via `ev.error`\n\t\t\tev.error = error;\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\twhile (entered > 0) {\n\t\t\t\tentered--;\n\t\t\t\tawait middleware[entered].exit?.(ev);\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Resolves the route's endpoint module, caching it on the handler so warm\n * requests don't pay for the loader promise.\n */\nasync function resolveModule(handler: RouteHandler): Promise<any> {\n\treturn (handler.resolvedModule ??= await (handler.modulePromise ??= handler.endPoint()));\n}\n\nasync function loadServerHooks(handler: RouteHandler): Promise<ServerHook[]> {\n\tif (!handler.serverHooks) return [];\n\t// Resolve each hook module once and cache the result on the handler\n\treturn (handler.resolvedHooks ??= Promise.all(\n\t\thandler.serverHooks.map(async (load) => (await load()).default),\n\t));\n}\n\nasync function handleResponse(response: Response, fromForm = false): Promise<Response> {\n\t$page.status = response.status;\n\n\t// Success codes and redirect codes are acceptable\n\tif (response.status >= 200 && response.status <= 399) {\n\t\treturn response;\n\t}\n\n\t// 4xx error codes are acceptable from form actions\n\tif (fromForm && response.status >= 400 && response.status <= 499) {\n\t\treturn response;\n\t}\n\n\t// It's an error, so redirect to the error page\n\t// We're just pushing the status and message in the URL, but maybe there's a\n\t// more sophisticated way to do this?\n\t// TODO: Should be returning loadView rather than redirecting\n\t// TODO: Should be returning the NEAREST error page to this path, including layouts\n\tlet params = new URLSearchParams();\n\tparams.append(\"status\", $page.status.toString());\n\tlet message = await response.text();\n\tif (response.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\tconst data = JSON.parse(message);\n\t\tmessage = data.message ?? message;\n\t}\n\tif (message) {\n\t\tif ($page.error) {\n\t\t\t$page.error.message = message;\n\t\t} else {\n\t\t\t$page.error = { message };\n\t\t}\n\t\tparams.append(\"message\", message);\n\t}\n\treturn seeOther(`/_error?${params.toString()}`);\n}\n\nasync function loadData(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tfunctionName: string,\n\tparams: Record<string, any>,\n) {\n\t// The endpoint module is cached on the handler, so once warm this is a\n\t// plain property read\n\tconst mod = handler.resolvedModule ?? (await resolveModule(handler));\n\tconst serverEndPoint: ServerEndPoint | undefined = mod?.default;\n\t// `functionName` is computed from the request method, so the handlers are\n\t// accessed through a loose record\n\tconst handlerFn = (\n\t\tserverEndPoint as unknown as Record<string, ServerRequest | undefined> | undefined\n\t)?.[functionName];\n\tif (!serverEndPoint || !handlerFn) {\n\t\treturn notFound();\n\t}\n\n\t// Route params come from the URL, so a failed validation means the\n\t// resource doesn't exist. Only endpoints declaring a params schema pay\n\t// for validation\n\tconst paramsSchema = endpointSchema(serverEndPoint, \"params\");\n\tif (paramsSchema) {\n\t\ttry {\n\t\t\tparams = (await validate(paramsSchema, params)) as Record<string, any>;\n\t\t} catch (error) {\n\t\t\tif (error instanceof ValidationError) return notFound();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\ttry {\n\t\t// If the endpoint declares a schema for this handler, validate its\n\t\t// input up front and reject with 422 if it fails. get/head/load\n\t\t// schemas validate the query string; other schemas validate the json\n\t\t// body. Endpoints without a schema skip straight to the handler\n\t\tconst schema = endpointSchema(serverEndPoint, functionName);\n\t\tconst values = schema\n\t\t\t? functionName === \"get\" || functionName === \"head\" || functionName === \"load\"\n\t\t\t\t? { query: await validate(schema, searchParamsToRecord(url.searchParams)) }\n\t\t\t\t: { json: await validate(schema, await ev.json()) }\n\t\t\t: {};\n\n\t\tconst serverParams = buildServerParams(ev, url, params, values);\n\n\t\t// Hooks are resolved once per handler; routes without hooks don't\n\t\t// await anything here\n\t\tconst hooks = handler.serverHooks ? await loadServerHooks(handler) : [];\n\n\t\tlet entered = 0;\n\t\tlet enterResponse: Response | undefined = undefined;\n\t\ttry {\n\t\t\t// Hooks run from the root down; a hook's enter can return a\n\t\t\t// Response to short-circuit the request\n\t\t\tfor (; entered < hooks.length && !enterResponse; entered++) {\n\t\t\t\tconst hookResult = await hooks[entered].enter?.(serverParams);\n\t\t\t\tif (hookResult) {\n\t\t\t\t\tenterResponse = hookResult;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// A hook's enter response short-circuits the request; otherwise, if\n\t\t\t// there was no response returned from load (such as errors or a\n\t\t\t// redirect), send an ok response\n\t\t\treturn enterResponse || (await handlerFn(serverParams)) || ok();\n\t\t} finally {\n\t\t\t// Run exit hooks in reverse order for hooks that were entered\n\t\t\twhile (entered > 0) {\n\t\t\t\tentered--;\n\t\t\t\tawait hooks[entered].exit?.(serverParams);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst response = validationErrorResponse(error);\n\t\tif (response) return response;\n\t\tthrow error;\n\t}\n}\n\nasync function loadView(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, any>,\n\ttemplate: string | undefined,\n\tformStatus?: number,\n\tform?: Record<string, string | number>,\n\tfromForm?: boolean,\n\t// The appData that the server hooks populated around the form action,\n\t// passed through so the re-render's loads see the same data\n\tappData?: Record<string, any>,\n) {\n\t// There must be a client endpoint with a component\n\tconst clientEndPoint: PageEndPoint | undefined = (await resolveModule(handler)).default;\n\tif (!clientEndPoint?.component) {\n\t\treturn notFound();\n\t}\n\n\t// Consume the flash message when a page is rendered (but not for API\n\t// requests), so that the following page -- and only that page -- can show\n\t// it. For a form action that re-renders instead of redirecting, the\n\t// message is set and read within this request\n\t$page.flash = ev.flash.get();\n\n\t// The template comes from site.html, which endpoints-only sites don't have\n\tif (!template) {\n\t\tthrow new Error(\n\t\t\t\"Page routes require a src/site.html file, which was not found; add it, or remove page/layout routes to serve endpoints only\",\n\t\t);\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// Validate the route params and the load query against the server\n\t// endpoint's schemas, if it declares any. Params come from the URL, so a\n\t// failed validation means the resource doesn't exist; a failed query goes\n\t// to the error page like any other load failure\n\tlet query: unknown;\n\tif (serverEndPoint) {\n\t\ttry {\n\t\t\tparams = await validateEndpointParams(serverEndPoint, params);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ValidationError) return notFound();\n\t\t\tthrow error;\n\t\t}\n\t\tconst loadSchema = endpointSchema(serverEndPoint, \"load\");\n\t\tif (loadSchema) {\n\t\t\tconst queryRecord = searchParamsToRecord(url.searchParams);\n\t\t\ttry {\n\t\t\t\tquery = await validate(loadSchema, queryRecord);\n\t\t\t} catch (error) {\n\t\t\t\t// A form re-render's url is the POST's, and a form action can\n\t\t\t\t// drop query params that the load schema requires, so failing\n\t\t\t\t// validation here would lose the form errors. Fall back to the\n\t\t\t\t// raw query values instead and let the load make of them what\n\t\t\t\t// it can\n\t\t\t\tif (fromForm) {\n\t\t\t\t\tquery = queryRecord;\n\t\t\t\t} else {\n\t\t\t\t\tconst response = validationErrorResponse(error);\n\t\t\t\t\tif (response) return response;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Maybe hit the server hooks -- unless they already ran for this request,\n\t// which is the case when the view is re-rendered after a form action. In\n\t// that case the hooks' appData is passed through, so that the loads below\n\t// see the same data (e.g. the user set by an auth hook) as the action did\n\tconst serverParams = buildServerParams(ev, url, params, query ? { query } : {}, appData);\n\tconst serverHooks = fromForm ? [] : await loadServerHooks(handler);\n\tlet entered = 0;\n\tlet enterResponse: Response | undefined = undefined;\n\ttry {\n\t\t// Hooks run from the root down; a hook's enter can return a Response\n\t\t// to short-circuit the request\n\t\tfor (; entered < serverHooks.length && !enterResponse; entered++) {\n\t\t\tconst hookResult = await serverHooks[entered].enter?.(serverParams);\n\t\t\tif (hookResult) {\n\t\t\t\tenterResponse = hookResult;\n\t\t\t}\n\t\t}\n\t\tif (enterResponse) {\n\t\t\treturn enterResponse;\n\t\t}\n\n\t\t// Pass the data into $props\n\t\t// TODO: Promise.all\n\t\t// NOTE: We're loading data from top to bottom, overriding as we go, and I'm\n\t\t// not sure if this is the best way to go\n\t\tlet data = {};\n\t\tif (handler.layouts) {\n\t\t\tfor (let layout of handler.layouts) {\n\t\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;\n\t\t\t\tconst layoutServerEndPoint: PageServerEndPoint | undefined =\n\t\t\t\t\tlayout.serverEndPoint && (await layout.serverEndPoint())?.default;\n\t\t\t\tconst layoutResponse = await loadClientAndServerData(\n\t\t\t\t\turl,\n\t\t\t\t\tparams,\n\t\t\t\t\tserverParams,\n\t\t\t\t\tdata,\n\t\t\t\t\tlayoutEndPoint,\n\t\t\t\t\tlayoutServerEndPoint,\n\t\t\t\t);\n\t\t\t\tif (layoutResponse?.ok === false) {\n\t\t\t\t\treturn layoutResponse;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet endPointResponse = await loadClientAndServerData(\n\t\t\turl,\n\t\t\tparams,\n\t\t\tserverParams,\n\t\t\tdata,\n\t\t\tclientEndPoint,\n\t\t\tserverEndPoint,\n\t\t);\n\t\tif (endPointResponse?.ok === false) {\n\t\t\treturn endPointResponse;\n\t\t}\n\t\tlet $props: Record<string, any> = { data, form };\n\n\t\tlet styles = \"\";\n\n\t\t// If there are layouts, work our way upwards, pushing each component into\n\t\t// the default slot of its parent\n\t\t// TODO: Also handle layout server data\n\t\t// TODO: There's probably a nicer way to do this with reducers or something\n\t\tlet component = clientEndPoint.component as ServerComponent;\n\t\tlet slots: Record<string, ServerSlotRender> | undefined = undefined;\n\t\t// Which slot levels actually ran during the render below, so a layout\n\t\t// that never rendered its <slot /> (silently dropping the page\n\t\t// content) can be reported (see findMissingSlotLayout)\n\t\tconst invokedLevels = new Set<number>();\n\t\tconst trackSlot = (level: number, slot: ServerSlotRender): ServerSlotRender => {\n\t\t\treturn ($slot, $context) => {\n\t\t\t\tinvokedLevels.add(level);\n\t\t\t\treturn slot($slot, $context);\n\t\t\t};\n\t\t};\n\t\tif (handler.layouts) {\n\t\t\tlet slotFunctions: ServerSlotRender[] = [];\n\t\t\t// The last slot function will render the client component\n\t\t\tslotFunctions[handler.layouts.length] = trackSlot(\n\t\t\t\thandler.layouts.length,\n\t\t\t\tasync (_, $context) => {\n\t\t\t\t\tlet { body, head } = await (clientEndPoint.component as ServerComponent)(\n\t\t\t\t\t\t$props,\n\t\t\t\t\t\t$context,\n\t\t\t\t\t);\n\t\t\t\t\tstyles += head;\n\t\t\t\t\treturn body;\n\t\t\t\t},\n\t\t\t);\n\t\t\tfor (let i = handler.layouts.length - 1; i >= 0; i--) {\n\t\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())\n\t\t\t\t\t?.default;\n\t\t\t\tif (layoutEndPoint?.component) {\n\t\t\t\t\tif (i === 0) {\n\t\t\t\t\t\tcomponent = layoutEndPoint.component as ServerComponent;\n\t\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t\t} else {\n\t\t\t\t\t\tslotFunctions[i] = trackSlot(i, async (_, $context) => {\n\t\t\t\t\t\t\tlet { body, head } = await (layoutEndPoint.component as ServerComponent)(\n\t\t\t\t\t\t\t\t$props,\n\t\t\t\t\t\t\t\t$context,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t_: slotFunctions[i + 1],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tstyles += head;\n\t\t\t\t\t\t\treturn body;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet html;\n\t\ttry {\n\t\t\tlet { body, head } = await component($props, undefined, slots);\n\n\t\t\t// Render the page's (and its layouts') head element data, if the\n\t\t\t// endpoint declared any. Layouts lose to the page (the nearest\n\t\t\t// endpoint wins, as with @head content), and everything loses to\n\t\t\t// the first title in the compiled head -- which mergeHead keeps\n\t\t\tlet headData = \"\";\n\t\t\tconst event = { url, params, data } as PageLoadEvent;\n\t\t\tconst layoutEndPoints = await Promise.all(\n\t\t\t\t(handler.layouts ?? []).map(async (layout) => await layout.endPoint()),\n\t\t\t);\n\t\t\tconst headPoints = [clientEndPoint, ...layoutEndPoints].reverse();\n\t\t\tfor (const endPoint of headPoints) {\n\t\t\t\tconst headElements =\n\t\t\t\t\ttypeof endPoint.head === \"function\" ? await endPoint.head(event) : endPoint.head;\n\t\t\t\tif (headElements) {\n\t\t\t\t\theadData += headElements.map((element: HeadElement) => markupOf(element)).join(\"\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Put the form info in a hidden input so that it can be accessed on the client\n\t\t\tif (form) {\n\t\t\t\tbody += `\\n<input type=\"hidden\" id=\"t-form-data\" value='${JSON.stringify(form).replaceAll(\"'\", \"\\\\'\")}' />`;\n\t\t\t}\n\n\t\t\tstyles += head;\n\t\t\thtml = rewriteBaseInHtml(\n\t\t\t\tmergeHead(template, styles + headData).replace(\"%COMPONENT_BODY%\", body),\n\t\t\t\tgetBasePath(),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// TODO: Show a proper Error component\n\t\t\thtml = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\t\tconsole.log(error);\n\t\t}\n\n\t\t// A layout that never rendered <slot /> silently drops the whole page\n\t\t// content from the output; find and report the outermost one\n\t\tif (handler.layouts) {\n\t\t\tconst missingLayout = findMissingSlotLayout(handler.layouts, invokedLevels);\n\t\t\tif (missingLayout !== undefined) {\n\t\t\t\twarnMissingSlotContent(missingLayout);\n\t\t\t}\n\t\t}\n\n\t\treturn new Response(html, {\n\t\t\tstatus: formStatus ?? 200,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t},\n\t\t});\n\t} finally {\n\t\t// Run exit hooks in reverse order for hooks that were entered\n\t\twhile (entered > 0) {\n\t\t\tentered--;\n\t\t\tawait serverHooks[entered].exit?.(serverParams);\n\t\t}\n\t}\n}\n\nasync function runAction(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tserverEndPoint: PageServerEndPoint | undefined,\n\tparams: Record<string, any>,\n\tquery: URLSearchParams,\n\ttemplate: string | undefined,\n) {\n\t// The action name rides in the query, so look for a `?/name` key rather\n\t// than taking the first key -- the url may carry ordinary load query\n\t// params as well (e.g. `?page=3&/like`, or a default action posted to a\n\t// url that already has a query). A key that names a declared action also\n\t// matches (e.g. `?set`), and a form without an action attribute calls the\n\t// `default` action\n\tconst queryKeys = Array.from(query.keys());\n\tconst actionKey =\n\t\tqueryKeys.find((key) => key.startsWith(\"/\")) ??\n\t\tqueryKeys.find((key) => !!serverEndPoint?.actions?.[key.replace(/^\\//, \"\")]);\n\tconst actionName = (actionKey ?? \"default\").replace(/^\\//, \"\");\n\tif (serverEndPoint?.actions) {\n\t\tconst action = serverEndPoint.actions[actionName];\n\t\tif (action) {\n\t\t\tif (actionName === \"load\" || actionName === \"params\") {\n\t\t\t\t// These names are reserved in the endpoint's schema map: `load`\n\t\t\t\t// validates the query string and `params` the route params, so\n\t\t\t\t// an action with one of these names would get the wrong schema\n\t\t\t\t// applied to its form data\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`The action name \"${actionName}\" is reserved. ` +\n\t\t\t\t\t\t`Rename the action (and its schema key, if any), e.g. to \"save\".`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Route params come from the URL, so a failed validation means the\n\t\t\t// resource doesn't exist\n\t\t\ttry {\n\t\t\t\tparams = await validateEndpointParams(serverEndPoint, params);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ValidationError) return notFound();\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tlet result: Response | undefined | void;\n\t\t\t// The event that the hooks and action run with. It's kept around so\n\t\t\t// that the view re-render below can pass its appData (populated by\n\t\t\t// the hooks' enter functions) through to the loads\n\t\t\tlet serverParams: ServerLoadEvent | undefined = undefined;\n\t\t\ttry {\n\t\t\t\t// If the endpoint declares a schema for this action, validate the\n\t\t\t\t// submitted form data up front and reject with 422 if it fails\n\t\t\t\tconst schema = endpointSchema(serverEndPoint, actionName);\n\t\t\t\tconst form = schema\n\t\t\t\t\t? await validate(schema, await formDataToRecord(ev.request))\n\t\t\t\t\t: undefined;\n\n\t\t\t\tserverParams = buildServerParams(ev, url, params, form ? { form } : {});\n\n\t\t\t\tconst serverHooks = await loadServerHooks(handler);\n\t\t\t\tlet entered = 0;\n\t\t\t\tlet enterResponse: Response | undefined = undefined;\n\t\t\t\ttry {\n\t\t\t\t\t// Hooks run from the root down; a hook's enter can return a\n\t\t\t\t\t// Response to short-circuit the action\n\t\t\t\t\tfor (; entered < serverHooks.length && !enterResponse; entered++) {\n\t\t\t\t\t\tconst hookResult = await serverHooks[entered].enter?.(serverParams);\n\t\t\t\t\t\tif (hookResult) {\n\t\t\t\t\t\t\tenterResponse = hookResult;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresult = enterResponse || (await action(serverParams));\n\t\t\t\t} finally {\n\t\t\t\t\t// Run exit hooks in reverse order for hooks that were entered\n\t\t\t\t\twhile (entered > 0) {\n\t\t\t\t\t\tentered--;\n\t\t\t\t\t\tawait serverHooks[entered].exit?.(serverParams);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst response = validationErrorResponse(error);\n\t\t\t\tif (response) return response;\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (ev.request.headers.has(\"X-Torpor-Form-Submit\")) {\n\t\t\t\t// If the form was submitted from javascript, just return the\n\t\t\t\t// response and it will be handled on the client\n\t\t\t\t// TODO: Should we make the user return a response??\n\t\t\t\tresult ??= ok();\n\n\t\t\t\t// If it's a redirect, we need to stop the browser handling it\n\t\t\t\tif (result.status >= 300 && result.status <= 399) {\n\t\t\t\t\tconst location = result.headers.get(\"Location\") ?? \"\";\n\t\t\t\t\tresult = ok();\n\t\t\t\t\tresult.headers.set(\"X-Torpor-Form-Redirect\", \"\");\n\t\t\t\t\tresult.headers.set(\"Location\", location);\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t} else if (\n\t\t\t\t!result ||\n\t\t\t\t(result.status >= 200 && result.status <= 299) ||\n\t\t\t\t(result.status >= 400 && result.status <= 499)\n\t\t\t) {\n\t\t\t\t// If the form was submitted without javascript, and there was\n\t\t\t\t// no result, an ok result, or a 4xx error, re-render the view\n\t\t\t\t// with the form result. The server hooks already ran around the\n\t\t\t\t// action, so they are not run again here -- but their appData\n\t\t\t\t// is passed through, so that loads guarding on it (e.g. an\n\t\t\t\t// auth hook's user) re-render the page instead of erroring\n\t\t\t\t// Keep the query string, so that loads validating the query see\n\t\t\t\t// the same values as the original GET. If the form action\n\t\t\t\t// dropped params that the load schema requires, loadView falls\n\t\t\t\t// back to the raw query instead of failing validation\n\t\t\t\tconst newUrl = new URL(url.pathname + url.search, url);\n\t\t\t\tconst formStatus = result?.status;\n\t\t\t\tlet formMessage = await result?.text();\n\t\t\t\tif (formMessage && result?.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\t$page.form = JSON.parse(formMessage);\n\t\t\t\t\tformMessage = undefined;\n\t\t\t\t} else {\n\t\t\t\t\t$page.form = {};\n\t\t\t\t}\n\t\t\t\t// Set status and message if they haven't already been set,\n\t\t\t\t// which means that you can just return e.g. forbidden() or\n\t\t\t\t// forbidden(\"no!\") and there will be something you can work\n\t\t\t\t// with in your component, or you can set a completely custom\n\t\t\t\t// object with whatever info you need\n\t\t\t\t$page.form!.status ??= formStatus ?? 0;\n\t\t\t\t$page.form!.message ??= formMessage ?? \"\";\n\t\t\t\treturn await loadView(\n\t\t\t\t\tev,\n\t\t\t\t\tnewUrl,\n\t\t\t\t\thandler,\n\t\t\t\t\tparams,\n\t\t\t\t\ttemplate,\n\t\t\t\t\tformStatus,\n\t\t\t\t\t$page.form,\n\t\t\t\t\ttrue,\n\t\t\t\t\tserverParams?.appData,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// If the form was submitted without javascript and there was a\n\t\t\t\t// redirect or server error, just return the result to handle it\n\t\t\t\t// as normal\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadClientAndServerData(\n\turl: URL,\n\tparams: Record<string, any>,\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, params, data);\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, any>, 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, any>,\n\tvalues: { json?: unknown; form?: unknown; query?: unknown } = {},\n\tappData: Record<string, any> = {},\n): ServerLoadEvent {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tappData,\n\t\trequest: ev.request,\n\t\t// The inputs may have been validated already, in which case the request\n\t\t// body has been consumed and the parsed values are returned instead.\n\t\t// Their types come from the endpoint's schemas, so they are loosely\n\t\t// typed here\n\t\tjson: (): Promise<any> =>\n\t\t\tvalues.json !== undefined ? Promise.resolve(values.json) : ev.json(),\n\t\tform: (): Promise<any> =>\n\t\t\tvalues.form !== undefined ? Promise.resolve(values.form) : formDataToRecord(ev.request),\n\t\tquery: (): Promise<any> =>\n\t\t\tvalues.query !== undefined\n\t\t\t\t? Promise.resolve(values.query)\n\t\t\t\t: Promise.resolve(searchParamsToRecord(url.searchParams)),\n\t\tcookies: ev.cookies,\n\t\theaders: ev.headers,\n\t\tsession: ev.session,\n\t\tflash: ev.flash,\n\t\tadapter: ev.adapter,\n\t};\n}\n\n/**\n * Renders a head element as markup. Attribute values are escaped, since\n * they can come from user data (e.g. a post title passed to `seo()`).\n */\nfunction markupOf(element: HeadElement): string {\n\tif (\"title\" in element) {\n\t\treturn `<title>${escapeHtml(element.title)}</title>`;\n\t}\n\tif (\"name\" in element) {\n\t\treturn `<meta name=\"${escapeHtml(element.name)}\" content=\"${escapeHtml(element.content)}\" />`;\n\t}\n\treturn `<meta property=\"${escapeHtml(element.property)}\" content=\"${escapeHtml(element.content)}\" />`;\n}\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\");\n}\n"],"mappings":";;;;;;;;;;;;;;AAWA,eAA8B,iBAC7B,SAC0B;CAC1B,MAAM,WAAW,mBAAmB,WAAW,UAAU,MAAM,QAAQ,SAAS;CAChF,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,OAAO,SAAS,KAAK,GAAG;EAClC,IAAI,OAAO,QAAQ;EACnB,MAAM,SAAS,SAAS,OAAO,GAAG;EAClC,OAAO,OAAO,OAAO,SAAS,IAAI,SAAS,OAAO;CACnD;CACA,OAAO;AACR;;;ACnBA,MAAM,cAA6B,OAAO,IAAI,WAAW;AAOzD,MAAM,QAEJ,WAAW,iBAAiB,OAAO;CACnC,QAAQ;CACR,KAAK,IAAI,IAAI,kBAAkB;CAC/B,MAAM,KAAA;CACN,OAAO,KAAA;CACP,OAAO,EACN,SAAS,GACV;AACD,CAAC;;;;;;;;;;ACXF,SAAwB,qBAAqB,cAA4C;CACxF,MAAM,SAAsB,CAAC;CAC7B,KAAK,MAAM,OAAO,aAAa,KAAK,GAAG;EACtC,IAAI,OAAO,QAAQ;EACnB,MAAM,SAAS,aAAa,OAAO,GAAG;EACtC,OAAO,OAAO,OAAO,SAAS,IAAI,SAAS,OAAO;CACnD;CACA,OAAO;AACR;;;;;;;ACKA,SAAgB,eAAe,UAAoB,MAA4C;CAC9F,OAAQ,SAAS,SAAiC;AACnD;;;;;;;;;;;AAYA,eAAsB,uBACrB,UACA,QAC+B;CAC/B,MAAM,SAAS,eAAe,UAAU,QAAQ;CAChD,OAAO,SAAW,MAAM,SAAS,QAAQ,MAAM,IAA6B;AAC7E;;;;;;;;;AAmCA,SAAgB,wBAAwB,OAAsC;CAC7E,IAAI,iBAAiB,iBACpB,OAAO,cAAc;EAAE,SAAS,MAAM;EAAS,QAAQ,MAAM;CAAO,CAAC;AAGvE;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzDA,SAAgB,sBACf,SACA,eACqB;CACrB,KAAK,IAAI,QAAQ,GAAG,SAAS,QAAQ,QAAQ,SAC5C,IAAI,CAAC,cAAc,IAAI,KAAK,GAC3B,OAAO,QAAQ,QAAQ,EAAE,CAAC;AAI7B;AAEA,MAAM,gCAAgB,IAAI,IAAY;;;;;;GAOlC,SAAgB,uBAAuB,YAA0B;CACpE,IAAI,cAAc,IAAI,UAAU,GAAG;CACnC,cAAc,IAAI,UAAU;CAC5B,QAAQ,KACP,2BAA2B,WAAW,6LAGvC;AACD;;;ACpDA,MAAM,cAAc;AACpB,MAAM,oBAAoB;;;;;;;;;AAU1B,SAAwB,UAAU,UAAkB,MAAsB;CACzE,MAAM,SAAS,KAAK,MAAM,WAAW;CACrC,IAAI,OAAO;CACX,IAAI,WAAW;CACf,IAAI,QAAQ,QAAQ;EACnB,WAAW,KAAK,QAAQ,aAAa,EAAE;EACvC,OAAO,KAAK,QAAQ,mBAAmB,EAAE;EACzC,WAAW,OAAO,KAAK;CACxB;CACA,OAAO,KAAK,QAAQ,oBAAoB,QAAQ;AACjD;;;;;;;;;;;;;;;;;ACsCA,SAAgB,iBACf,QACA,WAAW,IACX,aAAmC,CAAC,GACvB;CAIb,YAAY,QAAQ;CAEpB,MAAM,SAAS,OAAO,IAAiB,aAAyC;EAI/E,MAAM,MAAM,iBAAiB,GAAG,KAAK,QAAQ;EAC7C,IAAI,CAAC,KACJ,OAAO,eAAe,SAAS,CAAC;EAEjC,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,IAAI;EAIlB,IAAI,QAAQ,OAAO,MAAM,MAAM,KAAK;EACpC,IAAI,CAAC,OAIA;OAAA,GAAG,QAAQ,WAAW,UAAU,CAAC,KAAK,SAAS,UAAU,GAAG;IAC/D,MAAM,cACJ,KAAK,SAAS,GAAG,IAAI,KAAK,UAAU,GAAG,KAAK,SAAS,CAAC,IAAI,QAAQ;IACpE,QAAQ,OAAO,MAAM,YAAY,KAAK;GACvC;;EAED,IAAI,CAAC,OACJ,OAAO,eAAe,SAAS,CAAC;EAEjC,MAAM,UAAU,MAAM;EACtB,MAAM,SAAS,MAAM,UAAU,CAAC;EAKhC,MAAM,kBAAwC,CAAC;EAC/C,MAAM,iBAAiB,QAAQ,kBAC3B,MAAM,QAAQ,eAAe,EAAA,EAAI,UAClC,QAAQ,SAAA,KAA8B,QAAQ,SAAA,KAC5C,MAAM,cAAc,OAAO,EAAA,EAAI,UAChC,KAAA;EACJ,IAAI,MAAM,QAAQ,gBAAgB,UAAU,GAC3C,gBAAgB,KAAK,GAAG,eAAe,UAAU;EAIlD,MAAM,MAAM;EACZ,IAAI,KAAK,SAAS,SAAS,GAAG;GAC7B,MAAM,SAAS,SAAS,MAAM,IAAI,QAAQ,KAAK,KAAK;GACpD,MAAM,QAAQ,EAAE,SAAS,MAAM,IAAI,SAAS,KAAK,GAAG;EACrD,OACC,MAAM,SAAS;EAQhB,IAAI,eAAe;EACnB,IAAI,gBAAsC,KAAA;EAC1C,IAAI;GACH,OAAO,eAAe,gBAAgB,UAAU,CAAC,eAAe,gBAAgB;IAC/E,MAAM,SAAS,MAAM,gBAAgB,aAAa,CAAC,QAAQ,EAAE;IAC7D,IAAI,QACH,gBAAgB;GAElB;GACA,IAAI,eACH,OAAO;GAGR,QAAQ,QAAQ,MAAhB;IACC,KAAA;IACA,KAAA;KAEC,IAAI,GAAG,QAAQ,WAAW,OACzB,OAAO,eAAe,MAAM,SAAS,IAAI,KAAK,SAAS,QAAQ,QAAQ,CAAC;UAClE,IAAI,GAAG,QAAQ,WAAW,QAIhC,OAAO,eACN,MAAM,UAAU,IAAI,KAAK,SAFzB,QAAQ,mBAAmB,MAAM,QAAQ,eAAe,EAAA,CAAG,SAET,QAAQ,OAAO,QAAQ,GACzE,IACD;KAED;IAED,KAAA;IACA,KAAA;KAEC,IAAI,GAAG,QAAQ,WAAW,OACzB,OAAO,MAAM,SAAS,IAAI,KAAK,SAAS,QAAQ,MAAM;UAChD,IAAI,GAAG,QAAQ,WAAW,QAAQ;MACxC,MAAM,kBAAkD,MAAM,cAAc,OAAO,EAAA,CACjF;MACF,OAAO,eACN,MAAM,UAAU,IAAI,KAAK,SAAS,gBAAgB,QAAQ,OAAO,QAAQ,GACzE,IACD;KACD;KACA;IAED,KAAA,GAGC,OAAO,MAAM,SAAS,IAAI,KAAK,SADV,GAAG,QAAQ,OAAO,YAAY,CAAC,CAAC,QAAQ,UAAU,KACpB,GAAG,MAAM;IAE7D,KAAA;IACA,KAAA,GACC;IAED,KAAA,GACC,OAAO,MAAM,SAAS,IAAI,KAAK,SAAS,QAAQ,QAAQ;GAE1D;EACD,UAAU;GACT,OAAO,eAAe,GAAG;IACxB;IACA,MAAM,gBAAgB,aAAa,CAAC,OAAO,EAAE;GAC9C;EACD;EAEA,OAAO,SAAS;CACjB;CAEA,OAAO,eAAe,KAAK,IAAiB,UAAsC;EAIjF,IAAI,UAAU;EACd,IAAI;GACH,IAAI,WAAiC,KAAA;GACrC,OAAO,UAAU,WAAW,UAAU,CAAC,UAAU,WAAW;IAC3D,MAAM,SAAS,MAAM,WAAW,QAAQ,CAAC,QAAQ,EAAE;IACnD,IAAI,QACH,WAAW;GAEb;GACA,IAAI,UACH,OAAO;GAER,OAAO,MAAM,OAAO,IAAI,QAAQ;EACjC,SAAS,OAAO;GAGf,GAAG,QAAQ;GACX,MAAM;EACP,UAAU;GACT,OAAO,UAAU,GAAG;IACnB;IACA,MAAM,WAAW,QAAQ,CAAC,OAAO,EAAE;GACpC;EACD;CACD;AACD;;;;;AAMA,eAAe,cAAc,SAAqC;CACjE,OAAQ,QAAQ,mBAAmB,OAAO,QAAQ,kBAAkB,QAAQ,SAAS;AACtF;AAEA,eAAe,gBAAgB,SAA8C;CAC5E,IAAI,CAAC,QAAQ,aAAa,OAAO,CAAC;CAElC,OAAQ,QAAQ,kBAAkB,QAAQ,IACzC,QAAQ,YAAY,IAAI,OAAO,UAAU,MAAM,KAAK,EAAA,CAAG,OAAO,CAC/D;AACD;AAEA,eAAe,eAAe,UAAoB,WAAW,OAA0B;CACtF,MAAM,SAAS,SAAS;CAGxB,IAAI,SAAS,UAAU,OAAO,SAAS,UAAU,KAChD,OAAO;CAIR,IAAI,YAAY,SAAS,UAAU,OAAO,SAAS,UAAU,KAC5D,OAAO;CAQR,IAAI,SAAS,IAAI,gBAAgB;CACjC,OAAO,OAAO,UAAU,MAAM,OAAO,SAAS,CAAC;CAC/C,IAAI,UAAU,MAAM,SAAS,KAAK;CAClC,IAAI,SAAS,QAAQ,IAAI,cAAc,CAAC,EAAE,SAAS,kBAAkB,GAEpE,UADa,KAAK,MAAM,OACX,CAAC,CAAC,WAAW;CAE3B,IAAI,SAAS;EACZ,IAAI,MAAM,OACT,MAAM,MAAM,UAAU;OAEtB,MAAM,QAAQ,EAAE,QAAQ;EAEzB,OAAO,OAAO,WAAW,OAAO;CACjC;CACA,OAAO,SAAS,WAAW,OAAO,SAAS,GAAG;AAC/C;AAEA,eAAe,SACd,IACA,KACA,SACA,cACA,QACC;CAID,MAAM,kBADM,QAAQ,kBAAmB,MAAM,cAAc,OAAO,EAAA,EACV;CAGxD,MAAM,YACL,iBACG;CACJ,IAAI,CAAC,kBAAkB,CAAC,WACvB,OAAO,SAAS;CAMjB,MAAM,eAAe,eAAe,gBAAgB,QAAQ;CAC5D,IAAI,cACH,IAAI;EACH,SAAU,MAAM,SAAS,cAAc,MAAM;CAC9C,SAAS,OAAO;EACf,IAAI,iBAAiB,iBAAiB,OAAO,SAAS;EACtD,MAAM;CACP;CAGD,IAAI;EAKH,MAAM,SAAS,eAAe,gBAAgB,YAAY;EAC1D,MAAM,SAAS,SACZ,iBAAiB,SAAS,iBAAiB,UAAU,iBAAiB,SACrE,EAAE,OAAO,MAAM,SAAS,QAAQ,qBAAqB,IAAI,YAAY,CAAC,EAAE,IACxE,EAAE,MAAM,MAAM,SAAS,QAAQ,MAAM,GAAG,KAAK,CAAC,EAAE,IACjD,CAAC;EAEJ,MAAM,eAAe,kBAAkB,IAAI,KAAK,QAAQ,MAAM;EAI9D,MAAM,QAAQ,QAAQ,cAAc,MAAM,gBAAgB,OAAO,IAAI,CAAC;EAEtE,IAAI,UAAU;EACd,IAAI,gBAAsC,KAAA;EAC1C,IAAI;GAGH,OAAO,UAAU,MAAM,UAAU,CAAC,eAAe,WAAW;IAC3D,MAAM,aAAa,MAAM,MAAM,QAAQ,CAAC,QAAQ,YAAY;IAC5D,IAAI,YACH,gBAAgB;GAElB;GAKA,OAAO,iBAAkB,MAAM,UAAU,YAAY,KAAM,GAAG;EAC/D,UAAU;GAET,OAAO,UAAU,GAAG;IACnB;IACA,MAAM,MAAM,QAAQ,CAAC,OAAO,YAAY;GACzC;EACD;CACD,SAAS,OAAO;EACf,MAAM,WAAW,wBAAwB,KAAK;EAC9C,IAAI,UAAU,OAAO;EACrB,MAAM;CACP;AACD;AAEA,eAAe,SACd,IACA,KACA,SACA,QACA,UACA,YACA,MACA,UAGA,SACC;CAED,MAAM,kBAA4C,MAAM,cAAc,OAAO,EAAA,CAAG;CAChF,IAAI,CAAC,gBAAgB,WACpB,OAAO,SAAS;CAOjB,MAAM,QAAQ,GAAG,MAAM,IAAI;CAG3B,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,6HACD;CAID,MAAM,iBACL,QAAQ,mBAAmB,MAAM,QAAQ,eAAe,EAAA,EAAI;CAM7D,IAAI;CACJ,IAAI,gBAAgB;EACnB,IAAI;GACH,SAAS,MAAM,uBAAuB,gBAAgB,MAAM;EAC7D,SAAS,OAAO;GACf,IAAI,iBAAiB,iBAAiB,OAAO,SAAS;GACtD,MAAM;EACP;EACA,MAAM,aAAa,eAAe,gBAAgB,MAAM;EACxD,IAAI,YAAY;GACf,MAAM,cAAc,qBAAqB,IAAI,YAAY;GACzD,IAAI;IACH,QAAQ,MAAM,SAAS,YAAY,WAAW;GAC/C,SAAS,OAAO;IAMf,IAAI,UACH,QAAQ;SACF;KACN,MAAM,WAAW,wBAAwB,KAAK;KAC9C,IAAI,UAAU,OAAO;KACrB,MAAM;IACP;GACD;EACD;CACD;CAMA,MAAM,eAAe,kBAAkB,IAAI,KAAK,QAAQ,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAG,OAAO;CACvF,MAAM,cAAc,WAAW,CAAC,IAAI,MAAM,gBAAgB,OAAO;CACjE,IAAI,UAAU;CACd,IAAI,gBAAsC,KAAA;CAC1C,IAAI;EAGH,OAAO,UAAU,YAAY,UAAU,CAAC,eAAe,WAAW;GACjE,MAAM,aAAa,MAAM,YAAY,QAAQ,CAAC,QAAQ,YAAY;GAClE,IAAI,YACH,gBAAgB;EAElB;EACA,IAAI,eACH,OAAO;EAOR,IAAI,OAAO,CAAC;EACZ,IAAI,QAAQ,SACX,KAAK,IAAI,UAAU,QAAQ,SAAS;GACnC,MAAM,kBAA4C,MAAM,OAAO,SAAS,EAAA,EAAI;GAC5E,MAAM,uBACL,OAAO,mBAAmB,MAAM,OAAO,eAAe,EAAA,EAAI;GAC3D,MAAM,iBAAiB,MAAM,wBAC5B,KACA,QACA,cACA,MACA,gBACA,oBACD;GACA,IAAI,gBAAgB,OAAO,OAC1B,OAAO;EAET;EAED,IAAI,mBAAmB,MAAM,wBAC5B,KACA,QACA,cACA,MACA,gBACA,cACD;EACA,IAAI,kBAAkB,OAAO,OAC5B,OAAO;EAER,IAAI,SAA8B;GAAE;GAAM;EAAK;EAE/C,IAAI,SAAS;EAMb,IAAI,YAAY,eAAe;EAC/B,IAAI,QAAsD,KAAA;EAI1D,MAAM,gCAAgB,IAAI,IAAY;EACtC,MAAM,aAAa,OAAe,SAA6C;GAC9E,QAAQ,OAAO,aAAa;IAC3B,cAAc,IAAI,KAAK;IACvB,OAAO,KAAK,OAAO,QAAQ;GAC5B;EACD;EACA,IAAI,QAAQ,SAAS;GACpB,IAAI,gBAAoC,CAAC;GAEzC,cAAc,QAAQ,QAAQ,UAAU,UACvC,QAAQ,QAAQ,QAChB,OAAO,GAAG,aAAa;IACtB,IAAI,EAAE,MAAM,SAAS,MAAO,eAAe,UAC1C,QACA,QACD;IACA,UAAU;IACV,OAAO;GACR,CACD;GACA,KAAK,IAAI,IAAI,QAAQ,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;IACrD,MAAM,kBAA4C,MAAM,QAAQ,QAAQ,EAAE,CAAC,SAAS,EAAA,EACjF;IACH,IAAI,gBAAgB,WAAW;KAC9B,IAAI,MAAM,GAAG;MACZ,YAAY,eAAe;MAC3B,QAAQ,EAAE,GAAG,cAAc,IAAI,GAAG;KACnC,OACC,cAAc,KAAK,UAAU,GAAG,OAAO,GAAG,aAAa;MACtD,IAAI,EAAE,MAAM,SAAS,MAAO,eAAe,UAC1C,QACA,UACA,EACC,GAAG,cAAc,IAAI,GACtB,CACD;MACA,UAAU;MACV,OAAO;KACR,CAAC;IAEH;GACD;EACD;EAEA,IAAI;EACJ,IAAI;GACH,IAAI,EAAE,MAAM,SAAS,MAAM,UAAU,QAAQ,KAAA,GAAW,KAAK;GAM7D,IAAI,WAAW;GACf,MAAM,QAAQ;IAAE;IAAK;IAAQ;GAAK;GAIlC,MAAM,aAAa,CAAC,gBAAgB,GAAG,MAHT,QAAQ,KACpC,QAAQ,WAAW,CAAC,EAAA,CAAG,IAAI,OAAO,WAAW,MAAM,OAAO,SAAS,CAAC,CACtE,CACsD,CAAC,CAAC,QAAQ;GAChE,KAAK,MAAM,YAAY,YAAY;IAClC,MAAM,eACL,OAAO,SAAS,SAAS,aAAa,MAAM,SAAS,KAAK,KAAK,IAAI,SAAS;IAC7E,IAAI,cACH,YAAY,aAAa,KAAK,YAAyB,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE;GAEnF;GAGA,IAAI,MACH,QAAQ,kDAAkD,KAAK,UAAU,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK,EAAE;GAGvG,UAAU;GACV,OAAO,kBACN,UAAU,UAAU,SAAS,QAAQ,CAAC,CAAC,QAAQ,oBAAoB,IAAI,GACvE,YAAY,CACb;EACD,SAAS,OAAO;GAEf,OAAO,6DAA2D,QAAQ;GAC1E,QAAQ,IAAI,KAAK;EAClB;EAIA,IAAI,QAAQ,SAAS;GACpB,MAAM,gBAAgB,sBAAsB,QAAQ,SAAS,aAAa;GAC1E,IAAI,kBAAkB,KAAA,GACrB,uBAAuB,aAAa;EAEtC;EAEA,OAAO,IAAI,SAAS,MAAM;GACzB,QAAQ,cAAc;GACtB,SAAS,EACR,gBAAgB,YACjB;EACD,CAAC;CACF,UAAU;EAET,OAAO,UAAU,GAAG;GACnB;GACA,MAAM,YAAY,QAAQ,CAAC,OAAO,YAAY;EAC/C;CACD;AACD;AAEA,eAAe,UACd,IACA,KACA,SACA,gBACA,QACA,OACA,UACC;CAOD,MAAM,YAAY,MAAM,KAAK,MAAM,KAAK,CAAC;CAIzC,MAAM,cAFL,UAAU,MAAM,QAAQ,IAAI,WAAW,GAAG,CAAC,KAC3C,UAAU,MAAM,QAAQ,CAAC,CAAC,gBAAgB,UAAU,IAAI,QAAQ,OAAO,EAAE,EAAE,KAC3C,UAAA,CAAW,QAAQ,OAAO,EAAE;CAC7D,IAAI,gBAAgB,SAAS;EAC5B,MAAM,SAAS,eAAe,QAAQ;EACtC,IAAI,QAAQ;GACX,IAAI,eAAe,UAAU,eAAe,UAK3C,MAAM,IAAI,MACT,oBAAoB,WAAW,+EAEhC;GAID,IAAI;IACH,SAAS,MAAM,uBAAuB,gBAAgB,MAAM;GAC7D,SAAS,OAAO;IACf,IAAI,iBAAiB,iBAAiB,OAAO,SAAS;IACtD,MAAM;GACP;GAEA,IAAI;GAIJ,IAAI,eAA4C,KAAA;GAChD,IAAI;IAGH,MAAM,SAAS,eAAe,gBAAgB,UAAU;IACxD,MAAM,OAAO,SACV,MAAM,SAAS,QAAQ,MAAM,iBAAiB,GAAG,OAAO,CAAC,IACzD,KAAA;IAEH,eAAe,kBAAkB,IAAI,KAAK,QAAQ,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC;IAEtE,MAAM,cAAc,MAAM,gBAAgB,OAAO;IACjD,IAAI,UAAU;IACd,IAAI,gBAAsC,KAAA;IAC1C,IAAI;KAGH,OAAO,UAAU,YAAY,UAAU,CAAC,eAAe,WAAW;MACjE,MAAM,aAAa,MAAM,YAAY,QAAQ,CAAC,QAAQ,YAAY;MAClE,IAAI,YACH,gBAAgB;KAElB;KAEA,SAAS,iBAAkB,MAAM,OAAO,YAAY;IACrD,UAAU;KAET,OAAO,UAAU,GAAG;MACnB;MACA,MAAM,YAAY,QAAQ,CAAC,OAAO,YAAY;KAC/C;IACD;GACD,SAAS,OAAO;IACf,MAAM,WAAW,wBAAwB,KAAK;IAC9C,IAAI,UAAU,OAAO;IACrB,MAAM;GACP;GAEA,IAAI,GAAG,QAAQ,QAAQ,IAAI,sBAAsB,GAAG;IAInD,WAAW,GAAG;IAGd,IAAI,OAAO,UAAU,OAAO,OAAO,UAAU,KAAK;KACjD,MAAM,WAAW,OAAO,QAAQ,IAAI,UAAU,KAAK;KACnD,SAAS,GAAG;KACZ,OAAO,QAAQ,IAAI,0BAA0B,EAAE;KAC/C,OAAO,QAAQ,IAAI,YAAY,QAAQ;IACxC;IAEA,OAAO;GACR,OAAO,IACN,CAAC,UACA,OAAO,UAAU,OAAO,OAAO,UAAU,OACzC,OAAO,UAAU,OAAO,OAAO,UAAU,KACzC;IAWD,MAAM,SAAS,IAAI,IAAI,IAAI,WAAW,IAAI,QAAQ,GAAG;IACrD,MAAM,aAAa,QAAQ;IAC3B,IAAI,cAAc,MAAM,QAAQ,KAAK;IACrC,IAAI,eAAe,QAAQ,QAAQ,IAAI,cAAc,CAAC,EAAE,SAAS,kBAAkB,GAAG;KACrF,MAAM,OAAO,KAAK,MAAM,WAAW;KACnC,cAAc,KAAA;IACf,OACC,MAAM,OAAO,CAAC;IAOf,MAAM,KAAM,WAAW,cAAc;IACrC,MAAM,KAAM,YAAY,eAAe;IACvC,OAAO,MAAM,SACZ,IACA,QACA,SACA,QACA,UACA,YACA,MAAM,MACN,MACA,cAAc,OACf;GACD,OAIC,OAAO;EAET;CACD;CAEA,OAAO,SAAS;AACjB;AAEA,eAAe,wBACd,KACA,QACA,cACA,MACA,gBACA,gBACC;CACD,IAAI,gBAAgB,MAAM;EACzB,MAAM,eAAe,kBAAkB,KAAK,QAAQ,IAAI;EACxD,MAAM,iBAAiB,MAAM,eAAe,KAAK,YAAY;EAC7D,IAAI,gBAAgB;GACnB,IAAI,eAAe,IACd;QAAA,eAAe,QAAQ,IAAI,cAAc,CAAC,EAAE,SAAS,kBAAkB,GAC1E,OAAO,OAAO,MAAM,MAAM,eAAe,KAAK,CAAC;GAAA,OAGhD,OAAO;EAET;CACD;CACA,IAAI,gBAAgB,MAAM;EACzB,MAAM,iBAAiB,MAAM,eAAe,KAAK,YAAY;EAC7D,IAAI,gBAAgB;GACnB,IAAI,eAAe,IACd;QAAA,eAAe,QAAQ,IAAI,cAAc,CAAC,EAAE,SAAS,kBAAkB,GAC1E,OAAO,OAAO,MAAM,MAAM,eAAe,KAAK,CAAC;GAAA,OAGhD,OAAO;EAET;CACD;AACD;AAEA,SAAS,kBAAkB,KAAU,QAA6B,MAA2B;CAC5F,OAAO;EACN;EACA;EACA;CACD;AACD;AAEA,SAAS,kBACR,IACA,KACA,QACA,SAA8D,CAAC,GAC/D,UAA+B,CAAC,GACd;CAClB,OAAO;EACN;EACA;EACA;EACA,SAAS,GAAG;EAKZ,YACC,OAAO,SAAS,KAAA,IAAY,QAAQ,QAAQ,OAAO,IAAI,IAAI,GAAG,KAAK;EACpE,YACC,OAAO,SAAS,KAAA,IAAY,QAAQ,QAAQ,OAAO,IAAI,IAAI,iBAAiB,GAAG,OAAO;EACvF,aACC,OAAO,UAAU,KAAA,IACd,QAAQ,QAAQ,OAAO,KAAK,IAC5B,QAAQ,QAAQ,qBAAqB,IAAI,YAAY,CAAC;EAC1D,SAAS,GAAG;EACZ,SAAS,GAAG;EACZ,SAAS,GAAG;EACZ,OAAO,GAAG;EACV,SAAS,GAAG;CACb;AACD;;;;;AAMA,SAAS,SAAS,SAA8B;CAC/C,IAAI,WAAW,SACd,OAAO,UAAU,WAAW,QAAQ,KAAK,EAAE;CAE5C,IAAI,UAAU,SACb,OAAO,eAAe,WAAW,QAAQ,IAAI,EAAE,aAAa,WAAW,QAAQ,OAAO,EAAE;CAEzF,OAAO,mBAAmB,WAAW,QAAQ,QAAQ,EAAE,aAAa,WAAW,QAAQ,OAAO,EAAE;AACjG;AAEA,SAAS,WAAW,OAAuB;CAC1C,OAAO,MACL,WAAW,KAAK,OAAO,CAAC,CACxB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,MAAK,QAAQ;AAC3B"}
|
package/dist/test.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Router } from "./Router-C4ADUfhv.mjs";
|
|
2
|
-
import { t as ServerEvent } from "./ServerEvent-
|
|
3
|
-
import { t as createServerLoad } from "./serverHandlers-
|
|
2
|
+
import { t as ServerEvent } from "./ServerEvent-BY4f3RVF.mjs";
|
|
3
|
+
import { t as createServerLoad } from "./serverHandlers-C8qzOSX-.mjs";
|
|
4
4
|
import fpath from "node:path";
|
|
5
5
|
//#region src/test/runTest.ts
|
|
6
6
|
/**
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ServerEvent-Bh9cEpro.mjs","names":["COOKIE_NAME","#cookies","#cookies","#id","#write","#url"],"sources":["../src/server/CookieHelper.ts","../src/server/FlashHelper.ts","../src/server/HeaderHelper.ts","../src/server/SessionHelper.ts","../src/server/ServerEvent.ts"],"sourcesContent":["import { type SerializeOptions, parseCookie, stringifySetCookie } 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\t// The stored value is a serialized Set-Cookie header; pull the\n\t\t\t// \"name=value\" prefix off and re-parse to get just the value\n\t\t\treturn parseCookie(this.cookies.get(name)!)[name];\n\t\t}\n\n\t\t// Otherwise, return the value from the `cookie` header\n\t\tconst header = this.request.headers.get(\"Cookie\");\n\t\treturn parseCookie(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\n\t\tthis.cookies.set(name, stringifySetCookie({ name, value, ...options }));\n\t}\n\n\tdelete(name: string, options?: SerializeOptions): void {\n\t\t// Set a cookie header with an empty value and an immediate expiry date.\n\t\t// Apply the same secure defaults as `set` so a previously-set Secure\n\t\t// cookie is actually overridden by the deletion.\n\t\toptions ??= {};\n\t\toptions.httpOnly ??= true;\n\t\toptions.secure ??= true;\n\t\toptions.sameSite ??= \"lax\";\n\t\toptions.expires = new Date(0);\n\n\t\tthis.cookies.set(name, stringifySetCookie({ name, value: \"\", ...options }));\n\t}\n}\n","import CookieHelper from \"./CookieHelper\";\n\nconst COOKIE_NAME = \"torpor-flash\";\n\n/**\n * A one-time values store set in a form action and shown on the next page\n * render, which survives the redirect. Available as `event.flash`; the\n * framework reads it into `$page.flash` when a page is rendered, so\n * components display it reactively:\n *\n * ```ts\n * actions: {\n * \tsave: async (event) => {\n * \t\t// ...save...\n * \t\tevent.flash.set(\"Project saved\"); // { message: \"Project saved\" }\n * \t\tevent.flash.set({ error: \"Import failed\", retryable: true });\n * \t\treturn seeOther(`/projects/${id}`);\n * \t},\n * }\n * ```\n *\n * ```torp\n * @if ($page.flash?.message) {\n * \t<p class=\"flash\">{$page.flash.message}</p>\n * }\n * ```\n *\n * The values ride in a (session-lifetime) cookie that is deleted when it is\n * read, so a page refresh shows them once. No signing: the only person who\n * can fake a flash is the one seeing it, and values are rendered as text.\n * They must be JSON-safe and small enough to fit in the cookie (~4KB). A\n * common shape for a styled banner is `{ type, message }`, for use e.g.\n * with the Notification component -- but the framework only requires the\n * values to be an object. Without cookies the message is simply lost --\n * the action itself still completes.\n */\nexport default class FlashHelper {\n\t#cookies: CookieHelper;\n\n\tconstructor(cookies: CookieHelper) {\n\t\tthis.#cookies = cookies;\n\t}\n\n\t/**\n\t * Reads the flash values, if any, and clears them: the same response\n\t * deletes the cookie, so the message shows exactly once. (When the\n\t * message was set during this same request, e.g. a form action that\n\t * re-renders instead of redirecting, the cookie is consumed without it\n\t * ever reaching the browser.)\n\t */\n\tget(): Record<string, unknown> | undefined {\n\t\tconst cookie = this.#cookies.get(COOKIE_NAME);\n\t\tif (!cookie) return undefined;\n\t\tthis.#cookies.delete(COOKIE_NAME, { path: \"/\" });\n\t\ttry {\n\t\t\tconst decoded = JSON.parse(decodeURIComponent(cookie));\n\t\t\tif (typeof decoded !== \"object\" || decoded === null || Array.isArray(decoded)) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn decoded as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Sets the flash values. A string is stored as `{ message: value }`, an\n\t * object as-is, e.g. `flash.set({ error: \"Import failed\" })`.\n\t */\n\tset(value: string | Record<string, unknown>): void {\n\t\tconst data = typeof value === \"string\" ? { message: value } : value;\n\t\tthis.#cookies.set(COOKIE_NAME, encodeURIComponent(JSON.stringify(data)), { path: \"/\" });\n\t}\n\n\t/**\n\t * Clears an unread flash message, e.g. so it isn't shown on a later\n\t * re-render of the same page.\n\t */\n\tclear(): void {\n\t\tthis.#cookies.delete(COOKIE_NAME, { path: \"/\" });\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 env from \"../env\";\nimport CookieHelper from \"./CookieHelper\";\n\nconst COOKIE_NAME = \"torpor-session\";\nconst DEFAULT_MAX_AGE = 30 * 24 * 60 * 60; // 30 days, in seconds\n\n/**\n * The data stored in the session. Must be JSON-safe, and small enough to\n * fit in a cookie (4KB) along with the id, expiry and signature.\n */\ntype SessionData = Record<string, unknown>;\n\ntype SessionPayload = {\n\tid: string;\n\tdata: SessionData;\n\t/** Expiry, as a Unix epoch in milliseconds */\n\texp: number;\n};\n\n// The imported signing key, cached per secret so repeated calls (per\n// request) don't re-import\nconst keys = new Map<string, Promise<CryptoKey>>();\n\n/**\n * A helper for reading and writing the signed session cookie, available as\n * `event.session` in load functions, actions, +server endpoints and hooks.\n *\n * The value is signed with HMAC-SHA256 using `TORPOR_SESSION_SECRET` from\n * the environment, so it can't be forged or tampered with -- but it is\n * still client-side storage: everything rides in the cookie, it can't be\n * revoked individually, and it is limited to ~4KB. When you need\n * revocation, store `session.id` in your database, check it on each\n * authed request (e.g. in a `_hook.server.ts`), and delete the row to\n * revoke.\n *\n * ```ts\n * // Login action\n * const user = await checkPassword(event.form());\n * await event.session.regenerate({ userId: user.id });\n *\n * // Root hook\n * const session = await event.session.get();\n * if (session) {\n * \tevent.appData.user = await getUser(session.userId);\n * }\n * ```\n */\nexport default class SessionHelper {\n\t#cookies: CookieHelper;\n\t#id: string | undefined;\n\n\tconstructor(cookies: CookieHelper) {\n\t\tthis.#cookies = cookies;\n\t}\n\n\t/**\n\t * The id embedded in the current session cookie, or undefined when\n\t * there is no valid session. Stable across `set` calls, so it can be\n\t * used as a database key; `regenerate` mints a new one.\n\t */\n\tget id(): string | undefined {\n\t\treturn this.#id;\n\t}\n\n\t/**\n\t * Reads and verifies the session, returning its data, or undefined when\n\t * there is no cookie, the signature doesn't match, or the session has\n\t * expired.\n\t */\n\tasync get(): Promise<SessionData | undefined> {\n\t\tconst value = this.#cookies.get(COOKIE_NAME);\n\t\tif (!value) return undefined;\n\t\tconst payload = await verifyValue(value);\n\t\tif (!payload) return undefined;\n\t\tthis.#id = payload.id;\n\t\treturn payload.data;\n\t}\n\n\t/**\n\t * Writes the session, signing the data and setting the cookie. Keeps\n\t * the current session id, minting one when there isn't a valid session\n\t * yet. Call `regenerate` instead when the session's identity should\n\t * change (e.g. at login, to prevent session fixation).\n\t */\n\tasync set(data: SessionData, options?: { maxAge?: number }): Promise<void> {\n\t\tconst id = this.#id ?? crypto.randomUUID();\n\t\tthis.#id = id;\n\t\tawait this.#write(id, data, options);\n\t}\n\n\t/**\n\t * Writes the session with a fresh id, replacing any existing one. Use\n\t * it when a session's privilege level changes (login, permission\n\t * upgrade), so a cookie that was set before the change can't be reused.\n\t */\n\tasync regenerate(data: SessionData = {}, options?: { maxAge?: number }): Promise<void> {\n\t\tconst id = crypto.randomUUID();\n\t\tthis.#id = id;\n\t\tawait this.#write(id, data, options);\n\t}\n\n\t/**\n\t * Deletes the session cookie. Note that a server-side record (if you\n\t * keep one) must be deleted or revoked separately.\n\t */\n\tasync destroy(): Promise<void> {\n\t\tthis.#id = undefined;\n\t\tthis.#cookies.delete(COOKIE_NAME, { path: \"/\" });\n\t}\n\n\tasync #write(\n\t\tid: string,\n\t\tdata: SessionData,\n\t\toptions: { maxAge?: number } | undefined,\n\t): Promise<void> {\n\t\tconst maxAge = options?.maxAge ?? DEFAULT_MAX_AGE;\n\t\tconst payload: SessionPayload = { id, data, exp: Date.now() + maxAge * 1000 };\n\t\tconst encoded = encodeBase64Url(new TextEncoder().encode(JSON.stringify(payload)));\n\t\tconst signature = await sign(encoded);\n\t\tthis.#cookies.set(COOKIE_NAME, `${encoded}.${signature}`, { path: \"/\", maxAge });\n\t}\n}\n\nfunction secret(): string {\n\tconst value = env().TORPOR_SESSION_SECRET;\n\tif (!value) {\n\t\tthrow new Error(\n\t\t\t\"TORPOR_SESSION_SECRET is not set. Add it to the environment (or to a .env \" +\n\t\t\t\t\"file in dev and preview) to use sessions.\",\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction signingKey(): Promise<CryptoKey> {\n\tconst value = secret();\n\tlet key = keys.get(value);\n\tif (!key) {\n\t\tkey = crypto.subtle.importKey(\n\t\t\t\"raw\",\n\t\t\tnew TextEncoder().encode(value),\n\t\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\t\tfalse,\n\t\t\t[\"sign\", \"verify\"],\n\t\t);\n\t\tkeys.set(value, key);\n\t}\n\treturn key;\n}\n\nasync function sign(payload: string): Promise<string> {\n\tconst signature = await crypto.subtle.sign(\n\t\t\"HMAC\",\n\t\tawait signingKey(),\n\t\tnew TextEncoder().encode(payload),\n\t);\n\treturn encodeBase64Url(new Uint8Array(signature));\n}\n\nasync function verifyValue(value: string): Promise<SessionPayload | undefined> {\n\tconst dot = value.lastIndexOf(\".\");\n\tif (dot === -1) return undefined;\n\tconst encoded = value.slice(0, dot);\n\tconst valid = await crypto.subtle.verify(\n\t\t\"HMAC\",\n\t\tawait signingKey(),\n\t\tdecodeBase64Url(value.slice(dot + 1)),\n\t\tnew TextEncoder().encode(encoded),\n\t);\n\tif (!valid) return undefined;\n\n\tlet payload: SessionPayload;\n\ttry {\n\t\tpayload = JSON.parse(new TextDecoder().decode(decodeBase64Url(encoded)));\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (\n\t\ttypeof payload?.id !== \"string\" ||\n\t\ttypeof payload?.exp !== \"number\" ||\n\t\ttypeof payload?.data !== \"object\" ||\n\t\tpayload.data === null ||\n\t\tpayload.exp < Date.now()\n\t) {\n\t\treturn undefined;\n\t}\n\treturn payload;\n}\n\nfunction encodeBase64Url(bytes: Uint8Array): string {\n\t// btoa can't take raw bytes, and long argument lists overflow, so\n\t// convert in chunks\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i += 0x1000) {\n\t\tbinary += String.fromCharCode(...bytes.subarray(i, i + 0x1000));\n\t}\n\treturn btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replaceAll(\"=\", \"\");\n}\n\nfunction decodeBase64Url(value: string): Uint8Array<ArrayBuffer> {\n\tconst binary = atob(value.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\"));\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n","import { addBaseToPath, getBasePath } from \"../site/basePath\";\nimport CookieHelper from \"./CookieHelper\";\nimport FlashHelper from \"./FlashHelper\";\nimport HeaderHelper from \"./HeaderHelper\";\nimport SessionHelper from \"./SessionHelper\";\n\nexport default class ServerEvent {\n\trequest: Request;\n\tresponse: Response | undefined | void;\n\t/**\n\t * Set when the route handler (or a middleware enter hook) threw an\n\t * error. Exit hooks can return a Response to handle it; if none does,\n\t * the error is rethrown to the caller.\n\t */\n\terror: unknown;\n\t// TODO: Should we cast types??\n\tparams?: Record<string, string>;\n\n\tadapter: any;\n\n\tcookies: CookieHelper;\n\theaders: HeaderHelper;\n\tsession: SessionHelper;\n\tflash: FlashHelper;\n\n\t// Parsed once per request; Server.fetch passes the URL it already needed\n\t// for matching so it isn't parsed twice\n\t#url: URL | undefined;\n\n\t/** The request URL, parsed lazily. */\n\tget url(): URL {\n\t\treturn (this.#url ??= new URL(this.request.url));\n\t}\n\n\tconstructor(request: Request, params?: Record<string, string>, url?: URL) {\n\t\tthis.request = request;\n\t\tthis.params = params;\n\t\tthis.#url = url;\n\t\tthis.cookies = new CookieHelper(request);\n\t\tthis.headers = new HeaderHelper(request);\n\t\tthis.session = new SessionHelper(this.cookies);\n\t\tthis.flash = new FlashHelper(this.cookies);\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\n\t\t// Redirect locations are written base-free, so that user code isn't\n\t\t// aware of the base path\n\t\tconst base = getBasePath();\n\t\tif (base) {\n\t\t\tconst location = this.response.headers.get(\"Location\");\n\t\t\tif (location) {\n\t\t\t\tconst rewritten = addBaseToPath(location, base);\n\t\t\t\tif (rewritten !== location) {\n\t\t\t\t\tthis.response.headers.set(\"Location\", rewritten);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reads the request body as JSON.\n\t */\n\tjson(): Promise<unknown> {\n\t\treturn this.request.json();\n\t}\n}\n"],"mappings":"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"}
|