@sveltejs/kit 3.0.0-next.6 → 3.0.0-next.8

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 (80) hide show
  1. package/package.json +8 -3
  2. package/src/core/adapt/builder.js +11 -39
  3. package/src/core/config/index.js +76 -71
  4. package/src/core/config/options.js +280 -285
  5. package/src/core/config/types.d.ts +1 -1
  6. package/src/core/env.js +86 -1
  7. package/src/core/generate_manifest/index.js +12 -15
  8. package/src/core/sync/create_manifest_data/index.js +7 -45
  9. package/src/core/sync/sync.js +0 -2
  10. package/src/core/sync/write_client_manifest.js +7 -21
  11. package/src/core/sync/write_non_ambient.js +12 -9
  12. package/src/core/sync/write_server.js +0 -4
  13. package/src/core/sync/write_types/index.js +28 -24
  14. package/src/core/utils.js +2 -2
  15. package/src/exports/index.js +3 -3
  16. package/src/exports/internal/client.js +5 -0
  17. package/src/exports/internal/index.js +1 -91
  18. package/src/exports/internal/{event.js → server/event.js} +1 -2
  19. package/src/exports/internal/server/index.js +33 -0
  20. package/src/exports/internal/shared.js +89 -0
  21. package/src/exports/node/index.js +5 -9
  22. package/src/exports/params.js +63 -0
  23. package/src/exports/public.d.ts +103 -46
  24. package/src/exports/url.js +84 -0
  25. package/src/exports/vite/dev/index.js +31 -20
  26. package/src/exports/vite/index.js +280 -199
  27. package/src/exports/vite/preview/index.js +14 -15
  28. package/src/exports/vite/utils.js +8 -10
  29. package/src/runtime/app/env/internal.js +4 -4
  30. package/src/runtime/app/forms.js +2 -2
  31. package/src/runtime/app/paths/client.js +3 -7
  32. package/src/runtime/app/paths/internal/client.js +4 -2
  33. package/src/runtime/app/paths/internal/server.js +2 -23
  34. package/src/runtime/app/paths/server.js +3 -3
  35. package/src/runtime/app/server/remote/query.js +6 -12
  36. package/src/runtime/app/state/client.js +1 -2
  37. package/src/runtime/app/stores.js +13 -76
  38. package/src/runtime/client/bundle.js +1 -1
  39. package/src/runtime/client/client-entry.js +3 -0
  40. package/src/runtime/client/client.js +230 -238
  41. package/src/runtime/client/entry.js +24 -3
  42. package/src/runtime/client/payload.js +17 -0
  43. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  44. package/src/runtime/client/remote-functions/form.svelte.js +11 -30
  45. package/src/runtime/client/remote-functions/prerender.svelte.js +10 -3
  46. package/src/runtime/client/remote-functions/query/instance.svelte.js +18 -9
  47. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +16 -6
  48. package/src/runtime/client/remote-functions/shared.svelte.js +1 -2
  49. package/src/runtime/client/state.svelte.js +66 -49
  50. package/src/runtime/client/types.d.ts +3 -7
  51. package/src/runtime/client/utils.js +0 -96
  52. package/src/runtime/components/root.svelte +66 -0
  53. package/src/runtime/form-utils.js +16 -6
  54. package/src/runtime/server/cookie.js +17 -7
  55. package/src/runtime/server/index.js +1 -1
  56. package/src/runtime/server/page/index.js +7 -14
  57. package/src/runtime/server/page/load_data.js +1 -1
  58. package/src/runtime/server/page/render.js +72 -88
  59. package/src/runtime/server/page/server_routing.js +13 -9
  60. package/src/runtime/server/remote.js +23 -13
  61. package/src/runtime/server/respond.js +11 -8
  62. package/src/runtime/server/utils.js +28 -5
  63. package/src/runtime/telemetry/otel.js +1 -1
  64. package/src/runtime/types.d.ts +8 -0
  65. package/src/types/ambient.d.ts +5 -1
  66. package/src/types/global-private.d.ts +11 -18
  67. package/src/types/internal.d.ts +25 -30
  68. package/src/utils/error.js +1 -1
  69. package/src/utils/import.js +6 -1
  70. package/src/utils/mime.js +9 -0
  71. package/src/utils/params.js +66 -0
  72. package/src/utils/routing.js +90 -44
  73. package/src/utils/streaming.js +14 -4
  74. package/src/utils/url.js +0 -79
  75. package/src/version.js +1 -1
  76. package/types/index.d.ts +127 -125
  77. package/types/index.d.ts.map +12 -8
  78. package/src/core/sync/write_root.js +0 -148
  79. package/src/exports/internal/server.js +0 -22
  80. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -1,21 +1,13 @@
1
1
  /** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
2
2
  /** @import { NavigationIntent } from './types.js' */
3
+ /** @import { RenderNode } from '../types.js' */
3
4
  /** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
4
5
  /** @import { Query } from './remote-functions/query/instance.svelte.js' */
5
6
  /** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
6
7
  import { BROWSER, DEV } from 'esm-env';
7
8
  import * as svelte from 'svelte';
8
9
  import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
9
- const { onMount, tick } = svelte;
10
- // Svelte 4 and under don't have `untrack`, so we have to fallback if `untrack` is not exported
11
- const untrack = svelte.untrack ?? ((value) => value());
12
- import {
13
- decode_params,
14
- decode_pathname,
15
- strip_hash,
16
- make_trackable,
17
- normalize_path
18
- } from '../../utils/url.js';
10
+ import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
19
11
  import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
20
12
  import { parse, parse_server_route } from './parse.js';
21
13
  import * as storage from './session-storage.js';
@@ -27,8 +19,6 @@ import {
27
19
  is_external_url,
28
20
  origin,
29
21
  scroll_state,
30
- notifiable_store,
31
- create_updated_store,
32
22
  load_css
33
23
  } from './utils.js';
34
24
  import { base } from '$app/paths/internal/client';
@@ -44,7 +34,6 @@ import {
44
34
  } from './constants.js';
45
35
  import { validate_page_exports } from '../../utils/exports.js';
46
36
  import { noop } from '../../utils/functions.js';
47
- import { compact } from '../../utils/array.js';
48
37
  import {
49
38
  INVALIDATED_PARAM,
50
39
  TRAILING_SLASH_PARAM,
@@ -53,11 +42,15 @@ import {
53
42
  validate_load_response
54
43
  } from '../shared.js';
55
44
  import { get_message, get_status } from '../../utils/error.js';
56
- import { writable } from 'svelte/store';
57
- import { page, update, navigating } from './state.svelte.js';
45
+ import { page, update, navigating, updated } from './state.svelte.js';
46
+ import { payload } from './payload.js';
58
47
  import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
59
48
  import { noop_span } from '../telemetry/noop.js';
60
49
  import { read_ndjson } from './ndjson.js';
50
+ import RootModern from '../components/root.svelte';
51
+ import { asClassComponent } from 'svelte/legacy';
52
+
53
+ const Root = asClassComponent(RootModern);
61
54
 
62
55
  export { load_css };
63
56
  const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
@@ -72,6 +65,17 @@ let errored = false;
72
65
  */
73
66
  let rendering_error = null;
74
67
 
68
+ /**
69
+ * `reset` functions for `<svelte:boundary>`s in the generated root that have
70
+ * failed. A failed boundary stays failed until `reset()` is called — prop
71
+ * updates alone don't re-render its content — so without resetting, a client
72
+ * navigation away from a render error would leave the stale `+error.svelte`
73
+ * mounted. The boundary's `onerror` populates this array; `navigate` drains it
74
+ * after applying the new props. See sveltejs/kit#15694.
75
+ * @type {Array<(() => void) | undefined>}
76
+ */
77
+ const resetters = [];
78
+
75
79
  // We track the scroll position associated with each history entry in sessionStorage,
76
80
  // rather than on history.state itself, because when navigation is driven by
77
81
  // popstate it's too late to update the scroll position associated with the
@@ -88,6 +92,11 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
88
92
  */
89
93
  const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
90
94
 
95
+ /**
96
+ * @deprecated this is a temporary measure to avoid a regression, replace with nested `RenderNode` classes
97
+ */
98
+ let current_tree = /** @type {RenderNode} */ ({});
99
+
91
100
  if (DEV && BROWSER) {
92
101
  let warned = false;
93
102
 
@@ -127,15 +136,6 @@ if (DEV && BROWSER) {
127
136
  };
128
137
  }
129
138
 
130
- export const stores = {
131
- url: /* @__PURE__ */ notifiable_store({}),
132
- page: /* @__PURE__ */ notifiable_store({}),
133
- navigating: /* @__PURE__ */ writable(
134
- /** @type {import('@sveltejs/kit').Navigation | null} */ (null)
135
- ),
136
- updated: /* @__PURE__ */ create_updated_store()
137
- };
138
-
139
139
  /** @param {number} index */
140
140
  function update_scroll_positions(index) {
141
141
  scroll_positions[index] = scroll_state();
@@ -344,8 +344,8 @@ export async function start(_app, _target, hydrate) {
344
344
  );
345
345
  }
346
346
 
347
- if (__SVELTEKIT_PAYLOAD__.data) {
348
- const { q = {}, p = {}, l = {}, f = {} } = __SVELTEKIT_PAYLOAD__.data;
347
+ if (payload.data) {
348
+ const { q = {}, p = {}, l = {}, f = {} } = payload.data;
349
349
 
350
350
  // store the whole nodes — error records seed the corresponding
351
351
  // resources in a failed state when they are created during hydration
@@ -424,7 +424,7 @@ export async function start(_app, _target, hydrate) {
424
424
  _start_router();
425
425
  }
426
426
 
427
- async function _invalidate(include_load_functions = true, reset_page_state = true) {
427
+ async function _invalidate(reset_page_state = true) {
428
428
  // Accept all invalidations as they come, don't swallow any while another invalidation
429
429
  // is running because subsequent invalidations may make earlier ones outdated,
430
430
  // but batch multiple synchronous invalidations.
@@ -463,39 +463,36 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
463
463
  }
464
464
  }
465
465
 
466
- if (include_load_functions) {
467
- const prev_state = page.state;
468
- const navigation_result = intent && (await load_route(intent));
469
- if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
470
- return;
471
- }
466
+ const prev_state = page.state;
467
+ const navigation_result = intent && (await load_route(intent));
468
+ if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
469
+ return;
470
+ }
472
471
 
473
- if (navigation_result.type === 'redirect') {
474
- return _goto(
475
- new URL(navigation_result.location, current.url).href,
476
- { replaceState: true },
477
- 1,
478
- token
479
- );
480
- }
472
+ if (navigation_result.type === 'redirect') {
473
+ return _goto(
474
+ new URL(navigation_result.location, current.url).href,
475
+ { replaceState: true },
476
+ 1,
477
+ token
478
+ );
479
+ }
481
480
 
482
- // A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
483
- // hence it likely contains outdated data now, so we ignore it.
484
- if (navigating && !is_navigating) {
485
- return;
486
- }
481
+ // A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
482
+ // hence it likely contains outdated data now, so we ignore it.
483
+ if (navigating && !is_navigating) {
484
+ return;
485
+ }
487
486
 
488
- // This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
489
- if (!reset_page_state) {
490
- navigation_result.props.page.state = prev_state;
491
- }
492
- update(navigation_result.props.page);
493
- current = { ...navigation_result.state, nav: current.nav };
494
- reset_invalidation();
495
- root.$set(navigation_result.props);
496
- } else {
497
- reset_invalidation();
487
+ // Preserve `page.state` when invalidating without resetting it (e.g. `refresh`/`refreshAll`)
488
+ if (!reset_page_state) {
489
+ navigation_result.props.page.state = prev_state;
498
490
  }
491
+ update(navigation_result.props.page);
492
+ current_tree = navigation_result.props.tree;
493
+ current = { ...navigation_result.state, nav: current.nav };
494
+ reset_invalidation();
495
+ root.$set(navigation_result.props);
499
496
 
500
497
  // only wait for promises that are connected to queries that still exist
501
498
  /** @type {Promise<any>[]} */
@@ -548,7 +545,7 @@ function persist_state() {
548
545
 
549
546
  /**
550
547
  * @param {string | URL} url
551
- * @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
548
+ * @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
552
549
  * @param {number} redirect_count
553
550
  * @param {{}} [nav_token]
554
551
  * @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
@@ -560,9 +557,9 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
560
557
  /** @type {Set<string>} */
561
558
  let live_query_keys;
562
559
 
563
- // Clear preload cache when invalidateAll is true to ensure fresh data
560
+ // Clear preload cache when refreshAll is true to ensure fresh data
564
561
  // after form submissions or explicit invalidations
565
- if (options.invalidateAll) {
562
+ if (options.refreshAll) {
566
563
  discard_load_cache();
567
564
  }
568
565
 
@@ -577,7 +574,7 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
577
574
  nav_token,
578
575
  intent,
579
576
  accept: () => {
580
- if (options.invalidateAll) {
577
+ if (options.refreshAll) {
581
578
  force_invalidation = true;
582
579
  query_keys = new Set();
583
580
  for (const [id, entries] of query_map) {
@@ -603,7 +600,7 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
603
600
  }
604
601
  });
605
602
 
606
- if (options.invalidateAll) {
603
+ if (options.refreshAll) {
607
604
  // TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
608
605
  // when a query in a layout that still exists after page change is refreshed earlier than this
609
606
  void svelte
@@ -642,17 +639,14 @@ async function _preload_data(intent) {
642
639
  load_cache = {
643
640
  id: intent.id,
644
641
  token: preload,
645
- promise: load_route({ ...intent, preload }).then((result) => {
642
+ promise: load_route({ ...intent, preload }).finally(() => {
646
643
  preload_tokens.delete(preload);
647
- if (result.type === 'loaded' && result.state.error) {
648
- // Don't cache errors, because they might be transient
649
- discard_load_cache();
650
- }
651
- return result;
652
644
  }),
653
645
  fork: null
654
646
  };
655
647
 
648
+ load_cache.promise.catch(discard_load_cache);
649
+
656
650
  if (__SVELTEKIT_FORK_PRELOADS__ && svelte.fork) {
657
651
  const lc = load_cache;
658
652
 
@@ -664,6 +658,7 @@ async function _preload_data(intent) {
664
658
  return svelte.fork(() => {
665
659
  root.$set(result.props);
666
660
  update(result.props.page);
661
+ current_tree = result.props.tree;
667
662
  });
668
663
  } catch {
669
664
  // if it errors, it's because the experimental flag isn't enabled in Svelte
@@ -722,23 +717,22 @@ async function initialize(result, target, hydrate) {
722
717
  }
723
718
 
724
719
  update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
720
+ current_tree = result.props.tree;
725
721
 
726
- root = new app.root({
722
+ // TODO: use mount()
723
+ root = new Root({
727
724
  target,
728
- props: { ...result.props, stores, components },
725
+ props: { ...result.props, components, resetters },
729
726
  hydrate,
730
- // @ts-ignore Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
727
+ // Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
731
728
  sync: false,
732
- // @ts-ignore Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
733
- transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
734
- ? /** @param {unknown} e */ async (e) => {
735
- const error = await handle_error(e, current.nav);
736
- rendering_error = { error, status: error.status };
737
- page.error = error;
738
- page.status = rendering_error.status;
739
- return error;
740
- }
741
- : undefined
729
+ transformError: /** @param {unknown} e */ async (e) => {
730
+ const error = await handle_error(e, current.nav);
731
+ rendering_error = { error, status: error.status };
732
+ page.error = error;
733
+ page.status = rendering_error.status;
734
+ return error;
735
+ }
742
736
  });
743
737
 
744
738
  // Wait for a microtask in case svelte experimental async is enabled,
@@ -817,35 +811,12 @@ async function get_navigation_result_from_branch({
817
811
  route
818
812
  },
819
813
  props: {
820
- // @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
821
- constructors: compact(branch).map((branch_node) => branch_node.node.component),
822
- page: clone_page(page)
814
+ page,
815
+ tree: /** @type {RenderNode} */ ({})
823
816
  }
824
817
  };
825
818
 
826
- if (errors && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
827
- let last_idx = -1;
828
- result.props.errors = await Promise.all(
829
- // eslint-disable-next-line @typescript-eslint/await-thenable
830
- branch
831
- .map((b, i) => {
832
- if (i === 0) return undefined; // root layout wraps root error component, not the other way around
833
- if (!b) return null;
834
-
835
- i--;
836
- // Find the closest error component up to the previous branch
837
- while (i > last_idx + 1 && !errors[i]) i -= 1;
838
- last_idx = i;
839
- return errors[i]?.()
840
- .then((e) => e.component)
841
- .catch(() => undefined);
842
- })
843
- // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
844
- .filter((e) => e !== null)
845
- );
846
- }
847
-
848
- if (error && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
819
+ if (error) {
849
820
  result.props.error = error;
850
821
  }
851
822
 
@@ -856,23 +827,44 @@ async function get_navigation_result_from_branch({
856
827
  let data = {};
857
828
  let data_changed = !page;
858
829
 
859
- let p = 0;
830
+ let current_node = result.props.tree;
831
+
832
+ /** @type {RenderNode | undefined} */
833
+ let previous_node = current_tree;
860
834
 
861
- for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) {
835
+ for (let i = 0; i < branch.length; i += 1) {
862
836
  const node = branch[i];
863
837
  const prev = current.branch[i];
864
838
 
865
- if (node?.data !== prev?.data) data_changed = true;
866
839
  if (!node) continue;
867
840
 
868
- data = { ...data, ...node.data };
841
+ const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader;
869
842
 
870
- // Only set props if the node actually updated. This prevents needless rerenders.
871
- if (data_changed) {
872
- result.props[`data_${p}`] = data;
843
+ current_node.error = (await error_loader()).component;
844
+ current_node.component = node.node.component;
845
+
846
+ if (
847
+ // if an ancestor node in this path changed, `data_changed` is already true and the
848
+ // accumulated `data` differs from the previous render, so we must re-merge
849
+ data_changed ||
850
+ !previous_node ||
851
+ node?.data !== prev?.data
852
+ ) {
853
+ current_node.data = { ...data, ...node.data };
854
+ data_changed = true;
855
+ } else {
856
+ // use existing object — prevents effects re-running unnecessarily
857
+ current_node.data = previous_node.data;
873
858
  }
874
859
 
875
- p += 1;
860
+ data = current_node.data;
861
+
862
+ if (i < branch.length - 1) {
863
+ current_node.child = /** @type {import('../types.js').RenderNode} */ ({});
864
+ current_node = current_node.child;
865
+
866
+ previous_node = previous_node?.child;
867
+ }
876
868
  }
877
869
 
878
870
  const page_changed =
@@ -893,8 +885,7 @@ async function get_navigation_result_from_branch({
893
885
  status: status ?? error?.status ?? 200,
894
886
  url: new URL(url),
895
887
  form: form ?? null,
896
- // The whole page store is updated, but this way the object reference stays the same
897
- data: data_changed ? data : page.data
888
+ data
898
889
  };
899
890
  }
900
891
 
@@ -1172,31 +1163,6 @@ function diff_search_params(old_url, new_url) {
1172
1163
  return changed;
1173
1164
  }
1174
1165
 
1175
- /**
1176
- * @param {Omit<import('./types.js').NavigationFinished['state'], 'branch'> & { error: App.Error }} opts
1177
- * @returns {import('./types.js').NavigationFinished}
1178
- */
1179
- function preload_error({ error, url, route, params }) {
1180
- // we skipped loading the error page, so we need to use the current page
1181
- // store, but we still pass the updated status to the preloadData function
1182
- const new_page = clone_page(page);
1183
- new_page.status = error.status;
1184
- return {
1185
- type: 'loaded',
1186
- state: {
1187
- error,
1188
- url,
1189
- route,
1190
- params,
1191
- branch: []
1192
- },
1193
- props: {
1194
- page: new_page,
1195
- constructors: []
1196
- }
1197
- };
1198
- }
1199
-
1200
1166
  /**
1201
1167
  * @overload
1202
1168
  * @param {import('./types.js').NavigationIntent} intent
@@ -1267,7 +1233,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1267
1233
  const handled_error = await handle_error(error, { url, params, route: { id } });
1268
1234
 
1269
1235
  if (preload && preload_tokens.has(preload)) {
1270
- return preload_error({ error: handled_error, url, params, route });
1236
+ throw handled_error;
1271
1237
  }
1272
1238
 
1273
1239
  return load_root_error_page({
@@ -1357,13 +1323,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1357
1323
  }
1358
1324
 
1359
1325
  if (preload && preload_tokens.has(preload)) {
1360
- const error = await handle_error(err, { params, url, route: { id: route.id } });
1361
- return preload_error({
1362
- error,
1363
- url,
1364
- params,
1365
- route
1366
- });
1326
+ throw await handle_error(err, { params, url, route: { id: route.id } });
1367
1327
  }
1368
1328
 
1369
1329
  /** @type {App.Error} */
@@ -1377,8 +1337,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1377
1337
  error = err.body;
1378
1338
  } else {
1379
1339
  // Referenced node could have been removed due to redeploy, check
1380
- const updated = await stores.updated.check();
1381
- if (updated) {
1340
+ if (await updated.check()) {
1382
1341
  // Before reloading, try to update the service worker if it exists
1383
1342
  await update_service_worker();
1384
1343
  return await native_navigation(url);
@@ -1631,7 +1590,7 @@ export async function get_navigation_intent(url, invalidating) {
1631
1590
  id: get_page_key(url),
1632
1591
  invalidating,
1633
1592
  route,
1634
- params: decode_params(params),
1593
+ params,
1635
1594
  url
1636
1595
  };
1637
1596
  }
@@ -1780,7 +1739,7 @@ async function navigate({
1780
1739
  is_navigating = true;
1781
1740
 
1782
1741
  if (started && nav.navigation.type !== 'enter') {
1783
- stores.navigating.set((navigating.current = nav.navigation));
1742
+ navigating.current = nav.navigation;
1784
1743
  }
1785
1744
 
1786
1745
  let navigation_result = intent && (await load_route(intent));
@@ -1867,8 +1826,7 @@ async function navigate({
1867
1826
 
1868
1827
  if (!navigation_result) return;
1869
1828
  } else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
1870
- const updated = await stores.updated.check();
1871
- if (updated) {
1829
+ if (await updated.check()) {
1872
1830
  // Before reloading, try to update the service worker if it exists
1873
1831
  await update_service_worker();
1874
1832
  return await native_navigation(url, replace_state);
@@ -1980,9 +1938,24 @@ async function navigate({
1980
1938
 
1981
1939
  if (fork) {
1982
1940
  commit_promise = fork.commit();
1941
+ // `fork.commit()` applies the preloaded state synchronously before the
1942
+ // first `await`, so reset any previously-failed boundaries now so the
1943
+ // stale `+error.svelte` is torn down. See sveltejs/kit#15694.
1944
+ for (const reset of resetters) {
1945
+ reset?.();
1946
+ }
1947
+ resetters.length = 0;
1983
1948
  } else {
1984
1949
  rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
1985
1950
  root.$set(navigation_result.props);
1951
+ current_tree = navigation_result.props.tree;
1952
+ // Reset any boundaries that failed on a previous navigation now that the
1953
+ // new props are applied, otherwise the stale `+error.svelte` stays
1954
+ // mounted above the new route's content. See sveltejs/kit#15694.
1955
+ for (const reset of resetters) {
1956
+ reset?.();
1957
+ }
1958
+ resetters.length = 0;
1986
1959
  // Check for sync rendering error
1987
1960
  if (rendering_error) {
1988
1961
  Object.assign(navigation_result.props.page, rendering_error);
@@ -1999,13 +1972,7 @@ async function navigate({
1999
1972
 
2000
1973
  const { activeElement } = document;
2001
1974
 
2002
- await commit_promise;
2003
-
2004
- // TODO 3.0 remote — the double tick is probably necessary because
2005
- // of some store shenanigans. `settled()` and `f.commit()`
2006
- // should resolve after DOM updates in newer versions
2007
- await svelte.tick();
2008
- await svelte.tick();
1975
+ await (commit_promise ?? svelte.tick());
2009
1976
 
2010
1977
  if (navigation_token !== nav_token) {
2011
1978
  // a new navigation happened while we were waiting for the DOM to update, so abort
@@ -2069,7 +2036,7 @@ async function navigate({
2069
2036
  restore_snapshot(current_navigation_index);
2070
2037
  }
2071
2038
 
2072
- stores.navigating.set((navigating.current = null));
2039
+ navigating.current = null;
2073
2040
 
2074
2041
  updating = false;
2075
2042
  }
@@ -2122,22 +2089,63 @@ let current_a = { element: undefined, href: undefined };
2122
2089
  function setup_preload() {
2123
2090
  /** @type {NodeJS.Timeout} */
2124
2091
  let mousemove_timeout;
2092
+ /** @type {HTMLAnchorElement | SVGAElement | undefined} */
2093
+ let hovered_a;
2125
2094
  /** @type {PreloadDataPriority} */
2126
2095
  let current_priority;
2127
2096
 
2128
- container.addEventListener('mousemove', (event) => {
2129
- const target = /** @type {Element} */ (event.target);
2097
+ function clear_hover_preload() {
2098
+ clearTimeout(mousemove_timeout);
2099
+ hovered_a?.removeEventListener('mousemove', start_hover_preload);
2100
+ hovered_a?.removeEventListener('mouseleave', clear_hover_preload);
2101
+ hovered_a = undefined;
2102
+ }
2130
2103
 
2104
+ function start_hover_preload() {
2131
2105
  clearTimeout(mousemove_timeout);
2132
2106
  mousemove_timeout = setTimeout(() => {
2133
- void preload(target, PRELOAD_PRIORITIES.hover);
2107
+ if (!hovered_a) return;
2108
+ void preload(hovered_a, PRELOAD_PRIORITIES.hover);
2134
2109
  }, 20);
2110
+ }
2111
+
2112
+ // Use mouseover initially instead of mousemove to avoid cluttering the event queue
2113
+ container.addEventListener('mouseover', (event) => {
2114
+ if (!(event.target instanceof Element)) return;
2115
+
2116
+ const a = find_anchor(event.target, container);
2117
+ if (!a || a === hovered_a) return;
2118
+
2119
+ clear_hover_preload();
2120
+ hovered_a = a;
2121
+
2122
+ const options = get_router_options(a);
2123
+
2124
+ if (
2125
+ options.preload_code < PRELOAD_PRIORITIES.hover &&
2126
+ options.preload_data < PRELOAD_PRIORITIES.hover
2127
+ ) {
2128
+ // don't add event listeners if no preloading will happen
2129
+ return;
2130
+ }
2131
+
2132
+ // Instead of just preloading right away, we start a mousemove listener to implement
2133
+ // "mouse comes to a rest" behavior. This avoid false positives when you just move
2134
+ // your mouse across the screen and happen to pass over a link.
2135
+ a.addEventListener('mousemove', start_hover_preload);
2136
+ a.addEventListener('mouseleave', clear_hover_preload, { once: true });
2137
+
2138
+ start_hover_preload();
2135
2139
  });
2136
2140
 
2137
2141
  /** @param {Event} event */
2138
2142
  function tap(event) {
2139
2143
  if (event.defaultPrevented) return;
2140
- void preload(/** @type {Element} */ (event.composedPath()[0]), PRELOAD_PRIORITIES.tap);
2144
+
2145
+ const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
2146
+ if (!a) return;
2147
+
2148
+ void preload(a, PRELOAD_PRIORITIES.tap);
2141
2149
  }
2142
2150
 
2143
2151
  container.addEventListener('mousedown', tap);
@@ -2156,12 +2164,10 @@ function setup_preload() {
2156
2164
  );
2157
2165
 
2158
2166
  /**
2159
- * @param {Element} element
2167
+ * @param {HTMLAnchorElement | SVGAElement} a
2160
2168
  * @param {PreloadDataPriority} priority
2161
2169
  */
2162
- async function preload(element, priority) {
2163
- const a = find_anchor(element, container);
2164
-
2170
+ async function preload(a, priority) {
2165
2171
  // we don't want to preload data again if the user has already hovered/tapped
2166
2172
  const interacted =
2167
2173
  a === current_a.element && a?.href === current_a.href && priority >= current_priority;
@@ -2185,15 +2191,13 @@ function setup_preload() {
2185
2191
  if (!intent) return;
2186
2192
 
2187
2193
  if (DEV) {
2188
- void _preload_data(intent).then((result) => {
2189
- if (result.type === 'loaded' && result.state.error) {
2190
- console.warn(
2191
- `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
2192
- 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
2193
- 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
2194
- 'See https://svelte.dev/docs/kit/link-options for more info'
2195
- );
2196
- }
2194
+ void _preload_data(intent).catch((error) => {
2195
+ console.warn(
2196
+ `Preloading data for ${intent.url.pathname} failed with the following error: ${error.message}\n` +
2197
+ 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
2198
+ 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
2199
+ 'See https://svelte.dev/docs/kit/link-options for more info'
2200
+ );
2197
2201
  });
2198
2202
  } else {
2199
2203
  void _preload_data(intent);
@@ -2257,7 +2261,7 @@ export async function handle_error(error, event) {
2257
2261
  * @param {T} callback
2258
2262
  */
2259
2263
  function add_navigation_callback(callbacks, callback) {
2260
- onMount(() => {
2264
+ svelte.onMount(() => {
2261
2265
  callbacks.add(callback);
2262
2266
 
2263
2267
  return () => {
@@ -2328,6 +2332,8 @@ export function disableScrollHandling() {
2328
2332
  }
2329
2333
  }
2330
2334
 
2335
+ let warned_on_invalidate_all = false;
2336
+
2331
2337
  /**
2332
2338
  * Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
2333
2339
  * Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
@@ -2341,8 +2347,9 @@ export function disableScrollHandling() {
2341
2347
  * @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
2342
2348
  * @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
2343
2349
  * @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
2344
- * @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
2350
+ * @param {boolean} [opts.refreshAll] If `true`, all `load` functions and queries of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
2345
2351
  * @param {Array<string | URL | ((url: URL) => boolean)>} [opts.invalidate] Causes any load functions to re-run if they depend on one of the urls
2352
+ * @param {boolean} [opts.invalidateAll] Deprecated in favour of opts.refreshAll.
2346
2353
  * @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
2347
2354
  * @returns {Promise<void>}
2348
2355
  */
@@ -2371,6 +2378,14 @@ export async function goto(url, opts = {}) {
2371
2378
  );
2372
2379
  }
2373
2380
 
2381
+ if (DEV && 'invalidateAll' in opts && !warned_on_invalidate_all) {
2382
+ warned_on_invalidate_all = true;
2383
+ console.warn(
2384
+ `The \`goto(..., { invalidateAll: ${opts.invalidateAll} })\` option has been deprecated in favour of \`refreshAll\``
2385
+ );
2386
+ }
2387
+
2388
+ opts.refreshAll = opts.refreshAll ?? opts.invalidateAll;
2374
2389
  return _goto(url, opts, 0, {}, intent);
2375
2390
  }
2376
2391
 
@@ -2390,16 +2405,17 @@ export async function goto(url, opts = {}) {
2390
2405
  * invalidate((url) => url.pathname === '/path');
2391
2406
  * ```
2392
2407
  * @param {string | URL | ((url: URL) => boolean)} resource The invalidated URL
2408
+ * @param {boolean} [keepState] If `true`, the current `page.state` will be preserved. Otherwise, it will be reset to an empty object. `false` by default.
2393
2409
  * @returns {Promise<void>}
2394
2410
  */
2395
- export function invalidate(resource) {
2411
+ export function invalidate(resource, keepState = false) {
2396
2412
  if (!BROWSER) {
2397
2413
  throw new Error('Cannot call invalidate(...) on the server');
2398
2414
  }
2399
2415
 
2400
2416
  push_invalidated(resource);
2401
2417
 
2402
- return _invalidate();
2418
+ return _invalidate(!keepState);
2403
2419
  }
2404
2420
 
2405
2421
  /**
@@ -2416,6 +2432,10 @@ function push_invalidated(resource) {
2416
2432
 
2417
2433
  /**
2418
2434
  * Causes all `load` and `query` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
2435
+ *
2436
+ * Note that this resets `page.state` to an empty object. If you want to preserve `page.state` (for example when using [shallow routing](https://svelte.dev/docs/kit/shallow-routing)), use `refreshAll` instead.
2437
+ *
2438
+ * @deprecated Use [`refreshAll`](https://svelte.dev/docs/kit/$app-navigation#refreshAll) instead. Unlike `invalidateAll`, `refreshAll` does not reset `page.state`.
2419
2439
  * @returns {Promise<void>}
2420
2440
  */
2421
2441
  export function invalidateAll() {
@@ -2428,18 +2448,17 @@ export function invalidateAll() {
2428
2448
  }
2429
2449
 
2430
2450
  /**
2431
- * Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run (unless disabled via the option argument).
2451
+ * Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run.
2432
2452
  * Returns a `Promise` that resolves when the page is subsequently updated.
2433
- * @param {{ includeLoadFunctions?: boolean }} [options]
2434
2453
  * @returns {Promise<void>}
2435
2454
  */
2436
- export function refreshAll({ includeLoadFunctions = true } = {}) {
2455
+ export function refreshAll() {
2437
2456
  if (!BROWSER) {
2438
2457
  throw new Error('Cannot call refreshAll() on the server');
2439
2458
  }
2440
2459
 
2441
2460
  force_invalidation = true;
2442
- return _invalidate(includeLoadFunctions, false);
2461
+ return _invalidate(false);
2443
2462
  }
2444
2463
 
2445
2464
  /**
@@ -2466,7 +2485,22 @@ export async function preloadData(href) {
2466
2485
  throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
2467
2486
  }
2468
2487
 
2469
- const result = await _preload_data(intent);
2488
+ /** @type {Awaited<ReturnType<typeof _preload_data>>} */
2489
+ let result;
2490
+
2491
+ try {
2492
+ result = await _preload_data(intent);
2493
+ } catch (error) {
2494
+ // `load_route` throws the handled error (an `App.Error` with a `status`)
2495
+ // when a preload fails, so surface it in the documented `{ type: 'error' }` shape
2496
+ const handled = /** @type {App.Error & { status?: number }} */ (error);
2497
+ return {
2498
+ type: 'error',
2499
+ status: handled?.status ?? 500,
2500
+ error: handled
2501
+ };
2502
+ }
2503
+
2470
2504
  if (result.type === 'redirect') {
2471
2505
  return {
2472
2506
  type: result.type,
@@ -2477,14 +2511,6 @@ export async function preloadData(href) {
2477
2511
 
2478
2512
  const { status, data } = result.props.page ?? page;
2479
2513
 
2480
- if (result.type === 'loaded' && result.state.error) {
2481
- return {
2482
- type: 'error',
2483
- status,
2484
- error: result.state.error
2485
- };
2486
- }
2487
-
2488
2514
  return { type: result.type, status, data };
2489
2515
  }
2490
2516
 
@@ -2572,10 +2598,6 @@ export function pushState(url, state) {
2572
2598
  has_navigated = true;
2573
2599
 
2574
2600
  page.state = state;
2575
- root.$set({
2576
- // we need to assign a new page object so that subscribers are correctly notified
2577
- page: untrack(() => clone_page(page))
2578
- });
2579
2601
 
2580
2602
  clear_onward_history(current_history_index, current_navigation_index);
2581
2603
  }
@@ -2616,9 +2638,6 @@ export function replaceState(url, state) {
2616
2638
  history.replaceState(opts, '', resolve_url(url));
2617
2639
 
2618
2640
  page.state = state;
2619
- root.$set({
2620
- page: untrack(() => clone_page(page))
2621
- });
2622
2641
  }
2623
2642
 
2624
2643
  /**
@@ -2637,7 +2656,7 @@ export async function applyAction(result) {
2637
2656
  if (result.type === 'error') {
2638
2657
  await set_nearest_error_page(result.error);
2639
2658
  } else if (result.type === 'redirect') {
2640
- await _goto(result.location, { invalidateAll: true }, 0);
2659
+ await _goto(result.location, { refreshAll: true }, 0);
2641
2660
  } else {
2642
2661
  page.form = result.data;
2643
2662
  page.status = result.status;
@@ -2646,16 +2665,15 @@ export async function applyAction(result) {
2646
2665
  root.$set({
2647
2666
  // this brings Svelte's view of the world in line with SvelteKit's
2648
2667
  // after use:enhance reset the form....
2649
- form: null,
2650
- page: clone_page(page)
2668
+ form: null
2651
2669
  });
2652
2670
 
2653
2671
  // ...so that setting the `form` prop takes effect and isn't ignored
2654
- await tick();
2672
+ await svelte.tick();
2655
2673
  root.$set({ form: result.data });
2656
2674
 
2657
2675
  if (result.type === 'success') {
2658
- reset_focus(page.url);
2676
+ reset_focus(/** @type {URL} */ (page.url));
2659
2677
  }
2660
2678
  }
2661
2679
  }
@@ -2683,9 +2701,10 @@ export async function set_nearest_error_page(error) {
2683
2701
  current = { ...navigation_result.state, nav: current.nav };
2684
2702
 
2685
2703
  root.$set(navigation_result.props);
2704
+ current_tree = navigation_result.props.tree;
2686
2705
  update(navigation_result.props.page);
2687
2706
 
2688
- void tick().then(() => reset_focus(current.url));
2707
+ void svelte.tick().then(() => reset_focus(current.url));
2689
2708
  }
2690
2709
  }
2691
2710
 
@@ -3010,7 +3029,7 @@ function _start_router() {
3010
3029
  // the navigation away from it was successful.
3011
3030
  // Info about bfcache here: https://web.dev/bfcache
3012
3031
  if (event.persisted) {
3013
- stores.navigating.set((navigating.current = null));
3032
+ navigating.current = null;
3014
3033
  }
3015
3034
  });
3016
3035
 
@@ -3019,8 +3038,6 @@ function _start_router() {
3019
3038
  */
3020
3039
  function update_url(url) {
3021
3040
  current.url = page.url = url;
3022
- stores.page.set(clone_page(page));
3023
- stores.page.notify();
3024
3041
  }
3025
3042
  }
3026
3043
 
@@ -3309,9 +3326,6 @@ function reset_focus(url, scroll = true) {
3309
3326
  const tabindex = root.getAttribute('tabindex');
3310
3327
 
3311
3328
  root.tabIndex = -1;
3312
- // TODO: remove this when we switch to TypeScript 6
3313
- // @ts-ignore options.focusVisible is only typed in TypeScript 6
3314
- // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#browser_compatibility
3315
3329
  root.focus({ preventScroll: true, focusVisible: false });
3316
3330
 
3317
3331
  // restore `tabindex` as to prevent `root` from stealing input from elements
@@ -3414,28 +3428,6 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
3414
3428
  };
3415
3429
  }
3416
3430
 
3417
- /**
3418
- * TODO: remove this in 3.0 when the page store is also removed
3419
- *
3420
- * We need to assign a new page object so that subscribers are correctly notified.
3421
- * However, spreading `{ ...page }` returns an empty object so we manually
3422
- * assign to each property instead.
3423
- *
3424
- * @param {import('@sveltejs/kit').Page} page
3425
- */
3426
- function clone_page(page) {
3427
- return {
3428
- data: page.data,
3429
- error: page.error,
3430
- form: page.form,
3431
- params: page.params,
3432
- route: page.route,
3433
- state: page.state,
3434
- status: page.status,
3435
- url: page.url
3436
- };
3437
- }
3438
-
3439
3431
  /**
3440
3432
  * @param {URL} url
3441
3433
  * @returns {URL}