@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.
Files changed (176) hide show
  1. package/README.md +20 -2
  2. package/dist/Jsonify-Dg6gChd3.d.mts +10 -0
  3. package/dist/Jsonify-Dg6gChd3.d.mts.map +1 -0
  4. package/dist/ParseRouteParams-DBmqlmLh.d.mts +42 -0
  5. package/dist/ParseRouteParams-DBmqlmLh.d.mts.map +1 -0
  6. package/dist/{RouteType-B6BMOyYw.mjs → Router-C4ADUfhv.mjs} +30 -42
  7. package/dist/Router-C4ADUfhv.mjs.map +1 -0
  8. package/dist/{Server-CF5wDJp6.d.mts → Server-C_IKav_e.d.mts} +44 -16
  9. package/dist/Server-C_IKav_e.d.mts.map +1 -0
  10. package/dist/{ServerEvent-CxPwA4lH.mjs → ServerEvent-Dljn5i7A.mjs} +37 -11
  11. package/dist/ServerEvent-Dljn5i7A.mjs.map +1 -0
  12. package/dist/Site-CMa153FA.d.mts +565 -0
  13. package/dist/Site-CMa153FA.d.mts.map +1 -0
  14. package/dist/StandardSchema-D28L8_zZ.d.mts +100 -0
  15. package/dist/StandardSchema-D28L8_zZ.d.mts.map +1 -0
  16. package/dist/TypedResponse-6lznqN4U.d.mts +22 -0
  17. package/dist/TypedResponse-6lznqN4U.d.mts.map +1 -0
  18. package/dist/{_page-BQs4WvCO.mjs → _page-4cQKs_Dn.mjs} +3 -5
  19. package/dist/_page-4cQKs_Dn.mjs.map +1 -0
  20. package/dist/bin/index.d.mts +1 -1
  21. package/dist/bin/index.js +15 -11
  22. package/dist/bin/index.js.map +1 -1
  23. package/dist/flattenHeaders-C_YYLdOq.mjs +114 -0
  24. package/dist/flattenHeaders-C_YYLdOq.mjs.map +1 -0
  25. package/dist/form.d.mts +35 -0
  26. package/dist/form.d.mts.map +1 -0
  27. package/dist/form.mjs +48 -0
  28. package/dist/form.mjs.map +1 -0
  29. package/dist/index.d.mts +72 -156
  30. package/dist/index.d.mts.map +1 -1
  31. package/dist/index.mjs +164 -32
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/nav.d.mts +58 -5
  34. package/dist/nav.d.mts.map +1 -1
  35. package/dist/nav.mjs +116 -38
  36. package/dist/nav.mjs.map +1 -1
  37. package/dist/openapi.d.mts +144 -0
  38. package/dist/openapi.d.mts.map +1 -0
  39. package/dist/openapi.mjs +2 -0
  40. package/dist/pathTrie-D4Ax2Mu9.mjs +160 -0
  41. package/dist/pathTrie-D4Ax2Mu9.mjs.map +1 -0
  42. package/dist/plugin-D8Www11B.mjs +202 -0
  43. package/dist/plugin-D8Www11B.mjs.map +1 -0
  44. package/dist/{response-C5TtAsh1.mjs → response-Bg4w2S1q.mjs} +16 -39
  45. package/dist/response-Bg4w2S1q.mjs.map +1 -0
  46. package/dist/response.d.mts +309 -289
  47. package/dist/response.d.mts.map +1 -1
  48. package/dist/response.mjs +3 -4
  49. package/dist/run.d.mts +11 -4
  50. package/dist/run.d.mts.map +1 -1
  51. package/dist/run.mjs +2 -8
  52. package/dist/runOpenApi-BUi1RH1l.mjs +1381 -0
  53. package/dist/runOpenApi-BUi1RH1l.mjs.map +1 -0
  54. package/dist/schema.d.mts +32 -0
  55. package/dist/schema.d.mts.map +1 -0
  56. package/dist/schema.mjs +2 -0
  57. package/dist/server.d.mts +10 -8
  58. package/dist/server.d.mts.map +1 -1
  59. package/dist/server.mjs +132 -4
  60. package/dist/server.mjs.map +1 -0
  61. package/dist/state.d.mts.map +1 -1
  62. package/dist/state.mjs +2 -3
  63. package/dist/test.d.mts +8 -10
  64. package/dist/test.d.mts.map +1 -1
  65. package/dist/test.mjs +286 -82
  66. package/dist/test.mjs.map +1 -1
  67. package/dist/{seeOther-B4Yhu9iq.mjs → unprocessable-DAZbrDeR.mjs} +34 -13
  68. package/dist/unprocessable-DAZbrDeR.mjs.map +1 -0
  69. package/dist/validate-Ok4krlkX.mjs +40 -0
  70. package/dist/validate-Ok4krlkX.mjs.map +1 -0
  71. package/package.json +28 -17
  72. package/src/bin/index.ts +13 -2
  73. package/src/dev.ts +10 -0
  74. package/src/form/formDataToRecord.ts +23 -0
  75. package/src/form/readForm.ts +71 -0
  76. package/src/form.ts +4 -0
  77. package/src/index.ts +53 -1
  78. package/src/nav/api.test-d.ts +79 -0
  79. package/src/nav/api.ts +104 -0
  80. package/src/nav/formSubmit.ts +18 -4
  81. package/src/nav/navigate.ts +40 -15
  82. package/src/nav/route.ts +38 -0
  83. package/src/nav.ts +3 -1
  84. package/src/openapi/docsHtml.ts +25 -0
  85. package/src/openapi/document.ts +154 -0
  86. package/src/openapi/plugin.ts +78 -0
  87. package/src/openapi/types.ts +82 -0
  88. package/src/openapi.ts +14 -0
  89. package/src/response/TypedResponse.ts +17 -0
  90. package/src/response/badRequest.ts +14 -2
  91. package/src/response/created.ts +13 -2
  92. package/src/response/found.ts +2 -2
  93. package/src/response/movedPermanently.ts +2 -2
  94. package/src/response/notModified.ts +2 -2
  95. package/src/response/ok.ts +13 -4
  96. package/src/response/response.ts +4 -4
  97. package/src/response/unprocessable.ts +14 -2
  98. package/src/run/depCache.ts +175 -0
  99. package/src/run/devPlugin.ts +128 -0
  100. package/src/run/prepareTemplate.ts +6 -3
  101. package/src/run/run.ts +63 -39
  102. package/src/run/runBuild.ts +63 -12
  103. package/src/run/runDev.ts +146 -58
  104. package/src/run/runOpenApi.ts +52 -0
  105. package/src/run/runPreview.ts +17 -26
  106. package/src/run/staleTorpCopies.ts +100 -0
  107. package/src/run.ts +2 -1
  108. package/src/schema.ts +7 -0
  109. package/src/server/CookieHelper.ts +17 -7
  110. package/src/server/Server.ts +54 -30
  111. package/src/server/ServerEvent.ts +24 -1
  112. package/src/server/connect/connectMiddleware.ts +44 -40
  113. package/src/server/connect/flattenHeaders.ts +1 -23
  114. package/src/server/connect/requestToNodeMessage.ts +6 -3
  115. package/src/server/types/MiddlewareFunction.ts +18 -4
  116. package/src/site/Router.ts +52 -41
  117. package/src/site/Site.ts +184 -10
  118. package/src/site/checkLayoutSlots.ts +114 -0
  119. package/src/site/checkRoutes.ts +435 -0
  120. package/src/site/clientEntry.ts +22 -9
  121. package/src/site/layoutSlots.ts +53 -0
  122. package/src/site/manifest.ts +125 -6
  123. package/src/site/serverEntry.ts +352 -133
  124. package/src/state/$page.ts +2 -1
  125. package/src/state/$serverPage.ts +22 -0
  126. package/src/test/runTest.ts +306 -118
  127. package/src/types/Adapter.ts +12 -0
  128. package/src/types/Jsonify.ts +20 -0
  129. package/src/types/PageData.test-d.ts +136 -0
  130. package/src/types/PageData.ts +35 -0
  131. package/src/types/PageEndPoint.ts +25 -9
  132. package/src/types/PageForm.test-d.ts +78 -0
  133. package/src/types/PageForm.ts +29 -0
  134. package/src/types/PageLoadEvent.ts +16 -4
  135. package/src/types/PageLoadReturn.ts +13 -0
  136. package/src/types/PageProps.ts +14 -0
  137. package/src/types/PageServerAction.ts +8 -2
  138. package/src/types/PageServerEndPoint.test-d.ts +105 -0
  139. package/src/types/PageServerEndPoint.ts +99 -5
  140. package/src/types/PageServerLoad.ts +11 -3
  141. package/src/types/ParseRouteParams.test-d.ts +84 -0
  142. package/src/types/ParseRouteParams.ts +64 -0
  143. package/src/types/Route.ts +20 -1
  144. package/src/types/RouteHandler.ts +8 -1
  145. package/src/types/ServerEndPoint.test-d.ts +77 -0
  146. package/src/types/ServerEndPoint.ts +148 -16
  147. package/src/types/ServerHook.ts +10 -3
  148. package/src/types/ServerLoadEvent.ts +48 -3
  149. package/src/types/ServerRequest.ts +10 -2
  150. package/src/types/SitePlugin.ts +24 -0
  151. package/src/types/StandardSchema.ts +109 -0
  152. package/src/utils/pathToRegex.ts +4 -2
  153. package/src/utils/pathTrie.ts +182 -0
  154. package/src/utils/searchParamsToRecord.ts +18 -0
  155. package/src/utils/torporPackages.ts +150 -0
  156. package/src/utils/tsconfigAliases.ts +90 -0
  157. package/src/validation/ValidationError.ts +18 -0
  158. package/src/validation/endpoint.ts +83 -0
  159. package/src/validation/validate.ts +26 -0
  160. package/dist/RouteType-B6BMOyYw.mjs.map +0 -1
  161. package/dist/Server-CF5wDJp6.d.mts.map +0 -1
  162. package/dist/ServerEvent-CxPwA4lH.mjs.map +0 -1
  163. package/dist/Site-DgC6WWn1.d.mts +0 -78
  164. package/dist/Site-DgC6WWn1.d.mts.map +0 -1
  165. package/dist/_page-BQs4WvCO.mjs.map +0 -1
  166. package/dist/connectMiddleware-D7nehjj3.mjs +0 -252
  167. package/dist/connectMiddleware-D7nehjj3.mjs.map +0 -1
  168. package/dist/pathToRegex-TUAMc3zS.mjs +0 -11
  169. package/dist/pathToRegex-TUAMc3zS.mjs.map +0 -1
  170. package/dist/response-C5TtAsh1.mjs.map +0 -1
  171. package/dist/run-DiZgkMnG.mjs +0 -332
  172. package/dist/run-DiZgkMnG.mjs.map +0 -1
  173. package/dist/seeOther-B4Yhu9iq.mjs.map +0 -1
  174. package/src/server/Routerx.ts +0 -72
  175. package/src/server/connect/bufferToArrayBuffer.ts +0 -8
  176. package/src/server/connect/readableToBuffer.ts +0 -16
package/dist/test.mjs CHANGED
@@ -1,10 +1,111 @@
1
- import "./pathToRegex-TUAMc3zS.mjs";
2
- import { a as LAYOUT_SERVER_ROUTE, c as SERVER_ROUTE, i as LAYOUT_ROUTE, l as Router, n as HOOK_ROUTE, o as PAGE_ROUTE, r as HOOK_SERVER_ROUTE, s as PAGE_SERVER_ROUTE } from "./RouteType-B6BMOyYw.mjs";
3
- import { t as $page_default } from "./_page-BQs4WvCO.mjs";
4
- import { n as ok, r as notFound, t as seeOther } from "./seeOther-B4Yhu9iq.mjs";
5
- import { t as ServerEvent } from "./ServerEvent-CxPwA4lH.mjs";
6
- import path from "node:path";
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: () => import(
24
- /* @vite-ignore */
25
- path.join(site.root, r.file)
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 = new URL(ev.request.url);
33
- const path$1 = url.pathname;
140
+ const url = ev.url;
141
+ const path = url.pathname;
34
142
  const query = url.searchParams;
35
- const route = router.match(path$1, query);
143
+ const route = router.match(path, query);
36
144
  if (!route) return notFound();
37
- $page_default.url = url;
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 PAGE_ROUTE:
42
- case LAYOUT_ROUTE:
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 PAGE_SERVER_ROUTE:
47
- case LAYOUT_SERVER_ROUTE:
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 SERVER_ROUTE: return await loadData(ev, url, handler, ev.request.method.toLowerCase().replace("delete", "del"), params);
51
- case HOOK_ROUTE:
52
- case HOOK_SERVER_ROUTE: break;
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
- if (serverEndPoint && serverEndPoint[functionName]) {
59
- const serverParams = buildServerParams(ev, url, params);
60
- if (handler.serverHook) {
61
- const serverHook = (await handler.serverHook()).default;
62
- if (serverHook?.handle) await serverHook.handle(serverParams);
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
- const serverParams = buildServerParams(ev, url, params);
73
- if (handler.serverHook) {
74
- const serverHook = (await handler.serverHook()).default;
75
- if (serverHook?.handle) await serverHook.handle(serverParams);
76
- }
77
- let data = {};
78
- if (handler.layouts) for (let layout of handler.layouts) {
79
- const layoutEndPoint = (await layout.endPoint())?.default;
80
- const layoutResponse = await loadClientAndServerData(url, params, serverParams, data, layoutEndPoint, layout.serverEndPoint && (await layout.serverEndPoint())?.default);
81
- if (layoutResponse?.ok === false) return layoutResponse;
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
- let endPointResponse = await loadClientAndServerData(url, params, serverParams, data, clientEndPoint, serverEndPoint);
84
- if (endPointResponse?.ok === false) return endPointResponse;
85
- let $props = { data };
86
- let styles = "";
87
- let component = clientEndPoint.component;
88
- let slots = void 0;
89
- if (handler.layouts) {
90
- let slotFunctions = [];
91
- slotFunctions[handler.layouts.length] = (_, $context) => {
92
- let { body, head } = clientEndPoint.component($props, $context);
93
- styles += head;
94
- return body;
95
- };
96
- for (let i = handler.layouts.length - 1; i >= 0; i--) {
97
- const layoutEndPoint = (await handler.layouts[i].endPoint())?.default;
98
- if (layoutEndPoint?.component) if (i === 0) {
99
- component = layoutEndPoint.component;
100
- slots = { _: slotFunctions[i + 1] };
101
- } else slotFunctions[i] = (_, $context) => {
102
- let { body, head } = layoutEndPoint.component($props, $context, { _: slotFunctions[i + 1] });
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
- const serverParams = buildServerParams(ev, url, params);
129
- if (handler.serverHook) {
130
- const serverHook = (await handler.serverHook()).default;
131
- if (serverHook?.handle) await serverHook.handle(serverParams);
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
- return await action(serverParams) || seeOther(url.pathname);
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, data, params);
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
- function buildServerParams(ev, url, params) {
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 content-type
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
- * content-type header will be set to "text/plain".
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 = { "content-type": "application/json" };
12
+ headers = { "Content-Type": "application/json" };
13
13
  body = JSON.stringify(body);
14
- } else headers = { "content-type": "text/plain" };
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 a
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
- export { response as a, transfer as i, ok as n, notFound as r, seeOther as t };
110
- //# sourceMappingURL=seeOther-B4Yhu9iq.mjs.map
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