@torpor/build 0.4.13 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/Jsonify-Dg6gChd3.d.mts +10 -0
- package/dist/Jsonify-Dg6gChd3.d.mts.map +1 -0
- package/dist/ParseRouteParams-DBmqlmLh.d.mts +42 -0
- package/dist/ParseRouteParams-DBmqlmLh.d.mts.map +1 -0
- package/dist/{RouteType-B6BMOyYw.mjs → Router-C4ADUfhv.mjs} +30 -42
- package/dist/Router-C4ADUfhv.mjs.map +1 -0
- package/dist/{Server-CF5wDJp6.d.mts → Server-C_IKav_e.d.mts} +44 -16
- package/dist/Server-C_IKav_e.d.mts.map +1 -0
- package/dist/{ServerEvent-CxPwA4lH.mjs → ServerEvent-Dljn5i7A.mjs} +37 -11
- package/dist/ServerEvent-Dljn5i7A.mjs.map +1 -0
- package/dist/Site-CMa153FA.d.mts +565 -0
- package/dist/Site-CMa153FA.d.mts.map +1 -0
- package/dist/StandardSchema-D28L8_zZ.d.mts +100 -0
- package/dist/StandardSchema-D28L8_zZ.d.mts.map +1 -0
- package/dist/TypedResponse-6lznqN4U.d.mts +22 -0
- package/dist/TypedResponse-6lznqN4U.d.mts.map +1 -0
- package/dist/{_page-BQs4WvCO.mjs → _page-4cQKs_Dn.mjs} +3 -5
- package/dist/_page-4cQKs_Dn.mjs.map +1 -0
- package/dist/bin/index.d.mts +1 -1
- package/dist/bin/index.js +15 -11
- package/dist/bin/index.js.map +1 -1
- package/dist/flattenHeaders-C_YYLdOq.mjs +114 -0
- package/dist/flattenHeaders-C_YYLdOq.mjs.map +1 -0
- package/dist/form.d.mts +35 -0
- package/dist/form.d.mts.map +1 -0
- package/dist/form.mjs +48 -0
- package/dist/form.mjs.map +1 -0
- package/dist/index.d.mts +72 -156
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +164 -32
- package/dist/index.mjs.map +1 -1
- package/dist/nav.d.mts +58 -5
- package/dist/nav.d.mts.map +1 -1
- package/dist/nav.mjs +116 -38
- package/dist/nav.mjs.map +1 -1
- package/dist/openapi.d.mts +144 -0
- package/dist/openapi.d.mts.map +1 -0
- package/dist/openapi.mjs +2 -0
- package/dist/pathTrie-D4Ax2Mu9.mjs +160 -0
- package/dist/pathTrie-D4Ax2Mu9.mjs.map +1 -0
- package/dist/plugin-D8Www11B.mjs +202 -0
- package/dist/plugin-D8Www11B.mjs.map +1 -0
- package/dist/{response-C5TtAsh1.mjs → response-Bg4w2S1q.mjs} +16 -39
- package/dist/response-Bg4w2S1q.mjs.map +1 -0
- package/dist/response.d.mts +309 -289
- package/dist/response.d.mts.map +1 -1
- package/dist/response.mjs +3 -4
- package/dist/run.d.mts +11 -4
- package/dist/run.d.mts.map +1 -1
- package/dist/run.mjs +2 -8
- package/dist/runOpenApi-BUi1RH1l.mjs +1381 -0
- package/dist/runOpenApi-BUi1RH1l.mjs.map +1 -0
- package/dist/schema.d.mts +32 -0
- package/dist/schema.d.mts.map +1 -0
- package/dist/schema.mjs +2 -0
- package/dist/server.d.mts +10 -8
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +132 -4
- package/dist/server.mjs.map +1 -0
- package/dist/state.d.mts.map +1 -1
- package/dist/state.mjs +2 -3
- package/dist/test.d.mts +8 -10
- package/dist/test.d.mts.map +1 -1
- package/dist/test.mjs +286 -82
- package/dist/test.mjs.map +1 -1
- package/dist/{seeOther-B4Yhu9iq.mjs → unprocessable-DAZbrDeR.mjs} +34 -13
- package/dist/unprocessable-DAZbrDeR.mjs.map +1 -0
- package/dist/validate-Ok4krlkX.mjs +40 -0
- package/dist/validate-Ok4krlkX.mjs.map +1 -0
- package/package.json +28 -17
- package/src/bin/index.ts +13 -2
- package/src/dev.ts +10 -0
- package/src/form/formDataToRecord.ts +23 -0
- package/src/form/readForm.ts +71 -0
- package/src/form.ts +4 -0
- package/src/index.ts +53 -1
- package/src/nav/api.test-d.ts +79 -0
- package/src/nav/api.ts +104 -0
- package/src/nav/formSubmit.ts +18 -4
- package/src/nav/navigate.ts +40 -15
- package/src/nav/route.ts +38 -0
- package/src/nav.ts +3 -1
- package/src/openapi/docsHtml.ts +25 -0
- package/src/openapi/document.ts +154 -0
- package/src/openapi/plugin.ts +78 -0
- package/src/openapi/types.ts +82 -0
- package/src/openapi.ts +14 -0
- package/src/response/TypedResponse.ts +17 -0
- package/src/response/badRequest.ts +14 -2
- package/src/response/created.ts +13 -2
- package/src/response/found.ts +2 -2
- package/src/response/movedPermanently.ts +2 -2
- package/src/response/notModified.ts +2 -2
- package/src/response/ok.ts +13 -4
- package/src/response/response.ts +4 -4
- package/src/response/unprocessable.ts +14 -2
- package/src/run/depCache.ts +175 -0
- package/src/run/devPlugin.ts +128 -0
- package/src/run/prepareTemplate.ts +6 -3
- package/src/run/run.ts +63 -39
- package/src/run/runBuild.ts +63 -12
- package/src/run/runDev.ts +146 -58
- package/src/run/runOpenApi.ts +52 -0
- package/src/run/runPreview.ts +17 -26
- package/src/run/staleTorpCopies.ts +100 -0
- package/src/run.ts +2 -1
- package/src/schema.ts +7 -0
- package/src/server/CookieHelper.ts +17 -7
- package/src/server/Server.ts +54 -30
- package/src/server/ServerEvent.ts +24 -1
- package/src/server/connect/connectMiddleware.ts +44 -40
- package/src/server/connect/flattenHeaders.ts +1 -23
- package/src/server/connect/requestToNodeMessage.ts +6 -3
- package/src/server/types/MiddlewareFunction.ts +18 -4
- package/src/site/Router.ts +52 -41
- package/src/site/Site.ts +184 -10
- package/src/site/checkLayoutSlots.ts +114 -0
- package/src/site/checkRoutes.ts +435 -0
- package/src/site/clientEntry.ts +22 -9
- package/src/site/layoutSlots.ts +53 -0
- package/src/site/manifest.ts +125 -6
- package/src/site/serverEntry.ts +352 -133
- package/src/state/$page.ts +2 -1
- package/src/state/$serverPage.ts +22 -0
- package/src/test/runTest.ts +306 -118
- package/src/types/Adapter.ts +12 -0
- package/src/types/Jsonify.ts +20 -0
- package/src/types/PageData.test-d.ts +136 -0
- package/src/types/PageData.ts +35 -0
- package/src/types/PageEndPoint.ts +25 -9
- package/src/types/PageForm.test-d.ts +78 -0
- package/src/types/PageForm.ts +29 -0
- package/src/types/PageLoadEvent.ts +16 -4
- package/src/types/PageLoadReturn.ts +13 -0
- package/src/types/PageProps.ts +14 -0
- package/src/types/PageServerAction.ts +8 -2
- package/src/types/PageServerEndPoint.test-d.ts +105 -0
- package/src/types/PageServerEndPoint.ts +99 -5
- package/src/types/PageServerLoad.ts +11 -3
- package/src/types/ParseRouteParams.test-d.ts +84 -0
- package/src/types/ParseRouteParams.ts +64 -0
- package/src/types/Route.ts +20 -1
- package/src/types/RouteHandler.ts +8 -1
- package/src/types/ServerEndPoint.test-d.ts +77 -0
- package/src/types/ServerEndPoint.ts +148 -16
- package/src/types/ServerHook.ts +10 -3
- package/src/types/ServerLoadEvent.ts +48 -3
- package/src/types/ServerRequest.ts +10 -2
- package/src/types/SitePlugin.ts +24 -0
- package/src/types/StandardSchema.ts +109 -0
- package/src/utils/pathToRegex.ts +4 -2
- package/src/utils/pathTrie.ts +182 -0
- package/src/utils/searchParamsToRecord.ts +18 -0
- package/src/utils/torporPackages.ts +150 -0
- package/src/utils/tsconfigAliases.ts +90 -0
- package/src/validation/ValidationError.ts +18 -0
- package/src/validation/endpoint.ts +83 -0
- package/src/validation/validate.ts +26 -0
- package/dist/RouteType-B6BMOyYw.mjs.map +0 -1
- package/dist/Server-CF5wDJp6.d.mts.map +0 -1
- package/dist/ServerEvent-CxPwA4lH.mjs.map +0 -1
- package/dist/Site-DgC6WWn1.d.mts +0 -78
- package/dist/Site-DgC6WWn1.d.mts.map +0 -1
- package/dist/_page-BQs4WvCO.mjs.map +0 -1
- package/dist/connectMiddleware-D7nehjj3.mjs +0 -252
- package/dist/connectMiddleware-D7nehjj3.mjs.map +0 -1
- package/dist/pathToRegex-TUAMc3zS.mjs +0 -11
- package/dist/pathToRegex-TUAMc3zS.mjs.map +0 -1
- package/dist/response-C5TtAsh1.mjs.map +0 -1
- package/dist/run-DiZgkMnG.mjs +0 -332
- package/dist/run-DiZgkMnG.mjs.map +0 -1
- package/dist/seeOther-B4Yhu9iq.mjs.map +0 -1
- package/src/server/Routerx.ts +0 -72
- package/src/server/connect/bufferToArrayBuffer.ts +0 -8
- package/src/server/connect/readableToBuffer.ts +0 -16
package/dist/test.mjs
CHANGED
|
@@ -1,10 +1,111 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import { n as
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
1
|
+
import { t as Router } from "./Router-C4ADUfhv.mjs";
|
|
2
|
+
import { i as notFound, n as seeOther, r as ok, t as unprocessable } from "./unprocessable-DAZbrDeR.mjs";
|
|
3
|
+
import { t as ServerEvent } from "./ServerEvent-Dljn5i7A.mjs";
|
|
4
|
+
import { n as ValidationError, t as validate } from "./validate-Ok4krlkX.mjs";
|
|
5
|
+
import fpath from "node:path";
|
|
6
|
+
import { $watch } from "@torpor/view/ssr";
|
|
7
|
+
//#region src/form/formDataToRecord.ts
|
|
8
|
+
/**
|
|
9
|
+
* Converts the submitted fields of a form request into a plain record: one
|
|
10
|
+
* value per field, or an array when a field was submitted with multiple
|
|
11
|
+
* values (e.g. a multi select). File inputs pass through as `File` values;
|
|
12
|
+
* coercing them is left to the schema or the action.
|
|
13
|
+
*
|
|
14
|
+
* @param request The request (or FormData directly)
|
|
15
|
+
* @returns The field values
|
|
16
|
+
*/
|
|
17
|
+
async function formDataToRecord(request) {
|
|
18
|
+
const formData = request instanceof FormData ? request : await request.formData();
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const key of formData.keys()) {
|
|
21
|
+
if (key in result) continue;
|
|
22
|
+
const values = formData.getAll(key);
|
|
23
|
+
result[key] = values.length > 1 ? values : values[0];
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/state/$serverPage.ts
|
|
29
|
+
const PAGE_SYMBOL = Symbol.for("torp.Page");
|
|
30
|
+
const $page = globalThis[PAGE_SYMBOL] ??= $watch({
|
|
31
|
+
status: 404,
|
|
32
|
+
url: new URL("http://localhost"),
|
|
33
|
+
form: void 0,
|
|
34
|
+
error: { message: "" }
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/utils/searchParamsToRecord.ts
|
|
38
|
+
/**
|
|
39
|
+
* Converts a URL's query string into a plain record: one string per param,
|
|
40
|
+
* or an array when a param was repeated.
|
|
41
|
+
*
|
|
42
|
+
* @param searchParams The URL's search params
|
|
43
|
+
* @returns The param values
|
|
44
|
+
*/
|
|
45
|
+
function searchParamsToRecord(searchParams) {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const key of searchParams.keys()) {
|
|
48
|
+
if (key in result) continue;
|
|
49
|
+
const values = searchParams.getAll(key);
|
|
50
|
+
result[key] = values.length > 1 ? values : values[0];
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/validation/endpoint.ts
|
|
56
|
+
/**
|
|
57
|
+
* Gets the standard schema declared for a handler or action, if any. The
|
|
58
|
+
* schemas map is keyed by name; the runtime treats it as a loose record.
|
|
59
|
+
*/
|
|
60
|
+
function endpointSchema(endPoint, name) {
|
|
61
|
+
return endPoint.schema?.[name];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validates the route params against the endpoint's `params` schema, if it
|
|
65
|
+
* declares one, returning the coerced params. Throws a ValidationError when
|
|
66
|
+
* they don't match, which callers turn into a 404 (params come from the URL,
|
|
67
|
+
* so a failed validation means the resource doesn't exist).
|
|
68
|
+
*
|
|
69
|
+
* @param endPoint The endpoint declaring the (optional) `params` schema
|
|
70
|
+
* @param params The route params from the matched route
|
|
71
|
+
* @returns The validated params
|
|
72
|
+
*/
|
|
73
|
+
async function validateEndpointParams(endPoint, params) {
|
|
74
|
+
const schema = endpointSchema(endPoint, "params");
|
|
75
|
+
return schema ? await validate(schema, params) : params;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validates a handler's input against its schema: get/head handlers and load
|
|
79
|
+
* functions validate the URL's query string, other handlers validate the JSON
|
|
80
|
+
* request body. Returns the validated values (an empty object when no schema
|
|
81
|
+
* is declared), which callers pass through to the handler's event.
|
|
82
|
+
*
|
|
83
|
+
* @param schema The handler's (optional) schema
|
|
84
|
+
* @param functionName The handler name (`get`, `post`, `load`, etc)
|
|
85
|
+
* @param url The request URL, for the query string
|
|
86
|
+
* @param ev The server event, for the request body
|
|
87
|
+
* @returns The validated values
|
|
88
|
+
*/
|
|
89
|
+
async function validateHandlerInput(schema, functionName, url, ev) {
|
|
90
|
+
if (!schema) return {};
|
|
91
|
+
if (functionName === "get" || functionName === "head" || functionName === "load") return { query: await validate(schema, searchParamsToRecord(url.searchParams)) };
|
|
92
|
+
return { json: await validate(schema, await ev.json()) };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Turns a ValidationError thrown during endpoint validation into a 422
|
|
96
|
+
* response with the schema's issues. Returns undefined for any other error,
|
|
97
|
+
* which callers should rethrow.
|
|
98
|
+
*
|
|
99
|
+
* @param error The error thrown by a handler or validation
|
|
100
|
+
* @returns A 422 response, or undefined when the error isn't a validation failure
|
|
101
|
+
*/
|
|
102
|
+
function validationErrorResponse(error) {
|
|
103
|
+
if (error instanceof ValidationError) return unprocessable({
|
|
104
|
+
message: error.message,
|
|
105
|
+
issues: error.issues
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
8
109
|
//#region src/test/runTest.ts
|
|
9
110
|
/**
|
|
10
111
|
* Runs a site route in test mode
|
|
@@ -20,48 +121,85 @@ async function runTest(site, route, ev) {
|
|
|
20
121
|
router.addPages(site.routes.map((r) => ({
|
|
21
122
|
path: r.path,
|
|
22
123
|
type: r.type,
|
|
23
|
-
endPoint: () =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
124
|
+
endPoint: async () => {
|
|
125
|
+
if (r.file) {
|
|
126
|
+
const mod = await import(
|
|
127
|
+
/* @vite-ignore */
|
|
128
|
+
fpath.join(site.root, r.file)
|
|
129
|
+
);
|
|
130
|
+
if (r.file.endsWith(".torp")) return { default: { component: mod.default } };
|
|
131
|
+
return mod;
|
|
132
|
+
}
|
|
133
|
+
return { default: r.endPoint };
|
|
134
|
+
},
|
|
27
135
|
subFolder: r.subFolder
|
|
28
136
|
})));
|
|
29
137
|
return await load(router, ev, "%COMPONENT_BODY%");
|
|
30
138
|
}
|
|
31
139
|
async function load(router, ev, template) {
|
|
32
|
-
const url =
|
|
33
|
-
const path
|
|
140
|
+
const url = ev.url;
|
|
141
|
+
const path = url.pathname;
|
|
34
142
|
const query = url.searchParams;
|
|
35
|
-
const route = router.match(path
|
|
143
|
+
const route = router.match(path, query);
|
|
36
144
|
if (!route) return notFound();
|
|
37
|
-
$
|
|
145
|
+
$page.url = url;
|
|
146
|
+
if (path.endsWith("/_error")) {
|
|
147
|
+
$page.status = parseInt(query.get("status") ?? "404");
|
|
148
|
+
$page.error = { message: query.get("message") ?? "" };
|
|
149
|
+
} else $page.status = 200;
|
|
38
150
|
const handler = route.handler;
|
|
39
151
|
const params = route.params || {};
|
|
40
152
|
switch (handler.type) {
|
|
41
|
-
case
|
|
42
|
-
case
|
|
153
|
+
case 0:
|
|
154
|
+
case 4:
|
|
43
155
|
if (ev.request.method === "GET") return await loadView(ev, url, handler, params, template);
|
|
44
|
-
else if (ev.request.method === "POST") return await runAction(ev, url, handler, params, query);
|
|
156
|
+
else if (ev.request.method === "POST") return await runAction(ev, url, handler, handler.serverEndPoint && (await handler.serverEndPoint()).default, params, query);
|
|
45
157
|
break;
|
|
46
|
-
case
|
|
47
|
-
case
|
|
158
|
+
case 1:
|
|
159
|
+
case 5:
|
|
48
160
|
if (ev.request.method === "GET") return await loadData(ev, url, handler, "load", params);
|
|
161
|
+
else if (ev.request.method === "POST") return await runAction(ev, url, handler, handler.endPoint && (await handler.endPoint()).default, params, query);
|
|
49
162
|
break;
|
|
50
|
-
case
|
|
51
|
-
case
|
|
52
|
-
case
|
|
163
|
+
case 3: return await loadData(ev, url, handler, ev.request.method.toLowerCase().replace("delete", "del"), params);
|
|
164
|
+
case 8: return await loadView(ev, url, handler, params, template);
|
|
165
|
+
case 6:
|
|
166
|
+
case 7: break;
|
|
53
167
|
}
|
|
54
168
|
return notFound();
|
|
55
169
|
}
|
|
56
170
|
async function loadData(ev, url, handler, functionName, params) {
|
|
57
171
|
const serverEndPoint = (await handler.endPoint()).default;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
172
|
+
const handlerFn = serverEndPoint?.[functionName];
|
|
173
|
+
if (serverEndPoint && handlerFn) {
|
|
174
|
+
try {
|
|
175
|
+
params = await validateEndpointParams(serverEndPoint, params);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof ValidationError) return notFound();
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const values = await validateHandlerInput(endpointSchema(serverEndPoint, functionName), functionName, url, ev);
|
|
182
|
+
const serverParams = buildServerParams(ev, url, params, values);
|
|
183
|
+
const serverHooks = await loadServerHooks(handler);
|
|
184
|
+
let entered = 0;
|
|
185
|
+
let enterResponse = void 0;
|
|
186
|
+
try {
|
|
187
|
+
for (; entered < serverHooks.length && !enterResponse; entered++) {
|
|
188
|
+
const hookResult = await serverHooks[entered].enter?.(serverParams);
|
|
189
|
+
if (hookResult) enterResponse = hookResult;
|
|
190
|
+
}
|
|
191
|
+
return enterResponse || await handlerFn(serverParams) || ok();
|
|
192
|
+
} finally {
|
|
193
|
+
while (entered > 0) {
|
|
194
|
+
entered--;
|
|
195
|
+
await serverHooks[entered].exit?.(serverParams);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const response = validationErrorResponse(error);
|
|
200
|
+
if (response) return response;
|
|
201
|
+
throw error;
|
|
63
202
|
}
|
|
64
|
-
return await serverEndPoint[functionName](serverParams) || ok();
|
|
65
203
|
}
|
|
66
204
|
return notFound();
|
|
67
205
|
}
|
|
@@ -69,75 +207,130 @@ async function loadView(ev, url, handler, params, template) {
|
|
|
69
207
|
const clientEndPoint = (await handler.endPoint()).default;
|
|
70
208
|
if (!clientEndPoint?.component) return notFound();
|
|
71
209
|
const serverEndPoint = handler.serverEndPoint && (await handler.serverEndPoint())?.default;
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
210
|
+
let query;
|
|
211
|
+
if (serverEndPoint) {
|
|
212
|
+
try {
|
|
213
|
+
params = await validateEndpointParams(serverEndPoint, params);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error instanceof ValidationError) return notFound();
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
const loadSchema = endpointSchema(serverEndPoint, "load");
|
|
219
|
+
if (loadSchema) try {
|
|
220
|
+
query = await validate(loadSchema, searchParamsToRecord(url.searchParams));
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const response = validationErrorResponse(error);
|
|
223
|
+
if (response) return response;
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
82
226
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
227
|
+
const serverParams = buildServerParams(ev, url, params, query ? { query } : {});
|
|
228
|
+
const serverHooks = await loadServerHooks(handler);
|
|
229
|
+
let entered = 0;
|
|
230
|
+
let enterResponse = void 0;
|
|
231
|
+
try {
|
|
232
|
+
for (; entered < serverHooks.length && !enterResponse; entered++) {
|
|
233
|
+
const hookResult = await serverHooks[entered].enter?.(serverParams);
|
|
234
|
+
if (hookResult) enterResponse = hookResult;
|
|
235
|
+
}
|
|
236
|
+
if (enterResponse) return enterResponse;
|
|
237
|
+
let data = {};
|
|
238
|
+
if (handler.layouts) for (let layout of handler.layouts) {
|
|
239
|
+
const layoutEndPoint = (await layout.endPoint())?.default;
|
|
240
|
+
const layoutServerEndPoint = layout.serverEndPoint && (await layout.serverEndPoint())?.default;
|
|
241
|
+
const layoutResponse = await loadClientAndServerData(url, params, serverParams, data, layoutEndPoint, layoutServerEndPoint);
|
|
242
|
+
if (layoutResponse?.ok === false) return layoutResponse;
|
|
243
|
+
}
|
|
244
|
+
let endPointResponse = await loadClientAndServerData(url, params, serverParams, data, clientEndPoint, serverEndPoint);
|
|
245
|
+
if (endPointResponse?.ok === false) return endPointResponse;
|
|
246
|
+
let $props = { data };
|
|
247
|
+
let styles = "";
|
|
248
|
+
let component = clientEndPoint.component;
|
|
249
|
+
let slots = void 0;
|
|
250
|
+
if (handler.layouts) {
|
|
251
|
+
let slotFunctions = [];
|
|
252
|
+
slotFunctions[handler.layouts.length] = (_, $context) => {
|
|
253
|
+
let { body, head } = clientEndPoint.component($props, $context);
|
|
103
254
|
styles += head;
|
|
104
255
|
return body;
|
|
105
256
|
};
|
|
257
|
+
for (let i = handler.layouts.length - 1; i >= 0; i--) {
|
|
258
|
+
const layoutEndPoint = (await handler.layouts[i].endPoint())?.default;
|
|
259
|
+
if (layoutEndPoint?.component) if (i === 0) {
|
|
260
|
+
component = layoutEndPoint.component;
|
|
261
|
+
slots = { _: slotFunctions[i + 1] };
|
|
262
|
+
} else slotFunctions[i] = (_, $context) => {
|
|
263
|
+
let { body, head } = layoutEndPoint.component($props, $context, { _: slotFunctions[i + 1] });
|
|
264
|
+
styles += head;
|
|
265
|
+
return body;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
let html;
|
|
270
|
+
try {
|
|
271
|
+
let { body, head } = component($props, void 0, slots);
|
|
272
|
+
styles += head;
|
|
273
|
+
html = template.replace("%COMPONENT_BODY%", body).replace("%COMPONENT_HEAD%", styles);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
html = "<span style=\"color: red\">Script syntax error</span><p>" + error + "</p>";
|
|
276
|
+
console.log(error);
|
|
277
|
+
}
|
|
278
|
+
return new Response(html, {
|
|
279
|
+
status: 200,
|
|
280
|
+
headers: { "Content-Type": "text/html" }
|
|
281
|
+
});
|
|
282
|
+
} finally {
|
|
283
|
+
while (entered > 0) {
|
|
284
|
+
entered--;
|
|
285
|
+
await serverHooks[entered].exit?.(serverParams);
|
|
106
286
|
}
|
|
107
287
|
}
|
|
108
|
-
let html;
|
|
109
|
-
try {
|
|
110
|
-
let { body, head } = component($props, void 0, slots);
|
|
111
|
-
styles += head;
|
|
112
|
-
html = template.replace("%COMPONENT_BODY%", body).replace("%COMPONENT_HEAD", styles);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
html = "<span style=\"color: red\">Script syntax error</span><p>" + error + "</p>";
|
|
115
|
-
console.log(error);
|
|
116
|
-
}
|
|
117
|
-
return new Response(html, {
|
|
118
|
-
status: 200,
|
|
119
|
-
headers: { "Content-Type": "text/html" }
|
|
120
|
-
});
|
|
121
288
|
}
|
|
122
|
-
async function runAction(ev, url, handler, params, query) {
|
|
289
|
+
async function runAction(ev, url, handler, serverEndPoint, params, query) {
|
|
123
290
|
const actionName = (Array.from(query.keys())[0] || "default").replace(/^\//, "");
|
|
124
|
-
const serverEndPoint = handler.serverEndPoint && (await handler.serverEndPoint()).default;
|
|
125
291
|
if (serverEndPoint?.actions) {
|
|
126
292
|
const action = serverEndPoint.actions[actionName];
|
|
127
293
|
if (action) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
294
|
+
if (actionName === "load" || actionName === "params") throw new Error(`The action name "${actionName}" is reserved. Rename the action (and its schema key, if any), e.g. to "save".`);
|
|
295
|
+
try {
|
|
296
|
+
params = await validateEndpointParams(serverEndPoint, params);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (error instanceof ValidationError) return notFound();
|
|
299
|
+
throw error;
|
|
132
300
|
}
|
|
133
|
-
|
|
301
|
+
let result;
|
|
302
|
+
try {
|
|
303
|
+
const schema = endpointSchema(serverEndPoint, actionName);
|
|
304
|
+
const form = schema ? await validate(schema, await formDataToRecord(ev.request)) : void 0;
|
|
305
|
+
const serverParams = buildServerParams(ev, url, params, form ? { form } : {});
|
|
306
|
+
const serverHooks = await loadServerHooks(handler);
|
|
307
|
+
let entered = 0;
|
|
308
|
+
let enterResponse = void 0;
|
|
309
|
+
try {
|
|
310
|
+
for (; entered < serverHooks.length && !enterResponse; entered++) {
|
|
311
|
+
const hookResult = await serverHooks[entered].enter?.(serverParams);
|
|
312
|
+
if (hookResult) enterResponse = hookResult;
|
|
313
|
+
}
|
|
314
|
+
result = enterResponse || await action(serverParams);
|
|
315
|
+
} finally {
|
|
316
|
+
while (entered > 0) {
|
|
317
|
+
entered--;
|
|
318
|
+
await serverHooks[entered].exit?.(serverParams);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const response = validationErrorResponse(error);
|
|
323
|
+
if (response) return response;
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
return result || seeOther(url.pathname);
|
|
134
327
|
}
|
|
135
328
|
}
|
|
136
329
|
return notFound();
|
|
137
330
|
}
|
|
138
331
|
async function loadClientAndServerData(url, params, serverParams, data, clientEndPoint, serverEndPoint) {
|
|
139
332
|
if (clientEndPoint?.load) {
|
|
140
|
-
const clientParams = buildClientParams(url,
|
|
333
|
+
const clientParams = buildClientParams(url, params, data);
|
|
141
334
|
const clientResponse = await clientEndPoint.load(clientParams);
|
|
142
335
|
if (clientResponse) if (clientResponse.ok) {
|
|
143
336
|
if (clientResponse.headers.get("Content-Type")?.includes("application/json")) Object.assign(data, await clientResponse.json());
|
|
@@ -157,18 +350,29 @@ function buildClientParams(url, params, data) {
|
|
|
157
350
|
data
|
|
158
351
|
};
|
|
159
352
|
}
|
|
160
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Loads the route's server hooks, from the root down
|
|
355
|
+
*/
|
|
356
|
+
async function loadServerHooks(handler) {
|
|
357
|
+
const hooks = [];
|
|
358
|
+
if (handler.serverHooks) for (let serverHook of handler.serverHooks) hooks.push((await serverHook()).default);
|
|
359
|
+
return hooks;
|
|
360
|
+
}
|
|
361
|
+
function buildServerParams(ev, url, params, values = {}) {
|
|
161
362
|
return {
|
|
162
363
|
url,
|
|
163
364
|
params,
|
|
164
365
|
appData: {},
|
|
165
366
|
request: ev.request,
|
|
367
|
+
json: () => values.json !== void 0 ? Promise.resolve(values.json) : ev.json(),
|
|
368
|
+
form: () => values.form !== void 0 ? Promise.resolve(values.form) : formDataToRecord(ev.request),
|
|
369
|
+
query: () => values.query !== void 0 ? Promise.resolve(values.query) : Promise.resolve(searchParamsToRecord(url.searchParams)),
|
|
166
370
|
cookies: ev.cookies,
|
|
167
371
|
headers: ev.headers,
|
|
168
372
|
adapter: ev.adapter
|
|
169
373
|
};
|
|
170
374
|
}
|
|
171
|
-
|
|
172
375
|
//#endregion
|
|
173
376
|
export { runTest };
|
|
377
|
+
|
|
174
378
|
//# sourceMappingURL=test.mjs.map
|
package/dist/test.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.mjs","names":["path","serverEndPoint: ServerEndPoint | undefined","serverHook: ServerHook | undefined","clientEndPoint: PageEndPoint | undefined","serverEndPoint: PageServerEndPoint | undefined","layoutEndPoint: PageEndPoint | undefined","$props: Record<string, any>","slots: Record<string, ServerSlotRender> | undefined","slotFunctions: ServerSlotRender[]"],"sources":["../src/test/runTest.ts"],"sourcesContent":["import { type ServerComponent, type ServerSlotRender } from \"@torpor/view/ssr\";\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 type 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\tsubFolder: r.subFolder,\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\tlet styles = \"\";\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\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\tlet { body, head } = (clientEndPoint.component as ServerComponent)($props, $context);\n\t\t\tstyles += head;\n\t\t\treturn body;\n\t\t};\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\tlet { body, head } = (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\t\tstyles += head;\n\t\t\t\t\t\treturn body;\n\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\tstyles += head;\n\t\thtml = template.replace(\"%COMPONENT_BODY%\", body).replace(\"%COMPONENT_HEAD\", styles);\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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"}
|
|
1
|
+
{"version":3,"file":"test.mjs","names":["path","handlers"],"sources":["../src/form/formDataToRecord.ts","../src/state/$serverPage.ts","../src/utils/searchParamsToRecord.ts","../src/validation/endpoint.ts","../src/test/runTest.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\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","import { type ServerComponent, type ServerSlotRender } from \"@torpor/view/ssr\";\nimport path from \"node:path\";\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 Router from \"../site/Router.ts\";\nimport Site from \"../site/Site\";\nimport $page from \"../state/$serverPage.ts\";\nimport type PageEndPoint from \"../types/PageEndPoint.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\tvalidateHandlerInput,\n\tvalidationErrorResponse,\n} from \"../validation/endpoint.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: async () => {\n\t\t\t\tif (r.file) {\n\t\t\t\t\tconst mod = await import(/* @vite-ignore */ path.join(site.root, r.file));\n\t\t\t\t\t// A .torp file's default export is a component, so wrap it as\n\t\t\t\t\t// a PageEndPoint ({ component }) for the entries\n\t\t\t\t\tif (r.file.endsWith(\".torp\")) {\n\t\t\t\t\t\treturn { default: { component: mod.default } };\n\t\t\t\t\t}\n\t\t\t\t\treturn mod;\n\t\t\t\t}\n\t\t\t\t// Inline endpoint (no file) — return directly from memory\n\t\t\t\treturn { default: r.endPoint };\n\t\t\t},\n\t\t\tsubFolder: r.subFolder,\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 = ev.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\tif (path.endsWith(\"/_error\")) {\n\t\t$page.status = parseInt(query.get(\"status\") ?? \"404\");\n\t\t$page.error = { message: query.get(\"message\") ?? \"\" };\n\t} else {\n\t\t$page.status = 200;\n\t}\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\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\t\t\t\thandler.serverEndPoint && (await handler.serverEndPoint()).default;\n\t\t\t\treturn await runAction(ev, url, handler, serverEndPoint, 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} else if (ev.request.method === \"POST\") {\n\t\t\t\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\t\t\t\thandler.endPoint && (await handler.endPoint()).default;\n\t\t\t\treturn await runAction(ev, url, handler, serverEndPoint, params, query);\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 ERROR_ROUTE: {\n\t\t\treturn await loadView(ev, url, handler, params, template);\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, any>,\n) {\n\tconst serverEndPoint: ServerEndPoint | undefined = (await handler.endPoint()).default;\n\t// `functionName` is computed from the request method, so the handlers are\n\t// accessed through a loose record\n\tconst handlers = serverEndPoint as unknown as Record<string, ServerRequest | undefined>;\n\tconst handlerFn: ServerRequest | undefined = handlers?.[functionName];\n\tif (serverEndPoint && handlerFn) {\n\t\t// Route params come from the URL, so a failed validation means the\n\t\t// resource doesn't exist\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\n\t\ttry {\n\t\t\t// If the endpoint declares a schema for this handler, validate its\n\t\t\t// input up front and reject with 422 if it fails. get/head schemas\n\t\t\t// validate the query string; other schemas validate the json body\n\t\t\tconst schema = endpointSchema(serverEndPoint, functionName);\n\t\t\tconst values = await validateHandlerInput(schema, functionName, url, ev);\n\n\t\t\tconst serverParams = buildServerParams(ev, url, params, values);\n\n\t\t\tconst serverHooks = await loadServerHooks(handler);\n\t\t\tlet entered = 0;\n\t\t\tlet enterResponse: Response | undefined = undefined;\n\t\t\ttry {\n\t\t\t\t// Hooks run from the root down; a hook's enter can return a\n\t\t\t\t// Response to short-circuit the request\n\t\t\t\tfor (; entered < serverHooks.length && !enterResponse; entered++) {\n\t\t\t\t\tconst hookResult = await serverHooks[entered].enter?.(serverParams);\n\t\t\t\t\tif (hookResult) {\n\t\t\t\t\t\tenterResponse = hookResult;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst result = enterResponse || (await handlerFn(serverParams));\n\n\t\t\t\t// If there was no response returned from load (such as errors or a\n\t\t\t\t// redirect), send an ok response\n\t\t\t\treturn result || ok();\n\t\t\t} finally {\n\t\t\t\t// Run exit hooks in reverse order for hooks that were entered\n\t\t\t\twhile (entered > 0) {\n\t\t\t\t\tentered--;\n\t\t\t\t\tawait serverHooks[entered].exit?.(serverParams);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst response = validationErrorResponse(error);\n\t\t\tif (response) return response;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\treturn notFound();\n}\n\nasync function loadView(\n\tev: ServerEvent,\n\turl: URL,\n\thandler: RouteHandler,\n\tparams: Record<string, any>,\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// 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\n\t// returns 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\ttry {\n\t\t\t\tquery = await validate(loadSchema, searchParamsToRecord(url.searchParams));\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\t\t}\n\t}\n\n\t// Maybe hit the server hooks\n\tconst serverParams = buildServerParams(ev, url, params, query ? { query } : {});\n\tconst serverHooks = 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 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 };\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\tif (handler.layouts) {\n\t\t\tlet slotFunctions: ServerSlotRender[] = [];\n\t\t\tslotFunctions[handler.layouts.length] = (_, $context) => {\n\t\t\t\tlet { body, head } = (clientEndPoint.component as ServerComponent)($props, $context);\n\t\t\t\tstyles += head;\n\t\t\t\treturn body;\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] = (_, $context) => {\n\t\t\t\t\t\t\tlet { body, head } = (layoutEndPoint.component as ServerComponent)($props, $context, {\n\t\t\t\t\t\t\t\t_: slotFunctions[i + 1],\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 } = component($props, undefined, slots);\n\t\t\tstyles += head;\n\t\t\thtml = template.replace(\"%COMPONENT_BODY%\", body).replace(\"%COMPONENT_HEAD%\", styles);\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\treturn new Response(html, {\n\t\t\tstatus: 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) {\n\tconst actionName = (Array.from(query.keys())[0] || \"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// TODO: form.errors etc\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\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\tconst serverParams = 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\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, 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\n/**\n * Loads the route's server hooks, from the root down\n */\nasync function loadServerHooks(handler: RouteHandler): Promise<ServerHook[]> {\n\tconst hooks: ServerHook[] = [];\n\tif (handler.serverHooks) {\n\t\tfor (let serverHook of handler.serverHooks) {\n\t\t\thooks.push((await serverHook()).default);\n\t\t}\n\t}\n\treturn hooks;\n}\n\nfunction buildServerParams(\n\tev: ServerEvent,\n\turl: URL,\n\tparams: Record<string, any>,\n\tvalues: { json?: unknown; form?: unknown; query?: unknown } = {},\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\tadapter: ev.adapter,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a response with the supplied status code and optional body.
|
|
4
4
|
*
|
|
5
|
-
* If the body is an object, it will be converted to JSON and the
|
|
5
|
+
* If the body is an object, it will be converted to JSON and the Content-Type
|
|
6
6
|
* header set to "application/json". Otherwise, if the body is a string, the
|
|
7
|
-
*
|
|
7
|
+
* Content-Type header will be set to "text/plain".
|
|
8
8
|
*/
|
|
9
9
|
function response(status, body) {
|
|
10
10
|
let headers = void 0;
|
|
11
11
|
if (body) if (typeof body === "object") {
|
|
12
|
-
headers = { "
|
|
12
|
+
headers = { "Content-Type": "application/json" };
|
|
13
13
|
body = JSON.stringify(body);
|
|
14
|
-
} else headers = { "
|
|
14
|
+
} else headers = { "Content-Type": "text/plain" };
|
|
15
15
|
return new Response(body, {
|
|
16
16
|
status,
|
|
17
17
|
headers
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
|
|
21
20
|
//#endregion
|
|
22
21
|
//#region src/response/transfer.ts
|
|
23
22
|
/**
|
|
@@ -29,7 +28,6 @@ function transfer(status, location) {
|
|
|
29
28
|
headers: { location }
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
|
-
|
|
33
31
|
//#endregion
|
|
34
32
|
//#region src/response/notFound.ts
|
|
35
33
|
/**
|
|
@@ -54,7 +52,6 @@ function transfer(status, location) {
|
|
|
54
52
|
function notFound(body) {
|
|
55
53
|
return response(404, body ?? "Not found");
|
|
56
54
|
}
|
|
57
|
-
|
|
58
55
|
//#endregion
|
|
59
56
|
//#region src/response/ok.ts
|
|
60
57
|
/**
|
|
@@ -74,17 +71,19 @@ function notFound(body) {
|
|
|
74
71
|
* - TRACE: The response has a message body containing the request as received
|
|
75
72
|
* by the server.
|
|
76
73
|
*
|
|
77
|
-
* Although possible, successful PUT or DELETE requests often do not result in
|
|
78
|
-
* 200 OK response. It is more common to see 201 Created if the resource is
|
|
74
|
+
* Although possible, successful PUT or DELETE requests often do not result in
|
|
75
|
+
* a 200 OK response. It is more common to see 201 Created if the resource is
|
|
79
76
|
* uploaded or created for the first time, or 204 No Content upon successful
|
|
80
77
|
* deletion of a resource.
|
|
81
78
|
*
|
|
79
|
+
* An object body is typed: the client sees its JSON form through
|
|
80
|
+
* `makeApi`-created callers.
|
|
81
|
+
*
|
|
82
82
|
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200
|
|
83
83
|
*/
|
|
84
84
|
function ok(body) {
|
|
85
85
|
return response(200, body);
|
|
86
86
|
}
|
|
87
|
-
|
|
88
87
|
//#endregion
|
|
89
88
|
//#region src/response/seeOther.ts
|
|
90
89
|
/**
|
|
@@ -104,7 +103,29 @@ function ok(body) {
|
|
|
104
103
|
function seeOther(location) {
|
|
105
104
|
return transfer(303, location);
|
|
106
105
|
}
|
|
107
|
-
|
|
108
106
|
//#endregion
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
//#region src/response/unprocessable.ts
|
|
108
|
+
/**
|
|
109
|
+
* 422 Unprocessable Content
|
|
110
|
+
*
|
|
111
|
+
* The HTTP 422 Unprocessable Content client error response status code
|
|
112
|
+
* indicates that the server understood the content type of the request content,
|
|
113
|
+
* and the syntax of the request content was correct, but it was unable to
|
|
114
|
+
* process the contained instructions.
|
|
115
|
+
*
|
|
116
|
+
* Clients that receive a 422 response should expect that repeating the request
|
|
117
|
+
* without modification will fail with the same error.
|
|
118
|
+
*
|
|
119
|
+
* An object body is typed: it becomes the page's `$props.form` (with its JSON
|
|
120
|
+
* form) after a form submit, so validation errors can be surfaced with their
|
|
121
|
+
* field types via `PageForm`.
|
|
122
|
+
*
|
|
123
|
+
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/422
|
|
124
|
+
*/
|
|
125
|
+
function unprocessable(body) {
|
|
126
|
+
return response(422, body ?? "Unprocessable");
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { transfer as a, notFound as i, seeOther as n, response as o, ok as r, unprocessable as t };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=unprocessable-DAZbrDeR.mjs.map
|