@sveltejs/kit 3.0.0-next.13 → 3.0.0-next.14

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 (55) hide show
  1. package/package.json +2 -2
  2. package/src/cli.js +2 -2
  3. package/src/core/adapt/builder.js +8 -5
  4. package/src/core/generate_manifest/index.js +6 -0
  5. package/src/core/postbuild/entities.js +8 -2
  6. package/src/core/postbuild/fallback.js +2 -1
  7. package/src/core/postbuild/prerender.js +12 -3
  8. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  9. package/src/core/sync/create_manifest_data/index.js +33 -2
  10. package/src/core/sync/write_app_types.js +72 -27
  11. package/src/core/sync/write_tsconfig/index.js +2 -1
  12. package/src/core/sync/write_types/index.js +5 -2
  13. package/src/exports/node/index.js +2 -7
  14. package/src/exports/public.d.ts +3 -2
  15. package/src/exports/vite/index.js +45 -11
  16. package/src/exports/vite/utils.js +1 -7
  17. package/src/runtime/app/server/remote/query.js +0 -6
  18. package/src/runtime/client/client.js +148 -38
  19. package/src/runtime/client/fetcher.js +2 -13
  20. package/src/runtime/client/remote-functions/form.svelte.js +3 -7
  21. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  22. package/src/runtime/client/remote-functions/query/index.js +1 -1
  23. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  24. package/src/runtime/client/remote-functions/query-live/proxy.js +0 -10
  25. package/src/runtime/client/remote-functions/shared.svelte.js +1 -1
  26. package/src/runtime/client/utils.js +1 -0
  27. package/src/runtime/form-utils.js +4 -6
  28. package/src/runtime/pathname.js +37 -0
  29. package/src/runtime/server/fetch.js +5 -8
  30. package/src/runtime/server/index.js +10 -24
  31. package/src/runtime/server/page/crypto.js +2 -2
  32. package/src/runtime/server/page/csp.js +2 -1
  33. package/src/runtime/server/page/load_data.js +2 -29
  34. package/src/runtime/server/page/render.js +19 -1
  35. package/src/runtime/server/page/serialize_data.js +2 -13
  36. package/src/runtime/server/page/server_routing.js +58 -9
  37. package/src/runtime/server/respond.js +20 -27
  38. package/src/runtime/server/state.js +43 -0
  39. package/src/runtime/shared.js +4 -2
  40. package/src/runtime/utils.js +3 -0
  41. package/src/types/ambient-private.d.ts +1 -1
  42. package/src/types/ambient.d.ts +58 -7
  43. package/src/types/global-private.d.ts +1 -1
  44. package/src/types/internal.d.ts +2 -1
  45. package/src/utils/hash.js +21 -0
  46. package/src/utils/http.js +8 -7
  47. package/src/utils/import.js +2 -1
  48. package/src/utils/params.js +1 -1
  49. package/src/utils/regex.js +9 -0
  50. package/src/utils/routing.js +52 -27
  51. package/src/utils/url.js +1 -0
  52. package/src/version.js +1 -1
  53. package/types/index.d.ts +74 -10
  54. package/types/index.d.ts.map +1 -1
  55. package/src/exports/node/polyfills.js +0 -30
@@ -21,7 +21,7 @@ import {
21
21
  scroll_state,
22
22
  load_css
23
23
  } from './utils.js';
24
- import { base, set_match_implementation } from '$app/paths/internal/client';
24
+ import { base, app_dir, set_match_implementation } from '$app/paths/internal/client';
25
25
  import * as devalue from 'devalue';
26
26
  import {
27
27
  HISTORY_INFO_KEY,
@@ -41,7 +41,11 @@ import {
41
41
  import { get_message, get_status } from '../../utils/error.js';
42
42
  import { page, navigating, updated, notify_version } from './state.svelte.js';
43
43
  import { payload } from './payload.js';
44
- import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
44
+ import {
45
+ add_data_suffix,
46
+ add_resolution_suffix,
47
+ route_id_resolution_pathname
48
+ } from '../pathname.js';
45
49
  import { noop_span } from '../telemetry/noop.js';
46
50
  import { read_ndjson } from './ndjson.js';
47
51
  import Root from '../components/root.svelte';
@@ -294,6 +298,35 @@ function discard_load_cache() {
294
298
  */
295
299
  const reroute_cache = new Map();
296
300
 
301
+ /**
302
+ * Sentinel for a route that exists but has no code to preload, i.e. a `+server.js` with no
303
+ * `+page`. Cached like a parsed route so that repeated `preloadCode(id)` calls for the same
304
+ * id don't re-request it.
305
+ */
306
+ const ENDPOINT_ONLY = Symbol('endpoint only');
307
+
308
+ /**
309
+ * Cache of route ID -> parsed route for server-side route resolution.
310
+ * Populated whenever a server route resolution occurs (`match`, link preloading,
311
+ * hydration), so that `preloadCode(id)` doesn't need an extra server round trip.
312
+ * Lives until full page reload.
313
+ * @type {Map<string, import('types').CSRRoute | typeof ENDPOINT_ONLY>}
314
+ */
315
+ const route_id_cache = new Map();
316
+
317
+ /**
318
+ * Parse a server-provided route and record it in `route_id_cache`, so that a subsequent
319
+ * `preloadCode(id)` for the same route doesn't need another server round trip. Always use
320
+ * this rather than calling `parse_server_route` directly.
321
+ * @param {import('types').CSRRouteServer} server_route
322
+ * @returns {import('types').CSRRoute}
323
+ */
324
+ function parse_and_cache_server_route(server_route) {
325
+ const route = parse_server_route(server_route, app.nodes);
326
+ route_id_cache.set(route.id, route);
327
+ return route;
328
+ }
329
+
297
330
  /**
298
331
  * Note on before_navigate_callbacks, on_navigate_callbacks and after_navigate_callbacks:
299
332
  * do not re-assign as some closures keep references to these Sets
@@ -661,13 +694,13 @@ function persist_state() {
661
694
 
662
695
  /**
663
696
  * @param {string | URL} url
664
- * @param {{ replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean }} options
665
- * @param {number} redirect_count
697
+ * @param {{ type?: import('@sveltejs/kit').NavigationType; replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean; event?: Event }} [options]
698
+ * @param {number} [redirect_count]
666
699
  * @param {{}} [nav_token]
667
700
  * @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
668
701
  * @returns {Promise<void>}
669
702
  */
670
- export async function _goto(url, options, redirect_count, nav_token, intent) {
703
+ export async function _goto(url, options = {}, redirect_count = 0, nav_token = {}, intent) {
671
704
  /** @type {Set<string>} */
672
705
  let query_keys;
673
706
  /** @type {Set<string>} */
@@ -680,12 +713,13 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
680
713
  }
681
714
 
682
715
  await navigate({
683
- type: 'goto',
716
+ type: options.type ?? 'goto',
684
717
  url: resolve_url(url),
685
718
  reset: options.reset,
686
719
  replace_state: options.replace,
687
720
  state: options.state,
688
721
  persist_state: options.persistState,
722
+ event: options.event,
689
723
  redirect_count,
690
724
  nav_token,
691
725
  intent,
@@ -786,6 +820,53 @@ async function _preload_data(intent) {
786
820
  return load_cache.promise;
787
821
  }
788
822
 
823
+ /**
824
+ * Fetch and parse the route with the given ID from the server-side route resolution endpoint.
825
+ * Returns `ENDPOINT_ONLY` if the route exists but has no code to preload, or `undefined` if
826
+ * there is no such route. Only used when `router.resolution === 'server'`.
827
+ * @param {string} id
828
+ * @returns {Promise<import('types').CSRRoute | typeof ENDPOINT_ONLY | undefined>}
829
+ */
830
+ async function load_route_by_id(id) {
831
+ /** @type {{ route?: import('types').CSRRouteServer, endpoint_only?: boolean }} */
832
+ let module;
833
+
834
+ try {
835
+ module = await import(
836
+ /* @vite-ignore */
837
+ base + route_id_resolution_pathname(app_dir, id)
838
+ );
839
+ } catch {
840
+ // if there's no module at that path the response is a 404 (or, on a static
841
+ // host, fallback HTML with the wrong MIME type) and the import rejects —
842
+ // treat it the same as an unknown route rather than surfacing a cryptic error
843
+ return;
844
+ }
845
+
846
+ if (module.endpoint_only) {
847
+ // The route exists, it just has no code to preload.
848
+ route_id_cache.set(id, ENDPOINT_ONLY);
849
+ return ENDPOINT_ONLY;
850
+ }
851
+
852
+ if (!module.route) return;
853
+
854
+ return parse_and_cache_server_route(module.route);
855
+ }
856
+
857
+ /**
858
+ * Import the modules for a route's layout and leaf nodes, without running `load` functions.
859
+ * @param {import('types').CSRRoute} route
860
+ * @returns {Promise<void>}
861
+ */
862
+ async function load_route_nodes(route) {
863
+ await Promise.all(
864
+ /** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
865
+ [...route.layouts, route.leaf].filter(Boolean)
866
+ ).map(([, node_loader]) => node_loader())
867
+ );
868
+ }
869
+
789
870
  /**
790
871
  * @param {URL} url
791
872
  * @returns {Promise<void>}
@@ -794,11 +875,7 @@ async function _preload_code(url) {
794
875
  const route = (await get_navigation_intent(url, false))?.route;
795
876
 
796
877
  if (route) {
797
- await Promise.all(
798
- /** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
799
- [...route.layouts, route.leaf].filter(Boolean)
800
- ).map((load) => load[1]())
801
- );
878
+ await load_route_nodes(route);
802
879
  }
803
880
  }
804
881
 
@@ -1607,7 +1684,7 @@ async function load_root_error_page({ error, url, route }) {
1607
1684
  // client-side navigation if the root layout loader throws a redirect while
1608
1685
  // rendering the default error page
1609
1686
  if (error instanceof Redirect) {
1610
- await _goto(new URL(error.location, location.href), {}, 0);
1687
+ await _goto(new URL(error.location, location.href));
1611
1688
  return;
1612
1689
  }
1613
1690
 
@@ -1729,7 +1806,7 @@ export async function get_navigation_intent(url, invalidating) {
1729
1806
  return {
1730
1807
  id: get_page_key(url),
1731
1808
  invalidating,
1732
- route: parse_server_route(route, app.nodes),
1809
+ route: parse_and_cache_server_route(route),
1733
1810
  params,
1734
1811
  url
1735
1812
  };
@@ -2656,45 +2733,76 @@ export async function preloadData(href) {
2656
2733
  * Programmatically imports the code for routes that haven't yet been fetched.
2657
2734
  * Typically, you might call this to speed up subsequent navigation.
2658
2735
  *
2659
- * You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
2736
+ * Takes a route ID such as `/about` or `/blog/[slug]`. Unlike pathnames, route IDs
2737
+ * are never prefixed with the app's [base path](https://svelte.dev/docs/kit/configuration#paths).
2738
+ * If you have a pathname rather than a route ID, you can convert it with
2739
+ * [`match`](https://svelte.dev/docs/kit/$app-paths#match) from `$app/paths`:
2740
+ *
2741
+ * ```js
2742
+ * import { match } from '$app/paths';
2743
+ * import { preloadCode } from '$app/navigation';
2744
+ *
2745
+ * const matched = await match('/blog/hello-world');
2746
+ * if (matched) await preloadCode(matched.id);
2747
+ * ```
2660
2748
  *
2661
2749
  * Unlike `preloadData`, this won't call `load` functions.
2662
2750
  * Returns a Promise that resolves when the modules have been imported.
2663
2751
  *
2664
- * @param {string} pathname
2752
+ * @param {RouteId} id
2665
2753
  * @returns {Promise<void>}
2666
2754
  */
2667
- export async function preloadCode(pathname) {
2755
+ export async function preloadCode(id) {
2668
2756
  if (!BROWSER) {
2669
2757
  throw new Error('Cannot call preloadCode(...) on the server');
2670
2758
  }
2671
2759
 
2672
- // `current.url` is null until the first navigation/hydration completes, so fall back
2673
- // to `location` to support calling `preloadCode` during initial page load (#13297)
2674
- const url = new URL(pathname, current.url ?? location.href);
2760
+ if (DEV && id[0] !== '/') {
2761
+ throw new Error(
2762
+ `argument passed to preloadCode must be a route ID (i.e. "/blog/[slug]" rather than "blog/[slug]")`
2763
+ );
2764
+ }
2675
2765
 
2676
- if (DEV) {
2677
- if (!pathname.startsWith('/')) {
2678
- throw new Error(
2679
- 'argument passed to preloadCode must be a pathname (i.e. "/about" rather than "http://example.com/about"'
2680
- );
2681
- }
2766
+ const route = __SVELTEKIT_CLIENT_ROUTING__
2767
+ ? routes.find((r) => r.id === id)
2768
+ : (route_id_cache.get(id) ?? (await load_route_by_id(id)));
2682
2769
 
2683
- if (!pathname.startsWith(base)) {
2684
- throw new Error(
2685
- `pathname passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
2770
+ if (route === ENDPOINT_ONLY) {
2771
+ if (DEV) {
2772
+ console.warn(
2773
+ `'${id}' has no \`+page\`, so there is no code to preload. If you meant to warm up an ` +
2774
+ `endpoint, request it with \`fetch\` instead.`
2686
2775
  );
2687
2776
  }
2688
2777
 
2689
- if (__SVELTEKIT_CLIENT_ROUTING__) {
2690
- const rerouted = await get_rerouted_url(url);
2691
- if (!rerouted || !routes.find((route) => route.exec(get_url_path(rerouted)))) {
2692
- throw new Error(`'${pathname}' did not match any routes`);
2778
+ return;
2779
+ }
2780
+
2781
+ if (!route) {
2782
+ if (DEV) {
2783
+ // warn rather than throw, since under client routing an endpoint-only route id is
2784
+ // indistinguishable from a typo — the client manifest only contains routes with a `+page`
2785
+ let message = `'${id}' did not match any route`;
2786
+
2787
+ if (__SVELTEKIT_CLIENT_ROUTING__) {
2788
+ message += ` (note that routes without a \`+page\` have no code to preload)`;
2789
+
2790
+ // the most common migration mistake is passing a pathname, which used to work
2791
+ const candidates = [id];
2792
+ if (base && id.startsWith(base)) candidates.push(id.slice(base.length) || '/');
2793
+
2794
+ if (candidates.some((path) => routes.some((r) => r.exec(path)))) {
2795
+ message += `. It does match as a pathname — use \`match(...)\` from \`$app/paths\` to convert a pathname into a route ID`;
2796
+ }
2693
2797
  }
2798
+
2799
+ console.warn(message);
2694
2800
  }
2801
+
2802
+ return;
2695
2803
  }
2696
2804
 
2697
- return _preload_code(url);
2805
+ await load_route_nodes(route);
2698
2806
  }
2699
2807
 
2700
2808
  /**
@@ -2892,7 +3000,7 @@ export async function applyAction(result) {
2892
3000
  if (result.type === 'error') {
2893
3001
  await set_nearest_error_page(result.error);
2894
3002
  } else if (result.type === 'redirect') {
2895
- await _goto(result.location, { refreshAll: true }, 0);
3003
+ await _goto(result.location, { refreshAll: true });
2896
3004
  } else {
2897
3005
  page.form = result.data;
2898
3006
  page.status = result.status;
@@ -3108,11 +3216,13 @@ function _start_router() {
3108
3216
  setTimeout(fulfil, 100); // fallback for edge case where rAF doesn't fire because e.g. tab was backgrounded
3109
3217
  });
3110
3218
 
3111
- await navigate({
3219
+ const changed = url.href !== location.href;
3220
+
3221
+ await _goto(url, {
3112
3222
  type: 'link',
3113
- url,
3114
3223
  reset: options.reset,
3115
- replace_state: options.replace_state ?? url.href === location.href,
3224
+ replace: options.replace_state ?? !changed,
3225
+ refreshAll: !changed,
3116
3226
  event
3117
3227
  });
3118
3228
  });
@@ -3343,7 +3453,7 @@ async function _hydrate(
3343
3453
  } else {
3344
3454
  // undefined in case of 404
3345
3455
  if (server_route) {
3346
- parsed_route = route = parse_server_route(server_route, app.nodes);
3456
+ parsed_route = route = parse_and_cache_server_route(server_route);
3347
3457
  } else {
3348
3458
  route = { id: null };
3349
3459
  params = {};
@@ -1,6 +1,6 @@
1
1
  import { BROWSER, DEV } from 'esm-env';
2
2
  import { noop } from '../../utils/functions.js';
3
- import { hash } from '../../utils/hash.js';
3
+ import { hash_request } from '../../utils/hash.js';
4
4
  import { base64_decode } from '../utils.js';
5
5
 
6
6
  let loading = 0;
@@ -172,18 +172,7 @@ function build_selector(resource, opts) {
172
172
  return null;
173
173
  }
174
174
 
175
- /** @type {import('types').StrictBody[]} */
176
- const values = [];
177
-
178
- if (opts.headers) {
179
- values.push([...new Headers(opts.headers)].join(','));
180
- }
181
-
182
- if (body) {
183
- values.push(/** @type {import('types').StrictBody} */ (body));
184
- }
185
-
186
- selector += `[data-hash="${hash(...values)}"]`;
175
+ selector += `[data-hash="${hash_request(opts.headers, body)}"]`;
187
176
  }
188
177
 
189
178
  return selector;
@@ -262,13 +262,9 @@ export function form(id) {
262
262
 
263
263
  if (response.redirect) {
264
264
  // Use internal version to allow redirects to external URLs
265
- void _goto(
266
- response.redirect,
267
- {
268
- refreshAll: should_refresh
269
- },
270
- 0
271
- );
265
+ void _goto(response.redirect, {
266
+ refreshAll: should_refresh
267
+ });
272
268
  return true;
273
269
  }
274
270
 
@@ -100,7 +100,7 @@ export function prerender(id) {
100
100
 
101
101
  if (result.redirect) {
102
102
  // Use internal version to allow redirects to external URLs
103
- void _goto(result.redirect, {}, 0);
103
+ void _goto(result.redirect);
104
104
  return;
105
105
  }
106
106
 
@@ -30,7 +30,7 @@ export function query(id) {
30
30
 
31
31
  if (result.redirect) {
32
32
  // Use internal version to allow redirects to external URLs
33
- await _goto(result.redirect, {}, 0);
33
+ await _goto(result.redirect);
34
34
  }
35
35
  });
36
36
  };
@@ -50,7 +50,7 @@ export function query_batch(id) {
50
50
 
51
51
  if (response.redirect) {
52
52
  // Use internal version to allow redirects to external URLs
53
- await _goto(response.redirect, {}, 0);
53
+ await _goto(response.redirect);
54
54
 
55
55
  // settle all batched promises (with `undefined`, like a redirect
56
56
  // from a non-batched query) so that callers don't hang forever
@@ -74,16 +74,6 @@ export class LiveQueryProxy {
74
74
  return this.#get_cached_query().done;
75
75
  }
76
76
 
77
- /**
78
- * @deprecated Use `for await (const value of liveQuery())` instead.
79
- * @returns {AsyncGenerator<T>}
80
- */
81
- run() {
82
- throw new Error(
83
- '`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.'
84
- );
85
- }
86
-
87
77
  reconnect() {
88
78
  return this.#get_cached_query().reconnect();
89
79
  }
@@ -197,7 +197,7 @@ export async function remote_request(url, init) {
197
197
  export async function handle_side_channel_response(response) {
198
198
  if (response.type === 'redirect') {
199
199
  // Use internal version to allow redirects to external URLs
200
- await _goto(response.location, {}, 0);
200
+ await _goto(response.location);
201
201
  throw new Redirect(307, response.location);
202
202
  }
203
203
 
@@ -122,6 +122,7 @@ export function get_link_info(a, base, uses_hash_router) {
122
122
  /** @type {URL | undefined} */
123
123
  let url;
124
124
 
125
+ // TODO replace the try/catch with `URL.parse` when browser support allows (Chrome 126, Firefox 126, Safari 18)
125
126
  try {
126
127
  url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
127
128
 
@@ -3,12 +3,10 @@
3
3
 
4
4
  import { DEV } from 'esm-env';
5
5
  import * as devalue from 'devalue';
6
- import { text_encoder } from './utils.js';
6
+ import { text_decoder, text_encoder } from './utils.js';
7
7
  import { noop } from '../utils/functions.js';
8
8
  import { SvelteKitError } from '@sveltejs/kit/internal';
9
9
 
10
- const decoder = new TextDecoder();
11
-
12
10
  /**
13
11
  * Sets a parsed form field value in a nested object, mutating the original object.
14
12
  * @param {Record<string, any>} object
@@ -283,7 +281,7 @@ export async function deserialize_binary_form(request, form_id) {
283
281
  const file_offsets_buffer = await get_buffer(HEADER_BYTES + data_length, file_offsets_length);
284
282
  if (!file_offsets_buffer) throw deserialize_error('file offset table too short');
285
283
 
286
- const parsed_offsets = JSON.parse(decoder.decode(file_offsets_buffer));
284
+ const parsed_offsets = JSON.parse(text_decoder.decode(file_offsets_buffer));
287
285
 
288
286
  if (
289
287
  !Array.isArray(parsed_offsets) ||
@@ -298,7 +296,7 @@ export async function deserialize_binary_form(request, form_id) {
298
296
 
299
297
  /** @type {Array<{ offset: number, size: number }>} */
300
298
  const file_spans = [];
301
- const [data, meta] = devalue.parse(decoder.decode(data_buffer), {
299
+ const [data, meta] = devalue.parse(text_decoder.decode(data_buffer), {
302
300
  File: ([name, type, size, last_modified, index]) => {
303
301
  if (
304
302
  typeof name !== 'string' ||
@@ -488,7 +486,7 @@ class LazyFile {
488
486
  });
489
487
  }
490
488
  async text() {
491
- return decoder.decode(await this.arrayBuffer());
489
+ return text_decoder.decode(await this.arrayBuffer());
492
490
  }
493
491
  }
494
492
 
@@ -47,3 +47,40 @@ export function add_resolution_suffix(pathname) {
47
47
  export function strip_resolution_suffix(pathname) {
48
48
  return pathname.slice(0, -ROUTE_SUFFIX.length);
49
49
  }
50
+
51
+ const ROUTES_PREFIX = '/routes';
52
+
53
+ /**
54
+ * The pathname of the route-ID-keyed resolution module for a given route ID,
55
+ * e.g. `/_app/routes/blog/[slug]/__route.js` (before prefixing with `base`).
56
+ * @param {string} app_dir
57
+ * @param {string} route_id
58
+ * @returns {string}
59
+ */
60
+ export function route_id_resolution_pathname(app_dir, route_id) {
61
+ return add_resolution_suffix(`/${app_dir}${ROUTES_PREFIX}${route_id === '/' ? '' : route_id}`);
62
+ }
63
+
64
+ /**
65
+ * Whether a pathname (with the `/__route.js` suffix already stripped, and `base` NOT yet stripped)
66
+ * is a route-ID resolution request rather than a pathname resolution request.
67
+ * @param {string} pathname
68
+ * @param {string} base
69
+ * @param {string} app_dir
70
+ * @returns {boolean}
71
+ */
72
+ export function is_route_id_resolution_path(pathname, base, app_dir) {
73
+ const prefix = `${base}/${app_dir}${ROUTES_PREFIX}`;
74
+ return pathname === prefix || pathname.startsWith(prefix + '/');
75
+ }
76
+
77
+ /**
78
+ * Extract the route ID from a decoded, base-stripped, suffix-stripped pathname,
79
+ * e.g. `/_app/routes/blog/[slug]` -> `/blog/[slug]`, `/_app/routes` -> `/`.
80
+ * @param {string} pathname
81
+ * @param {string} app_dir
82
+ * @returns {string}
83
+ */
84
+ export function extract_route_id(pathname, app_dir) {
85
+ return pathname.slice(`/${app_dir}${ROUTES_PREFIX}`.length) || '/';
86
+ }
@@ -4,6 +4,7 @@ import { respond } from './respond.js';
4
4
  import * as paths from '$app/paths/internal/server';
5
5
  import { read_implementation } from './internal.js';
6
6
  import { has_prerendered_path } from './utils.js';
7
+ import { fork_state_for_subrequest } from './state.js';
7
8
 
8
9
  /**
9
10
  * @param {{
@@ -198,6 +199,8 @@ function normalize_fetch_input(info, init, url) {
198
199
  * @returns {Promise<Response>}
199
200
  */
200
201
  async function internal_fetch(request, options, manifest, state) {
202
+ const subrequest_state = fork_state_for_subrequest(state);
203
+
201
204
  if (request.signal) {
202
205
  if (request.signal.aborted) {
203
206
  throw new DOMException('The operation was aborted.', 'AbortError');
@@ -214,18 +217,12 @@ async function internal_fetch(request, options, manifest, state) {
214
217
  });
215
218
 
216
219
  const result = await Promise.race([
217
- respond(request, options, manifest, {
218
- ...state,
219
- depth: state.depth + 1
220
- }),
220
+ respond(request, options, manifest, subrequest_state),
221
221
  abort_promise
222
222
  ]);
223
223
  remove_abort_listener();
224
224
  return result;
225
225
  } else {
226
- return await respond(request, options, manifest, {
227
- ...state,
228
- depth: state.depth + 1
229
- });
226
+ return await respond(request, options, manifest, subrequest_state);
230
227
  }
231
228
  }
@@ -67,31 +67,17 @@ export class Server {
67
67
  const result = read(file);
68
68
  if (result instanceof ReadableStream) {
69
69
  return result;
70
- } else {
71
- return new ReadableStream({
72
- async start(controller) {
73
- try {
74
- const stream = await Promise.resolve(result);
75
- if (!stream) {
76
- controller.close();
77
- return;
78
- }
79
-
80
- const reader = stream.getReader();
81
-
82
- while (true) {
83
- const { done, value } = await reader.read();
84
- if (done) break;
85
- controller.enqueue(value);
86
- }
87
-
88
- controller.close();
89
- } catch (error) {
90
- controller.error(error);
91
- }
92
- }
93
- });
94
70
  }
71
+
72
+ // TODO remove the cast once TypeScript's lib includes `ReadableStream.from`
73
+ return /** @type {ReadableStream} */ (
74
+ /** @type {any} */ (ReadableStream).from(
75
+ (async function* () {
76
+ const stream = await result;
77
+ if (stream) yield* stream;
78
+ })()
79
+ )
80
+ );
95
81
  };
96
82
 
97
83
  set_read_implementation(wrapped_read);
@@ -1,4 +1,4 @@
1
- import { text_encoder } from '../../utils.js';
1
+ import { base64_encode, text_encoder } from '../../utils.js';
2
2
 
3
3
  /**
4
4
  * SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
@@ -102,7 +102,7 @@ export function sha256(data) {
102
102
  const bytes = new Uint8Array(out.buffer);
103
103
  reverse_endianness(bytes);
104
104
 
105
- return btoa(String.fromCharCode(...bytes));
105
+ return base64_encode(bytes);
106
106
  }
107
107
 
108
108
  /** The SHA-256 initialization vector */
@@ -1,11 +1,12 @@
1
1
  import { escape_html } from '../../../utils/escape.js';
2
+ import { base64_encode } from '../../utils.js';
2
3
  import { sha256 } from './crypto.js';
3
4
 
4
5
  const array = new Uint8Array(16);
5
6
 
6
7
  function generate_nonce() {
7
8
  crypto.getRandomValues(array);
8
- return btoa(String.fromCharCode(...array));
9
+ return base64_encode(array);
9
10
  }
10
11
 
11
12
  const quoted = new Set([
@@ -336,7 +336,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
336
336
 
337
337
  const request_body =
338
338
  input instanceof Request && cloned_body
339
- ? await stream_to_string(cloned_body)
339
+ ? await new Response(cloned_body).text()
340
340
  : init?.body;
341
341
 
342
342
  if (
@@ -371,16 +371,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
371
371
  const [a, b] = response.body.tee();
372
372
 
373
373
  void (async () => {
374
- let result = new Uint8Array();
375
-
376
- for await (const chunk of a) {
377
- const combined = new Uint8Array(result.length + chunk.length);
378
-
379
- combined.set(result, 0);
380
- combined.set(chunk, result.length);
381
-
382
- result = combined;
383
- }
374
+ const result = new Uint8Array(await new Response(a).arrayBuffer());
384
375
 
385
376
  if (dependency) {
386
377
  dependency.body = new Uint8Array(result);
@@ -496,21 +487,3 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
496
487
  return response;
497
488
  };
498
489
  }
499
-
500
- /**
501
- * @param {ReadableStream<Uint8Array>} stream
502
- */
503
- async function stream_to_string(stream) {
504
- let result = '';
505
- const reader = stream.getReader();
506
- const decoder = new TextDecoder();
507
- while (true) {
508
- const { done, value } = await reader.read();
509
- if (done) {
510
- result += decoder.decode();
511
- break;
512
- }
513
- result += decoder.decode(value, { stream: true });
514
- }
515
- return result;
516
- }