@qwik.dev/router 2.0.0-beta.27 → 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 +171 -157
  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 +24 -8
  26. package/lib/middleware/cloudflare-pages/index.mjs +10 -3
  27. package/lib/middleware/deno/index.mjs +23 -8
  28. package/lib/middleware/netlify-edge/index.mjs +10 -3
  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 +10 -3
  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 +1106 -630
  38. package/modules.d.ts +7 -4
  39. package/package.json +9 -9
  40. package/lib/chunks/format-error.mjs +0 -137
  41. package/lib/chunks/index.mjs +0 -884
  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
  }
@@ -2,6 +2,7 @@ import { setServerPlatform } from '@qwik.dev/core/server';
2
2
  import { _TextEncoderStream_polyfill, isStaticPath, getNotFound, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { join, extname } from 'node:path';
4
4
  import { M as MIME_TYPES } from '../../chunks/mime-types.mjs';
5
+ import { isDev } from '@qwik.dev/core/build';
5
6
 
6
7
  function getRequestUrl(request, opts) {
7
8
  const url = new URL(request.url);
@@ -71,9 +72,12 @@ function createQwikRouter(opts) {
71
72
  return null;
72
73
  } catch (e) {
73
74
  console.error(e);
74
- return new Response(String(e || "Error"), {
75
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
75
76
  status: 500,
76
- 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
+ }
77
81
  });
78
82
  }
79
83
  }
@@ -83,13 +87,19 @@ function createQwikRouter(opts) {
83
87
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
84
88
  return new Response(notFoundHtml, {
85
89
  status: 404,
86
- 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
+ }
87
94
  });
88
95
  } catch (e) {
89
96
  console.error(e);
90
- return new Response(String(e || "Error"), {
97
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
91
98
  status: 500,
92
- 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
+ }
93
103
  });
94
104
  }
95
105
  };
@@ -117,7 +127,10 @@ function createQwikRouter(opts) {
117
127
  if (!await content.exists()) {
118
128
  return new Response("Not Found", {
119
129
  status: 404,
120
- 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
+ }
121
134
  });
122
135
  }
123
136
  const ext = extname(filePath).replace(/^\./, "");
@@ -132,9 +145,12 @@ function createQwikRouter(opts) {
132
145
  return null;
133
146
  } catch (e) {
134
147
  console.error(e);
135
- return new Response(String(e || "Error"), {
148
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
136
149
  status: 500,
137
- 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
+ }
138
154
  });
139
155
  }
140
156
  };
@@ -1,5 +1,6 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
2
  import { _TextEncoderStream_polyfill, isStaticPath, mergeHeadersCookies, requestHandler, getNotFound } from '@qwik.dev/router/middleware/request-handler';
3
+ import { isDev } from '@qwik.dev/core/build';
3
4
 
4
5
  function createQwikRouter(opts) {
5
6
  if (opts.qwikCityPlan && !opts.qwikRouterConfig) {
@@ -78,13 +79,19 @@ function createQwikRouter(opts) {
78
79
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
79
80
  return new Response(notFoundHtml, {
80
81
  status: 404,
81
- 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
+ }
82
86
  });
83
87
  } catch (e) {
84
88
  console.error(e);
85
- return new Response(String(e || "Error"), {
89
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
86
90
  status: 500,
87
- 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
+ }
88
95
  });
89
96
  }
90
97
  }
@@ -2,6 +2,7 @@ import { setServerPlatform } from '@qwik.dev/core/server';
2
2
  import { isStaticPath, getNotFound, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { M as MIME_TYPES } from '../../chunks/mime-types.mjs';
4
4
  import { join, fromFileUrl, extname } from 'https://deno.land/std/path/mod.ts';
5
+ import { isDev } from '@qwik.dev/core/build';
5
6
 
6
7
  function getRequestUrl(request, opts, info) {
7
8
  const url = new URL(request.url);
@@ -63,9 +64,12 @@ function createQwikRouter(opts) {
63
64
  return null;
64
65
  } catch (e) {
65
66
  console.error(e);
66
- return new Response(String(e || "Error"), {
67
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
67
68
  status: 500,
68
- 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
+ }
69
73
  });
70
74
  }
71
75
  }
@@ -75,13 +79,19 @@ function createQwikRouter(opts) {
75
79
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
76
80
  return new Response(notFoundHtml, {
77
81
  status: 404,
78
- 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
+ }
79
86
  });
80
87
  } catch (e) {
81
88
  console.error(e);
82
- return new Response(String(e || "Error"), {
89
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
83
90
  status: 500,
84
- 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
+ }
85
95
  });
86
96
  }
87
97
  };
@@ -99,7 +109,9 @@ function createQwikRouter(opts) {
99
109
  return {
100
110
  filePath,
101
111
  // @ts-ignore
102
- content: await Deno.open(filePath, { read: true })
112
+ content: await Deno.open(filePath, {
113
+ read: true
114
+ })
103
115
  };
104
116
  };
105
117
  const staticFile = async (request) => {
@@ -119,9 +131,12 @@ function createQwikRouter(opts) {
119
131
  return null;
120
132
  } catch (e) {
121
133
  console.error(e);
122
- return new Response(String(e || "Error"), {
134
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
123
135
  status: 500,
124
- 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
+ }
125
140
  });
126
141
  }
127
142
  };
@@ -1,5 +1,6 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
2
  import { isStaticPath, mergeHeadersCookies, requestHandler, getNotFound } from '@qwik.dev/router/middleware/request-handler';
3
+ import { isDev } from '@qwik.dev/core/build';
3
4
 
4
5
  function createQwikRouter(opts) {
5
6
  if (opts.qwikCityPlan && !opts.qwikRouterConfig) {
@@ -53,13 +54,19 @@ function createQwikRouter(opts) {
53
54
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
54
55
  return new Response(notFoundHtml, {
55
56
  status: 404,
56
- 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
+ }
57
61
  });
58
62
  } catch (e) {
59
63
  console.error(e);
60
- return new Response(String(e || "Error"), {
64
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
61
65
  status: 500,
62
- 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
+ }
63
70
  });
64
71
  }
65
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>>;