@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,228 @@
1
+ import { t as $page } from "./_page-4cQKs_Dn.mjs";
2
+ import { clearLayoutSlot, fillLayoutSlot, hydrate, mount, unmount } from "@torpor/view";
3
+ //#region src/state/client.ts
4
+ const CLIENT_SYMBOL = Symbol.for("torp.Client");
5
+ const client = globalThis[CLIENT_SYMBOL] ??= {
6
+ router: null,
7
+ layoutStack: [],
8
+ prefetchedData: {}
9
+ };
10
+ //#endregion
11
+ //#region src/nav/reload.ts
12
+ /**
13
+ * Reloads data for the currently loaded page and rerenders it.
14
+ */
15
+ async function reload() {
16
+ return await navigate(new URL(document.location.href));
17
+ }
18
+ //#endregion
19
+ //#region src/nav/formSubmit.ts
20
+ async function formSubmit(e) {
21
+ e.preventDefault();
22
+ $page.form = void 0;
23
+ const form = e.target;
24
+ const button = e.submitter;
25
+ let action = form.action || document.URL;
26
+ if (button && button.formAction && button.formAction !== document.URL) action = button.formAction;
27
+ const options = {
28
+ body: new FormData(form),
29
+ method: "POST",
30
+ mode: "cors",
31
+ credentials: "same-origin",
32
+ headers: { "X-Torpor-Form-Submit": "" }
33
+ };
34
+ const response = await fetch(action, options);
35
+ if (response.status === 200 && response.headers.has("Content-Disposition") && /attachment;\s*filename="(.+?)"/.test(response.headers.get("Content-Disposition"))) {
36
+ const downloadFileName = response.headers.get("Content-Disposition")?.match(/attachment;\s*filename="(.+?)"/)[1] ?? "file";
37
+ const downloadLink = document.createElement("a");
38
+ downloadLink.href = window.URL.createObjectURL(await response.blob());
39
+ downloadLink.download = downloadFileName;
40
+ downloadLink.click();
41
+ } else if (response.status === 200 && response.headers.has("X-Torpor-Form-Redirect")) {
42
+ const redirect = response.headers.get("Location");
43
+ await load(redirect);
44
+ window.location.href = redirect;
45
+ } else if (response.status >= 200 && response.status <= 299 || response.status >= 400 && response.status <= 499) {
46
+ if (response.headers.get("Content-Type") === "application/json") $page.form = await response.json();
47
+ await reload();
48
+ }
49
+ }
50
+ //#endregion
51
+ //#region src/nav/loadData.ts
52
+ async function loadData(handler, params, path, query, newLayoutStack, clientEndPoint, serverEndPoint, prefetch = false) {
53
+ let data = {};
54
+ if (handler.layouts) {
55
+ let layoutStack = client.layoutStack;
56
+ for (let [i, layout] of handler.layouts.entries()) {
57
+ let layoutPath = layout.path;
58
+ for (let key in params) layoutPath = layoutPath.replace(`[${key}]`, params[key]);
59
+ if (layoutStack.at(i)?.path === layoutPath) {
60
+ if (!prefetch) layoutStack[i].reuse = true;
61
+ newLayoutStack[i] = layoutStack[i];
62
+ Object.assign(data, layoutStack[i].data);
63
+ } else {
64
+ const stackLayout = {
65
+ path: layoutPath,
66
+ data: {},
67
+ reuse: false,
68
+ slotRegion: null
69
+ };
70
+ const layoutEndPoint = (await layout.endPoint())?.default;
71
+ const layoutServerEndPoint = layout.serverEndPoint && (await layout.serverEndPoint())?.default;
72
+ if ((await loadClientAndServerData(stackLayout.data, document.location.origin + layoutPath, query, params, layoutEndPoint, layoutServerEndPoint))?.ok === false) return;
73
+ Object.assign(data, stackLayout.data);
74
+ newLayoutStack.push(stackLayout);
75
+ }
76
+ }
77
+ }
78
+ if ((await loadClientAndServerData(data, document.location.origin + path, query, params, clientEndPoint, serverEndPoint))?.ok === false) return;
79
+ return data;
80
+ }
81
+ async function loadClientAndServerData(data, location, query, params, clientEndPoint, serverEndPoint) {
82
+ let prefetchedData = client.prefetchedData;
83
+ if (clientEndPoint?.load) {
84
+ const clientKey = location + (query.size > 0 ? `?${query}` : "");
85
+ if (prefetchedData[clientKey]) Object.assign(data, prefetchedData[clientKey]);
86
+ else {
87
+ const clientUrl = new URL(document.location.href);
88
+ for (let [name, value] of query) clientUrl.searchParams.append(name, value);
89
+ const clientParams = buildClientParams(clientUrl, params, data);
90
+ const clientResponse = await clientEndPoint.load(clientParams);
91
+ if (clientResponse) if (clientResponse.ok) {
92
+ if (clientResponse.headers.get("Content-Type")?.includes("application/json")) {
93
+ const clientData = await clientResponse.json();
94
+ Object.assign(data, clientData);
95
+ prefetchedData[clientKey] = clientData;
96
+ }
97
+ } else return clientResponse;
98
+ }
99
+ }
100
+ if (serverEndPoint?.load) {
101
+ const serverLocation = location.replace(/\/$/, "") + "/~server";
102
+ const serverKey = serverLocation + (query.size > 0 ? `?${query}` : "");
103
+ if (prefetchedData[serverKey]) Object.assign(data, prefetchedData[serverKey]);
104
+ else {
105
+ const serverUrl = new URL(serverLocation);
106
+ for (let [name, value] of query) serverUrl.searchParams.append(name, value);
107
+ const serverResponse = await fetch(serverUrl);
108
+ if (serverResponse) if (serverResponse.ok) {
109
+ if (serverResponse.headers.get("Content-Type")?.includes("application/json")) {
110
+ const serverData = await serverResponse.json();
111
+ Object.assign(data, serverData);
112
+ prefetchedData[serverKey] = serverData;
113
+ }
114
+ } else return serverResponse;
115
+ }
116
+ }
117
+ }
118
+ function buildClientParams(url, params, data) {
119
+ return {
120
+ url,
121
+ params,
122
+ data
123
+ };
124
+ }
125
+ //#endregion
126
+ //#region src/nav/navigate.ts
127
+ async function navigate(url, withHydration = false) {
128
+ let parent = document.getElementById("app");
129
+ if (!parent) {
130
+ console.log("500");
131
+ return false;
132
+ }
133
+ const path = url.pathname;
134
+ const query = url.searchParams;
135
+ const route = client.router.match(path, query);
136
+ if (!route) {
137
+ console.log("404");
138
+ return false;
139
+ }
140
+ $page.url = url;
141
+ if (path.endsWith("/_error")) {
142
+ $page.status = parseInt(query.get("status") ?? "404");
143
+ $page.error = { message: query.get("message") ?? "" };
144
+ window.history.replaceState({}, "", url.toString().split("?")[0]);
145
+ } else $page.status = 200;
146
+ const handler = route.handler;
147
+ const params = route.params || {};
148
+ const clientEndPoint = (await handler.endPoint()).default;
149
+ if (!clientEndPoint?.component) {
150
+ console.log("404");
151
+ return false;
152
+ }
153
+ const serverEndPoint = handler.serverEndPoint && (await handler.serverEndPoint())?.default;
154
+ let newLayoutStack = [];
155
+ const data = await loadData(handler, params, path, query, newLayoutStack, clientEndPoint, serverEndPoint);
156
+ let formInput = document.getElementById("t-form-data");
157
+ let form;
158
+ if (formInput) {
159
+ form = JSON.parse(formInput.value);
160
+ $page.form = form;
161
+ formInput.remove();
162
+ }
163
+ let $props = { data };
164
+ $props.form = $page.form;
165
+ const $context = { TorporBuildContext: { onformsubmit: formSubmit } };
166
+ client.layoutStack.push({
167
+ path: route.handler.path,
168
+ data: {},
169
+ reuse: false,
170
+ slotRegion: null
171
+ });
172
+ client.layoutStack = newLayoutStack;
173
+ let layoutStack = client.layoutStack;
174
+ let component = clientEndPoint.component;
175
+ let slots = void 0;
176
+ let reused = false;
177
+ let reusedIndex = -1;
178
+ if (handler.layouts) {
179
+ let slotFunctions = [];
180
+ slotFunctions[handler.layouts.length] = function clientComponent(parent, anchor) {
181
+ let i = layoutStack.length - 1;
182
+ layoutStack[i].slotRegion = fillLayoutSlot(clientEndPoint.component, slotFunctions[i + 1], parent, anchor, $props, $context);
183
+ };
184
+ for (let i = handler.layouts.length - 1; i >= 0; i--) {
185
+ const layoutEndPoint = (await handler.layouts[i].endPoint())?.default;
186
+ if (layoutEndPoint?.component) if (layoutStack[i].reuse) {
187
+ component = slotFunctions[i + 1];
188
+ reusedIndex = i;
189
+ reused = true;
190
+ break;
191
+ } else if (i === 0) {
192
+ component = layoutEndPoint.component;
193
+ slots = { _: slotFunctions[i + 1] };
194
+ } else slotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {
195
+ layoutStack[i - 1].slotRegion = fillLayoutSlot(layoutEndPoint.component, slotFunctions[i + 1], parent, anchor, $props, $context);
196
+ };
197
+ }
198
+ }
199
+ try {
200
+ if (reused) {
201
+ const slotRegion = layoutStack[reusedIndex].slotRegion;
202
+ parent = slotRegion.startNode.parentNode;
203
+ clearLayoutSlot(slotRegion);
204
+ component(parent, null);
205
+ } else if (withHydration) hydrate(parent, component, $props, slots);
206
+ else {
207
+ unmount(parent);
208
+ mount(parent, component, $props, slots);
209
+ }
210
+ } catch (error) {
211
+ parent.innerHTML = "<span style=\"color: red\">Script syntax error</span><p>" + error + "</p>";
212
+ console.log(error);
213
+ }
214
+ client.prefetchedData = {};
215
+ return true;
216
+ }
217
+ //#endregion
218
+ //#region src/nav/load.ts
219
+ /**
220
+ * Loads and renders the page at the supplied path.
221
+ */
222
+ async function load(path) {
223
+ return await navigate(new URL(path, document.location.href));
224
+ }
225
+ //#endregion
226
+ export { client as a, reload as i, navigate as n, loadData as r, load as t };
227
+
228
+ //# sourceMappingURL=load-BntzvmwL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-BntzvmwL.mjs","names":[],"sources":["../src/state/client.ts","../src/nav/reload.ts","../src/nav/formSubmit.ts","../src/nav/loadData.ts","../src/nav/navigate.ts","../src/nav/load.ts"],"sourcesContent":["import type ClientState from \"../types/ClientState\";\n\nconst CLIENT_SYMBOL: unique symbol = Symbol.for(\"torp.Client\");\n\nconst client: ClientState =\n\t// @ts-ignore\n\t(globalThis[CLIENT_SYMBOL] ??= {\n\t\t// @ts-ignore This will definitely, 100% get set\n\t\trouter: null,\n\t\tlayoutStack: [],\n\t\t// TODO: Probably expire after 30 seconds or something?\n\t\tprefetchedData: {},\n\t});\n\nexport default client;\n","import navigate from \"./navigate\";\n\n/**\n * Reloads data for the currently loaded page and rerenders it.\n */\nexport default async function reload(): Promise<boolean> {\n\tconst url = new URL(document.location.href);\n\treturn await navigate(url);\n}\n","import $page from \"../state/$page\";\nimport load from \"./load\";\nimport reload from \"./reload\";\n\nexport default async function formSubmit(e: SubmitEvent): Promise<void> {\n\t// We'll take it from here\n\te.preventDefault();\n\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\t// Perform a POST fetch to the form's action location\n\tconst form = e.target as HTMLFormElement;\n\tconst button = e.submitter as HTMLButtonElement;\n\t// HACK: submit buttons have their formAction set to the current page if not\n\t// set, so we can't just check whether it's empty and fallback to form action\n\t//const action = button?.formAction ?? form.action;\n\tlet action = form.action || document.URL;\n\tif (button && button.formAction && button.formAction !== document.URL) {\n\t\taction = button.formAction;\n\t}\n\tconst options = {\n\t\tbody: new FormData(form),\n\t\tmethod: \"POST\",\n\t\tmode: \"cors\",\n\t\tcredentials: \"same-origin\",\n\t\t// Add a special header which tells the server that we want the raw\n\t\t// error response (if applicable) rather than the reloaded page that you\n\t\t// would get if submitting a page without javascript\n\t\theaders: {\n\t\t\t\"X-Torpor-Form-Submit\": \"\",\n\t\t},\n\t};\n\tconst response = await fetch(\n\t\taction,\n\t\t// @ts-ignore -- don't know what it's complaining about here\n\t\toptions,\n\t);\n\n\tif (\n\t\tresponse.status === 200 &&\n\t\tresponse.headers.has(\"Content-Disposition\") &&\n\t\t/attachment;\\s*filename=\"(.+?)\"/.test(response.headers.get(\"Content-Disposition\")!)\n\t) {\n\t\t// TODO: Is this enough to check to see if we have a file that should download?\n\t\t// Download the file by creating a dummy <a> and clicking it\n\t\tconst downloadFileName =\n\t\t\tresponse.headers.get(\"Content-Disposition\")?.match(/attachment;\\s*filename=\"(.+?)\"/)![1] ??\n\t\t\t\"file\";\n\t\tconst downloadLink = document.createElement(\"a\");\n\t\tdownloadLink.href = window.URL.createObjectURL(await response.blob());\n\t\tdownloadLink.download = downloadFileName;\n\t\tdownloadLink.click();\n\t} else if (response.status === 200 && response.headers.has(\"X-Torpor-Form-Redirect\")) {\n\t\t// TODO: Should do the correct type of redirect e.g. seeOther etc\n\t\tconst redirect = response.headers.get(\"Location\")!;\n\t\tawait load(redirect);\n\t\twindow.location.href = redirect;\n\t} else if (\n\t\t(response.status >= 200 && response.status <= 299) ||\n\t\t(response.status >= 400 && response.status <= 499)\n\t) {\n\t\t// Put the response into `$page.form`, and it will be put into `$props` in reload\n\t\tif (response.headers.get(\"Content-Type\") === \"application/json\") {\n\t\t\t$page.form = await response.json();\n\t\t}\n\t\tawait reload();\n\t}\n}\n","import client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport type RouteHandler from \"../types/RouteHandler\";\n\nexport default async function loadData(\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\tpath: string,\n\tquery: URLSearchParams,\n\tnewLayoutStack: LayoutPath[],\n\tclientEndPoint: PageEndPoint | undefined,\n\tserverEndPoint: PageServerEndPoint | undefined,\n\tprefetch = false,\n): Promise<Record<string, string> | void> {\n\tlet data = {};\n\tif (handler.layouts) {\n\t\tlet layoutStack = client.layoutStack;\n\t\tfor (let [i, layout] of handler.layouts.entries()) {\n\t\t\tlet layoutPath = layout.path;\n\t\t\tfor (let key in params) {\n\t\t\t\tlayoutPath = layoutPath.replace(`[${key}]`, params[key]);\n\t\t\t}\n\t\t\tif (layoutStack.at(i)?.path === layoutPath) {\n\t\t\t\t// We've already loaded this layout, we can just re-use its data and UI\n\t\t\t\tif (!prefetch) {\n\t\t\t\t\tlayoutStack[i].reuse = true;\n\t\t\t\t}\n\t\t\t\tnewLayoutStack[i] = layoutStack[i];\n\t\t\t\tObject.assign(data, layoutStack[i].data);\n\t\t\t} else {\n\t\t\t\tconst stackLayout = { path: layoutPath, data: {}, reuse: false, slotRegion: null };\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\tstackLayout.data,\n\t\t\t\t\tdocument.location.origin + layoutPath,\n\t\t\t\t\tquery,\n\t\t\t\t\tparams,\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;\n\t\t\t\t}\n\t\t\t\tObject.assign(data, stackLayout.data);\n\t\t\t\tnewLayoutStack.push(stackLayout);\n\t\t\t}\n\t\t}\n\t}\n\tlet endPointResponse = await loadClientAndServerData(\n\t\tdata,\n\t\tdocument.location.origin + path,\n\t\tquery,\n\t\tparams,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\tif (endPointResponse?.ok === false) {\n\t\treturn;\n\t}\n\treturn data;\n}\n\nasync function loadClientAndServerData(\n\tdata: Record<string, any>,\n\tlocation: string,\n\tquery: URLSearchParams,\n\tparams: Record<string, string>,\n\tclientEndPoint?: PageEndPoint,\n\tserverEndPoint?: PageServerEndPoint,\n): Promise<Response | undefined | void> {\n\tlet prefetchedData = client.prefetchedData;\n\n\tif (clientEndPoint?.load) {\n\t\tconst clientKey = location + (query.size > 0 ? `?${query}` : \"\");\n\t\tif (prefetchedData[clientKey]) {\n\t\t\tObject.assign(data, prefetchedData[clientKey]);\n\t\t} else {\n\t\t\tconst clientUrl = new URL(document.location.href);\n\t\t\tfor (let [name, value] of query) {\n\t\t\t\tclientUrl.searchParams.append(name, value);\n\t\t\t}\n\t\t\tconst clientParams = buildClientParams(clientUrl, params, data);\n\t\t\tconst clientResponse = await clientEndPoint.load(clientParams);\n\t\t\tif (clientResponse) {\n\t\t\t\tif (clientResponse.ok) {\n\t\t\t\t\tif (clientResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\t\tconst clientData = await clientResponse.json();\n\t\t\t\t\t\tObject.assign(data, clientData);\n\t\t\t\t\t\tprefetchedData[clientKey] = clientData;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn clientResponse;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (serverEndPoint?.load) {\n\t\tconst serverLocation = location.replace(/\\/$/, \"\") + \"/~server\";\n\t\tconst serverKey = serverLocation + (query.size > 0 ? `?${query}` : \"\");\n\t\tif (prefetchedData[serverKey]) {\n\t\t\tObject.assign(data, prefetchedData[serverKey]);\n\t\t} else {\n\t\t\tconst serverUrl = new URL(serverLocation);\n\t\t\tfor (let [name, value] of query) {\n\t\t\t\tserverUrl.searchParams.append(name, value);\n\t\t\t}\n\t\t\tconst serverResponse = await fetch(serverUrl);\n\t\t\tif (serverResponse) {\n\t\t\t\tif (serverResponse.ok) {\n\t\t\t\t\tif (serverResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\t\tconst serverData = await serverResponse.json();\n\t\t\t\t\t\tObject.assign(data, serverData);\n\t\t\t\t\t\tprefetchedData[serverKey] = serverData;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn serverResponse;\n\t\t\t\t}\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","import { clearLayoutSlot, fillLayoutSlot, hydrate } from \"@torpor/view\";\nimport { type Component, type SlotRender } from \"@torpor/view\";\nimport { mount, unmount } from \"@torpor/view\";\nimport $page from \"../state/$page\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport formSubmit from \"./formSubmit\";\nimport loadData from \"./loadData\";\n\n// @ts-ignore\nexport default async function navigate(url: URL, withHydration = false): Promise<boolean> {\n\tlet parent = document.getElementById(\"app\");\n\tif (!parent) {\n\t\t// TODO: 500\n\t\tconsole.log(\"500\");\n\t\treturn false;\n\t}\n\n\tconst path = url.pathname;\n\tconst query = url.searchParams;\n\n\t//console.log(`navigating to '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\tconst route = client.router.match(path, query);\n\tif (!route) {\n\t\t// TODO: 404\n\t\tconsole.log(\"404\");\n\t\treturn false;\n\t}\n\n\t// Update $page before building the components\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\t// Make it look a bit classier by removing the query\n\t\twindow.history.replaceState({}, \"\", url.toString().split(\"?\")[0]);\n\t} else {\n\t\t$page.status = 200;\n\t}\n\tconst handler = route.handler;\n\tconst params = route.params || {};\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\t// TODO: 404\n\t\tconsole.log(\"404\");\n\t\treturn false;\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\tlet newLayoutStack: LayoutPath[] = [];\n\n\t// Pass the data into $props\n\t// TODO: Don't load if this is the first time -- it should have been passed\n\t// to us, somehow...\n\tconst data = await loadData(\n\t\thandler,\n\t\tparams,\n\t\tpath,\n\t\tquery,\n\t\tnewLayoutStack,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\t// We may have form data in a hidden input -- not sure if this is the best\n\t// way to do it\n\tlet formInput = document.getElementById(\"t-form-data\") as HTMLInputElement;\n\tlet form: Record<string, string> | undefined;\n\tif (formInput) {\n\t\tform = JSON.parse(formInput.value);\n\t\t$page.form = form;\n\t\tformInput.remove();\n\t}\n\tlet $props: Record<string, any> = { data };\n\n\t// We may have form data in $page.form, either from the hidden input\n\t// (above), or added via onformsubmit (below)\n\t$props.form = $page.form;\n\n\t// Add some special context for submitting Forms client-side\n\tconst $context = {\n\t\tTorporBuildContext: { onformsubmit: formSubmit },\n\t};\n\n\tclient.layoutStack.push({ path: route.handler.path, data: {}, reuse: false, slotRegion: null });\n\tclient.layoutStack = newLayoutStack;\n\tlet layoutStack = client.layoutStack;\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: There's probably a nicer way to do this with reducers or something\n\tlet component = clientEndPoint.component as Component;\n\tlet slots: Record<string, SlotRender> | undefined = undefined;\n\tlet reused = false;\n\t// The index of the innermost reused layout. Its slotRegion is the region\n\t// that must be cleared and refilled (it contains the next layout's or the\n\t// page's content). This is usually the last entry of the stack, but not\n\t// when an outer layout is reused while an inner one is new — e.g.\n\t// navigating between sections that share the root layout.\n\tlet reusedIndex = -1;\n\tif (handler.layouts) {\n\t\tlet slotFunctions: SlotRender[] = [];\n\t\t// The last slot function will render the client component\n\t\tslotFunctions[handler.layouts.length] = function clientComponent(parent, anchor) {\n\t\t\tlet i = layoutStack.length - 1;\n\t\t\tlayoutStack[i].slotRegion = fillLayoutSlot(\n\t\t\t\tclientEndPoint.component!,\n\t\t\t\tslotFunctions[i + 1],\n\t\t\t\tparent,\n\t\t\t\tanchor,\n\t\t\t\t$props,\n\t\t\t\t$context,\n\t\t\t);\n\t\t};\n\n\t\t// Each earlier slot function will render a layout\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 (layoutStack[i].reuse) {\n\t\t\t\t\t// Reuse this layout — clear and refill its slot (done in\n\t\t\t\t\t// the try block below so a failure doesn't leave the slot\n\t\t\t\t\t// half-cleared)\n\t\t\t\t\tcomponent = slotFunctions[i + 1] as Component;\n\t\t\t\t\treusedIndex = i;\n\t\t\t\t\treused = true;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (i === 0) {\n\t\t\t\t\tcomponent = layoutEndPoint.component as Component;\n\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t} else {\n\t\t\t\t\tslotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {\n\t\t\t\t\t\tlayoutStack[i - 1].slotRegion = fillLayoutSlot(\n\t\t\t\t\t\t\tlayoutEndPoint.component!,\n\t\t\t\t\t\t\tslotFunctions[i + 1],\n\t\t\t\t\t\t\tparent,\n\t\t\t\t\t\t\tanchor,\n\t\t\t\t\t\t\t$props,\n\t\t\t\t\t\t\t$context,\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\t}\n\n\ttry {\n\t\tif (reused) {\n\t\t\t// The layout is being reused — clear the old slot content, then\n\t\t\t// call the slot function directly to fill it with the new page.\n\t\t\t// We must not go through `mount` here: the slot's container still\n\t\t\t// holds the layout's own children (e.g. a header), which `mount`\n\t\t\t// refuses to mount into. Both the clear and the fill are inside\n\t\t\t// the try so that a failure doesn't leave the slot half-cleared.\n\t\t\tconst slotRegion = layoutStack[reusedIndex].slotRegion;\n\t\t\tparent = slotRegion.startNode!.parentNode as HTMLElement;\n\t\t\tclearLayoutSlot(slotRegion);\n\t\t\tcomponent(parent, null);\n\t\t} else if (withHydration) {\n\t\t\thydrate(parent, component, $props, slots);\n\t\t} else {\n\t\t\t// The layout chain changed (or there was no previous layout to\n\t\t\t// reuse): tear down the previous UI entirely — disposing its\n\t\t\t// region tree and clearing `#app` — so `mount` starts fresh.\n\t\t\t// Without this, `mount` throws because `#app` still holds the\n\t\t\t// previous render's children, and it would reuse a stale root\n\t\t\t// region.\n\t\t\tunmount(parent);\n\t\t\tmount(parent, component, $props, slots);\n\t\t}\n\t} catch (error) {\n\t\t// TODO: Show a proper Error component\n\t\tparent.innerHTML = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\tconsole.log(error);\n\t}\n\n\t// Reset prefetched data on each navigation\n\tclient.prefetchedData = {};\n\n\treturn true;\n}\n","import navigate from \"./navigate\";\n\n/**\n * Loads and renders the page at the supplied path.\n */\nexport default async function load(path: string): Promise<boolean> {\n\tconst url = new URL(path, document.location.href);\n\treturn await navigate(url);\n}\n"],"mappings":"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"}
package/dist/nav.d.mts CHANGED
@@ -1,14 +1,67 @@
1
+ import { n as ParseRouteParams, t as ExactRouteParams } from "./ParseRouteParams-DBmqlmLh.mjs";
2
+ import { t as TypedResponse } from "./TypedResponse-6lznqN4U.mjs";
3
+ //#region src/nav/api.d.ts
4
+ /**
5
+ * The value a client call resolves to: the endpoint's typed JSON body when it
6
+ * returns a typed response, or the raw Response when the body isn't JSON.
7
+ * Untyped endpoints resolve to `unknown`.
8
+ */
9
+ type ApiResult<Handler> = Handler extends ((event: any) => any) ? Awaited<ReturnType<Handler>> extends TypedResponse<infer Body> ? Body | Response : unknown : never;
10
+ /**
11
+ * The body a client call accepts: the endpoint event's annotated JSON body
12
+ * type (from `ServerLoadEvent<Route, Body>`), or a loose value when the
13
+ * handler doesn't annotate one.
14
+ */
15
+ type ApiRequestBody<Handler> = Handler extends ((event: infer Event) => any) ? Event extends {
16
+ json: () => Promise<infer Body>;
17
+ } ? unknown extends Body ? BodyInit | object : Body : BodyInit | object : BodyInit | object;
18
+ /** The callable methods of an endpoint, one per handler it defines. */
19
+ type ApiMethods<Endpoint extends object> = { [Method in string & keyof Endpoint as NonNullable<Endpoint[Method]> extends ((event: any) => any) ? Method : never]: (body?: ApiRequestBody<NonNullable<Endpoint[Method]>>, init?: RequestInit) => Promise<ApiResult<NonNullable<Endpoint[Method]>>>; };
20
+ /**
21
+ * Creates typed client callers for a `+server` endpoint, e.g.
22
+ *
23
+ * ```ts
24
+ * import type endpoint from "@/routes/api/posts/[id]/+server";
25
+ * const post = makeApi<"/api/posts/[id]", typeof endpoint>("/api/posts/[id]");
26
+ * const data = await post.get(); // typed from the endpoint's responses
27
+ * ```
28
+ *
29
+ * Method names match the endpoint's handlers (`del` for DELETE). Params are
30
+ * filled in and URI-encoded from the route path, and JSON responses are
31
+ * parsed; non-JSON responses resolve to the raw Response. Failed requests
32
+ * (non-2xx) throw.
33
+ *
34
+ * @param path The endpoint's route path
35
+ * @param params The route params, required if the path has dynamic segments
36
+ * @returns An object with one caller per handler defined by the endpoint
37
+ */
38
+ declare function makeApi<Route extends string, Endpoint extends object, Params extends ParseRouteParams<Route> = ParseRouteParams<Route>>(path: Route, ...args: string extends Route ? [params?: Record<string, string>] : keyof ParseRouteParams<Route> extends never ? [] : [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]): ApiMethods<Endpoint>;
39
+ //#endregion
1
40
  //#region src/nav/load.d.ts
2
41
  /**
3
- * Loads and renders the page at the supplied path.
4
- */
42
+ * Loads and renders the page at the supplied path.
43
+ */
5
44
  declare function load(path: string): Promise<boolean>;
6
45
  //#endregion
7
46
  //#region src/nav/reload.d.ts
8
47
  /**
9
- * Reloads data for the currently loaded page and rerenders it.
10
- */
48
+ * Reloads data for the currently loaded page and rerenders it.
49
+ */
11
50
  declare function reload(): Promise<boolean>;
12
51
  //#endregion
13
- export { load, reload };
52
+ //#region src/nav/route.d.ts
53
+ /**
54
+ * Builds a route path in a type-safe manner, filling in dynamic segments, e.g.
55
+ * `route("/posts/[id]", { id: 5 })` returns `/posts/5`. The params object is
56
+ * checked for exact keys: missing or unknown params error at compile time.
57
+ *
58
+ * NOTE: The rest args conditional must stay INLINE — routing it through a type
59
+ * alias defeats `Params` inference, and excess keys stop being checked
60
+ * @param path The route path
61
+ * @param params The route params, required if the path has dynamic segments
62
+ * @returns The path with the params filled in, URI-encoded
63
+ */
64
+ declare function route<Route extends string, Params extends ParseRouteParams<Route> = ParseRouteParams<Route>>(path: Route, ...args: string extends Route ? [params?: Record<string, string>] : keyof ParseRouteParams<Route> extends never ? [] : [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]): string;
65
+ //#endregion
66
+ export { load, makeApi, reload, route };
14
67
  //# sourceMappingURL=nav.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nav.d.mts","names":[],"sources":["../src/nav/load.ts","../src/nav/reload.ts"],"sourcesContent":[],"mappings":";;;;iBAK8B,IAAA,gBAAoB;;;;;;iBCApB,MAAA,CAAA,GAAU"}
1
+ {"version":3,"file":"nav.d.mts","names":[],"sources":["../src/nav/api.ts","../src/nav/load.ts","../src/nav/reload.ts","../src/nav/route.ts"],"mappings":";;;;;;;;KASK,UAAU,WAAW,kBAAiB,sBACxC,QAAQ,WAAW,kBAAkB,oBAAoB,QACxD,OAAO;;;;;;KASN,eAAe,WAAW,kBAAiB,aAAa,iBAC1D;EAAgB,YAAY,cAAc;oBACzB,OACf,oBACA,OACD,oBACD;;KAGS,WAAW,8BACrB,yBAAyB,YAAY,YAAY,SAAS,mBAAkB,sBAC1E,kBAEF,OAAO,eAAe,YAAY,SAAS,WAC3C,OAAO,gBACH,QAAQ,UAAU,YAAY,SAAS;;;;;;;;;;;;;;;;;;;iBAuBrB,QACvB,sBACA,yBACA,eAAe,iBAAiB,SAAS,iBAAiB,QAE1D,MAAM,UAGH,qBAAqB,SACpB,SAAS,gCACJ,iBAAiB,6BAErB,QAAQ,SAAS,iBAAiB,QAAQ,iBAAiB,WAC9D,WAAW;;;;;;iBClEgB,KAAK,eAAe;;;;;;iBCApB,UAAU;;;;;;;;;;;;;;iBCQhB,MACvB,sBACA,eAAe,iBAAiB,SAAS,iBAAiB,QAE1D,MAAM,UACH,qBAAqB,SACpB,SAAS,gCACJ,iBAAiB,6BAErB,QAAQ,SAAS,iBAAiB,QAAQ,iBAAiB"}
package/dist/nav.mjs CHANGED
@@ -1,219 +1,73 @@
1
- import { t as $page_default } from "./_page-BQs4WvCO.mjs";
2
- import { clearLayoutSlot, fillLayoutSlot, hydrate, mount } from "@torpor/view";
3
-
4
- //#region src/state/client.ts
5
- const CLIENT_SYMBOL = Symbol.for("torp.Client");
6
- const client = globalThis[CLIENT_SYMBOL] ??= {
7
- router: null,
8
- layoutStack: [],
9
- prefetchedData: {}
10
- };
11
- var client_default = client;
12
-
13
- //#endregion
14
- //#region src/nav/reload.ts
1
+ import { i as reload, t as load } from "./load-BntzvmwL.mjs";
2
+ //#region src/nav/route.ts
15
3
  /**
16
- * Reloads data for the currently loaded page and rerenders it.
4
+ * Builds a route path in a type-safe manner, filling in dynamic segments, e.g.
5
+ * `route("/posts/[id]", { id: 5 })` returns `/posts/5`. The params object is
6
+ * checked for exact keys: missing or unknown params error at compile time.
7
+ *
8
+ * NOTE: The rest args conditional must stay INLINE — routing it through a type
9
+ * alias defeats `Params` inference, and excess keys stop being checked
10
+ * @param path The route path
11
+ * @param params The route params, required if the path has dynamic segments
12
+ * @returns The path with the params filled in, URI-encoded
17
13
  */
18
- async function reload() {
19
- return await navigate(new URL(document.location.href));
20
- }
21
-
22
- //#endregion
23
- //#region src/nav/formSubmit.ts
24
- async function formSubmit(e) {
25
- e.preventDefault();
26
- $page_default.form = void 0;
27
- const form = e.target;
28
- const button = e.submitter;
29
- let action = form.action || document.URL;
30
- if (button && button.formAction && button.formAction !== document.URL) action = button.formAction;
31
- const options = {
32
- body: new FormData(form),
33
- method: "POST",
34
- mode: "cors",
35
- credentials: "same-origin",
36
- headers: { "x-torpor-form-submit": "" }
37
- };
38
- const response = await fetch(action, options);
39
- if (response.status === 200 && response.headers.has("x-torpor-form-redirect")) {
40
- const redirect = response.headers.get("location");
41
- await load(redirect);
42
- window.location.href = redirect;
43
- } else if (response.status >= 200 && response.status <= 299 || response.status >= 400 && response.status <= 499) {
44
- if (response.headers.get("content-type") === "application/json") $page_default.form = await response.json();
45
- await reload();
46
- }
47
- }
48
-
49
- //#endregion
50
- //#region src/nav/loadData.ts
51
- async function loadData(handler, params, path, query, newLayoutStack, clientEndPoint, serverEndPoint, prefetch = false) {
52
- let data = {};
53
- if (handler.layouts) {
54
- let layoutStack = client_default.layoutStack;
55
- for (let [i, layout] of handler.layouts.entries()) {
56
- let layoutPath = layout.path;
57
- for (let key in params) layoutPath = layoutPath.replace(`[${key}]`, params[key]);
58
- if (layoutStack.at(i)?.path === layoutPath) {
59
- if (!prefetch) layoutStack[i].reuse = true;
60
- newLayoutStack[i] = layoutStack[i];
61
- Object.assign(data, layoutStack[i].data);
62
- } else {
63
- const stackLayout = {
64
- path: layoutPath,
65
- data: {},
66
- reuse: false,
67
- slotRegion: null
68
- };
69
- const layoutEndPoint = (await layout.endPoint())?.default;
70
- const layoutServerEndPoint = layout.serverEndPoint && (await layout.serverEndPoint())?.default;
71
- if ((await loadClientAndServerData(stackLayout.data, document.location.origin + layoutPath, query, params, layoutEndPoint, layoutServerEndPoint))?.ok === false) return;
72
- Object.assign(data, stackLayout.data);
73
- newLayoutStack.push(stackLayout);
74
- }
75
- }
76
- }
77
- if ((await loadClientAndServerData(data, document.location.origin + path, query, params, clientEndPoint, serverEndPoint))?.ok === false) return;
78
- return data;
79
- }
80
- async function loadClientAndServerData(data, location, query, params, clientEndPoint, serverEndPoint) {
81
- let prefetchedData = client_default.prefetchedData;
82
- if (clientEndPoint?.load) {
83
- const clientKey = location + (query.size > 0 ? `?${query}` : "");
84
- if (prefetchedData[clientKey]) Object.assign(data, prefetchedData[clientKey]);
85
- else {
86
- const clientUrl = new URL(document.location.href);
87
- for (let [name, value] of query) clientUrl.searchParams.append(name, value);
88
- const clientParams = buildClientParams(clientUrl, params, data);
89
- const clientResponse = await clientEndPoint.load(clientParams);
90
- if (clientResponse) if (clientResponse.ok) {
91
- if (clientResponse.headers.get("Content-Type")?.includes("application/json")) {
92
- const clientData = await clientResponse.json();
93
- Object.assign(data, clientData);
94
- prefetchedData[clientKey] = clientData;
95
- }
96
- } else return clientResponse;
97
- }
98
- }
99
- if (serverEndPoint?.load) {
100
- const serverLocation = location.replace(/\/$/, "") + "/~server";
101
- const serverKey = serverLocation + (query.size > 0 ? `?${query}` : "");
102
- if (prefetchedData[serverKey]) Object.assign(data, prefetchedData[serverKey]);
103
- else {
104
- const serverUrl = new URL(serverLocation);
105
- for (let [name, value] of query) serverUrl.searchParams.append(name, value);
106
- const serverResponse = await fetch(serverUrl);
107
- if (serverResponse) if (serverResponse.ok) {
108
- if (serverResponse.headers.get("Content-Type")?.includes("application/json")) {
109
- const serverData = await serverResponse.json();
110
- Object.assign(data, serverData);
111
- prefetchedData[serverKey] = serverData;
112
- }
113
- } else return serverResponse;
114
- }
115
- }
116
- }
117
- function buildClientParams(url, params, data) {
118
- return {
119
- url,
120
- params,
121
- data
122
- };
123
- }
124
-
125
- //#endregion
126
- //#region src/nav/navigate.ts
127
- async function navigate(url, withHydration = false) {
128
- let parent = document.getElementById("app");
129
- if (!parent) {
130
- console.log("500");
131
- return false;
132
- }
133
- const path = url.pathname;
134
- const query = url.searchParams;
135
- const route = client_default.router.match(path, query);
136
- if (!route) {
137
- console.log("404");
138
- return false;
139
- }
140
- $page_default.url = url;
141
- if (path.endsWith("/_error")) {
142
- $page_default.status = parseInt(query.get("status") ?? "404");
143
- $page_default.error = { message: query.get("message") ?? "" };
144
- window.history.replaceState({}, "", url.toString().split("?")[0]);
145
- } else $page_default.status = 200;
146
- const handler = route.handler;
147
- const params = route.params || {};
148
- const clientEndPoint = (await handler.endPoint()).default;
149
- if (!clientEndPoint?.component) {
150
- console.log("404");
151
- return false;
152
- }
153
- const serverEndPoint = handler.serverEndPoint && (await handler.serverEndPoint())?.default;
154
- let newLayoutStack = [];
155
- const data = await loadData(handler, params, path, query, newLayoutStack, clientEndPoint, serverEndPoint);
156
- let formInput = document.getElementById("t-form-data");
157
- let form;
158
- if (formInput) {
159
- form = JSON.parse(formInput.value);
160
- $page_default.form = form;
161
- formInput.remove();
162
- }
163
- let $props = { data };
164
- $props.form = $page_default.form;
165
- const $context = { TorporBuildContext: { onformsubmit: formSubmit } };
166
- client_default.layoutStack.push({
167
- path: route.handler.path,
168
- data: {},
169
- reuse: false,
170
- slotRegion: null
14
+ function route(path, ...args) {
15
+ const params = args[0] ?? {};
16
+ return path.replace(/\[(\.\.\.)?([^\]]+)\]/g, (_, splat, name) => {
17
+ const value = params[name];
18
+ if (value === void 0) throw new Error(`Missing param '${name}' for route '${path}'`);
19
+ return splat ? value.split("/").map((segment) => encodeURIComponent(segment)).join("/") : encodeURIComponent(String(value));
171
20
  });
172
- client_default.layoutStack = newLayoutStack;
173
- let layoutStack = client_default.layoutStack;
174
- let component = clientEndPoint.component;
175
- let slots = void 0;
176
- if (handler.layouts) {
177
- let slotFunctions = [];
178
- slotFunctions[handler.layouts.length] = function clientComponent(parent$1, anchor) {
179
- let i = layoutStack.length - 1;
180
- layoutStack[i].slotRegion = fillLayoutSlot(clientEndPoint.component, slotFunctions[i + 1], parent$1, anchor, $props, $context);
181
- };
182
- for (let i = handler.layouts.length - 1; i >= 0; i--) {
183
- const layoutEndPoint = (await handler.layouts[i].endPoint())?.default;
184
- if (layoutEndPoint?.component) if (layoutStack[i].reuse) {
185
- parent = layoutStack[i].slotRegion.startNode.parentNode;
186
- clearLayoutSlot(layoutStack[i].slotRegion);
187
- component = slotFunctions[i + 1];
188
- break;
189
- } else if (i === 0) {
190
- component = layoutEndPoint.component;
191
- slots = { _: slotFunctions[i + 1] };
192
- } else slotFunctions[i] = function layoutComponent(parent$1, anchor, _, $context$1) {
193
- layoutStack[i - 1].slotRegion = fillLayoutSlot(layoutEndPoint.component, slotFunctions[i + 1], parent$1, anchor, $props, $context$1);
194
- };
195
- }
196
- }
197
- if (withHydration) hydrate(parent, component, $props, slots);
198
- else try {
199
- mount(parent, component, $props, slots);
200
- } catch (error) {
201
- parent.innerHTML = "<span style=\"color: red\">Script syntax error</span><p>" + error + "</p>";
202
- console.log(error);
203
- }
204
- client_default.prefetchedData = {};
205
- return true;
206
21
  }
207
-
208
22
  //#endregion
209
- //#region src/nav/load.ts
23
+ //#region src/nav/api.ts
24
+ const METHODS = [
25
+ "get",
26
+ "post",
27
+ "patch",
28
+ "put",
29
+ "del",
30
+ "options",
31
+ "head"
32
+ ];
210
33
  /**
211
- * Loads and renders the page at the supplied path.
34
+ * Creates typed client callers for a `+server` endpoint, e.g.
35
+ *
36
+ * ```ts
37
+ * import type endpoint from "@/routes/api/posts/[id]/+server";
38
+ * const post = makeApi<"/api/posts/[id]", typeof endpoint>("/api/posts/[id]");
39
+ * const data = await post.get(); // typed from the endpoint's responses
40
+ * ```
41
+ *
42
+ * Method names match the endpoint's handlers (`del` for DELETE). Params are
43
+ * filled in and URI-encoded from the route path, and JSON responses are
44
+ * parsed; non-JSON responses resolve to the raw Response. Failed requests
45
+ * (non-2xx) throw.
46
+ *
47
+ * @param path The endpoint's route path
48
+ * @param params The route params, required if the path has dynamic segments
49
+ * @returns An object with one caller per handler defined by the endpoint
212
50
  */
213
- async function load(path) {
214
- return await navigate(new URL(path, document.location.href));
51
+ function makeApi(path, ...args) {
52
+ const url = route(path, ...args);
53
+ const api = {};
54
+ for (const method of METHODS) api[method] = (body, init) => request(method, url, body, init);
55
+ return api;
56
+ }
57
+ async function request(method, url, body, init) {
58
+ const options = {
59
+ method: (method === "del" ? "delete" : method).toUpperCase(),
60
+ ...init
61
+ };
62
+ if (body !== void 0 && options.body === void 0 && method !== "get" && method !== "head") if (typeof body === "object") {
63
+ options.headers ??= { "Content-Type": "application/json" };
64
+ options.body = JSON.stringify(body);
65
+ } else options.body = body;
66
+ const response = await fetch(url, options);
67
+ if (!response.ok) throw new Error(`API ${method.toUpperCase()} ${url} failed: ${response.status}`);
68
+ return (response.headers.get("Content-Type") ?? "").includes("application/json") ? await response.json() : response;
215
69
  }
216
-
217
70
  //#endregion
218
- export { load, reload };
71
+ export { load, makeApi, reload, route };
72
+
219
73
  //# sourceMappingURL=nav.mjs.map