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

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 (84) hide show
  1. package/package.json +10 -10
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +1 -1
  4. package/src/core/config/options.js +43 -35
  5. package/src/core/env.js +25 -11
  6. package/src/core/postbuild/prerender.js +3 -3
  7. package/src/core/sync/sync.js +11 -14
  8. package/src/core/sync/utils.js +21 -1
  9. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +24 -23
  10. package/src/core/sync/write_client_manifest.js +4 -12
  11. package/src/core/sync/write_env.js +6 -4
  12. package/src/core/sync/write_server.js +10 -15
  13. package/src/core/sync/write_tsconfig/index.js +257 -0
  14. package/src/core/sync/write_tsconfig/utils.js +162 -0
  15. package/src/core/sync/write_types/index.js +80 -88
  16. package/src/exports/index.js +23 -12
  17. package/src/exports/internal/server/index.js +3 -1
  18. package/src/exports/internal/shared.js +4 -12
  19. package/src/exports/public.d.ts +87 -62
  20. package/src/exports/vite/build/remote.js +10 -12
  21. package/src/exports/vite/dev/index.js +113 -88
  22. package/src/exports/vite/index.js +452 -237
  23. package/src/exports/vite/module_ids.js +3 -1
  24. package/src/exports/vite/utils.js +1 -10
  25. package/src/runner.js +13 -0
  26. package/src/runtime/app/forms.js +4 -0
  27. package/src/runtime/app/manifest/index.js +1 -0
  28. package/src/runtime/app/paths/client.js +30 -30
  29. package/src/runtime/app/paths/internal/client.js +26 -0
  30. package/src/runtime/app/paths/server.js +22 -9
  31. package/src/runtime/app/paths/types.d.ts +11 -19
  32. package/src/runtime/app/server/remote/command.js +12 -7
  33. package/src/runtime/app/server/remote/form.js +37 -26
  34. package/src/runtime/app/server/remote/prerender.js +22 -11
  35. package/src/runtime/app/server/remote/query.js +5 -4
  36. package/src/runtime/app/server/remote/requested.js +4 -4
  37. package/src/runtime/app/server/remote/shared.js +48 -30
  38. package/src/runtime/app/service-worker/index.js +24 -0
  39. package/src/runtime/app/state/client.js +3 -0
  40. package/src/runtime/app/state/index.js +3 -3
  41. package/src/runtime/app/state/server.js +3 -0
  42. package/src/runtime/client/client.js +627 -311
  43. package/src/runtime/client/constants.js +2 -6
  44. package/src/runtime/client/fetcher.js +27 -17
  45. package/src/runtime/client/remote-functions/form.svelte.js +82 -58
  46. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  47. package/src/runtime/client/remote-functions/query/index.js +2 -2
  48. package/src/runtime/client/remote-functions/query/instance.svelte.js +2 -2
  49. package/src/runtime/client/remote-functions/query-batch.svelte.js +3 -4
  50. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +2 -2
  51. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -10
  52. package/src/runtime/client/remote-functions/shared.svelte.js +15 -11
  53. package/src/runtime/client/state.svelte.js +54 -33
  54. package/src/runtime/client/types.d.ts +1 -2
  55. package/src/runtime/client/utils.js +21 -14
  56. package/src/runtime/components/root.svelte +4 -14
  57. package/src/runtime/form-utils.js +89 -54
  58. package/src/runtime/props.svelte.js +71 -0
  59. package/src/runtime/server/cookie.js +1 -1
  60. package/src/runtime/server/data/index.js +31 -28
  61. package/src/runtime/server/errors.js +1 -1
  62. package/src/runtime/server/fetch.js +4 -6
  63. package/src/runtime/server/page/actions.js +3 -3
  64. package/src/runtime/server/page/csp.js +86 -103
  65. package/src/runtime/server/page/index.js +1 -2
  66. package/src/runtime/server/page/load_data.js +15 -5
  67. package/src/runtime/server/page/render.js +46 -52
  68. package/src/runtime/server/page/respond_with_error.js +1 -3
  69. package/src/runtime/server/remote-functions.js +66 -35
  70. package/src/runtime/server/respond.js +56 -16
  71. package/src/runtime/server/utils.js +10 -0
  72. package/src/types/ambient-private.d.ts +9 -1
  73. package/src/types/ambient.d.ts +23 -28
  74. package/src/types/global-private.d.ts +12 -0
  75. package/src/types/internal.d.ts +15 -10
  76. package/src/types/private.d.ts +3 -12
  77. package/src/utils/url.js +12 -0
  78. package/src/version.js +1 -1
  79. package/types/index.d.ts +172 -155
  80. package/types/index.d.ts.map +5 -1
  81. package/src/core/sync/write_ambient.js +0 -18
  82. package/src/core/sync/write_tsconfig.js +0 -258
  83. package/src/runtime/types.d.ts +0 -8
  84. /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
@@ -9,6 +9,7 @@ export const page = new (class Page {
9
9
  error = $state.raw(null);
10
10
  params = $state.raw({});
11
11
  route = $state.raw({ id: null });
12
+ shallow = $state.raw(null);
12
13
  state = $state.raw({});
13
14
  status = $state.raw(-1);
14
15
  url = $state.raw(new URL('a:'));
@@ -25,51 +26,71 @@ export const updated = new (class Updated {
25
26
  check = async () => false;
26
27
  })();
27
28
 
29
+ /**
30
+ * Internal: mark `updated.current` as `true` if the given version differs.
31
+ * Called from the server response header path. No-op unless version checks
32
+ * are enabled (assigned below). Not exported on the public `updated` object.
33
+ * @type {(new_version: string | null) => void}
34
+ */
35
+ export let notify_version = () => {};
36
+
28
37
  if (!DEV && BROWSER) {
29
38
  const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
30
39
 
31
- /** @type {number} */
40
+ /** @type {number | undefined} */
32
41
  let timeout;
33
42
 
43
+ /** @type {Promise<boolean> | undefined} */
44
+ let checking;
45
+
46
+ if (__SVELTEKIT_APP_VERSION_CHECKS_ENABLED__) {
47
+ /**
48
+ * Mark `updated.current` as `true` if the given version differs from the one
49
+ * the app was hydrated with. Called from the server response header path.
50
+ * Does NOT reset the poll timer — unlike `check()`, this is a passive observation
51
+ * from a single server instance's response, not an explicit version check. The
52
+ * poll timer continues on its original schedule as a backstop. This is important
53
+ * for platforms that implement skew protection, where `x-sveltekit-version`
54
+ * may be out of date — in this case we still need to poll for `version.json`.
55
+ * @param {string | null} new_version
56
+ */
57
+ notify_version = (new_version) => {
58
+ if (new_version && new_version !== version) {
59
+ updated.current = true;
60
+ }
61
+ };
62
+ }
63
+
34
64
  /** @type {() => Promise<boolean>} */
35
- async function check() {
65
+ updated.check = function check() {
36
66
  window.clearTimeout(timeout);
37
67
 
38
- if (interval) timeout = window.setTimeout(check, interval);
68
+ if (updated.current) {
69
+ return Promise.resolve(true);
70
+ }
39
71
 
40
- try {
41
- const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
42
- headers: {
43
- 'cache-control': 'no-cache'
72
+ return (checking ??= (async () => {
73
+ try {
74
+ const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
75
+ headers: {
76
+ 'cache-control': 'no-cache'
77
+ }
78
+ });
79
+
80
+ if (!res.ok) {
81
+ return false;
44
82
  }
45
- });
46
83
 
47
- if (!res.ok) {
84
+ const data = await res.json();
85
+ return (updated.current ||= data.version !== version);
86
+ } catch {
48
87
  return false;
88
+ } finally {
89
+ checking = undefined;
90
+ if (interval && !updated.current) timeout = window.setTimeout(check, interval);
49
91
  }
92
+ })());
93
+ };
50
94
 
51
- const data = await res.json();
52
- const new_update = data.version !== version;
53
-
54
- if (new_update) {
55
- updated.current = true;
56
- window.clearTimeout(timeout);
57
- }
58
-
59
- return new_update;
60
- } catch {
61
- return false;
62
- }
63
- }
64
-
65
- if (interval) timeout = window.setTimeout(check, interval);
66
-
67
- updated.check = check;
68
- }
69
-
70
- /**
71
- * @param {import('@sveltejs/kit').Page} new_page
72
- */
73
- export function update(new_page) {
74
- Object.assign(page, new_page);
95
+ if (interval) timeout = window.setTimeout(updated.check, interval);
75
96
  }
@@ -1,4 +1,3 @@
1
- import { SvelteComponent } from 'svelte';
2
1
  import {
3
2
  ClientHooks,
4
3
  CSRPageNode,
@@ -10,7 +9,7 @@ import {
10
9
  Uses
11
10
  } from 'types';
12
11
  import { Page, ParamMatcher } from '@sveltejs/kit';
13
- import { RenderNode } from '../types.js';
12
+ import { RenderNode } from '../props.svelte.js';
14
13
 
15
14
  export interface SvelteKitApp {
16
15
  /**
@@ -31,10 +31,9 @@ const warned = new WeakSet();
31
31
  const valid_link_options = /** @type {const} */ ({
32
32
  'preload-code': ['', 'false', 'tap', 'hover', 'viewport', 'eager'],
33
33
  'preload-data': ['', 'false', 'tap', 'hover'],
34
- keepfocus: ['', 'true', 'false'],
35
- noscroll: ['', 'true', 'false'],
36
34
  reload: ['', 'true', 'false'],
37
- replacestate: ['', 'true', 'false']
35
+ replacestate: ['', 'true', 'false'],
36
+ reset: ['', 'true', 'false']
38
37
  });
39
38
 
40
39
  /**
@@ -150,12 +149,6 @@ export function get_link_info(a, base, uses_hash_router) {
150
149
  * @param {HTMLFormElement | HTMLAnchorElement | SVGAElement} element
151
150
  */
152
151
  export function get_router_options(element) {
153
- /** @type {ValidLinkOptions<'keepfocus'> | null} */
154
- let keepfocus = null;
155
-
156
- /** @type {ValidLinkOptions<'noscroll'> | null} */
157
- let noscroll = null;
158
-
159
152
  /** @type {ValidLinkOptions<'preload-code'> | null} */
160
153
  let preload_code = null;
161
154
 
@@ -168,16 +161,31 @@ export function get_router_options(element) {
168
161
  /** @type {ValidLinkOptions<'replacestate'> | null} */
169
162
  let replace_state = null;
170
163
 
164
+ /** @type {ValidLinkOptions<'reset'> | null} */
165
+ let reset = null;
166
+
171
167
  /** @type {Element} */
172
168
  let el = element;
173
169
 
174
170
  while (el && el !== document.documentElement) {
171
+ if (DEV) {
172
+ for (const name of ['keepfocus', 'noscroll']) {
173
+ const value = el.getAttribute(`data-sveltekit-${name}`);
174
+ if (value !== null && !warned.has(el)) {
175
+ warned.add(el);
176
+ console.warn(
177
+ `\`data-sveltekit-${name}="true"\` has been replaced with \`data-sveltekit-reset="false"\``
178
+ );
179
+ console.log(el);
180
+ }
181
+ }
182
+ }
183
+
175
184
  if (preload_code === null) preload_code = link_option(el, 'preload-code');
176
185
  if (preload_data === null) preload_data = link_option(el, 'preload-data');
177
- if (keepfocus === null) keepfocus = link_option(el, 'keepfocus');
178
- if (noscroll === null) noscroll = link_option(el, 'noscroll');
179
186
  if (reload === null) reload = link_option(el, 'reload');
180
187
  if (replace_state === null) replace_state = link_option(el, 'replacestate');
188
+ if (reset === null) reset = link_option(el, 'reset');
181
189
 
182
190
  el = /** @type {Element} */ (parent_element(el));
183
191
  }
@@ -198,10 +206,9 @@ export function get_router_options(element) {
198
206
  return {
199
207
  preload_code: levels[preload_code ?? 'false'],
200
208
  preload_data: levels[preload_data ?? 'false'],
201
- keepfocus: get_option_state(keepfocus),
202
- noscroll: get_option_state(noscroll),
203
209
  reload: get_option_state(reload),
204
- replace_state: get_option_state(replace_state)
210
+ replace_state: get_option_state(replace_state),
211
+ reset: get_option_state(reset) ?? true
205
212
  };
206
213
  }
207
214
 
@@ -1,18 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { afterNavigate } from '$app/navigation';
3
- import type { Page } from '@sveltejs/kit';
4
- import type { RenderNode } from '../types.js';
3
+ import type { Props, RenderNode } from '../props.svelte.js';
5
4
 
6
- interface Props {
7
- page: Page;
8
- tree: RenderNode;
9
- components: any[];
10
- resetters: Array<(() => void) | undefined>;
11
- form?: any;
12
- error?: App.Error;
13
- }
14
-
15
- const { page, components, resetters, tree, form, error }: Props = $props();
5
+ const { page, components, onerror, tree, form, error }: Props = $props();
16
6
 
17
7
  let mounted = $state(false);
18
8
  let navigated = $state(false);
@@ -33,7 +23,7 @@
33
23
  {const Error = $derived(n.error)}
34
24
  {const data = $derived(n.data)}
35
25
 
36
- <svelte:boundary onerror={(_, reset) => (resetters[depth] = reset)}>
26
+ <svelte:boundary {onerror}>
37
27
  {#if n.child}
38
28
  <!-- svelte-ignore binding_property_non_reactive -->
39
29
  <Component bind:this={components[depth]} {data} {form} params={page.params}>
@@ -44,7 +34,7 @@
44
34
  <Component bind:this={components[depth]} {data} {form} params={page.params} {error} />
45
35
  {/if}
46
36
 
47
- {#snippet failed(error)}
37
+ {#snippet failed(error: unknown)}
48
38
  <Error {error} />
49
39
  {/snippet}
50
40
  </svelte:boundary>
@@ -10,21 +10,58 @@ import { SvelteKitError } from '@sveltejs/kit/internal';
10
10
  const decoder = new TextDecoder();
11
11
 
12
12
  /**
13
- * Sets a value in a nested object using a path string, mutating the original object
13
+ * Sets a parsed form field value in a nested object, mutating the original object.
14
14
  * @param {Record<string, any>} object
15
- * @param {string} path_string
15
+ * @param {{ name: string; type: 'number' | 'boolean' | null }} field
16
16
  * @param {any} value
17
17
  */
18
- export function set_nested_value(object, path_string, value) {
19
- if (path_string.startsWith('n:')) {
20
- path_string = path_string.slice(2);
21
- value = value === '' ? undefined : parseFloat(value);
22
- } else if (path_string.startsWith('b:')) {
23
- path_string = path_string.slice(2);
24
- value = value === 'on';
18
+ export function set_nested_value(object, field, value) {
19
+ deep_set(object, split_path(field.name), value);
20
+ }
21
+
22
+ /**
23
+ * Separates a form field's path from the metadata encoded in its name.
24
+ * @param {string} form_id
25
+ * @param {string} key
26
+ * @returns {{ name: string; type: 'number' | 'boolean' | null; is_array: boolean }}
27
+ */
28
+ export function parse_form_key(form_id, key) {
29
+ const suffix = '/' + form_id;
30
+ let name = key;
31
+
32
+ if (!name.endsWith(suffix)) {
33
+ throw new Error(`Form contained a field that wasn't created with form.fields.as(...): ${name}`);
25
34
  }
26
35
 
27
- deep_set(object, split_path(path_string), value);
36
+ name = name.slice(0, -suffix.length);
37
+
38
+ /** @type {'number' | 'boolean' | null} */
39
+ let type = null;
40
+
41
+ if (name.startsWith('n:')) {
42
+ name = name.slice(2);
43
+ type = 'number';
44
+ } else if (name.startsWith('b:')) {
45
+ name = name.slice(2);
46
+ type = 'boolean';
47
+ }
48
+
49
+ const is_array = name.endsWith('[]');
50
+ if (is_array) name = name.slice(0, -2);
51
+
52
+ return { name, type, is_array };
53
+ }
54
+
55
+ /**
56
+ * @param {'number' | 'boolean' | null} type
57
+ * @param {any} value
58
+ * @returns {any}
59
+ */
60
+ export function coerce_form_value(type, value) {
61
+ if (Array.isArray(value)) return value.map((value) => coerce_form_value(type, value));
62
+ if (type === 'number') return value === '' ? undefined : parseFloat(value);
63
+ if (type === 'boolean') return value === 'on';
64
+ return value;
28
65
  }
29
66
 
30
67
  /** Pass this to set_nested_value to delete the last part of the given path */
@@ -32,38 +69,36 @@ export const DELETE_KEY = {};
32
69
 
33
70
  /**
34
71
  * Convert `FormData` into a POJO
72
+ * @param {string} form_id
35
73
  * @param {FormData} data
36
74
  */
37
- export function convert_formdata(data) {
75
+ export function convert_formdata(form_id, data) {
38
76
  /** @type {Record<string, any>} */
39
77
  const result = {};
40
78
 
41
- for (let key of data.keys()) {
42
- const is_array = key.endsWith('[]');
79
+ for (const field_name of data.keys()) {
43
80
  /** @type {any[]} */
44
- let values = data.getAll(key);
81
+ const values = data.getAll(field_name);
45
82
 
46
- if (is_array) key = key.slice(0, -2);
83
+ const field = parse_form_key(form_id, field_name);
47
84
 
48
85
  // an empty `<input type="file">` will submit a non-existent file, bizarrely
49
- values = values.filter(
86
+ const entries = values.filter(
50
87
  (entry) => typeof entry === 'string' || entry.name !== '' || entry.size > 0
51
88
  );
52
- if (values.length === 0 && !is_array) continue;
53
-
54
- if (key.startsWith('n:')) {
55
- key = key.slice(2);
56
- values = values.map((v) => (v === '' ? undefined : parseFloat(/** @type {string} */ (v))));
57
- } else if (key.startsWith('b:')) {
58
- key = key.slice(2);
59
- values = values.map((v) => v === 'on');
60
- }
89
+ if (entries.length === 0 && !field.is_array) continue;
61
90
 
62
- if (values.length > 1 && !is_array) {
63
- throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
91
+ if (entries.length > 1 && !field.is_array) {
92
+ throw new Error(
93
+ `Form cannot contain duplicated keys — "${field.name}" has ${entries.length} values`
94
+ );
64
95
  }
65
96
 
66
- set_nested_value(result, key, is_array ? values : values[0]);
97
+ set_nested_value(
98
+ result,
99
+ field,
100
+ coerce_form_value(field.type, field.is_array ? entries : entries[0])
101
+ );
67
102
  }
68
103
 
69
104
  return result;
@@ -139,12 +174,13 @@ export function serialize_binary_form(data, meta) {
139
174
 
140
175
  /**
141
176
  * @param {Request} request
177
+ * @param {string} form_id
142
178
  * @returns {Promise<{ data: Record<string, any>; meta: BinaryFormMeta; form_data: FormData | null }>}
143
179
  */
144
- export async function deserialize_binary_form(request) {
180
+ export async function deserialize_binary_form(request, form_id) {
145
181
  if (request.headers.get('content-type') !== BINARY_FORM_CONTENT_TYPE) {
146
182
  const form_data = await request.formData();
147
- return { data: convert_formdata(form_data), meta: {}, form_data };
183
+ return { data: convert_formdata(form_id, form_data), meta: {}, form_data };
148
184
  }
149
185
  if (!request.body) {
150
186
  throw deserialize_error('no body');
@@ -653,18 +689,21 @@ function deep_clone(value) {
653
689
 
654
690
  /**
655
691
  * Creates a proxy-based field accessor for form data
692
+ * @param {{
693
+ * form_id: string,
694
+ * get: () => Record<string, any>,
695
+ * set: (path: (string | number)[], value: any) => void,
696
+ * get_issues: (path?: (string | number)[], all?: boolean) => Record<string, InternalRemoteFormIssue[]>,
697
+ * get_touched: () => Record<string, boolean>,
698
+ * get_dirty: () => Record<string, boolean>
699
+ * }} context - Form context, including value accessors and form metadata
656
700
  * @param {any} target - Function or empty POJO
657
- * @param {() => Record<string, any>} get - Function to get current input data
658
- * @param {(path: (string | number)[], value: any) => void} set - Function to set input data
659
- * @param {(path?: (string | number)[], all?: boolean) => Record<string, InternalRemoteFormIssue[]>} get_issues - Function to get current issues
660
- * @param {() => Record<string, boolean>} get_touched - Function to get touched fields
661
- * @param {() => Record<string, boolean>} get_dirty - Function to get dirty fields
662
701
  * @param {(string | number)[]} path - Current access path
663
702
  * @returns {any} Proxy object with name(), value(), and issues() methods
664
703
  */
665
- export function create_field_proxy(target, get, set, get_issues, get_touched, get_dirty, path) {
704
+ export function create_field_proxy(context, target = {}, path = []) {
666
705
  const get_value = () => {
667
- const value = deep_get(get(), path);
706
+ const value = deep_get(context.get(), path);
668
707
  return deep_clone(value);
669
708
  };
670
709
 
@@ -674,10 +713,7 @@ export function create_field_proxy(target, get, set, get_issues, get_touched, ge
674
713
 
675
714
  // Handle array access like jobs[0]
676
715
  if (/^\d+$/.test(prop)) {
677
- return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, [
678
- ...path,
679
- parseInt(prop, 10)
680
- ]);
716
+ return create_field_proxy(context, {}, [...path, parseInt(prop, 10)]);
681
717
  }
682
718
 
683
719
  const key = build_path_string(path);
@@ -685,20 +721,19 @@ export function create_field_proxy(target, get, set, get_issues, get_touched, ge
685
721
 
686
722
  if (prop === 'set') {
687
723
  const set_func = function (/** @type {any} */ newValue) {
688
- set(path, newValue);
724
+ context.set(path, newValue);
689
725
  return newValue;
690
726
  };
691
-
692
- return create_field_proxy(set_func, get, set, get_issues, get_touched, get_dirty, next);
727
+ return create_field_proxy(context, set_func, next);
693
728
  }
694
729
 
695
730
  if (prop === 'value') {
696
- return create_field_proxy(get_value, get, set, get_issues, get_touched, get_dirty, next);
731
+ return create_field_proxy(context, get_value, next);
697
732
  }
698
733
 
699
734
  if (prop === 'issues' || prop === 'allIssues') {
700
735
  const issues_func = () => {
701
- const all_issues = get_issues(path, prop === 'allIssues')[key === '' ? '$' : key];
736
+ const all_issues = context.get_issues(path, prop === 'allIssues')[key === '' ? '$' : key];
702
737
 
703
738
  if (prop === 'allIssues') {
704
739
  return all_issues?.map((issue) => ({
@@ -717,12 +752,12 @@ export function create_field_proxy(target, get, set, get_issues, get_touched, ge
717
752
  return issues?.length ? issues : undefined;
718
753
  };
719
754
 
720
- return create_field_proxy(issues_func, get, set, get_issues, get_touched, get_dirty, next);
755
+ return create_field_proxy(context, issues_func, next);
721
756
  }
722
757
 
723
758
  if (prop === 'touched' || prop === 'dirty') {
724
759
  const fn = () => {
725
- const object = prop === 'dirty' ? get_dirty() : get_touched();
760
+ const object = prop === 'dirty' ? context.get_dirty() : context.get_touched();
726
761
 
727
762
  if (key === '') {
728
763
  return Object.keys(object).length > 0;
@@ -745,7 +780,7 @@ export function create_field_proxy(target, get, set, get_issues, get_touched, ge
745
780
  return false;
746
781
  };
747
782
 
748
- return create_field_proxy(fn, get, set, get_issues, get_touched, get_dirty, next);
783
+ return create_field_proxy(context, fn, next);
749
784
  }
750
785
 
751
786
  if (prop === 'as') {
@@ -759,14 +794,14 @@ export function create_field_proxy(target, get, set, get_issues, get_touched, ge
759
794
  type === 'select multiple' ||
760
795
  (type === 'checkbox' && typeof input_value === 'string');
761
796
 
762
- const prefix = get_type_prefix(type, is_array, input_value);
797
+ const type_prefix = get_type_prefix(type, is_array, input_value);
763
798
 
764
799
  // Base properties for all input types
765
800
  /** @type {Record<string, any>} */
766
801
  const base_props = {
767
- name: prefix + key + (is_array ? '[]' : ''),
802
+ name: type_prefix + key + (is_array ? '[]' : '') + '/' + context.form_id,
768
803
  get 'aria-invalid'() {
769
- const issues = get_issues();
804
+ const issues = context.get_issues();
770
805
  return key in issues ? 'true' : undefined;
771
806
  }
772
807
  };
@@ -911,11 +946,11 @@ export function create_field_proxy(target, get, set, get_issues, get_touched, ge
911
946
  });
912
947
  };
913
948
 
914
- return create_field_proxy(as_func, get, set, get_issues, get_touched, get_dirty, next);
949
+ return create_field_proxy(context, as_func, next);
915
950
  }
916
951
 
917
952
  // Handle property access (nested fields)
918
- return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, next);
953
+ return create_field_proxy(context, {}, next);
919
954
  }
920
955
  });
921
956
  }
@@ -0,0 +1,71 @@
1
+ /** @import { Component } from 'svelte'; */
2
+ /** @import { Page } from '@sveltejs/kit'; */
3
+
4
+ import { noop } from '../utils/functions.js';
5
+
6
+ export class Props {
7
+ /** @type {Page} */
8
+ page;
9
+
10
+ /**
11
+ * An array of the `+layout.svelte` and `+page.svelte` component instances
12
+ * that currently live on the page — used for capturing and restoring snapshots.
13
+ * It's updated/manipulated through `bind:this` in `Root.svelte`.
14
+ * @type {Array<Record<string, any>>}
15
+ */
16
+ components = [];
17
+
18
+ /** @type {any} */
19
+ form;
20
+
21
+ /** @type {App.Error | undefined} */
22
+ error;
23
+
24
+ /** @type {RenderNode} */
25
+ tree;
26
+
27
+ /** @type {(error: unknown, reset: () => void) => void} */
28
+ onerror;
29
+
30
+ /**
31
+ * @param {{
32
+ * page: Page;
33
+ * tree: RenderNode;
34
+ * form: any;
35
+ * error: App.Error | undefined;
36
+ * onerror?: (error: unknown, reset: () => void) => void;
37
+ * }} props
38
+ */
39
+ constructor({ page, tree, form, error, onerror = noop }) {
40
+ this.page = page;
41
+ this.tree = tree;
42
+ this.onerror = onerror;
43
+
44
+ this.form = $state.raw(form);
45
+ this.error = $state.raw(error);
46
+ }
47
+ }
48
+
49
+ export class RenderNode {
50
+ /** @type {Component} */
51
+ component;
52
+
53
+ /** @type {Component | undefined} */
54
+ error;
55
+
56
+ /** @type {Record<string, any>} */
57
+ data = $state.raw({});
58
+
59
+ /** @type {RenderNode | undefined} */
60
+ child = $state.raw();
61
+
62
+ /**
63
+ *
64
+ * @param {Component} component
65
+ * @param {Component} error
66
+ */
67
+ constructor(component, error) {
68
+ this.component = component;
69
+ this.error = error;
70
+ }
71
+ }
@@ -73,7 +73,7 @@ export function get_cookies(request, url) {
73
73
  httpOnly: true,
74
74
  path: '/',
75
75
  sameSite: 'lax',
76
- secure: url.hostname === 'localhost' && url.protocol === 'http:' ? false : true
76
+ secure: !__SVELTEKIT_DEV__ && !(url.hostname === 'localhost' && url.protocol === 'http:')
77
77
  };
78
78
 
79
79
  /** @type {import('@sveltejs/kit').Cookies} */
@@ -7,6 +7,7 @@ import { load_server_data } from '../page/load_data.js';
7
7
  import { handle_error_and_jsonify } from '../errors.js';
8
8
  import { normalize_path } from '../../../utils/url.js';
9
9
  import { text_encoder } from '../../utils.js';
10
+ import { with_version_header } from '../utils.js';
10
11
 
11
12
  /**
12
13
  * @param {import('@sveltejs/kit').RequestEvent} event
@@ -31,9 +32,7 @@ export async function render_data(
31
32
  ) {
32
33
  if (!route.page) {
33
34
  // requesting /__data.json should fail for a +server.js
34
- return new Response(undefined, {
35
- status: 404
36
- });
35
+ return with_version_header(new Response(undefined, { status: 404 }));
37
36
  }
38
37
 
39
38
  try {
@@ -123,26 +122,28 @@ export async function render_data(
123
122
  return json_response(data);
124
123
  }
125
124
 
126
- return new Response(
127
- new ReadableStream({
128
- async start(controller) {
129
- controller.enqueue(text_encoder.encode(data));
130
- for await (const chunk of chunks) {
131
- controller.enqueue(text_encoder.encode(chunk));
125
+ return with_version_header(
126
+ new Response(
127
+ new ReadableStream({
128
+ async start(controller) {
129
+ controller.enqueue(text_encoder.encode(data));
130
+ for await (const chunk of chunks) {
131
+ controller.enqueue(text_encoder.encode(chunk));
132
+ }
133
+ controller.close();
134
+ },
135
+
136
+ type: 'bytes'
137
+ }),
138
+ {
139
+ headers: {
140
+ // we use a proprietary content type to prevent buffering.
141
+ // the `text` prefix makes it inspectable
142
+ 'content-type': 'text/sveltekit-data',
143
+ 'cache-control': 'private, no-store'
132
144
  }
133
- controller.close();
134
- },
135
-
136
- type: 'bytes'
137
- }),
138
- {
139
- headers: {
140
- // we use a proprietary content type to prevent buffering.
141
- // the `text` prefix makes it inspectable
142
- 'content-type': 'text/sveltekit-data',
143
- 'cache-control': 'private, no-store'
144
145
  }
145
- }
146
+ )
146
147
  );
147
148
  } catch (e) {
148
149
  const error = normalize_error(e);
@@ -161,13 +162,15 @@ export async function render_data(
161
162
  * @param {number} [status]
162
163
  */
163
164
  function json_response(json, status = 200) {
164
- return text(typeof json === 'string' ? json : JSON.stringify(json), {
165
- status,
166
- headers: {
167
- 'content-type': 'application/json',
168
- 'cache-control': 'private, no-store'
169
- }
170
- });
165
+ return with_version_header(
166
+ text(typeof json === 'string' ? json : JSON.stringify(json), {
167
+ status,
168
+ headers: {
169
+ 'content-type': 'application/json',
170
+ 'cache-control': 'private, no-store'
171
+ }
172
+ })
173
+ );
171
174
  }
172
175
 
173
176
  /**
@@ -17,7 +17,7 @@ export async function handle_fatal_error(event, state, options, error) {
17
17
  const body = await handle_error_and_jsonify(event, state, options, error);
18
18
  const status = body.status;
19
19
 
20
- // ideally we'd use sec-fetch-dest instead, but Safari quelle surprise doesn't support it
20
+ // sec-fetch-dest would be nicer, but non-browser clients and plain HTTP hosts don't send it
21
21
  const type = negotiate(event.request.headers.get('accept') || 'text/html', [
22
22
  'application/json',
23
23
  'text/html'