@qwik.dev/router 2.0.0-beta.32 → 2.0.0-beta.35

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.
@@ -1,5 +1,5 @@
1
1
  import { parentPort } from 'node:worker_threads';
2
- import { isDev, _UNINITIALIZED, _deserialize, _verifySerializable, _serialize } from '@qwik.dev/core/internal';
2
+ import { isDev, _UNINITIALIZED, _deserialize, _verifySerializable, _serialize, _getAsyncLocalStorage } from '@qwik.dev/core/internal';
3
3
  import { withLocale, inlinedQrl } from '@qwik.dev/core';
4
4
  import { A as AbortMessage$1 } from './redirect-handler.mjs';
5
5
  import { isServer } from '@qwik.dev/core/build';
@@ -1616,8 +1616,11 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1616
1616
  });
1617
1617
  pipeSource = readable.pipeThrough(capture);
1618
1618
  }
1619
+ let pipeError;
1619
1620
  const pipe = pipeSource.pipeTo(writableStream, {
1620
1621
  preventClose: true
1622
+ }).catch((error) => {
1623
+ pipeError = error;
1621
1624
  });
1622
1625
  const stream = writable.getWriter();
1623
1626
  const status = requestEv.status();
@@ -1648,6 +1651,9 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1648
1651
  await stream.close();
1649
1652
  await pipe;
1650
1653
  }
1654
+ if (pipeError) {
1655
+ throw pipeError;
1656
+ }
1651
1657
  if (eTagCacheKey && cacheChunks && cacheChunks.length > 0) {
1652
1658
  const totalLength = cacheChunks.reduce((sum, chunk) => sum + chunk.length, 0);
1653
1659
  const combined = new Uint8Array(totalLength);
@@ -2103,11 +2109,12 @@ function createRequestEvent(...args) {
2103
2109
 
2104
2110
  let _asyncRequestStore;
2105
2111
  if (isServer) {
2106
- import('node:async_hooks').then((module) => {
2107
- _asyncRequestStore = new module.AsyncLocalStorage();
2108
- }).catch((err) => {
2109
- 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);
2110
- });
2112
+ const AsyncLocalStorage = _getAsyncLocalStorage();
2113
+ if (AsyncLocalStorage) {
2114
+ _asyncRequestStore = new AsyncLocalStorage();
2115
+ } else {
2116
+ 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");
2117
+ }
2111
2118
  }
2112
2119
 
2113
2120
  const ESCAPE_HTML = /[&<>]/g;
package/lib/index.d.ts CHANGED
@@ -457,22 +457,39 @@ export declare const Link: Component<LinkProps>;
457
457
  /** @public */
458
458
  export declare interface LinkProps extends AnchorAttributes {
459
459
  /**
460
- * **Defaults to _true_.**
460
+ * @deprecated Use `prefetchBundle` and `prefetchData` instead for more granular control over what
461
+ * is prefetched and when. This prop will be removed in a future major version.
461
462
  *
462
- * Whether Qwik should prefetch and cache the target page of this **`Link`**, this includes
463
- * invoking any **`routeLoader$`**, **`onGet`**, etc.
463
+ * Legacy prefetch control for this **`Link`**.
464
464
  *
465
- * This **improves UX performance** for client-side (**SPA**) navigations.
465
+ * Setting this value to **`"js"`** will prefetch only javascript bundles required to render this
466
+ * page on the client when the link becomes visible. Setting this value to **`true`** will
467
+ * prefetch both javascript bundles and route data when the link becomes visible. Setting this
468
+ * value to **`false`** will disable prefetching altogether.
469
+ */
470
+ prefetch?: boolean | 'js';
471
+ /**
472
+ * Controls when Qwik should prefetch the javascript bundles required to render this **`Link`**
473
+ * target during client-side navigation.
466
474
  *
467
- * Prefetching occurs when a the Link enters the viewport in production (**`q-e:qvisible`**), or
468
- * with **`mouseover`/`focus`** during dev.
475
+ * Defaults to **`"visible"`**.
469
476
  *
470
477
  * Prefetching will not occur if the user has the **data saver** setting enabled.
478
+ */
479
+ prefetchBundle?: PrefetchStrategy;
480
+ /**
481
+ * Controls when Qwik should prefetch and cache route data for this **`Link`** target, including
482
+ * invoking any **`routeLoader$`**, **`onGet`**, etc.
483
+ *
484
+ * Defaults to **`"intent"`**. When using the deprecated **`prefetch="js"`** prop, route data
485
+ * prefetching defaults to **`"off"`**.
486
+ *
487
+ * Prefetching route data can improve client-side navigation performance for pages that wait on
488
+ * loaders, server handlers, databases, or API calls.
471
489
  *
472
- * Setting this value to **`"js"`** will prefetch only javascript bundles required to render this
473
- * page on the client, **`false`** will disable prefetching altogether.
490
+ * Prefetching will not occur if the user has the **data saver** setting enabled.
474
491
  */
475
- prefetch?: boolean | 'js';
492
+ prefetchData?: PrefetchStrategy;
476
493
  reload?: boolean;
477
494
  replaceState?: boolean;
478
495
  scroll?: boolean;
@@ -562,6 +579,29 @@ export declare type PageModule = RouteModule & {
562
579
  /** @public */
563
580
  export declare type PathParams = Record<string, string>;
564
581
 
582
+ /**
583
+ * Defines when link prefetching should be triggered.
584
+ *
585
+ * @public
586
+ */
587
+ export declare type PrefetchStrategy =
588
+ /**
589
+ * Prefetch when the user commits to navigating.
590
+ *
591
+ * Triggered by `pointerdown` or the `Enter` key.
592
+ */
593
+ 'commit'
594
+ /**
595
+ * Prefetch when the user shows navigation intent.
596
+ *
597
+ * Triggered by `pointerenter`, hover, or focus.
598
+ */
599
+ | 'intent'
600
+ /** Prefetch when the link becomes visible in the viewport. */
601
+ | 'visible'
602
+ /** Disable link prefetching. */
603
+ | 'off';
604
+
565
605
  declare type Prettify<T> = {} & {
566
606
  [K in keyof T]: T[K];
567
607
  };
@@ -1,4 +1,4 @@
1
- import { componentQrl, inlinedQrl, useErrorBoundary, useOnWindow, _captures, _jsxSorted, Slot, useSignal, untrack, _qrlSync, useVisibleTaskQrl, isDev, _jsxSplit, _getConstProps, _getVarProps, eventQrl, isServer, useStylesQrl, useServerData, useStore, useContextProvider, useTaskQrl, getLocale, noSerialize, isBrowser, useContext, SkipRender, implicit$FirstArg, withLocale, _wrapProp, _restProps, _fnSignal, createElement } from '@qwik.dev/core';
1
+ import { componentQrl, inlinedQrl, useErrorBoundary, useOnWindow, _captures, _jsxSorted, Slot, useSignal, untrack, _qrlSync, useVisibleTaskQrl, _jsxSplit, _getConstProps, _getVarProps, isDev, eventQrl, isServer, useStylesQrl, useServerData, useStore, useContextProvider, useTaskQrl, getLocale, noSerialize, isBrowser, useContext, SkipRender, implicit$FirstArg, withLocale, _wrapProp, _restProps, _fnSignal, createElement } from '@qwik.dev/core';
2
2
  import { Fragment } from '@qwik.dev/core/jsx-runtime';
3
3
  import { g as getClientNavPath, s as shouldPreload, p as preloadRouteBundles, l as loadClientData, i as isSamePath, c as createLoaderSignal, a as createDocumentHead, b as loadRoute, r as resolveHead, C as CLIENT_DATA_CACHE, d as isSameOrigin, Q as Q_ROUTE, D as DEFAULT_LOADERS_SERIALIZATION_STRATEGY, t as toUrl, e as clientNavigate, f as toPath, h as QFN_KEY, j as QACTION_KEY, k as QDATA_KEY } from './chunks/routing.qwik.mjs';
4
4
  import { u as useNavigate, a as useLocation, b as useQwikRouterEnv, C as ContentContext, c as ContentInternalContext, D as DocumentHeadContext, H as HttpStatusContext, R as RouteLocationContext, d as RouteNavigateContext, e as RouteStateContext, f as RouteActionContext, g as RoutePreventNavigateContext, h as useAction, i as useDocumentHead } from './chunks/use-functions.qwik.mjs';
@@ -26,39 +26,62 @@ const ErrorBoundary = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((p
26
26
  return /* @__PURE__ */ _jsxSorted(Slot, null, null, null, 3, "bA_1");
27
27
  }, "ErrorBoundary_component_pOa6vjtC7ik"));
28
28
 
29
+ const __vite_import_meta_env__ = {};
29
30
  const Link = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((props) => {
30
31
  const nav = useNavigate();
31
32
  const loc = useLocation();
32
33
  const originalHref = props.href;
33
34
  const anchorRef = useSignal();
34
- const { onClick$, prefetch: prefetchProp, reload, replaceState, scroll, ...linkProps } = /* @__PURE__ */ (() => props)();
35
+ const { onClick$, prefetch: prefetchProp, reload, replaceState, scroll, prefetchBundle: prefetchBundleProp = "visible", prefetchData: prefetchDataProp = prefetchProp === "js" ? "off" : "intent", ...linkProps } = props;
35
36
  const clientNavPath = untrack(getClientNavPath, {
36
37
  ...linkProps,
37
38
  reload
38
39
  }, loc);
39
40
  linkProps.href = clientNavPath || originalHref;
40
- const prefetchData = !!clientNavPath && prefetchProp !== false && prefetchProp !== "js" || void 0;
41
- const prefetch = prefetchData || !!clientNavPath && prefetchProp !== false && untrack(shouldPreload, clientNavPath, loc);
42
- const handlePrefetch = prefetch ? /* @__PURE__ */ inlinedQrl((_, elm) => {
41
+ const isDepratedPrefetchDisabled = prefetchProp === false;
42
+ const shouldPrefetch = untrack(shouldPreload, clientNavPath, loc);
43
+ const shouldPrefetchBundle = !!clientNavPath && prefetchBundleProp !== "off" && shouldPrefetch && !isDepratedPrefetchDisabled;
44
+ const shouldPrefetchData = !!clientNavPath && prefetchDataProp !== "off" && shouldPrefetch && !isDepratedPrefetchDisabled;
45
+ const handleBundlePrefetch = shouldPrefetchBundle ? /* @__PURE__ */ inlinedQrl((_, elm) => {
43
46
  if (navigator.connection?.saveData) {
44
47
  return;
45
48
  }
46
49
  if (elm && elm.href) {
47
50
  const url = new URL(elm.href);
48
51
  preloadRouteBundles(url.pathname);
49
- if (elm.hasAttribute("data-prefetch")) {
50
- loadClientData(url, {
51
- preloadRouteBundles: false,
52
- isPrefetch: true
53
- });
54
- }
55
52
  }
56
- }, "Link_component_handlePrefetch_AGvVXzXKbms") : void 0;
53
+ }, "Link_component_handleBundlePrefetch_ZttUP0kLKj0") : null;
54
+ const handleDataPrefetch = shouldPrefetchData ? /* @__PURE__ */ inlinedQrl((_, elm) => {
55
+ if (navigator.connection?.saveData) {
56
+ return;
57
+ }
58
+ if (elm && elm.href) {
59
+ const url = new URL(elm.href);
60
+ loadClientData(url, {
61
+ preloadRouteBundles: false,
62
+ isPrefetch: true
63
+ });
64
+ }
65
+ }, "Link_component_handleDataPrefetch_lcd4O9LHbbA") : null;
57
66
  const preventDefault = clientNavPath ? _qrlSync((event) => {
58
67
  if (!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
59
68
  event.preventDefault();
60
69
  }
61
- }, "event=>{if(!(event.metaKey||event.ctrlKey||event.shiftKey||event.altKey)){event.preventDefault();}}") : void 0;
70
+ }, "event=>{if(!(event.metaKey||event.ctrlKey||event.shiftKey||event.altKey)){event.preventDefault();}}") : null;
71
+ const prefetchData = /* @__PURE__ */ inlinedQrl(async (_, elm) => {
72
+ const handleDataPrefetch2 = _captures[0];
73
+ handleDataPrefetch2?.(null, elm);
74
+ }, "Link_component_prefetchData_UwVx4NhHkSo", [
75
+ handleDataPrefetch
76
+ ]);
77
+ const onEnterKeyDown = /* @__PURE__ */ inlinedQrl((event, element) => {
78
+ const prefetchData2 = _captures[0];
79
+ if (event.key === "Enter") {
80
+ prefetchData2(null, element);
81
+ }
82
+ }, "Link_component_onEnterKeyDown_z274ws00irY", [
83
+ prefetchData
84
+ ]);
62
85
  const handleClientSideNavigation = clientNavPath ? /* @__PURE__ */ inlinedQrl((event, elm) => {
63
86
  const nav2 = _captures[0], reload2 = _captures[1], replaceState2 = _captures[2], scroll2 = _captures[3];
64
87
  if (event.defaultPrevented) {
@@ -78,13 +101,13 @@ const Link = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((props) =>
78
101
  reload,
79
102
  replaceState,
80
103
  scroll
81
- ]) : void 0;
104
+ ]) : null;
82
105
  const handlePreload = /* @__PURE__ */ inlinedQrl((_, elm) => {
83
106
  const url = new URL(elm.href);
84
107
  preloadRouteBundles(url.pathname, 1);
85
108
  }, "Link_component_handlePreload_AAemwtuBjsE");
86
109
  useVisibleTaskQrl(/* @__PURE__ */ inlinedQrl(({ track }) => {
87
- const anchorRef2 = _captures[0], handlePrefetch2 = _captures[1], linkProps2 = _captures[2], loc2 = _captures[3];
110
+ const anchorRef2 = _captures[0], handleBundlePrefetch2 = _captures[1], handleDataPrefetch2 = _captures[2], linkProps2 = _captures[3], loc2 = _captures[4], prefetchBundleProp2 = _captures[5], prefetchDataProp2 = _captures[6], prefetchProp2 = _captures[7];
88
111
  track(() => loc2.url.pathname);
89
112
  const handler = linkProps2.onQVisible$;
90
113
  if (handler) {
@@ -95,14 +118,26 @@ const Link = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((props) =>
95
118
  handler?.(event, anchorRef2.value);
96
119
  }
97
120
  }
98
- if (!isDev && anchorRef2.value) {
99
- handlePrefetch2?.(void 0, anchorRef2.value);
121
+ const isProdOrTest = !isDev || __vite_import_meta_env__?.TEST;
122
+ if (isProdOrTest && anchorRef2.value) {
123
+ if (prefetchBundleProp2 === "visible" || // deprecated prop below, remove in favor of prefetchBundle
124
+ prefetchProp2 === "js" || prefetchProp2 === true) {
125
+ handleBundlePrefetch2?.(null, anchorRef2.value);
126
+ }
127
+ if (prefetchDataProp2 === "visible" || // deprecated prop below, remove in favor of prefetchData
128
+ prefetchProp2 === true) {
129
+ handleDataPrefetch2?.(null, anchorRef2.value);
130
+ }
100
131
  }
101
132
  }, "Link_component_useVisibleTask_xKeuRmnoNSA", [
102
133
  anchorRef,
103
- handlePrefetch,
134
+ handleBundlePrefetch,
135
+ handleDataPrefetch,
104
136
  linkProps,
105
- loc
137
+ loc,
138
+ prefetchBundleProp,
139
+ prefetchDataProp,
140
+ prefetchProp
106
141
  ]));
107
142
  return /* @__PURE__ */ _jsxSplit("a", {
108
143
  ref: anchorRef,
@@ -115,14 +150,21 @@ const Link = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((props) =>
115
150
  onClick$,
116
151
  handleClientSideNavigation
117
152
  ],
118
- "data-prefetch": prefetchData,
119
- "q-e:mouseover": [
153
+ "q-e:pointerenter": [
120
154
  linkProps.onMouseOver$,
121
- handlePrefetch
155
+ prefetchDataProp === "intent" ? prefetchData : null
122
156
  ],
123
157
  "q-e:focus": [
124
158
  linkProps.onFocus$,
125
- handlePrefetch
159
+ prefetchDataProp === "intent" ? prefetchData : null
160
+ ],
161
+ "q-e:pointerdown": [
162
+ linkProps.onPointerDown$,
163
+ prefetchDataProp === "commit" ? prefetchData : null
164
+ ],
165
+ "q-e:keydown": [
166
+ linkProps.onKeyDown$,
167
+ prefetchDataProp === "commit" ? onEnterKeyDown : null
126
168
  ]
127
169
  }, {
128
170
  // We need to prevent the onQVisible$ from being called twice since it is handled in the visible task
@@ -841,6 +883,9 @@ const useQwikRouter = (props) => {
841
883
  };
842
884
  _waitNextPage().catch((err) => {
843
885
  navigate();
886
+ if (err instanceof DOMException && err.name === "TimeoutError") {
887
+ throw new Error('View transition timed out. This can happen if you have "disableCache" in your browser devtools enabled.');
888
+ }
844
889
  throw err;
845
890
  }).finally(() => {
846
891
  container2.element.setAttribute?.(Q_ROUTE, $routeName$);
@@ -903,13 +948,16 @@ const useQwikMockRouter = (props) => {
903
948
  }, {
904
949
  deep: false
905
950
  });
906
- const loadersData = props.loaders?.reduce((acc, { loader, data }) => {
907
- acc[loader.__id] = data;
908
- return acc;
909
- }, {});
910
- const loaderState = useStore(loadersData ?? {}, {
911
- deep: false
912
- });
951
+ const container = _getContextContainer();
952
+ const loadersObject = {};
953
+ const loadersState = {};
954
+ if (props.loaders) {
955
+ for (const { loader, data } of props.loaders) {
956
+ const id = loader.__id;
957
+ loadersObject[id] = data;
958
+ loadersState[id] = createLoaderSignal(loadersObject, id, url, DEFAULT_LOADERS_SERIALIZATION_STRATEGY, container);
959
+ }
960
+ }
913
961
  const goto = props.goto ?? /* @__PURE__ */ inlinedQrl(async () => {
914
962
  console.warn("QwikRouterMockProvider: goto not provided");
915
963
  }, "useQwikMockRouter_goto_aViHFxQ1a3s");
@@ -934,7 +982,7 @@ const useQwikMockRouter = (props) => {
934
982
  useContextProvider(HttpStatusContext, httpStatus);
935
983
  useContextProvider(RouteLocationContext, routeLocation);
936
984
  useContextProvider(RouteNavigateContext, goto);
937
- useContextProvider(RouteStateContext, loaderState);
985
+ useContextProvider(RouteStateContext, loadersState);
938
986
  useContextProvider(RouteActionContext, actionState);
939
987
  const actionsMocks = props.actions?.reduce((acc, { action, handler }) => {
940
988
  acc[action.__id] = handler;
@@ -18,12 +18,15 @@ function fallbackOrigin(req) {
18
18
  function computeOrigin(req, opts) {
19
19
  return opts?.getOrigin?.(req) ?? opts?.origin ?? process.env.ORIGIN ?? fallbackOrigin(req);
20
20
  }
21
- function isIgnoredError(message = "") {
22
- const ignoredErrors = [
23
- "The stream has been destroyed",
24
- "write after end"
25
- ];
26
- return ignoredErrors.some((ignored) => message.includes(ignored));
21
+ function isClientAbortWriteError(error) {
22
+ if (!(error instanceof Error)) {
23
+ return false;
24
+ }
25
+ const code = error.code;
26
+ if (code === "EPIPE" || code === "ECONNRESET" || code === "ERR_STREAM_DESTROYED" || code === "ERR_STREAM_WRITE_AFTER_END") {
27
+ return true;
28
+ }
29
+ return false;
27
30
  }
28
31
  const invalidHeadersPattern = /^:(method|scheme|authority|path)$/i;
29
32
  function normalizeUrl(url, base) {
@@ -32,6 +35,155 @@ function normalizeUrl(url, base) {
32
35
  function getUrl(req, origin) {
33
36
  return normalizeUrl(req.originalUrl || req.url || "/", origin);
34
37
  }
38
+ function createNodeResponseSink(res) {
39
+ let closed = res.closed || res.destroyed;
40
+ let responseError;
41
+ let resolveClosed;
42
+ const closedPromise = closed ? Promise.resolve() : new Promise((resolve) => {
43
+ resolveClosed = resolve;
44
+ res.once("close", () => {
45
+ closed = true;
46
+ resolve();
47
+ });
48
+ });
49
+ res.on("error", (error) => {
50
+ if (responseError === void 0) {
51
+ responseError = error;
52
+ }
53
+ if (!closed) {
54
+ closed = true;
55
+ resolveClosed();
56
+ }
57
+ });
58
+ const shouldPropagateResponseError = () => responseError !== void 0 && !isClientAbortWriteError(responseError);
59
+ const write = (chunk) => {
60
+ if (closed || res.closed || res.destroyed) {
61
+ if (shouldPropagateResponseError()) {
62
+ return Promise.reject(responseError);
63
+ }
64
+ return;
65
+ }
66
+ return new Promise((resolve, reject) => {
67
+ let settled = false;
68
+ let onDrain;
69
+ const cleanupDrain = () => {
70
+ if (onDrain) {
71
+ res.off("drain", onDrain);
72
+ onDrain = void 0;
73
+ }
74
+ };
75
+ const finish = () => {
76
+ if (settled) {
77
+ return;
78
+ }
79
+ settled = true;
80
+ cleanupDrain();
81
+ setImmediate(resolve);
82
+ };
83
+ const finishClosed = () => {
84
+ closed = true;
85
+ finish();
86
+ };
87
+ const fail = (error) => {
88
+ if (settled) {
89
+ return;
90
+ }
91
+ settled = true;
92
+ closed = true;
93
+ cleanupDrain();
94
+ reject(error);
95
+ };
96
+ closedPromise.then(() => {
97
+ if (shouldPropagateResponseError()) {
98
+ fail(responseError);
99
+ return;
100
+ }
101
+ finish();
102
+ });
103
+ let ok;
104
+ try {
105
+ ok = res.write(chunk, (error) => {
106
+ if (error) {
107
+ if (isClientAbortWriteError(error)) {
108
+ finishClosed();
109
+ return;
110
+ }
111
+ fail(error);
112
+ return;
113
+ }
114
+ finish();
115
+ });
116
+ } catch (error) {
117
+ if (isClientAbortWriteError(error)) {
118
+ finishClosed();
119
+ return;
120
+ }
121
+ fail(error);
122
+ return;
123
+ }
124
+ if (settled) {
125
+ return;
126
+ }
127
+ if (ok) {
128
+ finish();
129
+ } else {
130
+ onDrain = () => {
131
+ onDrain = void 0;
132
+ finish();
133
+ };
134
+ res.once("drain", onDrain);
135
+ }
136
+ });
137
+ };
138
+ const close = () => {
139
+ if (closed || res.closed || res.destroyed) {
140
+ if (shouldPropagateResponseError()) {
141
+ return Promise.reject(responseError);
142
+ }
143
+ return;
144
+ }
145
+ return new Promise((resolve, reject) => {
146
+ let settled = false;
147
+ const finish = () => {
148
+ if (settled) {
149
+ return;
150
+ }
151
+ settled = true;
152
+ closed = true;
153
+ resolve();
154
+ };
155
+ const fail = (error) => {
156
+ if (settled) {
157
+ return;
158
+ }
159
+ settled = true;
160
+ closed = true;
161
+ reject(error);
162
+ };
163
+ closedPromise.then(() => {
164
+ if (shouldPropagateResponseError()) {
165
+ fail(responseError);
166
+ return;
167
+ }
168
+ finish();
169
+ });
170
+ res.once("finish", finish);
171
+ try {
172
+ res.end();
173
+ } catch (error) {
174
+ if (isClientAbortWriteError(error)) {
175
+ finish();
176
+ return;
177
+ }
178
+ fail(error);
179
+ }
180
+ });
181
+ };
182
+ return {
183
+ write,
184
+ close
185
+ };
186
+ }
35
187
  async function fromNodeHttp(url, req, res, mode, getClientConn) {
36
188
  const requestHeaders = new Headers();
37
189
  const nodeRequestHeaders = req.headers;
@@ -58,6 +210,9 @@ async function fromNodeHttp(url, req, res, mode, getClientConn) {
58
210
  };
59
211
  const body = req.method === "HEAD" || req.method === "GET" ? void 0 : getRequestBody();
60
212
  const controller = new AbortController();
213
+ const abort = () => {
214
+ controller.abort();
215
+ };
61
216
  const options = {
62
217
  method: req.method,
63
218
  headers: requestHeaders,
@@ -65,9 +220,10 @@ async function fromNodeHttp(url, req, res, mode, getClientConn) {
65
220
  signal: controller.signal,
66
221
  duplex: "half"
67
222
  };
68
- res.on("close", () => {
69
- controller.abort();
70
- });
223
+ req.on("aborted", abort);
224
+ req.on("error", abort);
225
+ res.on("close", abort);
226
+ res.on("error", abort);
71
227
  const serverRequestEv = {
72
228
  mode,
73
229
  url,
@@ -77,8 +233,9 @@ async function fromNodeHttp(url, req, res, mode, getClientConn) {
77
233
  return process.env[key];
78
234
  }
79
235
  },
80
- getWritableStream: (status, headers, cookies) => {
236
+ getWritableStream: (status, headers, cookies, resolve) => {
81
237
  res.statusCode = status;
238
+ const sink = createNodeResponseSink(res);
82
239
  try {
83
240
  for (const [key, value] of headers) {
84
241
  if (invalidHeadersPattern.test(key)) {
@@ -93,19 +250,13 @@ async function fromNodeHttp(url, req, res, mode, getClientConn) {
93
250
  } catch (err) {
94
251
  console.error(err);
95
252
  }
253
+ resolve(true);
96
254
  return new WritableStream({
97
255
  write(chunk) {
98
- if (res.closed || res.destroyed) {
99
- return;
100
- }
101
- res.write(chunk, (error) => {
102
- if (error && !isIgnoredError(error.message)) {
103
- console.error(error);
104
- }
105
- });
256
+ return sink.write(chunk);
106
257
  },
107
258
  close() {
108
- res.end();
259
+ return sink.close();
109
260
  }
110
261
  });
111
262
  },
@@ -139,12 +290,12 @@ function createQwikRouter(opts) {
139
290
  const handled = await requestHandler(serverRequestEv, opts);
140
291
  if (handled) {
141
292
  const err = await handled.completion;
142
- if (err) {
143
- throw err;
144
- }
145
293
  if (handled.requestEv.headersSent) {
146
294
  return;
147
295
  }
296
+ if (err) {
297
+ throw err;
298
+ }
148
299
  }
149
300
  next();
150
301
  } catch (e) {
@@ -1,5 +1,5 @@
1
1
  import { Q as Q_ROUTE, m as resolveRouteConfig, n as QLOADER_KEY, h as QFN_KEY, j as QACTION_KEY, o as isPromise, k as QDATA_KEY, b as loadRoute } from '../../chunks/routing.qwik.mjs';
2
- import { isDev, _UNINITIALIZED, _deserialize, _verifySerializable, _serialize } from '@qwik.dev/core/internal';
2
+ import { isDev, _UNINITIALIZED, _deserialize, _verifySerializable, _serialize, _getAsyncLocalStorage } from '@qwik.dev/core/internal';
3
3
  import { inlinedQrl } from '@qwik.dev/core';
4
4
  import { R as RedirectMessage, A as AbortMessage } from '../../chunks/redirect-handler.mjs';
5
5
  import { isServer } from '@qwik.dev/core/build';
@@ -1079,8 +1079,11 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1079
1079
  });
1080
1080
  pipeSource = readable.pipeThrough(capture);
1081
1081
  }
1082
+ let pipeError;
1082
1083
  const pipe = pipeSource.pipeTo(writableStream, {
1083
1084
  preventClose: true
1085
+ }).catch((error) => {
1086
+ pipeError = error;
1084
1087
  });
1085
1088
  const stream = writable.getWriter();
1086
1089
  const status = requestEv.status();
@@ -1111,6 +1114,9 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1111
1114
  await stream.close();
1112
1115
  await pipe;
1113
1116
  }
1117
+ if (pipeError) {
1118
+ throw pipeError;
1119
+ }
1114
1120
  if (eTagCacheKey && cacheChunks && cacheChunks.length > 0) {
1115
1121
  const totalLength = cacheChunks.reduce((sum, chunk) => sum + chunk.length, 0);
1116
1122
  const combined = new Uint8Array(totalLength);
@@ -1291,11 +1297,12 @@ const { renderQwikMiddleware, resolveRequestHandlers} = requestHandlers;
1291
1297
 
1292
1298
  let _asyncRequestStore;
1293
1299
  if (isServer) {
1294
- import('node:async_hooks').then((module) => {
1295
- _asyncRequestStore = new module.AsyncLocalStorage();
1296
- }).catch((err) => {
1297
- 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);
1298
- });
1300
+ const AsyncLocalStorage = _getAsyncLocalStorage();
1301
+ if (AsyncLocalStorage) {
1302
+ _asyncRequestStore = new AsyncLocalStorage();
1303
+ } else {
1304
+ 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");
1305
+ }
1299
1306
  }
1300
1307
 
1301
1308
  function createCacheControl(cacheControl) {
@@ -479,7 +479,7 @@ async function walkServerPlugins(opts) {
479
479
  });
480
480
  }
481
481
  }));
482
- return sourceFiles;
482
+ return sourceFiles.sort((a, b) => a.filePath < b.filePath ? -1 : 1);
483
483
  }
484
484
 
485
485
  function resolveEntry(opts, sourceFile) {
@@ -2010,13 +2010,13 @@ function imagePlugin(userOpts) {
2010
2010
  ];
2011
2011
  }
2012
2012
 
2013
- async function collectServerFnModuleIds(ctx, resolvedVirtualId, loadModule) {
2013
+ async function collectServerFnModuleIds(routingContext, resolvedVirtualId, pluginContext) {
2014
2014
  const serverFnModules = /* @__PURE__ */ new Set();
2015
2015
  const queuedModuleIds = /* @__PURE__ */ new Set();
2016
2016
  const seenModuleIds = /* @__PURE__ */ new Set();
2017
- const routes = ctx.routes;
2018
- const layouts = ctx.layouts;
2019
- const serverPlugins = ctx.serverPlugins;
2017
+ const routes = routingContext.routes;
2018
+ const layouts = routingContext.layouts;
2019
+ const serverPlugins = routingContext.serverPlugins;
2020
2020
  for (let i = 0; i < routes.length; i++) {
2021
2021
  const route = routes[i];
2022
2022
  queuedModuleIds.add(route.filePath);
@@ -2038,15 +2038,26 @@ async function collectServerFnModuleIds(ctx, resolvedVirtualId, loadModule) {
2038
2038
  continue;
2039
2039
  }
2040
2040
  seenModuleIds.add(id);
2041
- const moduleInfo = await loadModule(id);
2042
- if (moduleInfo.code?.includes("serverQrl(")) {
2041
+ const resolved = await pluginContext.resolve(id, void 0, {
2042
+ skipSelf: true
2043
+ });
2044
+ if (!resolved || resolved.external) {
2045
+ continue;
2046
+ }
2047
+ const moduleInfo = await pluginContext.load({
2048
+ id: resolved.id
2049
+ });
2050
+ if (moduleInfo.code == null) {
2051
+ continue;
2052
+ }
2053
+ if (moduleInfo.code.includes("serverQrl(")) {
2043
2054
  serverFnModules.add(moduleInfo.id);
2044
2055
  }
2045
- const resolvedImports = moduleInfo.importedIdResolutions.concat(moduleInfo.dynamicallyImportedIdResolutions);
2056
+ const resolvedImports = moduleInfo.importedIds.concat(moduleInfo.dynamicallyImportedIds);
2046
2057
  for (let i = 0; i < resolvedImports.length; i++) {
2047
2058
  const resolvedImport = resolvedImports[i];
2048
- if (!resolvedImport.external && !seenModuleIds.has(resolvedImport.id)) {
2049
- queuedModuleIds.add(resolvedImport.id);
2059
+ if (resolvedImport && !seenModuleIds.has(resolvedImport)) {
2060
+ queuedModuleIds.add(resolvedImport);
2050
2061
  }
2051
2062
  }
2052
2063
  }
@@ -2264,7 +2275,11 @@ class HtmlTransformPatcher {
2264
2275
  }
2265
2276
  }
2266
2277
  async handleEnd(chunk, encoding, callback) {
2267
- if (typeof encoding === "function") {
2278
+ if (typeof chunk === "function") {
2279
+ callback = chunk;
2280
+ chunk = void 0;
2281
+ encoding = void 0;
2282
+ } else if (typeof encoding === "function") {
2268
2283
  callback = encoding;
2269
2284
  encoding = void 0;
2270
2285
  }
@@ -2282,7 +2297,15 @@ function wrapResponseForHtmlTransform(request, response, server) {
2282
2297
  }
2283
2298
 
2284
2299
  const makeRouterDevMiddleware = (server, ctx) => async (req, res, next) => {
2285
- const mod = await server.ssrLoadModule("src/entry.ssr");
2300
+ let mod;
2301
+ try {
2302
+ mod = await server.ssrLoadModule("src/entry.ssr");
2303
+ } catch (e) {
2304
+ if (e instanceof Error) {
2305
+ server.ssrFixStacktrace(e);
2306
+ }
2307
+ return next();
2308
+ }
2286
2309
  if (!mod.default) {
2287
2310
  console.error("No default export found in src/entry.ssr");
2288
2311
  return next();
@@ -2723,9 +2746,7 @@ function serverFnsPlugin(buildContextRef) {
2723
2746
  if (!ctx) {
2724
2747
  return;
2725
2748
  }
2726
- const moduleIds = await collectServerFnModuleIds(ctx, RESOLVED_ID, (id) => this.load({
2727
- id
2728
- }));
2749
+ const moduleIds = await collectServerFnModuleIds(ctx, RESOLVED_ID, this);
2729
2750
  for (let i = 0; i < moduleIds.length; i++) {
2730
2751
  serverFnModules.add(moduleIds[i]);
2731
2752
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qwik.dev/router",
3
3
  "description": "The router for Qwik.",
4
- "version": "2.0.0-beta.32",
4
+ "version": "2.0.0-beta.35",
5
5
  "bugs": "https://github.com/QwikDev/qwik/issues",
6
6
  "dependencies": {
7
7
  "@azure/functions": "3.5.1",
@@ -40,7 +40,7 @@
40
40
  "tsm": "2.3.0",
41
41
  "typescript": "5.9.3",
42
42
  "uvu": "0.5.6",
43
- "@qwik.dev/core": "2.0.0-beta.32"
43
+ "@qwik.dev/core": "2.0.0-beta.35"
44
44
  },
45
45
  "engines": {
46
46
  "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -169,7 +169,7 @@
169
169
  "main": "./lib/index.qwik.mjs",
170
170
  "peerDependencies": {
171
171
  "vite": ">=6 <9",
172
- "@qwik.dev/core": "^2.0.0-beta.32"
172
+ "@qwik.dev/core": "^2.0.0-beta.35"
173
173
  },
174
174
  "publishConfig": {
175
175
  "access": "public"