@torpor/build 0.4.14 → 1.0.1

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 (193) 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-BnjLJleZ.mjs +104 -0
  9. package/dist/Server-BnjLJleZ.mjs.map +1 -0
  10. package/dist/Server-Cler2w5O.d.mts +61 -0
  11. package/dist/Server-Cler2w5O.d.mts.map +1 -0
  12. package/dist/ServerEvent-CTx3Q-hk.d.mts +48 -0
  13. package/dist/ServerEvent-CTx3Q-hk.d.mts.map +1 -0
  14. package/dist/{ServerEvent-CxPwA4lH.mjs → ServerEvent-Dljn5i7A.mjs} +37 -11
  15. package/dist/ServerEvent-Dljn5i7A.mjs.map +1 -0
  16. package/dist/Site-CE0_o5Ci.d.mts +566 -0
  17. package/dist/Site-CE0_o5Ci.d.mts.map +1 -0
  18. package/dist/StandardSchema-D28L8_zZ.d.mts +100 -0
  19. package/dist/StandardSchema-D28L8_zZ.d.mts.map +1 -0
  20. package/dist/TypedResponse-6lznqN4U.d.mts +22 -0
  21. package/dist/TypedResponse-6lznqN4U.d.mts.map +1 -0
  22. package/dist/{_page-BQs4WvCO.mjs → _page-4cQKs_Dn.mjs} +3 -5
  23. package/dist/_page-4cQKs_Dn.mjs.map +1 -0
  24. package/dist/bin/index.d.mts +1 -1
  25. package/dist/bin/index.js +15 -11
  26. package/dist/bin/index.js.map +1 -1
  27. package/dist/clientEntry.d.mts +1 -0
  28. package/dist/clientEntry.mjs +59 -0
  29. package/dist/clientEntry.mjs.map +1 -0
  30. package/dist/clientEntryDev.d.mts +1 -0
  31. package/dist/clientEntryDev.mjs +7 -0
  32. package/dist/clientEntryDev.mjs.map +1 -0
  33. package/dist/dev.d.mts +3 -0
  34. package/dist/dev.mjs +3 -0
  35. package/dist/endpoint-EULRGCKA.mjs +108 -0
  36. package/dist/endpoint-EULRGCKA.mjs.map +1 -0
  37. package/dist/flattenHeaders-DM5qsSqO.mjs +13 -0
  38. package/dist/flattenHeaders-DM5qsSqO.mjs.map +1 -0
  39. package/dist/form.d.mts +35 -0
  40. package/dist/form.d.mts.map +1 -0
  41. package/dist/form.mjs +48 -0
  42. package/dist/form.mjs.map +1 -0
  43. package/dist/index.d.mts +72 -156
  44. package/dist/index.d.mts.map +1 -1
  45. package/dist/index.mjs +158 -26
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/load-BntzvmwL.mjs +228 -0
  48. package/dist/load-BntzvmwL.mjs.map +1 -0
  49. package/dist/nav.d.mts +58 -5
  50. package/dist/nav.d.mts.map +1 -1
  51. package/dist/nav.mjs +63 -209
  52. package/dist/nav.mjs.map +1 -1
  53. package/dist/openapi.d.mts +144 -0
  54. package/dist/openapi.d.mts.map +1 -0
  55. package/dist/openapi.mjs +2 -0
  56. package/dist/pathTrie-D4Ax2Mu9.mjs +160 -0
  57. package/dist/pathTrie-D4Ax2Mu9.mjs.map +1 -0
  58. package/dist/plugin-D8Www11B.mjs +202 -0
  59. package/dist/plugin-D8Www11B.mjs.map +1 -0
  60. package/dist/{response-C5TtAsh1.mjs → response-Bg4w2S1q.mjs} +16 -39
  61. package/dist/response-Bg4w2S1q.mjs.map +1 -0
  62. package/dist/response.d.mts +309 -289
  63. package/dist/response.d.mts.map +1 -1
  64. package/dist/response.mjs +3 -4
  65. package/dist/run.d.mts +11 -4
  66. package/dist/run.d.mts.map +1 -1
  67. package/dist/run.mjs +2 -8
  68. package/dist/runOpenApi-DlOgF2Il.mjs +1419 -0
  69. package/dist/runOpenApi-DlOgF2Il.mjs.map +1 -0
  70. package/dist/schema.d.mts +32 -0
  71. package/dist/schema.d.mts.map +1 -0
  72. package/dist/schema.mjs +2 -0
  73. package/dist/server/Server.d.mts +2 -0
  74. package/dist/server/Server.mjs +2 -0
  75. package/dist/server.d.mts +11 -8
  76. package/dist/server.d.mts.map +1 -1
  77. package/dist/server.mjs +133 -4
  78. package/dist/server.mjs.map +1 -0
  79. package/dist/serverEntry-1qY3wZig.mjs +351 -0
  80. package/dist/serverEntry-1qY3wZig.mjs.map +1 -0
  81. package/dist/serverEntry-BAO8m03V.d.mts +6 -0
  82. package/dist/serverEntry-BAO8m03V.d.mts.map +1 -0
  83. package/dist/serverEntry.d.mts +2 -0
  84. package/dist/serverEntry.mjs +2 -0
  85. package/dist/state.d.mts.map +1 -1
  86. package/dist/state.mjs +2 -3
  87. package/dist/test.d.mts +8 -10
  88. package/dist/test.d.mts.map +1 -1
  89. package/dist/test.mjs +181 -79
  90. package/dist/test.mjs.map +1 -1
  91. package/dist/{seeOther-B4Yhu9iq.mjs → unprocessable-DAZbrDeR.mjs} +34 -13
  92. package/dist/unprocessable-DAZbrDeR.mjs.map +1 -0
  93. package/dist/validate-Ok4krlkX.mjs +40 -0
  94. package/dist/validate-Ok4krlkX.mjs.map +1 -0
  95. package/package.json +30 -18
  96. package/dist/RouteType-B6BMOyYw.mjs.map +0 -1
  97. package/dist/Server-CF5wDJp6.d.mts +0 -78
  98. package/dist/Server-CF5wDJp6.d.mts.map +0 -1
  99. package/dist/ServerEvent-CxPwA4lH.mjs.map +0 -1
  100. package/dist/Site-DgC6WWn1.d.mts +0 -78
  101. package/dist/Site-DgC6WWn1.d.mts.map +0 -1
  102. package/dist/_page-BQs4WvCO.mjs.map +0 -1
  103. package/dist/connectMiddleware-D7nehjj3.mjs +0 -252
  104. package/dist/connectMiddleware-D7nehjj3.mjs.map +0 -1
  105. package/dist/pathToRegex-TUAMc3zS.mjs +0 -11
  106. package/dist/pathToRegex-TUAMc3zS.mjs.map +0 -1
  107. package/dist/response-C5TtAsh1.mjs.map +0 -1
  108. package/dist/run-BPFtSYA3.mjs +0 -332
  109. package/dist/run-BPFtSYA3.mjs.map +0 -1
  110. package/dist/seeOther-B4Yhu9iq.mjs.map +0 -1
  111. package/src/bin/index.ts +0 -11
  112. package/src/index.ts +0 -30
  113. package/src/nav/formSubmit.ts +0 -56
  114. package/src/nav/load.ts +0 -9
  115. package/src/nav/loadData.ts +0 -134
  116. package/src/nav/navigate.ts +0 -164
  117. package/src/nav/reload.ts +0 -9
  118. package/src/nav.ts +0 -4
  119. package/src/response/badRequest.ts +0 -19
  120. package/src/response/created.ts +0 -20
  121. package/src/response/forbidden.ts +0 -21
  122. package/src/response/found.ts +0 -30
  123. package/src/response/methodNotAllowed.ts +0 -18
  124. package/src/response/movedPermanently.ts +0 -25
  125. package/src/response/notFound.ts +0 -24
  126. package/src/response/notModified.ts +0 -34
  127. package/src/response/ok.ts +0 -29
  128. package/src/response/permanentRedirect.ts +0 -30
  129. package/src/response/response.ts +0 -22
  130. package/src/response/seeOther.ts +0 -19
  131. package/src/response/serverError.ts +0 -24
  132. package/src/response/temporaryRedirect.ts +0 -31
  133. package/src/response/transfer.ts +0 -14
  134. package/src/response/unauthorized.ts +0 -21
  135. package/src/response/unprocessable.ts +0 -18
  136. package/src/response.ts +0 -37
  137. package/src/run/prepareTemplate.ts +0 -39
  138. package/src/run/run.ts +0 -62
  139. package/src/run/runBuild.ts +0 -77
  140. package/src/run/runDev.ts +0 -89
  141. package/src/run/runPreview.ts +0 -93
  142. package/src/run.ts +0 -6
  143. package/src/server/CookieHelper.ts +0 -36
  144. package/src/server/HeaderHelper.ts +0 -25
  145. package/src/server/Routerx.ts +0 -72
  146. package/src/server/Server.ts +0 -144
  147. package/src/server/ServerEvent.ts +0 -39
  148. package/src/server/connect/bufferToArrayBuffer.ts +0 -8
  149. package/src/server/connect/connectMiddleware.ts +0 -81
  150. package/src/server/connect/flattenHeaders.ts +0 -44
  151. package/src/server/connect/nodeMessageToNodeResponse.ts +0 -89
  152. package/src/server/connect/readableToBuffer.ts +0 -16
  153. package/src/server/connect/requestToNodeMessage.ts +0 -26
  154. package/src/server/contentType.ts +0 -85
  155. package/src/server/types/HttpMethod.ts +0 -12
  156. package/src/server/types/MiddlewareFunction.ts +0 -8
  157. package/src/server/types/ServerFunction.ts +0 -7
  158. package/src/server.ts +0 -7
  159. package/src/site/Router.ts +0 -164
  160. package/src/site/Site.ts +0 -214
  161. package/src/site/clientEntry.ts +0 -108
  162. package/src/site/clientEntryDev.ts +0 -3
  163. package/src/site/defaultAdapter.ts +0 -11
  164. package/src/site/manifest.ts +0 -54
  165. package/src/site/serverEntry.ts +0 -439
  166. package/src/state/$page.ts +0 -24
  167. package/src/state/client.ts +0 -15
  168. package/src/state.ts +0 -3
  169. package/src/test/runTest.ts +0 -350
  170. package/src/test.ts +0 -3
  171. package/src/types/Adapter.ts +0 -8
  172. package/src/types/ClientState.ts +0 -8
  173. package/src/types/InternalState.ts +0 -7
  174. package/src/types/LayoutHandler.ts +0 -5
  175. package/src/types/LayoutPath.ts +0 -10
  176. package/src/types/ManifestRoute.ts +0 -6
  177. package/src/types/PageEndPoint.ts +0 -39
  178. package/src/types/PageLoadEvent.ts +0 -14
  179. package/src/types/PageServerAction.ts +0 -7
  180. package/src/types/PageServerEndPoint.ts +0 -16
  181. package/src/types/PageServerLoad.ts +0 -7
  182. package/src/types/PageState.ts +0 -8
  183. package/src/types/Route.ts +0 -11
  184. package/src/types/RouteHandler.ts +0 -16
  185. package/src/types/RouteLayoutHandler.ts +0 -5
  186. package/src/types/RouteMatchResult.ts +0 -7
  187. package/src/types/RouteType.ts +0 -19
  188. package/src/types/ServerEndPoint.ts +0 -37
  189. package/src/types/ServerHook.ts +0 -11
  190. package/src/types/ServerLoadEvent.ts +0 -37
  191. package/src/types/ServerRequest.ts +0 -7
  192. package/src/utils/pathToRegex.ts +0 -15
  193. package/src/vite-env.d.ts +0 -2
@@ -0,0 +1,22 @@
1
+ //#region src/response/TypedResponse.d.ts
2
+ /**
3
+ * A Response whose JSON body type is known, as returned by e.g. `ok({ ... })`.
4
+ * Purely a compile-time marker: `__body` is never set at runtime.
5
+ */
6
+ interface TypedResponse<T> extends Response {
7
+ /**
8
+ * Phantom property carrying the body type. Required so that plain
9
+ * `Response` values do not match `TypedResponse<infer T>`.
10
+ */
11
+ readonly __body: T;
12
+ }
13
+ /**
14
+ * A Response that does not carry typed JSON data (e.g. redirects, errors,
15
+ * plain-text bodies). Loads may return these alongside typed data responses.
16
+ */
17
+ type UntypedResponse = Response & {
18
+ readonly __body?: never;
19
+ };
20
+ //#endregion
21
+ export { UntypedResponse as n, TypedResponse as t };
22
+ //# sourceMappingURL=TypedResponse-6lznqN4U.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypedResponse-6lznqN4U.d.mts","names":[],"sources":["../src/response/TypedResponse.ts"],"mappings":";;;;;UAIyB,cAAc,WAAW;;;;;WAKxC,QAAQ;;;;;;KAON,kBAAkB;WAAsB"}
@@ -1,5 +1,4 @@
1
1
  import { $watch } from "@torpor/view";
2
-
3
2
  //#region src/state/$page.ts
4
3
  const PAGE_SYMBOL = Symbol.for("torp.Page");
5
4
  const $page = globalThis[PAGE_SYMBOL] ??= $watch({
@@ -8,8 +7,7 @@ const $page = globalThis[PAGE_SYMBOL] ??= $watch({
8
7
  form: void 0,
9
8
  error: { message: "" }
10
9
  }, { shallow: true });
11
- var $page_default = $page;
12
-
13
10
  //#endregion
14
- export { $page_default as t };
15
- //# sourceMappingURL=_page-BQs4WvCO.mjs.map
11
+ export { $page as t };
12
+
13
+ //# sourceMappingURL=_page-4cQKs_Dn.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_page-4cQKs_Dn.mjs","names":[],"sources":["../src/state/$page.ts"],"sourcesContent":["import { $watch } from \"@torpor/view\";\nimport type PageState from \"../types/PageState\";\n\nconst PAGE_SYMBOL: unique symbol = Symbol.for(\"torp.Page\");\n\nconst $page: PageState =\n\t// @ts-ignore\n\t(globalThis[PAGE_SYMBOL] ??=\n\t\t// The server request path uses `state/$serverPage`, which stores the\n\t\t// same state (same `globalThis` slot) without a reactive proxy\n\t\t$watch(\n\t\t\t{\n\t\t\t\tstatus: 404,\n\t\t\t\turl: new URL(\"http://localhost\"),\n\t\t\t\tform: undefined,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tshallow: true,\n\t\t\t},\n\t\t));\n\nexport default $page;\n"],"mappings":";;AAGA,MAAM,cAA6B,OAAO,IAAI,WAAW;AAEzD,MAAM,QAEJ,WAAW,iBAGX,OACC;CACC,QAAQ;CACR,KAAK,IAAI,IAAI,kBAAkB;CAC/B,MAAM,KAAA;CACN,OAAO,EACN,SAAS,GACV;AACD,GACA,EACC,SAAS,KACV,CACD"}
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/bin/index.js CHANGED
@@ -1,17 +1,21 @@
1
1
  #! /usr/bin/env node
2
- import "../pathToRegex-TUAMc3zS.mjs";
3
- import "../seeOther-B4Yhu9iq.mjs";
4
- import "../response-C5TtAsh1.mjs";
5
- import { t as run } from "../run-BPFtSYA3.mjs";
6
- import "../ServerEvent-CxPwA4lH.mjs";
7
- import "../connectMiddleware-D7nehjj3.mjs";
8
-
2
+ import { n as loadSite, r as run, t as runOpenApi } from "../runOpenApi-DlOgF2Il.mjs";
9
3
  //#region src/bin/index.ts
10
4
  const workingDir = process.cwd();
11
- if (process.argv.includes("--dev")) await run(workingDir, "dev");
5
+ if (process.argv.includes("--openapi")) {
6
+ const flagIndex = process.argv.indexOf("--openapi");
7
+ const next = process.argv[flagIndex + 1];
8
+ const outFile = next && !next.startsWith("-") ? next : "openapi.json";
9
+ const { site, vite } = await loadSite(workingDir);
10
+ try {
11
+ await runOpenApi(site, vite, outFile);
12
+ } finally {
13
+ await vite.close();
14
+ }
15
+ } else if (process.argv.includes("--dev")) await run(workingDir, "dev");
12
16
  else if (process.argv.includes("--build")) await run(workingDir, "build");
13
17
  else if (process.argv.includes("--preview")) await run(workingDir, "preview");
14
-
15
18
  //#endregion
16
- export { };
17
- //# sourceMappingURL=index.mjs.map
19
+ export {};
20
+
21
+ //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/bin/index.ts"],"sourcesContent":["#! /usr/bin/env node\nimport run from \"../run/run\";\n\nconst workingDir = process.cwd();\nif (process.argv.includes(\"--dev\")) {\n\tawait run(workingDir, \"dev\");\n} else if (process.argv.includes(\"--build\")) {\n\tawait run(workingDir, \"build\");\n} else if (process.argv.includes(\"--preview\")) {\n\tawait run(workingDir, \"preview\");\n}\n"],"mappings":";;;;;;;;;AAGA,MAAM,aAAa,QAAQ,KAAK;AAChC,IAAI,QAAQ,KAAK,SAAS,QAAQ,CACjC,OAAM,IAAI,YAAY,MAAM;SAClB,QAAQ,KAAK,SAAS,UAAU,CAC1C,OAAM,IAAI,YAAY,QAAQ;SACpB,QAAQ,KAAK,SAAS,YAAY,CAC5C,OAAM,IAAI,YAAY,UAAU"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/bin/index.ts"],"sourcesContent":["#! /usr/bin/env node\nimport run, { loadSite } from \"../run/run\";\nimport runOpenApi from \"../run/runOpenApi\";\n\nconst workingDir = process.cwd();\nif (process.argv.includes(\"--openapi\")) {\n\tconst flagIndex = process.argv.indexOf(\"--openapi\");\n\tconst next = process.argv[flagIndex + 1];\n\tconst outFile = next && !next.startsWith(\"-\") ? next : \"openapi.json\";\n\tconst { site, vite } = await loadSite(workingDir);\n\ttry {\n\t\tawait runOpenApi(site, vite, outFile);\n\t} finally {\n\t\tawait vite.close();\n\t}\n} else if (process.argv.includes(\"--dev\")) {\n\tawait run(workingDir, \"dev\");\n} else if (process.argv.includes(\"--build\")) {\n\tawait run(workingDir, \"build\");\n} else if (process.argv.includes(\"--preview\")) {\n\tawait run(workingDir, \"preview\");\n}\n"],"mappings":";;;AAIA,MAAM,aAAa,QAAQ,IAAI;AAC/B,IAAI,QAAQ,KAAK,SAAS,WAAW,GAAG;CACvC,MAAM,YAAY,QAAQ,KAAK,QAAQ,WAAW;CAClD,MAAM,OAAO,QAAQ,KAAK,YAAY;CACtC,MAAM,UAAU,QAAQ,CAAC,KAAK,WAAW,GAAG,IAAI,OAAO;CACvD,MAAM,EAAE,MAAM,SAAS,MAAM,SAAS,UAAU;CAChD,IAAI;EACH,MAAM,WAAW,MAAM,MAAM,OAAO;CACrC,UAAU;EACT,MAAM,KAAK,MAAM;CAClB;AACD,OAAO,IAAI,QAAQ,KAAK,SAAS,OAAO,GACvC,MAAM,IAAI,YAAY,KAAK;KACrB,IAAI,QAAQ,KAAK,SAAS,SAAS,GACzC,MAAM,IAAI,YAAY,OAAO;KACvB,IAAI,QAAQ,KAAK,SAAS,WAAW,GAC3C,MAAM,IAAI,YAAY,SAAS"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,59 @@
1
+ import { t as Router } from "./Router-C4ADUfhv.mjs";
2
+ import { t as $page } from "./_page-4cQKs_Dn.mjs";
3
+ import { a as client, n as navigate, r as loadData } from "./load-BntzvmwL.mjs";
4
+ import manifest from "@torpor/build/manifest";
5
+ //#region src/site/clientEntry.ts
6
+ const router = new Router();
7
+ router.addPages(manifest.routes);
8
+ client.router = router;
9
+ window.addEventListener("click", async (e) => {
10
+ const link = getLink(e.target);
11
+ if (link) {
12
+ if (ignoreAnchorMouseEvent(link, e)) return;
13
+ e.preventDefault();
14
+ const href = link.href;
15
+ const url = new URL(href);
16
+ if (await navigate(url)) window.history.pushState(null, "", url);
17
+ else window.location.href = href;
18
+ }
19
+ });
20
+ window.addEventListener("mouseover", maybePrefetch);
21
+ window.addEventListener("touchstart", maybePrefetch);
22
+ async function maybePrefetch(e) {
23
+ const link = getLink(e.target);
24
+ if (link) {
25
+ if (ignoreAnchorMouseEvent(link, e)) return;
26
+ const href = link.href;
27
+ const url = new URL(href);
28
+ const path = url.pathname;
29
+ const query = url.searchParams;
30
+ const route = router.match(path, query);
31
+ if (route) {
32
+ const handler = route.handler;
33
+ const params = route.params || {};
34
+ const clientEndPoint = (await handler.endPoint()).default;
35
+ await loadData(handler, params, path, query, [], clientEndPoint, handler.serverEndPoint && (await handler.serverEndPoint())?.default, true);
36
+ }
37
+ }
38
+ }
39
+ function getLink(target) {
40
+ if (!(target instanceof Element)) return;
41
+ const link = target.closest("a[href]");
42
+ if (link?.getAttribute("href")?.startsWith("#")) return;
43
+ return link ?? void 0;
44
+ }
45
+ function ignoreAnchorMouseEvent(link, e) {
46
+ return e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || link.target && link.target !== "_self" || link.rel === "external" || link.hasAttribute("download") || link.isContentEditable;
47
+ }
48
+ window.addEventListener("popstate", async () => {
49
+ await navigateToLocation(document.location);
50
+ });
51
+ await navigateToLocation(document.location, true);
52
+ async function navigateToLocation(location, firstTime = false) {
53
+ $page.form = void 0;
54
+ return await navigate(new URL(location.toString()), firstTime);
55
+ }
56
+ //#endregion
57
+ export {};
58
+
59
+ //# sourceMappingURL=clientEntry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientEntry.mjs","names":[],"sources":["../src/site/clientEntry.ts"],"sourcesContent":["import manifest from \"@torpor/build/manifest\";\nimport loadData from \"../nav/loadData.ts\";\nimport navigate from \"../nav/navigate.ts\";\nimport $page from \"../state/$page.ts\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport Router from \"./Router.ts\";\n\n// Build the router from the Site object created by the user\nconst router = new Router();\nrouter.addPages(manifest.routes);\n\n// Put it in client state so that it can be accessed from navigate. We can't\n// just import it to navigate because that gets built by tsdown, without Vite\n// and the manifest\nclient.router = router;\n\n// Intercept clicks on links\nwindow.addEventListener(\"click\", async (e) => {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\te.preventDefault();\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tif (await navigate(url)) {\n\t\t\twindow.history.pushState(null, \"\", url);\n\t\t} else {\n\t\t\twindow.location.href = href;\n\t\t}\n\t}\n});\n\n// Intercept mouseover/focus/touchstart on links for prefetching\n// TODO: settings e.g. <a href=\"\" data-tb-prefetch=\"eager\"/\"visible\"/\"hover\"/\"touch\">\nwindow.addEventListener(\"mouseover\", maybePrefetch);\nwindow.addEventListener(\"touchstart\", maybePrefetch);\n\nasync function maybePrefetch(e: MouseEvent | TouchEvent) {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tconst path = url.pathname;\n\t\tconst query = url.searchParams;\n\t\tconst route = router.match(path, query);\n\t\tif (route) {\n\t\t\tconst handler = route.handler;\n\t\t\tconst params = route.params || {};\n\n\t\t\t// There should be a client endpoint\n\t\t\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\n\t\t\t// There may be a server endpoint\n\t\t\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\t\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t\t\t// Just dummy this up\n\t\t\tlet newStack: LayoutPath[] = [];\n\n\t\t\tawait loadData(handler, params, path, query, newStack, clientEndPoint, serverEndPoint, true);\n\t\t}\n\t}\n}\n\nfunction getLink(target: EventTarget | null): HTMLAnchorElement | undefined {\n\t// The target may be a child element of the link (e.g. an icon inside it),\n\t// so walk up to the nearest actual link. Fragment-only links (href=\"#...\")\n\t// are left to the browser's native jump-to-anchor behavior\n\tif (!(target instanceof Element)) return;\n\tconst link = target.closest(\"a[href]\");\n\tif (link?.getAttribute(\"href\")?.startsWith(\"#\")) return;\n\treturn (link as HTMLAnchorElement) ?? undefined;\n}\n\nfunction ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {\n\t// Ignore the event if it was already handled (e.g. a user handler calling\n\t// preventDefault), a modifier key is pressed, the target is not \"_self\"\n\t// (e.g. \"_blank\"), rel=\"external\", it's a download link, or it's in a\n\t// contenteditable region\n\treturn (\n\t\te.defaultPrevented ||\n\t\te.altKey ||\n\t\te.ctrlKey ||\n\t\te.metaKey ||\n\t\te.shiftKey ||\n\t\t(link.target && link.target !== \"_self\") ||\n\t\tlink.rel === \"external\" ||\n\t\tlink.hasAttribute(\"download\") ||\n\t\tlink.isContentEditable\n\t);\n}\n\n// Listen for changes to the URL that occur when the user navigates using the\n// back or forward buttons\nwindow.addEventListener(\"popstate\", async () => {\n\tawait navigateToLocation(document.location);\n});\n\n// Do the initial navigation and hydration\nawait navigateToLocation(document.location, true);\n\nasync function navigateToLocation(location: Location, firstTime = false) {\n\t// Reset $page on submit and link navigation\n\t// TODO: Should we put data in $page.data??\n\t$page.form = undefined;\n\n\treturn await navigate(new URL(location.toString()), firstTime);\n}\n"],"mappings":";;;;;AAWA,MAAM,SAAS,IAAI,OAAO;AAC1B,OAAO,SAAS,SAAS,MAAM;AAK/B,OAAO,SAAS;AAGhB,OAAO,iBAAiB,SAAS,OAAO,MAAM;CAC7C,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,EAAE,eAAe;EAEjB,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,IAAI,MAAM,SAAS,GAAG,GACrB,OAAO,QAAQ,UAAU,MAAM,IAAI,GAAG;OAEtC,OAAO,SAAS,OAAO;CAEzB;AACD,CAAC;AAID,OAAO,iBAAiB,aAAa,aAAa;AAClD,OAAO,iBAAiB,cAAc,aAAa;AAEnD,eAAe,cAAc,GAA4B;CACxD,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,IAAI;EAClB,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK;EACtC,IAAI,OAAO;GACV,MAAM,UAAU,MAAM;GACtB,MAAM,SAAS,MAAM,UAAU,CAAC;GAGhC,MAAM,kBAA4C,MAAM,QAAQ,SAAS,EAAA,CAAG;GAS5E,MAAM,SAAS,SAAS,QAAQ,MAAM,OAAO,CAAO,GAAG,gBALtD,QAAQ,mBAAmB,MAAM,QAAQ,eAAe,EAAA,EAAI,SAK0B,IAAI;EAC5F;CACD;AACD;AAEA,SAAS,QAAQ,QAA2D;CAI3E,IAAI,EAAE,kBAAkB,UAAU;CAClC,MAAM,OAAO,OAAO,QAAQ,SAAS;CACrC,IAAI,MAAM,aAAa,MAAM,CAAC,EAAE,WAAW,GAAG,GAAG;CACjD,OAAQ,QAA8B,KAAA;AACvC;AAEA,SAAS,uBAAuB,MAAyB,GAA4B;CAKpF,OACC,EAAE,oBACF,EAAE,UACF,EAAE,WACF,EAAE,WACF,EAAE,YACD,KAAK,UAAU,KAAK,WAAW,WAChC,KAAK,QAAQ,cACb,KAAK,aAAa,UAAU,KAC5B,KAAK;AAEP;AAIA,OAAO,iBAAiB,YAAY,YAAY;CAC/C,MAAM,mBAAmB,SAAS,QAAQ;AAC3C,CAAC;AAGD,MAAM,mBAAmB,SAAS,UAAU,IAAI;AAEhD,eAAe,mBAAmB,UAAoB,YAAY,OAAO;CAGxE,MAAM,OAAO,KAAA;CAEb,OAAO,MAAM,SAAS,IAAI,IAAI,SAAS,SAAS,CAAC,GAAG,SAAS;AAC9D"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,7 @@
1
+ import { setDevMode } from "@torpor/view/dev";
2
+ //#region src/site/clientEntryDev.ts
3
+ setDevMode();
4
+ //#endregion
5
+ export {};
6
+
7
+ //# sourceMappingURL=clientEntryDev.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientEntryDev.mjs","names":[],"sources":["../src/site/clientEntryDev.ts"],"sourcesContent":["import { setDevMode } from \"@torpor/view/dev\";\n\nsetDevMode();\n"],"mappings":";;AAEA,WAAW"}
package/dist/dev.d.mts ADDED
@@ -0,0 +1,3 @@
1
+ import { t as Server } from "./Server-Cler2w5O.mjs";
2
+ import { t as load } from "./serverEntry-BAO8m03V.mjs";
3
+ export { Server, load };
package/dist/dev.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { t as Server } from "./Server-BnjLJleZ.mjs";
2
+ import { t as load } from "./serverEntry-1qY3wZig.mjs";
3
+ export { Server, load };
@@ -0,0 +1,108 @@
1
+ import { t as unprocessable } from "./unprocessable-DAZbrDeR.mjs";
2
+ import { n as ValidationError, t as validate } from "./validate-Ok4krlkX.mjs";
3
+ import { $watch } from "@torpor/view/ssr";
4
+ //#region src/form/formDataToRecord.ts
5
+ /**
6
+ * Converts the submitted fields of a form request into a plain record: one
7
+ * value per field, or an array when a field was submitted with multiple
8
+ * values (e.g. a multi select). File inputs pass through as `File` values;
9
+ * coercing them is left to the schema or the action.
10
+ *
11
+ * @param request The request (or FormData directly)
12
+ * @returns The field values
13
+ */
14
+ async function formDataToRecord(request) {
15
+ const formData = request instanceof FormData ? request : await request.formData();
16
+ const result = {};
17
+ for (const key of formData.keys()) {
18
+ if (key in result) continue;
19
+ const values = formData.getAll(key);
20
+ result[key] = values.length > 1 ? values : values[0];
21
+ }
22
+ return result;
23
+ }
24
+ //#endregion
25
+ //#region src/state/$serverPage.ts
26
+ const PAGE_SYMBOL = Symbol.for("torp.Page");
27
+ const $page = globalThis[PAGE_SYMBOL] ??= $watch({
28
+ status: 404,
29
+ url: new URL("http://localhost"),
30
+ form: void 0,
31
+ error: { message: "" }
32
+ });
33
+ //#endregion
34
+ //#region src/utils/searchParamsToRecord.ts
35
+ /**
36
+ * Converts a URL's query string into a plain record: one string per param,
37
+ * or an array when a param was repeated.
38
+ *
39
+ * @param searchParams The URL's search params
40
+ * @returns The param values
41
+ */
42
+ function searchParamsToRecord(searchParams) {
43
+ const result = {};
44
+ for (const key of searchParams.keys()) {
45
+ if (key in result) continue;
46
+ const values = searchParams.getAll(key);
47
+ result[key] = values.length > 1 ? values : values[0];
48
+ }
49
+ return result;
50
+ }
51
+ //#endregion
52
+ //#region src/validation/endpoint.ts
53
+ /**
54
+ * Gets the standard schema declared for a handler or action, if any. The
55
+ * schemas map is keyed by name; the runtime treats it as a loose record.
56
+ */
57
+ function endpointSchema(endPoint, name) {
58
+ return endPoint.schema?.[name];
59
+ }
60
+ /**
61
+ * Validates the route params against the endpoint's `params` schema, if it
62
+ * declares one, returning the coerced params. Throws a ValidationError when
63
+ * they don't match, which callers turn into a 404 (params come from the URL,
64
+ * so a failed validation means the resource doesn't exist).
65
+ *
66
+ * @param endPoint The endpoint declaring the (optional) `params` schema
67
+ * @param params The route params from the matched route
68
+ * @returns The validated params
69
+ */
70
+ async function validateEndpointParams(endPoint, params) {
71
+ const schema = endpointSchema(endPoint, "params");
72
+ return schema ? await validate(schema, params) : params;
73
+ }
74
+ /**
75
+ * Validates a handler's input against its schema: get/head handlers and load
76
+ * functions validate the URL's query string, other handlers validate the JSON
77
+ * request body. Returns the validated values (an empty object when no schema
78
+ * is declared), which callers pass through to the handler's event.
79
+ *
80
+ * @param schema The handler's (optional) schema
81
+ * @param functionName The handler name (`get`, `post`, `load`, etc)
82
+ * @param url The request URL, for the query string
83
+ * @param ev The server event, for the request body
84
+ * @returns The validated values
85
+ */
86
+ async function validateHandlerInput(schema, functionName, url, ev) {
87
+ if (!schema) return {};
88
+ if (functionName === "get" || functionName === "head" || functionName === "load") return { query: await validate(schema, searchParamsToRecord(url.searchParams)) };
89
+ return { json: await validate(schema, await ev.json()) };
90
+ }
91
+ /**
92
+ * Turns a ValidationError thrown during endpoint validation into a 422
93
+ * response with the schema's issues. Returns undefined for any other error,
94
+ * which callers should rethrow.
95
+ *
96
+ * @param error The error thrown by a handler or validation
97
+ * @returns A 422 response, or undefined when the error isn't a validation failure
98
+ */
99
+ function validationErrorResponse(error) {
100
+ if (error instanceof ValidationError) return unprocessable({
101
+ message: error.message,
102
+ issues: error.issues
103
+ });
104
+ }
105
+ //#endregion
106
+ export { searchParamsToRecord as a, validationErrorResponse as i, validateEndpointParams as n, $page as o, validateHandlerInput as r, formDataToRecord as s, endpointSchema as t };
107
+
108
+ //# sourceMappingURL=endpoint-EULRGCKA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-EULRGCKA.mjs","names":[],"sources":["../src/form/formDataToRecord.ts","../src/state/$serverPage.ts","../src/utils/searchParamsToRecord.ts","../src/validation/endpoint.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"],"mappings":";;;;;;;;;;;;;AAWA,eAA8B,iBAC7B,SAC0B;CAC1B,MAAM,WAAW,mBAAmB,WAAW,UAAU,MAAM,QAAQ,SAAS;CAChF,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,OAAO,SAAS,KAAK,GAAG;EAClC,IAAI,OAAO,QAAQ;EACnB,MAAM,SAAS,SAAS,OAAO,GAAG;EAClC,OAAO,OAAO,OAAO,SAAS,IAAI,SAAS,OAAO;CACnD;CACA,OAAO;AACR;;;ACnBA,MAAM,cAA6B,OAAO,IAAI,WAAW;AAOzD,MAAM,QAEJ,WAAW,iBAAiB,OAAO;CACnC,QAAQ;CACR,KAAK,IAAI,IAAI,kBAAkB;CAC/B,MAAM,KAAA;CACN,OAAO,EACN,SAAS,GACV;AACD,CAAC;;;;;;;;;;ACVF,SAAwB,qBAAqB,cAA4C;CACxF,MAAM,SAAsB,CAAC;CAC7B,KAAK,MAAM,OAAO,aAAa,KAAK,GAAG;EACtC,IAAI,OAAO,QAAQ;EACnB,MAAM,SAAS,aAAa,OAAO,GAAG;EACtC,OAAO,OAAO,OAAO,SAAS,IAAI,SAAS,OAAO;CACnD;CACA,OAAO;AACR;;;;;;;ACKA,SAAgB,eAAe,UAAoB,MAA4C;CAC9F,OAAQ,SAAS,SAAiC;AACnD;;;;;;;;;;;AAYA,eAAsB,uBACrB,UACA,QAC+B;CAC/B,MAAM,SAAS,eAAe,UAAU,QAAQ;CAChD,OAAO,SAAW,MAAM,SAAS,QAAQ,MAAM,IAA6B;AAC7E;;;;;;;;;;;;;AAcA,eAAsB,qBACrB,QACA,cACA,KACA,IAC+D;CAC/D,IAAI,CAAC,QAAQ,OAAO,CAAC;CACrB,IAAI,iBAAiB,SAAS,iBAAiB,UAAU,iBAAiB,QACzE,OAAO,EAAE,OAAO,MAAM,SAAS,QAAQ,qBAAqB,IAAI,YAAY,CAAC,EAAE;CAEhF,OAAO,EAAE,MAAM,MAAM,SAAS,QAAQ,MAAM,GAAG,KAAK,CAAC,EAAE;AACxD;;;;;;;;;AAUA,SAAgB,wBAAwB,OAAsC;CAC7E,IAAI,iBAAiB,iBACpB,OAAO,cAAc;EAAE,SAAS,MAAM;EAAS,QAAQ,MAAM;CAAO,CAAC;AAGvE"}
@@ -0,0 +1,13 @@
1
+ //#region src/server/connect/flattenHeaders.ts
2
+ function flattenHeaders(headers) {
3
+ const flatHeaders = new Headers();
4
+ for (const [key, value] of Object.entries(headers)) if (value === void 0 || value === null) continue;
5
+ else if (Array.isArray(value)) {
6
+ for (const v of value) if (v !== void 0 && v !== null) flatHeaders.append(key, String(v));
7
+ } else flatHeaders.set(key, String(value));
8
+ return flatHeaders;
9
+ }
10
+ //#endregion
11
+ export { flattenHeaders as t };
12
+
13
+ //# sourceMappingURL=flattenHeaders-DM5qsSqO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenHeaders-DM5qsSqO.mjs","names":[],"sources":["../src/server/connect/flattenHeaders.ts"],"sourcesContent":["import { IncomingHttpHeaders } from \"node:http\";\nimport { OutgoingHttpHeaders } from \"node:http2\";\n\nexport default function flattenHeaders(\n\theaders: IncomingHttpHeaders | OutgoingHttpHeaders,\n): HeadersInit {\n\tconst flatHeaders = new Headers();\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value === undefined || value === null) {\n\t\t\tcontinue;\n\t\t} else if (Array.isArray(value)) {\n\t\t\tfor (const v of value) {\n\t\t\t\tif (v !== undefined && v !== null) {\n\t\t\t\t\tflatHeaders.append(key, String(v));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tflatHeaders.set(key, String(value));\n\t\t}\n\t}\n\treturn flatHeaders;\n}\n"],"mappings":";AAGA,SAAwB,eACvB,SACc;CACd,MAAM,cAAc,IAAI,QAAQ;CAChC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAChD,IAAI,UAAU,KAAA,KAAa,UAAU,MACpC;MACM,IAAI,MAAM,QAAQ,KAAK,GACxB;OAAA,MAAM,KAAK,OACf,IAAI,MAAM,KAAA,KAAa,MAAM,MAC5B,YAAY,OAAO,KAAK,OAAO,CAAC,CAAC;CAAA,OAInC,YAAY,IAAI,KAAK,OAAO,KAAK,CAAC;CAGpC,OAAO;AACR"}
@@ -0,0 +1,35 @@
1
+ //#region src/form/readForm.d.ts
2
+ /**
3
+ * The values read from a form submission, coerced by the spec's default
4
+ * value types: string defaults produce strings, number defaults produce
5
+ * numbers, boolean defaults produce booleans, and array defaults produce
6
+ * arrays of the element type (an empty array default produces strings).
7
+ */
8
+ type FormValues<Spec extends Record<string, unknown>> = { [K in keyof Spec]: FormValue<Spec[K]>; };
9
+ type FormValue<T> = T extends boolean ? boolean : T extends number ? number : T extends readonly unknown[] ? [T[number]] extends [never] ? string[] : T[number][] : string;
10
+ /**
11
+ * Reads and coerces the submitted fields of a form action, e.g.
12
+ *
13
+ * ```ts
14
+ * const data = await readForm(request, {
15
+ * title: "",
16
+ * done: false,
17
+ * priority: 0,
18
+ * tags: [],
19
+ * });
20
+ * // data: { title: string; done: boolean; priority: number; tags: string[] }
21
+ * ```
22
+ *
23
+ * The spec object declares the expected fields; each key's default value
24
+ * determines the type and the fallback when the field is missing or can't be
25
+ * coerced. Booleans follow checkbox semantics (unchecked fields are absent,
26
+ * so they come back as `false`).
27
+ *
28
+ * @param request The action event's request, or its FormData directly
29
+ * @param spec An object mapping field names to default values
30
+ * @returns The coerced field values
31
+ */
32
+ declare function readForm<Spec extends Record<string, unknown>>(request: Request | FormData, spec: Spec): Promise<FormValues<Spec>>;
33
+ //#endregion
34
+ export { type FormValues, readForm };
35
+ //# sourceMappingURL=form.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.mts","names":[],"sources":["../src/form/readForm.ts"],"mappings":";;;;;;;KAMY,WAAW,aAAa,8BAClC,WAAW,OAAO,UAAU,KAAK;KAG9B,UAAU,KAAK,8BAEjB,4BAEC,gCACE,wCAEA;;;;;;;;;;;;;;;;;;;;;;;iBAyBwB,SAAS,aAAa,yBACnD,SAAS,UAAU,UACnB,MAAM,OACJ,QAAQ,WAAW"}
package/dist/form.mjs ADDED
@@ -0,0 +1,48 @@
1
+ //#region src/form/readForm.ts
2
+ /**
3
+ * Reads and coerces the submitted fields of a form action, e.g.
4
+ *
5
+ * ```ts
6
+ * const data = await readForm(request, {
7
+ * title: "",
8
+ * done: false,
9
+ * priority: 0,
10
+ * tags: [],
11
+ * });
12
+ * // data: { title: string; done: boolean; priority: number; tags: string[] }
13
+ * ```
14
+ *
15
+ * The spec object declares the expected fields; each key's default value
16
+ * determines the type and the fallback when the field is missing or can't be
17
+ * coerced. Booleans follow checkbox semantics (unchecked fields are absent,
18
+ * so they come back as `false`).
19
+ *
20
+ * @param request The action event's request, or its FormData directly
21
+ * @param spec An object mapping field names to default values
22
+ * @returns The coerced field values
23
+ */
24
+ async function readForm(request, spec) {
25
+ const formData = request instanceof FormData ? request : await request.formData();
26
+ const result = {};
27
+ for (const key of Object.keys(spec)) {
28
+ const defaultValue = spec[key];
29
+ const values = formData.getAll(key).map((v) => v.toString());
30
+ result[key] = Array.isArray(defaultValue) ? values.map((value) => coerceValue(value, defaultValue[0])) : coerceValue(values[0], defaultValue);
31
+ }
32
+ return result;
33
+ }
34
+ function coerceValue(value, defaultValue) {
35
+ if (typeof defaultValue === "number") {
36
+ const n = value === void 0 || value === "" ? NaN : Number(value);
37
+ return Number.isNaN(n) ? defaultValue : n;
38
+ }
39
+ if (typeof defaultValue === "boolean") {
40
+ if (value === void 0) return false;
41
+ return !(value === "" || value === "false" || value === "off");
42
+ }
43
+ return value ?? defaultValue ?? "";
44
+ }
45
+ //#endregion
46
+ export { readForm };
47
+
48
+ //# sourceMappingURL=form.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.mjs","names":[],"sources":["../src/form/readForm.ts"],"sourcesContent":["/**\n * The values read from a form submission, coerced by the spec's default\n * value types: string defaults produce strings, number defaults produce\n * numbers, boolean defaults produce booleans, and array defaults produce\n * arrays of the element type (an empty array default produces strings).\n */\nexport type FormValues<Spec extends Record<string, unknown>> = {\n\t[K in keyof Spec]: FormValue<Spec[K]>;\n};\n\ntype FormValue<T> = T extends boolean\n\t? boolean\n\t: T extends number\n\t\t? number\n\t\t: T extends readonly unknown[]\n\t\t\t? [T[number]] extends [never]\n\t\t\t\t? string[]\n\t\t\t\t: T[number][]\n\t\t\t: string;\n\n/**\n * Reads and coerces the submitted fields of a form action, e.g.\n *\n * ```ts\n * const data = await readForm(request, {\n * title: \"\",\n * done: false,\n * priority: 0,\n * tags: [],\n * });\n * // data: { title: string; done: boolean; priority: number; tags: string[] }\n * ```\n *\n * The spec object declares the expected fields; each key's default value\n * determines the type and the fallback when the field is missing or can't be\n * coerced. Booleans follow checkbox semantics (unchecked fields are absent,\n * so they come back as `false`).\n *\n * @param request The action event's request, or its FormData directly\n * @param spec An object mapping field names to default values\n * @returns The coerced field values\n */\nexport default async function readForm<Spec extends Record<string, unknown>>(\n\trequest: Request | FormData,\n\tspec: Spec,\n): Promise<FormValues<Spec>> {\n\tconst formData = request instanceof FormData ? request : await request.formData();\n\tconst result: Record<string, unknown> = {};\n\tfor (const key of Object.keys(spec)) {\n\t\tconst defaultValue = spec[key];\n\t\t// oxlint-disable-next-line typescript/no-base-to-string\n\t\tconst values = formData.getAll(key).map((v) => v.toString());\n\t\tresult[key] = Array.isArray(defaultValue)\n\t\t\t? values.map((value) => coerceValue(value, defaultValue[0]))\n\t\t\t: coerceValue(values[0], defaultValue);\n\t}\n\treturn result as FormValues<Spec>;\n}\n\nfunction coerceValue(value: string | undefined, defaultValue: unknown): unknown {\n\tif (typeof defaultValue === \"number\") {\n\t\tconst n = value === undefined || value === \"\" ? NaN : Number(value);\n\t\treturn Number.isNaN(n) ? defaultValue : n;\n\t}\n\tif (typeof defaultValue === \"boolean\") {\n\t\t// Unchecked checkboxes are not submitted at all\n\t\tif (value === undefined) return false;\n\t\treturn !(value === \"\" || value === \"false\" || value === \"off\");\n\t}\n\treturn value ?? defaultValue ?? \"\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0CA,eAA8B,SAC7B,SACA,MAC4B;CAC5B,MAAM,WAAW,mBAAmB,WAAW,UAAU,MAAM,QAAQ,SAAS;CAChF,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;EACpC,MAAM,eAAe,KAAK;EAE1B,MAAM,SAAS,SAAS,OAAO,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,SAAS,CAAC;EAC3D,OAAO,OAAO,MAAM,QAAQ,YAAY,IACrC,OAAO,KAAK,UAAU,YAAY,OAAO,aAAa,EAAE,CAAC,IACzD,YAAY,OAAO,IAAI,YAAY;CACvC;CACA,OAAO;AACR;AAEA,SAAS,YAAY,OAA2B,cAAgC;CAC/E,IAAI,OAAO,iBAAiB,UAAU;EACrC,MAAM,IAAI,UAAU,KAAA,KAAa,UAAU,KAAK,MAAM,OAAO,KAAK;EAClE,OAAO,OAAO,MAAM,CAAC,IAAI,eAAe;CACzC;CACA,IAAI,OAAO,iBAAiB,WAAW;EAEtC,IAAI,UAAU,KAAA,GAAW,OAAO;EAChC,OAAO,EAAE,UAAU,MAAM,UAAU,WAAW,UAAU;CACzD;CACA,OAAO,SAAS,gBAAgB;AACjC"}