@torpor/build 0.4.13 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/Jsonify-Dg6gChd3.d.mts +10 -0
- package/dist/Jsonify-Dg6gChd3.d.mts.map +1 -0
- package/dist/ParseRouteParams-DBmqlmLh.d.mts +42 -0
- package/dist/ParseRouteParams-DBmqlmLh.d.mts.map +1 -0
- package/dist/{RouteType-B6BMOyYw.mjs → Router-C4ADUfhv.mjs} +30 -42
- package/dist/Router-C4ADUfhv.mjs.map +1 -0
- package/dist/{Server-CF5wDJp6.d.mts → Server-C_IKav_e.d.mts} +44 -16
- package/dist/Server-C_IKav_e.d.mts.map +1 -0
- package/dist/{ServerEvent-CxPwA4lH.mjs → ServerEvent-Dljn5i7A.mjs} +37 -11
- package/dist/ServerEvent-Dljn5i7A.mjs.map +1 -0
- package/dist/Site-CMa153FA.d.mts +565 -0
- package/dist/Site-CMa153FA.d.mts.map +1 -0
- package/dist/StandardSchema-D28L8_zZ.d.mts +100 -0
- package/dist/StandardSchema-D28L8_zZ.d.mts.map +1 -0
- package/dist/TypedResponse-6lznqN4U.d.mts +22 -0
- package/dist/TypedResponse-6lznqN4U.d.mts.map +1 -0
- package/dist/{_page-BQs4WvCO.mjs → _page-4cQKs_Dn.mjs} +3 -5
- package/dist/_page-4cQKs_Dn.mjs.map +1 -0
- package/dist/bin/index.d.mts +1 -1
- package/dist/bin/index.js +15 -11
- package/dist/bin/index.js.map +1 -1
- package/dist/flattenHeaders-C_YYLdOq.mjs +114 -0
- package/dist/flattenHeaders-C_YYLdOq.mjs.map +1 -0
- package/dist/form.d.mts +35 -0
- package/dist/form.d.mts.map +1 -0
- package/dist/form.mjs +48 -0
- package/dist/form.mjs.map +1 -0
- package/dist/index.d.mts +72 -156
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +164 -32
- package/dist/index.mjs.map +1 -1
- package/dist/nav.d.mts +58 -5
- package/dist/nav.d.mts.map +1 -1
- package/dist/nav.mjs +116 -38
- package/dist/nav.mjs.map +1 -1
- package/dist/openapi.d.mts +144 -0
- package/dist/openapi.d.mts.map +1 -0
- package/dist/openapi.mjs +2 -0
- package/dist/pathTrie-D4Ax2Mu9.mjs +160 -0
- package/dist/pathTrie-D4Ax2Mu9.mjs.map +1 -0
- package/dist/plugin-D8Www11B.mjs +202 -0
- package/dist/plugin-D8Www11B.mjs.map +1 -0
- package/dist/{response-C5TtAsh1.mjs → response-Bg4w2S1q.mjs} +16 -39
- package/dist/response-Bg4w2S1q.mjs.map +1 -0
- package/dist/response.d.mts +309 -289
- package/dist/response.d.mts.map +1 -1
- package/dist/response.mjs +3 -4
- package/dist/run.d.mts +11 -4
- package/dist/run.d.mts.map +1 -1
- package/dist/run.mjs +2 -8
- package/dist/runOpenApi-BUi1RH1l.mjs +1381 -0
- package/dist/runOpenApi-BUi1RH1l.mjs.map +1 -0
- package/dist/schema.d.mts +32 -0
- package/dist/schema.d.mts.map +1 -0
- package/dist/schema.mjs +2 -0
- package/dist/server.d.mts +10 -8
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +132 -4
- package/dist/server.mjs.map +1 -0
- package/dist/state.d.mts.map +1 -1
- package/dist/state.mjs +2 -3
- package/dist/test.d.mts +8 -10
- package/dist/test.d.mts.map +1 -1
- package/dist/test.mjs +286 -82
- package/dist/test.mjs.map +1 -1
- package/dist/{seeOther-B4Yhu9iq.mjs → unprocessable-DAZbrDeR.mjs} +34 -13
- package/dist/unprocessable-DAZbrDeR.mjs.map +1 -0
- package/dist/validate-Ok4krlkX.mjs +40 -0
- package/dist/validate-Ok4krlkX.mjs.map +1 -0
- package/package.json +28 -17
- package/src/bin/index.ts +13 -2
- package/src/dev.ts +10 -0
- package/src/form/formDataToRecord.ts +23 -0
- package/src/form/readForm.ts +71 -0
- package/src/form.ts +4 -0
- package/src/index.ts +53 -1
- package/src/nav/api.test-d.ts +79 -0
- package/src/nav/api.ts +104 -0
- package/src/nav/formSubmit.ts +18 -4
- package/src/nav/navigate.ts +40 -15
- package/src/nav/route.ts +38 -0
- package/src/nav.ts +3 -1
- package/src/openapi/docsHtml.ts +25 -0
- package/src/openapi/document.ts +154 -0
- package/src/openapi/plugin.ts +78 -0
- package/src/openapi/types.ts +82 -0
- package/src/openapi.ts +14 -0
- package/src/response/TypedResponse.ts +17 -0
- package/src/response/badRequest.ts +14 -2
- package/src/response/created.ts +13 -2
- package/src/response/found.ts +2 -2
- package/src/response/movedPermanently.ts +2 -2
- package/src/response/notModified.ts +2 -2
- package/src/response/ok.ts +13 -4
- package/src/response/response.ts +4 -4
- package/src/response/unprocessable.ts +14 -2
- package/src/run/depCache.ts +175 -0
- package/src/run/devPlugin.ts +128 -0
- package/src/run/prepareTemplate.ts +6 -3
- package/src/run/run.ts +63 -39
- package/src/run/runBuild.ts +63 -12
- package/src/run/runDev.ts +146 -58
- package/src/run/runOpenApi.ts +52 -0
- package/src/run/runPreview.ts +17 -26
- package/src/run/staleTorpCopies.ts +100 -0
- package/src/run.ts +2 -1
- package/src/schema.ts +7 -0
- package/src/server/CookieHelper.ts +17 -7
- package/src/server/Server.ts +54 -30
- package/src/server/ServerEvent.ts +24 -1
- package/src/server/connect/connectMiddleware.ts +44 -40
- package/src/server/connect/flattenHeaders.ts +1 -23
- package/src/server/connect/requestToNodeMessage.ts +6 -3
- package/src/server/types/MiddlewareFunction.ts +18 -4
- package/src/site/Router.ts +52 -41
- package/src/site/Site.ts +184 -10
- package/src/site/checkLayoutSlots.ts +114 -0
- package/src/site/checkRoutes.ts +435 -0
- package/src/site/clientEntry.ts +22 -9
- package/src/site/layoutSlots.ts +53 -0
- package/src/site/manifest.ts +125 -6
- package/src/site/serverEntry.ts +352 -133
- package/src/state/$page.ts +2 -1
- package/src/state/$serverPage.ts +22 -0
- package/src/test/runTest.ts +306 -118
- package/src/types/Adapter.ts +12 -0
- package/src/types/Jsonify.ts +20 -0
- package/src/types/PageData.test-d.ts +136 -0
- package/src/types/PageData.ts +35 -0
- package/src/types/PageEndPoint.ts +25 -9
- package/src/types/PageForm.test-d.ts +78 -0
- package/src/types/PageForm.ts +29 -0
- package/src/types/PageLoadEvent.ts +16 -4
- package/src/types/PageLoadReturn.ts +13 -0
- package/src/types/PageProps.ts +14 -0
- package/src/types/PageServerAction.ts +8 -2
- package/src/types/PageServerEndPoint.test-d.ts +105 -0
- package/src/types/PageServerEndPoint.ts +99 -5
- package/src/types/PageServerLoad.ts +11 -3
- package/src/types/ParseRouteParams.test-d.ts +84 -0
- package/src/types/ParseRouteParams.ts +64 -0
- package/src/types/Route.ts +20 -1
- package/src/types/RouteHandler.ts +8 -1
- package/src/types/ServerEndPoint.test-d.ts +77 -0
- package/src/types/ServerEndPoint.ts +148 -16
- package/src/types/ServerHook.ts +10 -3
- package/src/types/ServerLoadEvent.ts +48 -3
- package/src/types/ServerRequest.ts +10 -2
- package/src/types/SitePlugin.ts +24 -0
- package/src/types/StandardSchema.ts +109 -0
- package/src/utils/pathToRegex.ts +4 -2
- package/src/utils/pathTrie.ts +182 -0
- package/src/utils/searchParamsToRecord.ts +18 -0
- package/src/utils/torporPackages.ts +150 -0
- package/src/utils/tsconfigAliases.ts +90 -0
- package/src/validation/ValidationError.ts +18 -0
- package/src/validation/endpoint.ts +83 -0
- package/src/validation/validate.ts +26 -0
- package/dist/RouteType-B6BMOyYw.mjs.map +0 -1
- package/dist/Server-CF5wDJp6.d.mts.map +0 -1
- package/dist/ServerEvent-CxPwA4lH.mjs.map +0 -1
- package/dist/Site-DgC6WWn1.d.mts +0 -78
- package/dist/Site-DgC6WWn1.d.mts.map +0 -1
- package/dist/_page-BQs4WvCO.mjs.map +0 -1
- package/dist/connectMiddleware-D7nehjj3.mjs +0 -252
- package/dist/connectMiddleware-D7nehjj3.mjs.map +0 -1
- package/dist/pathToRegex-TUAMc3zS.mjs +0 -11
- package/dist/pathToRegex-TUAMc3zS.mjs.map +0 -1
- package/dist/response-C5TtAsh1.mjs.map +0 -1
- package/dist/run-DiZgkMnG.mjs +0 -332
- package/dist/run-DiZgkMnG.mjs.map +0 -1
- package/dist/seeOther-B4Yhu9iq.mjs.map +0 -1
- package/src/server/Routerx.ts +0 -72
- package/src/server/connect/bufferToArrayBuffer.ts +0 -8
- package/src/server/connect/readableToBuffer.ts +0 -16
package/dist/nav.mjs
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
|
-
import { t as $
|
|
2
|
-
import { clearLayoutSlot, fillLayoutSlot, hydrate, mount } from "@torpor/view";
|
|
3
|
-
|
|
1
|
+
import { t as $page } from "./_page-4cQKs_Dn.mjs";
|
|
2
|
+
import { clearLayoutSlot, fillLayoutSlot, hydrate, mount, unmount } from "@torpor/view";
|
|
3
|
+
//#region src/nav/route.ts
|
|
4
|
+
/**
|
|
5
|
+
* Builds a route path in a type-safe manner, filling in dynamic segments, e.g.
|
|
6
|
+
* `route("/posts/[id]", { id: 5 })` returns `/posts/5`. The params object is
|
|
7
|
+
* checked for exact keys: missing or unknown params error at compile time.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: The rest args conditional must stay INLINE — routing it through a type
|
|
10
|
+
* alias defeats `Params` inference, and excess keys stop being checked
|
|
11
|
+
* @param path The route path
|
|
12
|
+
* @param params The route params, required if the path has dynamic segments
|
|
13
|
+
* @returns The path with the params filled in, URI-encoded
|
|
14
|
+
*/
|
|
15
|
+
function route(path, ...args) {
|
|
16
|
+
const params = args[0] ?? {};
|
|
17
|
+
return path.replace(/\[(\.\.\.)?([^\]]+)\]/g, (_, splat, name) => {
|
|
18
|
+
const value = params[name];
|
|
19
|
+
if (value === void 0) throw new Error(`Missing param '${name}' for route '${path}'`);
|
|
20
|
+
return splat ? value.split("/").map((segment) => encodeURIComponent(segment)).join("/") : encodeURIComponent(String(value));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/nav/api.ts
|
|
25
|
+
const METHODS = [
|
|
26
|
+
"get",
|
|
27
|
+
"post",
|
|
28
|
+
"patch",
|
|
29
|
+
"put",
|
|
30
|
+
"del",
|
|
31
|
+
"options",
|
|
32
|
+
"head"
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Creates typed client callers for a `+server` endpoint, e.g.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* import type endpoint from "@/routes/api/posts/[id]/+server";
|
|
39
|
+
* const post = makeApi<"/api/posts/[id]", typeof endpoint>("/api/posts/[id]");
|
|
40
|
+
* const data = await post.get(); // typed from the endpoint's responses
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Method names match the endpoint's handlers (`del` for DELETE). Params are
|
|
44
|
+
* filled in and URI-encoded from the route path, and JSON responses are
|
|
45
|
+
* parsed; non-JSON responses resolve to the raw Response. Failed requests
|
|
46
|
+
* (non-2xx) throw.
|
|
47
|
+
*
|
|
48
|
+
* @param path The endpoint's route path
|
|
49
|
+
* @param params The route params, required if the path has dynamic segments
|
|
50
|
+
* @returns An object with one caller per handler defined by the endpoint
|
|
51
|
+
*/
|
|
52
|
+
function makeApi(path, ...args) {
|
|
53
|
+
const url = route(path, ...args);
|
|
54
|
+
const api = {};
|
|
55
|
+
for (const method of METHODS) api[method] = (body, init) => request(method, url, body, init);
|
|
56
|
+
return api;
|
|
57
|
+
}
|
|
58
|
+
async function request(method, url, body, init) {
|
|
59
|
+
const options = {
|
|
60
|
+
method: (method === "del" ? "delete" : method).toUpperCase(),
|
|
61
|
+
...init
|
|
62
|
+
};
|
|
63
|
+
if (body !== void 0 && options.body === void 0 && method !== "get" && method !== "head") if (typeof body === "object") {
|
|
64
|
+
options.headers ??= { "Content-Type": "application/json" };
|
|
65
|
+
options.body = JSON.stringify(body);
|
|
66
|
+
} else options.body = body;
|
|
67
|
+
const response = await fetch(url, options);
|
|
68
|
+
if (!response.ok) throw new Error(`API ${method.toUpperCase()} ${url} failed: ${response.status}`);
|
|
69
|
+
return (response.headers.get("Content-Type") ?? "").includes("application/json") ? await response.json() : response;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
4
72
|
//#region src/state/client.ts
|
|
5
73
|
const CLIENT_SYMBOL = Symbol.for("torp.Client");
|
|
6
74
|
const client = globalThis[CLIENT_SYMBOL] ??= {
|
|
@@ -8,8 +76,6 @@ const client = globalThis[CLIENT_SYMBOL] ??= {
|
|
|
8
76
|
layoutStack: [],
|
|
9
77
|
prefetchedData: {}
|
|
10
78
|
};
|
|
11
|
-
var client_default = client;
|
|
12
|
-
|
|
13
79
|
//#endregion
|
|
14
80
|
//#region src/nav/reload.ts
|
|
15
81
|
/**
|
|
@@ -18,12 +84,11 @@ var client_default = client;
|
|
|
18
84
|
async function reload() {
|
|
19
85
|
return await navigate(new URL(document.location.href));
|
|
20
86
|
}
|
|
21
|
-
|
|
22
87
|
//#endregion
|
|
23
88
|
//#region src/nav/formSubmit.ts
|
|
24
89
|
async function formSubmit(e) {
|
|
25
90
|
e.preventDefault();
|
|
26
|
-
$
|
|
91
|
+
$page.form = void 0;
|
|
27
92
|
const form = e.target;
|
|
28
93
|
const button = e.submitter;
|
|
29
94
|
let action = form.action || document.URL;
|
|
@@ -33,25 +98,30 @@ async function formSubmit(e) {
|
|
|
33
98
|
method: "POST",
|
|
34
99
|
mode: "cors",
|
|
35
100
|
credentials: "same-origin",
|
|
36
|
-
headers: { "
|
|
101
|
+
headers: { "X-Torpor-Form-Submit": "" }
|
|
37
102
|
};
|
|
38
103
|
const response = await fetch(action, options);
|
|
39
|
-
if (response.status === 200 && response.headers.has("
|
|
40
|
-
const
|
|
104
|
+
if (response.status === 200 && response.headers.has("Content-Disposition") && /attachment;\s*filename="(.+?)"/.test(response.headers.get("Content-Disposition"))) {
|
|
105
|
+
const downloadFileName = response.headers.get("Content-Disposition")?.match(/attachment;\s*filename="(.+?)"/)[1] ?? "file";
|
|
106
|
+
const downloadLink = document.createElement("a");
|
|
107
|
+
downloadLink.href = window.URL.createObjectURL(await response.blob());
|
|
108
|
+
downloadLink.download = downloadFileName;
|
|
109
|
+
downloadLink.click();
|
|
110
|
+
} else if (response.status === 200 && response.headers.has("X-Torpor-Form-Redirect")) {
|
|
111
|
+
const redirect = response.headers.get("Location");
|
|
41
112
|
await load(redirect);
|
|
42
113
|
window.location.href = redirect;
|
|
43
114
|
} else if (response.status >= 200 && response.status <= 299 || response.status >= 400 && response.status <= 499) {
|
|
44
|
-
if (response.headers.get("
|
|
115
|
+
if (response.headers.get("Content-Type") === "application/json") $page.form = await response.json();
|
|
45
116
|
await reload();
|
|
46
117
|
}
|
|
47
118
|
}
|
|
48
|
-
|
|
49
119
|
//#endregion
|
|
50
120
|
//#region src/nav/loadData.ts
|
|
51
121
|
async function loadData(handler, params, path, query, newLayoutStack, clientEndPoint, serverEndPoint, prefetch = false) {
|
|
52
122
|
let data = {};
|
|
53
123
|
if (handler.layouts) {
|
|
54
|
-
let layoutStack =
|
|
124
|
+
let layoutStack = client.layoutStack;
|
|
55
125
|
for (let [i, layout] of handler.layouts.entries()) {
|
|
56
126
|
let layoutPath = layout.path;
|
|
57
127
|
for (let key in params) layoutPath = layoutPath.replace(`[${key}]`, params[key]);
|
|
@@ -78,7 +148,7 @@ async function loadData(handler, params, path, query, newLayoutStack, clientEndP
|
|
|
78
148
|
return data;
|
|
79
149
|
}
|
|
80
150
|
async function loadClientAndServerData(data, location, query, params, clientEndPoint, serverEndPoint) {
|
|
81
|
-
let prefetchedData =
|
|
151
|
+
let prefetchedData = client.prefetchedData;
|
|
82
152
|
if (clientEndPoint?.load) {
|
|
83
153
|
const clientKey = location + (query.size > 0 ? `?${query}` : "");
|
|
84
154
|
if (prefetchedData[clientKey]) Object.assign(data, prefetchedData[clientKey]);
|
|
@@ -121,7 +191,6 @@ function buildClientParams(url, params, data) {
|
|
|
121
191
|
data
|
|
122
192
|
};
|
|
123
193
|
}
|
|
124
|
-
|
|
125
194
|
//#endregion
|
|
126
195
|
//#region src/nav/navigate.ts
|
|
127
196
|
async function navigate(url, withHydration = false) {
|
|
@@ -132,17 +201,17 @@ async function navigate(url, withHydration = false) {
|
|
|
132
201
|
}
|
|
133
202
|
const path = url.pathname;
|
|
134
203
|
const query = url.searchParams;
|
|
135
|
-
const route =
|
|
204
|
+
const route = client.router.match(path, query);
|
|
136
205
|
if (!route) {
|
|
137
206
|
console.log("404");
|
|
138
207
|
return false;
|
|
139
208
|
}
|
|
140
|
-
$
|
|
209
|
+
$page.url = url;
|
|
141
210
|
if (path.endsWith("/_error")) {
|
|
142
|
-
$
|
|
143
|
-
$
|
|
211
|
+
$page.status = parseInt(query.get("status") ?? "404");
|
|
212
|
+
$page.error = { message: query.get("message") ?? "" };
|
|
144
213
|
window.history.replaceState({}, "", url.toString().split("?")[0]);
|
|
145
|
-
} else $
|
|
214
|
+
} else $page.status = 200;
|
|
146
215
|
const handler = route.handler;
|
|
147
216
|
const params = route.params || {};
|
|
148
217
|
const clientEndPoint = (await handler.endPoint()).default;
|
|
@@ -157,54 +226,63 @@ async function navigate(url, withHydration = false) {
|
|
|
157
226
|
let form;
|
|
158
227
|
if (formInput) {
|
|
159
228
|
form = JSON.parse(formInput.value);
|
|
160
|
-
$
|
|
229
|
+
$page.form = form;
|
|
161
230
|
formInput.remove();
|
|
162
231
|
}
|
|
163
232
|
let $props = { data };
|
|
164
|
-
$props.form = $
|
|
233
|
+
$props.form = $page.form;
|
|
165
234
|
const $context = { TorporBuildContext: { onformsubmit: formSubmit } };
|
|
166
|
-
|
|
235
|
+
client.layoutStack.push({
|
|
167
236
|
path: route.handler.path,
|
|
168
237
|
data: {},
|
|
169
238
|
reuse: false,
|
|
170
239
|
slotRegion: null
|
|
171
240
|
});
|
|
172
|
-
|
|
173
|
-
let layoutStack =
|
|
241
|
+
client.layoutStack = newLayoutStack;
|
|
242
|
+
let layoutStack = client.layoutStack;
|
|
174
243
|
let component = clientEndPoint.component;
|
|
175
244
|
let slots = void 0;
|
|
245
|
+
let reused = false;
|
|
246
|
+
let reusedIndex = -1;
|
|
176
247
|
if (handler.layouts) {
|
|
177
248
|
let slotFunctions = [];
|
|
178
|
-
slotFunctions[handler.layouts.length] = function clientComponent(parent
|
|
249
|
+
slotFunctions[handler.layouts.length] = function clientComponent(parent, anchor) {
|
|
179
250
|
let i = layoutStack.length - 1;
|
|
180
|
-
layoutStack[i].slotRegion = fillLayoutSlot(clientEndPoint.component, slotFunctions[i + 1], parent
|
|
251
|
+
layoutStack[i].slotRegion = fillLayoutSlot(clientEndPoint.component, slotFunctions[i + 1], parent, anchor, $props, $context);
|
|
181
252
|
};
|
|
182
253
|
for (let i = handler.layouts.length - 1; i >= 0; i--) {
|
|
183
254
|
const layoutEndPoint = (await handler.layouts[i].endPoint())?.default;
|
|
184
255
|
if (layoutEndPoint?.component) if (layoutStack[i].reuse) {
|
|
185
|
-
parent = layoutStack[i].slotRegion.startNode.parentNode;
|
|
186
|
-
clearLayoutSlot(layoutStack[i].slotRegion);
|
|
187
256
|
component = slotFunctions[i + 1];
|
|
257
|
+
reusedIndex = i;
|
|
258
|
+
reused = true;
|
|
188
259
|
break;
|
|
189
260
|
} else if (i === 0) {
|
|
190
261
|
component = layoutEndPoint.component;
|
|
191
262
|
slots = { _: slotFunctions[i + 1] };
|
|
192
|
-
} else slotFunctions[i] = function layoutComponent(parent
|
|
193
|
-
layoutStack[i - 1].slotRegion = fillLayoutSlot(layoutEndPoint.component, slotFunctions[i + 1], parent
|
|
263
|
+
} else slotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {
|
|
264
|
+
layoutStack[i - 1].slotRegion = fillLayoutSlot(layoutEndPoint.component, slotFunctions[i + 1], parent, anchor, $props, $context);
|
|
194
265
|
};
|
|
195
266
|
}
|
|
196
267
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
268
|
+
try {
|
|
269
|
+
if (reused) {
|
|
270
|
+
const slotRegion = layoutStack[reusedIndex].slotRegion;
|
|
271
|
+
parent = slotRegion.startNode.parentNode;
|
|
272
|
+
clearLayoutSlot(slotRegion);
|
|
273
|
+
component(parent, null);
|
|
274
|
+
} else if (withHydration) hydrate(parent, component, $props, slots);
|
|
275
|
+
else {
|
|
276
|
+
unmount(parent);
|
|
277
|
+
mount(parent, component, $props, slots);
|
|
278
|
+
}
|
|
200
279
|
} catch (error) {
|
|
201
280
|
parent.innerHTML = "<span style=\"color: red\">Script syntax error</span><p>" + error + "</p>";
|
|
202
281
|
console.log(error);
|
|
203
282
|
}
|
|
204
|
-
|
|
283
|
+
client.prefetchedData = {};
|
|
205
284
|
return true;
|
|
206
285
|
}
|
|
207
|
-
|
|
208
286
|
//#endregion
|
|
209
287
|
//#region src/nav/load.ts
|
|
210
288
|
/**
|
|
@@ -213,7 +291,7 @@ async function navigate(url, withHydration = false) {
|
|
|
213
291
|
async function load(path) {
|
|
214
292
|
return await navigate(new URL(path, document.location.href));
|
|
215
293
|
}
|
|
216
|
-
|
|
217
294
|
//#endregion
|
|
218
|
-
export { load, reload };
|
|
295
|
+
export { load, makeApi, reload, route };
|
|
296
|
+
|
|
219
297
|
//# sourceMappingURL=nav.mjs.map
|
package/dist/nav.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.mjs","names":["CLIENT_SYMBOL: unique symbol","client: ClientState","client","layoutEndPoint: PageEndPoint | undefined","layoutServerEndPoint: PageServerEndPoint | undefined","client","clientEndPoint: PageEndPoint | undefined","serverEndPoint: PageServerEndPoint | undefined","newLayoutStack: LayoutPath[]","form: Record<string, string> | undefined","$props: Record<string, any>","$page","slots: Record<string, SlotRender> | undefined","slotFunctions: SlotRender[]","parent","layoutEndPoint: PageEndPoint | undefined","$context"],"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 (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 } 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\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// Set the parent to add the new content to (from the old\n\t\t\t\t\t// content), clear the range under this point, and set the\n\t\t\t\t\t// component to the slot function within this layout\n\t\t\t\t\tparent = layoutStack[i].slotRegion.startNode.parentNode as HTMLElement;\n\t\t\t\t\tclearLayoutSlot(layoutStack[i].slotRegion);\n\t\t\t\t\tcomponent = slotFunctions[i + 1];\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\tif (withHydration) {\n\t\thydrate(parent, component, $props, slots);\n\t} else {\n\t\ttry {\n\t\t\tmount(parent, component, $props, slots);\n\t\t} catch (error) {\n\t\t\t// TODO: Show a proper Error component\n\t\t\tparent.innerHTML = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\t\tconsole.log(error);\n\t\t}\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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"}
|
|
1
|
+
{"version":3,"file":"nav.mjs","names":[],"sources":["../src/nav/route.ts","../src/nav/api.ts","../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 { ExactRouteParams, ParseRouteParams } from \"../types/ParseRouteParams\";\n\n/**\n * Builds a route path in a type-safe manner, filling in dynamic segments, e.g.\n * `route(\"/posts/[id]\", { id: 5 })` returns `/posts/5`. The params object is\n * checked for exact keys: missing or unknown params error at compile time.\n *\n * NOTE: The rest args conditional must stay INLINE — routing it through a type\n * alias defeats `Params` inference, and excess keys stop being checked\n * @param path The route path\n * @param params The route params, required if the path has dynamic segments\n * @returns The path with the params filled in, URI-encoded\n */\nexport default function route<\n\tRoute extends string,\n\tParams extends ParseRouteParams<Route> = ParseRouteParams<Route>,\n>(\n\tpath: Route,\n\t...args: string extends Route\n\t\t? [params?: Record<string, string>]\n\t\t: keyof ParseRouteParams<Route> extends never\n\t\t\t? []\n\t\t\t: [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]\n): string {\n\tconst params = ((args as unknown[])[0] ?? {}) as Record<string, string>;\n\treturn path.replace(/\\[(\\.\\.\\.)?([^\\]]+)\\]/g, (_, splat: string | undefined, name: string) => {\n\t\tconst value = params[name];\n\t\tif (value === undefined) {\n\t\t\tthrow new Error(`Missing param '${name}' for route '${path}'`);\n\t\t}\n\t\treturn splat\n\t\t\t? value\n\t\t\t\t\t.split(\"/\")\n\t\t\t\t\t.map((segment) => encodeURIComponent(segment))\n\t\t\t\t\t.join(\"/\")\n\t\t\t: encodeURIComponent(String(value));\n\t});\n}\n","import type { ExactRouteParams, ParseRouteParams } from \"../types/ParseRouteParams\";\nimport type TypedResponse from \"../response/TypedResponse\";\nimport route from \"./route\";\n\n/**\n * The value a client call resolves to: the endpoint's typed JSON body when it\n * returns a typed response, or the raw Response when the body isn't JSON.\n * Untyped endpoints resolve to `unknown`.\n */\ntype ApiResult<Handler> = Handler extends (event: any) => any\n\t? Awaited<ReturnType<Handler>> extends TypedResponse<infer Body>\n\t\t? Body | Response\n\t\t: unknown\n\t: never;\n\n/**\n * The body a client call accepts: the endpoint event's annotated JSON body\n * type (from `ServerLoadEvent<Route, Body>`), or a loose value when the\n * handler doesn't annotate one.\n */\ntype ApiRequestBody<Handler> = Handler extends (event: infer Event) => any\n\t? Event extends { json: () => Promise<infer Body> }\n\t\t? unknown extends Body\n\t\t\t? BodyInit | object\n\t\t\t: Body\n\t\t: BodyInit | object\n\t: BodyInit | object;\n\n/** The callable methods of an endpoint, one per handler it defines. */\nexport type ApiMethods<Endpoint extends object> = {\n\t[Method in string & keyof Endpoint as NonNullable<Endpoint[Method]> extends (event: any) => any\n\t\t? Method\n\t\t: never]: (\n\t\tbody?: ApiRequestBody<NonNullable<Endpoint[Method]>>,\n\t\tinit?: RequestInit,\n\t) => Promise<ApiResult<NonNullable<Endpoint[Method]>>>;\n};\n\nconst METHODS = [\"get\", \"post\", \"patch\", \"put\", \"del\", \"options\", \"head\"] as const;\n\n/**\n * Creates typed client callers for a `+server` endpoint, e.g.\n *\n * ```ts\n * import type endpoint from \"@/routes/api/posts/[id]/+server\";\n * const post = makeApi<\"/api/posts/[id]\", typeof endpoint>(\"/api/posts/[id]\");\n * const data = await post.get(); // typed from the endpoint's responses\n * ```\n *\n * Method names match the endpoint's handlers (`del` for DELETE). Params are\n * filled in and URI-encoded from the route path, and JSON responses are\n * parsed; non-JSON responses resolve to the raw Response. Failed requests\n * (non-2xx) throw.\n *\n * @param path The endpoint's route path\n * @param params The route params, required if the path has dynamic segments\n * @returns An object with one caller per handler defined by the endpoint\n */\nexport default function makeApi<\n\tRoute extends string,\n\tEndpoint extends object,\n\tParams extends ParseRouteParams<Route> = ParseRouteParams<Route>,\n>(\n\tpath: Route,\n\t// NOTE: The rest args conditional must stay INLINE — routing it through a\n\t// type alias defeats `Params` inference, and excess keys stop being checked\n\t...args: string extends Route\n\t\t? [params?: Record<string, string>]\n\t\t: keyof ParseRouteParams<Route> extends never\n\t\t\t? []\n\t\t\t: [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]\n): ApiMethods<Endpoint> {\n\tconst url = route(path as string, ...(args as [params?: Record<string, string>]));\n\tconst api: Record<string, (body?: unknown, init?: RequestInit) => Promise<unknown>> = {};\n\tfor (const method of METHODS) {\n\t\tapi[method] = (body, init) => request(method, url, body, init);\n\t}\n\treturn api as ApiMethods<Endpoint>;\n}\n\nasync function request(\n\tmethod: string,\n\turl: string,\n\tbody: unknown,\n\tinit?: RequestInit,\n): Promise<unknown> {\n\t// `del` is the endpoint handler name for DELETE requests\n\tconst httpMethod = method === \"del\" ? \"delete\" : method;\n\tconst options: RequestInit = { method: httpMethod.toUpperCase(), ...init };\n\tif (body !== undefined && options.body === undefined && method !== \"get\" && method !== \"head\") {\n\t\tif (typeof body === \"object\") {\n\t\t\toptions.headers ??= { \"Content-Type\": \"application/json\" };\n\t\t\toptions.body = JSON.stringify(body);\n\t\t} else {\n\t\t\toptions.body = body as BodyInit;\n\t\t}\n\t}\n\tconst response = await fetch(url, options);\n\tif (!response.ok) {\n\t\tthrow new Error(`API ${method.toUpperCase()} ${url} failed: ${response.status}`);\n\t}\n\tconst contentType = response.headers.get(\"Content-Type\") ?? \"\";\n\treturn contentType.includes(\"application/json\") ? await response.json() : response;\n}\n","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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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { v as SitePlugin } from "./Site-CMa153FA.mjs";
|
|
2
|
+
import { t as StandardSchemaV1 } from "./StandardSchema-D28L8_zZ.mjs";
|
|
3
|
+
//#region src/openapi/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A JSON Schema object, as produced by a schema library's converter (e.g.
|
|
6
|
+
* `z.toJSONSchema`, valibot's `toJsonSchema`, arktype's `toJsonSchema`).
|
|
7
|
+
*/
|
|
8
|
+
type JsonSchema = Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Converts a Standard Schema (zod, valibot, arktype, ...) into a JSON Schema
|
|
11
|
+
* object, for inclusion in an OpenAPI document. Supplied to the openApi
|
|
12
|
+
* plugin, since each schema library has its own conversion function:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* // zod
|
|
16
|
+
* openApi({ toJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: "input" }) })
|
|
17
|
+
* // arktype
|
|
18
|
+
* openApi({ toJsonSchema: (s) => (s as Type).toJsonSchema() })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
type ToJsonSchema = (schema: StandardSchemaV1) => JsonSchema;
|
|
22
|
+
/**
|
|
23
|
+
* Options for the openApi plugin.
|
|
24
|
+
*/
|
|
25
|
+
type OpenApiPluginOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* The path to serve the OpenAPI document at. Defaults to `/openapi.json`.
|
|
28
|
+
*/
|
|
29
|
+
path?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The path to serve interactive API docs at, or `false` to disable the
|
|
32
|
+
* docs page. Defaults to `/docs`.
|
|
33
|
+
*/
|
|
34
|
+
docs?: string | false;
|
|
35
|
+
/**
|
|
36
|
+
* The API title for the document's `info` section. Defaults to `"API"`.
|
|
37
|
+
*/
|
|
38
|
+
title?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The API version for the document's `info` section. Defaults to
|
|
41
|
+
* `"1.0.0"`.
|
|
42
|
+
*/
|
|
43
|
+
version?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Converts an endpoint's schemas into JSON Schema. Required for endpoints
|
|
46
|
+
* that declare schemas; endpoints without schemas are documented with
|
|
47
|
+
* their paths and methods only.
|
|
48
|
+
*/
|
|
49
|
+
toJsonSchema?: ToJsonSchema;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* OpenApi options with defaults applied. Created by the openApi plugin and
|
|
53
|
+
* consumed by the document builder.
|
|
54
|
+
*/
|
|
55
|
+
type ResolvedOpenApiOptions = {
|
|
56
|
+
path: string;
|
|
57
|
+
/** Undefined when the docs page is disabled */
|
|
58
|
+
docs: string | undefined;
|
|
59
|
+
title: string;
|
|
60
|
+
version: string;
|
|
61
|
+
toJsonSchema: ToJsonSchema | undefined;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A ServerEndPoint shape, loosely typed so that endpoint modules loaded from
|
|
65
|
+
* anywhere can be passed to the document builder.
|
|
66
|
+
*/
|
|
67
|
+
type OpenApiEndPoint = {
|
|
68
|
+
schema?: Record<string, StandardSchemaV1 | undefined> | undefined;
|
|
69
|
+
[method: string]: unknown;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* A single API endpoint to include in an OpenAPI document: its route path
|
|
73
|
+
* (with `[param]` segments) and its default-exported endpoint object.
|
|
74
|
+
*/
|
|
75
|
+
type OpenApiRouteEntry = {
|
|
76
|
+
path: string;
|
|
77
|
+
endPoint: OpenApiEndPoint;
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/openapi/document.d.ts
|
|
81
|
+
/**
|
|
82
|
+
* Builds an OpenAPI 3.1 document from a list of +server endpoints.
|
|
83
|
+
*
|
|
84
|
+
* Each endpoint's route path (`/api/posts/[id]`) is converted to OpenAPI
|
|
85
|
+
* syntax (`/api/posts/{id}`); its `params` schema becomes path parameters,
|
|
86
|
+
* its `get`/`head` schema becomes query parameters, and its `post`/`patch`/
|
|
87
|
+
* `put`/`del`/`options` schema becomes a json request body. Responses are
|
|
88
|
+
* stubbed: a `200` for every handler, plus a `422` when the handler declares
|
|
89
|
+
* an input schema (validation errors are returned automatically).
|
|
90
|
+
*
|
|
91
|
+
* Throws if any endpoint declares a schema but the options have no
|
|
92
|
+
* `toJsonSchema` converter.
|
|
93
|
+
*/
|
|
94
|
+
declare function buildOpenApiDocument(entries: OpenApiRouteEntry[], options: OpenApiPluginOptions): Record<string, unknown>;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/openapi/docsHtml.d.ts
|
|
97
|
+
/**
|
|
98
|
+
* A standalone HTML page loading Swagger UI from a CDN, pointed at the
|
|
99
|
+
* OpenAPI document endpoint.
|
|
100
|
+
*/
|
|
101
|
+
declare function openApiDocsHtml(docPath: string): string;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/openapi/plugin.d.ts
|
|
104
|
+
/**
|
|
105
|
+
* The `site.pluginState` key the openApi plugin stores its resolved options
|
|
106
|
+
* under. A registry symbol (`Symbol.for`), so that the user's config, the
|
|
107
|
+
* manifest plugin and the generated runtime code all resolve to the same
|
|
108
|
+
* key even when they are loaded as separate module instances.
|
|
109
|
+
*/
|
|
110
|
+
declare const OPEN_API_STATE_KEY: symbol;
|
|
111
|
+
/**
|
|
112
|
+
* Serves an OpenAPI 3.1 document for the site's `+server` endpoints, plus an
|
|
113
|
+
* optional interactive docs page.
|
|
114
|
+
*
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { Site } from "@torpor/build";
|
|
117
|
+
* import { openApi } from "@torpor/build/openapi";
|
|
118
|
+
* import { z } from "zod";
|
|
119
|
+
*
|
|
120
|
+
* const site = new Site();
|
|
121
|
+
* site.addRouteFolder("src/routes");
|
|
122
|
+
* site.plugins = [
|
|
123
|
+
* openApi({
|
|
124
|
+
* title: "My API",
|
|
125
|
+
* toJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: "input" }),
|
|
126
|
+
* }),
|
|
127
|
+
* ];
|
|
128
|
+
* export default site;
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* The document is served at `/openapi.json` (configurable with `path`) and
|
|
132
|
+
* the docs page at `/docs` (set `docs: false` to disable). Endpoints are
|
|
133
|
+
* picked up from every `+server.ts` route: its `params` schema becomes path
|
|
134
|
+
* parameters, `get`/`head` schemas become query parameters, and
|
|
135
|
+
* `post`/`patch`/`put`/`del`/`options` schemas become request bodies.
|
|
136
|
+
*
|
|
137
|
+
* The resolved options are stored in `site.pluginState` under
|
|
138
|
+
* `OPEN_API_STATE_KEY`, which the manifest plugin reads at build time to
|
|
139
|
+
* generate the document endpoint.
|
|
140
|
+
*/
|
|
141
|
+
declare function openApi(options?: OpenApiPluginOptions): SitePlugin;
|
|
142
|
+
//#endregion
|
|
143
|
+
export { type JsonSchema, OPEN_API_STATE_KEY, type OpenApiEndPoint, type OpenApiPluginOptions, type OpenApiRouteEntry, type ResolvedOpenApiOptions, type ToJsonSchema, buildOpenApiDocument, openApi, openApiDocsHtml };
|
|
144
|
+
//# sourceMappingURL=openapi.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.mts","names":[],"sources":["../src/openapi/types.ts","../src/openapi/document.ts","../src/openapi/docsHtml.ts","../src/openapi/plugin.ts"],"mappings":";;;;;;;KAMY,aAAa;;;;;;;;;;;;;KAcb,gBAAgB,QAAQ,qBAAqB;;;;KAK7C;;;;EAIX;;;;;EAKA;;;;EAIA;;;;;EAKA;;;;;;EAMA,eAAe;;;;;;KAOJ;EACX;;EAEA;EACA;EACA;EACA,cAAc;;;;;;KAOH;EACX,SAAS,eAAe;;;;;;;KAQb;EACX;EACA,UAAU;;;;;;;;;;;;;;;;;iBCvDa,qBACvB,SAAS,qBACT,SAAS,uBACP;;;;;;;iBCxBqB,gBAAgB;;;;;;;;;cCM3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCG,QAAQ,UAAS,uBAA4B"}
|
package/dist/openapi.mjs
ADDED