@sveltejs/kit 3.0.0-next.4 → 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 (91) hide show
  1. package/package.json +23 -18
  2. package/src/core/adapt/builder.js +29 -43
  3. package/src/core/adapt/index.js +1 -4
  4. package/src/core/config/index.js +135 -71
  5. package/src/core/config/options.js +291 -244
  6. package/src/core/config/types.d.ts +1 -1
  7. package/src/core/env.js +120 -5
  8. package/src/core/generate_manifest/index.js +12 -15
  9. package/src/core/postbuild/analyse.js +8 -12
  10. package/src/core/postbuild/crawl.js +22 -6
  11. package/src/core/postbuild/prerender.js +40 -23
  12. package/src/core/sync/create_manifest_data/index.js +29 -49
  13. package/src/core/sync/write_client_manifest.js +14 -14
  14. package/src/core/sync/write_non_ambient.js +10 -7
  15. package/src/core/sync/write_root.js +9 -30
  16. package/src/core/sync/write_server.js +13 -11
  17. package/src/core/sync/write_tsconfig.js +2 -4
  18. package/src/core/sync/write_types/index.js +11 -10
  19. package/src/exports/index.js +34 -12
  20. package/src/exports/internal/client.js +5 -0
  21. package/src/exports/internal/env.js +1 -1
  22. package/src/exports/internal/index.js +1 -90
  23. package/src/exports/internal/{event.js → server/event.js} +1 -2
  24. package/src/exports/internal/server/index.js +33 -0
  25. package/src/exports/internal/shared.js +89 -0
  26. package/src/exports/node/index.js +58 -7
  27. package/src/exports/params.js +63 -0
  28. package/src/exports/public.d.ts +299 -145
  29. package/src/exports/url.js +84 -0
  30. package/src/exports/vite/build/build_server.js +6 -1
  31. package/src/exports/vite/dev/index.js +38 -37
  32. package/src/exports/vite/index.js +498 -353
  33. package/src/exports/vite/preview/index.js +16 -8
  34. package/src/exports/vite/utils.js +7 -11
  35. package/src/runtime/app/env/types.d.ts +1 -1
  36. package/src/runtime/app/forms.js +22 -5
  37. package/src/runtime/app/paths/client.js +4 -10
  38. package/src/runtime/app/paths/public.d.ts +0 -28
  39. package/src/runtime/app/paths/server.js +8 -4
  40. package/src/runtime/app/server/remote/form.js +10 -3
  41. package/src/runtime/app/server/remote/query.js +9 -18
  42. package/src/runtime/app/server/remote/requested.js +8 -4
  43. package/src/runtime/app/server/remote/shared.js +5 -7
  44. package/src/runtime/app/state/client.js +1 -2
  45. package/src/runtime/app/stores.js +13 -76
  46. package/src/runtime/client/client.js +205 -167
  47. package/src/runtime/client/fetcher.js +3 -2
  48. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  49. package/src/runtime/client/remote-functions/form.svelte.js +122 -31
  50. package/src/runtime/client/remote-functions/prerender.svelte.js +17 -4
  51. package/src/runtime/client/remote-functions/query/index.js +3 -2
  52. package/src/runtime/client/remote-functions/query/instance.svelte.js +27 -11
  53. package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
  54. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +42 -15
  55. package/src/runtime/client/remote-functions/shared.svelte.js +18 -9
  56. package/src/runtime/client/state.svelte.js +66 -49
  57. package/src/runtime/client/types.d.ts +10 -2
  58. package/src/runtime/client/utils.js +0 -96
  59. package/src/runtime/form-utils.js +99 -18
  60. package/src/runtime/server/cookie.js +22 -33
  61. package/src/runtime/server/csrf.js +65 -0
  62. package/src/runtime/server/data/index.js +7 -7
  63. package/src/runtime/server/env_module.js +0 -5
  64. package/src/runtime/server/index.js +2 -2
  65. package/src/runtime/server/page/actions.js +41 -26
  66. package/src/runtime/server/page/index.js +2 -1
  67. package/src/runtime/server/page/load_data.js +1 -1
  68. package/src/runtime/server/page/render.js +36 -47
  69. package/src/runtime/server/page/respond_with_error.js +7 -8
  70. package/src/runtime/server/page/server_routing.js +13 -9
  71. package/src/runtime/server/remote.js +85 -39
  72. package/src/runtime/server/respond.js +92 -58
  73. package/src/runtime/server/utils.js +7 -7
  74. package/src/runtime/telemetry/otel.js +1 -1
  75. package/src/types/ambient.d.ts +10 -5
  76. package/src/types/global-private.d.ts +4 -4
  77. package/src/types/internal.d.ts +17 -20
  78. package/src/types/private.d.ts +33 -1
  79. package/src/types/synthetic/$lib.md +1 -1
  80. package/src/utils/error.js +12 -4
  81. package/src/utils/mime.js +9 -0
  82. package/src/utils/params.js +66 -0
  83. package/src/utils/routing.js +84 -38
  84. package/src/utils/shared-iterator.js +5 -0
  85. package/src/utils/streaming.js +14 -4
  86. package/src/utils/url.js +20 -2
  87. package/src/version.js +1 -1
  88. package/types/index.d.ts +434 -269
  89. package/types/index.d.ts.map +19 -15
  90. package/src/exports/internal/server.js +0 -22
  91. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -2,7 +2,7 @@
2
2
  /** @import { RemoteQueryUpdate } from '@sveltejs/kit' */
3
3
  /** @import { CacheEntry } from './cache.svelte.js' */
4
4
  import * as devalue from 'devalue';
5
- import { app, goto, live_query_map, query_map, query_responses } from '../client.js';
5
+ import { app, _goto, live_query_map, query_map, query_responses } from '../client.js';
6
6
  import { HttpError, Redirect } from '@sveltejs/kit/internal';
7
7
  import { untrack } from 'svelte';
8
8
  import { create_remote_key, split_remote_key } from '../../shared.js';
@@ -86,7 +86,7 @@ export function pin_while_resolving(cache_map, cache, id, payload, then) {
86
86
  */
87
87
  export function unwrap_node(node) {
88
88
  if (node.e) {
89
- throw new HttpError(node.e[0] ?? 500, node.e[1]);
89
+ throw new HttpError(node.e.status, node.e);
90
90
  }
91
91
 
92
92
  return node.v;
@@ -114,13 +114,22 @@ export async function remote_request(url, init) {
114
114
  const response = await fetch(url, init);
115
115
 
116
116
  if (!response.ok) {
117
- throw new HttpError(500, 'Failed to execute remote function');
117
+ const result = await response.json().catch(() => ({
118
+ type: 'error',
119
+ status: response.status,
120
+ error: response.statusText
121
+ }));
122
+
123
+ throw new HttpError(
124
+ result.error?.status ?? result.status ?? response.status ?? 500,
125
+ result.error
126
+ );
118
127
  }
119
128
 
120
129
  const result = /** @type {RemoteFunctionResponse} */ (await response.json());
121
130
 
122
131
  if (result.type === 'error') {
123
- throw new HttpError(result.status ?? 500, result.error);
132
+ throw new HttpError(result.error.status, result.error);
124
133
  }
125
134
 
126
135
  const data = /** @type {RemoteFunctionData} */ (
@@ -128,15 +137,14 @@ export async function remote_request(url, init) {
128
137
  );
129
138
 
130
139
  /**
131
- *
132
140
  * @param {string} key
133
- * @param {CacheEntry<any> | undefined} entry
141
+ * @param {CacheEntry | undefined} entry
134
142
  * @param {any} result
135
143
  */
136
144
  function refresh(key, entry, result) {
137
145
  if (entry?.resource) {
138
146
  if (result.e) {
139
- entry.resource.fail(new HttpError(result.e[0] ?? 500, result.e[1]));
147
+ entry.resource.fail(new HttpError(result.e.status, result.e));
140
148
  } else {
141
149
  entry.resource.set(result.v);
142
150
  }
@@ -184,12 +192,13 @@ export async function remote_request(url, init) {
184
192
  */
185
193
  export async function handle_side_channel_response(response) {
186
194
  if (response.type === 'redirect') {
187
- await goto(response.location);
195
+ // Use internal version to allow redirects to external URLs
196
+ await _goto(response.location, {}, 0);
188
197
  throw new Redirect(307, response.location);
189
198
  }
190
199
 
191
200
  if (response.type === 'error') {
192
- throw new HttpError(response.status ?? 500, response.error);
201
+ throw new HttpError(response.error.status, response.error);
193
202
  }
194
203
 
195
204
  return response;
@@ -1,54 +1,71 @@
1
- import { onMount } from 'svelte';
2
- import { updated_listener } from './utils.js';
1
+ import { version } from '$app/env';
2
+ import { assets } from '$app/paths/internal/client';
3
+ import { BROWSER, DEV } from 'esm-env';
3
4
 
4
5
  /** @type {import('@sveltejs/kit').Page} */
5
- export let page;
6
-
7
- /** @type {{ current: import('@sveltejs/kit').Navigation | null }} */
8
- export let navigating;
9
-
10
- /** @type {{ current: boolean }} */
11
- export let updated;
12
-
13
- // this is a bootleg way to tell if we're in old svelte or new svelte
14
- const is_legacy =
15
- onMount.toString().includes('$$') || /function \w+\(\) \{\}/.test(onMount.toString());
16
-
17
- const placeholder_url = 'a:';
18
-
19
- if (is_legacy) {
20
- page = {
21
- data: {},
22
- form: null,
23
- error: null,
24
- params: {},
25
- route: { id: null },
26
- state: {},
27
- status: -1,
28
- url: new URL(placeholder_url)
29
- };
30
- navigating = { current: null };
31
- updated = { current: false };
32
- } else {
33
- page = new (class Page {
34
- data = $state.raw({});
35
- form = $state.raw(null);
36
- error = $state.raw(null);
37
- params = $state.raw({});
38
- route = $state.raw({ id: null });
39
- state = $state.raw({});
40
- status = $state.raw(-1);
41
- url = $state.raw(new URL(placeholder_url));
42
- })();
43
-
44
- navigating = new (class Navigating {
45
- current = $state.raw(null);
46
- })();
47
-
48
- updated = new (class Updated {
49
- current = $state.raw(false);
50
- })();
51
- updated_listener.v = () => (updated.current = true);
6
+ export const page = new (class Page {
7
+ data = $state.raw({});
8
+ form = $state.raw(null);
9
+ error = $state.raw(null);
10
+ params = $state.raw({});
11
+ route = $state.raw({ id: null });
12
+ state = $state.raw({});
13
+ status = $state.raw(-1);
14
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
15
+ url = $state.raw(new URL('a:'));
16
+ })();
17
+
18
+ export const navigating = new (class Navigating {
19
+ /** @type {import('@sveltejs/kit').Navigation | null} */
20
+ current = $state.raw(null);
21
+ })();
22
+
23
+ export const updated = new (class Updated {
24
+ current = $state.raw(false);
25
+ // eslint-disable-next-line @typescript-eslint/require-await
26
+ check = async () => false;
27
+ })();
28
+
29
+ if (!DEV && BROWSER) {
30
+ const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
31
+
32
+ /** @type {number} */
33
+ let timeout;
34
+
35
+ /** @type {() => Promise<boolean>} */
36
+ async function check() {
37
+ window.clearTimeout(timeout);
38
+
39
+ if (interval) timeout = window.setTimeout(check, interval);
40
+
41
+ try {
42
+ const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
43
+ headers: {
44
+ 'cache-control': 'no-cache'
45
+ }
46
+ });
47
+
48
+ if (!res.ok) {
49
+ return false;
50
+ }
51
+
52
+ const data = await res.json();
53
+ const new_update = data.version !== version;
54
+
55
+ if (new_update) {
56
+ updated.current = true;
57
+ window.clearTimeout(timeout);
58
+ }
59
+
60
+ return new_update;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ if (interval) timeout = window.setTimeout(check, interval);
67
+
68
+ updated.check = check;
52
69
  }
53
70
 
54
71
  /**
@@ -39,7 +39,7 @@ export interface SvelteKitApp {
39
39
  dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
40
40
 
41
41
  /**
42
- * A map of `[matcherName: string]: (..) => boolean`, which is used to match route parameters.
42
+ * A map of `[matcherName: string]: ParamMatcher`, which is used to match and parse route parameters.
43
43
  *
44
44
  * In case of router.resolution=server, this object is empty, as resolution happens on the server.
45
45
  */
@@ -58,6 +58,12 @@ export interface SvelteKitApp {
58
58
  hash: boolean;
59
59
 
60
60
  root: typeof SvelteComponent;
61
+
62
+ /**
63
+ * Lazily loads the contents of src/error.html, used as a last-resort
64
+ * error page when the root layout's load function throws during client-side rendering.
65
+ */
66
+ get_error_template: () => Promise<(data: { status: number; message: string }) => string>;
61
67
  }
62
68
 
63
69
  export type NavigationIntent = {
@@ -77,6 +83,7 @@ export type NavigationResult = NavigationRedirect | NavigationFinished;
77
83
 
78
84
  export type NavigationRedirect = {
79
85
  type: 'redirect';
86
+ status: number;
80
87
  location: string;
81
88
  };
82
89
 
@@ -119,7 +126,8 @@ export interface NavigationState {
119
126
  }
120
127
 
121
128
  export interface HydrateOptions {
122
- status: number;
129
+ /** Provided in the case of a form action that returns `fail`, but otherwise derived from `error` */
130
+ status?: number;
123
131
  error: App.Error | null;
124
132
  node_ids: number[];
125
133
  params: Record<string, string>;
@@ -1,12 +1,6 @@
1
1
  import { BROWSER, DEV } from 'esm-env';
2
- import { writable } from 'svelte/store';
3
- import { assets } from '$app/paths';
4
- import { version } from '$app/env';
5
- import { noop } from '../../utils/functions.js';
6
2
  import { PRELOAD_PRIORITIES } from './constants.js';
7
3
 
8
- /* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
9
-
10
4
  export const origin = BROWSER ? location.origin : '';
11
5
 
12
6
  /** @param {string | URL} url */
@@ -211,96 +205,6 @@ export function get_router_options(element) {
211
205
  };
212
206
  }
213
207
 
214
- /** @param {any} value */
215
- export function notifiable_store(value) {
216
- const store = writable(value);
217
- let ready = true;
218
-
219
- function notify() {
220
- ready = true;
221
- store.update((val) => val);
222
- }
223
-
224
- /** @param {any} new_value */
225
- function set(new_value) {
226
- ready = false;
227
- store.set(new_value);
228
- }
229
-
230
- /** @param {(value: any) => void} run */
231
- function subscribe(run) {
232
- /** @type {any} */
233
- let old_value;
234
- return store.subscribe((new_value) => {
235
- if (old_value === undefined || (ready && new_value !== old_value)) {
236
- run((old_value = new_value));
237
- }
238
- });
239
- }
240
-
241
- return { notify, set, subscribe };
242
- }
243
-
244
- export const updated_listener = {
245
- v: noop
246
- };
247
-
248
- export function create_updated_store() {
249
- const { set, subscribe } = writable(false);
250
-
251
- if (__SVELTEKIT_DEV__ || !BROWSER) {
252
- return {
253
- subscribe,
254
- // eslint-disable-next-line @typescript-eslint/require-await
255
- check: async () => false
256
- };
257
- }
258
-
259
- const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
260
-
261
- /** @type {NodeJS.Timeout} */
262
- let timeout;
263
-
264
- /** @type {() => Promise<boolean>} */
265
- async function check() {
266
- clearTimeout(timeout);
267
-
268
- if (interval) timeout = setTimeout(check, interval);
269
-
270
- try {
271
- const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
272
- headers: {
273
- 'cache-control': 'no-cache'
274
- }
275
- });
276
-
277
- if (!res.ok) {
278
- return false;
279
- }
280
-
281
- const data = await res.json();
282
- const updated = data.version !== version;
283
-
284
- if (updated) {
285
- set(true);
286
- updated_listener.v();
287
- clearTimeout(timeout);
288
- }
289
-
290
- return updated;
291
- } catch {
292
- return false;
293
- }
294
- }
295
-
296
- if (interval) timeout = setTimeout(check, interval);
297
-
298
- return {
299
- subscribe,
300
- check
301
- };
302
- }
303
-
304
208
  /**
305
209
  * Is external if
306
210
  * - origin different
@@ -4,6 +4,7 @@
4
4
  import { DEV } from 'esm-env';
5
5
  import * as devalue from 'devalue';
6
6
  import { text_encoder } from './utils.js';
7
+ import { noop } from '../utils/functions.js';
7
8
  import { SvelteKitError } from '@sveltejs/kit/internal';
8
9
 
9
10
  const decoder = new TextDecoder();
@@ -26,6 +27,9 @@ export function set_nested_value(object, path_string, value) {
26
27
  deep_set(object, split_path(path_string), value);
27
28
  }
28
29
 
30
+ /** Pass this to set_nested_value to delete the last part of the given path */
31
+ export const DELETE_KEY = {};
32
+
29
33
  /**
30
34
  * Convert `FormData` into a POJO
31
35
  * @param {FormData} data
@@ -41,14 +45,11 @@ export function convert_formdata(data) {
41
45
 
42
46
  if (is_array) key = key.slice(0, -2);
43
47
 
44
- if (values.length > 1 && !is_array) {
45
- throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
46
- }
47
-
48
48
  // an empty `<input type="file">` will submit a non-existent file, bizarrely
49
49
  values = values.filter(
50
50
  (entry) => typeof entry === 'string' || entry.name !== '' || entry.size > 0
51
51
  );
52
+ if (values.length === 0 && !is_array) continue;
52
53
 
53
54
  if (key.startsWith('n:')) {
54
55
  key = key.slice(2);
@@ -58,6 +59,10 @@ export function convert_formdata(data) {
58
59
  values = values.map((v) => v === 'on');
59
60
  }
60
61
 
62
+ if (values.length > 1 && !is_array) {
63
+ throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
64
+ }
65
+
61
66
  set_nested_value(result, key, is_array ? values : values[0]);
62
67
  }
63
68
 
@@ -319,7 +324,7 @@ export async function deserialize_binary_form(request) {
319
324
  const chunk = await get_chunk(chunks.length);
320
325
  has_more = !!chunk;
321
326
  }
322
- })();
327
+ })().catch(noop); // prevent unhandled rejection potentially crashing the process
323
328
 
324
329
  return { data, meta, form_data: null };
325
330
  }
@@ -503,6 +508,10 @@ export function deep_set(object, keys, value) {
503
508
  }
504
509
 
505
510
  if (!exists) {
511
+ if (value === DELETE_KEY) {
512
+ // don't create the nested structure if we want to delete the key anyway
513
+ return;
514
+ }
506
515
  current[key] = is_array ? [] : {};
507
516
  }
508
517
 
@@ -511,7 +520,12 @@ export function deep_set(object, keys, value) {
511
520
 
512
521
  const final_key = keys[keys.length - 1];
513
522
  check_prototype_pollution(final_key);
514
- current[final_key] = value;
523
+
524
+ if (value === DELETE_KEY) {
525
+ delete current[final_key];
526
+ } else {
527
+ current[final_key] = value;
528
+ }
515
529
  }
516
530
 
517
531
  /**
@@ -608,18 +622,53 @@ function get_type_prefix(field_type, is_array, input_value) {
608
622
  return '';
609
623
  }
610
624
 
625
+ /**
626
+ * A deep-clone implementation specifically for form data, where
627
+ * we don't need to worry about cycles and whatnot
628
+ * @param {any} value
629
+ * @returns {any}
630
+ */
631
+ function deep_clone(value) {
632
+ if (value !== null && typeof value === 'object') {
633
+ if (value instanceof Date) {
634
+ return new Date(value.getTime());
635
+ }
636
+
637
+ if (value instanceof File) {
638
+ return value;
639
+ }
640
+
641
+ if (Array.isArray(value)) {
642
+ return value.map(deep_clone);
643
+ }
644
+
645
+ /** @type {Record<string, any>} */
646
+ const clone = {};
647
+ for (const key of Object.keys(value)) {
648
+ clone[key] = deep_clone(value[key]);
649
+ }
650
+
651
+ return clone;
652
+ }
653
+
654
+ return value;
655
+ }
656
+
611
657
  /**
612
658
  * Creates a proxy-based field accessor for form data
613
659
  * @param {any} target - Function or empty POJO
614
- * @param {() => Record<string, any>} get_input - Function to get current input data
615
- * @param {(path: (string | number)[], value: any) => void} set_input - Function to set input data
660
+ * @param {() => Record<string, any>} get - Function to get current input data
661
+ * @param {(path: (string | number)[], value: any) => void} set - Function to set input data
616
662
  * @param {(path?: (string | number)[], all?: boolean) => Record<string, InternalRemoteFormIssue[]>} get_issues - Function to get current issues
663
+ * @param {() => Record<string, boolean>} get_touched - Function to get touched fields
664
+ * @param {() => Record<string, boolean>} get_dirty - Function to get dirty fields
617
665
  * @param {(string | number)[]} path - Current access path
618
666
  * @returns {any} Proxy object with name(), value(), and issues() methods
619
667
  */
620
- export function create_field_proxy(target, get_input, set_input, get_issues, path = []) {
668
+ export function create_field_proxy(target, get, set, get_issues, get_touched, get_dirty, path) {
621
669
  const get_value = () => {
622
- return deep_get(get_input(), path);
670
+ const value = deep_get(get(), path);
671
+ return deep_clone(value);
623
672
  };
624
673
 
625
674
  return new Proxy(target, {
@@ -628,24 +677,26 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
628
677
 
629
678
  // Handle array access like jobs[0]
630
679
  if (/^\d+$/.test(prop)) {
631
- return create_field_proxy({}, get_input, set_input, get_issues, [
680
+ return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, [
632
681
  ...path,
633
682
  parseInt(prop, 10)
634
683
  ]);
635
684
  }
636
685
 
637
686
  const key = build_path_string(path);
687
+ const next = [...path, prop];
638
688
 
639
689
  if (prop === 'set') {
640
690
  const set_func = function (/** @type {any} */ newValue) {
641
- set_input(path, newValue);
691
+ set(path, newValue);
642
692
  return newValue;
643
693
  };
644
- return create_field_proxy(set_func, get_input, set_input, get_issues, [...path, prop]);
694
+
695
+ return create_field_proxy(set_func, get, set, get_issues, get_touched, get_dirty, next);
645
696
  }
646
697
 
647
698
  if (prop === 'value') {
648
- return create_field_proxy(get_value, get_input, set_input, get_issues, [...path, prop]);
699
+ return create_field_proxy(get_value, get, set, get_issues, get_touched, get_dirty, next);
649
700
  }
650
701
 
651
702
  if (prop === 'issues' || prop === 'allIssues') {
@@ -659,15 +710,45 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
659
710
  }));
660
711
  }
661
712
 
662
- return all_issues
713
+ const issues = all_issues
663
714
  ?.filter((issue) => issue.name === key)
664
715
  ?.map((issue) => ({
665
716
  path: issue.path,
666
717
  message: issue.message
667
718
  }));
719
+
720
+ return issues?.length ? issues : undefined;
721
+ };
722
+
723
+ return create_field_proxy(issues_func, get, set, get_issues, get_touched, get_dirty, next);
724
+ }
725
+
726
+ if (prop === 'touched' || prop === 'dirty') {
727
+ const fn = () => {
728
+ const object = prop === 'dirty' ? get_dirty() : get_touched();
729
+
730
+ if (key === '') {
731
+ return Object.keys(object).length > 0;
732
+ }
733
+
734
+ if (Object.hasOwn(object, key)) {
735
+ return true;
736
+ }
737
+
738
+ for (const candidate in object) {
739
+ if (!Object.hasOwn(object, candidate)) continue;
740
+ if (!candidate.startsWith(key)) continue;
741
+
742
+ const next = candidate[key.length];
743
+ if (next === '.' || next === '[') {
744
+ return true;
745
+ }
746
+ }
747
+
748
+ return false;
668
749
  };
669
750
 
670
- return create_field_proxy(issues_func, get_input, set_input, get_issues, [...path, prop]);
751
+ return create_field_proxy(fn, get, set, get_issues, get_touched, get_dirty, next);
671
752
  }
672
753
 
673
754
  if (prop === 'as') {
@@ -833,11 +914,11 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
833
914
  });
834
915
  };
835
916
 
836
- return create_field_proxy(as_func, get_input, set_input, get_issues, [...path, 'as']);
917
+ return create_field_proxy(as_func, get, set, get_issues, get_touched, get_dirty, next);
837
918
  }
838
919
 
839
920
  // Handle property access (nested fields)
840
- return create_field_proxy({}, get_input, set_input, get_issues, [...path, prop]);
921
+ return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, next);
841
922
  }
842
923
  });
843
924
  }