@qwik.dev/router 2.0.0-beta.4 → 2.0.0-beta.41
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/adapters/static/vite.d.ts +1 -1
- package/lib/adapters/azure-swa/vite/index.d.ts +2 -2
- package/lib/adapters/azure-swa/vite/index.mjs +44 -58
- package/lib/adapters/bun-server/vite/index.d.ts +2 -2
- package/lib/adapters/bun-server/vite/index.mjs +16 -23
- package/lib/adapters/cloud-run/vite/index.d.ts +2 -2
- package/lib/adapters/cloud-run/vite/index.mjs +15 -20
- package/lib/adapters/cloudflare-pages/vite/index.d.ts +2 -2
- package/lib/adapters/cloudflare-pages/vite/index.mjs +45 -77
- package/lib/adapters/deno-server/vite/index.d.ts +2 -2
- package/lib/adapters/deno-server/vite/index.mjs +25 -35
- package/lib/adapters/netlify-edge/vite/index.d.ts +2 -2
- package/lib/adapters/netlify-edge/vite/index.mjs +49 -89
- package/lib/adapters/node-server/vite/index.d.ts +2 -2
- package/lib/adapters/node-server/vite/index.mjs +16 -23
- package/lib/adapters/shared/vite/index.d.ts +9 -22
- package/lib/adapters/shared/vite/index.mjs +280 -333
- package/lib/adapters/ssg/vite/index.d.ts +13 -0
- package/lib/adapters/ssg/vite/index.mjs +13 -0
- package/lib/adapters/vercel-edge/vite/index.d.ts +3 -3
- package/lib/adapters/vercel-edge/vite/index.mjs +73 -80
- package/lib/chunks/dev-preloaded-route-loader.mjs +2 -0
- package/lib/chunks/format.mjs +24 -0
- package/lib/chunks/fs.mjs +140 -0
- package/lib/chunks/head.qwik.mjs +1202 -0
- package/lib/chunks/http-error.qwik.mjs +27 -0
- package/lib/chunks/pathname.mjs +57 -0
- package/lib/chunks/request-body-limit.mjs +27 -0
- package/lib/chunks/request-path.mjs +49 -0
- package/lib/chunks/routes.mjs +26 -0
- package/lib/chunks/server-error.mjs +16 -0
- package/lib/chunks/system.mjs +265 -0
- package/lib/chunks/url.mjs +59 -0
- package/lib/chunks/user-response.mjs +1730 -0
- package/lib/chunks/worker-thread.mjs +264 -0
- package/lib/index.d.ts +631 -174
- package/lib/index.qwik.mjs +1656 -1964
- package/lib/middleware/aws-lambda/index.d.ts +15 -16
- package/lib/middleware/aws-lambda/index.mjs +45 -54
- package/lib/middleware/azure-swa/index.mjs +85 -295
- package/lib/middleware/bun/index.d.ts +20 -5
- package/lib/middleware/bun/index.mjs +119 -205
- package/lib/middleware/cloudflare-pages/index.mjs +88 -105
- package/lib/middleware/deno/index.d.ts +22 -5
- package/lib/middleware/deno/index.mjs +105 -192
- package/lib/middleware/firebase/index.mjs +22 -35
- package/lib/middleware/netlify-edge/index.mjs +63 -81
- package/lib/middleware/node/index.d.ts +17 -5
- package/lib/middleware/node/index.mjs +299 -273
- package/lib/middleware/request-handler/index.d.ts +206 -99
- package/lib/middleware/request-handler/index.mjs +105 -1648
- package/lib/middleware/vercel-edge/index.mjs +76 -101
- package/lib/modules.d.ts +11 -16
- package/lib/service-worker/index.mjs +13 -0
- package/lib/{static → ssg}/index.d.ts +46 -22
- package/lib/ssg/index.mjs +257 -0
- package/lib/vite/index.d.ts +58 -10
- package/lib/vite/index.mjs +2471 -27446
- package/modules.d.ts +11 -16
- package/package.json +62 -70
- package/ssg.d.ts +2 -0
- package/static.d.ts +1 -1
- package/lib/adapters/azure-swa/vite/index.cjs +0 -96
- package/lib/adapters/bun-server/vite/index.cjs +0 -50
- package/lib/adapters/cloud-run/vite/index.cjs +0 -47
- package/lib/adapters/cloudflare-pages/vite/index.cjs +0 -115
- package/lib/adapters/deno-server/vite/index.cjs +0 -62
- package/lib/adapters/netlify-edge/vite/index.cjs +0 -129
- package/lib/adapters/node-server/vite/index.cjs +0 -50
- package/lib/adapters/shared/vite/index.cjs +0 -378
- package/lib/adapters/static/vite/index.cjs +0 -368
- package/lib/adapters/static/vite/index.d.ts +0 -10
- package/lib/adapters/static/vite/index.mjs +0 -331
- package/lib/adapters/vercel-edge/vite/index.cjs +0 -118
- package/lib/index.qwik.cjs +0 -2022
- package/lib/middleware/node/index.cjs +0 -314
- package/lib/middleware/request-handler/index.cjs +0 -1691
- package/lib/service-worker.cjs +0 -17
- package/lib/service-worker.mjs +0 -15
- package/lib/static/deno.mjs +0 -8
- package/lib/static/index.cjs +0 -67
- package/lib/static/index.mjs +0 -48
- package/lib/static/node.cjs +0 -1126
- package/lib/static/node.mjs +0 -1088
- package/lib/vite/index.cjs +0 -27525
- package/middleware/request-handler/generated/not-found-paths.ts +0 -7
- package/middleware/request-handler/generated/static-paths.ts +0 -35
|
@@ -0,0 +1,1730 @@
|
|
|
1
|
+
import { t as ensureSlash } from "./pathname.mjs";
|
|
2
|
+
import { C as getRouteLoaderValues, D as loadRouteLoader, G as QACTION_KEY, J as Q_ROUTE, K as QDATA_KEY, N as setRouteLoaders, O as matchesRouteLoaderId, S as getRouteLoaderResponse, Y as _asyncRequestStore, a as isPromise, f as FULLPATH_HEADER, h as clearRouteLoaderData, k as resolveRouteLoaderByHash, q as QFN_KEY, r as resolveRouteConfig, v as getLoaderRequestEvent, x as getRouteLoaderCtx } from "./head.qwik.mjs";
|
|
3
|
+
import { a as QLoaderId, c as resolveValidInternalFullPathname, i as QActionId, s as recognizeRequest, t as IsQAction, u as trimRecognizedInternalPathname } from "./request-path.mjs";
|
|
4
|
+
import { i as RedirectMessage, n as throwIfControlFlowSignal, r as AbortMessage, t as ServerError } from "./server-error.mjs";
|
|
5
|
+
import { inlinedQrl, isDev } from "@qwik.dev/core";
|
|
6
|
+
import { _deserialize, _serialize, _verifySerializable, isDev as isDev$1 } from "@qwik.dev/core/internal";
|
|
7
|
+
/**
|
|
8
|
+
* FNV-1a 32-bit hash for generating eTags from response data. Cheap to compute, sufficient
|
|
9
|
+
* collision resistance for cache validation (not cryptographic).
|
|
10
|
+
*/ function hash(str) {
|
|
11
|
+
let hash = 2166136261;
|
|
12
|
+
for (let i = 0; i < str.length; i++) {
|
|
13
|
+
hash ^= str.charCodeAt(i);
|
|
14
|
+
hash = hash * 16777619 | 0;
|
|
15
|
+
}
|
|
16
|
+
return (hash >>> 0).toString(36);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Normalize an ETag header value (or a single `If-None-Match` entry) into a canonical form so byte
|
|
20
|
+
* equality compares the same logical tag. Strips weak ETag syntax (`W/"..."`), quotes, and any
|
|
21
|
+
* character outside RFC 7232's `etagc` grammar (printable ASCII except DQUOTE — no escape mechanism
|
|
22
|
+
* exists, and non-ASCII bytes are mangled by many HTTP stacks even though `obs-text` technically
|
|
23
|
+
* permits them). Idempotent.
|
|
24
|
+
*/ function normalizeETag(raw) {
|
|
25
|
+
let tag = raw.trim();
|
|
26
|
+
if (tag.endsWith("\"") && tag.startsWith("W/\"")) tag = tag.slice(3);
|
|
27
|
+
tag = tag.replace(/[^!#-~]/g, "");
|
|
28
|
+
return tag;
|
|
29
|
+
}
|
|
30
|
+
function setETagHeader(requestEv, normalizedETag) {
|
|
31
|
+
const headerETag = `"${normalizedETag}"`;
|
|
32
|
+
requestEv.headers.set("ETag", headerETag);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Set the strong ETag header on the response and check the request's `If-None-Match` against it.
|
|
36
|
+
* The eTag argument must already be normalized. Returns true if a 304 was sent (caller should stop
|
|
37
|
+
* processing).
|
|
38
|
+
*/ function performETagMatch(requestEv, normalizedETag) {
|
|
39
|
+
setETagHeader(requestEv, normalizedETag);
|
|
40
|
+
const ifNoneMatch = requestEv.request.headers.get("If-None-Match");
|
|
41
|
+
if (ifNoneMatch) {
|
|
42
|
+
let found = ifNoneMatch.trim() === "*";
|
|
43
|
+
if (!found) {
|
|
44
|
+
for (const entry of ifNoneMatch.split(",")) if (normalizeETag(entry) === normalizedETag) {
|
|
45
|
+
found = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (found) {
|
|
50
|
+
requestEv.status(304);
|
|
51
|
+
requestEv.send(304, "");
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
function createCacheControl(cacheControl) {
|
|
58
|
+
const controls = [];
|
|
59
|
+
if (cacheControl === "day") cacheControl = 86400;
|
|
60
|
+
else if (cacheControl === "week") cacheControl = 604800;
|
|
61
|
+
else if (cacheControl === "month") cacheControl = 2592e3;
|
|
62
|
+
else if (cacheControl === "year") cacheControl = 31536e3;
|
|
63
|
+
else if (cacheControl === "private") cacheControl = {
|
|
64
|
+
private: true,
|
|
65
|
+
noCache: true
|
|
66
|
+
};
|
|
67
|
+
else if (cacheControl === "immutable") cacheControl = {
|
|
68
|
+
public: true,
|
|
69
|
+
immutable: true,
|
|
70
|
+
maxAge: 31536e3
|
|
71
|
+
};
|
|
72
|
+
else if (cacheControl === "no-cache") cacheControl = { noCache: true };
|
|
73
|
+
if (typeof cacheControl === "number") cacheControl = {
|
|
74
|
+
maxAge: cacheControl,
|
|
75
|
+
sMaxAge: cacheControl
|
|
76
|
+
};
|
|
77
|
+
if (cacheControl.immutable) controls.push("immutable");
|
|
78
|
+
if (cacheControl.maxAge) controls.push(`max-age=${cacheControl.maxAge}`);
|
|
79
|
+
if (cacheControl.sMaxAge) controls.push(`s-maxage=${cacheControl.sMaxAge}`);
|
|
80
|
+
if (cacheControl.noStore) controls.push("no-store");
|
|
81
|
+
if (cacheControl.noCache) controls.push("no-cache");
|
|
82
|
+
if (cacheControl.private) controls.push("private");
|
|
83
|
+
if (cacheControl.public) controls.push("public");
|
|
84
|
+
if (cacheControl.staleWhileRevalidate) controls.push(`stale-while-revalidate=${cacheControl.staleWhileRevalidate}`);
|
|
85
|
+
if (cacheControl.staleIfError) controls.push(`stale-if-error=${cacheControl.staleIfError}`);
|
|
86
|
+
return controls.join(", ");
|
|
87
|
+
}
|
|
88
|
+
var SAMESITE = {
|
|
89
|
+
lax: "Lax",
|
|
90
|
+
Lax: "Lax",
|
|
91
|
+
None: "None",
|
|
92
|
+
none: "None",
|
|
93
|
+
strict: "Strict",
|
|
94
|
+
Strict: "Strict"
|
|
95
|
+
};
|
|
96
|
+
var UNIT = {
|
|
97
|
+
seconds: 1,
|
|
98
|
+
minutes: 60,
|
|
99
|
+
hours: 3600,
|
|
100
|
+
days: 86400,
|
|
101
|
+
weeks: 604800
|
|
102
|
+
};
|
|
103
|
+
function tryDecodeUriComponent(str) {
|
|
104
|
+
try {
|
|
105
|
+
return decodeURIComponent(str);
|
|
106
|
+
} catch {
|
|
107
|
+
return str;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
var parseCookieString = (cookieString) => {
|
|
111
|
+
const cookie = {};
|
|
112
|
+
if (typeof cookieString === "string" && cookieString !== "") {
|
|
113
|
+
const cookieSegments = cookieString.split(";");
|
|
114
|
+
for (const cookieSegment of cookieSegments) {
|
|
115
|
+
const separatorIndex = cookieSegment.indexOf("=");
|
|
116
|
+
if (separatorIndex !== -1) cookie[tryDecodeUriComponent(cookieSegment.slice(0, separatorIndex).trim())] = tryDecodeUriComponent(cookieSegment.slice(separatorIndex + 1).trim());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return cookie;
|
|
120
|
+
};
|
|
121
|
+
function resolveSameSite(sameSite) {
|
|
122
|
+
if (sameSite === true) return "Strict";
|
|
123
|
+
if (sameSite === false) return "None";
|
|
124
|
+
if (sameSite) return SAMESITE[sameSite];
|
|
125
|
+
}
|
|
126
|
+
var createSetCookieValue = (cookieName, cookieValue, options) => {
|
|
127
|
+
const c = [`${cookieName}=${cookieValue}`];
|
|
128
|
+
if (typeof options.domain === "string") c.push(`Domain=${options.domain}`);
|
|
129
|
+
if (typeof options.maxAge === "number") c.push(`Max-Age=${options.maxAge}`);
|
|
130
|
+
else if (Array.isArray(options.maxAge)) c.push(`Max-Age=${options.maxAge[0] * UNIT[options.maxAge[1]]}`);
|
|
131
|
+
else if (typeof options.expires === "number" || typeof options.expires == "string") c.push(`Expires=${options.expires}`);
|
|
132
|
+
else if (options.expires instanceof Date) c.push(`Expires=${options.expires.toUTCString()}`);
|
|
133
|
+
if (options.httpOnly) c.push("HttpOnly");
|
|
134
|
+
if (typeof options.path === "string") c.push(`Path=${options.path}`);
|
|
135
|
+
const sameSite = resolveSameSite(options.sameSite);
|
|
136
|
+
if (sameSite) c.push(`SameSite=${sameSite}`);
|
|
137
|
+
if (options.secure) c.push("Secure");
|
|
138
|
+
return c.join("; ");
|
|
139
|
+
};
|
|
140
|
+
var REQ_COOKIE = Symbol("request-cookies");
|
|
141
|
+
var RES_COOKIE = Symbol("response-cookies");
|
|
142
|
+
var LIVE_COOKIE = Symbol("live-cookies");
|
|
143
|
+
var Cookie = class {
|
|
144
|
+
[REQ_COOKIE];
|
|
145
|
+
[RES_COOKIE] = {};
|
|
146
|
+
[LIVE_COOKIE] = {};
|
|
147
|
+
appendCounter = 0;
|
|
148
|
+
constructor(cookieString) {
|
|
149
|
+
this[REQ_COOKIE] = parseCookieString(cookieString);
|
|
150
|
+
this[LIVE_COOKIE] = { ...this[REQ_COOKIE] };
|
|
151
|
+
}
|
|
152
|
+
get(cookieName, live = true) {
|
|
153
|
+
const value = this[live ? LIVE_COOKIE : REQ_COOKIE][cookieName];
|
|
154
|
+
if (!value) return null;
|
|
155
|
+
return {
|
|
156
|
+
value,
|
|
157
|
+
json() {
|
|
158
|
+
return JSON.parse(value);
|
|
159
|
+
},
|
|
160
|
+
number() {
|
|
161
|
+
return Number(value);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
getAll(live = true) {
|
|
166
|
+
return Object.keys(this[live ? LIVE_COOKIE : REQ_COOKIE]).reduce((cookies, cookieName) => {
|
|
167
|
+
cookies[cookieName] = this.get(cookieName);
|
|
168
|
+
return cookies;
|
|
169
|
+
}, {});
|
|
170
|
+
}
|
|
171
|
+
has(cookieName, live = true) {
|
|
172
|
+
return !!this[live ? LIVE_COOKIE : REQ_COOKIE][cookieName];
|
|
173
|
+
}
|
|
174
|
+
set(cookieName, cookieValue, options = {}) {
|
|
175
|
+
this[LIVE_COOKIE][cookieName] = typeof cookieValue === "string" ? cookieValue : JSON.stringify(cookieValue);
|
|
176
|
+
const resolvedValue = typeof cookieValue === "string" ? cookieValue : encodeURIComponent(JSON.stringify(cookieValue));
|
|
177
|
+
this[RES_COOKIE][cookieName] = createSetCookieValue(cookieName, resolvedValue, options);
|
|
178
|
+
}
|
|
179
|
+
append(cookieName, cookieValue, options = {}) {
|
|
180
|
+
this[LIVE_COOKIE][cookieName] = typeof cookieValue === "string" ? cookieValue : JSON.stringify(cookieValue);
|
|
181
|
+
const resolvedValue = typeof cookieValue === "string" ? cookieValue : encodeURIComponent(JSON.stringify(cookieValue));
|
|
182
|
+
this[RES_COOKIE][++this.appendCounter] = createSetCookieValue(cookieName, resolvedValue, options);
|
|
183
|
+
}
|
|
184
|
+
delete(name, options) {
|
|
185
|
+
this.set(name, "deleted", {
|
|
186
|
+
...options,
|
|
187
|
+
maxAge: 0
|
|
188
|
+
});
|
|
189
|
+
this[LIVE_COOKIE][name] = null;
|
|
190
|
+
}
|
|
191
|
+
headers() {
|
|
192
|
+
return Object.values(this[RES_COOKIE]);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/** @public */ var mergeHeadersCookies = (headers, cookies) => {
|
|
196
|
+
const cookieHeaders = cookies.headers();
|
|
197
|
+
if (cookieHeaders.length > 0) {
|
|
198
|
+
const newHeaders = new Headers(headers);
|
|
199
|
+
for (const cookie of cookieHeaders) newHeaders.append("Set-Cookie", cookie);
|
|
200
|
+
return newHeaders;
|
|
201
|
+
}
|
|
202
|
+
return headers;
|
|
203
|
+
};
|
|
204
|
+
/** @public */ var RewriteMessage = class extends AbortMessage {
|
|
205
|
+
pathname;
|
|
206
|
+
constructor(pathname) {
|
|
207
|
+
super(), this.pathname = pathname;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var encoder = /*#__PURE__*/ new TextEncoder();
|
|
211
|
+
function getContentType(headers) {
|
|
212
|
+
return (headers.get("content-type")?.split(/[;,]/, 1)[0].trim() ?? "").toLowerCase();
|
|
213
|
+
}
|
|
214
|
+
function isContentType(headers, ...types) {
|
|
215
|
+
const type = getContentType(headers);
|
|
216
|
+
for (let i = 0; i < types.length; i++) if (types[i].toLowerCase() === type) return true;
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
var RequestEvMode = Symbol("RequestEvMode");
|
|
220
|
+
var RequestEvRoute = Symbol("RequestEvRoute");
|
|
221
|
+
var RequestRouteName = "@routeName";
|
|
222
|
+
var RequestEvSharedActionId = "@actionId";
|
|
223
|
+
var RequestEvSharedActionFormData = "@actionFormData";
|
|
224
|
+
var RequestEvSharedNonce = "@nonce";
|
|
225
|
+
var RequestEvIsRewrite = "@rewrite";
|
|
226
|
+
var RequestEvShareServerTiming = "@serverTiming";
|
|
227
|
+
var RequestEvETagCacheKey = "@eTagCacheKey";
|
|
228
|
+
var RequestEvHttpStatusMessage = "@httpStatusMessage";
|
|
229
|
+
function getRequestRoute(requestEv) {
|
|
230
|
+
return requestEv[RequestEvRoute];
|
|
231
|
+
}
|
|
232
|
+
function getRequestMode(requestEv) {
|
|
233
|
+
return requestEv[RequestEvMode];
|
|
234
|
+
}
|
|
235
|
+
var ABORT_INDEX = Number.MAX_SAFE_INTEGER;
|
|
236
|
+
var isDangerousKey = (k) => k === "__proto__" || k === "constructor" || k === "prototype";
|
|
237
|
+
var isArrayIndexKey = (k) => /^(0|[1-9]\d*)$/.test(k);
|
|
238
|
+
var createFormPathNode = () => ({
|
|
239
|
+
children: /* @__PURE__ */ new Map(),
|
|
240
|
+
isArray: true
|
|
241
|
+
});
|
|
242
|
+
var getFormPathTree = (formData) => {
|
|
243
|
+
const root = createFormPathNode();
|
|
244
|
+
for (const [name] of formData) {
|
|
245
|
+
const keys = name.split(".");
|
|
246
|
+
let hasDangerousKey = false;
|
|
247
|
+
for (const key of keys) if (isDangerousKey(key)) {
|
|
248
|
+
hasDangerousKey = true;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (hasDangerousKey) continue;
|
|
252
|
+
let pathNode = root;
|
|
253
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
254
|
+
const key = keys[i];
|
|
255
|
+
if (key.endsWith("[]")) break;
|
|
256
|
+
let childNode = pathNode.children.get(key);
|
|
257
|
+
if (!childNode) {
|
|
258
|
+
childNode = createFormPathNode();
|
|
259
|
+
pathNode.children.set(key, childNode);
|
|
260
|
+
}
|
|
261
|
+
if (!isArrayIndexKey(keys[i + 1])) childNode.isArray = false;
|
|
262
|
+
pathNode = childNode;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return root;
|
|
266
|
+
};
|
|
267
|
+
var formToObj = (formData) => {
|
|
268
|
+
/**
|
|
269
|
+
* Convert FormData to object Handle nested form input using dot notation Handle array input using
|
|
270
|
+
* indexed dot notation (name.0, name.0) or bracket notation (name[]), the later is needed for
|
|
271
|
+
* multiselects Create values object by form data entries
|
|
272
|
+
*/ const values = Object.create(null);
|
|
273
|
+
const pathTree = getFormPathTree(formData);
|
|
274
|
+
for (const [name, value] of formData) {
|
|
275
|
+
const keys = name.split(".");
|
|
276
|
+
let hasDangerousKey = false;
|
|
277
|
+
for (let i = 0; i < keys.length; i++) if (isDangerousKey(keys[i])) {
|
|
278
|
+
hasDangerousKey = true;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
if (hasDangerousKey) continue;
|
|
282
|
+
let object = values;
|
|
283
|
+
let pathNode = pathTree;
|
|
284
|
+
for (let i = 0; i < keys.length; i++) {
|
|
285
|
+
const key = keys[i];
|
|
286
|
+
if (key.endsWith("[]")) {
|
|
287
|
+
const arrayKey = key.slice(0, -2);
|
|
288
|
+
if (isDangerousKey(arrayKey)) break;
|
|
289
|
+
const existingValue = object[arrayKey];
|
|
290
|
+
if (existingValue !== void 0 && !Array.isArray(existingValue)) break;
|
|
291
|
+
object[arrayKey] = existingValue || [];
|
|
292
|
+
object[arrayKey].push(value);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
if (Array.isArray(object) && !isArrayIndexKey(key)) break;
|
|
296
|
+
if (i < keys.length - 1) {
|
|
297
|
+
const childNode = pathNode.children.get(key);
|
|
298
|
+
const nextValue = object[key];
|
|
299
|
+
if (nextValue !== void 0) {
|
|
300
|
+
object = nextValue;
|
|
301
|
+
pathNode = childNode;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
object = object[key] = childNode.isArray ? [] : Object.create(null);
|
|
305
|
+
pathNode = childNode;
|
|
306
|
+
} else object[key] = value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return values;
|
|
310
|
+
};
|
|
311
|
+
var parseRequest = async ({ request, method, query }, sharedMap) => {
|
|
312
|
+
const type = getContentType(request.headers);
|
|
313
|
+
if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") {
|
|
314
|
+
const formData = await request.formData();
|
|
315
|
+
sharedMap.set(RequestEvSharedActionFormData, formData);
|
|
316
|
+
return formToObj(formData);
|
|
317
|
+
} else if (type === "application/json") return await request.json();
|
|
318
|
+
else if (type === "application/qwik-json") {
|
|
319
|
+
if (method === "GET" && query.has("qdata")) {
|
|
320
|
+
const data = query.get(QDATA_KEY);
|
|
321
|
+
if (data) try {
|
|
322
|
+
return await _deserialize(decodeURIComponent(data));
|
|
323
|
+
} catch {}
|
|
324
|
+
}
|
|
325
|
+
return await _deserialize(await request.text());
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, basePathname, resolved) {
|
|
329
|
+
const { request, platform, env } = serverRequestEv;
|
|
330
|
+
const sharedMap = /* @__PURE__ */ new Map();
|
|
331
|
+
const cookie = new Cookie(request.headers.get("cookie"));
|
|
332
|
+
const headers = new Headers();
|
|
333
|
+
const url = new URL(request.url);
|
|
334
|
+
let internalRequest = false;
|
|
335
|
+
const recognized = recognizeRequest(url.pathname);
|
|
336
|
+
if (recognized) {
|
|
337
|
+
url.pathname = trimRecognizedInternalPathname(url.pathname, recognized);
|
|
338
|
+
sharedMap.set(recognized.type, true);
|
|
339
|
+
internalRequest = "loader";
|
|
340
|
+
if (recognized.data?.loaderId) sharedMap.set(QLoaderId, recognized.data.loaderId);
|
|
341
|
+
}
|
|
342
|
+
const actionId = url.searchParams.get(QACTION_KEY);
|
|
343
|
+
if (actionId) {
|
|
344
|
+
sharedMap.set(IsQAction, true);
|
|
345
|
+
sharedMap.set(QActionId, actionId);
|
|
346
|
+
if (request.method === "POST" && request.headers.get("accept")?.includes("application/json")) internalRequest = "action";
|
|
347
|
+
}
|
|
348
|
+
let routeModuleIndex = -1;
|
|
349
|
+
let writableStream = null;
|
|
350
|
+
let requestData = void 0;
|
|
351
|
+
let locale = serverRequestEv.locale;
|
|
352
|
+
let status = loadedRoute?.$notFound$ ? 404 : 200;
|
|
353
|
+
const next = async () => {
|
|
354
|
+
routeModuleIndex++;
|
|
355
|
+
while (routeModuleIndex < requestHandlers.length) {
|
|
356
|
+
const moduleRequestHandler = requestHandlers[routeModuleIndex];
|
|
357
|
+
const result = moduleRequestHandler(requestEv);
|
|
358
|
+
throwIfControlFlowSignal(isPromise(result) ? await result : result);
|
|
359
|
+
routeModuleIndex++;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const resetRoute = (_loadedRoute, _requestHandlers, _url = url) => {
|
|
363
|
+
loadedRoute = _loadedRoute;
|
|
364
|
+
status = loadedRoute?.$notFound$ ? 404 : 200;
|
|
365
|
+
requestHandlers = _requestHandlers;
|
|
366
|
+
url.pathname = _url.pathname;
|
|
367
|
+
url.search = _url.search;
|
|
368
|
+
routeModuleIndex = -1;
|
|
369
|
+
};
|
|
370
|
+
const check = () => {
|
|
371
|
+
if (writableStream !== null) throw new Error("Response already sent");
|
|
372
|
+
};
|
|
373
|
+
const send = (statusOrResponse, body) => {
|
|
374
|
+
check();
|
|
375
|
+
if (typeof statusOrResponse === "number") {
|
|
376
|
+
status = statusOrResponse;
|
|
377
|
+
const writer = requestEv.getWritableStream().getWriter();
|
|
378
|
+
writer.write(typeof body === "string" ? encoder.encode(body) : body);
|
|
379
|
+
writer.close();
|
|
380
|
+
} else {
|
|
381
|
+
status = statusOrResponse.status;
|
|
382
|
+
statusOrResponse.headers.forEach((value, key) => {
|
|
383
|
+
if (key.toLowerCase() === "set-cookie") return;
|
|
384
|
+
headers.append(key, value);
|
|
385
|
+
});
|
|
386
|
+
statusOrResponse.headers.getSetCookie().forEach((ck) => {
|
|
387
|
+
const index = ck.indexOf("=");
|
|
388
|
+
if (index === -1) return;
|
|
389
|
+
const key = ck.slice(0, index).trim();
|
|
390
|
+
const value = ck.slice(index + 1).trim();
|
|
391
|
+
cookie.set(key, value);
|
|
392
|
+
});
|
|
393
|
+
if (statusOrResponse.body) {
|
|
394
|
+
const writableStream = requestEv.getWritableStream();
|
|
395
|
+
statusOrResponse.body.pipeTo(writableStream);
|
|
396
|
+
} else requestEv.getWritableStream().getWriter().close();
|
|
397
|
+
}
|
|
398
|
+
return exit();
|
|
399
|
+
};
|
|
400
|
+
const exit = (message = new AbortMessage()) => {
|
|
401
|
+
routeModuleIndex = ABORT_INDEX;
|
|
402
|
+
return message;
|
|
403
|
+
};
|
|
404
|
+
const requestEv = {
|
|
405
|
+
[RequestEvMode]: serverRequestEv.mode,
|
|
406
|
+
get [RequestEvRoute]() {
|
|
407
|
+
return loadedRoute;
|
|
408
|
+
},
|
|
409
|
+
cookie,
|
|
410
|
+
headers,
|
|
411
|
+
env,
|
|
412
|
+
method: request.method,
|
|
413
|
+
signal: request.signal,
|
|
414
|
+
originalUrl: new URL(url),
|
|
415
|
+
get params() {
|
|
416
|
+
return loadedRoute?.$params$ ?? {};
|
|
417
|
+
},
|
|
418
|
+
get pathname() {
|
|
419
|
+
return url.pathname;
|
|
420
|
+
},
|
|
421
|
+
platform,
|
|
422
|
+
get query() {
|
|
423
|
+
return url.searchParams;
|
|
424
|
+
},
|
|
425
|
+
request,
|
|
426
|
+
url,
|
|
427
|
+
internalRequest,
|
|
428
|
+
basePathname,
|
|
429
|
+
sharedMap,
|
|
430
|
+
get headersSent() {
|
|
431
|
+
return writableStream !== null;
|
|
432
|
+
},
|
|
433
|
+
get exited() {
|
|
434
|
+
return routeModuleIndex >= ABORT_INDEX;
|
|
435
|
+
},
|
|
436
|
+
get clientConn() {
|
|
437
|
+
return serverRequestEv.getClientConn();
|
|
438
|
+
},
|
|
439
|
+
next,
|
|
440
|
+
resetRoute,
|
|
441
|
+
exit,
|
|
442
|
+
cacheControl: (cacheControl, target = "Cache-Control") => {
|
|
443
|
+
check();
|
|
444
|
+
headers.set(target, createCacheControl(cacheControl));
|
|
445
|
+
},
|
|
446
|
+
resolveValue: async (loaderOrAction) => {
|
|
447
|
+
if (loaderOrAction.__brand === "server_loader") {
|
|
448
|
+
const loaderValues = getRouteLoaderValues(requestEv);
|
|
449
|
+
if (loaderOrAction.__id in loaderValues) return loaderValues[loaderOrAction.__id];
|
|
450
|
+
return loadRouteLoader(loaderOrAction, requestEv);
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
status: (statusCode) => {
|
|
454
|
+
if (typeof statusCode === "number") {
|
|
455
|
+
check();
|
|
456
|
+
status = statusCode;
|
|
457
|
+
return statusCode;
|
|
458
|
+
}
|
|
459
|
+
return status;
|
|
460
|
+
},
|
|
461
|
+
locale: (_locale) => {
|
|
462
|
+
if (typeof _locale === "string") locale = _locale;
|
|
463
|
+
return locale || "";
|
|
464
|
+
},
|
|
465
|
+
error: (statusCode, message) => {
|
|
466
|
+
status = statusCode;
|
|
467
|
+
headers.delete("Cache-Control");
|
|
468
|
+
return new ServerError(statusCode, message);
|
|
469
|
+
},
|
|
470
|
+
redirect: (statusCode, url) => {
|
|
471
|
+
check();
|
|
472
|
+
status = statusCode;
|
|
473
|
+
if (url) {
|
|
474
|
+
if (/^\/\//.test(url) || /([^:])\/\/+/.test(url)) {
|
|
475
|
+
const fixedURL = url.replace(/^\/\/+/, "/").replace(/([^:])\/\/+/g, "$1/");
|
|
476
|
+
console.warn(`Redirect URL ${url} is invalid, fixing to ${fixedURL}`);
|
|
477
|
+
url = fixedURL;
|
|
478
|
+
}
|
|
479
|
+
headers.set("Location", url);
|
|
480
|
+
}
|
|
481
|
+
headers.delete("Cache-Control");
|
|
482
|
+
if (statusCode > 301) headers.set("Cache-Control", "no-store");
|
|
483
|
+
if (internalRequest === "loader") return new RedirectMessage();
|
|
484
|
+
return exit(new RedirectMessage());
|
|
485
|
+
},
|
|
486
|
+
rewrite: (pathname) => {
|
|
487
|
+
check();
|
|
488
|
+
if (pathname.startsWith("http")) throw new ServerError(400, isDev$1 ? "Rewrite does not support absolute urls" : "Bad Request");
|
|
489
|
+
sharedMap.set(RequestEvIsRewrite, true);
|
|
490
|
+
return exit(new RewriteMessage(pathname.replace(/\/+/g, "/")));
|
|
491
|
+
},
|
|
492
|
+
defer: (returnData) => {
|
|
493
|
+
return typeof returnData === "function" ? returnData : () => returnData;
|
|
494
|
+
},
|
|
495
|
+
fail: (statusCode, data) => {
|
|
496
|
+
check();
|
|
497
|
+
status = statusCode;
|
|
498
|
+
headers.delete("Cache-Control");
|
|
499
|
+
return {
|
|
500
|
+
failed: true,
|
|
501
|
+
...data
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
text: (statusCode, text) => {
|
|
505
|
+
headers.set("Content-Type", "text/plain; charset=utf-8");
|
|
506
|
+
return send(statusCode, text);
|
|
507
|
+
},
|
|
508
|
+
html: (statusCode, html) => {
|
|
509
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
510
|
+
return send(statusCode, html);
|
|
511
|
+
},
|
|
512
|
+
parseBody: async () => {
|
|
513
|
+
if (requestData !== void 0) return requestData;
|
|
514
|
+
return requestData = parseRequest(requestEv, sharedMap);
|
|
515
|
+
},
|
|
516
|
+
json: (statusCode, data) => {
|
|
517
|
+
headers.set("Content-Type", "application/json; charset=utf-8");
|
|
518
|
+
return send(statusCode, JSON.stringify(data));
|
|
519
|
+
},
|
|
520
|
+
send,
|
|
521
|
+
isDirty: () => {
|
|
522
|
+
return writableStream !== null;
|
|
523
|
+
},
|
|
524
|
+
getWritableStream: () => {
|
|
525
|
+
if (writableStream === null) {
|
|
526
|
+
if (isDev$1) {
|
|
527
|
+
const serverTiming = sharedMap.get(RequestEvShareServerTiming);
|
|
528
|
+
if (serverTiming) headers.set("Server-Timing", serverTiming.map(([name, duration]) => `${name};dur=${duration}`).join(","));
|
|
529
|
+
}
|
|
530
|
+
writableStream = serverRequestEv.getWritableStream(status, headers, cookie, resolved, requestEv);
|
|
531
|
+
}
|
|
532
|
+
return writableStream;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
return requestEv;
|
|
536
|
+
}
|
|
537
|
+
var MAX_CACHE_SIZE = typeof globalThis.__SSR_CACHE_SIZE__ === "number" ? globalThis.__SSR_CACHE_SIZE__ : 50;
|
|
538
|
+
function createLruCache() {
|
|
539
|
+
const cache = /* @__PURE__ */ new Map();
|
|
540
|
+
return {
|
|
541
|
+
get(key) {
|
|
542
|
+
const entry = cache.get(key);
|
|
543
|
+
if (entry !== void 0) {
|
|
544
|
+
cache.delete(key);
|
|
545
|
+
cache.set(key, entry);
|
|
546
|
+
}
|
|
547
|
+
return entry;
|
|
548
|
+
},
|
|
549
|
+
set(key, entry) {
|
|
550
|
+
if (MAX_CACHE_SIZE <= 0) return;
|
|
551
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
552
|
+
const firstKey = cache.keys().next().value;
|
|
553
|
+
cache.delete(firstKey);
|
|
554
|
+
}
|
|
555
|
+
cache.set(key, entry);
|
|
556
|
+
},
|
|
557
|
+
clear(key) {
|
|
558
|
+
if (key != null) cache.delete(key);
|
|
559
|
+
else cache.clear();
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
var ssrCache = createLruCache();
|
|
564
|
+
var loaderCache = createLruCache();
|
|
565
|
+
/**
|
|
566
|
+
* Resolve an eTag value from a ContentModuleETag (string, function, or undefined). Returns the eTag
|
|
567
|
+
* string or null.
|
|
568
|
+
*/ function resolveETag(eTagExport, headProps) {
|
|
569
|
+
if (eTagExport === void 0) return null;
|
|
570
|
+
if (typeof eTagExport === "function") return eTagExport(headProps);
|
|
571
|
+
return eTagExport;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Resolve a cache key from a CacheKeyFn. The default key callback is only called when `cacheKey ===
|
|
575
|
+
* true`.
|
|
576
|
+
*/ function resolveCacheKey(cacheKeyExport, defaultKey, requestEv, eTag) {
|
|
577
|
+
if (cacheKeyExport === true) return defaultKey(requestEv, eTag);
|
|
578
|
+
if (typeof cacheKeyExport !== "function") return "";
|
|
579
|
+
return cacheKeyExport(requestEv, eTag) || "";
|
|
580
|
+
}
|
|
581
|
+
/** Build the default SSR cache key. eTag slot is dropped when no eTag was resolved. */ function defaultSsrCacheKey(requestEv, eTag) {
|
|
582
|
+
const status = requestEv.status();
|
|
583
|
+
const pathname = requestEv.url.pathname;
|
|
584
|
+
return eTag ? `${status}|${eTag}|${pathname}` : `${status}|${pathname}`;
|
|
585
|
+
}
|
|
586
|
+
/** Build the default loader cache key. eTag slot is dropped when no eTag was resolved. */ function defaultLoaderCacheKey(requestEv, eTag) {
|
|
587
|
+
const loaderId = requestEv.sharedMap.get(QLoaderId);
|
|
588
|
+
const base = `${requestEv.url.pathname}|${requestEv.url.search}|${loaderId}`;
|
|
589
|
+
return eTag ? `${base}|${eTag}` : base;
|
|
590
|
+
}
|
|
591
|
+
/** Get a cached SSR entry by key (LRU touch). */ function getCachedSsr(key) {
|
|
592
|
+
return ssrCache.get(key);
|
|
593
|
+
}
|
|
594
|
+
/** Cache an SSR entry by key, evicting the least recently used entry if at capacity. */ function setCachedSsr(key, entry) {
|
|
595
|
+
ssrCache.set(key, entry);
|
|
596
|
+
}
|
|
597
|
+
/** Get a cached loader entry by key (LRU touch). */ function getCachedLoader(key) {
|
|
598
|
+
return loaderCache.get(key);
|
|
599
|
+
}
|
|
600
|
+
/** Cache a loader entry by key, evicting the least recently used entry if at capacity. */ function setCachedLoader(key, entry) {
|
|
601
|
+
loaderCache.set(key, entry);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Clear the in-memory SSR cache. Call after deployments or data changes.
|
|
605
|
+
*
|
|
606
|
+
* When `cacheKey` is provided, only that single entry is removed; otherwise the entire cache is
|
|
607
|
+
* cleared.
|
|
608
|
+
*
|
|
609
|
+
* @public
|
|
610
|
+
*/ function clearSsrCache(cacheKey) {
|
|
611
|
+
ssrCache.clear(cacheKey);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Clear the in-memory loader cache. Call after deployments or data changes.
|
|
615
|
+
*
|
|
616
|
+
* When `cacheKey` is provided, only that single entry is removed; otherwise the entire cache is
|
|
617
|
+
* cleared.
|
|
618
|
+
*
|
|
619
|
+
* @public
|
|
620
|
+
*/ function clearLoaderCache(cacheKey) {
|
|
621
|
+
loaderCache.clear(cacheKey);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Create per-loader request event views. Loader-specific URL/search data is scoped to the view,
|
|
625
|
+
* while request-wide state (headers, cookies, loader value maps, etc.) still delegates to the
|
|
626
|
+
* original request event.
|
|
627
|
+
*/ function createLoaderRequestEventFactory(requestEv) {
|
|
628
|
+
return (loader) => getLoaderRequestEvent(loader, requestEv);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Resolve the loader's cacheControl option. Loaders default to `no-cache` so the browser always
|
|
632
|
+
* revalidates; pair with `eTag` for cheap 304s. A function form may return `null` to skip the
|
|
633
|
+
* header entirely.
|
|
634
|
+
*/ function resolveLoaderCacheControl(option, requestEv) {
|
|
635
|
+
const value = typeof option === "function" ? option(requestEv) : option;
|
|
636
|
+
return value === void 0 ? "no-cache" : value;
|
|
637
|
+
}
|
|
638
|
+
async function runBlockingLoadersBeforeTarget(routeLoaders, targetLoader, requestEv) {
|
|
639
|
+
for (const loader of routeLoaders) {
|
|
640
|
+
if (loader === targetLoader) return;
|
|
641
|
+
if (loader.__blockSSR) await loadRouteLoader(loader, requestEv);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function setLoaderData(requestEv, routeLoaders, loaderPaths) {
|
|
645
|
+
if (loaderPaths) Object.assign(getRouteLoaderCtx(requestEv).loaderPaths, loaderPaths);
|
|
646
|
+
setRouteLoaders(requestEv, routeLoaders);
|
|
647
|
+
}
|
|
648
|
+
/** Resolve eTag from a static string or function (before running the loader). */ function resolvePreETag(eTagOption, requestEv) {
|
|
649
|
+
if (typeof eTagOption === "string") return normalizeETag(eTagOption);
|
|
650
|
+
const result = eTagOption(requestEv);
|
|
651
|
+
return result ? normalizeETag(result) : "";
|
|
652
|
+
}
|
|
653
|
+
function addVaryHeader(requestEv, value) {
|
|
654
|
+
const vary = requestEv.headers.get("Vary");
|
|
655
|
+
if (!vary) {
|
|
656
|
+
requestEv.headers.set("Vary", value);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (!vary.split(",").map((item) => item.trim().toLowerCase()).includes(value.toLowerCase())) requestEv.headers.set("Vary", `${vary}, ${value}`);
|
|
660
|
+
}
|
|
661
|
+
async function sendLoaderResponse(requestEv, data, cacheControl) {
|
|
662
|
+
requestEv.headers.set("Content-Type", "application/json; charset=utf-8");
|
|
663
|
+
addVaryHeader(requestEv, FULLPATH_HEADER);
|
|
664
|
+
if (cacheControl !== null && !requestEv.headers.has("Cache-Control")) requestEv.cacheControl(cacheControl);
|
|
665
|
+
requestEv.send(200, data);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Handler that executes the requested loader and returns the result as JSON. Runs AFTER
|
|
669
|
+
* plugin/route middleware, so middleware redirects/errors are handled by `jsonRequestWrapper`. The
|
|
670
|
+
* loader function's own redirects/errors are caught by getRouteLoaderResponse and serialized in the
|
|
671
|
+
* LoaderResponse envelope ({ d, r, e }).
|
|
672
|
+
*/ function loaderHandler(routeLoaders, loaderPaths) {
|
|
673
|
+
return async (requestEvent) => {
|
|
674
|
+
const requestEv = requestEvent;
|
|
675
|
+
if (!requestEv.sharedMap.has("@isQLoader")) return;
|
|
676
|
+
if (requestEv.headersSent || requestEv.exited) return;
|
|
677
|
+
const loaderId = requestEv.sharedMap.get(QLoaderId);
|
|
678
|
+
const loader = resolveRouteLoaderByHash(routeLoaders, loaderId);
|
|
679
|
+
if (!loader) {
|
|
680
|
+
requestEv.json(404, { error: "Loader not found" });
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
setLoaderData(requestEv, routeLoaders, loaderPaths);
|
|
684
|
+
await runBlockingLoadersBeforeTarget(routeLoaders, loader, requestEv);
|
|
685
|
+
const loaderRequestEv = createLoaderRequestEventFactory(requestEv)(loader);
|
|
686
|
+
const cacheControl = resolveLoaderCacheControl(loader.__cacheControl, loaderRequestEv);
|
|
687
|
+
const normalizedETag = loader.__eTag !== void 0 ? resolvePreETag(loader.__eTag, loaderRequestEv) : "";
|
|
688
|
+
if (normalizedETag && performETagMatch(loaderRequestEv, normalizedETag)) return;
|
|
689
|
+
let cacheKey = "";
|
|
690
|
+
if (loader.__cacheKey) cacheKey = resolveCacheKey(loader.__cacheKey, defaultLoaderCacheKey, loaderRequestEv, normalizedETag);
|
|
691
|
+
if (cacheKey) {
|
|
692
|
+
const cached = getCachedLoader(cacheKey);
|
|
693
|
+
if (cached) {
|
|
694
|
+
if (!normalizedETag) {
|
|
695
|
+
if (performETagMatch(loaderRequestEv, cached.eTag)) return;
|
|
696
|
+
} else setETagHeader(loaderRequestEv, cached.eTag);
|
|
697
|
+
await sendLoaderResponse(requestEv, cached.body, cacheControl);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const responseData = await getRouteLoaderResponse(loader.__qrl, loader.__validators, loaderRequestEv);
|
|
702
|
+
const data = await _serialize(responseData);
|
|
703
|
+
const failed = responseData.d && typeof responseData.d === "object" && responseData.d.failed;
|
|
704
|
+
const cacheable = cacheKey && !responseData.r && !responseData.e && !failed;
|
|
705
|
+
const finalETag = normalizedETag || (cacheable ? hash(data) : "");
|
|
706
|
+
if (cacheable && finalETag) setCachedLoader(cacheKey, {
|
|
707
|
+
eTag: finalETag,
|
|
708
|
+
body: data
|
|
709
|
+
});
|
|
710
|
+
if (!normalizedETag && finalETag && performETagMatch(loaderRequestEv, finalETag)) return;
|
|
711
|
+
await sendLoaderResponse(requestEv, data, cacheControl);
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
/** Serialize and send a JSON response (used by error/redirect paths in jsonRequestWrapper). */ async function sendJsonResponse(requestEv, responseData, status = 200) {
|
|
715
|
+
const data = await _serialize(responseData);
|
|
716
|
+
requestEv.headers.set("Content-Type", "application/json; charset=utf-8");
|
|
717
|
+
requestEv.send(status, data);
|
|
718
|
+
}
|
|
719
|
+
async function sendActionResponse(requestEv, responseData) {
|
|
720
|
+
const data = await _serialize(responseData);
|
|
721
|
+
requestEv.headers.set("Content-Type", "application/json; charset=utf-8");
|
|
722
|
+
requestEv.send(responseData.s || 200, data);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Early handler that wraps `next()` for JSON API requests (q-loader and q-action).
|
|
726
|
+
*
|
|
727
|
+
* For `IsQLoader` requests, it also rewrites the URL using the `X-Qwik-fullpath` header so that
|
|
728
|
+
* downstream middleware sees the real page URL.
|
|
729
|
+
*
|
|
730
|
+
* By calling `await next()` inside a try/catch, middleware redirects and errors are captured and
|
|
731
|
+
* returned as JSON envelopes instead of HTTP redirects/error pages. This keeps SPA navigation
|
|
732
|
+
* intact on the client.
|
|
733
|
+
*/ function jsonRequestWrapper() {
|
|
734
|
+
return async (requestEvent) => {
|
|
735
|
+
const requestEv = requestEvent;
|
|
736
|
+
const isLoader = requestEv.internalRequest === "loader";
|
|
737
|
+
const isActionJson = requestEv.internalRequest === "action";
|
|
738
|
+
if (!isLoader && !isActionJson) return;
|
|
739
|
+
if (isLoader) {
|
|
740
|
+
addVaryHeader(requestEv, FULLPATH_HEADER);
|
|
741
|
+
const pagePath = requestEv.request.headers.get(FULLPATH_HEADER);
|
|
742
|
+
const pagePathname = resolveValidInternalFullPathname(requestEv.url.pathname, pagePath);
|
|
743
|
+
if (pagePathname) requestEv.url.pathname = pagePathname;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
await requestEv.next();
|
|
747
|
+
} catch (err) {
|
|
748
|
+
if (requestEv.headersSent) return;
|
|
749
|
+
if (err instanceof RedirectMessage) {
|
|
750
|
+
const location = requestEv.headers.get("Location") || "/";
|
|
751
|
+
requestEv.headers.delete("Location");
|
|
752
|
+
if (isLoader) await sendJsonResponse(requestEv, { r: location });
|
|
753
|
+
else await sendActionResponse(requestEv, { redirect: location });
|
|
754
|
+
} else if (err instanceof ServerError) {
|
|
755
|
+
if (isLoader) await sendJsonResponse(requestEv, { e: err });
|
|
756
|
+
else await sendActionResponse(requestEv, {
|
|
757
|
+
e: err,
|
|
758
|
+
s: err.status
|
|
759
|
+
});
|
|
760
|
+
} else if (err instanceof Error) {
|
|
761
|
+
console.error("JSON request error:", err);
|
|
762
|
+
const message = isDev ? `${err.message}\n(this is only visible in dev mode)` : "Internal Server Error";
|
|
763
|
+
const se = new ServerError(500, message);
|
|
764
|
+
if (isLoader) await sendJsonResponse(requestEv, { e: se });
|
|
765
|
+
else await sendActionResponse(requestEv, {
|
|
766
|
+
e: se,
|
|
767
|
+
s: 500
|
|
768
|
+
});
|
|
769
|
+
} else throw err;
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function verifySerializable$1(data, qrl) {
|
|
774
|
+
try {
|
|
775
|
+
_verifySerializable(data, void 0);
|
|
776
|
+
} catch (e) {
|
|
777
|
+
if (e instanceof Error && qrl.dev) e.loc = qrl.dev;
|
|
778
|
+
throw e;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
async function measure$1(requestEv, name, fn) {
|
|
782
|
+
const start = performance.now();
|
|
783
|
+
try {
|
|
784
|
+
return await fn();
|
|
785
|
+
} finally {
|
|
786
|
+
const duration = performance.now() - start;
|
|
787
|
+
let measurements = requestEv.sharedMap.get("@serverTiming");
|
|
788
|
+
if (!measurements) requestEv.sharedMap.set("@serverTiming", measurements = []);
|
|
789
|
+
measurements.push([name, duration]);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async function runValidators(requestEv, validators, data, devMode) {
|
|
793
|
+
let lastResult = {
|
|
794
|
+
success: true,
|
|
795
|
+
data
|
|
796
|
+
};
|
|
797
|
+
if (validators) for (const validator of validators) {
|
|
798
|
+
if (devMode) lastResult = await measure$1(requestEv, `validator$`, () => validator.validate(requestEv, data));
|
|
799
|
+
else lastResult = await validator.validate(requestEv, data);
|
|
800
|
+
if (!lastResult.success) return lastResult;
|
|
801
|
+
else data = lastResult.data;
|
|
802
|
+
}
|
|
803
|
+
return lastResult;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Handler for action requests (`?qaction={actionId}`).
|
|
807
|
+
*
|
|
808
|
+
* When the request has `Accept: application/json`, returns the action result as JSON. Otherwise,
|
|
809
|
+
* falls through to let the page render (progressive enhancement for forms).
|
|
810
|
+
*
|
|
811
|
+
* If the action has no `invalidate` list, the client invalidates all current route loaders unless
|
|
812
|
+
* strict loaders mode treats it as `invalidate: []`. If `invalidate` is specified, only those
|
|
813
|
+
* loader hashes are sent for the client to re-fetch.
|
|
814
|
+
*/ function actionHandler(routeActions) {
|
|
815
|
+
return async (requestEvent) => {
|
|
816
|
+
const requestEv = requestEvent;
|
|
817
|
+
if (!requestEv.sharedMap.has("@isQAction")) return;
|
|
818
|
+
if (!requestEv.request.headers.get("accept")?.includes("application/json")) return;
|
|
819
|
+
if (requestEv.headersSent || requestEv.exited) return;
|
|
820
|
+
const actionId = requestEv.sharedMap.get(QActionId);
|
|
821
|
+
const method = requestEv.method;
|
|
822
|
+
const devMode = isDev$1;
|
|
823
|
+
if (devMode && method === "GET") console.warn("Seems like you are submitting a Qwik Action via GET request. Qwik Actions should be submitted via POST request.\nMake sure your <form> has method=\"POST\" attribute, like this: <form method=\"POST\">");
|
|
824
|
+
if (method !== "POST") return;
|
|
825
|
+
let action;
|
|
826
|
+
for (const routeAction of routeActions) if (routeAction.__id === actionId) action = routeAction;
|
|
827
|
+
if (!action) action = globalThis._qwikActionsMap?.get(actionId);
|
|
828
|
+
if (!action) {
|
|
829
|
+
requestEv.json(404, { error: "Action not found" });
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const data = await requestEv.parseBody();
|
|
833
|
+
if (!data || typeof data !== "object") throw new Error(`Expected request data for the action id ${actionId} to be an object`);
|
|
834
|
+
let actionResult;
|
|
835
|
+
const result = await runValidators(requestEv, action.__validators, data, devMode);
|
|
836
|
+
if (!result.success) actionResult = requestEv.fail(result.status ?? 500, result.error);
|
|
837
|
+
else {
|
|
838
|
+
const actionResolved = devMode ? await measure$1(requestEv, action.__qrl.getHash(), () => action.__qrl.call(requestEv, result.data, requestEv)) : await action.__qrl.call(requestEv, result.data, requestEv);
|
|
839
|
+
throwIfControlFlowSignal(actionResolved);
|
|
840
|
+
if (devMode) verifySerializable$1(actionResolved, action.__qrl);
|
|
841
|
+
actionResult = actionResolved;
|
|
842
|
+
}
|
|
843
|
+
if (requestEv.headers.has("Location")) throw new RedirectMessage();
|
|
844
|
+
const responseData = { result: actionResult };
|
|
845
|
+
requestEv.sharedMap.set(RequestEvSharedActionId, actionId);
|
|
846
|
+
requestEv.sharedMap.set("@actionResult", actionResult);
|
|
847
|
+
if (action.__invalidate) responseData.loaderHashes = action.__invalidate;
|
|
848
|
+
else if (globalThis.__STRICT_LOADERS__) responseData.loaderHashes = [];
|
|
849
|
+
const serialized = await _serialize(responseData);
|
|
850
|
+
requestEv.headers.set("Content-Type", "application/json; charset=utf-8");
|
|
851
|
+
requestEv.send(requestEv.status(), serialized);
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
var staticPaths = /* @__PURE__ */ new Set(["__QWIK_ROUTER_STATIC_PATHS_ARRAY__"]);
|
|
855
|
+
/**
|
|
856
|
+
* Decide whether a given request path is a static path, for optimizing file access.
|
|
857
|
+
*
|
|
858
|
+
* @internal
|
|
859
|
+
*/ function isStaticPath(method, url) {
|
|
860
|
+
method = method.toUpperCase();
|
|
861
|
+
if (method !== "GET" && method !== "HEAD") return false;
|
|
862
|
+
const p = url.pathname;
|
|
863
|
+
if (p.startsWith("/" + (globalThis.__QWIK_BUILD_DIR__ || "build") + "/")) return true;
|
|
864
|
+
if (p.startsWith("/" + (globalThis.__QWIK_ASSETS_DIR__ || "assets") + "/")) return true;
|
|
865
|
+
return staticPaths.has(p);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Hypertext Transfer Protocol (HTTP) response status codes.
|
|
869
|
+
*
|
|
870
|
+
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
|
|
871
|
+
*/ var HttpStatus = /*#__PURE__*/ function(HttpStatus) {
|
|
872
|
+
/**
|
|
873
|
+
* The server has received the request headers and the client should proceed to send the request
|
|
874
|
+
* body (in the case of a request for which a body needs to be sent; for example, a POST request).
|
|
875
|
+
* Sending a large request body to a server after a request has been rejected for inappropriate
|
|
876
|
+
* headers would be inefficient. To have a server check the request's headers, a client must send
|
|
877
|
+
* Expect: 100-continue as a header in its initial request and receive a 100 Continue status code
|
|
878
|
+
* in response before sending the body. The response 417 Expectation Failed indicates the request
|
|
879
|
+
* should not be continued.
|
|
880
|
+
*/ HttpStatus[HttpStatus["Continue"] = 100] = "Continue";
|
|
881
|
+
/** The requester has asked the server to switch protocols and the server has agreed to do so. */ HttpStatus[HttpStatus["SwitchingProtocols"] = 101] = "SwitchingProtocols";
|
|
882
|
+
/**
|
|
883
|
+
* A WebDAV request may contain many sub-requests involving file operations, requiring a long time
|
|
884
|
+
* to complete the request. This code indicates that the server has received and is processing the
|
|
885
|
+
* request, but no response is available yet. This prevents the client from timing out and
|
|
886
|
+
* assuming the request was lost.
|
|
887
|
+
*/ HttpStatus[HttpStatus["Processing"] = 102] = "Processing";
|
|
888
|
+
/**
|
|
889
|
+
* Standard response for successful HTTP requests. The actual response will depend on the request
|
|
890
|
+
* method used. In a GET request, the response will contain an entity corresponding to the
|
|
891
|
+
* requested resource. In a POST request, the response will contain an entity describing or
|
|
892
|
+
* containing the result of the action.
|
|
893
|
+
*/ HttpStatus[HttpStatus["Ok"] = 200] = "Ok";
|
|
894
|
+
/** The request has been fulfilled, resulting in the creation of a new resource. */ HttpStatus[HttpStatus["Created"] = 201] = "Created";
|
|
895
|
+
/**
|
|
896
|
+
* The request has been accepted for processing, but the processing has not been completed. The
|
|
897
|
+
* request might or might not be eventually acted upon, and may be disallowed when processing
|
|
898
|
+
* occurs.
|
|
899
|
+
*/ HttpStatus[HttpStatus["Accepted"] = 202] = "Accepted";
|
|
900
|
+
/**
|
|
901
|
+
* SINCE HTTP/1.1 The server is a transforming proxy that received a 200 OK from its origin, but
|
|
902
|
+
* is returning a modified version of the origin's response.
|
|
903
|
+
*/ HttpStatus[HttpStatus["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
|
|
904
|
+
/** The server successfully processed the request and is not returning any content. */ HttpStatus[HttpStatus["NoContent"] = 204] = "NoContent";
|
|
905
|
+
/**
|
|
906
|
+
* The server successfully processed the request, but is not returning any content. Unlike a 204
|
|
907
|
+
* response, this response requires that the requester reset the document view.
|
|
908
|
+
*/ HttpStatus[HttpStatus["ResetContent"] = 205] = "ResetContent";
|
|
909
|
+
/**
|
|
910
|
+
* The server is delivering only part of the resource (byte serving) due to a range header sent by
|
|
911
|
+
* the client. The range header is used by HTTP clients to enable resuming of interrupted
|
|
912
|
+
* downloads, or split a download into multiple simultaneous streams.
|
|
913
|
+
*/ HttpStatus[HttpStatus["PartialContent"] = 206] = "PartialContent";
|
|
914
|
+
/**
|
|
915
|
+
* The message body that follows is an XML message and can contain a number of separate response
|
|
916
|
+
* codes, depending on how many sub-requests were made.
|
|
917
|
+
*/ HttpStatus[HttpStatus["MultiStatus"] = 207] = "MultiStatus";
|
|
918
|
+
/**
|
|
919
|
+
* The members of a DAV binding have already been enumerated in a preceding part of the
|
|
920
|
+
* (multistatus) response, and are not being included again.
|
|
921
|
+
*/ HttpStatus[HttpStatus["AlreadyReported"] = 208] = "AlreadyReported";
|
|
922
|
+
/**
|
|
923
|
+
* The server has fulfilled a request for the resource, and the response is a representation of
|
|
924
|
+
* the result of one or more instance-manipulations applied to the current instance.
|
|
925
|
+
*/ HttpStatus[HttpStatus["ImUsed"] = 226] = "ImUsed";
|
|
926
|
+
/**
|
|
927
|
+
* Indicates multiple options for the resource from which the client may choose (via agent-driven
|
|
928
|
+
* content negotiation). For example, this code could be used to present multiple video format
|
|
929
|
+
* options, to list files with different filename extensions, or to suggest word-sense
|
|
930
|
+
* disambiguation.
|
|
931
|
+
*/ HttpStatus[HttpStatus["MultipleChoices"] = 300] = "MultipleChoices";
|
|
932
|
+
/** This and all future requests should be directed to the given URI. */ HttpStatus[HttpStatus["MovedPermanently"] = 301] = "MovedPermanently";
|
|
933
|
+
/**
|
|
934
|
+
* This is an example of industry practice contradicting the standard. The HTTP/1.0 specification
|
|
935
|
+
* (RFC 1945) required the client to perform a temporary redirect (the original describing phrase
|
|
936
|
+
* was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303
|
|
937
|
+
* See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two
|
|
938
|
+
* behaviors. However, some Web applications and frameworks use the 302 status code as if it were
|
|
939
|
+
* the 303.
|
|
940
|
+
*/ HttpStatus[HttpStatus["Found"] = 302] = "Found";
|
|
941
|
+
/**
|
|
942
|
+
* SINCE HTTP/1.1 The response to the request can be found under another URI using a GET method.
|
|
943
|
+
* When received in response to a POST (or PUT/DELETE), the client should presume that the server
|
|
944
|
+
* has received the data and should issue a redirect with a separate GET message.
|
|
945
|
+
*/ HttpStatus[HttpStatus["SeeOther"] = 303] = "SeeOther";
|
|
946
|
+
/**
|
|
947
|
+
* Indicates that the resource has not been modified since the version specified by the request
|
|
948
|
+
* headers If-Modified-Since or If-None-Match. In such case, there is no need to retransmit the
|
|
949
|
+
* resource since the client still has a previously-downloaded copy.
|
|
950
|
+
*/ HttpStatus[HttpStatus["NotModified"] = 304] = "NotModified";
|
|
951
|
+
/**
|
|
952
|
+
* SINCE HTTP/1.1 The requested resource is available only through a proxy, the address for which
|
|
953
|
+
* is provided in the response. Many HTTP clients (such as Mozilla and Internet Explorer) do not
|
|
954
|
+
* correctly handle responses with this status code, primarily for security reasons.
|
|
955
|
+
*/ HttpStatus[HttpStatus["UseProxy"] = 305] = "UseProxy";
|
|
956
|
+
/** No longer used. Originally meant "Subsequent requests should use the specified proxy." */ HttpStatus[HttpStatus["SwitchProxy"] = 306] = "SwitchProxy";
|
|
957
|
+
/**
|
|
958
|
+
* SINCE HTTP/1.1 In this case, the request should be repeated with another URI; however, future
|
|
959
|
+
* requests should still use the original URI. In contrast to how 302 was historically
|
|
960
|
+
* implemented, the request method is not allowed to be changed when reissuing the original
|
|
961
|
+
* request. For example, a POST request should be repeated using another POST request.
|
|
962
|
+
*/ HttpStatus[HttpStatus["TemporaryRedirect"] = 307] = "TemporaryRedirect";
|
|
963
|
+
/**
|
|
964
|
+
* The request and all future requests should be repeated using another URI. 307 and 308 parallel
|
|
965
|
+
* the behaviors of 302 and 301, but do not allow the HTTP method to change. So, for example,
|
|
966
|
+
* submitting a form to a permanently redirected resource may continue smoothly.
|
|
967
|
+
*/ HttpStatus[HttpStatus["PermanentRedirect"] = 308] = "PermanentRedirect";
|
|
968
|
+
/**
|
|
969
|
+
* The server cannot or will not process the request due to an apparent client error (e.g.,
|
|
970
|
+
* malformed request syntax, too large size, invalid request message framing, or deceptive request
|
|
971
|
+
* routing).
|
|
972
|
+
*/ HttpStatus[HttpStatus["BadRequest"] = 400] = "BadRequest";
|
|
973
|
+
/**
|
|
974
|
+
* Similar to 403 Forbidden, but specifically for use when authentication is required and has
|
|
975
|
+
* failed or has not yet been provided. The response must include a WWW-Authenticate header field
|
|
976
|
+
* containing a challenge applicable to the requested resource. See Basic access authentication
|
|
977
|
+
* and Digest access authentication. 401 semantically means "unauthenticated",i.e. the user does
|
|
978
|
+
* not have the necessary credentials.
|
|
979
|
+
*/ HttpStatus[HttpStatus["Unauthorized"] = 401] = "Unauthorized";
|
|
980
|
+
/**
|
|
981
|
+
* Reserved for future use. The original intention was that this code might be used as part of
|
|
982
|
+
* some form of digital cash or micro payment scheme, but that has not happened, and this code is
|
|
983
|
+
* not usually used. Google Developers API uses this status if a particular developer has exceeded
|
|
984
|
+
* the daily limit on requests.
|
|
985
|
+
*/ HttpStatus[HttpStatus["PaymentRequired"] = 402] = "PaymentRequired";
|
|
986
|
+
/**
|
|
987
|
+
* The request was valid, but the server is refusing action. The user might not have the necessary
|
|
988
|
+
* permissions for a resource.
|
|
989
|
+
*/ HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";
|
|
990
|
+
/**
|
|
991
|
+
* The requested resource could not be found but may be available in the future. Subsequent
|
|
992
|
+
* requests by the client are permissible.
|
|
993
|
+
*/ HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound";
|
|
994
|
+
/**
|
|
995
|
+
* A request method is not supported for the requested resource; for example, a GET request on a
|
|
996
|
+
* form that requires data to be presented via POST, or a PUT request on a read-only resource.
|
|
997
|
+
*/ HttpStatus[HttpStatus["MethodNotAllowed"] = 405] = "MethodNotAllowed";
|
|
998
|
+
/**
|
|
999
|
+
* The requested resource is capable of generating only content not acceptable according to the
|
|
1000
|
+
* Accept headers sent in the request.
|
|
1001
|
+
*/ HttpStatus[HttpStatus["NotAcceptable"] = 406] = "NotAcceptable";
|
|
1002
|
+
/** The client must first authenticate itself with the proxy. */ HttpStatus[HttpStatus["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
|
|
1003
|
+
/**
|
|
1004
|
+
* The server timed out waiting for the request. According to HTTP specifications: "The client did
|
|
1005
|
+
* not produce a request within the time that the server was prepared to wait. The client MAY
|
|
1006
|
+
* repeat the request without modifications at any later time."
|
|
1007
|
+
*/ HttpStatus[HttpStatus["RequestTimeout"] = 408] = "RequestTimeout";
|
|
1008
|
+
/**
|
|
1009
|
+
* Indicates that the request could not be processed because of conflict in the request, such as
|
|
1010
|
+
* an edit conflict between multiple simultaneous updates.
|
|
1011
|
+
*/ HttpStatus[HttpStatus["Conflict"] = 409] = "Conflict";
|
|
1012
|
+
/**
|
|
1013
|
+
* Indicates that the resource requested is no longer available and will not be available again.
|
|
1014
|
+
* This should be used when a resource has been intentionally removed and the resource should be
|
|
1015
|
+
* purged. Upon receiving a 410 status code, the client should not request the resource in the
|
|
1016
|
+
* future. Clients such as search engines should remove the resource from their indices. Most use
|
|
1017
|
+
* cases do not require clients and search engines to purge the resource, and a "404 Not Found"
|
|
1018
|
+
* may be used instead.
|
|
1019
|
+
*/ HttpStatus[HttpStatus["Gone"] = 410] = "Gone";
|
|
1020
|
+
/**
|
|
1021
|
+
* The request did not specify the length of its content, which is required by the requested
|
|
1022
|
+
* resource.
|
|
1023
|
+
*/ HttpStatus[HttpStatus["LengthRequired"] = 411] = "LengthRequired";
|
|
1024
|
+
/** The server does not meet one of the preconditions that the requester put on the request. */ HttpStatus[HttpStatus["PreconditionFailed"] = 412] = "PreconditionFailed";
|
|
1025
|
+
/**
|
|
1026
|
+
* The request is larger than the server is willing or able to process. Previously called "Request
|
|
1027
|
+
* Entity Too Large".
|
|
1028
|
+
*/ HttpStatus[HttpStatus["PayloadTooLarge"] = 413] = "PayloadTooLarge";
|
|
1029
|
+
/**
|
|
1030
|
+
* The URI provided was too long for the server to process. Often the result of too much data
|
|
1031
|
+
* being encoded as a query-string of a GET request, in which case it should be converted to a
|
|
1032
|
+
* POST request. Called "Request-URI Too Long" previously.
|
|
1033
|
+
*/ HttpStatus[HttpStatus["UriTooLong"] = 414] = "UriTooLong";
|
|
1034
|
+
/**
|
|
1035
|
+
* The request entity has a media type which the server or resource does not support. For example,
|
|
1036
|
+
* the client uploads an image as image/svg+xml, but the server requires that images use a
|
|
1037
|
+
* different format.
|
|
1038
|
+
*/ HttpStatus[HttpStatus["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
|
|
1039
|
+
/**
|
|
1040
|
+
* The client has asked for a portion of the file (byte serving), but the server cannot supply
|
|
1041
|
+
* that portion. For example, if the client asked for a part of the file that lies beyond the end
|
|
1042
|
+
* of the file. Called "Requested Range Not Satisfiable" previously.
|
|
1043
|
+
*/ HttpStatus[HttpStatus["RangeNotSatisfiable"] = 416] = "RangeNotSatisfiable";
|
|
1044
|
+
/** The server cannot meet the requirements of the Expect request-header field. */ HttpStatus[HttpStatus["ExpectationFailed"] = 417] = "ExpectationFailed";
|
|
1045
|
+
/**
|
|
1046
|
+
* This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324,
|
|
1047
|
+
* Hyper Text Coffee Pot Control Protocol, and is not expected to be implemented by actual HTTP
|
|
1048
|
+
* servers. The RFC specifies this code should be returned by teapots requested to brew coffee.
|
|
1049
|
+
* This HTTP status is used as an Easter egg in some websites, including Google.com.
|
|
1050
|
+
*/ HttpStatus[HttpStatus["IAmATeapot"] = 418] = "IAmATeapot";
|
|
1051
|
+
/**
|
|
1052
|
+
* The request was directed at a server that is not able to produce a response (for example
|
|
1053
|
+
* because a connection reuse).
|
|
1054
|
+
*/ HttpStatus[HttpStatus["MisdirectedRequest"] = 421] = "MisdirectedRequest";
|
|
1055
|
+
/** The request was well-formed but was unable to be followed due to semantic errors. */ HttpStatus[HttpStatus["UnprocessableEntity"] = 422] = "UnprocessableEntity";
|
|
1056
|
+
/** The resource that is being accessed is locked. */ HttpStatus[HttpStatus["Locked"] = 423] = "Locked";
|
|
1057
|
+
/** The request failed due to failure of a previous request (e.g., a PROPPATCH). */ HttpStatus[HttpStatus["FailedDependency"] = 424] = "FailedDependency";
|
|
1058
|
+
/**
|
|
1059
|
+
* The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header
|
|
1060
|
+
* field.
|
|
1061
|
+
*/ HttpStatus[HttpStatus["UpgradeRequired"] = 426] = "UpgradeRequired";
|
|
1062
|
+
/**
|
|
1063
|
+
* The origin server requires the request to be conditional. Intended to prevent "the 'lost
|
|
1064
|
+
* update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the
|
|
1065
|
+
* server, when meanwhile a third party has modified the state on the server, leading to a
|
|
1066
|
+
* conflict."
|
|
1067
|
+
*/ HttpStatus[HttpStatus["PreconditionRequired"] = 428] = "PreconditionRequired";
|
|
1068
|
+
/**
|
|
1069
|
+
* The user has sent too many requests in a given amount of time. Intended for use with
|
|
1070
|
+
* rate-limiting schemes.
|
|
1071
|
+
*/ HttpStatus[HttpStatus["TooManyRequests"] = 429] = "TooManyRequests";
|
|
1072
|
+
/**
|
|
1073
|
+
* The server is unwilling to process the request because either an individual header field, or
|
|
1074
|
+
* all the header fields collectively, are too large.
|
|
1075
|
+
*/ HttpStatus[HttpStatus["RequestHeaderFieldsTooLarge"] = 431] = "RequestHeaderFieldsTooLarge";
|
|
1076
|
+
/**
|
|
1077
|
+
* A server operator has received a legal demand to deny access to a resource or to a set of
|
|
1078
|
+
* resources that includes the requested resource. The code 451 was chosen as a reference to the
|
|
1079
|
+
* novel Fahrenheit 451.
|
|
1080
|
+
*/ HttpStatus[HttpStatus["UnavailableForLegalReasons"] = 451] = "UnavailableForLegalReasons";
|
|
1081
|
+
/**
|
|
1082
|
+
* A generic error message, given when an unexpected condition was encountered and no more
|
|
1083
|
+
* specific message is suitable.
|
|
1084
|
+
*/ HttpStatus[HttpStatus["InternalServerError"] = 500] = "InternalServerError";
|
|
1085
|
+
/**
|
|
1086
|
+
* The server either does not recognize the request method, or it lacks the ability to fulfill the
|
|
1087
|
+
* request. Usually this implies future availability (e.g., a new feature of a web-service API).
|
|
1088
|
+
*/ HttpStatus[HttpStatus["NotImplemented"] = 501] = "NotImplemented";
|
|
1089
|
+
/**
|
|
1090
|
+
* The server was acting as a gateway or proxy and received an invalid response from the upstream
|
|
1091
|
+
* server.
|
|
1092
|
+
*/ HttpStatus[HttpStatus["BadGateway"] = 502] = "BadGateway";
|
|
1093
|
+
/**
|
|
1094
|
+
* The server is currently unavailable (because it is overloaded or down for maintenance).
|
|
1095
|
+
* Generally, this is a temporary state.
|
|
1096
|
+
*/ HttpStatus[HttpStatus["ServiceUnavailable"] = 503] = "ServiceUnavailable";
|
|
1097
|
+
/**
|
|
1098
|
+
* The server was acting as a gateway or proxy and did not receive a timely response from the
|
|
1099
|
+
* upstream server.
|
|
1100
|
+
*/ HttpStatus[HttpStatus["GatewayTimeout"] = 504] = "GatewayTimeout";
|
|
1101
|
+
/** The server does not support the HTTP protocol version used in the request */ HttpStatus[HttpStatus["HttpVersionNotSupported"] = 505] = "HttpVersionNotSupported";
|
|
1102
|
+
/** Transparent content negotiation for the request results in a circular reference. */ HttpStatus[HttpStatus["VariantAlsoNegotiates"] = 506] = "VariantAlsoNegotiates";
|
|
1103
|
+
/** The server is unable to store the representation needed to complete the request. */ HttpStatus[HttpStatus["InsufficientStorage"] = 507] = "InsufficientStorage";
|
|
1104
|
+
/** The server detected an infinite loop while processing the request. */ HttpStatus[HttpStatus["LoopDetected"] = 508] = "LoopDetected";
|
|
1105
|
+
/** Further extensions to the request are required for the server to fulfill it. */ HttpStatus[HttpStatus["NotExtended"] = 510] = "NotExtended";
|
|
1106
|
+
/**
|
|
1107
|
+
* The client needs to authenticate to gain network access. Intended for use by intercepting
|
|
1108
|
+
* proxies used to control access to the network (e.g., "captive portals" used to require
|
|
1109
|
+
* agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
|
|
1110
|
+
*/ HttpStatus[HttpStatus["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
|
|
1111
|
+
return HttpStatus;
|
|
1112
|
+
}({});
|
|
1113
|
+
function getQwikRouterServerData(requestEv) {
|
|
1114
|
+
const { params, request, status, locale, originalUrl } = requestEv;
|
|
1115
|
+
const requestHeaders = {};
|
|
1116
|
+
request.headers.forEach((value, key) => requestHeaders[key] = value);
|
|
1117
|
+
const action = requestEv.sharedMap.get(RequestEvSharedActionId);
|
|
1118
|
+
const actionResult = requestEv.sharedMap.get("@actionResult");
|
|
1119
|
+
const formData = requestEv.sharedMap.get(RequestEvSharedActionFormData);
|
|
1120
|
+
const routeName = requestEv.sharedMap.get(RequestRouteName);
|
|
1121
|
+
const nonce = requestEv.sharedMap.get(RequestEvSharedNonce);
|
|
1122
|
+
const headers = requestEv.request.headers;
|
|
1123
|
+
const reconstructedUrl = new URL(originalUrl.pathname + originalUrl.search, originalUrl);
|
|
1124
|
+
const host = headers.get("X-Forwarded-Host");
|
|
1125
|
+
const protocol = headers.get("X-Forwarded-Proto");
|
|
1126
|
+
if (host) {
|
|
1127
|
+
reconstructedUrl.port = "";
|
|
1128
|
+
reconstructedUrl.host = host;
|
|
1129
|
+
}
|
|
1130
|
+
if (protocol) reconstructedUrl.protocol = protocol;
|
|
1131
|
+
const routeLoaderCtx = getRouteLoaderCtx(requestEv);
|
|
1132
|
+
const loaderValues = getRouteLoaderValues(requestEv);
|
|
1133
|
+
return {
|
|
1134
|
+
url: reconstructedUrl.href,
|
|
1135
|
+
requestHeaders,
|
|
1136
|
+
renderMode: getRequestMode(requestEv),
|
|
1137
|
+
locale: locale(),
|
|
1138
|
+
nonce,
|
|
1139
|
+
containerAttributes: { [Q_ROUTE]: routeName },
|
|
1140
|
+
qwikrouter: {
|
|
1141
|
+
routeName,
|
|
1142
|
+
ev: requestEv,
|
|
1143
|
+
params: { ...params },
|
|
1144
|
+
loadedRoute: getRequestRoute(requestEv),
|
|
1145
|
+
routeLoaderCtx,
|
|
1146
|
+
loaderValues,
|
|
1147
|
+
response: {
|
|
1148
|
+
status: status(),
|
|
1149
|
+
statusMessage: requestEv.sharedMap.get(RequestEvHttpStatusMessage),
|
|
1150
|
+
action,
|
|
1151
|
+
actionResult,
|
|
1152
|
+
formData
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
var loadHttpError = () => import("./http-error.qwik.mjs");
|
|
1158
|
+
function createResolveRequestHandlers() {
|
|
1159
|
+
const resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderHandler) => {
|
|
1160
|
+
const routeLoaders = [];
|
|
1161
|
+
const routeActions = [];
|
|
1162
|
+
const requestHandlers = [];
|
|
1163
|
+
const isPageRoute = !!isLastModulePageRoute(route.$mods$);
|
|
1164
|
+
if (isPageRoute) {
|
|
1165
|
+
/**
|
|
1166
|
+
* JSON request wrapper must be before all middleware so it can rewrite the URL and catch
|
|
1167
|
+
* redirects/errors from plugin/route middleware via try/catch on next()
|
|
1168
|
+
*/ requestHandlers.push(jsonRequestWrapper());
|
|
1169
|
+
requestHandlers.push(serverErrorMiddleware(route, renderHandler));
|
|
1170
|
+
}
|
|
1171
|
+
if (serverPlugins) _resolveRequestHandlers(routeLoaders, routeActions, requestHandlers, serverPlugins, isPageRoute, method);
|
|
1172
|
+
if (!route.$notFound$ && isPageRoute && (method === "POST" || method === "GET")) requestHandlers.push(runServerFunction);
|
|
1173
|
+
const routeModules = route.$mods$;
|
|
1174
|
+
_resolveRequestHandlers(routeLoaders, routeActions, requestHandlers, routeModules, isPageRoute, method, isPageRoute);
|
|
1175
|
+
const routeName = route.$routeName$;
|
|
1176
|
+
if (checkOrigin && (method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE")) {
|
|
1177
|
+
if (checkOrigin === "lax-proto") requestHandlers.unshift(csrfLaxProtoCheckMiddleware);
|
|
1178
|
+
else requestHandlers.unshift(csrfCheckMiddleware);
|
|
1179
|
+
}
|
|
1180
|
+
if (isPageRoute) {
|
|
1181
|
+
requestHandlers.push(loaderHandler(routeLoaders, route.$loaderPaths$));
|
|
1182
|
+
requestHandlers.push(actionHandler(routeActions));
|
|
1183
|
+
requestHandlers.push(route.$notFound$ ? fixStaticTrailingSlash : fixTrailingSlash);
|
|
1184
|
+
}
|
|
1185
|
+
if (isPageRoute) {
|
|
1186
|
+
requestHandlers.push((ev) => {
|
|
1187
|
+
ev.sharedMap.set(RequestRouteName, routeName);
|
|
1188
|
+
});
|
|
1189
|
+
requestHandlers.push(actionsMiddleware(routeActions));
|
|
1190
|
+
requestHandlers.push(loadersMiddleware(routeLoaders, route));
|
|
1191
|
+
requestHandlers.push(eTagMiddleware(route));
|
|
1192
|
+
requestHandlers.push(renderHandler);
|
|
1193
|
+
}
|
|
1194
|
+
return requestHandlers;
|
|
1195
|
+
};
|
|
1196
|
+
function _resolveRequestHandlers(routeLoaders, routeActions, requestHandlers, routeModules, collectActions, method, guardPageHandlersForLoader = false) {
|
|
1197
|
+
for (let i = 0; i < routeModules.length; i++) {
|
|
1198
|
+
const routeModule = routeModules[i];
|
|
1199
|
+
const moduleHandlers = getModuleRequestHandlers(routeModule, method);
|
|
1200
|
+
const shouldGuardPageHandlers = guardPageHandlersForLoader && i === routeModules.length - 1;
|
|
1201
|
+
requestHandlers.push(...shouldGuardPageHandlers ? moduleHandlers.map((handler) => guardPageHandlerForLoader(routeModule, handler)) : moduleHandlers);
|
|
1202
|
+
if (collectActions) {
|
|
1203
|
+
for (const module of Object.values(routeModule)) if (typeof module === "function") {
|
|
1204
|
+
if (module.__brand === "server_loader") routeLoaders.push(module);
|
|
1205
|
+
else if (module.__brand === "server_action") routeActions.push(module);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
function getModuleRequestHandlers(routeModule, method) {
|
|
1211
|
+
const handlers = [];
|
|
1212
|
+
addRequestHandlers(handlers, routeModule.onRequest);
|
|
1213
|
+
let methodReqHandler;
|
|
1214
|
+
switch (method) {
|
|
1215
|
+
case "GET":
|
|
1216
|
+
methodReqHandler = routeModule.onGet;
|
|
1217
|
+
break;
|
|
1218
|
+
case "POST":
|
|
1219
|
+
methodReqHandler = routeModule.onPost;
|
|
1220
|
+
break;
|
|
1221
|
+
case "PUT":
|
|
1222
|
+
methodReqHandler = routeModule.onPut;
|
|
1223
|
+
break;
|
|
1224
|
+
case "PATCH":
|
|
1225
|
+
methodReqHandler = routeModule.onPatch;
|
|
1226
|
+
break;
|
|
1227
|
+
case "DELETE":
|
|
1228
|
+
methodReqHandler = routeModule.onDelete;
|
|
1229
|
+
break;
|
|
1230
|
+
case "OPTIONS":
|
|
1231
|
+
methodReqHandler = routeModule.onOptions;
|
|
1232
|
+
break;
|
|
1233
|
+
case "HEAD": methodReqHandler = routeModule.onHead;
|
|
1234
|
+
}
|
|
1235
|
+
addRequestHandlers(handlers, methodReqHandler);
|
|
1236
|
+
return handlers;
|
|
1237
|
+
}
|
|
1238
|
+
function addRequestHandlers(handlers, handler) {
|
|
1239
|
+
if (typeof handler === "function") handlers.push(handler);
|
|
1240
|
+
else if (Array.isArray(handler)) handlers.push(...handler);
|
|
1241
|
+
}
|
|
1242
|
+
function guardPageHandlerForLoader(routeModule, handler) {
|
|
1243
|
+
return (requestEv) => {
|
|
1244
|
+
if (requestEv.sharedMap.has("@isQLoader")) {
|
|
1245
|
+
if (!moduleHasLoader(routeModule, requestEv.sharedMap.get("@loaderId"))) return;
|
|
1246
|
+
}
|
|
1247
|
+
return handler(requestEv);
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
function moduleHasLoader(routeModule, loaderId) {
|
|
1251
|
+
if (typeof loaderId !== "string") return false;
|
|
1252
|
+
for (const value of Object.values(routeModule)) if (checkBrand(value, "server_loader") && matchesRouteLoaderId(value, loaderId)) return true;
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
const checkBrand = (obj, brand) => {
|
|
1256
|
+
return obj && typeof obj === "function" && obj.__brand === brand;
|
|
1257
|
+
};
|
|
1258
|
+
function actionsMiddleware(routeActions) {
|
|
1259
|
+
return async (requestEvent) => {
|
|
1260
|
+
const requestEv = requestEvent;
|
|
1261
|
+
if (requestEv.headersSent) {
|
|
1262
|
+
requestEv.exit();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const { method } = requestEv;
|
|
1266
|
+
if (isDev && method === "GET") {
|
|
1267
|
+
if (requestEv.query.has("qaction")) console.warn("Seems like you are submitting a Qwik Action via GET request. Qwik Actions should be submitted via POST request.\nMake sure your <form> has method=\"POST\" attribute, like this: <form method=\"POST\">");
|
|
1268
|
+
}
|
|
1269
|
+
if (method === "POST") {
|
|
1270
|
+
const selectedActionId = requestEv.query.get(QACTION_KEY);
|
|
1271
|
+
if (selectedActionId) {
|
|
1272
|
+
const serverActionsMap = globalThis._qwikActionsMap;
|
|
1273
|
+
const action = routeActions.find((action) => action.__id === selectedActionId) ?? serverActionsMap?.get(selectedActionId);
|
|
1274
|
+
if (action) {
|
|
1275
|
+
requestEv.sharedMap.set(RequestEvSharedActionId, selectedActionId);
|
|
1276
|
+
const data = await requestEv.parseBody();
|
|
1277
|
+
if (!data || typeof data !== "object") throw new Error(`Expected request data for the action id ${selectedActionId} to be an object`);
|
|
1278
|
+
const result = await runValidators(requestEv, action.__validators, data);
|
|
1279
|
+
let actionResult;
|
|
1280
|
+
if (!result.success) actionResult = requestEv.fail(result.status ?? 500, result.error);
|
|
1281
|
+
else {
|
|
1282
|
+
const actionResolved = isDev ? await measure(requestEv, action.__qrl.getHash(), () => action.__qrl.call(requestEv, result.data, requestEv)) : await action.__qrl.call(requestEv, result.data, requestEv);
|
|
1283
|
+
throwIfControlFlowSignal(actionResolved);
|
|
1284
|
+
if (isDev) verifySerializable(actionResolved, action.__qrl);
|
|
1285
|
+
actionResult = actionResolved;
|
|
1286
|
+
}
|
|
1287
|
+
requestEv.sharedMap.set("@actionResult", actionResult);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function loadersMiddleware(routeLoaders, route) {
|
|
1294
|
+
return (requestEvent) => {
|
|
1295
|
+
if (requestEvent.headersSent) {
|
|
1296
|
+
requestEvent.exit();
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (routeLoaders.length === 0) return;
|
|
1300
|
+
setLoaderData(requestEvent, routeLoaders, route);
|
|
1301
|
+
let allBlockSSRLoaders;
|
|
1302
|
+
for (let i = 0; i < routeLoaders.length; i++) {
|
|
1303
|
+
const loader = routeLoaders[i];
|
|
1304
|
+
const promise = loadRouteLoader(loader, requestEvent);
|
|
1305
|
+
promise.catch(() => {});
|
|
1306
|
+
if (loader.__blockSSR) allBlockSSRLoaders = allBlockSSRLoaders ? allBlockSSRLoaders.then(() => promise) : promise;
|
|
1307
|
+
}
|
|
1308
|
+
return allBlockSSRLoaders;
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function setLoaderData(requestEv, routeLoaders, route) {
|
|
1312
|
+
if (routeLoaders.length === 0) return;
|
|
1313
|
+
const routeLoaderCtx = getRouteLoaderCtx(requestEv);
|
|
1314
|
+
if (route.$loaderPaths$) Object.assign(routeLoaderCtx.loaderPaths, route.$loaderPaths$);
|
|
1315
|
+
setRouteLoaders(requestEv, routeLoaders);
|
|
1316
|
+
}
|
|
1317
|
+
function eTagMiddleware(route) {
|
|
1318
|
+
return (requestEv) => {
|
|
1319
|
+
if (requestEv.headersSent) return;
|
|
1320
|
+
if (requestEv.method !== "GET") return;
|
|
1321
|
+
const mods = route.$mods$;
|
|
1322
|
+
if (!mods.some((m) => {
|
|
1323
|
+
if (!m) return false;
|
|
1324
|
+
if (m.routeConfig) return typeof m.routeConfig === "function" || m.routeConfig.eTag !== void 0 || m.routeConfig.cacheKey !== void 0;
|
|
1325
|
+
return m.eTag !== void 0 || m.cacheKey !== void 0;
|
|
1326
|
+
})) return;
|
|
1327
|
+
const loaderValues = getRouteLoaderValues(requestEv);
|
|
1328
|
+
const getData = (loaderOrAction) => {
|
|
1329
|
+
const id = loaderOrAction.__id;
|
|
1330
|
+
if (loaderOrAction.__brand === "server_loader") {
|
|
1331
|
+
if (!(id in loaderValues)) throw new Error("Loader not executed for this request.");
|
|
1332
|
+
return loaderValues[id];
|
|
1333
|
+
}
|
|
1334
|
+
return requestEv.sharedMap.get("@actionId") === id ? requestEv.sharedMap.get("@actionResult") : void 0;
|
|
1335
|
+
};
|
|
1336
|
+
const routeLocation = {
|
|
1337
|
+
params: requestEv.params,
|
|
1338
|
+
url: requestEv.url,
|
|
1339
|
+
isNavigating: false,
|
|
1340
|
+
prevUrl: void 0
|
|
1341
|
+
};
|
|
1342
|
+
const status = requestEv.status();
|
|
1343
|
+
const config = resolveRouteConfig(getData, routeLocation, mods, "", status);
|
|
1344
|
+
const headProps = {
|
|
1345
|
+
head: config.head,
|
|
1346
|
+
status,
|
|
1347
|
+
withLocale: (fn) => fn(),
|
|
1348
|
+
resolveValue: getData,
|
|
1349
|
+
...routeLocation
|
|
1350
|
+
};
|
|
1351
|
+
const resolvedETag = resolveETag(config.eTag, headProps);
|
|
1352
|
+
const normalizedETag = resolvedETag !== null ? normalizeETag(resolvedETag) : "";
|
|
1353
|
+
if (normalizedETag && performETagMatch(requestEv, normalizedETag)) return;
|
|
1354
|
+
if (MAX_CACHE_SIZE <= 0) return;
|
|
1355
|
+
const cacheKey = resolveCacheKey(config.cacheKey, defaultSsrCacheKey, requestEv, normalizedETag);
|
|
1356
|
+
if (!cacheKey) return;
|
|
1357
|
+
const cached = getCachedSsr(cacheKey);
|
|
1358
|
+
if (cached) {
|
|
1359
|
+
requestEv.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
1360
|
+
requestEv.headers.set("X-SSR-Cache", "HIT");
|
|
1361
|
+
if (!normalizedETag) {
|
|
1362
|
+
if (performETagMatch(requestEv, cached.eTag)) return;
|
|
1363
|
+
} else setETagHeader(requestEv, cached.eTag);
|
|
1364
|
+
requestEv.send(status, cached.body);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
requestEv.sharedMap.set(RequestEvETagCacheKey, {
|
|
1368
|
+
key: cacheKey,
|
|
1369
|
+
eTag: normalizedETag
|
|
1370
|
+
});
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function serverErrorMiddleware(route, renderHandler) {
|
|
1374
|
+
return async (requestEv) => {
|
|
1375
|
+
try {
|
|
1376
|
+
await requestEv.next();
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
if (!(e instanceof ServerError) || requestEv.headersSent) throw e;
|
|
1379
|
+
const accept = requestEv.request.headers.get("Accept");
|
|
1380
|
+
if (accept && !accept.includes("text/html")) throw e;
|
|
1381
|
+
const status = e.status;
|
|
1382
|
+
requestEv.status(status);
|
|
1383
|
+
const errorLoader = route.$errorLoader$;
|
|
1384
|
+
route.$mods$ = errorLoader ? await Promise.all(errorLoader.map((load) => load())) : [await loadHttpError()];
|
|
1385
|
+
requestEv.sharedMap.set(RequestEvHttpStatusMessage, typeof e.data === "string" ? e.data : "Server Error");
|
|
1386
|
+
clearErrorResponseData(requestEv);
|
|
1387
|
+
await renderHandler(requestEv);
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function clearErrorResponseData(requestEv) {
|
|
1392
|
+
clearRouteLoaderData(requestEv);
|
|
1393
|
+
requestEv.sharedMap.delete(RequestEvSharedActionId);
|
|
1394
|
+
requestEv.sharedMap.delete(RequestEvSharedActionFormData);
|
|
1395
|
+
requestEv.sharedMap.delete("@actionResult");
|
|
1396
|
+
}
|
|
1397
|
+
async function runValidators(requestEv, validators, data) {
|
|
1398
|
+
let lastResult = {
|
|
1399
|
+
success: true,
|
|
1400
|
+
data
|
|
1401
|
+
};
|
|
1402
|
+
if (validators) for (const validator of validators) {
|
|
1403
|
+
if (isDev) lastResult = await measure(requestEv, `validator$`, () => validator.validate(requestEv, data));
|
|
1404
|
+
else lastResult = await validator.validate(requestEv, data);
|
|
1405
|
+
if (!lastResult.success) return lastResult;
|
|
1406
|
+
else data = lastResult.data;
|
|
1407
|
+
}
|
|
1408
|
+
return lastResult;
|
|
1409
|
+
}
|
|
1410
|
+
function isAsyncIterator(obj) {
|
|
1411
|
+
return obj ? typeof obj === "object" && Symbol.asyncIterator in obj : false;
|
|
1412
|
+
}
|
|
1413
|
+
async function runServerFunction(ev) {
|
|
1414
|
+
const serverFnHash = ev.query.get(QFN_KEY);
|
|
1415
|
+
if (serverFnHash && ev.request.headers.get("X-QRL") === serverFnHash && ev.request.headers.get("Content-Type") === "application/qwik-json") {
|
|
1416
|
+
ev.exit();
|
|
1417
|
+
const data = await ev.parseBody();
|
|
1418
|
+
if (!Array.isArray(data) || !(Array.isArray(data[0]) || data[0] === void 0)) throw ev.error(500, "Invalid request");
|
|
1419
|
+
const qrl = inlinedQrl(null, serverFnHash, data.slice(1));
|
|
1420
|
+
let result;
|
|
1421
|
+
try {
|
|
1422
|
+
if (isDev) result = await measure(ev, `server_${serverFnHash}`, () => qrl.apply(ev, data[0]));
|
|
1423
|
+
else result = await qrl.apply(ev, data[0]);
|
|
1424
|
+
} catch (err) {
|
|
1425
|
+
if (err instanceof ServerError) throw ev.error(err.status, err.data);
|
|
1426
|
+
console.error(`Server function ${serverFnHash} failed:`, err);
|
|
1427
|
+
throw ev.error(500, "Invalid request");
|
|
1428
|
+
}
|
|
1429
|
+
throwIfControlFlowSignal(result);
|
|
1430
|
+
if (isAsyncIterator(result)) await streamServerFunctionResult(ev, result, qrl);
|
|
1431
|
+
else {
|
|
1432
|
+
verifySerializable(result, qrl);
|
|
1433
|
+
ev.headers.set("Content-Type", "application/qwik-json");
|
|
1434
|
+
const message = await _serialize(result);
|
|
1435
|
+
ev.send(200, message);
|
|
1436
|
+
}
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
async function streamServerFunctionResult(ev, result, qrl) {
|
|
1441
|
+
ev.headers.set("Content-Type", "text/qwik-json-stream");
|
|
1442
|
+
const stream = ev.getWritableStream().getWriter();
|
|
1443
|
+
for await (const item of result) {
|
|
1444
|
+
if (isDev) verifySerializable(item, qrl);
|
|
1445
|
+
const message = await _serialize(item);
|
|
1446
|
+
if (ev.signal.aborted) break;
|
|
1447
|
+
await stream.write(encoder.encode(`${message}\n`)).catch((err) => {
|
|
1448
|
+
if (isDev) console.error(`Server function ${qrl.getSymbol()} stream write failed:`, err);
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
await stream.close().catch((err) => {
|
|
1452
|
+
if (isDev) console.error(`Server function ${qrl.getSymbol()} stream close failed:`, err);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
function fixTrailingSlash(ev) {
|
|
1456
|
+
const { basePathname, originalUrl } = ev;
|
|
1457
|
+
const { pathname, search } = originalUrl;
|
|
1458
|
+
if (!pathname.startsWith("/") || pathname.startsWith("//")) return;
|
|
1459
|
+
if (pathname !== basePathname && !pathname.endsWith(".html")) {
|
|
1460
|
+
if (!globalThis.__NO_TRAILING_SLASH__) {
|
|
1461
|
+
if (!pathname.endsWith("/")) throw ev.redirect(HttpStatus.MovedPermanently, ensureSlash(pathname) + search);
|
|
1462
|
+
} else if (pathname.endsWith("/")) throw ev.redirect(HttpStatus.MovedPermanently, pathname.slice(0, pathname.length - 1) + search);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Prerendered pages are pruned from the production server route plan, so a request missing the
|
|
1467
|
+
* canonical trailing slash lands here as not found. Redirect it when the canonical path was
|
|
1468
|
+
* prerendered, and leave a genuine 404 alone.
|
|
1469
|
+
*/ function fixStaticTrailingSlash(ev) {
|
|
1470
|
+
const canonicalUrl = new URL(ev.originalUrl);
|
|
1471
|
+
canonicalUrl.pathname = globalThis.__NO_TRAILING_SLASH__ ? canonicalUrl.pathname.replace(/\/$/, "") : ensureSlash(canonicalUrl.pathname);
|
|
1472
|
+
if (isStaticPath(ev.request.method, canonicalUrl)) fixTrailingSlash(ev);
|
|
1473
|
+
}
|
|
1474
|
+
function verifySerializable(data, qrl) {
|
|
1475
|
+
try {
|
|
1476
|
+
_verifySerializable(data, void 0);
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
if (e instanceof Error && qrl.dev) e.loc = qrl.dev;
|
|
1479
|
+
throw e;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
const isQrl = (value) => {
|
|
1483
|
+
return typeof value === "function" && typeof value.getSymbol === "function";
|
|
1484
|
+
};
|
|
1485
|
+
function isLastModulePageRoute(routeModules) {
|
|
1486
|
+
const lastRouteModule = routeModules[routeModules.length - 1];
|
|
1487
|
+
return lastRouteModule && typeof lastRouteModule.default === "function";
|
|
1488
|
+
}
|
|
1489
|
+
function getPathname(url) {
|
|
1490
|
+
url = new URL(url);
|
|
1491
|
+
if (!globalThis.__NO_TRAILING_SLASH__) {
|
|
1492
|
+
if (!url.pathname.endsWith("/")) url.pathname = ensureSlash(url.pathname);
|
|
1493
|
+
} else if (url.pathname.endsWith("/")) url.pathname = url.pathname.slice(0, -1);
|
|
1494
|
+
const search = url.search.slice(1).replaceAll(/&?q(action|data|func|loaders)=[^&]+/g, "");
|
|
1495
|
+
return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
|
|
1496
|
+
}
|
|
1497
|
+
function csrfLaxProtoCheckMiddleware(requestEv) {
|
|
1498
|
+
checkCSRF(requestEv, "lax-proto");
|
|
1499
|
+
}
|
|
1500
|
+
function csrfCheckMiddleware(requestEv) {
|
|
1501
|
+
checkCSRF(requestEv);
|
|
1502
|
+
}
|
|
1503
|
+
function checkCSRF(requestEv, laxProto) {
|
|
1504
|
+
if (!requestEv.request.headers.get("content-type") || isContentType(requestEv.request.headers, "application/x-www-form-urlencoded", "multipart/form-data", "text/plain")) {
|
|
1505
|
+
const inputOrigin = requestEv.request.headers.get("origin");
|
|
1506
|
+
const origin = requestEv.url.origin;
|
|
1507
|
+
let forbidden = inputOrigin !== origin;
|
|
1508
|
+
if (forbidden && laxProto && inputOrigin?.replace(/^http(s)?/g, "") === origin.replace(/^http(s)?/g, "")) forbidden = false;
|
|
1509
|
+
if (forbidden) throw requestEv.error(403, `CSRF check failed. Cross-site ${requestEv.method} form submissions are forbidden.
|
|
1510
|
+
The request origin "${inputOrigin}" does not match the server origin "${origin}".`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
function renderQwikMiddleware(render) {
|
|
1514
|
+
return async (requestEv) => {
|
|
1515
|
+
if (requestEv.headersSent) return;
|
|
1516
|
+
const responseHeaders = requestEv.headers;
|
|
1517
|
+
if (!responseHeaders.has("Content-Type")) responseHeaders.set("Content-Type", "text/html; charset=utf-8");
|
|
1518
|
+
const cachePlan = requestEv.sharedMap.get(RequestEvETagCacheKey);
|
|
1519
|
+
const { readable, writable } = new TextEncoderStream();
|
|
1520
|
+
const writableStream = requestEv.getWritableStream();
|
|
1521
|
+
let cacheChunks;
|
|
1522
|
+
let pipeSource = readable;
|
|
1523
|
+
if (cachePlan) {
|
|
1524
|
+
cacheChunks = [];
|
|
1525
|
+
const capture = new TransformStream({ transform(chunk, controller) {
|
|
1526
|
+
cacheChunks.push(chunk);
|
|
1527
|
+
controller.enqueue(chunk);
|
|
1528
|
+
} });
|
|
1529
|
+
pipeSource = readable.pipeThrough(capture);
|
|
1530
|
+
}
|
|
1531
|
+
let pipeError;
|
|
1532
|
+
const pipe = pipeSource.pipeTo(writableStream, { preventClose: true }).catch((error) => {
|
|
1533
|
+
pipeError = error;
|
|
1534
|
+
});
|
|
1535
|
+
const stream = writable.getWriter();
|
|
1536
|
+
try {
|
|
1537
|
+
const isStatic = getRequestMode(requestEv) === "static";
|
|
1538
|
+
const serverData = getQwikRouterServerData(requestEv);
|
|
1539
|
+
const result = await render({
|
|
1540
|
+
base: requestEv.basePathname + "build/",
|
|
1541
|
+
stream,
|
|
1542
|
+
streaming: isStatic ? { outOfOrder: false } : void 0,
|
|
1543
|
+
serverData,
|
|
1544
|
+
containerAttributes: {
|
|
1545
|
+
["q:render"]: isStatic ? "static" : "",
|
|
1546
|
+
...serverData.containerAttributes
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
if (typeof result.html === "string") await stream.write(result.html);
|
|
1550
|
+
} finally {
|
|
1551
|
+
await stream.ready;
|
|
1552
|
+
await stream.close();
|
|
1553
|
+
await pipe;
|
|
1554
|
+
}
|
|
1555
|
+
if (pipeError) throw pipeError;
|
|
1556
|
+
if (cachePlan && cacheChunks && cacheChunks.length > 0) {
|
|
1557
|
+
const totalLength = cacheChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1558
|
+
const combined = new Uint8Array(totalLength);
|
|
1559
|
+
let offset = 0;
|
|
1560
|
+
for (const chunk of cacheChunks) {
|
|
1561
|
+
combined.set(chunk, offset);
|
|
1562
|
+
offset += chunk.length;
|
|
1563
|
+
}
|
|
1564
|
+
const html = new TextDecoder().decode(combined);
|
|
1565
|
+
const cachedETag = cachePlan.eTag || hash(html);
|
|
1566
|
+
setCachedSsr(cachePlan.key, {
|
|
1567
|
+
eTag: cachedETag,
|
|
1568
|
+
body: html
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
await writableStream.close();
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
function now() {
|
|
1575
|
+
return typeof performance !== "undefined" ? performance.now() : 0;
|
|
1576
|
+
}
|
|
1577
|
+
async function measure(requestEv, name, fn) {
|
|
1578
|
+
const start = now();
|
|
1579
|
+
try {
|
|
1580
|
+
return await fn();
|
|
1581
|
+
} finally {
|
|
1582
|
+
const duration = now() - start;
|
|
1583
|
+
let measurements = requestEv.sharedMap.get(RequestEvShareServerTiming);
|
|
1584
|
+
if (!measurements) requestEv.sharedMap.set(RequestEvShareServerTiming, measurements = []);
|
|
1585
|
+
measurements.push([name, duration]);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return {
|
|
1589
|
+
actionsMiddleware,
|
|
1590
|
+
checkBrand,
|
|
1591
|
+
checkCSRF,
|
|
1592
|
+
fixStaticTrailingSlash,
|
|
1593
|
+
fixTrailingSlash,
|
|
1594
|
+
getPathname,
|
|
1595
|
+
isLastModulePageRoute,
|
|
1596
|
+
isQrl,
|
|
1597
|
+
loadersMiddleware,
|
|
1598
|
+
measure,
|
|
1599
|
+
renderQwikMiddleware,
|
|
1600
|
+
resolveRequestHandlers,
|
|
1601
|
+
streamServerFunctionResult,
|
|
1602
|
+
verifySerializable
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
var { actionsMiddleware, checkBrand, checkCSRF, fixStaticTrailingSlash, fixTrailingSlash, getPathname, isLastModulePageRoute, isQrl, loadersMiddleware, measure, renderQwikMiddleware, resolveRequestHandlers, streamServerFunctionResult, verifySerializable } = createResolveRequestHandlers();
|
|
1606
|
+
/** @public */ var ESCAPE_HTML = /[&<>]/g;
|
|
1607
|
+
var escapeHtml = (s) => {
|
|
1608
|
+
return s.replace(ESCAPE_HTML, (c) => {
|
|
1609
|
+
switch (c) {
|
|
1610
|
+
case "&": return "&";
|
|
1611
|
+
case "<": return "<";
|
|
1612
|
+
case ">": return ">";
|
|
1613
|
+
default: return "";
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
};
|
|
1617
|
+
var COLOR_400 = "#006ce9";
|
|
1618
|
+
var COLOR_500 = "#713fc2";
|
|
1619
|
+
function minimalHtmlResponse(status, message) {
|
|
1620
|
+
if (typeof status !== "number") status = 500;
|
|
1621
|
+
if (typeof message === "string") message = escapeHtml(message);
|
|
1622
|
+
else message = "";
|
|
1623
|
+
const width = typeof message === "string" ? "600px" : "300px";
|
|
1624
|
+
const color = status >= 500 ? COLOR_500 : COLOR_400;
|
|
1625
|
+
return `
|
|
1626
|
+
<head>
|
|
1627
|
+
<meta charset="utf-8">
|
|
1628
|
+
<meta http-equiv="Status" content="${status}">
|
|
1629
|
+
<title>${status} ${message}</title>
|
|
1630
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
1631
|
+
<style>
|
|
1632
|
+
body { color: ${color}; background-color: #fafafa; padding: 30px; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Roboto, sans-serif; }
|
|
1633
|
+
p { max-width: ${width}; margin: 60px auto 30px auto; background: white; border-radius: 4px; box-shadow: 0px 0px 50px -20px ${color}; overflow: hidden; }
|
|
1634
|
+
strong { display: inline-block; padding: 15px; background: ${color}; color: white; }
|
|
1635
|
+
span { display: inline-block; padding: 15px; }
|
|
1636
|
+
</style>
|
|
1637
|
+
</head>
|
|
1638
|
+
<body><p><strong>${status}</strong> <span>${message}</span></p></body>
|
|
1639
|
+
`;
|
|
1640
|
+
}
|
|
1641
|
+
function getErrorHtml(status, e) {
|
|
1642
|
+
let message = "Server Error";
|
|
1643
|
+
if (e != null) {
|
|
1644
|
+
if (typeof e.message === "string") message = e.message;
|
|
1645
|
+
else message = String(e);
|
|
1646
|
+
}
|
|
1647
|
+
return `<html>` + minimalHtmlResponse(status, message) + `</html>`;
|
|
1648
|
+
}
|
|
1649
|
+
var ensureError = (e) => e instanceof Error || typeof e === "object" && e !== null ? e : new Error(String(e));
|
|
1650
|
+
async function runNext(requestEv, rebuildRouteInfo, resolve) {
|
|
1651
|
+
try {
|
|
1652
|
+
const isValidURL = (url) => new URL(url.pathname + url.search, url);
|
|
1653
|
+
isValidURL(requestEv.originalUrl);
|
|
1654
|
+
} catch {
|
|
1655
|
+
const status = 404;
|
|
1656
|
+
const message = "Resource Not Found";
|
|
1657
|
+
requestEv.status(status);
|
|
1658
|
+
requestEv.html(status, getErrorHtml(status, message));
|
|
1659
|
+
return new ServerError(status, message);
|
|
1660
|
+
}
|
|
1661
|
+
let rewriteAttempt = 1;
|
|
1662
|
+
async function runOnce() {
|
|
1663
|
+
try {
|
|
1664
|
+
await requestEv.next();
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
if (e instanceof RedirectMessage) {
|
|
1667
|
+
await requestEv.getWritableStream().close();
|
|
1668
|
+
return e;
|
|
1669
|
+
} else if (e instanceof RewriteMessage) {
|
|
1670
|
+
if (rewriteAttempt > 50) return /* @__PURE__ */ new Error(`Infinite rewrite loop`);
|
|
1671
|
+
rewriteAttempt += 1;
|
|
1672
|
+
const url = new URL(requestEv.url);
|
|
1673
|
+
url.pathname = e.pathname;
|
|
1674
|
+
const { loadedRoute, requestHandlers } = await rebuildRouteInfo(url);
|
|
1675
|
+
requestEv.resetRoute(loadedRoute, requestHandlers, url);
|
|
1676
|
+
return await runOnce();
|
|
1677
|
+
} else if (e instanceof AbortMessage) return;
|
|
1678
|
+
else if (e instanceof ServerError && !requestEv.headersSent) {
|
|
1679
|
+
const status = e.status;
|
|
1680
|
+
const accept = requestEv.request.headers.get("Accept");
|
|
1681
|
+
if (accept && !accept.includes("text/html")) {
|
|
1682
|
+
requestEv.headers.set("Content-Type", "application/qwik-json");
|
|
1683
|
+
requestEv.send(status, await _serialize(e.data));
|
|
1684
|
+
} else requestEv.html(status, getErrorHtml(status, e.data));
|
|
1685
|
+
return e;
|
|
1686
|
+
}
|
|
1687
|
+
if (!isDev$1) try {
|
|
1688
|
+
if (!requestEv.headersSent) {
|
|
1689
|
+
requestEv.headers.set("content-type", "text/html; charset=utf-8");
|
|
1690
|
+
requestEv.cacheControl({ noCache: true });
|
|
1691
|
+
requestEv.status(500);
|
|
1692
|
+
}
|
|
1693
|
+
const stream = requestEv.getWritableStream();
|
|
1694
|
+
if (!stream.locked) {
|
|
1695
|
+
const writer = stream.getWriter();
|
|
1696
|
+
await writer.write(encoder.encode(getErrorHtml(500, "Internal Server Error")));
|
|
1697
|
+
await writer.close();
|
|
1698
|
+
}
|
|
1699
|
+
} catch {
|
|
1700
|
+
console.error("Unable to render error page");
|
|
1701
|
+
}
|
|
1702
|
+
return e instanceof Error || typeof e === "object" && e !== null ? e : new Error(String(e));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
try {
|
|
1706
|
+
return await runOnce();
|
|
1707
|
+
} finally {
|
|
1708
|
+
if (!requestEv.isDirty()) resolve(null);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
function runQwikRouter(serverRequestEv, loadedRoute, requestHandlers, rebuildRouteInfo, basePathname = "/") {
|
|
1712
|
+
let resolve;
|
|
1713
|
+
const responsePromise = new Promise((r) => resolve = r);
|
|
1714
|
+
try {
|
|
1715
|
+
const requestEv = createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, basePathname, resolve);
|
|
1716
|
+
return {
|
|
1717
|
+
response: responsePromise,
|
|
1718
|
+
requestEv,
|
|
1719
|
+
completion: _asyncRequestStore ? _asyncRequestStore.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve) : runNext(requestEv, rebuildRouteInfo, resolve)
|
|
1720
|
+
};
|
|
1721
|
+
} catch (e) {
|
|
1722
|
+
resolve(null);
|
|
1723
|
+
return {
|
|
1724
|
+
response: responsePromise,
|
|
1725
|
+
requestEv: { headersSent: false },
|
|
1726
|
+
completion: Promise.resolve(ensureError(e))
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
export { isStaticPath as a, RewriteMessage as c, resolveRequestHandlers as i, mergeHeadersCookies as l, getErrorHtml as n, clearLoaderCache as o, renderQwikMiddleware as r, clearSsrCache as s, runQwikRouter as t };
|