@qwik.dev/router 2.0.0-beta.28 → 2.0.0-beta.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/lib/adapters/azure-swa/vite/index.mjs +31 -36
  2. package/lib/adapters/bun-server/vite/index.mjs +0 -3
  3. package/lib/adapters/cloud-run/vite/index.mjs +0 -3
  4. package/lib/adapters/cloudflare-pages/vite/index.mjs +15 -9
  5. package/lib/adapters/deno-server/vite/index.mjs +7 -5
  6. package/lib/adapters/netlify-edge/vite/index.mjs +13 -23
  7. package/lib/adapters/node-server/vite/index.mjs +0 -3
  8. package/lib/adapters/shared/vite/index.d.ts +1 -7
  9. package/lib/adapters/shared/vite/index.mjs +164 -136
  10. package/lib/adapters/ssg/vite/index.mjs +3 -4
  11. package/lib/adapters/vercel-edge/vite/index.mjs +25 -9
  12. package/lib/chunks/error-handler.mjs +26 -26
  13. package/lib/chunks/fs.mjs +28 -138
  14. package/lib/chunks/http-error.qwik.mjs +27 -0
  15. package/lib/chunks/not-found-wrapper.qwik.mjs +25 -0
  16. package/lib/chunks/pathname.mjs +105 -0
  17. package/lib/chunks/routing.qwik.mjs +592 -216
  18. package/lib/chunks/system.mjs +328 -0
  19. package/lib/chunks/use-functions.qwik.mjs +35 -0
  20. package/lib/chunks/worker-thread.mjs +271 -0
  21. package/lib/index.d.ts +136 -102
  22. package/lib/index.qwik.mjs +699 -751
  23. package/lib/middleware/aws-lambda/index.mjs +7 -1
  24. package/lib/middleware/azure-swa/index.mjs +7 -2
  25. package/lib/middleware/bun/index.mjs +20 -5
  26. package/lib/middleware/cloudflare-pages/index.mjs +8 -2
  27. package/lib/middleware/deno/index.mjs +19 -5
  28. package/lib/middleware/netlify-edge/index.mjs +8 -2
  29. package/lib/middleware/node/index.mjs +10 -14
  30. package/lib/middleware/request-handler/index.d.ts +82 -12
  31. package/lib/middleware/request-handler/index.mjs +661 -524
  32. package/lib/middleware/vercel-edge/index.mjs +8 -2
  33. package/lib/modules.d.ts +7 -4
  34. package/lib/ssg/index.d.ts +48 -16
  35. package/lib/ssg/index.mjs +320 -7
  36. package/lib/vite/index.d.ts +6 -0
  37. package/lib/vite/index.mjs +1098 -641
  38. package/modules.d.ts +7 -4
  39. package/package.json +4 -4
  40. package/lib/chunks/format-error.mjs +0 -137
  41. package/lib/chunks/index.mjs +0 -896
  42. package/lib/chunks/types.qwik.mjs +0 -22
@@ -1,13 +1,57 @@
1
1
  import { isServer } from '@qwik.dev/core/build';
2
- import { i as isPromise, j as QDATA_KEY, Q as Q_ROUTE, f as QFN_KEY, k as QLOADER_KEY, h as QACTION_KEY, d as loadRoute } from '../../chunks/routing.qwik.mjs';
2
+ import { k as isPromise, j as QDATA_KEY, Q as Q_ROUTE, f as QFN_KEY, m as QLOADER_KEY, h as QACTION_KEY, n as resolveRouteConfig, d as loadRoute } from '../../chunks/routing.qwik.mjs';
3
3
  import { inlinedQrl } from '@qwik.dev/core';
4
4
  import { _serialize, isDev, _UNINITIALIZED, _deserialize, _verifySerializable } from '@qwik.dev/core/internal';
5
- import { L as LoadedRouteProp } from '../../chunks/types.qwik.mjs';
6
5
  import { _asyncRequestStore as _asyncRequestStore$1, ServerError as ServerError$1, RedirectMessage as RedirectMessage$1, RewriteMessage as RewriteMessage$1, AbortMessage as AbortMessage$1 } from '@qwik.dev/router/middleware/request-handler';
7
6
  import { g as getErrorHtml, m as minimalHtmlResponse } from '../../chunks/error-handler.mjs';
8
- import '@qwik.dev/core/preloader';
9
7
 
10
- var HttpStatus = /* @__PURE__ */ ((HttpStatus2) => {
8
+ const MAX_CACHE_SIZE = typeof globalThis.__SSR_CACHE_SIZE__ === "number" ? globalThis.__SSR_CACHE_SIZE__ : 50;
9
+ const ssrCache = /* @__PURE__ */ new Map();
10
+ function resolveETag(eTagExport, headProps) {
11
+ if (eTagExport === void 0) {
12
+ return null;
13
+ }
14
+ if (typeof eTagExport === "function") {
15
+ return eTagExport(headProps);
16
+ }
17
+ return eTagExport;
18
+ }
19
+ function resolveCacheKey(cacheKeyExport, status, eTag, pathname) {
20
+ if (cacheKeyExport === true) {
21
+ return `${status}|${eTag}|${pathname}`;
22
+ }
23
+ if (typeof cacheKeyExport !== "function") {
24
+ return null;
25
+ }
26
+ return cacheKeyExport(status, eTag, pathname);
27
+ }
28
+ function getCachedHtml(key) {
29
+ const html = ssrCache.get(key);
30
+ if (html !== void 0) {
31
+ ssrCache.delete(key);
32
+ ssrCache.set(key, html);
33
+ }
34
+ return html;
35
+ }
36
+ function setCachedHtml(key, html) {
37
+ if (MAX_CACHE_SIZE <= 0) {
38
+ return;
39
+ }
40
+ if (ssrCache.size >= MAX_CACHE_SIZE) {
41
+ const firstKey = ssrCache.keys().next().value;
42
+ ssrCache.delete(firstKey);
43
+ }
44
+ ssrCache.set(key, html);
45
+ }
46
+ function clearSsrCache(cacheKey) {
47
+ if (cacheKey != null) {
48
+ ssrCache.delete(cacheKey);
49
+ } else {
50
+ ssrCache.clear();
51
+ }
52
+ }
53
+
54
+ var HttpStatus = /* @__PURE__ */ (function(HttpStatus2) {
11
55
  HttpStatus2[HttpStatus2["Continue"] = 100] = "Continue";
12
56
  HttpStatus2[HttpStatus2["SwitchingProtocols"] = 101] = "SwitchingProtocols";
13
57
  HttpStatus2[HttpStatus2["Processing"] = 102] = "Processing";
@@ -70,7 +114,7 @@ var HttpStatus = /* @__PURE__ */ ((HttpStatus2) => {
70
114
  HttpStatus2[HttpStatus2["NotExtended"] = 510] = "NotExtended";
71
115
  HttpStatus2[HttpStatus2["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
72
116
  return HttpStatus2;
73
- })(HttpStatus || {});
117
+ })({});
74
118
 
75
119
  function createCacheControl(cacheControl) {
76
120
  const controls = [];
@@ -149,35 +193,6 @@ const UNIT = {
149
193
  days: 1 * 60 * 60 * 24,
150
194
  weeks: 1 * 60 * 60 * 24 * 7
151
195
  };
152
- const createSetCookieValue = (cookieName, cookieValue, options) => {
153
- const c = [`${cookieName}=${cookieValue}`];
154
- if (typeof options.domain === "string") {
155
- c.push(`Domain=${options.domain}`);
156
- }
157
- if (typeof options.maxAge === "number") {
158
- c.push(`Max-Age=${options.maxAge}`);
159
- } else if (Array.isArray(options.maxAge)) {
160
- c.push(`Max-Age=${options.maxAge[0] * UNIT[options.maxAge[1]]}`);
161
- } else if (typeof options.expires === "number" || typeof options.expires == "string") {
162
- c.push(`Expires=${options.expires}`);
163
- } else if (options.expires instanceof Date) {
164
- c.push(`Expires=${options.expires.toUTCString()}`);
165
- }
166
- if (options.httpOnly) {
167
- c.push("HttpOnly");
168
- }
169
- if (typeof options.path === "string") {
170
- c.push(`Path=${options.path}`);
171
- }
172
- const sameSite = resolveSameSite(options.sameSite);
173
- if (sameSite) {
174
- c.push(`SameSite=${sameSite}`);
175
- }
176
- if (options.secure) {
177
- c.push("Secure");
178
- }
179
- return c.join("; ");
180
- };
181
196
  function tryDecodeUriComponent(str) {
182
197
  try {
183
198
  return decodeURIComponent(str);
@@ -210,6 +225,37 @@ function resolveSameSite(sameSite) {
210
225
  }
211
226
  return void 0;
212
227
  }
228
+ const createSetCookieValue = (cookieName, cookieValue, options) => {
229
+ const c = [
230
+ `${cookieName}=${cookieValue}`
231
+ ];
232
+ if (typeof options.domain === "string") {
233
+ c.push(`Domain=${options.domain}`);
234
+ }
235
+ if (typeof options.maxAge === "number") {
236
+ c.push(`Max-Age=${options.maxAge}`);
237
+ } else if (Array.isArray(options.maxAge)) {
238
+ c.push(`Max-Age=${options.maxAge[0] * UNIT[options.maxAge[1]]}`);
239
+ } else if (typeof options.expires === "number" || typeof options.expires == "string") {
240
+ c.push(`Expires=${options.expires}`);
241
+ } else if (options.expires instanceof Date) {
242
+ c.push(`Expires=${options.expires.toUTCString()}`);
243
+ }
244
+ if (options.httpOnly) {
245
+ c.push("HttpOnly");
246
+ }
247
+ if (typeof options.path === "string") {
248
+ c.push(`Path=${options.path}`);
249
+ }
250
+ const sameSite = resolveSameSite(options.sameSite);
251
+ if (sameSite) {
252
+ c.push(`SameSite=${sameSite}`);
253
+ }
254
+ if (options.secure) {
255
+ c.push("Secure");
256
+ }
257
+ return c.join("; ");
258
+ };
213
259
  const REQ_COOKIE = /* @__PURE__ */ Symbol("request-cookies");
214
260
  const RES_COOKIE = /* @__PURE__ */ Symbol("response-cookies");
215
261
  const LIVE_COOKIE = /* @__PURE__ */ Symbol("live-cookies");
@@ -220,7 +266,9 @@ class Cookie {
220
266
  appendCounter = 0;
221
267
  constructor(cookieString) {
222
268
  this[REQ_COOKIE] = parseCookieString(cookieString);
223
- this[LIVE_COOKIE] = { ...this[REQ_COOKIE] };
269
+ this[LIVE_COOKIE] = {
270
+ ...this[REQ_COOKIE]
271
+ };
224
272
  }
225
273
  get(cookieName, live = true) {
226
274
  const value = this[live ? LIVE_COOKIE : REQ_COOKIE][cookieName];
@@ -238,13 +286,10 @@ class Cookie {
238
286
  };
239
287
  }
240
288
  getAll(live = true) {
241
- return Object.keys(this[live ? LIVE_COOKIE : REQ_COOKIE]).reduce(
242
- (cookies, cookieName) => {
243
- cookies[cookieName] = this.get(cookieName);
244
- return cookies;
245
- },
246
- {}
247
- );
289
+ return Object.keys(this[live ? LIVE_COOKIE : REQ_COOKIE]).reduce((cookies, cookieName) => {
290
+ cookies[cookieName] = this.get(cookieName);
291
+ return cookies;
292
+ }, {});
248
293
  }
249
294
  has(cookieName, live = true) {
250
295
  return !!this[live ? LIVE_COOKIE : REQ_COOKIE][cookieName];
@@ -257,14 +302,13 @@ class Cookie {
257
302
  append(cookieName, cookieValue, options = {}) {
258
303
  this[LIVE_COOKIE][cookieName] = typeof cookieValue === "string" ? cookieValue : JSON.stringify(cookieValue);
259
304
  const resolvedValue = typeof cookieValue === "string" ? cookieValue : encodeURIComponent(JSON.stringify(cookieValue));
260
- this[RES_COOKIE][++this.appendCounter] = createSetCookieValue(
261
- cookieName,
262
- resolvedValue,
263
- options
264
- );
305
+ this[RES_COOKIE][++this.appendCounter] = createSetCookieValue(cookieName, resolvedValue, options);
265
306
  }
266
307
  delete(name, options) {
267
- this.set(name, "deleted", { ...options, maxAge: 0 });
308
+ this.set(name, "deleted", {
309
+ ...options,
310
+ maxAge: 0
311
+ });
268
312
  this[LIVE_COOKIE][name] = null;
269
313
  }
270
314
  headers() {
@@ -283,22 +327,6 @@ const mergeHeadersCookies = (headers, cookies) => {
283
327
  return headers;
284
328
  };
285
329
 
286
- function runQwikRouter(serverRequestEv, loadedRoute, requestHandlers, rebuildRouteInfo, basePathname = "/") {
287
- let resolve;
288
- const responsePromise = new Promise((r) => resolve = r);
289
- const requestEv = createRequestEvent(
290
- serverRequestEv,
291
- loadedRoute,
292
- requestHandlers,
293
- basePathname,
294
- resolve
295
- );
296
- return {
297
- response: responsePromise,
298
- requestEv,
299
- completion: _asyncRequestStore$1 ? _asyncRequestStore$1.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve) : runNext(requestEv, rebuildRouteInfo, resolve)
300
- };
301
- }
302
330
  async function runNext(requestEv, rebuildRouteInfo, resolve) {
303
331
  try {
304
332
  const isValidURL = (url) => new URL(url.pathname + url.search, url);
@@ -347,7 +375,9 @@ async function runNext(requestEv, rebuildRouteInfo, resolve) {
347
375
  try {
348
376
  if (!requestEv.headersSent) {
349
377
  requestEv.headers.set("content-type", "text/html; charset=utf-8");
350
- requestEv.cacheControl({ noCache: true });
378
+ requestEv.cacheControl({
379
+ noCache: true
380
+ });
351
381
  requestEv.status(500);
352
382
  }
353
383
  const stream = requestEv.getWritableStream();
@@ -371,6 +401,18 @@ async function runNext(requestEv, rebuildRouteInfo, resolve) {
371
401
  }
372
402
  }
373
403
  }
404
+ function runQwikRouter(serverRequestEv, loadedRoute, requestHandlers, rebuildRouteInfo, basePathname = "/") {
405
+ let resolve;
406
+ const responsePromise = new Promise((r) => resolve = r);
407
+ const requestEv = createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, basePathname, resolve);
408
+ return {
409
+ response: responsePromise,
410
+ requestEv,
411
+ completion: _asyncRequestStore$1 ? _asyncRequestStore$1.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve) : runNext(requestEv, rebuildRouteInfo, resolve)
412
+ };
413
+ }
414
+ const IsQData = "@isQData";
415
+ const QDATA_JSON = "/q-data.json";
374
416
  function getRouteMatchPathname(pathname) {
375
417
  const isInternal = pathname.endsWith(QDATA_JSON);
376
418
  if (isInternal) {
@@ -380,24 +422,144 @@ function getRouteMatchPathname(pathname) {
380
422
  pathname = "/";
381
423
  }
382
424
  }
383
- return { pathname, isInternal };
425
+ return {
426
+ pathname,
427
+ isInternal
428
+ };
384
429
  }
385
- const IsQData = "@isQData";
386
- const QDATA_JSON = "/q-data.json";
387
430
 
388
431
  const RequestEvLoaders = /* @__PURE__ */ Symbol("RequestEvLoaders");
389
432
  const RequestEvMode = /* @__PURE__ */ Symbol("RequestEvMode");
390
433
  const RequestEvRoute = /* @__PURE__ */ Symbol("RequestEvRoute");
391
- const RequestEvLoaderSerializationStrategyMap = /* @__PURE__ */ Symbol(
392
- "RequestEvLoaderSerializationStrategyMap"
393
- );
434
+ const RequestEvLoaderSerializationStrategyMap = /* @__PURE__ */ Symbol("RequestEvLoaderSerializationStrategyMap");
394
435
  const RequestRouteName = "@routeName";
395
436
  const RequestEvSharedActionId = "@actionId";
396
437
  const RequestEvSharedActionFormData = "@actionFormData";
397
438
  const RequestEvSharedNonce = "@nonce";
398
439
  const RequestEvIsRewrite = "@rewrite";
399
440
  const RequestEvShareServerTiming = "@serverTiming";
441
+ const RequestEvETagCacheKey = "@eTagCacheKey";
442
+ const RequestEvHttpStatusMessage = "@httpStatusMessage";
400
443
  const RequestEvShareQData = "qData";
444
+ function getRequestLoaders(requestEv) {
445
+ return requestEv[RequestEvLoaders];
446
+ }
447
+ function getRequestLoaderSerializationStrategyMap(requestEv) {
448
+ return requestEv[RequestEvLoaderSerializationStrategyMap];
449
+ }
450
+ function getRequestRoute(requestEv) {
451
+ return requestEv[RequestEvRoute];
452
+ }
453
+ function getRequestMode(requestEv) {
454
+ return requestEv[RequestEvMode];
455
+ }
456
+ const ABORT_INDEX = Number.MAX_SAFE_INTEGER;
457
+ const isDangerousKey = (k) => k === "__proto__" || k === "constructor" || k === "prototype";
458
+ const isArrayIndexKey = (k) => /^(0|[1-9]\d*)$/.test(k);
459
+ const getArrayPaths = (formData) => {
460
+ const arrayCandidates = /* @__PURE__ */ new Map();
461
+ for (const [name] of formData) {
462
+ const keys = name.split(".");
463
+ let hasDangerousKey = false;
464
+ for (const key of keys) {
465
+ if (isDangerousKey(key)) {
466
+ hasDangerousKey = true;
467
+ break;
468
+ }
469
+ }
470
+ if (hasDangerousKey) {
471
+ continue;
472
+ }
473
+ let path = "";
474
+ for (let i = 0; i < keys.length - 1; i++) {
475
+ const key = keys[i];
476
+ if (key.endsWith("[]")) {
477
+ break;
478
+ }
479
+ path = path ? `${path}.${key}` : key;
480
+ if (!arrayCandidates.has(path)) {
481
+ arrayCandidates.set(path, true);
482
+ }
483
+ if (!isArrayIndexKey(keys[i + 1])) {
484
+ arrayCandidates.set(path, false);
485
+ }
486
+ }
487
+ }
488
+ return new Set(Array.from(arrayCandidates.entries()).filter(([, isArrayPath]) => isArrayPath).map(([path]) => path));
489
+ };
490
+ const formToObj = (formData) => {
491
+ const values = /* @__PURE__ */ Object.create(null);
492
+ const arrayPaths = getArrayPaths(formData);
493
+ for (const [name, value] of formData) {
494
+ const keys = name.split(".");
495
+ let hasDangerousKey = false;
496
+ for (let i = 0; i < keys.length; i++) {
497
+ if (isDangerousKey(keys[i])) {
498
+ hasDangerousKey = true;
499
+ break;
500
+ }
501
+ }
502
+ if (hasDangerousKey) {
503
+ continue;
504
+ }
505
+ let object = values;
506
+ let path = "";
507
+ for (let i = 0; i < keys.length; i++) {
508
+ const key = keys[i];
509
+ if (key.endsWith("[]")) {
510
+ const arrayKey = key.slice(0, -2);
511
+ if (isDangerousKey(arrayKey)) {
512
+ break;
513
+ }
514
+ const existingValue = object[arrayKey];
515
+ if (existingValue !== void 0 && !Array.isArray(existingValue)) {
516
+ break;
517
+ }
518
+ object[arrayKey] = existingValue || [];
519
+ object[arrayKey].push(value);
520
+ break;
521
+ }
522
+ if (Array.isArray(object) && !isArrayIndexKey(key)) {
523
+ break;
524
+ }
525
+ if (i < keys.length - 1) {
526
+ path = path ? `${path}.${key}` : key;
527
+ const nextValue = object[key];
528
+ if (nextValue !== void 0) {
529
+ object = nextValue;
530
+ continue;
531
+ }
532
+ object = object[key] = arrayPaths.has(path) ? [] : /* @__PURE__ */ Object.create(null);
533
+ } else {
534
+ object[key] = value;
535
+ }
536
+ }
537
+ }
538
+ return values;
539
+ };
540
+ const parseRequest = async ({ request, method, query }, sharedMap) => {
541
+ const type = getContentType(request.headers);
542
+ if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") {
543
+ const formData = await request.formData();
544
+ sharedMap.set(RequestEvSharedActionFormData, formData);
545
+ return formToObj(formData);
546
+ } else if (type === "application/json") {
547
+ const data = await request.json();
548
+ return data;
549
+ } else if (type === "application/qwik-json") {
550
+ if (method === "GET" && query.has(QDATA_KEY)) {
551
+ const data = query.get(QDATA_KEY);
552
+ if (data) {
553
+ try {
554
+ return _deserialize(decodeURIComponent(data));
555
+ } catch {
556
+ }
557
+ }
558
+ }
559
+ return _deserialize(await request.text());
560
+ }
561
+ return void 0;
562
+ };
401
563
  function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, basePathname, resolved) {
402
564
  const { request, platform, env } = serverRequestEv;
403
565
  const sharedMap = /* @__PURE__ */ new Map();
@@ -413,7 +575,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, baseP
413
575
  let writableStream = null;
414
576
  let requestData = void 0;
415
577
  let locale = serverRequestEv.locale;
416
- let status = 200;
578
+ let status = loadedRoute?.$notFound$ ? 404 : 200;
417
579
  const next = async () => {
418
580
  routeModuleIndex++;
419
581
  while (routeModuleIndex < requestHandlers.length) {
@@ -427,6 +589,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, baseP
427
589
  };
428
590
  const resetRoute = (_loadedRoute, _requestHandlers, _url = url) => {
429
591
  loadedRoute = _loadedRoute;
592
+ status = loadedRoute?.$notFound$ ? 404 : 200;
430
593
  requestHandlers = _requestHandlers;
431
594
  url.pathname = _url.pathname;
432
595
  url.search = _url.search;
@@ -490,7 +653,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, baseP
490
653
  signal: request.signal,
491
654
  originalUrl: new URL(url),
492
655
  get params() {
493
- return loadedRoute?.[LoadedRouteProp.Params] ?? {};
656
+ return loadedRoute?.$params$ ?? {};
494
657
  },
495
658
  get pathname() {
496
659
  return url.pathname;
@@ -519,20 +682,18 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, baseP
519
682
  check();
520
683
  headers.set(target, createCacheControl(cacheControl));
521
684
  },
522
- resolveValue: (async (loaderOrAction) => {
685
+ resolveValue: async (loaderOrAction) => {
523
686
  const id = loaderOrAction.__id;
524
687
  if (loaderOrAction.__brand === "server_loader") {
525
688
  if (!(id in loaders)) {
526
- throw new Error(
527
- "You can not get the returned data of a loader that has not been executed for this request."
528
- );
689
+ throw new Error("You can not get the returned data of a loader that has not been executed for this request.");
529
690
  }
530
691
  if (loaders[id] === _UNINITIALIZED) {
531
692
  await getRouteLoaderPromise(loaderOrAction, loaders, requestEv);
532
693
  }
533
694
  }
534
695
  return loaders[id];
535
- }),
696
+ },
536
697
  status: (statusCode) => {
537
698
  if (typeof statusCode === "number") {
538
699
  check();
@@ -576,10 +737,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, baseP
576
737
  rewrite: (pathname2) => {
577
738
  check();
578
739
  if (pathname2.startsWith("http")) {
579
- throw new ServerError$1(
580
- 400,
581
- isDev ? "Rewrite does not support absolute urls" : "Bad Request"
582
- );
740
+ throw new ServerError$1(400, isDev ? "Rewrite does not support absolute urls" : "Bad Request");
583
741
  }
584
742
  sharedMap.set(RequestEvIsRewrite, true);
585
743
  return exit(new RewriteMessage$1(pathname2.replace(/\/+/g, "/")));
@@ -623,113 +781,32 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, baseP
623
781
  if (isDev) {
624
782
  const serverTiming = sharedMap.get(RequestEvShareServerTiming);
625
783
  if (serverTiming) {
626
- headers.set(
627
- "Server-Timing",
628
- serverTiming.map(([name, duration]) => `${name};dur=${duration}`).join(",")
629
- );
784
+ headers.set("Server-Timing", serverTiming.map(([name, duration]) => `${name};dur=${duration}`).join(","));
630
785
  }
631
786
  }
632
- writableStream = serverRequestEv.getWritableStream(
633
- status,
634
- headers,
635
- cookie,
636
- resolved,
637
- requestEv
638
- );
787
+ writableStream = serverRequestEv.getWritableStream(status, headers, cookie, resolved, requestEv);
639
788
  }
640
789
  return writableStream;
641
790
  }
642
791
  };
643
792
  return requestEv;
644
793
  }
645
- function getRequestLoaders(requestEv) {
646
- return requestEv[RequestEvLoaders];
647
- }
648
- function getRequestLoaderSerializationStrategyMap(requestEv) {
649
- return requestEv[RequestEvLoaderSerializationStrategyMap];
650
- }
651
- function getRequestRoute(requestEv) {
652
- return requestEv[RequestEvRoute];
653
- }
654
- function getRequestMode(requestEv) {
655
- return requestEv[RequestEvMode];
656
- }
657
- const ABORT_INDEX = Number.MAX_SAFE_INTEGER;
658
- const parseRequest = async ({ request, method, query }, sharedMap) => {
659
- const type = getContentType(request.headers);
660
- if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") {
661
- const formData = await request.formData();
662
- sharedMap.set(RequestEvSharedActionFormData, formData);
663
- return formToObj(formData);
664
- } else if (type === "application/json") {
665
- const data = await request.json();
666
- return data;
667
- } else if (type === "application/qwik-json") {
668
- if (method === "GET" && query.has(QDATA_KEY)) {
669
- const data = query.get(QDATA_KEY);
670
- if (data) {
671
- try {
672
- return _deserialize(decodeURIComponent(data));
673
- } catch {
674
- }
675
- }
676
- }
677
- return _deserialize(await request.text());
678
- }
679
- return void 0;
680
- };
681
- const isDangerousKey = (k) => k === "__proto__" || k === "constructor" || k === "prototype";
682
- const formToObj = (formData) => {
683
- const values = /* @__PURE__ */ Object.create(null);
684
- for (const [name, value] of formData) {
685
- const keys = name.split(".");
686
- let hasDangerousKey = false;
687
- for (let i = 0; i < keys.length; i++) {
688
- if (isDangerousKey(keys[i])) {
689
- hasDangerousKey = true;
690
- break;
691
- }
692
- }
693
- if (hasDangerousKey) {
694
- continue;
695
- }
696
- let object = values;
697
- for (let i = 0; i < keys.length; i++) {
698
- const key = keys[i];
699
- if (key.endsWith("[]")) {
700
- const arrayKey = key.slice(0, -2);
701
- if (isDangerousKey(arrayKey)) {
702
- break;
703
- }
704
- object[arrayKey] = object[arrayKey] || [];
705
- object[arrayKey].push(value);
706
- break;
707
- }
708
- if (i < keys.length - 1) {
709
- object = object[key] = object[key] || (Number.isNaN(+keys[i + 1]) ? /* @__PURE__ */ Object.create(null) : []);
710
- } else {
711
- object[key] = value;
712
- }
713
- }
714
- }
715
- return values;
716
- };
717
-
718
- function getQwikRouterServerData(requestEv) {
719
- const { params, request, status, locale, originalUrl } = requestEv;
720
- const requestHeaders = {};
721
- request.headers.forEach((value, key) => requestHeaders[key] = value);
722
- const action = requestEv.sharedMap.get(RequestEvSharedActionId);
723
- const formData = requestEv.sharedMap.get(RequestEvSharedActionFormData);
724
- const routeName = requestEv.sharedMap.get(RequestRouteName);
725
- const nonce = requestEv.sharedMap.get(RequestEvSharedNonce);
726
- const headers = requestEv.request.headers;
727
- const reconstructedUrl = new URL(originalUrl.pathname + originalUrl.search, originalUrl);
728
- const host = headers.get("X-Forwarded-Host");
729
- const protocol = headers.get("X-Forwarded-Proto");
730
- if (host) {
731
- reconstructedUrl.port = "";
732
- reconstructedUrl.host = host;
794
+
795
+ function getQwikRouterServerData(requestEv) {
796
+ const { params, request, status, locale, originalUrl } = requestEv;
797
+ const requestHeaders = {};
798
+ request.headers.forEach((value, key) => requestHeaders[key] = value);
799
+ const action = requestEv.sharedMap.get(RequestEvSharedActionId);
800
+ const formData = requestEv.sharedMap.get(RequestEvSharedActionFormData);
801
+ const routeName = requestEv.sharedMap.get(RequestRouteName);
802
+ const nonce = requestEv.sharedMap.get(RequestEvSharedNonce);
803
+ const headers = requestEv.request.headers;
804
+ const reconstructedUrl = new URL(originalUrl.pathname + originalUrl.search, originalUrl);
805
+ const host = headers.get("X-Forwarded-Host");
806
+ const protocol = headers.get("X-Forwarded-Proto");
807
+ if (host) {
808
+ reconstructedUrl.port = "";
809
+ reconstructedUrl.host = host;
733
810
  }
734
811
  if (protocol) {
735
812
  reconstructedUrl.protocol = protocol;
@@ -747,10 +824,13 @@ function getQwikRouterServerData(requestEv) {
747
824
  qwikrouter: {
748
825
  routeName,
749
826
  ev: requestEv,
750
- params: { ...params },
827
+ params: {
828
+ ...params
829
+ },
751
830
  loadedRoute: getRequestRoute(requestEv),
752
831
  response: {
753
832
  status: status(),
833
+ statusMessage: requestEv.sharedMap.get(RequestEvHttpStatusMessage),
754
834
  loaders,
755
835
  loadersSerializationStrategy,
756
836
  action,
@@ -760,62 +840,6 @@ function getQwikRouterServerData(requestEv) {
760
840
  };
761
841
  }
762
842
 
763
- const resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderHandler, isInternal) => {
764
- const routeLoaders = [];
765
- const routeActions = [];
766
- const requestHandlers = [];
767
- const isPageRoute = !!(route && isLastModulePageRoute(route[LoadedRouteProp.Mods]));
768
- if (isInternal) {
769
- requestHandlers.push(handleQDataRedirect);
770
- }
771
- if (serverPlugins) {
772
- _resolveRequestHandlers(
773
- routeLoaders,
774
- routeActions,
775
- requestHandlers,
776
- serverPlugins,
777
- isPageRoute,
778
- method
779
- );
780
- }
781
- if (route) {
782
- const routeModules = route[LoadedRouteProp.Mods];
783
- _resolveRequestHandlers(
784
- routeLoaders,
785
- routeActions,
786
- requestHandlers,
787
- routeModules,
788
- isPageRoute,
789
- method
790
- );
791
- const routeName = route[LoadedRouteProp.RouteName];
792
- if (checkOrigin && (method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE")) {
793
- if (checkOrigin === "lax-proto") {
794
- requestHandlers.unshift(csrfLaxProtoCheckMiddleware);
795
- } else {
796
- requestHandlers.unshift(csrfCheckMiddleware);
797
- }
798
- }
799
- if (isPageRoute) {
800
- if (method === "POST" || method === "GET") {
801
- requestHandlers.push(runServerFunction);
802
- }
803
- requestHandlers.push(fixTrailingSlash);
804
- if (isInternal) {
805
- requestHandlers.push(renderQData);
806
- }
807
- }
808
- if (isPageRoute) {
809
- requestHandlers.push((ev) => {
810
- ev.sharedMap.set(RequestRouteName, routeName);
811
- });
812
- requestHandlers.push(actionsMiddleware(routeActions));
813
- requestHandlers.push(loadersMiddleware(routeLoaders));
814
- requestHandlers.push(renderHandler);
815
- }
816
- }
817
- return requestHandlers;
818
- };
819
843
  const _resolveRequestHandlers = (routeLoaders, routeActions, requestHandlers, routeModules, collectActions, method) => {
820
844
  for (const routeModule of routeModules) {
821
845
  if (typeof routeModule.onRequest === "function") {
@@ -872,185 +896,111 @@ const _resolveRequestHandlers = (routeLoaders, routeActions, requestHandlers, ro
872
896
  }
873
897
  }
874
898
  };
875
- function actionsMiddleware(routeActions) {
876
- return async (requestEvent) => {
877
- const requestEv = requestEvent;
899
+ function eTagMiddleware(route) {
900
+ return (requestEv) => {
878
901
  if (requestEv.headersSent) {
879
- requestEv.exit();
880
902
  return;
881
903
  }
882
- const { method } = requestEv;
883
- const loaders = getRequestLoaders(requestEv);
884
- if (isDev && method === "GET") {
885
- if (requestEv.query.has(QACTION_KEY)) {
886
- console.warn(
887
- '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">'
888
- );
904
+ if (requestEv.method !== "GET" || requestEv.sharedMap.has(IsQData)) {
905
+ return;
906
+ }
907
+ const mods = route.$mods$;
908
+ const hasETag = mods.some((m) => {
909
+ if (!m) {
910
+ return false;
911
+ }
912
+ if (m.routeConfig) {
913
+ return typeof m.routeConfig === "function" || m.routeConfig.eTag !== void 0;
889
914
  }
915
+ return m.eTag !== void 0;
916
+ });
917
+ if (!hasETag) {
918
+ return;
890
919
  }
891
- if (method === "POST") {
892
- const selectedActionId = requestEv.query.get(QACTION_KEY);
893
- if (selectedActionId) {
894
- const serverActionsMap = globalThis._qwikActionsMap;
895
- const action = routeActions.find((action2) => action2.__id === selectedActionId) ?? serverActionsMap?.get(selectedActionId);
896
- if (action) {
897
- requestEv.sharedMap.set(RequestEvSharedActionId, selectedActionId);
898
- const data = await requestEv.parseBody();
899
- if (!data || typeof data !== "object") {
900
- throw new Error(
901
- `Expected request data for the action id ${selectedActionId} to be an object`
902
- );
903
- }
904
- const result = await runValidators(requestEv, action.__validators, data);
905
- if (!result.success) {
906
- loaders[selectedActionId] = requestEv.fail(result.status ?? 500, result.error);
907
- } else {
908
- const actionResolved = isDev ? await measure(
909
- requestEv,
910
- action.__qrl.getHash(),
911
- () => action.__qrl.call(requestEv, result.data, requestEv)
912
- ) : await action.__qrl.call(requestEv, result.data, requestEv);
913
- if (isDev) {
914
- verifySerializable(actionResolved, action.__qrl);
915
- }
916
- loaders[selectedActionId] = actionResolved;
917
- }
918
- }
920
+ const loaders = getRequestLoaders(requestEv);
921
+ const getData = (loaderOrAction) => {
922
+ const id = loaderOrAction.__id;
923
+ if (loaderOrAction.__brand === "server_loader" && !(id in loaders)) {
924
+ throw new Error("Loader not executed for this request.");
925
+ }
926
+ const data = loaders[id];
927
+ if (data instanceof Promise) {
928
+ throw new Error("Loaders returning a promise cannot be resolved for the eTag function.");
919
929
  }
930
+ return data;
931
+ };
932
+ const routeLocation = {
933
+ params: requestEv.params,
934
+ url: requestEv.url,
935
+ isNavigating: false,
936
+ prevUrl: void 0
937
+ };
938
+ const status = requestEv.status();
939
+ const config = resolveRouteConfig(getData, routeLocation, mods, "", status);
940
+ const headProps = {
941
+ head: config.head,
942
+ status,
943
+ withLocale: (fn) => fn(),
944
+ resolveValue: getData,
945
+ ...routeLocation
946
+ };
947
+ const eTag = resolveETag(config.eTag, headProps);
948
+ if (!eTag) {
949
+ return;
920
950
  }
921
- };
922
- }
923
- function loadersMiddleware(routeLoaders) {
924
- return async (requestEvent) => {
925
- const requestEv = requestEvent;
926
- if (requestEv.headersSent) {
927
- requestEv.exit();
951
+ requestEv.headers.set("ETag", eTag);
952
+ const ifNoneMatch = requestEv.request.headers.get("If-None-Match");
953
+ if (ifNoneMatch && (ifNoneMatch === eTag || ifNoneMatch === `W/${eTag}` || `W/${ifNoneMatch}` === eTag)) {
954
+ requestEv.status(304);
955
+ requestEv.send(304, "");
928
956
  return;
929
957
  }
930
- const loaders = getRequestLoaders(requestEv);
931
- if (routeLoaders.length > 0) {
932
- const resolvedLoadersPromises = routeLoaders.map(
933
- (loader) => getRouteLoaderPromise(loader, loaders, requestEv)
934
- );
935
- await Promise.all(resolvedLoadersPromises);
958
+ if (MAX_CACHE_SIZE <= 0) {
959
+ return;
936
960
  }
937
- };
938
- }
939
- async function getRouteLoaderPromise(loader, loaders, requestEv) {
940
- const loaderId = loader.__id;
941
- loaders[loaderId] = runValidators(
942
- requestEv,
943
- loader.__validators,
944
- void 0
945
- // data
946
- ).then((res) => {
947
- if (res.success) {
948
- if (isDev) {
949
- return measure(
950
- requestEv,
951
- loader.__qrl.getHash(),
952
- () => loader.__qrl.call(requestEv, requestEv)
953
- );
954
- } else {
955
- return loader.__qrl.call(requestEv, requestEv);
956
- }
957
- } else {
958
- return requestEv.fail(res.status ?? 500, res.error);
961
+ const cacheKey = resolveCacheKey(config.cacheKey, status, eTag, requestEv.url.pathname);
962
+ if (!cacheKey) {
963
+ return;
959
964
  }
960
- }).then((resolvedLoader) => {
961
- if (typeof resolvedLoader === "function") {
962
- loaders[loaderId] = resolvedLoader();
963
- } else {
964
- if (isDev) {
965
- verifySerializable(resolvedLoader, loader.__qrl);
966
- }
967
- loaders[loaderId] = resolvedLoader;
965
+ const cachedHtml = getCachedHtml(cacheKey);
966
+ if (cachedHtml) {
967
+ requestEv.headers.set("Content-Type", "text/html; charset=utf-8");
968
+ requestEv.headers.set("X-SSR-Cache", "HIT");
969
+ requestEv.send(status, cachedHtml);
970
+ return;
968
971
  }
969
- return resolvedLoader;
970
- });
971
- const loadersSerializationStrategy = getRequestLoaderSerializationStrategyMap(requestEv);
972
- loadersSerializationStrategy.set(loaderId, loader.__serializationStrategy);
973
- return loaders[loaderId];
974
- }
975
- async function runValidators(requestEv, validators, data) {
976
- let lastResult = {
977
- success: true,
978
- data
972
+ requestEv.sharedMap.set(RequestEvETagCacheKey, cacheKey);
979
973
  };
980
- if (validators) {
981
- for (const validator of validators) {
982
- if (isDev) {
983
- lastResult = await measure(
984
- requestEv,
985
- `validator$`,
986
- () => validator.validate(requestEv, data)
987
- );
988
- } else {
989
- lastResult = await validator.validate(requestEv, data);
990
- }
991
- if (!lastResult.success) {
992
- return lastResult;
993
- } else {
994
- data = lastResult.data;
995
- }
996
- }
997
- }
998
- return lastResult;
999
- }
1000
- function isAsyncIterator(obj) {
1001
- return obj ? typeof obj === "object" && Symbol.asyncIterator in obj : false;
1002
974
  }
1003
- async function runServerFunction(ev) {
1004
- const serverFnHash = ev.query.get(QFN_KEY);
1005
- if (serverFnHash && ev.request.headers.get("X-QRL") === serverFnHash && ev.request.headers.get("Content-Type") === "application/qwik-json") {
1006
- ev.exit();
1007
- const data = await ev.parseBody();
1008
- if (!Array.isArray(data) || !(Array.isArray(data[0]) || data[0] === void 0)) {
1009
- throw ev.error(500, "Invalid request");
1010
- }
1011
- const qrl = inlinedQrl(null, serverFnHash, data.slice(1));
1012
- let result;
975
+ function serverErrorMiddleware(route, renderHandler) {
976
+ return async (requestEv) => {
1013
977
  try {
1014
- if (isDev) {
1015
- result = await measure(
1016
- ev,
1017
- `server_${serverFnHash}`,
1018
- () => qrl.apply(ev, data[0])
1019
- );
1020
- } else {
1021
- result = await qrl.apply(ev, data[0]);
978
+ await requestEv.next();
979
+ } catch (e) {
980
+ if (!(e instanceof ServerError$1) || requestEv.headersSent) {
981
+ throw e;
1022
982
  }
1023
- } catch (err) {
1024
- if (err instanceof ServerError$1) {
1025
- throw ev.error(err.status, err.data);
983
+ if (requestEv.sharedMap.has(IsQData)) {
984
+ throw e;
1026
985
  }
1027
- console.error(`Server function ${serverFnHash} failed:`, err);
1028
- throw ev.error(500, "Invalid request");
1029
- }
1030
- if (isAsyncIterator(result)) {
1031
- ev.headers.set("Content-Type", "text/qwik-json-stream");
1032
- const writable = ev.getWritableStream();
1033
- const stream = writable.getWriter();
1034
- for await (const item of result) {
1035
- if (isDev) {
1036
- verifySerializable(item, qrl);
1037
- }
1038
- const message = await _serialize(item);
1039
- if (ev.signal.aborted) {
1040
- break;
1041
- }
1042
- await stream.write(encoder.encode(`${message}
1043
- `));
986
+ const accept = requestEv.request.headers.get("Accept");
987
+ if (accept && !accept.includes("text/html")) {
988
+ throw e;
1044
989
  }
1045
- stream.close();
1046
- } else {
1047
- verifySerializable(result, qrl);
1048
- ev.headers.set("Content-Type", "application/qwik-json");
1049
- const message = await _serialize(result);
1050
- ev.send(200, message);
990
+ const status = e.status;
991
+ requestEv.status(status);
992
+ const errorLoader = route.$errorLoader$;
993
+ const errorModule = errorLoader ? await errorLoader() : await import('../../chunks/http-error.qwik.mjs');
994
+ route.$mods$ = [
995
+ errorModule
996
+ ];
997
+ requestEv.sharedMap.set(RequestEvHttpStatusMessage, typeof e.data === "string" ? e.data : "Server Error");
998
+ await renderHandler(requestEv);
1051
999
  }
1052
- return;
1053
- }
1000
+ };
1001
+ }
1002
+ function isAsyncIterator(obj) {
1003
+ return obj ? typeof obj === "object" && Symbol.asyncIterator in obj : false;
1054
1004
  }
1055
1005
  function fixTrailingSlash(ev) {
1056
1006
  const { basePathname, originalUrl, sharedMap } = ev;
@@ -1070,10 +1020,7 @@ function fixTrailingSlash(ev) {
1070
1020
  }
1071
1021
  } else {
1072
1022
  if (pathname.endsWith("/")) {
1073
- throw ev.redirect(
1074
- HttpStatus.MovedPermanently,
1075
- pathname.slice(0, pathname.length - 1) + search
1076
- );
1023
+ throw ev.redirect(HttpStatus.MovedPermanently, pathname.slice(0, pathname.length - 1) + search);
1077
1024
  }
1078
1025
  }
1079
1026
  }
@@ -1110,36 +1057,6 @@ function getPathname(url) {
1110
1057
  return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
1111
1058
  }
1112
1059
  const encoder = /* @__PURE__ */ new TextEncoder();
1113
- function csrfLaxProtoCheckMiddleware(requestEv) {
1114
- checkCSRF(requestEv, "lax-proto");
1115
- }
1116
- function csrfCheckMiddleware(requestEv) {
1117
- checkCSRF(requestEv);
1118
- }
1119
- function checkCSRF(requestEv, laxProto) {
1120
- const contentType = requestEv.request.headers.get("content-type");
1121
- const isSimpleRequest = !contentType || isContentType(
1122
- requestEv.request.headers,
1123
- "application/x-www-form-urlencoded",
1124
- "multipart/form-data",
1125
- "text/plain"
1126
- );
1127
- if (isSimpleRequest) {
1128
- const inputOrigin = requestEv.request.headers.get("origin");
1129
- const origin = requestEv.url.origin;
1130
- let forbidden = inputOrigin !== origin;
1131
- if (forbidden && laxProto && inputOrigin?.replace(/^http(s)?/g, "") === origin.replace(/^http(s)?/g, "")) {
1132
- forbidden = false;
1133
- }
1134
- if (forbidden) {
1135
- throw requestEv.error(
1136
- 403,
1137
- `CSRF check failed. Cross-site ${requestEv.method} form submissions are forbidden.
1138
- The request origin "${inputOrigin}" does not match the server origin "${origin}".`
1139
- );
1140
- }
1141
- }
1142
- }
1143
1060
  function renderQwikMiddleware(render) {
1144
1061
  return async (requestEv) => {
1145
1062
  if (requestEv.headersSent) {
@@ -1154,10 +1071,25 @@ function renderQwikMiddleware(render) {
1154
1071
  if (!responseHeaders.has("Content-Type")) {
1155
1072
  responseHeaders.set("Content-Type", "text/html; charset=utf-8");
1156
1073
  }
1074
+ const eTagCacheKey = requestEv.sharedMap.get(RequestEvETagCacheKey);
1157
1075
  const { readable, writable } = new TextEncoderStream();
1158
1076
  const writableStream = requestEv.getWritableStream();
1159
- const pipe = readable.pipeTo(writableStream, { preventClose: true });
1160
- const stream = writable.getWriter();
1077
+ let cacheChunks;
1078
+ let pipeSource = readable;
1079
+ if (eTagCacheKey) {
1080
+ cacheChunks = [];
1081
+ const capture = new TransformStream({
1082
+ transform(chunk, controller) {
1083
+ cacheChunks.push(chunk);
1084
+ controller.enqueue(chunk);
1085
+ }
1086
+ });
1087
+ pipeSource = readable.pipeThrough(capture);
1088
+ }
1089
+ const pipe = pipeSource.pipeTo(writableStream, {
1090
+ preventClose: true
1091
+ });
1092
+ const stream = writable.getWriter();
1161
1093
  const status = requestEv.status();
1162
1094
  try {
1163
1095
  const isStatic = getRequestMode(requestEv) === "static";
@@ -1186,35 +1118,19 @@ function renderQwikMiddleware(render) {
1186
1118
  await stream.close();
1187
1119
  await pipe;
1188
1120
  }
1121
+ if (eTagCacheKey && cacheChunks && cacheChunks.length > 0) {
1122
+ const totalLength = cacheChunks.reduce((sum, chunk) => sum + chunk.length, 0);
1123
+ const combined = new Uint8Array(totalLength);
1124
+ let offset = 0;
1125
+ for (const chunk of cacheChunks) {
1126
+ combined.set(chunk, offset);
1127
+ offset += chunk.length;
1128
+ }
1129
+ setCachedHtml(eTagCacheKey, new TextDecoder().decode(combined));
1130
+ }
1189
1131
  await writableStream.close();
1190
1132
  };
1191
1133
  }
1192
- async function handleQDataRedirect(requestEv) {
1193
- try {
1194
- await requestEv.next();
1195
- } catch (err) {
1196
- if (!(err instanceof RedirectMessage$1)) {
1197
- throw err;
1198
- }
1199
- }
1200
- if (requestEv.headersSent) {
1201
- return;
1202
- }
1203
- const status = requestEv.status();
1204
- const location = requestEv.headers.get("Location");
1205
- const isRedirect = status >= 301 && status <= 308 && location;
1206
- if (isRedirect) {
1207
- const adaptedLocation = makeQDataPath(location);
1208
- if (adaptedLocation) {
1209
- requestEv.headers.set("Location", adaptedLocation);
1210
- requestEv.getWritableStream().close();
1211
- return;
1212
- } else {
1213
- requestEv.status(200);
1214
- requestEv.headers.delete("Location");
1215
- }
1216
- }
1217
- }
1218
1134
  async function renderQData(requestEv) {
1219
1135
  await requestEv.next();
1220
1136
  if (requestEv.headersSent || requestEv.exited) {
@@ -1266,6 +1182,32 @@ function makeQDataPath(href) {
1266
1182
  return void 0;
1267
1183
  }
1268
1184
  }
1185
+ async function handleQDataRedirect(requestEv) {
1186
+ try {
1187
+ await requestEv.next();
1188
+ } catch (err) {
1189
+ if (!(err instanceof RedirectMessage$1)) {
1190
+ throw err;
1191
+ }
1192
+ }
1193
+ if (requestEv.headersSent) {
1194
+ return;
1195
+ }
1196
+ const status = requestEv.status();
1197
+ const location = requestEv.headers.get("Location");
1198
+ const isRedirect = status >= 301 && status <= 308 && location;
1199
+ if (isRedirect) {
1200
+ const adaptedLocation = makeQDataPath(location);
1201
+ if (adaptedLocation) {
1202
+ requestEv.headers.set("Location", adaptedLocation);
1203
+ requestEv.getWritableStream().close();
1204
+ return;
1205
+ } else {
1206
+ requestEv.status(200);
1207
+ requestEv.headers.delete("Location");
1208
+ }
1209
+ }
1210
+ }
1269
1211
  function now() {
1270
1212
  return typeof performance !== "undefined" ? performance.now() : 0;
1271
1213
  }
@@ -1279,7 +1221,165 @@ async function measure(requestEv, name, fn) {
1279
1221
  if (!measurements) {
1280
1222
  requestEv.sharedMap.set(RequestEvShareServerTiming, measurements = []);
1281
1223
  }
1282
- measurements.push([name, duration]);
1224
+ measurements.push([
1225
+ name,
1226
+ duration
1227
+ ]);
1228
+ }
1229
+ }
1230
+ async function runValidators(requestEv, validators, data) {
1231
+ let lastResult = {
1232
+ success: true,
1233
+ data
1234
+ };
1235
+ if (validators) {
1236
+ for (const validator of validators) {
1237
+ if (isDev) {
1238
+ lastResult = await measure(requestEv, `validator$`, () => validator.validate(requestEv, data));
1239
+ } else {
1240
+ lastResult = await validator.validate(requestEv, data);
1241
+ }
1242
+ if (!lastResult.success) {
1243
+ return lastResult;
1244
+ } else {
1245
+ data = lastResult.data;
1246
+ }
1247
+ }
1248
+ }
1249
+ return lastResult;
1250
+ }
1251
+ function actionsMiddleware(routeActions) {
1252
+ return async (requestEvent) => {
1253
+ const requestEv = requestEvent;
1254
+ if (requestEv.headersSent) {
1255
+ requestEv.exit();
1256
+ return;
1257
+ }
1258
+ const { method } = requestEv;
1259
+ const loaders = getRequestLoaders(requestEv);
1260
+ if (isDev && method === "GET") {
1261
+ if (requestEv.query.has(QACTION_KEY)) {
1262
+ 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">');
1263
+ }
1264
+ }
1265
+ if (method === "POST") {
1266
+ const selectedActionId = requestEv.query.get(QACTION_KEY);
1267
+ if (selectedActionId) {
1268
+ const serverActionsMap = globalThis._qwikActionsMap;
1269
+ const action = routeActions.find((action2) => action2.__id === selectedActionId) ?? serverActionsMap?.get(selectedActionId);
1270
+ if (action) {
1271
+ requestEv.sharedMap.set(RequestEvSharedActionId, selectedActionId);
1272
+ const data = await requestEv.parseBody();
1273
+ if (!data || typeof data !== "object") {
1274
+ throw new Error(`Expected request data for the action id ${selectedActionId} to be an object`);
1275
+ }
1276
+ const result = await runValidators(requestEv, action.__validators, data);
1277
+ if (!result.success) {
1278
+ loaders[selectedActionId] = requestEv.fail(result.status ?? 500, result.error);
1279
+ } else {
1280
+ const actionResolved = isDev ? await measure(requestEv, action.__qrl.getHash(), () => action.__qrl.call(requestEv, result.data, requestEv)) : await action.__qrl.call(requestEv, result.data, requestEv);
1281
+ if (isDev) {
1282
+ verifySerializable(actionResolved, action.__qrl);
1283
+ }
1284
+ loaders[selectedActionId] = actionResolved;
1285
+ }
1286
+ }
1287
+ }
1288
+ }
1289
+ };
1290
+ }
1291
+ async function getRouteLoaderPromise(loader, loaders, requestEv) {
1292
+ const loaderId = loader.__id;
1293
+ loaders[loaderId] = runValidators(
1294
+ requestEv,
1295
+ loader.__validators,
1296
+ void 0
1297
+ // data
1298
+ ).then((res) => {
1299
+ if (res.success) {
1300
+ if (isDev) {
1301
+ return measure(requestEv, loader.__qrl.getHash(), () => loader.__qrl.call(requestEv, requestEv));
1302
+ } else {
1303
+ return loader.__qrl.call(requestEv, requestEv);
1304
+ }
1305
+ } else {
1306
+ return requestEv.fail(res.status ?? 500, res.error);
1307
+ }
1308
+ }).then((resolvedLoader) => {
1309
+ if (typeof resolvedLoader === "function") {
1310
+ loaders[loaderId] = resolvedLoader();
1311
+ } else {
1312
+ if (isDev) {
1313
+ verifySerializable(resolvedLoader, loader.__qrl);
1314
+ }
1315
+ loaders[loaderId] = resolvedLoader;
1316
+ }
1317
+ return resolvedLoader;
1318
+ });
1319
+ const loadersSerializationStrategy = getRequestLoaderSerializationStrategyMap(requestEv);
1320
+ loadersSerializationStrategy.set(loaderId, loader.__serializationStrategy);
1321
+ return loaders[loaderId];
1322
+ }
1323
+ function loadersMiddleware(routeLoaders) {
1324
+ return async (requestEvent) => {
1325
+ const requestEv = requestEvent;
1326
+ if (requestEv.headersSent) {
1327
+ requestEv.exit();
1328
+ return;
1329
+ }
1330
+ const loaders = getRequestLoaders(requestEv);
1331
+ if (routeLoaders.length > 0) {
1332
+ const resolvedLoadersPromises = routeLoaders.map((loader) => getRouteLoaderPromise(loader, loaders, requestEv));
1333
+ await Promise.all(resolvedLoadersPromises);
1334
+ }
1335
+ };
1336
+ }
1337
+ async function runServerFunction(ev) {
1338
+ const serverFnHash = ev.query.get(QFN_KEY);
1339
+ if (serverFnHash && ev.request.headers.get("X-QRL") === serverFnHash && ev.request.headers.get("Content-Type") === "application/qwik-json") {
1340
+ ev.exit();
1341
+ const data = await ev.parseBody();
1342
+ if (!Array.isArray(data) || !(Array.isArray(data[0]) || data[0] === void 0)) {
1343
+ throw ev.error(500, "Invalid request");
1344
+ }
1345
+ const qrl = inlinedQrl(null, serverFnHash, data.slice(1));
1346
+ let result;
1347
+ try {
1348
+ if (isDev) {
1349
+ result = await measure(ev, `server_${serverFnHash}`, () => qrl.apply(ev, data[0]));
1350
+ } else {
1351
+ result = await qrl.apply(ev, data[0]);
1352
+ }
1353
+ } catch (err) {
1354
+ if (err instanceof ServerError$1) {
1355
+ throw ev.error(err.status, err.data);
1356
+ }
1357
+ console.error(`Server function ${serverFnHash} failed:`, err);
1358
+ throw ev.error(500, "Invalid request");
1359
+ }
1360
+ if (isAsyncIterator(result)) {
1361
+ ev.headers.set("Content-Type", "text/qwik-json-stream");
1362
+ const writable = ev.getWritableStream();
1363
+ const stream = writable.getWriter();
1364
+ for await (const item of result) {
1365
+ if (isDev) {
1366
+ verifySerializable(item, qrl);
1367
+ }
1368
+ const message = await _serialize(item);
1369
+ if (ev.signal.aborted) {
1370
+ break;
1371
+ }
1372
+ await stream.write(encoder.encode(`${message}
1373
+ `));
1374
+ }
1375
+ stream.close();
1376
+ } else {
1377
+ verifySerializable(result, qrl);
1378
+ ev.headers.set("Content-Type", "application/qwik-json");
1379
+ const message = await _serialize(result);
1380
+ ev.send(200, message);
1381
+ }
1382
+ return;
1283
1383
  }
1284
1384
  }
1285
1385
  function getContentType(headers) {
@@ -1294,19 +1394,93 @@ function isContentType(headers, ...types) {
1294
1394
  }
1295
1395
  return false;
1296
1396
  }
1397
+ function checkCSRF(requestEv, laxProto) {
1398
+ const contentType = requestEv.request.headers.get("content-type");
1399
+ const isSimpleRequest = !contentType || isContentType(requestEv.request.headers, "application/x-www-form-urlencoded", "multipart/form-data", "text/plain");
1400
+ if (isSimpleRequest) {
1401
+ const inputOrigin = requestEv.request.headers.get("origin");
1402
+ const origin = requestEv.url.origin;
1403
+ let forbidden = inputOrigin !== origin;
1404
+ if (forbidden && laxProto && inputOrigin?.replace(/^http(s)?/g, "") === origin.replace(/^http(s)?/g, "")) {
1405
+ forbidden = false;
1406
+ }
1407
+ if (forbidden) {
1408
+ throw requestEv.error(403, `CSRF check failed. Cross-site ${requestEv.method} form submissions are forbidden.
1409
+ The request origin "${inputOrigin}" does not match the server origin "${origin}".`);
1410
+ }
1411
+ }
1412
+ }
1413
+ function csrfLaxProtoCheckMiddleware(requestEv) {
1414
+ checkCSRF(requestEv, "lax-proto");
1415
+ }
1416
+ function csrfCheckMiddleware(requestEv) {
1417
+ checkCSRF(requestEv);
1418
+ }
1419
+ const resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderHandler, isInternal) => {
1420
+ const routeLoaders = [];
1421
+ const routeActions = [];
1422
+ const requestHandlers = [];
1423
+ const isPageRoute = !!(route && isLastModulePageRoute(route.$mods$));
1424
+ if (isInternal) {
1425
+ requestHandlers.push(handleQDataRedirect);
1426
+ }
1427
+ if (isPageRoute) {
1428
+ requestHandlers.push(serverErrorMiddleware(route, renderHandler));
1429
+ }
1430
+ if (serverPlugins) {
1431
+ _resolveRequestHandlers(routeLoaders, routeActions, requestHandlers, serverPlugins, isPageRoute, method);
1432
+ }
1433
+ if (route) {
1434
+ const routeModules = route.$mods$;
1435
+ _resolveRequestHandlers(routeLoaders, routeActions, requestHandlers, routeModules, isPageRoute, method);
1436
+ const routeName = route.$routeName$;
1437
+ if (checkOrigin && (method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE")) {
1438
+ if (checkOrigin === "lax-proto") {
1439
+ requestHandlers.unshift(csrfLaxProtoCheckMiddleware);
1440
+ } else {
1441
+ requestHandlers.unshift(csrfCheckMiddleware);
1442
+ }
1443
+ }
1444
+ if (isPageRoute) {
1445
+ if (method === "POST" || method === "GET") {
1446
+ requestHandlers.push(runServerFunction);
1447
+ }
1448
+ requestHandlers.push(fixTrailingSlash);
1449
+ if (isInternal) {
1450
+ requestHandlers.push(renderQData);
1451
+ }
1452
+ }
1453
+ if (isPageRoute) {
1454
+ requestHandlers.push((ev) => {
1455
+ ev.sharedMap.set(RequestRouteName, routeName);
1456
+ });
1457
+ requestHandlers.push(actionsMiddleware(routeActions));
1458
+ requestHandlers.push(loadersMiddleware(routeLoaders));
1459
+ requestHandlers.push(eTagMiddleware(route));
1460
+ requestHandlers.push(renderHandler);
1461
+ }
1462
+ }
1463
+ return requestHandlers;
1464
+ };
1297
1465
 
1298
1466
  let _asyncRequestStore;
1299
1467
  if (isServer) {
1300
1468
  import('node:async_hooks').then((module) => {
1301
1469
  _asyncRequestStore = new module.AsyncLocalStorage();
1302
1470
  }).catch((err) => {
1303
- console.warn(
1304
- "\n=====================\n Qwik Router Warning:\n AsyncLocalStorage is not available, continuing without it.\n This impacts concurrent async server calls, where they lose access to the ServerRequestEv object.\n=====================\n\n",
1305
- err
1306
- );
1471
+ console.warn("\n=====================\n Qwik Router Warning:\n AsyncLocalStorage is not available, continuing without it.\n This impacts concurrent async server calls, where they lose access to the ServerRequestEv object.\n=====================\n\n", err);
1307
1472
  });
1308
1473
  }
1309
1474
  let qwikRouterConfigActual;
1475
+ async function loadRequestHandlers(qwikRouterConfig, pathname, method, checkOrigin, renderFn, isInternal) {
1476
+ const { routes, serverPlugins, cacheModules } = qwikRouterConfig;
1477
+ const loadedRoute = await loadRoute(routes, cacheModules, pathname, isInternal);
1478
+ const requestHandlers = resolveRequestHandlers(serverPlugins, loadedRoute, method, checkOrigin, renderQwikMiddleware(renderFn), isInternal);
1479
+ return {
1480
+ loadedRoute,
1481
+ requestHandlers
1482
+ };
1483
+ }
1310
1484
  async function requestHandler(serverRequestEv, opts) {
1311
1485
  const { render, checkOrigin } = opts;
1312
1486
  let { qwikRouterConfig } = opts;
@@ -1323,58 +1497,15 @@ async function requestHandler(serverRequestEv, opts) {
1323
1497
  if (pathname === "/.well-known" || pathname.startsWith("/.well-known/")) {
1324
1498
  return null;
1325
1499
  }
1326
- const routeAndHandlers = await loadRequestHandlers(
1327
- qwikRouterConfig,
1328
- pathname,
1329
- serverRequestEv.request.method,
1330
- checkOrigin ?? true,
1331
- render,
1332
- isInternal
1333
- );
1334
- if (routeAndHandlers) {
1335
- const [route, requestHandlers] = routeAndHandlers;
1336
- const rebuildRouteInfo = async (url) => {
1337
- const { pathname: pathname2 } = getRouteMatchPathname(url.pathname);
1338
- const routeAndHandlers2 = await loadRequestHandlers(
1339
- qwikRouterConfig,
1340
- pathname2,
1341
- serverRequestEv.request.method,
1342
- checkOrigin ?? true,
1343
- render,
1344
- isInternal
1345
- );
1346
- if (routeAndHandlers2) {
1347
- const [loadedRoute, requestHandlers2] = routeAndHandlers2;
1348
- return { loadedRoute, requestHandlers: requestHandlers2 };
1349
- } else {
1350
- return { loadedRoute: null, requestHandlers: [] };
1351
- }
1352
- };
1353
- return runQwikRouter(
1354
- serverRequestEv,
1355
- route,
1356
- requestHandlers,
1357
- rebuildRouteInfo,
1358
- qwikRouterConfig.basePathname
1359
- );
1360
- }
1361
- return null;
1362
- }
1363
- async function loadRequestHandlers(qwikRouterConfig, pathname, method, checkOrigin, renderFn, isInternal) {
1364
- const { routes, serverPlugins, menus, cacheModules } = qwikRouterConfig;
1365
- const route = await loadRoute(routes, menus, cacheModules, pathname, isInternal);
1366
- const requestHandlers = resolveRequestHandlers(
1367
- serverPlugins,
1368
- route,
1369
- method,
1370
- checkOrigin,
1371
- renderQwikMiddleware(renderFn),
1372
- isInternal
1373
- );
1374
- if (requestHandlers.length > 0) {
1375
- return [route, requestHandlers];
1500
+ const { loadedRoute, requestHandlers } = await loadRequestHandlers(qwikRouterConfig, pathname, serverRequestEv.request.method, checkOrigin ?? true, render, isInternal);
1501
+ if (qwikRouterConfig.fallthrough && loadedRoute.$notFound$) {
1502
+ return null;
1376
1503
  }
1377
- return null;
1504
+ const rebuildRouteInfo = async (url) => {
1505
+ const { pathname: pathname2 } = getRouteMatchPathname(url.pathname);
1506
+ return loadRequestHandlers(qwikRouterConfig, pathname2, serverRequestEv.request.method, checkOrigin ?? true, render, isInternal);
1507
+ };
1508
+ return runQwikRouter(serverRequestEv, loadedRoute, requestHandlers, rebuildRouteInfo, qwikRouterConfig.basePathname);
1378
1509
  }
1379
1510
 
1380
1511
  const notFounds = [
@@ -1390,7 +1521,9 @@ function getNotFound(prefix) {
1390
1521
  return minimalHtmlResponse(404, "Resource Not Found");
1391
1522
  }
1392
1523
 
1393
- const staticPaths = /* @__PURE__ */ new Set(["__QWIK_ROUTER_STATIC_PATHS_ARRAY__"]);
1524
+ const staticPaths = /* @__PURE__ */ new Set([
1525
+ "__QWIK_ROUTER_STATIC_PATHS_ARRAY__"
1526
+ ]);
1394
1527
  function isStaticPath(method, url) {
1395
1528
  if (method.toUpperCase() !== "GET") {
1396
1529
  return false;
@@ -1418,10 +1551,10 @@ function isStaticPath(method, url) {
1418
1551
  }
1419
1552
 
1420
1553
  class ServerError extends Error {
1554
+ status;
1555
+ data;
1421
1556
  constructor(status, data) {
1422
- super(typeof data === "string" ? data : void 0);
1423
- this.status = status;
1424
- this.data = data;
1557
+ super(typeof data === "string" ? data : void 0), this.status = status, this.data = data;
1425
1558
  }
1426
1559
  }
1427
1560
 
@@ -1431,9 +1564,9 @@ class RedirectMessage extends AbortMessage {
1431
1564
  }
1432
1565
 
1433
1566
  class RewriteMessage extends AbortMessage {
1567
+ pathname;
1434
1568
  constructor(pathname) {
1435
- super();
1436
- this.pathname = pathname;
1569
+ super(), this.pathname = pathname;
1437
1570
  }
1438
1571
  }
1439
1572
 
@@ -1472,7 +1605,11 @@ class _TextEncoderStream_polyfill {
1472
1605
  },
1473
1606
  flush: (controller) => {
1474
1607
  if (this.#pendingHighSurrogate !== null) {
1475
- controller.enqueue(new Uint8Array([239, 191, 189]));
1608
+ controller.enqueue(new Uint8Array([
1609
+ 239,
1610
+ 191,
1611
+ 189
1612
+ ]));
1476
1613
  }
1477
1614
  }
1478
1615
  });
@@ -1490,4 +1627,4 @@ class _TextEncoderStream_polyfill {
1490
1627
  }
1491
1628
  }
1492
1629
 
1493
- export { AbortMessage, RedirectMessage, RequestEvShareQData, RewriteMessage, ServerError, _TextEncoderStream_polyfill, _asyncRequestStore, getErrorHtml, getNotFound, isStaticPath, mergeHeadersCookies, requestHandler };
1630
+ export { AbortMessage, RedirectMessage, RequestEvShareQData, RewriteMessage, ServerError, _TextEncoderStream_polyfill, _asyncRequestStore, clearSsrCache, getErrorHtml, getNotFound, isStaticPath, mergeHeadersCookies, requestHandler };