@qwik.dev/router 2.0.0-beta.36 → 2.0.0-beta.38

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 (41) hide show
  1. package/lib/adapters/cloudflare-pages/vite/index.mjs +3 -5
  2. package/lib/adapters/shared/vite/index.d.ts +3 -4
  3. package/lib/adapters/shared/vite/index.mjs +167 -155
  4. package/lib/chunks/dev-preloaded-route-loader.mjs +3 -0
  5. package/lib/chunks/format.mjs +31 -0
  6. package/lib/chunks/fs.mjs +10 -8
  7. package/lib/chunks/head.qwik.mjs +1425 -0
  8. package/lib/chunks/http-error.qwik.mjs +2 -2
  9. package/lib/chunks/pathname.mjs +3 -32
  10. package/lib/chunks/request-body-limit.mjs +37 -0
  11. package/lib/chunks/request-path.mjs +60 -0
  12. package/lib/chunks/routes.mjs +47 -0
  13. package/lib/chunks/server-error.mjs +19 -0
  14. package/lib/chunks/system.mjs +8 -8
  15. package/lib/chunks/use-functions.qwik.mjs +3 -12
  16. package/lib/chunks/user-response.mjs +1875 -0
  17. package/lib/chunks/worker-thread.mjs +317 -0
  18. package/lib/index.d.ts +251 -44
  19. package/lib/index.qwik.mjs +734 -485
  20. package/lib/middleware/aws-lambda/index.mjs +2 -1
  21. package/lib/middleware/azure-swa/index.mjs +13 -9
  22. package/lib/middleware/cloudflare-pages/index.mjs +2 -2
  23. package/lib/middleware/deno/index.d.ts +2 -0
  24. package/lib/middleware/deno/index.mjs +2 -1
  25. package/lib/middleware/netlify-edge/index.mjs +2 -2
  26. package/lib/middleware/node/index.d.ts +9 -1
  27. package/lib/middleware/node/index.mjs +28 -3
  28. package/lib/middleware/request-handler/index.d.ts +65 -26
  29. package/lib/middleware/request-handler/index.mjs +38 -1679
  30. package/lib/middleware/vercel-edge/index.mjs +2 -2
  31. package/lib/ssg/index.d.ts +5 -13
  32. package/lib/ssg/index.mjs +14 -53
  33. package/lib/vite/index.d.ts +23 -0
  34. package/lib/vite/index.mjs +533 -122
  35. package/package.json +4 -3
  36. package/lib/chunks/deepFreeze.qwik.mjs +0 -18
  37. package/lib/chunks/error-handler.mjs +0 -57
  38. package/lib/chunks/not-found-wrapper.qwik.mjs +0 -25
  39. package/lib/chunks/redirect-handler.mjs +0 -6
  40. package/lib/chunks/routing.qwik.mjs +0 -821
  41. package/lib/chunks/worker-thread.qwik.mjs +0 -2585
@@ -1,4 +1,5 @@
1
1
  import { createQwikRouter as createQwikRouter$1 } from '@qwik.dev/router/middleware/node';
2
+ import { e as ensureSlash } from '../../chunks/pathname.mjs';
2
3
 
3
4
  function createQwikRouter(opts) {
4
5
  try {
@@ -22,7 +23,7 @@ function createQwikRouter(opts) {
22
23
  return pathT;
23
24
  }
24
25
  if (!url.pathname.endsWith("/")) {
25
- return url.pathname + "/" + url.search;
26
+ return ensureSlash(url.pathname) + url.search;
26
27
  }
27
28
  }
28
29
  return pathT;
@@ -1,5 +1,5 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
- import { requestHandler, isStaticPath, getNotFound } from '@qwik.dev/router/middleware/request-handler';
2
+ import { requestHandler, isStaticPath, getErrorHtml } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { parseString } from 'set-cookie-parser';
4
4
 
5
5
  function createQwikRouter(opts) {
@@ -26,23 +26,27 @@ function createQwikRouter(opts) {
26
26
  },
27
27
  request: new Request(url, options),
28
28
  getWritableStream: (status, headers, cookies, resolve) => {
29
+ const chunks = [];
30
+ let bodyLength = 0;
29
31
  const response = {
30
32
  status,
31
- body: new Uint8Array(),
32
33
  headers: {},
33
34
  cookies: cookies.headers().map((header) => parseString(header))
34
35
  };
35
36
  headers.forEach((value, key) => response.headers[key] = value);
36
37
  return new WritableStream({
37
38
  write(chunk) {
38
- if (response.body instanceof Uint8Array) {
39
- const newBuffer = new Uint8Array(response.body.length + chunk.length);
40
- newBuffer.set(response.body);
41
- newBuffer.set(chunk, response.body.length);
42
- response.body = newBuffer;
43
- }
39
+ chunks.push(chunk.slice());
40
+ bodyLength += chunk.length;
44
41
  },
45
42
  close() {
43
+ const body = new Uint8Array(bodyLength);
44
+ let offset = 0;
45
+ for (const chunk of chunks) {
46
+ body.set(chunk, offset);
47
+ offset += chunk.length;
48
+ }
49
+ response.body = body;
46
50
  resolve(response);
47
51
  }
48
52
  });
@@ -66,7 +70,7 @@ function createQwikRouter(opts) {
66
70
  return response;
67
71
  }
68
72
  }
69
- const notFoundHtml = !req.headers.accept?.includes("text/html") || isStaticPath(req.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
73
+ const notFoundHtml = !req.headers.accept?.includes("text/html") || isStaticPath(req.method || "GET", url) ? "Not Found" : getErrorHtml(404, "Not Found");
70
74
  return {
71
75
  status: 404,
72
76
  headers: {
@@ -1,5 +1,5 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
- import { _TextEncoderStream_polyfill, isStaticPath, mergeHeadersCookies, requestHandler, getNotFound } from '@qwik.dev/router/middleware/request-handler';
2
+ import { _TextEncoderStream_polyfill, isStaticPath, mergeHeadersCookies, requestHandler, getErrorHtml } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { isDev } from '@qwik.dev/core/build';
4
4
 
5
5
  function createQwikRouter(opts) {
@@ -72,7 +72,7 @@ function createQwikRouter(opts) {
72
72
  return response;
73
73
  }
74
74
  }
75
- const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
75
+ const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getErrorHtml(404, "Not Found");
76
76
  return new Response(notFoundHtml, {
77
77
  status: 404,
78
78
  headers: {
@@ -33,6 +33,8 @@ export declare interface QwikRouterDenoMiddleware {
33
33
 
34
34
  /** @public */
35
35
  export declare interface QwikRouterDenoOptions extends ServerRenderOptions {
36
+ /** Maximum request body size in bytes. Defaults to 10 MiB. */
37
+ requestBodyLimit?: number;
36
38
  /** Options for serving static files */
37
39
  static?: {
38
40
  /** The root folder for statics files. Defaults to /dist */
@@ -1,6 +1,7 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
2
  import { isStaticPath, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { M as MIME_TYPES, n as normalizeRequestUrl } from '../../chunks/url.mjs';
4
+ import { l as limitRequestBody } from '../../chunks/request-body-limit.mjs';
4
5
  import { join, fromFileUrl, extname } from 'https://deno.land/std/path/mod.ts';
5
6
  import { isDev } from '@qwik.dev/core/build';
6
7
 
@@ -26,7 +27,7 @@ function createQwikRouter(opts) {
26
27
  url,
27
28
  // @ts-ignore
28
29
  env: Deno.env,
29
- request,
30
+ request: limitRequestBody(request, opts.requestBodyLimit),
30
31
  getWritableStream: (status, headers, cookies, resolve) => {
31
32
  const { readable, writable } = new TransformStream();
32
33
  const response = new Response(readable, {
@@ -1,5 +1,5 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
- import { isStaticPath, mergeHeadersCookies, requestHandler, getNotFound } from '@qwik.dev/router/middleware/request-handler';
2
+ import { isStaticPath, mergeHeadersCookies, requestHandler, getErrorHtml } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { isDev } from '@qwik.dev/core/build';
4
4
 
5
5
  function createQwikRouter(opts) {
@@ -47,7 +47,7 @@ function createQwikRouter(opts) {
47
47
  return response;
48
48
  }
49
49
  }
50
- const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
50
+ const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getErrorHtml(404, "Not Found");
51
51
  return new Response(notFoundHtml, {
52
52
  status: 404,
53
53
  headers: {
@@ -33,14 +33,22 @@ export declare type QwikCityNodeRequestOptions = QwikRouterNodeRequestOptions;
33
33
 
34
34
  /** @public */
35
35
  export declare interface QwikRouterNodeMiddleware {
36
- router: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: NodeRequestNextFunction) => Promise<void>;
36
+ router: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: NodeRequestNextFunction, limits?: QwikRouterNodeRequestLimits) => Promise<void>;
37
37
  /** @deprecated `router` handles 404 responses. Will be removed in V3. */
38
38
  notFound: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e: any) => void) => Promise<void>;
39
39
  staticFile: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e?: any) => void) => Promise<void>;
40
40
  }
41
41
 
42
+ /** @public */
43
+ export declare interface QwikRouterNodeRequestLimits {
44
+ /** Maximum request body size in bytes imposed by the host. */
45
+ bodyLimit?: number;
46
+ }
47
+
42
48
  /** @public */
43
49
  export declare interface QwikRouterNodeRequestOptions extends ServerRenderOptions {
50
+ /** Maximum request body size in bytes. Defaults to 10 MiB. */
51
+ requestBodyLimit?: number;
44
52
  /** Options for serving static files */
45
53
  static?: {
46
54
  /** The root folder for statics files. Defaults to /dist */
@@ -5,7 +5,9 @@ import { createReadStream } from 'node:fs';
5
5
  import { join, basename, extname } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { n as normalizeRequestUrl, M as MIME_TYPES } from '../../chunks/url.mjs';
8
+ import { d as devPreloadedRouteLoaders } from '../../chunks/dev-preloaded-route-loader.mjs';
8
9
  import { Http2ServerRequest } from 'node:http2';
10
+ import { D as DEFAULT_REQUEST_BODY_LIMIT, v as validateRequestBodyLimit, R as RequestBodyLimitError } from '../../chunks/request-body-limit.mjs';
9
11
 
10
12
  function fallbackOrigin(req) {
11
13
  const { PROTOCOL_HEADER, HOST_HEADER } = process.env;
@@ -184,7 +186,8 @@ function createNodeResponseSink(res) {
184
186
  close
185
187
  };
186
188
  }
187
- async function fromNodeHttp(url, req, res, mode, getClientConn) {
189
+ async function fromNodeHttp(url, req, res, mode, getClientConn, requestBodyLimit = DEFAULT_REQUEST_BODY_LIMIT) {
190
+ validateRequestBodyLimit(requestBodyLimit);
188
191
  const requestHeaders = new Headers();
189
192
  const nodeRequestHeaders = req.headers;
190
193
  try {
@@ -204,7 +207,14 @@ async function fromNodeHttp(url, req, res, mode, getClientConn) {
204
207
  console.error(err);
205
208
  }
206
209
  const getRequestBody = async function* () {
210
+ let received = 0;
207
211
  for await (const chunk of req) {
212
+ const byteLength = typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
213
+ if (byteLength > requestBodyLimit - received) {
214
+ req.resume();
215
+ throw new RequestBodyLimitError(requestBodyLimit);
216
+ }
217
+ received += byteLength;
208
218
  yield chunk;
209
219
  }
210
220
  };
@@ -280,13 +290,28 @@ function createQwikRouter(opts) {
280
290
  setServerPlatform(opts.manifest);
281
291
  }
282
292
  const staticFolder = opts.static?.root ?? join(fileURLToPath(import.meta.url), "..", "..", "dist");
283
- const router = async (req, res, next) => {
293
+ const router = async (req, res, next, limits = {}) => {
284
294
  try {
285
295
  const origin = computeOrigin(req, opts);
286
- const serverRequestEv = await fromNodeHttp(getUrl(req, origin), req, res, "server", opts.getClientConn);
296
+ let requestBodyLimit = opts.requestBodyLimit;
297
+ if (limits?.bodyLimit !== void 0) {
298
+ if (requestBodyLimit === void 0) {
299
+ requestBodyLimit = limits.bodyLimit;
300
+ } else {
301
+ requestBodyLimit = Math.min(requestBodyLimit, limits.bodyLimit);
302
+ }
303
+ }
304
+ const serverRequestEv = await fromNodeHttp(getUrl(req, origin), req, res, "server", opts.getClientConn, requestBodyLimit);
287
305
  if (isDev && opts.platform) {
288
306
  Object.assign(serverRequestEv.platform, opts.platform);
289
307
  }
308
+ if (isDev) {
309
+ const loader = devPreloadedRouteLoaders.get(req);
310
+ if (loader) {
311
+ devPreloadedRouteLoaders.set(serverRequestEv.request, loader);
312
+ devPreloadedRouteLoaders.delete(req);
313
+ }
314
+ }
290
315
  const handled = await requestHandler(serverRequestEv, opts);
291
316
  if (handled) {
292
317
  const err = await handled.completion;
@@ -10,11 +10,12 @@ import type { RenderOptions } from '@qwik.dev/core/server';
10
10
  import { RequestEvent as RequestEvent_2 } from '@qwik.dev/router/middleware/request-handler';
11
11
  import type { RequestHandler as RequestHandler_2 } from '@qwik.dev/router/middleware/request-handler';
12
12
  import type { ResolveSyncValue as ResolveSyncValue_2 } from '@qwik.dev/router/middleware/request-handler';
13
- import { SerializationStrategy } from '@qwik.dev/core/internal';
14
13
  import type { ValueOrPromise } from '@qwik.dev/core';
15
14
 
16
15
  /** @public */
17
16
  export declare class AbortMessage {
17
+ /** Type-only brand so `Exclude<>` can drop control-flow signals from resolved loader/action data. */
18
+ readonly __controlFlow: true;
18
19
  }
19
20
 
20
21
  /** @internal */
@@ -92,15 +93,35 @@ declare interface CacheControlOptions {
92
93
  declare type CacheControlTarget = 'Cache-Control' | 'CDN-Cache-Control' | 'Cloudflare-CDN-Cache-Control' | 'Vercel-CDN-Cache-Control' | '~ANY-OTHER-STRING' | (string & {});
93
94
 
94
95
  /**
95
- * The cacheKey export type. When exported from a page module alongside eTag, enables in-memory SSR
96
- * caching.
96
+ * Cache key function. Used by `routeConfig.cacheKey` (SSR HTML cache) and by `routeLoader$`'s
97
+ * `cacheKey` option (per-loader JSON cache).
97
98
  *
98
- * - `true`: use the default cache key `status|eTag|pathname`
99
- * - Function: receives status, eTag, and pathname; returns a cache key string or null (no cache)
99
+ * - `true`: use the surface's default key.
100
+ *
101
+ * - SSR default: `${status}|${eTag}|${pathname}` when an eTag is set, otherwise
102
+ * `${status}|${pathname}`.
103
+ * - Loader default: `${pathname}|${filteredSearch}|${loaderId}|${eTag}` when an eTag is set,
104
+ * otherwise `${pathname}|${filteredSearch}|${loaderId}`.
105
+ * - Function: receives the request event and the normalized, unquoted eTag (or an empty string when
106
+ * none was provided). Return the cache key string, or `null` or `''` to skip caching for this
107
+ * request. Loader callbacks receive the loader-scoped request event, with `url`, `query`, and
108
+ * `request.url` filtered by the loader's `search` allowlist.
109
+ *
110
+ * Note: valid cacheKeys are non-empty strings.
111
+ *
112
+ * @public
113
+ */
114
+ declare type CacheKeyFn = true | ((requestEv: RequestEvent_2, eTag: string) => string | null);
115
+
116
+ /**
117
+ * Clear the in-memory loader cache. Call after deployments or data changes.
118
+ *
119
+ * When `cacheKey` is provided, only that single entry is removed; otherwise the entire cache is
120
+ * cleared.
100
121
  *
101
122
  * @public
102
123
  */
103
- declare type CacheKeyFn = true | ((status: number, eTag: string, pathname: string) => string | null);
124
+ export declare function clearLoaderCache(cacheKey?: string): void;
104
125
 
105
126
  /**
106
127
  * Clear the in-memory SSR cache. Call after deployments or data changes.
@@ -141,7 +162,18 @@ declare interface ContentMenu {
141
162
  declare type ContentModule = PageModule | LayoutModule;
142
163
 
143
164
  /**
144
- * The eTag export type: a static string or a function receiving DocumentHeadProps.
165
+ * The eTag export type for routeConfig.
166
+ *
167
+ * - `string` — static ETag value.
168
+ * - `(props: DocumentHeadProps) => string | null` — compute the ETag from route context (params, URL,
169
+ * loaded data via `resolveValue`, etc.). Return `null` to skip eTag for this request.
170
+ *
171
+ * Qwik normalizes eTag values by stripping weak-form `W/` prefixes, quotes, and forbidden chars,
172
+ * then sends a strong `ETag` header. Values that normalize to an empty string are treated as
173
+ * absent.
174
+ *
175
+ * When set (and a value is produced), the server includes an `ETag` header and returns `304` if
176
+ * `If-None-Match` matches.
145
177
  *
146
178
  * @public
147
179
  */
@@ -309,6 +341,8 @@ declare type DocumentStyle = Readonly<((Omit<QwikIntrinsicElements['style'], 'da
309
341
  style?: never;
310
342
  })>;
311
343
 
344
+ declare type EndpointModuleLoader = () => ValueOrPromise<RouteModule>;
345
+
312
346
  /** @public */
313
347
  export declare interface EnvGetter {
314
348
  get(key: string): string | undefined;
@@ -319,19 +353,19 @@ declare type ErrorCodes = ClientErrorCode | ServerErrorCode;
319
353
  /** @public */
320
354
  export declare function getErrorHtml(status: number, e: any): string;
321
355
 
322
- /**
323
- * Return the HTML for a 404 page.
324
- *
325
- * @internal
326
- */
327
- export declare function getNotFound(prefix: string): string;
328
-
329
356
  /**
330
357
  * HTTP Informational Status Codes Status codes in the 1xx range indicate that the server has
331
358
  * received and is processing the request, but no response is available yet.
332
359
  */
333
360
  declare type InformationalCode = 100 | 101 | 102 | 103;
334
361
 
362
+ /**
363
+ * Internal JSON request kind handled by Qwik Router, or `false` for normal page requests.
364
+ *
365
+ * @public
366
+ */
367
+ export declare type InternalRequest = false | 'loader' | 'action';
368
+
335
369
  /**
336
370
  * Decide whether a given request path is a static path, for optimizing file access.
337
371
  *
@@ -359,13 +393,19 @@ declare interface LoadedRoute {
359
393
  $routeBundleNames$?: string[] | undefined;
360
394
  /** Whether this route is a not-found (404) route */
361
395
  $notFound$?: boolean;
362
- /** The error module loader (nearest _E ancestor), for rendering ServerErrors */
363
- $errorLoader$?: ContentModuleLoader;
396
+ /** The nearest _E (error.tsx) boundary's chain to render on a thrown ServerError (in its layouts). */
397
+ $errorLoader$?: ModuleLoader[];
398
+ /** Merged array of routeLoader$ hashes from all matched nodes (layouts + page) */
399
+ $loaders$?: string[];
400
+ /** Runtime-only mapping of routeLoader$ hashes to the matched pathname used for q-loader fetches */
401
+ $loaderPaths$?: Record<string, string>;
364
402
  }
365
403
 
366
404
  /** @public */
367
405
  export declare const mergeHeadersCookies: (headers: Headers, cookies: Cookie) => Headers;
368
406
 
407
+ declare type ModuleLoader = ContentModuleLoader | EndpointModuleLoader;
408
+
369
409
  /** @public */
370
410
  declare type PageModule = RouteModule & {
371
411
  readonly default: (props: Record<string, never>) => JSXOutput;
@@ -464,6 +504,14 @@ export declare interface RequestEventBase<PLATFORM = QwikRouterPlatform> {
464
504
  readonly query: URLSearchParams;
465
505
  /** HTTP request URL. */
466
506
  readonly url: URL;
507
+ /**
508
+ * Identifies Qwik Router internal JSON requests, such as route loader fetches and fetch-based
509
+ * action submissions. Returns `false` for normal page requests.
510
+ *
511
+ * Check this before applying broad rewrite rules so internal JSON requests keep their routing
512
+ * behavior.
513
+ */
514
+ readonly internalRequest: InternalRequest;
467
515
  /**
468
516
  * The original HTTP request URL.
469
517
  *
@@ -582,8 +630,6 @@ export declare interface RequestEventCommon<PLATFORM = QwikRouterPlatform> exten
582
630
  }
583
631
 
584
632
  declare interface RequestEventInternal extends Readonly<RequestEvent>, Readonly<RequestEventLoader> {
585
- readonly [RequestEvLoaders]: Record<string, ValueOrPromise<unknown> | undefined>;
586
- readonly [RequestEvLoaderSerializationStrategyMap]: Map<string, SerializationStrategy>;
587
633
  readonly [RequestEvMode]: ServerRequestMode;
588
634
  readonly [RequestEvRoute]: LoadedRoute;
589
635
  /**
@@ -608,19 +654,12 @@ export declare interface RequestEventLoader<PLATFORM = QwikRouterPlatform> exten
608
654
  defer: <T>(returnData: Promise<T> | (() => Promise<T>)) => DeferReturn<T>;
609
655
  }
610
656
 
611
- declare const RequestEvLoaders: unique symbol;
612
-
613
- declare const RequestEvLoaderSerializationStrategyMap: unique symbol;
614
-
615
657
  declare const RequestEvMode: unique symbol;
616
658
 
617
659
  declare const RequestEvRoute: unique symbol;
618
660
 
619
- /** @internal */
620
- export declare const RequestEvShareQData = "qData";
621
-
622
661
  /** @public */
623
- export declare type RequestHandler<PLATFORM = QwikRouterPlatform> = (ev: RequestEvent<PLATFORM>) => Promise<void> | void;
662
+ export declare type RequestHandler<PLATFORM = QwikRouterPlatform> = (ev: RequestEvent<PLATFORM>) => Promise<void | AbortMessage | ServerError> | void | AbortMessage | ServerError;
624
663
 
625
664
  /**
626
665
  * The request handler for QwikRouter. Called by every adapter.