@sveltejs/kit 3.0.0-next.25 → 3.0.0-next.27

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 (59) hide show
  1. package/package.json +5 -7
  2. package/src/core/adapt/builder.js +127 -31
  3. package/src/core/adapt/index.js +3 -0
  4. package/src/core/config/index.js +4 -7
  5. package/src/core/env.js +36 -8
  6. package/src/core/generate_manifest/index.js +42 -43
  7. package/src/core/postbuild/analyse.js +4 -4
  8. package/src/core/postbuild/fallback.js +1 -1
  9. package/src/core/postbuild/prerender.js +2 -2
  10. package/src/core/sync/create_manifest_data/index.js +0 -1
  11. package/src/core/sync/write_app_manifest.js +3 -2
  12. package/src/exports/public.d.ts +100 -48
  13. package/src/exports/vite/build/index.js +1173 -0
  14. package/src/exports/vite/build/remote.js +4 -4
  15. package/src/exports/vite/dev/generate_manifest.js +308 -0
  16. package/src/exports/vite/dev/index.js +28 -284
  17. package/src/exports/vite/index.js +117 -1308
  18. package/src/exports/vite/plugins/env-vars.js +11 -34
  19. package/src/exports/vite/plugins/guard.js +19 -7
  20. package/src/exports/vite/plugins/remote.js +237 -0
  21. package/src/exports/vite/preview/index.js +8 -8
  22. package/src/exports/vite/static_analysis/index.js +15 -15
  23. package/src/exports/vite/utils.js +1 -1
  24. package/src/runner.js +4 -3
  25. package/src/runtime/app/paths/server.js +2 -2
  26. package/src/runtime/app/server/index.js +3 -3
  27. package/src/runtime/app/server/public.d.ts +114 -57
  28. package/src/runtime/app/server/remote/requested.js +31 -15
  29. package/src/runtime/app/state/client.svelte.js +3 -1
  30. package/src/runtime/client/client.js +39 -194
  31. package/src/runtime/client/fetcher.js +6 -6
  32. package/src/runtime/client/focus.js +120 -0
  33. package/src/runtime/client/remote-functions/command.svelte.js +20 -9
  34. package/src/runtime/client/remote-functions/form.svelte.js +12 -7
  35. package/src/runtime/client/remote-functions/query/instance.svelte.js +19 -5
  36. package/src/runtime/client/remote-functions/shared.svelte.js +22 -1
  37. package/src/runtime/client/scroll.js +39 -0
  38. package/src/runtime/client/utils.js +28 -0
  39. package/src/runtime/form-utils.js +243 -245
  40. package/src/runtime/server/data/index.js +3 -10
  41. package/src/runtime/server/fetch.js +13 -15
  42. package/src/runtime/server/index.js +8 -7
  43. package/src/runtime/server/internal.js +1 -2
  44. package/src/runtime/server/page/index.js +10 -15
  45. package/src/runtime/server/page/load_data.js +2 -2
  46. package/src/runtime/server/page/render.js +8 -13
  47. package/src/runtime/server/page/respond_with_error.js +4 -5
  48. package/src/runtime/server/page/server_routing.js +21 -27
  49. package/src/runtime/server/remote-functions.js +14 -14
  50. package/src/runtime/server/respond.js +17 -40
  51. package/src/runtime/server/state.js +1 -0
  52. package/src/runtime/server/utils.js +4 -4
  53. package/src/runtime/shared.js +9 -0
  54. package/src/types/ambient-private.d.ts +1 -2
  55. package/src/types/internal.d.ts +49 -6
  56. package/src/utils/filesystem.js +43 -27
  57. package/src/version.js +1 -1
  58. package/types/index.d.ts +215 -92
  59. package/types/index.d.ts.map +6 -2
@@ -13,6 +13,8 @@ import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../
13
13
  import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
14
14
  import { parse_routes, parse_server_route } from './parse.js';
15
15
  import * as storage from './session-storage.js';
16
+ import { blur_active_element, is_resetting_focus, reset_focus } from './focus.js';
17
+ import { disable_scroll_handling, reset_scroll_and_focus } from './scroll.js';
16
18
  import {
17
19
  find_anchor,
18
20
  resolve_url,
@@ -47,7 +49,8 @@ import {
47
49
  TRAILING_SLASH_PARAM,
48
50
  create_remote_key,
49
51
  validate_depends,
50
- validate_load_response
52
+ validate_load_response,
53
+ fetch_cache_url
51
54
  } from '../shared.js';
52
55
 
53
56
  import { page, updated, notify_version, update_page, set_navigation } from '#app/state/client';
@@ -170,47 +173,6 @@ function set_history_options(index, options) {
170
173
  };
171
174
  }
172
175
 
173
- /** @param {boolean} reset */
174
- function blur_active_element(reset) {
175
- if (
176
- reset &&
177
- document.activeElement instanceof HTMLElement &&
178
- document.activeElement !== document.body
179
- ) {
180
- document.activeElement.blur();
181
- }
182
- }
183
-
184
- /**
185
- * @param {URL} url
186
- * @param {{ x: number; y: number } | null | undefined} scroll
187
- * @param {boolean} reset
188
- * @param {Element | null} active_element
189
- */
190
- function reset_scroll_and_focus(url, scroll, reset, active_element) {
191
- /** @type {Element | null} */
192
- let deep_linked = null;
193
-
194
- if (autoscroll) {
195
- if (scroll) {
196
- scrollTo(scroll.x, scroll.y);
197
- } else if ((deep_linked = get_hash_element(url))) {
198
- deep_linked.scrollIntoView();
199
- } else {
200
- scrollTo(0, 0);
201
- }
202
- }
203
-
204
- const changed_focus =
205
- document.activeElement !== active_element && document.activeElement !== document.body;
206
-
207
- if (reset && !changed_focus) {
208
- reset_focus(url, !deep_linked);
209
- }
210
-
211
- autoscroll = true;
212
- }
213
-
214
176
  /**
215
177
  * @param {number} current_history_index
216
178
  * @param {number} current_navigation_index
@@ -372,7 +334,6 @@ let current = {
372
334
  /** this being true means we SSR'd */
373
335
  let hydrated = false;
374
336
  let started = false;
375
- let autoscroll = true;
376
337
  let updating = false;
377
338
  let is_navigating = false;
378
339
  /** @type {HistoryMetadata | null} */
@@ -1309,10 +1270,7 @@ function resolve_fetch_url(input, init, url) {
1309
1270
  // we must fixup relative urls so they are resolved from the target page
1310
1271
  const resolved = new URL(input instanceof Request ? input.url : input, url);
1311
1272
 
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;
1273
+ const requested = fetch_cache_url(resolved, url);
1316
1274
 
1317
1275
  const promise = started
1318
1276
  ? subsequent_fetch(requested, resolved.href, init)
@@ -2072,6 +2030,9 @@ async function navigate({
2072
2030
  if (navigation_result.type === 'redirect') {
2073
2031
  // whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
2074
2032
  if (redirect_count < 20) {
2033
+ // a preloaded redirect has been consumed; a later hop back to this route must load afresh
2034
+ if (load_cache?.id === intent?.id) discard_load_cache();
2035
+
2075
2036
  await navigate({
2076
2037
  type,
2077
2038
  url: new URL(navigation_result.location, url),
@@ -2113,12 +2074,15 @@ async function navigate({
2113
2074
 
2114
2075
  updating = true;
2115
2076
 
2116
- capture_scroll(previous_history_index);
2117
- capture_snapshot(previous_navigation_index);
2118
- if (replace_state) {
2119
- delete_navigation_snapshot(previous_history_index);
2120
- } else {
2121
- capture_navigation_snapshot(previous_history_index);
2077
+ if (!popped) {
2078
+ // popstate captures the source entry synchronously, before the traversal is resolved
2079
+ capture_scroll(previous_history_index);
2080
+ capture_snapshot(previous_navigation_index);
2081
+ if (replace_state) {
2082
+ delete_navigation_snapshot(previous_history_index);
2083
+ } else {
2084
+ capture_navigation_snapshot(previous_history_index);
2085
+ }
2122
2086
  }
2123
2087
 
2124
2088
  // ensure the url pathname matches the page's trailing slash option
@@ -2617,7 +2581,7 @@ export function disableScrollHandling() {
2617
2581
  }
2618
2582
 
2619
2583
  if (updating || !started) {
2620
- autoscroll = false;
2584
+ disable_scroll_handling();
2621
2585
  }
2622
2586
  }
2623
2587
 
@@ -3374,7 +3338,7 @@ function _start_router() {
3374
3338
  });
3375
3339
 
3376
3340
  addEventListener('popstate', async (event) => {
3377
- if (resetting_focus) return;
3341
+ if (is_resetting_focus()) return;
3378
3342
 
3379
3343
  const history_metadata = get_history_metadata(event.state);
3380
3344
 
@@ -3404,9 +3368,25 @@ function _start_router() {
3404
3368
  (history_metadata.pageUrl === undefined || history_metadata.pageUrl === location.href)) ||
3405
3369
  is_hash_change);
3406
3370
  const shallow_url = history_metadata.pageUrl ? new URL(location.href) : null;
3371
+
3372
+ // the browser has already traversed, so record the source entry and move the
3373
+ // indices before anything async: a navigation that starts while this one
3374
+ // resolves must build on the entry we are actually on
3375
+ const previous_history_index = current_history_index;
3376
+ const previous_navigation_index = current_navigation_index;
3377
+ const previous_reset_index = current_reset_index;
3378
+ capture_scroll(previous_history_index);
3379
+ if (!shallow) capture_snapshot(previous_navigation_index);
3380
+ capture_navigation_snapshot(previous_history_index);
3381
+ current_history_index = history_index;
3382
+ current_navigation_index = navigation_index;
3383
+ current_reset_index = reset_index;
3384
+
3385
+ const token = navigation_token;
3407
3386
  const shallow_intent = shallow_url
3408
3387
  ? await get_navigation_intent(shallow_url, false)
3409
3388
  : undefined;
3389
+ if (navigation_token !== token) return;
3410
3390
  const shallow_target = shallow_url
3411
3391
  ? {
3412
3392
  params: shallow_intent?.params ?? null,
@@ -3430,10 +3410,6 @@ function _start_router() {
3430
3410
 
3431
3411
  update_url(url);
3432
3412
 
3433
- capture_scroll(current_history_index);
3434
- capture_navigation_snapshot(current_history_index);
3435
- current_history_index = history_index;
3436
- current_reset_index = reset_index;
3437
3413
  if (reset && scroll) scrollTo(scroll.x, scroll.y);
3438
3414
  restore_navigation_snapshot(current_history_index, current_registrations());
3439
3415
  return;
@@ -3449,15 +3425,13 @@ function _start_router() {
3449
3425
  delta,
3450
3426
  shallow: shallow_target
3451
3427
  },
3452
- accept: () => {
3453
- current_history_index = history_index;
3454
- current_navigation_index = navigation_index;
3455
- current_reset_index = reset_index;
3456
- },
3457
3428
  block: () => {
3429
+ current_history_index = previous_history_index;
3430
+ current_navigation_index = previous_navigation_index;
3431
+ current_reset_index = previous_reset_index;
3458
3432
  history.go(-delta);
3459
3433
  },
3460
- nav_token: navigation_token,
3434
+ nav_token: token,
3461
3435
  event
3462
3436
  });
3463
3437
  } else {
@@ -3763,109 +3737,6 @@ function deserialize_uses(uses) {
3763
3737
  };
3764
3738
  }
3765
3739
 
3766
- /**
3767
- * This flag is used to avoid client-side navigation when we're only using
3768
- * `location.replace()` to set focus.
3769
- */
3770
- let resetting_focus = false;
3771
-
3772
- /**
3773
- * @param {URL} url
3774
- * @param {boolean} [scroll]
3775
- */
3776
- function reset_focus(url, scroll = true) {
3777
- const autofocus = document.querySelector('[autofocus]');
3778
- if (autofocus) {
3779
- // @ts-ignore
3780
- autofocus.focus();
3781
- } else {
3782
- // Reset page selection and focus
3783
-
3784
- // Mimic the browsers' behaviour and set the sequential focus navigation
3785
- // starting point to the fragment identifier.
3786
- const element = get_hash_element(url);
3787
- if (element) {
3788
- const { x, y } = scroll_state();
3789
-
3790
- // `element.focus()` doesn't work on Safari and Firefox Ubuntu so we need
3791
- // to use this hack with `location.replace()` instead.
3792
- setTimeout(() => {
3793
- const history_state = history.state;
3794
-
3795
- resetting_focus = true;
3796
- location.replace(new URL(`#${element.id}`, location.href));
3797
-
3798
- // Firefox has a bug that sets the history state to `null` so we need to
3799
- // restore it after. See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
3800
- // This is also needed to restore the original hash if we're using hash routing
3801
- history.replaceState(history_state, '', url);
3802
-
3803
- // If scroll management has already happened earlier, we need to restore
3804
- // the scroll position after setting the sequential focus navigation starting point
3805
- if (scroll) scrollTo(x, y);
3806
- resetting_focus = false;
3807
- });
3808
- } else {
3809
- // If the ID doesn't exist, we try to mimic browsers' behaviour as closely
3810
- // as possible by targeting the first scrollable region. Unfortunately, it's
3811
- // not a perfect match — e.g. shift-tabbing won't immediately cycle up from
3812
- // the end of the page on Chromium
3813
- // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
3814
- const root = document.body;
3815
- const tabindex = root.getAttribute('tabindex');
3816
-
3817
- root.tabIndex = -1;
3818
- root.focus({ preventScroll: true, focusVisible: false });
3819
-
3820
- // restore `tabindex` as to prevent `root` from stealing input from elements
3821
- if (tabindex !== null) {
3822
- root.setAttribute('tabindex', tabindex);
3823
- } else {
3824
- root.removeAttribute('tabindex');
3825
- }
3826
- }
3827
-
3828
- // capture current selection, so we can compare the state after
3829
- // snapshot restoration and afterNavigate callbacks have run
3830
- const selection = getSelection();
3831
-
3832
- if (selection && selection.type !== 'None') {
3833
- /** @type {Range[]} */
3834
- const ranges = [];
3835
-
3836
- for (let i = 0; i < selection.rangeCount; i += 1) {
3837
- ranges.push(selection.getRangeAt(i));
3838
- }
3839
-
3840
- setTimeout(() => {
3841
- if (selection.rangeCount !== ranges.length) return;
3842
-
3843
- for (let i = 0; i < selection.rangeCount; i += 1) {
3844
- const a = ranges[i];
3845
- const b = selection.getRangeAt(i);
3846
-
3847
- // we need to do a deep comparison rather than just `a !== b` because
3848
- // Safari behaves differently to other browsers
3849
- if (
3850
- a.commonAncestorContainer !== b.commonAncestorContainer ||
3851
- a.startContainer !== b.startContainer ||
3852
- a.endContainer !== b.endContainer ||
3853
- a.startOffset !== b.startOffset ||
3854
- a.endOffset !== b.endOffset
3855
- ) {
3856
- return;
3857
- }
3858
- }
3859
-
3860
- // if the selection hasn't changed (as a result of an element being (auto)focused,
3861
- // or a programmatic selection, we reset everything as part of the navigation)
3862
- // fixes https://github.com/sveltejs/kit/issues/8439
3863
- selection.removeAllRanges();
3864
- });
3865
- }
3866
- }
3867
- }
3868
-
3869
3740
  /**
3870
3741
  * @template {NavigationType} T
3871
3742
  * @param {import('./types.js').NavigationState} current
@@ -3939,32 +3810,6 @@ function decode_hash(url) {
3939
3810
  return new_url;
3940
3811
  }
3941
3812
 
3942
- /**
3943
- * @param {URL} url
3944
- * @returns {string}
3945
- */
3946
- function get_id(url) {
3947
- let id;
3948
-
3949
- if (app.hash) {
3950
- const [, , second] = url.hash.split('#', 3);
3951
- id = second ?? '';
3952
- } else {
3953
- id = url.hash.slice(1);
3954
- }
3955
-
3956
- return decodeURIComponent(id);
3957
- }
3958
-
3959
- /**
3960
- * @param {URL} url
3961
- * @returns {Element | null}
3962
- */
3963
- function get_hash_element(url) {
3964
- const id = get_id(url);
3965
- return id ? document.getElementById(id) : null;
3966
- }
3967
-
3968
3813
  if (DEV) {
3969
3814
  // Nasty hack to silence harmless warnings the user can do nothing about
3970
3815
  const console_warn = console.warn;
@@ -1,6 +1,7 @@
1
1
  import { DEV } from 'esm-env';
2
2
  import { hash_request } from '../../utils/hash.js';
3
3
  import { base64_decode } from '../utils.js';
4
+ import { fetch_cache_url } from '../shared.js';
4
5
 
5
6
  let loading = 0;
6
7
 
@@ -152,15 +153,14 @@ export function dev_fetch(resource, opts) {
152
153
  }
153
154
 
154
155
  /**
155
- * Mirror the url normalization in `resolve_fetch_url`, so that non-GET requests
156
- * evict the cache entry regardless of how the url is spelled
156
+ * Non-GET requests must evict under the stored key, however the url is spelled
157
157
  * @param {RequestInfo | URL} input
158
158
  */
159
159
  function requested_url(input) {
160
- const resolved = new URL(input instanceof Request ? input.url : input, location.href);
161
- return resolved.origin === location.origin
162
- ? resolved.href.slice(location.origin.length)
163
- : resolved.href;
160
+ return fetch_cache_url(
161
+ new URL(input instanceof Request ? input.url : input, location.href),
162
+ location
163
+ );
164
164
  }
165
165
 
166
166
  /**
@@ -0,0 +1,120 @@
1
+ import { hash_routing } from '$app/paths/internal/client';
2
+ import { get_hash_element, scroll_state } from './utils.js';
3
+
4
+ /**
5
+ * This flag is used to avoid client-side navigation when we're only using
6
+ * `location.replace()` to set focus.
7
+ */
8
+ let resetting_focus = false;
9
+
10
+ export function is_resetting_focus() {
11
+ return resetting_focus;
12
+ }
13
+
14
+ /** @param {boolean} reset */
15
+ export function blur_active_element(reset) {
16
+ if (
17
+ reset &&
18
+ document.activeElement instanceof HTMLElement &&
19
+ document.activeElement !== document.body
20
+ ) {
21
+ document.activeElement.blur();
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @param {URL} url
27
+ * @param {boolean} [scroll]
28
+ */
29
+ export function reset_focus(url, scroll = true) {
30
+ const autofocus = document.querySelector('[autofocus]');
31
+ if (autofocus) {
32
+ // @ts-ignore
33
+ autofocus.focus();
34
+ } else {
35
+ // Reset page selection and focus
36
+
37
+ // Mimic the browsers' behaviour and set the sequential focus navigation
38
+ // starting point to the fragment identifier.
39
+ const element = get_hash_element(url, hash_routing);
40
+ if (element) {
41
+ const { x, y } = scroll_state();
42
+
43
+ // `element.focus()` doesn't work on Safari and Firefox Ubuntu so we need
44
+ // to use this hack with `location.replace()` instead.
45
+ setTimeout(() => {
46
+ const history_state = history.state;
47
+
48
+ resetting_focus = true;
49
+ location.replace(new URL(`#${element.id}`, location.href));
50
+
51
+ // Firefox has a bug that sets the history state to `null` so we need to
52
+ // restore it after. See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
53
+ // This is also needed to restore the original hash if we're using hash routing
54
+ history.replaceState(history_state, '', url);
55
+
56
+ // If scroll management has already happened earlier, we need to restore
57
+ // the scroll position after setting the sequential focus navigation starting point
58
+ if (scroll) scrollTo(x, y);
59
+ resetting_focus = false;
60
+ });
61
+ } else {
62
+ // If the ID doesn't exist, we try to mimic browsers' behaviour as closely
63
+ // as possible by targeting the first scrollable region. Unfortunately, it's
64
+ // not a perfect match — e.g. shift-tabbing won't immediately cycle up from
65
+ // the end of the page on Chromium
66
+ // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
67
+ const root = document.body;
68
+ const tabindex = root.getAttribute('tabindex');
69
+
70
+ root.tabIndex = -1;
71
+ root.focus({ preventScroll: true, focusVisible: false });
72
+
73
+ // restore `tabindex` as to prevent `root` from stealing input from elements
74
+ if (tabindex !== null) {
75
+ root.setAttribute('tabindex', tabindex);
76
+ } else {
77
+ root.removeAttribute('tabindex');
78
+ }
79
+ }
80
+
81
+ // capture current selection, so we can compare the state after
82
+ // snapshot restoration and afterNavigate callbacks have run
83
+ const selection = getSelection();
84
+
85
+ if (selection && selection.type !== 'None') {
86
+ /** @type {Range[]} */
87
+ const ranges = [];
88
+
89
+ for (let i = 0; i < selection.rangeCount; i += 1) {
90
+ ranges.push(selection.getRangeAt(i));
91
+ }
92
+
93
+ setTimeout(() => {
94
+ if (selection.rangeCount !== ranges.length) return;
95
+
96
+ for (let i = 0; i < selection.rangeCount; i += 1) {
97
+ const a = ranges[i];
98
+ const b = selection.getRangeAt(i);
99
+
100
+ // we need to do a deep comparison rather than just `a !== b` because
101
+ // Safari behaves differently to other browsers
102
+ if (
103
+ a.commonAncestorContainer !== b.commonAncestorContainer ||
104
+ a.startContainer !== b.startContainer ||
105
+ a.endContainer !== b.endContainer ||
106
+ a.startOffset !== b.startOffset ||
107
+ a.endOffset !== b.endOffset
108
+ ) {
109
+ return;
110
+ }
111
+ }
112
+
113
+ // if the selection hasn't changed (as a result of an element being (auto)focused,
114
+ // or a programmatic selection, we reset everything as part of the navigation)
115
+ // fixes https://github.com/sveltejs/kit/issues/8439
116
+ selection.removeAllRanges();
117
+ });
118
+ }
119
+ }
120
+ }
@@ -1,7 +1,12 @@
1
1
  /** @import { RemoteCommand, RemoteQueryUpdate } from '$app/server' */
2
2
  import { app_dir, base } from '#app/paths';
3
3
  import { stringify_command_arg } from '../../shared.js';
4
- import { get_remote_request_headers, categorize_updates, remote_request } from './shared.svelte.js';
4
+ import {
5
+ get_remote_request_headers,
6
+ categorize_updates,
7
+ remote_request,
8
+ fail_unhandled_refreshes
9
+ } from './shared.svelte.js';
5
10
 
6
11
  /**
7
12
  * Client-version of the `command` function from `$app/server`.
@@ -45,14 +50,18 @@ export function command(id) {
45
50
  throw updates_error;
46
51
  }
47
52
 
48
- const response = await remote_request(`${base}/${app_dir}/remote/${id}`, {
49
- method: 'POST',
50
- body: JSON.stringify({
51
- payload: await stringify_command_arg(arg),
52
- refreshes: Array.from(refreshes ?? [])
53
- }),
54
- headers
55
- });
53
+ const response = await remote_request(
54
+ `${base}/${app_dir}/remote/${id}`,
55
+ {
56
+ method: 'POST',
57
+ body: JSON.stringify({
58
+ payload: await stringify_command_arg(arg),
59
+ refreshes: Array.from(refreshes ?? [])
60
+ }),
61
+ headers
62
+ },
63
+ refreshes
64
+ );
56
65
 
57
66
  if (response.redirect) {
58
67
  throw new Error(
@@ -60,6 +69,8 @@ export function command(id) {
60
69
  );
61
70
  }
62
71
 
72
+ fail_unhandled_refreshes(refreshes);
73
+
63
74
  return response._;
64
75
  } finally {
65
76
  overrides?.forEach((fn) => fn());
@@ -13,7 +13,7 @@ import {
13
13
  } from '../client.js';
14
14
  import { page } from '#app/state/client';
15
15
  import { tick } from 'svelte';
16
- import { categorize_updates, remote_request } from './shared.svelte.js';
16
+ import { categorize_updates, fail_unhandled_refreshes, remote_request } from './shared.svelte.js';
17
17
  import { createAttachmentKey } from 'svelte/attachments';
18
18
  import {
19
19
  convert_formdata,
@@ -254,7 +254,8 @@ export function form(id) {
254
254
  'x-sveltekit-search': location.search
255
255
  },
256
256
  body: blob
257
- }
257
+ },
258
+ refreshes
258
259
  );
259
260
 
260
261
  ({ issues: raw_issues = [], result } = response._ ?? {});
@@ -274,6 +275,7 @@ export function form(id) {
274
275
  const succeeded = raw_issues.length === 0;
275
276
 
276
277
  if (succeeded) {
278
+ fail_unhandled_refreshes(refreshes);
277
279
  if (should_refresh) {
278
280
  await refreshAll();
279
281
  }
@@ -446,7 +448,7 @@ export function form(id) {
446
448
  set_nested_value(input, previous_submitter, undefined);
447
449
  }
448
450
 
449
- if (event.submitter) {
451
+ if (event.submitter && /** @type {HTMLInputElement} */ (event.submitter).type !== 'image') {
450
452
  const name = event.submitter.getAttribute('name');
451
453
 
452
454
  /** @type {null | ReturnType<typeof parse_form_key>} */
@@ -535,7 +537,7 @@ export function form(id) {
535
537
  }
536
538
  }
537
539
 
538
- set_nested_value(input, field, value);
540
+ set_nested_value(input, field, is_file ? value : coerce_form_value(field.type, value));
539
541
  } else if (is_file) {
540
542
  if (DEV && element.multiple) {
541
543
  throw new Error(
@@ -554,7 +556,10 @@ export function form(id) {
554
556
  set_nested_value(
555
557
  input,
556
558
  field,
557
- element.type === 'checkbox' && !element.checked ? null : element.value
559
+ coerce_form_value(
560
+ field.type,
561
+ element.type === 'checkbox' && !element.checked ? null : element.value
562
+ )
558
563
  );
559
564
  }
560
565
 
@@ -615,7 +620,7 @@ export function form(id) {
615
620
  }
616
621
 
617
622
  const default_submitter = /** @type {HTMLElement | undefined} */ (
618
- element.querySelector('button:not([type]), [type="submit"]')
623
+ element.querySelector('button:not([type]), [type="submit"], [type="image"]')
619
624
  );
620
625
 
621
626
  const form_data = new FormData(element, default_submitter);
@@ -706,7 +711,7 @@ export function form(id) {
706
711
  if (!element) return;
707
712
 
708
713
  const default_submitter = /** @type {HTMLElement | undefined} */ (
709
- element.querySelector('button:not([type]), [type="submit"]')
714
+ element.querySelector('button:not([type]), [type="submit"], [type="image"]')
710
715
  );
711
716
 
712
717
  const form_data = new FormData(element, default_submitter);
@@ -49,7 +49,15 @@ export class Query {
49
49
  this.#overrides.length;
50
50
 
51
51
  return (resolve, reject) => {
52
- const result = p.then(tick).then(() => /** @type {T} */ (this.#current));
52
+ const result = p.then(tick).then(() => {
53
+ if (!this.#ready) {
54
+ throw new HandledHttpError(
55
+ this.#error ?? { status: 500, message: 'Query resolved without a value' }
56
+ );
57
+ }
58
+
59
+ return /** @type {T} */ (this.#current);
60
+ });
53
61
 
54
62
  if (resolve || reject) {
55
63
  return result.then(resolve, reject);
@@ -119,14 +127,12 @@ export class Query {
119
127
 
120
128
  // Untrack this to not trigger mutation validation errors which can occur if you do e.g. $derived({ a: await queryA(), b: await queryB() })
121
129
  untrack(() => {
122
- this.#latest.splice(0, idx).forEach((r) => r(undefined));
130
+ this.#latest.splice(0, idx + 1).forEach((r) => r(undefined));
123
131
  this.#ready = true;
124
132
  this.#loading = false;
125
133
  this.#raw = value;
126
134
  this.#error = undefined;
127
135
  });
128
-
129
- resolve(undefined);
130
136
  })
131
137
  .catch(async (e) => {
132
138
  // TODO: Our behavior here could be better:
@@ -150,6 +156,7 @@ export class Query {
150
156
 
151
157
  untrack(() => {
152
158
  this.#latest.splice(0, idx).forEach((r) => r(undefined));
159
+ this.#latest.shift();
153
160
  this.#error = error;
154
161
  this.#loading = false;
155
162
  });
@@ -234,12 +241,19 @@ export class Query {
234
241
  // SSR record can never shadow the newly-set value
235
242
  delete query_responses[this.#key];
236
243
 
244
+ // a pending request's promise is settled with the value below; replacing it
245
+ // too would make awaiting consumers settle a second time in a new batch
246
+ const in_flight = this.#latest.length > 0;
247
+
237
248
  this.#clear_pending();
238
249
  this.#ready = true;
239
250
  this.#loading = false;
240
251
  this.#error = undefined;
241
252
  this.#raw = value;
242
- this.#promise = Promise.resolve();
253
+
254
+ if (!in_flight) {
255
+ this.#promise = Promise.resolve();
256
+ }
243
257
  }
244
258
 
245
259
  /** @param {HttpError} error */