@sveltejs/kit 3.0.0-next.22 → 3.0.0-next.24

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 (106) hide show
  1. package/package.json +40 -9
  2. package/src/cli.js +3 -3
  3. package/src/constants.js +13 -1
  4. package/src/core/adapt/builder.js +2 -3
  5. package/src/core/env.js +139 -181
  6. package/src/core/postbuild/analyse.js +9 -4
  7. package/src/core/postbuild/prerender.js +22 -23
  8. package/src/core/sync/create_manifest_data/index.js +29 -33
  9. package/src/core/sync/sync.js +23 -40
  10. package/src/core/sync/write_app_manifest.js +83 -0
  11. package/src/core/sync/write_client_manifest.js +6 -4
  12. package/src/core/sync/write_server.js +10 -6
  13. package/src/core/sync/write_types/index.js +2 -2
  14. package/src/core/utils.js +0 -28
  15. package/src/exports/hooks/public.d.ts +9 -1
  16. package/src/exports/index.js +10 -0
  17. package/src/exports/node/index.js +77 -5
  18. package/src/exports/public.d.ts +9 -0
  19. package/src/exports/vite/build/build_server.js +3 -3
  20. package/src/exports/vite/build/utils.js +48 -4
  21. package/src/exports/vite/dev/index.js +11 -22
  22. package/src/exports/vite/index.js +62 -345
  23. package/src/exports/vite/module_ids.js +1 -11
  24. package/src/exports/vite/plugins/env-vars.js +117 -0
  25. package/src/exports/vite/preview/index.js +6 -4
  26. package/src/exports/vite/utils.js +2 -4
  27. package/src/runtime/app/env/{types.d.ts → client.js} +9 -4
  28. package/src/runtime/app/env/index.js +1 -2
  29. package/src/runtime/app/env/private/index.js +1 -0
  30. package/src/runtime/app/env/public/client.js +1 -1
  31. package/src/runtime/app/env/public/server.js +1 -1
  32. package/src/runtime/app/env/{internal.js → server.js} +4 -4
  33. package/src/runtime/app/forms/client.js +263 -0
  34. package/src/runtime/app/forms/index.js +1 -263
  35. package/src/runtime/app/forms/public.d.ts +1 -1
  36. package/src/runtime/app/forms/server.js +6 -0
  37. package/src/runtime/app/forms/shared.js +34 -0
  38. package/src/runtime/app/manifest/index.js +1 -1
  39. package/src/runtime/app/navigation/client.js +15 -0
  40. package/src/runtime/app/navigation/index.js +1 -15
  41. package/src/runtime/app/navigation/public.d.ts +1 -1
  42. package/src/runtime/app/navigation/server.js +15 -0
  43. package/src/runtime/app/paths/server.js +1 -1
  44. package/src/runtime/app/server/public.d.ts +512 -0
  45. package/src/runtime/app/server/remote/command.js +1 -1
  46. package/src/runtime/app/server/remote/form.js +16 -6
  47. package/src/runtime/app/server/remote/prerender.js +2 -3
  48. package/src/runtime/app/server/remote/query.js +2 -2
  49. package/src/runtime/app/server/remote/requested.js +3 -3
  50. package/src/runtime/app/state/client.svelte.js +207 -0
  51. package/src/runtime/app/state/index.js +1 -66
  52. package/src/runtime/app/state/public.d.ts +1 -1
  53. package/src/runtime/client/bundle.js +1 -1
  54. package/src/runtime/client/client.js +206 -274
  55. package/src/runtime/client/fetcher.js +18 -8
  56. package/src/runtime/client/remote-functions/command.svelte.js +1 -1
  57. package/src/runtime/client/remote-functions/form.svelte.js +10 -7
  58. package/src/runtime/client/remote-functions/prerender.svelte.js +2 -2
  59. package/src/runtime/client/remote-functions/query/index.js +1 -1
  60. package/src/runtime/client/remote-functions/query/instance.svelte.js +2 -1
  61. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  62. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  63. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +4 -3
  64. package/src/runtime/client/remote-functions/query-live/iterator.js +1 -1
  65. package/src/runtime/client/remote-functions/shared.svelte.js +3 -3
  66. package/src/runtime/client/snapshots.js +1 -3
  67. package/src/runtime/client/stream.js +27 -20
  68. package/src/runtime/client/utils.js +7 -3
  69. package/src/runtime/components/root.svelte +5 -5
  70. package/src/runtime/env/dynamic/private.js +1 -1
  71. package/src/runtime/env/static/private.js +1 -1
  72. package/src/runtime/error-chain.js +54 -0
  73. package/src/runtime/form-utils.js +53 -77
  74. package/src/runtime/invalid-import.js +1 -0
  75. package/src/runtime/props.svelte.js +1 -1
  76. package/src/runtime/server/data/index.js +1 -1
  77. package/src/runtime/server/endpoint.js +8 -3
  78. package/src/runtime/server/env_module.js +1 -1
  79. package/src/runtime/server/errors.js +2 -2
  80. package/src/runtime/server/index.js +2 -2
  81. package/src/runtime/server/page/actions.js +77 -122
  82. package/src/runtime/server/page/index.js +37 -69
  83. package/src/runtime/server/page/render.js +18 -23
  84. package/src/runtime/server/remote-functions.js +15 -43
  85. package/src/runtime/server/respond.js +23 -3
  86. package/src/telemetry.js +1 -33
  87. package/src/types/ambient-private.d.ts +9 -17
  88. package/src/types/internal.d.ts +21 -5
  89. package/src/types/private.d.ts +1 -1
  90. package/src/utils/exports.js +1 -0
  91. package/src/utils/filesystem.js +9 -21
  92. package/src/utils/functions.js +10 -0
  93. package/src/utils/promise.js +25 -0
  94. package/src/utils/routing.js +0 -11
  95. package/src/utils/url.js +0 -11
  96. package/src/version.js +1 -1
  97. package/types/index.d.ts +1134 -1136
  98. package/types/index.d.ts.map +50 -50
  99. package/src/exports/remote/index.js +0 -11
  100. package/src/exports/remote/public.d.ts +0 -519
  101. package/src/runtime/app/env/private.js +0 -1
  102. package/src/runtime/app/env/standard-schema.d.ts +0 -0
  103. package/src/runtime/app/state/client.js +0 -63
  104. package/src/runtime/client/state.svelte.js +0 -98
  105. package/src/runtime/server/ambient.d.ts +0 -4
  106. package/src/utils/path.js +0 -23
@@ -6,7 +6,7 @@
6
6
  /** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
7
7
  /** @import { Query } from './remote-functions/query/instance.svelte.js' */
8
8
  /** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
9
- import { BROWSER, DEV } from 'esm-env';
9
+ import { DEV } from 'esm-env';
10
10
  import { settled, tick, fork, onMount, hydrate, mount } from 'svelte';
11
11
  import { HttpError, Redirect, SvelteKitError, HandledHttpError } from '@sveltejs/kit/internal';
12
12
  import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
@@ -50,7 +50,7 @@ import {
50
50
  validate_load_response
51
51
  } from '../shared.js';
52
52
 
53
- import { page, navigating, updated, notify_version } from './state.svelte.js';
53
+ import { page, updated, notify_version, update_page, set_navigation } from '#app/state/client';
54
54
  import { payload } from './payload.js';
55
55
  import {
56
56
  add_data_suffix,
@@ -62,6 +62,7 @@ import { read_ndjson } from './ndjson.js';
62
62
  import Root from '../components/root.svelte';
63
63
  import { Props, RenderNode } from '../props.svelte.js';
64
64
  import { init_transport, parse, stringify } from '#app/internal/transport';
65
+ import { build_error_chain, nearest_error_pages } from '../error-chain.js';
65
66
 
66
67
  /**
67
68
  * @typedef {{
@@ -111,7 +112,7 @@ init_snapshots(() => (started && !is_navigating && !updating ? current_history_i
111
112
  /** @type {Props} */
112
113
  let props;
113
114
 
114
- if (DEV && BROWSER) {
115
+ if (DEV) {
115
116
  let warned = false;
116
117
 
117
118
  const current_module_url = import.meta.url.split('?')[0]; // remove query params that vite adds to the URL when it is loaded from node_modules
@@ -472,6 +473,19 @@ export function start(_app, _target, data) {
472
473
  return _start(_app, _target, data);
473
474
  }
474
475
 
476
+ /**
477
+ * @template T
478
+ * @param {Map<string, Map<string, T>>} map
479
+ * @returns {Generator<[string, T]>} every entry of the cache map, keyed by remote key
480
+ */
481
+ function* cache_entries(map) {
482
+ for (const [id, entries] of map) {
483
+ for (const [payload, entry] of entries) {
484
+ yield [create_remote_key(id, payload), entry];
485
+ }
486
+ }
487
+ }
488
+
475
489
  /**
476
490
  * @param {import('./types.js').SvelteKitApp} _app
477
491
  * @param {HTMLElement} _target
@@ -518,12 +532,10 @@ async function _start(_app, _target, data) {
518
532
  default_layout_loader = _app.nodes[0];
519
533
  default_error_loader = _app.nodes[1];
520
534
 
521
- const [root_layout, root_error] = await Promise.all([
522
- default_layout_loader(),
523
- default_error_loader()
524
- ]);
535
+ const [root_layout] = await Promise.all([default_layout_loader(), default_error_loader()]);
525
536
 
526
- const tree = new RenderNode(root_layout.component, root_error.component);
537
+ // the root boundary stays unarmed: the root +error.svelte renders at the node below it
538
+ const tree = new RenderNode(root_layout.component, undefined);
527
539
 
528
540
  props = new Props({
529
541
  page,
@@ -616,19 +628,14 @@ async function _invalidate(reset_page_state = true) {
616
628
  /** @type {Map<string, Promise<void>>} */
617
629
  const live_query_reconnects = new Map();
618
630
  if (force_invalidation) {
619
- for (const entries of query_map.values()) {
620
- for (const { resource } of entries.values()) {
621
- void resource.refresh();
622
- }
631
+ for (const [, { resource }] of cache_entries(query_map)) {
632
+ void resource.refresh();
623
633
  }
624
634
 
625
- for (const [query_id, entries] of live_query_map) {
626
- for (const [payload, { resource }] of entries) {
627
- const key = create_remote_key(query_id, payload);
628
- const promise = resource.reconnect();
629
- promise.catch(noop);
630
- live_query_reconnects.set(key, promise);
631
- }
635
+ for (const [key, { resource }] of cache_entries(live_query_map)) {
636
+ const promise = resource.reconnect();
637
+ promise.catch(noop);
638
+ live_query_reconnects.set(key, promise);
632
639
  }
633
640
  }
634
641
 
@@ -654,30 +661,29 @@ async function _invalidate(reset_page_state = true) {
654
661
  return;
655
662
  }
656
663
 
657
- // Preserve `page.state` when invalidating without resetting it (e.g. `refresh`/`refreshAll`)
664
+ apply_navigation_result(navigation_result);
665
+
666
+ // Preserve `page.state` when invalidating without resetting it (e.g. `refresh`/`refreshAll`).
667
+ // Must run after `apply_navigation_result`, which overwrites `state`/`shallow` with the fresh
668
+ // page object's `{}`/`null` values when the page changed.
658
669
  if (!reset_page_state) {
659
- navigation_result.props.page.state = prev_state;
670
+ update_page({ state: prev_state });
660
671
  }
661
- navigation_result.props.page.shallow = prev_shallow;
662
- apply_navigation_result(navigation_result);
672
+
673
+ update_page({ shallow: prev_shallow });
663
674
  current = { ...navigation_result.state, nav: current.nav };
664
675
  reset_invalidation();
665
676
 
666
677
  // only wait for promises that are connected to queries that still exist
667
678
  /** @type {Promise<any>[]} */
668
679
  const promises = [];
669
- for (const entries of query_map.values()) {
670
- for (const { resource } of entries.values()) {
671
- promises.push(resource);
672
- }
680
+ for (const [, { resource }] of cache_entries(query_map)) {
681
+ promises.push(resource);
673
682
  }
674
- for (const [query_id, entries] of live_query_map) {
675
- for (const payload of entries.keys()) {
676
- const key = create_remote_key(query_id, payload);
677
- const promise = live_query_reconnects.get(key);
678
- if (promise) {
679
- promises.push(promise);
680
- }
683
+ for (const [key] of cache_entries(live_query_map)) {
684
+ const promise = live_query_reconnects.get(key);
685
+ if (promise) {
686
+ promises.push(promise);
681
687
  }
682
688
  }
683
689
 
@@ -764,20 +770,16 @@ export async function _goto(url, options = {}, redirect_count = 0, nav_token = {
764
770
  if (options.refreshAll) {
765
771
  force_invalidation = true;
766
772
  query_keys = new Set();
767
- for (const [id, entries] of query_map) {
768
- for (const [payload, entry] of entries) {
769
- // don't refresh yet, as some queries will be unrendered,
770
- // but clear caches so that newly rendered queries
771
- // don't use stale data. TODO same for `live_query_map`
772
- entry.resource?.reset();
773
- query_keys.add(create_remote_key(id, payload));
774
- }
773
+ for (const [key, entry] of cache_entries(query_map)) {
774
+ // don't refresh yet, as some queries will be unrendered,
775
+ // but clear caches so that newly rendered queries
776
+ // don't use stale data. TODO same for `live_query_map`
777
+ entry.resource?.reset();
778
+ query_keys.add(key);
775
779
  }
776
780
  live_query_keys = new Set();
777
- for (const [id, entries] of live_query_map) {
778
- for (const payload of entries.keys()) {
779
- live_query_keys.add(create_remote_key(id, payload));
780
- }
781
+ for (const [key] of cache_entries(live_query_map)) {
782
+ live_query_keys.add(key);
781
783
  }
782
784
  }
783
785
 
@@ -793,18 +795,14 @@ export async function _goto(url, options = {}, redirect_count = 0, nav_token = {
793
795
  void tick()
794
796
  .then(tick)
795
797
  .then(() => {
796
- for (const [id, entries] of query_map) {
797
- for (const [payload, { resource }] of entries) {
798
- if (query_keys?.has(create_remote_key(id, payload))) {
799
- void resource.start();
800
- }
798
+ for (const [key, { resource }] of cache_entries(query_map)) {
799
+ if (query_keys?.has(key)) {
800
+ void resource.start();
801
801
  }
802
802
  }
803
- for (const [id, entries] of live_query_map) {
804
- for (const [payload, { resource }] of entries) {
805
- if (live_query_keys?.has(create_remote_key(id, payload))) {
806
- void resource.reconnect();
807
- }
803
+ for (const [key, { resource }] of cache_entries(live_query_map)) {
804
+ if (live_query_keys?.has(key)) {
805
+ void resource.reconnect();
808
806
  }
809
807
  }
810
808
  });
@@ -953,8 +951,7 @@ async function initialize(result, target, should_hydrate) {
953
951
  props,
954
952
  transformError: /** @param {unknown} e */ async (e) => {
955
953
  const error = await handle_error(e, current.nav);
956
- page.error = error;
957
- page.status = error.status;
954
+ update_page({ error, status: error.status });
958
955
  return error;
959
956
  }
960
957
  });
@@ -1037,7 +1034,7 @@ async function get_navigation_result_from_branch({
1037
1034
  route
1038
1035
  },
1039
1036
  props: {
1040
- page,
1037
+ page: { ...page },
1041
1038
  tree: /** @type {RenderNode} */ ({})
1042
1039
  }
1043
1040
  };
@@ -1053,7 +1050,12 @@ async function get_navigation_result_from_branch({
1053
1050
  let data = {};
1054
1051
  let data_changed = !page;
1055
1052
 
1053
+ const error_components =
1054
+ errors &&
1055
+ (await build_error_chain(branch, errors, (loader) => loader().then((e) => e.component)));
1056
+
1056
1057
  let current_node = result.props.tree;
1058
+ let current_depth = 1;
1057
1059
 
1058
1060
  /** @type {RenderNode | undefined} */
1059
1061
  let previous_node = props.tree;
@@ -1089,12 +1091,9 @@ async function get_navigation_result_from_branch({
1089
1091
  const next = branch[next_index];
1090
1092
 
1091
1093
  if (next) {
1092
- const error_loader =
1093
- errors?.slice(0, next_index + 1).findLast((x) => x) ?? default_error_loader;
1094
-
1095
1094
  current_node = current_node.child = new RenderNode(
1096
1095
  next.node.component,
1097
- (await error_loader()).component
1096
+ error_components?.[current_depth++]
1098
1097
  );
1099
1098
 
1100
1099
  previous_node = previous_node?.child;
@@ -1307,17 +1306,14 @@ async function load_node({ loader, parent, url, params, route, server_data_node
1307
1306
  * @param {URL} url
1308
1307
  */
1309
1308
  function resolve_fetch_url(input, init, url) {
1310
- let requested = input instanceof Request ? input.url : input;
1311
-
1312
1309
  // we must fixup relative urls so they are resolved from the target page
1313
- const resolved = new URL(requested, url);
1310
+ const resolved = new URL(input instanceof Request ? input.url : input, url);
1314
1311
 
1315
- // match ssr serialized data url, which is important to find cached responses
1316
- if (resolved.origin === url.origin) {
1317
- requested = resolved.href.slice(url.origin.length);
1318
- }
1312
+ // match the server's serialization of `fetched.url` (see load_data.js): a path for same-origin
1313
+ // urls, so prerendered pages can be served from any origin, the normalized href otherwise
1314
+ const requested =
1315
+ resolved.origin === url.origin ? resolved.href.slice(url.origin.length) : resolved.href;
1319
1316
 
1320
- // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
1321
1317
  const promise = started
1322
1318
  ? subsequent_fetch(requested, resolved.href, init)
1323
1319
  : initial_fetch(requested, init);
@@ -1385,12 +1381,12 @@ function diff_search_params(old_url, new_url) {
1385
1381
  const changed = new Set([...old_url.searchParams.keys(), ...new_url.searchParams.keys()]);
1386
1382
 
1387
1383
  for (const key of changed) {
1388
- const old_values = old_url.searchParams.getAll(key);
1389
- const new_values = new_url.searchParams.getAll(key);
1384
+ const old_values = old_url.searchParams.getAll(key).sort();
1385
+ const new_values = new_url.searchParams.getAll(key).sort();
1390
1386
 
1391
1387
  if (
1392
- old_values.every((value) => new_values.includes(value)) &&
1393
- new_values.every((value) => old_values.includes(value))
1388
+ old_values.length === new_values.length &&
1389
+ old_values.every((value, i) => value === new_values[i])
1394
1390
  ) {
1395
1391
  changed.delete(key);
1396
1392
  }
@@ -1642,27 +1638,20 @@ async function load_route({ id, invalidating, url, params, route, preload, actio
1642
1638
  * @param {number} i Start index to backtrack from
1643
1639
  * @param {Array<import('./types.js').BranchNode | undefined>} branch Branch to backtrack
1644
1640
  * @param {Array<import('types').CSRPageNodeLoader | undefined>} errors All error pages for this branch
1645
- * @returns {Promise<{idx: number; node: import('./types.js').BranchNode} | undefined>}
1641
+ * @returns {Promise<Array<import('./types.js').BranchNode | undefined> | undefined>} the branch truncated at the error page's depth
1646
1642
  */
1647
1643
  async function load_nearest_error_page(i, branch, errors) {
1648
- while (i--) {
1649
- if (errors[i]) {
1650
- let j = i;
1651
- while (!branch[j]) j -= 1;
1652
- try {
1653
- return {
1654
- idx: j + 1,
1655
- node: {
1656
- node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
1657
- loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
1658
- data: {},
1659
- server: null,
1660
- universal: null
1661
- }
1662
- };
1663
- } catch {
1664
- continue;
1665
- }
1644
+ for (const { error, idx } of nearest_error_pages(i, branch, errors)) {
1645
+ try {
1646
+ return branch.slice(0, idx).concat({
1647
+ node: await error(),
1648
+ loader: error,
1649
+ data: {},
1650
+ server: null,
1651
+ universal: null
1652
+ });
1653
+ } catch {
1654
+ continue;
1666
1655
  }
1667
1656
  }
1668
1657
  }
@@ -1680,12 +1669,12 @@ async function load_nearest_error_page(i, branch, errors) {
1680
1669
  * }} opts
1681
1670
  */
1682
1671
  async function load_route_error({ i, branch, errors, error, status, url, params, route }) {
1683
- const error_load = await load_nearest_error_page(i, branch, errors);
1684
- if (error_load) {
1672
+ const error_branch = await load_nearest_error_page(i, branch, errors);
1673
+ if (error_branch) {
1685
1674
  return get_navigation_result_from_branch({
1686
1675
  url,
1687
1676
  params,
1688
- branch: branch.slice(0, error_load.idx).concat(error_load.node),
1677
+ branch: error_branch,
1689
1678
  errors,
1690
1679
  error,
1691
1680
  status,
@@ -2038,48 +2027,34 @@ async function navigate({
2038
2027
  is_navigating = true;
2039
2028
 
2040
2029
  if (started && nav.navigation.type !== 'enter') {
2041
- navigating.current = nav.navigation;
2030
+ set_navigation(nav.navigation);
2042
2031
  }
2043
2032
 
2044
2033
  let navigation_result = intent && (await load_route({ ...intent, action_result }));
2045
2034
 
2046
2035
  if (!navigation_result) {
2047
- if (is_external_url(url, base, app.hash)) {
2048
- if (DEV && app.hash) {
2049
- // Special case for hash mode during DEV: If someone accidentally forgets to use a hash for the link,
2050
- // they would end up here in an endless loop. Fall back to error page in that case
2051
- navigation_result = await server_fallback(
2052
- url,
2053
- { id: null },
2054
- await handle_error(
2055
- new SvelteKitError(
2056
- 404,
2057
- 'Not Found',
2058
- `Not found: ${url.pathname} (did you forget the hash?)`
2059
- ),
2060
- {
2061
- url,
2062
- params: {},
2063
- route: { id: null }
2064
- }
2065
- ),
2066
- replace_state
2067
- );
2068
- } else {
2069
- return await native_navigation(url, replace_state);
2070
- }
2071
- } else {
2072
- navigation_result = await server_fallback(
2073
- url,
2074
- { id: null },
2075
- await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
2076
- url,
2077
- params: {},
2078
- route: { id: null }
2079
- }),
2080
- replace_state
2081
- );
2036
+ const external = is_external_url(url, base, app.hash);
2037
+ // Special case for hash mode during DEV: If someone accidentally forgets to use a hash for the link,
2038
+ // they would end up here in an endless loop. Fall back to error page in that case
2039
+ const missing_hash = external && DEV && app.hash;
2040
+
2041
+ if (external && !missing_hash) {
2042
+ return await native_navigation(url, replace_state);
2082
2043
  }
2044
+
2045
+ navigation_result = await server_fallback(
2046
+ url,
2047
+ { id: null },
2048
+ await handle_error(
2049
+ new SvelteKitError(
2050
+ 404,
2051
+ 'Not Found',
2052
+ `Not found: ${url.pathname}${missing_hash ? ' (did you forget the hash?)' : ''}`
2053
+ ),
2054
+ { url, params: {}, route: { id: null } }
2055
+ ),
2056
+ replace_state
2057
+ );
2083
2058
  }
2084
2059
 
2085
2060
  // if this is an internal navigation intent, use the normalized
@@ -2205,26 +2180,7 @@ async function navigate({
2205
2180
  */
2206
2181
  let commit_promise;
2207
2182
  if (started) {
2208
- const after_navigate = (
2209
- await Promise.all(
2210
- // eslint-disable-next-line @typescript-eslint/await-thenable -- we need to await because they can be asynchronous
2211
- Array.from(on_navigate_callbacks, (fn) => fn(/** @type {OnNavigate} */ (nav.navigation)))
2212
- )
2213
- ).filter(/** @returns {value is () => void} */ (value) => typeof value === 'function');
2214
-
2215
- if (after_navigate.length > 0) {
2216
- function cleanup() {
2217
- after_navigate.forEach((fn) => {
2218
- after_navigate_callbacks.delete(fn);
2219
- });
2220
- }
2221
-
2222
- after_navigate.push(cleanup);
2223
-
2224
- after_navigate.forEach((fn) => {
2225
- after_navigate_callbacks.add(fn);
2226
- });
2227
- }
2183
+ await run_on_navigate_callbacks(/** @type {OnNavigate} */ (nav.navigation));
2228
2184
 
2229
2185
  // Type-casts are save because we know this resolved a proper SvelteKit route
2230
2186
  const target = popped?.shallow
@@ -2266,15 +2222,15 @@ async function navigate({
2266
2222
  } else {
2267
2223
  apply_navigation_result(navigation_result);
2268
2224
 
2269
- // Reset any boundaries that failed on a previous navigation now that the
2270
- // new props are applied, otherwise the stale `+error.svelte` stays
2271
- // mounted above the new route's content. See sveltejs/kit#15694.
2272
- for (const reset_boundary of resetters) {
2273
- reset_boundary();
2274
- }
2275
- resetters.clear();
2276
-
2277
- commit_promise = settled();
2225
+ // Reset boundaries that failed on a previous navigation once the new props have
2226
+ // flushed (see sveltejs/kit#15694). Resetting first re-renders the old content at
2227
+ // a depth the new tree may not have, stranding the stale `+error.svelte`.
2228
+ commit_promise = settled().then(() => {
2229
+ for (const reset_boundary of resetters) {
2230
+ reset_boundary();
2231
+ }
2232
+ resetters.clear();
2233
+ });
2278
2234
  }
2279
2235
 
2280
2236
  has_navigated = true;
@@ -2282,17 +2238,70 @@ async function navigate({
2282
2238
  await initialize(navigation_result, target, false);
2283
2239
  }
2284
2240
 
2285
- const { activeElement } = document;
2241
+ const finished = await finish_navigation(
2242
+ nav,
2243
+ nav_token,
2244
+ url,
2245
+ popped?.scroll,
2246
+ reset,
2247
+ commit_promise
2248
+ );
2249
+ if (!finished) return;
2286
2250
 
2287
- await commit_promise;
2251
+ if (type === 'popstate') {
2252
+ restore_snapshot(current_navigation_index);
2253
+ }
2254
+ // new and replaced entries have no stored values, so this only resets there
2255
+ restore_navigation_snapshot(current_history_index, previous_snapshot_registrations);
2256
+
2257
+ set_navigation(null);
2258
+
2259
+ updating = false;
2260
+ }
2261
+
2262
+ /**
2263
+ * Runs the `onNavigate` callbacks and registers any functions they return to run after the navigation
2264
+ * @param {OnNavigate} navigation
2265
+ */
2266
+ async function run_on_navigate_callbacks(navigation) {
2267
+ const after_navigate = (
2268
+ await Promise.all(
2269
+ // eslint-disable-next-line @typescript-eslint/await-thenable -- we need to await because they can be asynchronous
2270
+ Array.from(on_navigate_callbacks, (fn) => fn(navigation))
2271
+ )
2272
+ ).filter(/** @returns {value is () => void} */ (value) => typeof value === 'function');
2273
+
2274
+ if (after_navigate.length > 0) {
2275
+ function cleanup() {
2276
+ after_navigate.forEach((fn) => after_navigate_callbacks.delete(fn));
2277
+ }
2278
+
2279
+ after_navigate.push(cleanup);
2280
+ after_navigate.forEach((fn) => after_navigate_callbacks.add(fn));
2281
+ }
2282
+ }
2283
+
2284
+ /**
2285
+ * Settles the navigation once `updated` resolves. Returns `false` if a newer navigation
2286
+ * superseded it in the meantime, in which case the caller must stop
2287
+ * @param {ReturnType<typeof create_navigation>} nav
2288
+ * @param {{}} nav_token
2289
+ * @param {URL} url
2290
+ * @param {{ x: number, y: number } | null | undefined} popped_scroll the scroll position to restore for popstate navigations
2291
+ * @param {boolean} reset
2292
+ * @param {Promise<void> | undefined} updated
2293
+ */
2294
+ async function finish_navigation(nav, nav_token, url, popped_scroll, reset, updated) {
2295
+ const active_element = document.activeElement;
2296
+
2297
+ await updated;
2288
2298
 
2289
2299
  if (navigation_token !== nav_token) {
2290
- // a new navigation happened while we were waiting for the DOM to update, so abort
2291
2300
  nav.reject(new Error('navigation aborted'));
2292
- return;
2301
+ return false;
2293
2302
  }
2294
2303
 
2295
- reset_scroll_and_focus(url, reset ? popped?.scroll : scroll_state(), reset, activeElement);
2304
+ reset_scroll_and_focus(url, reset ? popped_scroll : scroll_state(), reset, active_element);
2296
2305
 
2297
2306
  is_navigating = false;
2298
2307
 
@@ -2305,15 +2314,7 @@ async function navigate({
2305
2314
 
2306
2315
  after_navigate_callbacks.forEach((fn) => fn(/** @type {AfterNavigate} */ (nav.navigation)));
2307
2316
 
2308
- if (type === 'popstate') {
2309
- restore_snapshot(current_navigation_index);
2310
- }
2311
- // new and replaced entries have no stored values, so this only resets there
2312
- restore_navigation_snapshot(current_history_index, previous_snapshot_registrations);
2313
-
2314
- navigating.current = null;
2315
-
2316
- updating = false;
2317
+ return true;
2317
2318
  }
2318
2319
 
2319
2320
  /**
@@ -2611,10 +2612,6 @@ export function onNavigate(callback) {
2611
2612
  * @returns {void}
2612
2613
  */
2613
2614
  export function disableScrollHandling() {
2614
- if (!BROWSER) {
2615
- throw new Error('Cannot call disableScrollHandling() on the server');
2616
- }
2617
-
2618
2615
  if (DEV && started && !updating) {
2619
2616
  throw new Error('Can only disable scroll handling during navigation');
2620
2617
  }
@@ -2671,10 +2668,6 @@ async function resolve_intent(url, caller) {
2671
2668
  * @returns {Promise<void>}
2672
2669
  */
2673
2670
  export async function goto(url, opts = {}) {
2674
- if (!BROWSER) {
2675
- throw new Error('Cannot call goto(...) on the server');
2676
- }
2677
-
2678
2671
  if (DEV) {
2679
2672
  if ('replaceState' in opts && !warned_on_replace_state) {
2680
2673
  warned_on_replace_state = true;
@@ -2743,12 +2736,7 @@ export async function goto(url, opts = {}) {
2743
2736
  * @returns {Promise<void>}
2744
2737
  */
2745
2738
  export function invalidate(resource, keepState = false) {
2746
- if (!BROWSER) {
2747
- throw new Error('Cannot call invalidate(...) on the server');
2748
- }
2749
-
2750
2739
  push_invalidated(resource);
2751
-
2752
2740
  return _invalidate(!keepState);
2753
2741
  }
2754
2742
 
@@ -2773,10 +2761,6 @@ function push_invalidated(resource) {
2773
2761
  * @returns {Promise<void>}
2774
2762
  */
2775
2763
  export function invalidateAll() {
2776
- if (!BROWSER) {
2777
- throw new Error('Cannot call invalidateAll() on the server');
2778
- }
2779
-
2780
2764
  force_invalidation = true;
2781
2765
  return _invalidate();
2782
2766
  }
@@ -2787,10 +2771,6 @@ export function invalidateAll() {
2787
2771
  * @returns {Promise<void>}
2788
2772
  */
2789
2773
  export function refreshAll() {
2790
- if (!BROWSER) {
2791
- throw new Error('Cannot call refreshAll() on the server');
2792
- }
2793
-
2794
2774
  force_invalidation = true;
2795
2775
  return _invalidate(false);
2796
2776
  }
@@ -2808,10 +2788,6 @@ export function refreshAll() {
2808
2788
  * @returns {Promise<({ type: 'loaded'; data: Record<string, any> } | { type: 'redirect'; location: string } | { type: 'error'; error: App.Error }) & { status: number; }>}
2809
2789
  */
2810
2790
  export async function preloadData(href) {
2811
- if (!BROWSER) {
2812
- throw new Error('Cannot call preloadData(...) on the server');
2813
- }
2814
-
2815
2791
  const url = resolve_url(href);
2816
2792
  const intent = await get_navigation_intent(url, false);
2817
2793
 
@@ -2872,10 +2848,6 @@ export async function preloadData(href) {
2872
2848
  * @returns {Promise<void>}
2873
2849
  */
2874
2850
  export async function preloadCode(id) {
2875
- if (!BROWSER) {
2876
- throw new Error('Cannot call preloadCode(...) on the server');
2877
- }
2878
-
2879
2851
  if (DEV && id[0] !== '/') {
2880
2852
  throw new Error(
2881
2853
  `argument passed to preloadCode must be a route ID (i.e. "/blog/[slug]" rather than "blog/[slug]")`
@@ -2933,10 +2905,6 @@ export async function preloadCode(id) {
2933
2905
  * @returns {Promise<void>}
2934
2906
  */
2935
2907
  export async function pushState(url, state) {
2936
- if (!BROWSER) {
2937
- throw new Error('Cannot call pushState(...) on the server');
2938
- }
2939
-
2940
2908
  if (DEV && !warned_on_push_state) {
2941
2909
  warned_on_push_state = true;
2942
2910
  console.warn(
@@ -2963,10 +2931,6 @@ export async function pushState(url, state) {
2963
2931
  * @returns {Promise<void>}
2964
2932
  */
2965
2933
  export async function replaceState(url, state) {
2966
- if (!BROWSER) {
2967
- throw new Error('Cannot call replaceState(...) on the server');
2968
- }
2969
-
2970
2934
  if (DEV && !warned_on_replace_state_function) {
2971
2935
  warned_on_replace_state_function = true;
2972
2936
  console.warn(
@@ -3009,7 +2973,7 @@ async function update_state(intent, state, { replace, persist_state, reset }, ca
3009
2973
  if (nav) {
3010
2974
  navigation_token = invalidation_token = nav_token;
3011
2975
  is_navigating = true;
3012
- navigating.current = nav.navigation;
2976
+ set_navigation(nav.navigation);
3013
2977
  updating = true;
3014
2978
  }
3015
2979
 
@@ -3046,59 +3010,29 @@ async function update_state(intent, state, { replace, persist_state, reset }, ca
3046
3010
  }
3047
3011
 
3048
3012
  if (nav) {
3049
- const after_navigate = (
3050
- await Promise.all(
3051
- // eslint-disable-next-line @typescript-eslint/await-thenable -- we need to await because they can be asynchronous
3052
- Array.from(on_navigate_callbacks, (fn) => fn(/** @type {OnNavigate} */ (nav.navigation)))
3053
- )
3054
- ).filter(/** @returns {value is () => void} */ (value) => typeof value === 'function');
3055
-
3056
- if (after_navigate.length > 0) {
3057
- function cleanup() {
3058
- after_navigate.forEach((fn) => after_navigate_callbacks.delete(fn));
3059
- }
3060
-
3061
- after_navigate.push(cleanup);
3062
- after_navigate.forEach((fn) => after_navigate_callbacks.add(fn));
3063
- }
3013
+ await run_on_navigate_callbacks(/** @type {OnNavigate} */ (nav.navigation));
3064
3014
  }
3065
3015
 
3066
3016
  blur_active_element(reset);
3067
3017
 
3068
- page.state = state;
3069
- page.shallow = {
3070
- params: intent?.params ?? null,
3071
- route: intent ? { id: intent.route.id } : null,
3072
- url
3073
- };
3074
-
3075
- if (nav) {
3076
- const { activeElement } = document;
3077
-
3078
- await settled();
3079
-
3080
- if (navigation_token !== nav_token) {
3081
- // a new navigation happened while we were waiting for the DOM to update, so abort
3082
- nav.reject(new Error('navigation aborted'));
3083
- return;
3084
- }
3085
-
3086
- reset_scroll_and_focus(url, reset ? null : scroll_state(), reset, activeElement);
3087
-
3088
- is_navigating = false;
3089
- nav.fulfil(undefined);
3090
-
3091
- if (nav.navigation.to) {
3092
- nav.navigation.to.scroll = scroll_state();
3018
+ update_page({
3019
+ state,
3020
+ shallow: {
3021
+ params: intent?.params ?? null,
3022
+ route: intent ? { id: intent.route.id } : null,
3023
+ url
3093
3024
  }
3025
+ });
3094
3026
 
3095
- after_navigate_callbacks.forEach((fn) => fn(/** @type {AfterNavigate} */ (nav.navigation)));
3027
+ if (nav) {
3028
+ const finished = await finish_navigation(nav, nav_token, url, null, reset, settled());
3029
+ if (!finished) return;
3096
3030
  }
3097
3031
 
3098
3032
  restore_navigation_snapshot(current_history_index, previous_snapshot_registrations);
3099
3033
 
3100
3034
  if (nav) {
3101
- navigating.current = null;
3035
+ set_navigation(null);
3102
3036
  updating = false;
3103
3037
  }
3104
3038
  }
@@ -3113,10 +3047,6 @@ async function update_state(intent, state, { replace, persist_state, reset }, ca
3113
3047
  * @returns {Promise<void>}
3114
3048
  */
3115
3049
  export async function applyAction(result) {
3116
- if (!BROWSER) {
3117
- throw new Error('Cannot call applyAction(...) on the server');
3118
- }
3119
-
3120
3050
  if (result.type === 'redirect') {
3121
3051
  await _goto(result.location, { refreshAll: true });
3122
3052
  return;
@@ -3125,8 +3055,10 @@ export async function applyAction(result) {
3125
3055
  if (result.type === 'error') {
3126
3056
  await set_nearest_error_page(result.error);
3127
3057
  } else {
3128
- page.form = result.data;
3129
- page.status = result.status;
3058
+ update_page({
3059
+ form: result.data,
3060
+ status: result.status
3061
+ });
3130
3062
 
3131
3063
  /** @type {Record<string, any>} */
3132
3064
  // this brings Svelte's view of the world in line with SvelteKit's
@@ -3197,14 +3129,14 @@ export async function set_nearest_error_page(error) {
3197
3129
  const { branch, route } = current;
3198
3130
  if (!route) return;
3199
3131
 
3200
- const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
3201
- if (error_load) {
3132
+ const error_branch = await load_nearest_error_page(current.branch.length, branch, route.errors);
3133
+ if (error_branch) {
3202
3134
  const navigation_result = await get_navigation_result_from_branch({
3203
3135
  url,
3204
3136
  params: current.params,
3205
- branch: branch.slice(0, error_load.idx).concat(error_load.node),
3137
+ branch: error_branch,
3206
3138
  error,
3207
- // do not set errors, we haven't changed the page so the previous ones are still current
3139
+ errors: route.errors,
3208
3140
  route
3209
3141
  });
3210
3142
 
@@ -3491,11 +3423,10 @@ function _start_router() {
3491
3423
 
3492
3424
  blur_active_element(reset);
3493
3425
 
3494
- if (state !== page.state) {
3495
- page.state = state;
3496
- }
3497
-
3498
- page.shallow = shallow_target;
3426
+ update_page({
3427
+ state,
3428
+ shallow: shallow_target
3429
+ });
3499
3430
 
3500
3431
  update_url(url);
3501
3432
 
@@ -3584,7 +3515,7 @@ function _start_router() {
3584
3515
  // the navigation away from it was successful.
3585
3516
  // Info about bfcache here: https://web.dev/bfcache
3586
3517
  if (event.persisted) {
3587
- navigating.current = null;
3518
+ set_navigation(null);
3588
3519
  }
3589
3520
  });
3590
3521
 
@@ -3592,7 +3523,8 @@ function _start_router() {
3592
3523
  * @param {URL} url
3593
3524
  */
3594
3525
  function update_url(url) {
3595
- current.url = page.url = url;
3526
+ current.url = url;
3527
+ update_page({ url });
3596
3528
  }
3597
3529
  }
3598
3530
 
@@ -4061,7 +3993,7 @@ if (DEV) {
4061
3993
  * @param {NavigationFinished} result
4062
3994
  */
4063
3995
  function apply_navigation_result(result) {
4064
- Object.assign(page, result.props.page);
3996
+ update_page(result.props.page);
4065
3997
 
4066
3998
  props.tree.data = result.props.tree.data;
4067
3999
  props.tree.child = result.props.tree.child;