@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
@@ -41,7 +41,13 @@ function createQwikRouter(opts) {
41
41
  });
42
42
  });
43
43
  };
44
- return { fixPath, router, staticFile, notFound, handle };
44
+ return {
45
+ fixPath,
46
+ router,
47
+ staticFile,
48
+ notFound,
49
+ handle
50
+ };
45
51
  } catch (err) {
46
52
  throw new Error(err.message);
47
53
  }
@@ -73,14 +73,19 @@ function createQwikRouter(opts) {
73
73
  const notFoundHtml = !req.headers.accept?.includes("text/html") || isStaticPath(req.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
74
74
  return {
75
75
  status: 404,
76
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname },
76
+ headers: {
77
+ "Content-Type": "text/html; charset=utf-8",
78
+ "X-Not-Found": url.pathname
79
+ },
77
80
  body: notFoundHtml
78
81
  };
79
82
  } catch (e) {
80
83
  console.error(e);
81
84
  return {
82
85
  status: 500,
83
- headers: { "Content-Type": "text/plain; charset=utf-8" }
86
+ headers: {
87
+ "Content-Type": "text/plain; charset=utf-8"
88
+ }
84
89
  };
85
90
  }
86
91
  }
@@ -74,7 +74,10 @@ function createQwikRouter(opts) {
74
74
  console.error(e);
75
75
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
76
76
  status: 500,
77
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "bun-server" }
77
+ headers: {
78
+ "Content-Type": "text/plain; charset=utf-8",
79
+ "X-Error": "bun-server"
80
+ }
78
81
  });
79
82
  }
80
83
  }
@@ -84,13 +87,19 @@ function createQwikRouter(opts) {
84
87
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
85
88
  return new Response(notFoundHtml, {
86
89
  status: 404,
87
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname }
90
+ headers: {
91
+ "Content-Type": "text/html; charset=utf-8",
92
+ "X-Not-Found": url.pathname
93
+ }
88
94
  });
89
95
  } catch (e) {
90
96
  console.error(e);
91
97
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
92
98
  status: 500,
93
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "bun-server" }
99
+ headers: {
100
+ "Content-Type": "text/plain; charset=utf-8",
101
+ "X-Error": "bun-server"
102
+ }
94
103
  });
95
104
  }
96
105
  };
@@ -118,7 +127,10 @@ function createQwikRouter(opts) {
118
127
  if (!await content.exists()) {
119
128
  return new Response("Not Found", {
120
129
  status: 404,
121
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Not-Found": url.pathname }
130
+ headers: {
131
+ "Content-Type": "text/plain; charset=utf-8",
132
+ "X-Not-Found": url.pathname
133
+ }
122
134
  });
123
135
  }
124
136
  const ext = extname(filePath).replace(/^\./, "");
@@ -135,7 +147,10 @@ function createQwikRouter(opts) {
135
147
  console.error(e);
136
148
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
137
149
  status: 500,
138
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "bun-server" }
150
+ headers: {
151
+ "Content-Type": "text/plain; charset=utf-8",
152
+ "X-Error": "bun-server"
153
+ }
139
154
  });
140
155
  }
141
156
  };
@@ -79,13 +79,19 @@ function createQwikRouter(opts) {
79
79
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
80
80
  return new Response(notFoundHtml, {
81
81
  status: 404,
82
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname }
82
+ headers: {
83
+ "Content-Type": "text/html; charset=utf-8",
84
+ "X-Not-Found": url.pathname
85
+ }
83
86
  });
84
87
  } catch (e) {
85
88
  console.error(e);
86
89
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
87
90
  status: 500,
88
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "cloudflare-pages" }
91
+ headers: {
92
+ "Content-Type": "text/plain; charset=utf-8",
93
+ "X-Error": "cloudflare-pages"
94
+ }
89
95
  });
90
96
  }
91
97
  }
@@ -66,7 +66,10 @@ function createQwikRouter(opts) {
66
66
  console.error(e);
67
67
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
68
68
  status: 500,
69
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "deno-server" }
69
+ headers: {
70
+ "Content-Type": "text/plain; charset=utf-8",
71
+ "X-Error": "deno-server"
72
+ }
70
73
  });
71
74
  }
72
75
  }
@@ -76,13 +79,19 @@ function createQwikRouter(opts) {
76
79
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
77
80
  return new Response(notFoundHtml, {
78
81
  status: 404,
79
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname }
82
+ headers: {
83
+ "Content-Type": "text/html; charset=utf-8",
84
+ "X-Not-Found": url.pathname
85
+ }
80
86
  });
81
87
  } catch (e) {
82
88
  console.error(e);
83
89
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
84
90
  status: 500,
85
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "deno-server" }
91
+ headers: {
92
+ "Content-Type": "text/plain; charset=utf-8",
93
+ "X-Error": "deno-server"
94
+ }
86
95
  });
87
96
  }
88
97
  };
@@ -100,7 +109,9 @@ function createQwikRouter(opts) {
100
109
  return {
101
110
  filePath,
102
111
  // @ts-ignore
103
- content: await Deno.open(filePath, { read: true })
112
+ content: await Deno.open(filePath, {
113
+ read: true
114
+ })
104
115
  };
105
116
  };
106
117
  const staticFile = async (request) => {
@@ -122,7 +133,10 @@ function createQwikRouter(opts) {
122
133
  console.error(e);
123
134
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
124
135
  status: 500,
125
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "deno-server" }
136
+ headers: {
137
+ "Content-Type": "text/plain; charset=utf-8",
138
+ "X-Error": "deno-server"
139
+ }
126
140
  });
127
141
  }
128
142
  };
@@ -54,13 +54,19 @@ function createQwikRouter(opts) {
54
54
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
55
55
  return new Response(notFoundHtml, {
56
56
  status: 404,
57
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname }
57
+ headers: {
58
+ "Content-Type": "text/html; charset=utf-8",
59
+ "X-Not-Found": url.pathname
60
+ }
58
61
  });
59
62
  } catch (e) {
60
63
  console.error(e);
61
64
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
62
65
  status: 500,
63
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "netlify-edge" }
66
+ headers: {
67
+ "Content-Type": "text/plain; charset=utf-8",
68
+ "X-Error": "netlify-edge"
69
+ }
64
70
  });
65
71
  }
66
72
  }
@@ -7,9 +7,6 @@ import { fileURLToPath } from 'node:url';
7
7
  import { M as MIME_TYPES } from '../../chunks/mime-types.mjs';
8
8
  import { Http2ServerRequest } from 'node:http2';
9
9
 
10
- function computeOrigin(req, opts) {
11
- return opts?.getOrigin?.(req) ?? opts?.origin ?? process.env.ORIGIN ?? fallbackOrigin(req);
12
- }
13
10
  function fallbackOrigin(req) {
14
11
  const { PROTOCOL_HEADER, HOST_HEADER } = process.env;
15
12
  const headers = req.headers;
@@ -18,11 +15,14 @@ function fallbackOrigin(req) {
18
15
  const host = headers[hostHeader];
19
16
  return `${protocol}://${host}`;
20
17
  }
21
- function getUrl(req, origin) {
22
- return normalizeUrl(req.originalUrl || req.url || "/", origin);
18
+ function computeOrigin(req, opts) {
19
+ return opts?.getOrigin?.(req) ?? opts?.origin ?? process.env.ORIGIN ?? fallbackOrigin(req);
23
20
  }
24
21
  function isIgnoredError(message = "") {
25
- const ignoredErrors = ["The stream has been destroyed", "write after end"];
22
+ const ignoredErrors = [
23
+ "The stream has been destroyed",
24
+ "write after end"
25
+ ];
26
26
  return ignoredErrors.some((ignored) => message.includes(ignored));
27
27
  }
28
28
  const invalidHeadersPattern = /^:(method|scheme|authority|path)$/i;
@@ -30,6 +30,9 @@ function normalizeUrl(url, base) {
30
30
  const DOUBLE_SLASH_REG = /\/\/|\\\\/g;
31
31
  return new URL(url.replace(DOUBLE_SLASH_REG, "/"), base);
32
32
  }
33
+ function getUrl(req, origin) {
34
+ return normalizeUrl(req.originalUrl || req.url || "/", origin);
35
+ }
33
36
  async function fromNodeHttp(url, req, res, mode, getClientConn) {
34
37
  const requestHeaders = new Headers();
35
38
  const nodeRequestHeaders = req.headers;
@@ -116,7 +119,6 @@ async function fromNodeHttp(url, req, res, mode, getClientConn) {
116
119
  ssr: true,
117
120
  incomingMessage: req,
118
121
  node: process.versions.node
119
- // Weirdly needed to make typecheck of insights happy
120
122
  },
121
123
  locale: void 0
122
124
  };
@@ -135,13 +137,7 @@ function createQwikRouter(opts) {
135
137
  const router = async (req, res, next) => {
136
138
  try {
137
139
  const origin = computeOrigin(req, opts);
138
- const serverRequestEv = await fromNodeHttp(
139
- getUrl(req, origin),
140
- req,
141
- res,
142
- "server",
143
- opts.getClientConn
144
- );
140
+ const serverRequestEv = await fromNodeHttp(getUrl(req, origin), req, res, "server", opts.getClientConn);
145
141
  if (isDev && opts.platform) {
146
142
  Object.assign(serverRequestEv.platform, opts.platform);
147
143
  }
@@ -2,6 +2,7 @@ import type { Action } from '@qwik.dev/router';
2
2
  import type { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import type { EnvGetter as EnvGetter_2 } from '@qwik.dev/router/middleware/request-handler';
4
4
  import type { FailReturn } from '@qwik.dev/router';
5
+ import type { JSXOutput } from '@qwik.dev/core';
5
6
  import type { Loader as Loader_2 } from '@qwik.dev/router';
6
7
  import type { QwikCityPlan } from '@qwik.dev/router';
7
8
  import type { QwikIntrinsicElements } from '@qwik.dev/core';
@@ -93,6 +94,27 @@ declare interface CacheControlOptions {
93
94
  /** @public */
94
95
  declare type CacheControlTarget = 'Cache-Control' | 'CDN-Cache-Control' | 'Cloudflare-CDN-Cache-Control' | 'Vercel-CDN-Cache-Control' | '~ANY-OTHER-STRING' | (string & {});
95
96
 
97
+ /**
98
+ * The cacheKey export type. When exported from a page module alongside eTag, enables in-memory SSR
99
+ * caching.
100
+ *
101
+ * - `true`: use the default cache key `status|eTag|pathname`
102
+ * - Function: receives status, eTag, and pathname; returns a cache key string or null (no cache)
103
+ *
104
+ * @public
105
+ */
106
+ declare type CacheKeyFn = true | ((status: number, eTag: string, pathname: string) => string | null);
107
+
108
+ /**
109
+ * Clear the in-memory SSR cache. Call after deployments or data changes.
110
+ *
111
+ * When `cacheKey` is provided, only that single entry is removed; otherwise the entire cache is
112
+ * cleared.
113
+ *
114
+ * @public
115
+ */
116
+ export declare function clearSsrCache(cacheKey?: string): void;
117
+
96
118
  /** @public */
97
119
  export declare interface ClientConn {
98
120
  ip?: string;
@@ -121,8 +143,18 @@ declare interface ContentMenu {
121
143
 
122
144
  declare type ContentModule = PageModule | LayoutModule;
123
145
 
146
+ /**
147
+ * The eTag export type: a static string or a function receiving DocumentHeadProps.
148
+ *
149
+ * @public
150
+ */
151
+ declare type ContentModuleETag = string | ((props: DocumentHeadProps) => string | null);
152
+
153
+ /** @public */
124
154
  declare type ContentModuleHead = DocumentHead | ResolvedDocumentHead;
125
155
 
156
+ declare type ContentModuleLoader = () => ValueOrPromise<ContentModule>;
157
+
126
158
  /** @public */
127
159
  export declare interface Cookie {
128
160
  /** Gets a `Request` cookie header value by name. */
@@ -209,6 +241,8 @@ declare type DocumentHead = DocumentHeadValue | ((props: DocumentHeadProps) => D
209
241
  /** @public */
210
242
  declare interface DocumentHeadProps extends RouteLocation {
211
243
  readonly head: ResolvedDocumentHead;
244
+ /** The HTTP status code of the response (e.g. 200, 404). */
245
+ readonly status: number;
212
246
  /** @deprecated This is not necessary, it works correctly without */
213
247
  readonly withLocale: <T>(fn: () => T) => T;
214
248
  readonly resolveValue: ResolveSyncValue_2;
@@ -309,28 +343,42 @@ declare type InformationalCode = 100 | 101 | 102 | 103;
309
343
  export declare function isStaticPath(method: string, url: URL): boolean;
310
344
 
311
345
  declare interface LayoutModule extends RouteModule {
312
- readonly default: unknown;
346
+ readonly default?: (props: Record<string, never>) => JSXOutput;
347
+ readonly routeConfig?: RouteConfig;
313
348
  readonly head?: ContentModuleHead;
314
349
  }
315
350
 
316
- declare type LoadedRoute = [
317
- routeName: string,
318
- params: PathParams,
319
- mods: (RouteModule | ContentModule)[],
320
- menu: ContentMenu | undefined,
321
- routeBundleNames: string[] | undefined
322
- ];
351
+ /** The route to render */
352
+ declare interface LoadedRoute {
353
+ /** The canonical path of the route, e.g. `/products/[id]` */
354
+ $routeName$: string;
355
+ /** The route parameters, e.g. `{ id: '123' }` */
356
+ $params$: PathParams;
357
+ /** The modules associated with this route (on 404, contains only the error component) */
358
+ $mods$: (RouteModule | ContentModule)[];
359
+ /** The menu associated with this route */
360
+ $menu$?: ContentMenu | undefined;
361
+ /** The bundle names for this route */
362
+ $routeBundleNames$?: string[] | undefined;
363
+ /** Whether this route is a not-found (404) route */
364
+ $notFound$?: boolean;
365
+ /** The error module loader (nearest _E ancestor), for rendering ServerErrors */
366
+ $errorLoader$?: ContentModuleLoader;
367
+ }
323
368
 
324
369
  /** @public */
325
370
  export declare const mergeHeadersCookies: (headers: Headers, cookies: Cookie) => Headers;
326
371
 
327
372
  /** @public */
328
- declare interface PageModule extends RouteModule {
329
- readonly default: unknown;
373
+ declare type PageModule = RouteModule & {
374
+ readonly default: (props: Record<string, never>) => JSXOutput;
375
+ readonly routeConfig?: RouteConfig;
330
376
  readonly head?: ContentModuleHead;
377
+ readonly eTag?: ContentModuleETag;
378
+ readonly cacheKey?: CacheKeyFn;
331
379
  readonly headings?: ContentHeading[];
332
380
  readonly onStaticGenerate?: StaticGenerateHandler;
333
- }
381
+ };
334
382
 
335
383
  /** @public */
336
384
  declare type PathParams = Record<string, string>;
@@ -550,7 +598,7 @@ declare interface RequestEventInternal extends Readonly<RequestEvent>, Readonly<
550
598
  readonly [RequestEvLoaders]: Record<string, ValueOrPromise<unknown> | undefined>;
551
599
  readonly [RequestEvLoaderSerializationStrategyMap]: Map<string, SerializationStrategy>;
552
600
  readonly [RequestEvMode]: ServerRequestMode;
553
- readonly [RequestEvRoute]: LoadedRoute | null;
601
+ readonly [RequestEvRoute]: LoadedRoute;
554
602
  /**
555
603
  * Check if this request is already written to.
556
604
  *
@@ -615,6 +663,28 @@ export declare class RewriteMessage extends AbortMessage {
615
663
  constructor(pathname: string);
616
664
  }
617
665
 
666
+ /**
667
+ * Unified route configuration export. Groups head, eTag, and cacheKey with the same resolution
668
+ * rules as DocumentHead: can be a static object or a function receiving DocumentHeadProps.
669
+ *
670
+ * When a module exports `routeConfig`, the separate `head`, `eTag`, and `cacheKey` exports are
671
+ * ignored for that module.
672
+ *
673
+ * @public
674
+ */
675
+ declare type RouteConfig = RouteConfigValue | ((props: DocumentHeadProps) => RouteConfigValue);
676
+
677
+ /**
678
+ * The value shape returned by a routeConfig export (object form or function return).
679
+ *
680
+ * @public
681
+ */
682
+ declare interface RouteConfigValue {
683
+ readonly head?: DocumentHeadValue;
684
+ readonly eTag?: ContentModuleETag;
685
+ readonly cacheKey?: CacheKeyFn;
686
+ }
687
+
618
688
  /** @public */
619
689
  declare interface RouteLocation {
620
690
  readonly params: Readonly<Record<string, string>>;