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

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 (64) hide show
  1. package/package.json +7 -2
  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 +85 -1
  7. package/src/core/generate_manifest/index.js +12 -15
  8. package/src/core/sync/create_manifest_data/index.js +6 -44
  9. package/src/core/sync/write_client_manifest.js +7 -14
  10. package/src/core/sync/write_non_ambient.js +10 -7
  11. package/src/core/sync/write_root.js +9 -30
  12. package/src/core/sync/write_server.js +0 -1
  13. package/src/core/sync/write_types/index.js +11 -10
  14. package/src/exports/index.js +3 -3
  15. package/src/exports/internal/client.js +5 -0
  16. package/src/exports/internal/index.js +1 -91
  17. package/src/exports/internal/{event.js → server/event.js} +1 -2
  18. package/src/exports/internal/server/index.js +33 -0
  19. package/src/exports/internal/shared.js +89 -0
  20. package/src/exports/node/index.js +1 -1
  21. package/src/exports/params.js +63 -0
  22. package/src/exports/public.d.ts +82 -32
  23. package/src/exports/url.js +84 -0
  24. package/src/exports/vite/dev/index.js +28 -17
  25. package/src/exports/vite/index.js +217 -156
  26. package/src/exports/vite/preview/index.js +1 -1
  27. package/src/exports/vite/utils.js +3 -5
  28. package/src/runtime/app/paths/client.js +3 -7
  29. package/src/runtime/app/paths/server.js +1 -1
  30. package/src/runtime/app/server/remote/query.js +6 -12
  31. package/src/runtime/app/state/client.js +1 -2
  32. package/src/runtime/app/stores.js +13 -76
  33. package/src/runtime/client/client.js +26 -79
  34. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  35. package/src/runtime/client/remote-functions/form.svelte.js +5 -24
  36. package/src/runtime/client/remote-functions/prerender.svelte.js +10 -3
  37. package/src/runtime/client/remote-functions/query/instance.svelte.js +18 -9
  38. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +16 -6
  39. package/src/runtime/client/remote-functions/shared.svelte.js +1 -2
  40. package/src/runtime/client/state.svelte.js +66 -49
  41. package/src/runtime/client/types.d.ts +1 -1
  42. package/src/runtime/client/utils.js +0 -96
  43. package/src/runtime/form-utils.js +15 -2
  44. package/src/runtime/server/index.js +1 -1
  45. package/src/runtime/server/page/load_data.js +1 -1
  46. package/src/runtime/server/page/render.js +15 -24
  47. package/src/runtime/server/page/server_routing.js +13 -9
  48. package/src/runtime/server/remote.js +21 -12
  49. package/src/runtime/server/respond.js +11 -8
  50. package/src/runtime/telemetry/otel.js +1 -1
  51. package/src/types/ambient.d.ts +5 -1
  52. package/src/types/global-private.d.ts +1 -1
  53. package/src/types/internal.d.ts +9 -10
  54. package/src/utils/error.js +1 -1
  55. package/src/utils/mime.js +9 -0
  56. package/src/utils/params.js +66 -0
  57. package/src/utils/routing.js +84 -38
  58. package/src/utils/streaming.js +14 -4
  59. package/src/utils/url.js +0 -79
  60. package/src/version.js +1 -1
  61. package/types/index.d.ts +98 -87
  62. package/types/index.d.ts.map +10 -7
  63. package/src/exports/internal/server.js +0 -22
  64. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -411,20 +411,14 @@ export function refresh(event, state, internals, payload, fn) {
411
411
 
412
412
  const key = create_remote_key(internals.id, payload);
413
413
 
414
- // `fn()` is invoked eagerly here, which starts running the query immediately.
415
- // The resulting promise is normally awaited (and its rejection handled) in
416
- // `collect_remote_data`, but some code paths (e.g. a command throwing a
417
- // non-redirect error) never reach that point. Attach a no-op `catch` to the
418
- // promise so a rejection is always considered handled and can never become an
419
- // unhandled promise rejection (which crashes the process on modern Node).
420
- // We still store the original promise so `collect_remote_data` can serialize
421
- // either its value or its error as before.
422
- const promise = fn();
423
- promise.catch(() => {});
424
-
414
+ // `fn` is stored rather than invoked eagerly. The query is run at the end of
415
+ // the command/form (in `collect_remote_data`), so that it observes any state
416
+ // mutations that happen after `refresh()` is called. If the developer re-awaits
417
+ // the query before the command finishes, the cache entry created by that await
418
+ // is reused instead of re-running the query.
425
419
  (state.remote.explicit ??= new Map()).set(key, {
426
420
  internals,
427
- promise
421
+ fn
428
422
  });
429
423
  }
430
424
 
@@ -3,7 +3,6 @@ import {
3
3
  navigating as _navigating,
4
4
  updated as _updated
5
5
  } from '../../client/state.svelte.js';
6
- import { stores } from '../../client/client.js';
7
6
 
8
7
  export const page = {
9
8
  get data() {
@@ -57,5 +56,5 @@ export const updated = {
57
56
  get current() {
58
57
  return _updated.current;
59
58
  },
60
- check: stores.updated.check
59
+ check: _updated.check
61
60
  };
@@ -1,101 +1,38 @@
1
- import { getContext } from 'svelte';
2
- import { BROWSER, DEV } from 'esm-env';
3
- import { stores as browser_stores } from '../client/client.js';
1
+ // TODO: remove this file when enough people have migrated to Kit 3
2
+
3
+ /** @returns {never} */
4
+ function removed() {
5
+ throw new Error(
6
+ '`$app/stores` has been removed in favour of `$app/state`. See https://svelte.dev/docs/kit/migrating-to-sveltekit-2#SvelteKit-2.12:-$app-stores-deprecated'
7
+ );
8
+ }
4
9
 
5
10
  /**
6
- * A function that returns all of the contextual stores. On the server, this must be called during component initialization.
7
- * Only use this if you need to defer store subscription until after the component has mounted, for some reason.
8
- *
9
11
  * @deprecated Use `$app/state` instead (requires Svelte 5, [see docs for more info](https://svelte.dev/docs/kit/migrating-to-sveltekit-2#SvelteKit-2.12:-$app-stores-deprecated))
10
12
  */
11
- export const getStores = () => {
12
- const stores = BROWSER ? browser_stores : getContext('__svelte__');
13
-
14
- return {
15
- /** @type {typeof page} */
16
- page: {
17
- subscribe: stores.page.subscribe
18
- },
19
- /** @type {typeof navigating} */
20
- navigating: {
21
- subscribe: stores.navigating.subscribe
22
- },
23
- /** @type {typeof updated} */
24
- updated: stores.updated
25
- };
26
- };
13
+ export const getStores = removed;
27
14
 
28
15
  /**
29
- * A readable store whose value contains page data.
30
- *
31
- * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
32
- *
33
16
  * @deprecated Use `page` from `$app/state` instead (requires Svelte 5, [see docs for more info](https://svelte.dev/docs/kit/migrating-to-sveltekit-2#SvelteKit-2.12:-$app-stores-deprecated))
34
17
  * @type {import('svelte/store').Readable<import('@sveltejs/kit').Page>}
35
18
  */
36
19
  export const page = {
37
- subscribe(fn) {
38
- const store = DEV ? get_store('page') : getStores().page;
39
- return store.subscribe(fn);
40
- }
20
+ subscribe: removed
41
21
  };
42
22
 
43
23
  /**
44
- * A readable store.
45
- * When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
46
- * When navigating finishes, its value reverts to `null`.
47
- *
48
- * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
49
- *
50
24
  * @deprecated Use `navigating` from `$app/state` instead (requires Svelte 5, [see docs for more info](https://svelte.dev/docs/kit/migrating-to-sveltekit-2#SvelteKit-2.12:-$app-stores-deprecated))
51
25
  * @type {import('svelte/store').Readable<import('@sveltejs/kit').Navigation | null>}
52
26
  */
53
27
  export const navigating = {
54
- subscribe(fn) {
55
- const store = DEV ? get_store('navigating') : getStores().navigating;
56
- return store.subscribe(fn);
57
- }
28
+ subscribe: removed
58
29
  };
59
30
 
60
31
  /**
61
- * A readable store whose initial value is `false`. If [`version.pollInterval`](https://svelte.dev/docs/kit/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
62
- *
63
- * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
64
- *
65
32
  * @deprecated Use `updated` from `$app/state` instead (requires Svelte 5, [see docs for more info](https://svelte.dev/docs/kit/migrating-to-sveltekit-2#SvelteKit-2.12:-$app-stores-deprecated))
66
33
  * @type {import('svelte/store').Readable<boolean> & { check(): Promise<boolean> }}
67
34
  */
68
35
  export const updated = {
69
- subscribe(fn) {
70
- const store = DEV ? get_store('updated') : getStores().updated;
71
-
72
- if (BROWSER) {
73
- updated.check = store.check;
74
- }
75
-
76
- return store.subscribe(fn);
77
- },
78
- check: () => {
79
- throw new Error(
80
- BROWSER
81
- ? 'Cannot check updated store before subscribing'
82
- : 'Can only check updated store in browser'
83
- );
84
- }
36
+ subscribe: removed,
37
+ check: removed
85
38
  };
86
-
87
- /**
88
- * @template {keyof ReturnType<typeof getStores>} Name
89
- * @param {Name} name
90
- * @returns {ReturnType<typeof getStores>[Name]}
91
- */
92
- function get_store(name) {
93
- try {
94
- return getStores()[name];
95
- } catch {
96
- throw new Error(
97
- `Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.` +
98
- 'For more information, see https://svelte.dev/docs/kit/state-management#avoid-shared-state-on-the-server'
99
- );
100
- }
101
- }
@@ -6,16 +6,7 @@
6
6
  import { BROWSER, DEV } from 'esm-env';
7
7
  import * as svelte from 'svelte';
8
8
  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';
9
+ import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
19
10
  import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
20
11
  import { parse, parse_server_route } from './parse.js';
21
12
  import * as storage from './session-storage.js';
@@ -27,8 +18,6 @@ import {
27
18
  is_external_url,
28
19
  origin,
29
20
  scroll_state,
30
- notifiable_store,
31
- create_updated_store,
32
21
  load_css
33
22
  } from './utils.js';
34
23
  import { base } from '$app/paths/internal/client';
@@ -53,8 +42,7 @@ 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';
58
46
  import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
59
47
  import { noop_span } from '../telemetry/noop.js';
60
48
  import { read_ndjson } from './ndjson.js';
@@ -127,15 +115,6 @@ if (DEV && BROWSER) {
127
115
  };
128
116
  }
129
117
 
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
118
  /** @param {number} index */
140
119
  function update_scroll_positions(index) {
141
120
  scroll_positions[index] = scroll_state();
@@ -723,13 +702,14 @@ async function initialize(result, target, hydrate) {
723
702
 
724
703
  update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
725
704
 
705
+ // TODO: use mount()
726
706
  root = new app.root({
727
707
  target,
728
- props: { ...result.props, stores, components },
708
+ props: { ...result.props, components },
729
709
  hydrate,
730
- // @ts-ignore Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
710
+ // Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
731
711
  sync: false,
732
- // @ts-ignore Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
712
+ // Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
733
713
  transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
734
714
  ? /** @param {unknown} e */ async (e) => {
735
715
  const error = await handle_error(e, current.nav);
@@ -817,9 +797,8 @@ async function get_navigation_result_from_branch({
817
797
  route
818
798
  },
819
799
  props: {
820
- // @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
821
800
  constructors: compact(branch).map((branch_node) => branch_node.node.component),
822
- page: clone_page(page)
801
+ page
823
802
  }
824
803
  };
825
804
 
@@ -1177,10 +1156,6 @@ function diff_search_params(old_url, new_url) {
1177
1156
  * @returns {import('./types.js').NavigationFinished}
1178
1157
  */
1179
1158
  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
1159
  return {
1185
1160
  type: 'loaded',
1186
1161
  state: {
@@ -1191,7 +1166,12 @@ function preload_error({ error, url, route, params }) {
1191
1166
  branch: []
1192
1167
  },
1193
1168
  props: {
1194
- page: new_page,
1169
+ page: {
1170
+ // we skipped loading the error page, so we have to use the current page
1171
+ // store, but update the status received while preloading
1172
+ ...page,
1173
+ status: error.status
1174
+ },
1195
1175
  constructors: []
1196
1176
  }
1197
1177
  };
@@ -1377,8 +1357,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1377
1357
  error = err.body;
1378
1358
  } else {
1379
1359
  // Referenced node could have been removed due to redeploy, check
1380
- const updated = await stores.updated.check();
1381
- if (updated) {
1360
+ if (await updated.check()) {
1382
1361
  // Before reloading, try to update the service worker if it exists
1383
1362
  await update_service_worker();
1384
1363
  return await native_navigation(url);
@@ -1631,7 +1610,7 @@ export async function get_navigation_intent(url, invalidating) {
1631
1610
  id: get_page_key(url),
1632
1611
  invalidating,
1633
1612
  route,
1634
- params: decode_params(params),
1613
+ params,
1635
1614
  url
1636
1615
  };
1637
1616
  }
@@ -1780,7 +1759,7 @@ async function navigate({
1780
1759
  is_navigating = true;
1781
1760
 
1782
1761
  if (started && nav.navigation.type !== 'enter') {
1783
- stores.navigating.set((navigating.current = nav.navigation));
1762
+ navigating.current = nav.navigation;
1784
1763
  }
1785
1764
 
1786
1765
  let navigation_result = intent && (await load_route(intent));
@@ -1867,8 +1846,7 @@ async function navigate({
1867
1846
 
1868
1847
  if (!navigation_result) return;
1869
1848
  } else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
1870
- const updated = await stores.updated.check();
1871
- if (updated) {
1849
+ if (await updated.check()) {
1872
1850
  // Before reloading, try to update the service worker if it exists
1873
1851
  await update_service_worker();
1874
1852
  return await native_navigation(url, replace_state);
@@ -1999,13 +1977,7 @@ async function navigate({
1999
1977
 
2000
1978
  const { activeElement } = document;
2001
1979
 
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();
1980
+ await (commit_promise ?? svelte.tick());
2009
1981
 
2010
1982
  if (navigation_token !== nav_token) {
2011
1983
  // a new navigation happened while we were waiting for the DOM to update, so abort
@@ -2069,7 +2041,7 @@ async function navigate({
2069
2041
  restore_snapshot(current_navigation_index);
2070
2042
  }
2071
2043
 
2072
- stores.navigating.set((navigating.current = null));
2044
+ navigating.current = null;
2073
2045
 
2074
2046
  updating = false;
2075
2047
  }
@@ -2257,7 +2229,7 @@ export async function handle_error(error, event) {
2257
2229
  * @param {T} callback
2258
2230
  */
2259
2231
  function add_navigation_callback(callbacks, callback) {
2260
- onMount(() => {
2232
+ svelte.onMount(() => {
2261
2233
  callbacks.add(callback);
2262
2234
 
2263
2235
  return () => {
@@ -2573,8 +2545,7 @@ export function pushState(url, state) {
2573
2545
 
2574
2546
  page.state = state;
2575
2547
  root.$set({
2576
- // we need to assign a new page object so that subscribers are correctly notified
2577
- page: untrack(() => clone_page(page))
2548
+ page
2578
2549
  });
2579
2550
 
2580
2551
  clear_onward_history(current_history_index, current_navigation_index);
@@ -2617,7 +2588,7 @@ export function replaceState(url, state) {
2617
2588
 
2618
2589
  page.state = state;
2619
2590
  root.$set({
2620
- page: untrack(() => clone_page(page))
2591
+ page
2621
2592
  });
2622
2593
  }
2623
2594
 
@@ -2647,11 +2618,11 @@ export async function applyAction(result) {
2647
2618
  // this brings Svelte's view of the world in line with SvelteKit's
2648
2619
  // after use:enhance reset the form....
2649
2620
  form: null,
2650
- page: clone_page(page)
2621
+ page
2651
2622
  });
2652
2623
 
2653
2624
  // ...so that setting the `form` prop takes effect and isn't ignored
2654
- await tick();
2625
+ await svelte.tick();
2655
2626
  root.$set({ form: result.data });
2656
2627
 
2657
2628
  if (result.type === 'success') {
@@ -2685,7 +2656,7 @@ export async function set_nearest_error_page(error) {
2685
2656
  root.$set(navigation_result.props);
2686
2657
  update(navigation_result.props.page);
2687
2658
 
2688
- void tick().then(() => reset_focus(current.url));
2659
+ void svelte.tick().then(() => reset_focus(current.url));
2689
2660
  }
2690
2661
  }
2691
2662
 
@@ -3010,7 +2981,7 @@ function _start_router() {
3010
2981
  // the navigation away from it was successful.
3011
2982
  // Info about bfcache here: https://web.dev/bfcache
3012
2983
  if (event.persisted) {
3013
- stores.navigating.set((navigating.current = null));
2984
+ navigating.current = null;
3014
2985
  }
3015
2986
  });
3016
2987
 
@@ -3019,8 +2990,6 @@ function _start_router() {
3019
2990
  */
3020
2991
  function update_url(url) {
3021
2992
  current.url = page.url = url;
3022
- stores.page.set(clone_page(page));
3023
- stores.page.notify();
3024
2993
  }
3025
2994
  }
3026
2995
 
@@ -3414,28 +3383,6 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
3414
3383
  };
3415
3384
  }
3416
3385
 
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
3386
  /**
3440
3387
  * @param {URL} url
3441
3388
  * @returns {URL}
@@ -1,8 +1,10 @@
1
+ /** @import { Query } from './query/instance.svelte.js' */
2
+ /** @import { LiveQuery } from './query-live/instance.svelte.js' */
1
3
  import { tick } from 'svelte';
2
4
  import { once } from '../../../utils/functions.js';
3
5
 
4
6
  /**
5
- * @template R
7
+ * @template [R=Query<any> | LiveQuery<any>]
6
8
  * @typedef {object} CacheEntry
7
9
  * @property {number} proxy_count The number of live proxy instances referencing this
8
10
  * entry. The entry is eligible for eviction when this hits zero.
@@ -23,8 +23,9 @@ import {
23
23
  build_path_string,
24
24
  normalize_issue,
25
25
  serialize_binary_form,
26
- BINARY_FORM_CONTENT_TYPE,
27
- deep_get
26
+ deep_get,
27
+ DELETE_KEY,
28
+ BINARY_FORM_CONTENT_TYPE
28
29
  } from '../../form-utils.js';
29
30
 
30
31
  /**
@@ -514,14 +515,7 @@ export function form(id) {
514
515
  if (file) {
515
516
  set_nested_value(input, name, file);
516
517
  } else {
517
- // Remove the property by setting to undefined and clean up
518
- const path_parts = name.split(/\.|\[|\]/).filter(Boolean);
519
- let current = /** @type {any} */ (input);
520
- for (let i = 0; i < path_parts.length - 1; i++) {
521
- if (current[path_parts[i]] == null) return;
522
- current = current[path_parts[i]];
523
- }
524
- delete current[path_parts[path_parts.length - 1]];
518
+ set_nested_value(input, name, DELETE_KEY);
525
519
  }
526
520
  } else {
527
521
  set_nested_value(
@@ -672,12 +666,7 @@ export function form(id) {
672
666
  },
673
667
  validate: {
674
668
  /** @type {RemoteForm<any, any>['validate']} */
675
- value: async ({
676
- all = false,
677
- preflightOnly = false,
678
- // @ts-expect-error TODO remove this in 3.0
679
- includeUntouched
680
- } = {}) => {
669
+ value: async ({ all = false, preflightOnly = false } = {}) => {
681
670
  if (!element) return;
682
671
 
683
672
  const id = ++validate_id;
@@ -728,14 +717,6 @@ export function form(id) {
728
717
  array = /** @type {InternalRemoteFormIssue[]} */ (result._);
729
718
  }
730
719
 
731
- if (includeUntouched !== undefined) {
732
- console.warn(
733
- `\`{ includeUntouched: ${includeUntouched} }\` has been replaced with \`{ all: ${includeUntouched} }\``
734
- );
735
-
736
- all = includeUntouched;
737
- }
738
-
739
720
  if (!all && !submitted) {
740
721
  array = array.filter((issue) => can_validate[issue.name]);
741
722
  }
@@ -2,10 +2,11 @@
2
2
  import { app_dir, base } from '$app/paths/internal/client';
3
3
  import { version } from '$app/env';
4
4
  import * as devalue from 'devalue';
5
- import { app, _goto, prerender_responses } from '../client.js';
5
+ import { app, _goto, handle_error, prerender_responses } from '../client.js';
6
6
  import { get_remote_request_headers, remote_request, unwrap_node } from './shared.svelte.js';
7
7
  import { create_remote_key, stringify_remote_arg } from '../../shared.js';
8
8
  import { noop } from '../../../utils/functions.js';
9
+ import { HttpError } from '@sveltejs/kit/internal';
9
10
 
10
11
  // Initialize Cache API for prerender functions
11
12
  const CACHE_NAME = __SVELTEKIT_DEV__ ? `sveltekit:${Date.now()}` : `sveltekit:${version}`;
@@ -148,6 +149,7 @@ class Prerender {
148
149
  /** @type {T | undefined} */
149
150
  #current = $state.raw();
150
151
 
152
+ /** @type {App.Error | undefined} */
151
153
  #error = $state.raw(undefined);
152
154
 
153
155
  /**
@@ -162,10 +164,15 @@ class Prerender {
162
164
  this.#error = undefined;
163
165
  return value;
164
166
  },
165
- (error) => {
167
+ async (e) => {
168
+ const error = await handle_error(e, {
169
+ params: {},
170
+ route: { id: null },
171
+ url: new URL(location.href)
172
+ });
166
173
  this.#loading = false;
167
174
  this.#error = error;
168
- throw error;
175
+ throw new HttpError(error.status, error); // so that transformError doesn't transform it again
169
176
  }
170
177
  );
171
178
 
@@ -1,4 +1,4 @@
1
- import { query_responses } from '../../client.js';
1
+ import { query_responses, handle_error } from '../../client.js';
2
2
  import { HttpError } from '@sveltejs/kit/internal';
3
3
  import { QUERY_OVERRIDE_KEY } from '../shared.svelte.js';
4
4
  import { noop } from '../../../../utils/functions.js';
@@ -37,7 +37,7 @@ export class Query {
37
37
  return this.#overrides.reduce((v, r) => r(v), /** @type {T} */ (this.#raw));
38
38
  });
39
39
 
40
- /** @type {any} */
40
+ /** @type {App.Error | undefined} */
41
41
  #error = $state.raw(undefined);
42
42
 
43
43
  /** @type {Promise<T>['then']} */
@@ -126,22 +126,33 @@ export class Query {
126
126
 
127
127
  resolve(undefined);
128
128
  })
129
- .catch((e) => {
129
+ .catch(async (e) => {
130
130
  // TODO: Our behavior here could be better:
131
131
  // - We should not reject on redirects, but should hook into the router
132
132
  // to ensure the query is properly refreshed before the navigation completes
133
133
  // - Instead of failing on transport-level errors, we should probably do what
134
134
  // LiveQuery does and preserve the last known good value and retry the connection
135
+ if (this.#latest.indexOf(resolve) === -1) return;
136
+
137
+ const error = await handle_error(e, {
138
+ params: {},
139
+ route: { id: null },
140
+ url: new URL(location.href)
141
+ });
142
+
143
+ // Re-check after the async `handle_error` gap: a later request may have
144
+ // resolved/rejected while we were awaiting and superseded this one, so
145
+ // recompute the index and bail out if this request is no longer current
135
146
  const idx = this.#latest.indexOf(resolve);
136
147
  if (idx === -1) return;
137
148
 
138
149
  untrack(() => {
139
150
  this.#latest.splice(0, idx).forEach((r) => r(undefined));
140
- this.#error = e;
151
+ this.#error = error;
141
152
  this.#loading = false;
142
153
  });
143
154
 
144
- reject(e);
155
+ reject(new HttpError(error.status, error)); // so that transformError doesn't transform it again
145
156
  });
146
157
 
147
158
  return promise;
@@ -229,9 +240,7 @@ export class Query {
229
240
  this.#promise = Promise.resolve();
230
241
  }
231
242
 
232
- /**
233
- * @param {unknown} error
234
- */
243
+ /** @param {HttpError} error */
235
244
  fail(error) {
236
245
  // normally consumed in the constructor, but make sure a leftover
237
246
  // SSR record can never shadow the newly-set error
@@ -239,7 +248,7 @@ export class Query {
239
248
 
240
249
  this.#clear_pending();
241
250
  this.#loading = false;
242
- this.#error = error;
251
+ this.#error = error.body;
243
252
 
244
253
  const promise = Promise.reject(error);
245
254
 
@@ -1,4 +1,4 @@
1
- import { query_responses } from '../../client.js';
1
+ import { query_responses, handle_error } from '../../client.js';
2
2
  import { HttpError, Redirect } from '@sveltejs/kit/internal';
3
3
  import { noop, once } from '../../../../utils/functions.js';
4
4
  import { SharedIterator } from '../../../../utils/shared-iterator.js';
@@ -24,7 +24,7 @@ export class LiveQuery {
24
24
  #done = $state(false);
25
25
  /** @type {T | undefined} */
26
26
  #raw = $state.raw();
27
- /** @type {any} */
27
+ /** @type {App.Error | undefined} */
28
28
  #error = $state.raw(undefined);
29
29
  /** @type {Promise<void>} */
30
30
  #promise;
@@ -93,7 +93,7 @@ export class LiveQuery {
93
93
  // and the query can recover
94
94
  const error = new HttpError(node.e.status, node.e);
95
95
  this.#loading = false;
96
- this.#error = error;
96
+ this.#error = error.body;
97
97
 
98
98
  promise.catch(noop);
99
99
  this.#reject_first?.(error);
@@ -175,7 +175,7 @@ export class LiveQuery {
175
175
 
176
176
  if (!this.#ready) {
177
177
  // If we haven't successfully connected and received a value yet, surface the error
178
- this.fail(error);
178
+ await this.#fail(error);
179
179
  on_connect_failed(error);
180
180
  break;
181
181
  }
@@ -385,10 +385,10 @@ export class LiveQuery {
385
385
  this.#fan_out.push(value);
386
386
  }
387
387
 
388
- /** @param {unknown} error */
388
+ /** @param {HttpError} error */
389
389
  fail(error) {
390
390
  this.#loading = false;
391
- this.#error = error;
391
+ this.#error = error.body;
392
392
  // `fail` is terminal — once a live query has hard-failed, the only way to start
393
393
  // streaming again is via `reconnect()`. Mark it done and abort any in-flight
394
394
  // request so that callers from outside the main loop (e.g. `apply_reconnections`)
@@ -410,6 +410,16 @@ export class LiveQuery {
410
410
  this.#fan_out.fail(error);
411
411
  }
412
412
 
413
+ /** @param {unknown} e */
414
+ async #fail(e) {
415
+ const error = await handle_error(e, {
416
+ params: {},
417
+ route: { id: null },
418
+ url: new URL(location.href)
419
+ });
420
+ this.fail(new HttpError(error.status, error));
421
+ }
422
+
413
423
  get [Symbol.toStringTag]() {
414
424
  return 'LiveQuery';
415
425
  }
@@ -137,9 +137,8 @@ export async function remote_request(url, init) {
137
137
  );
138
138
 
139
139
  /**
140
- *
141
140
  * @param {string} key
142
- * @param {CacheEntry<any> | undefined} entry
141
+ * @param {CacheEntry | undefined} entry
143
142
  * @param {any} result
144
143
  */
145
144
  function refresh(key, entry, result) {