@sveltejs/kit 3.0.0-next.0 → 3.0.0-next.10

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 (127) hide show
  1. package/package.json +33 -21
  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 +132 -71
  5. package/src/core/config/options.js +294 -244
  6. package/src/core/config/types.d.ts +1 -1
  7. package/src/core/env.js +121 -5
  8. package/src/core/generate_manifest/index.js +12 -15
  9. package/src/core/postbuild/analyse.js +7 -19
  10. package/src/core/postbuild/crawl.js +22 -6
  11. package/src/core/postbuild/prerender.js +42 -23
  12. package/src/core/sync/create_manifest_data/index.js +53 -50
  13. package/src/core/sync/sync.js +0 -2
  14. package/src/core/sync/write_client_manifest.js +8 -15
  15. package/src/core/sync/write_env.js +2 -1
  16. package/src/core/sync/write_non_ambient.js +12 -9
  17. package/src/core/sync/write_server.js +13 -14
  18. package/src/core/sync/write_tsconfig.js +16 -8
  19. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  20. package/src/core/sync/write_types/index.js +28 -24
  21. package/src/core/utils.js +2 -2
  22. package/src/exports/env/index.js +12 -0
  23. package/src/exports/hooks/index.js +3 -9
  24. package/src/exports/hooks/sequence.js +3 -2
  25. package/src/exports/index.js +35 -13
  26. package/src/exports/internal/client.js +5 -0
  27. package/src/exports/internal/env.js +2 -2
  28. package/src/exports/internal/index.js +1 -90
  29. package/src/exports/internal/{event.js → server/event.js} +1 -2
  30. package/src/exports/internal/server/index.js +33 -0
  31. package/src/exports/internal/shared.js +89 -0
  32. package/src/exports/node/index.js +62 -15
  33. package/src/exports/params.js +63 -0
  34. package/src/exports/public.d.ts +323 -170
  35. package/src/exports/url.js +86 -0
  36. package/src/exports/vite/build/build_server.js +53 -59
  37. package/src/exports/vite/build/remote.js +18 -11
  38. package/src/exports/vite/build/utils.js +0 -8
  39. package/src/exports/vite/dev/index.js +42 -44
  40. package/src/exports/vite/index.js +810 -593
  41. package/src/exports/vite/preview/index.js +29 -22
  42. package/src/exports/vite/static_analysis/index.js +2 -4
  43. package/src/exports/vite/static_analysis/types.d.ts +14 -0
  44. package/src/exports/vite/utils.js +21 -36
  45. package/src/runtime/app/env/internal.js +4 -4
  46. package/src/runtime/app/env/types.d.ts +1 -1
  47. package/src/runtime/app/environment/index.js +6 -0
  48. package/src/runtime/app/forms.js +24 -7
  49. package/src/runtime/app/paths/client.js +4 -10
  50. package/src/runtime/app/paths/internal/client.js +4 -2
  51. package/src/runtime/app/paths/internal/server.js +2 -23
  52. package/src/runtime/app/paths/public.d.ts +0 -28
  53. package/src/runtime/app/paths/server.js +9 -5
  54. package/src/runtime/app/server/remote/command.js +0 -3
  55. package/src/runtime/app/server/remote/form.js +27 -15
  56. package/src/runtime/app/server/remote/prerender.js +29 -36
  57. package/src/runtime/app/server/remote/query.js +101 -99
  58. package/src/runtime/app/server/remote/requested.js +22 -14
  59. package/src/runtime/app/server/remote/shared.js +30 -26
  60. package/src/runtime/app/state/client.js +1 -2
  61. package/src/runtime/app/stores.js +13 -76
  62. package/src/runtime/client/bundle.js +1 -1
  63. package/src/runtime/client/client-entry.js +3 -0
  64. package/src/runtime/client/client.js +411 -318
  65. package/src/runtime/client/entry.js +24 -3
  66. package/src/runtime/client/fetcher.js +3 -2
  67. package/src/runtime/client/ndjson.js +6 -33
  68. package/src/runtime/client/payload.js +17 -0
  69. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  70. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  71. package/src/runtime/client/remote-functions/form.svelte.js +183 -112
  72. package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
  73. package/src/runtime/client/remote-functions/query/index.js +7 -14
  74. package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
  75. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  76. package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
  77. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
  78. package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
  79. package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
  80. package/src/runtime/client/sse.js +32 -0
  81. package/src/runtime/client/state.svelte.js +65 -49
  82. package/src/runtime/client/stream.js +38 -0
  83. package/src/runtime/client/types.d.ts +11 -7
  84. package/src/runtime/client/utils.js +0 -96
  85. package/src/runtime/components/root.svelte +66 -0
  86. package/src/runtime/env/dynamic/private.js +7 -0
  87. package/src/runtime/env/dynamic/public.js +7 -0
  88. package/src/runtime/env/static/private.js +6 -0
  89. package/src/runtime/env/static/public.js +6 -0
  90. package/src/runtime/form-utils.js +100 -22
  91. package/src/runtime/server/cookie.js +69 -52
  92. package/src/runtime/server/csrf.js +65 -0
  93. package/src/runtime/server/data/index.js +14 -18
  94. package/src/runtime/server/env_module.js +0 -5
  95. package/src/runtime/server/index.js +2 -2
  96. package/src/runtime/server/page/actions.js +41 -26
  97. package/src/runtime/server/page/index.js +9 -15
  98. package/src/runtime/server/page/load_data.js +1 -1
  99. package/src/runtime/server/page/render.js +112 -187
  100. package/src/runtime/server/page/respond_with_error.js +7 -8
  101. package/src/runtime/server/page/server_routing.js +27 -18
  102. package/src/runtime/server/remote.js +355 -216
  103. package/src/runtime/server/respond.js +97 -61
  104. package/src/runtime/server/utils.js +32 -9
  105. package/src/runtime/shared.js +83 -13
  106. package/src/runtime/telemetry/otel.js +1 -1
  107. package/src/runtime/types.d.ts +8 -0
  108. package/src/types/ambient.d.ts +10 -5
  109. package/src/types/global-private.d.ts +15 -22
  110. package/src/types/internal.d.ts +89 -74
  111. package/src/types/private.d.ts +33 -1
  112. package/src/utils/error.js +24 -4
  113. package/src/utils/import.js +6 -1
  114. package/src/utils/imports.js +83 -0
  115. package/src/utils/mime.js +9 -0
  116. package/src/utils/params.js +66 -0
  117. package/src/utils/routing.js +90 -44
  118. package/src/utils/shared-iterator.js +5 -0
  119. package/src/utils/streaming.js +14 -4
  120. package/src/utils/url.js +20 -2
  121. package/src/version.js +1 -1
  122. package/types/index.d.ts +467 -537
  123. package/types/index.d.ts.map +22 -39
  124. package/src/core/sync/write_root.js +0 -148
  125. package/src/exports/internal/server.js +0 -22
  126. package/src/types/synthetic/$lib.md +0 -5
  127. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -1,13 +1,18 @@
1
1
  /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
2
2
  /** @import { RemoteFormInput, RemoteForm, RemoteQueryUpdate } from '@sveltejs/kit' */
3
- /** @import { InternalRemoteFormIssue, RemoteFunctionResponse } from 'types' */
3
+ /** @import { InternalRemoteFormIssue } from 'types' */
4
4
  import { app_dir, base } from '$app/paths/internal/client';
5
- import * as devalue from 'devalue';
6
5
  import { DEV } from 'esm-env';
7
6
  import { HttpError } from '@sveltejs/kit/internal';
8
- import { app, query_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js';
7
+ import {
8
+ query_responses,
9
+ _goto,
10
+ set_nearest_error_page,
11
+ handle_error,
12
+ refreshAll
13
+ } from '../client.js';
9
14
  import { tick } from 'svelte';
10
- import { apply_refreshes, categorize_updates, apply_reconnections } from './shared.svelte.js';
15
+ import { categorize_updates, remote_request } from './shared.svelte.js';
11
16
  import { createAttachmentKey } from 'svelte/attachments';
12
17
  import {
13
18
  convert_formdata,
@@ -18,6 +23,8 @@ import {
18
23
  build_path_string,
19
24
  normalize_issue,
20
25
  serialize_binary_form,
26
+ deep_get,
27
+ DELETE_KEY,
21
28
  BINARY_FORM_CONTENT_TYPE
22
29
  } from '../../form-utils.js';
23
30
 
@@ -53,24 +60,34 @@ export function form(id) {
53
60
  /** @type {Map<any, { count: number, instance: RemoteForm<T, U> }>} */
54
61
  const instances = new Map();
55
62
 
63
+ /** @type {StandardSchemaV1 | null} */
64
+ let shared_preflight_schema = null;
65
+
56
66
  /** @param {string | number | boolean} [key] */
57
67
  function create_instance(key) {
58
68
  const action_id_without_key = id;
59
69
  const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : '');
60
70
  const action = '?/remote=' + encodeURIComponent(action_id);
61
71
 
72
+ // the output of a non-enhanced submission that resulted in this page —
73
+ // consume it so the form's state survives hydration (form outputs are
74
+ // always value nodes; the server never serializes them as errors)
75
+ /** @type {{ input?: Record<string, any>, issues?: InternalRemoteFormIssue[], result?: any } | undefined} */
76
+ const initial = query_responses[action_id]?.v;
77
+ delete query_responses[action_id];
78
+
62
79
  /**
63
80
  * @type {Record<string, string | string[] | File | File[]>}
64
81
  */
65
- let input = $state({});
82
+ let input = $state(initial?.input ?? {});
66
83
 
67
84
  /** @type {InternalRemoteFormIssue[]} */
68
- let raw_issues = $state.raw([]);
85
+ let raw_issues = $state.raw(initial?.issues ?? []);
69
86
 
70
87
  const issues = $derived(flatten_issues(raw_issues));
71
88
 
72
89
  /** @type {any} */
73
- let result = $state.raw(query_responses[action_id]);
90
+ let result = $state.raw(initial?.result);
74
91
 
75
92
  /** @type {number} */
76
93
  let pending_count = $state(0);
@@ -83,7 +100,9 @@ export function form(id) {
83
100
  */
84
101
  let enhance_callback = async (instance) => {
85
102
  if (await instance.submit()) {
86
- instance.element.reset();
103
+ await tick();
104
+ // We call reset from the prototype to avoid DOM clobbering
105
+ HTMLFormElement.prototype.reset.call(instance.element);
87
106
  }
88
107
  };
89
108
 
@@ -91,13 +110,22 @@ export function form(id) {
91
110
  let element = null;
92
111
 
93
112
  /** @type {Record<string, boolean>} */
94
- let touched = {};
113
+ let touched = $state({});
114
+
115
+ /** @type {Record<string, boolean>} */
116
+ let dirty = $state({});
95
117
 
96
- let submitted = false;
118
+ /** @type {Record<string, boolean>} */
119
+ let can_validate = {};
120
+
121
+ let submitted = $state(false);
97
122
 
98
123
  /** @type {InternalRemoteFormIssue[] | null} */
99
124
  let unread_issues = null;
100
125
 
126
+ /** @type {string | null} */
127
+ let previous_submitter_name = null;
128
+
101
129
  /**
102
130
  * In dev, warn if there are validation issues going unread
103
131
  */
@@ -181,77 +209,54 @@ export function form(id) {
181
209
  remote_refreshes: Array.from(refreshes ?? [])
182
210
  });
183
211
 
184
- const response = await fetch(`${base}/${app_dir}/remote/${action_id_without_key}`, {
185
- method: 'POST',
186
- headers: {
187
- 'Content-Type': BINARY_FORM_CONTENT_TYPE,
188
- // Forms cannot be called during rendering, so it's save to use location here
189
- 'x-sveltekit-pathname': location.pathname,
190
- 'x-sveltekit-search': location.search
191
- },
192
- body: blob
193
- });
194
-
195
- if (!response.ok) {
196
- // We only end up here in case of a network error or if the server has an internal error
197
- // (which shouldn't happen because we handle errors on the server and always send a 200 response)
198
- throw new Error('Failed to execute remote function');
199
- }
200
-
201
- const form_result = /** @type { RemoteFunctionResponse} */ (await response.json());
202
-
203
- // reset issues in case it's a redirect or error (but issues passed in that case)
204
- raw_issues = [];
205
- result = undefined;
206
-
207
- if (form_result.type === 'result') {
208
- ({ issues: raw_issues = [], result } = devalue.parse(form_result.result, app.decoders));
209
- const succeeded = raw_issues.length === 0;
210
-
211
- if (succeeded) {
212
- if (refreshes === null && !form_result.refreshes && !form_result.reconnects) {
213
- void invalidateAll();
214
- } else {
215
- if (form_result.refreshes) {
216
- apply_refreshes(form_result.refreshes);
217
- }
218
- if (form_result.reconnects) {
219
- apply_reconnections(form_result.reconnects);
220
- }
221
- }
222
- } else {
223
- if (DEV) {
224
- warn_on_missing_issue_reads();
225
- }
212
+ const response = await remote_request(
213
+ `${base}/${app_dir}/remote/${action_id_without_key}`,
214
+ {
215
+ method: 'POST',
216
+ headers: {
217
+ 'Content-Type': BINARY_FORM_CONTENT_TYPE,
218
+ // Forms cannot be called during rendering, so it's save to use location here
219
+ 'x-sveltekit-pathname': location.pathname,
220
+ 'x-sveltekit-search': location.search
221
+ },
222
+ body: blob
226
223
  }
224
+ );
227
225
 
228
- return succeeded;
229
- } else if (form_result.type === 'redirect') {
230
- const stringified_refreshes = form_result.refreshes ?? '';
231
- const stringified_reconnects = form_result.reconnects ?? '';
232
- if (stringified_refreshes) {
233
- apply_refreshes(stringified_refreshes);
234
- }
226
+ ({ issues: raw_issues = [], result } = response._ ?? {});
235
227
 
236
- if (stringified_reconnects) {
237
- apply_reconnections(stringified_reconnects);
238
- }
228
+ // if the developer took control of updates via `.updates(...)` (even with
229
+ // no arguments), or the server performed explicit refreshes, don't invalidateAll
230
+ const should_refresh = refreshes === null && !response.r;
239
231
 
232
+ if (response.redirect) {
240
233
  // Use internal version to allow redirects to external URLs
241
234
  void _goto(
242
- form_result.location,
235
+ response.redirect,
243
236
  {
244
- invalidateAll:
245
- refreshes === null && !stringified_refreshes && !stringified_reconnects
237
+ refreshAll: should_refresh
246
238
  },
247
239
  0
248
240
  );
249
241
  return true;
242
+ }
243
+
244
+ const succeeded = raw_issues.length === 0;
245
+
246
+ if (succeeded) {
247
+ if (should_refresh) {
248
+ void refreshAll();
249
+ }
250
250
  } else {
251
- throw new HttpError(form_result.status ?? 500, form_result.error);
251
+ if (DEV) {
252
+ warn_on_missing_issue_reads();
253
+ }
252
254
  }
255
+
256
+ return succeeded;
253
257
  } catch (e) {
254
258
  result = undefined;
259
+ raw_issues = [];
255
260
  throw e;
256
261
  } finally {
257
262
  overrides?.forEach((fn) => fn());
@@ -319,7 +324,8 @@ export function form(id) {
319
324
  */
320
325
  async function preflight(form_data) {
321
326
  const data = convert(form_data);
322
- const validated = await preflight_schema?.['~standard'].validate(data);
327
+ const schema = preflight_schema ?? shared_preflight_schema;
328
+ const validated = await schema?.['~standard'].validate(data);
323
329
 
324
330
  if (validated?.issues) {
325
331
  raw_issues = merge_with_server_issues(
@@ -363,6 +369,8 @@ export function form(id) {
363
369
  element = form;
364
370
 
365
371
  touched = {};
372
+ dirty = {};
373
+ can_validate = {};
366
374
 
367
375
  /** @param {SubmitEvent} event */
368
376
  const handle_submit = async (event) => {
@@ -396,6 +404,29 @@ export function form(id) {
396
404
 
397
405
  const form_data = new FormData(form, event.submitter);
398
406
 
407
+ if (
408
+ previous_submitter_name !== null &&
409
+ !Array.from(form_data.keys()).map(strip_prefix).includes(previous_submitter_name)
410
+ ) {
411
+ // Strip any `n:`/`b:` type prefix before clearing, otherwise
412
+ // `set_nested_value` would coerce `undefined` to `NaN`/`false`
413
+ // instead of clearing the previously-submitted value.
414
+ set_nested_value(input, previous_submitter_name, undefined);
415
+ }
416
+
417
+ if (event.submitter) {
418
+ const name = event.submitter.getAttribute('name');
419
+ const value = /** @type {any} */ (event.submitter).value;
420
+
421
+ if (name !== null && value !== undefined) {
422
+ set_nested_value(input, name, value);
423
+ }
424
+
425
+ previous_submitter_name = strip_prefix(name);
426
+ } else {
427
+ previous_submitter_name = null;
428
+ }
429
+
399
430
  if (DEV) {
400
431
  validate_form_data(form_data, clone(form).enctype);
401
432
  }
@@ -413,9 +444,14 @@ export function form(id) {
413
444
  await enhance_callback(create_enhance_callback_instance(form, form_data));
414
445
  } catch (e) {
415
446
  const error =
416
- e instanceof HttpError ? e.body : { message: /** @type {any} */ (e).message };
417
- const status = e instanceof HttpError ? e.status : 500;
418
- void set_nearest_error_page(error, status);
447
+ e instanceof HttpError
448
+ ? e.body
449
+ : await handle_error(e, {
450
+ params: {},
451
+ route: { id: null },
452
+ url: new URL(location.href)
453
+ });
454
+ void set_nearest_error_page(error);
419
455
  } finally {
420
456
  pending_count--;
421
457
  }
@@ -435,8 +471,6 @@ export function form(id) {
435
471
 
436
472
  const is_file = element.type === 'file';
437
473
 
438
- touched[name] = true;
439
-
440
474
  if (is_array) {
441
475
  let value;
442
476
 
@@ -481,14 +515,7 @@ export function form(id) {
481
515
  if (file) {
482
516
  set_nested_value(input, name, file);
483
517
  } else {
484
- // Remove the property by setting to undefined and clean up
485
- const path_parts = name.split(/\.|\[|\]/).filter(Boolean);
486
- let current = /** @type {any} */ (input);
487
- for (let i = 0; i < path_parts.length - 1; i++) {
488
- if (current[path_parts[i]] == null) return;
489
- current = current[path_parts[i]];
490
- }
491
- delete current[path_parts[path_parts.length - 1]];
518
+ set_nested_value(input, name, DELETE_KEY);
492
519
  }
493
520
  } else {
494
521
  set_nested_value(
@@ -498,9 +525,9 @@ export function form(id) {
498
525
  );
499
526
  }
500
527
 
501
- name = name.replace(/^[nb]:/, '');
528
+ name = strip_prefix(name);
502
529
 
503
- touched[name] = true;
530
+ dirty[name] = true;
504
531
  };
505
532
 
506
533
  const handle_reset = async () => {
@@ -509,18 +536,38 @@ export function form(id) {
509
536
  await tick();
510
537
 
511
538
  input = convert_formdata(new FormData(form));
539
+ raw_issues = [];
540
+ touched = {};
541
+ dirty = {};
542
+ can_validate = {};
543
+ submitted = false;
544
+ };
545
+
546
+ /** @param {Event} e */
547
+ const handle_focusout = (e) => {
548
+ let name = /** @type {HTMLInputElement} */ (e.target).name;
549
+ if (!name) return;
550
+
551
+ name = strip_prefix(name).replace(/\[\]$/, '');
552
+
553
+ touched[name] = true;
554
+
555
+ if (Object.hasOwn(dirty, name)) {
556
+ can_validate[name] = true;
557
+ }
512
558
  };
513
559
 
514
560
  form.addEventListener('submit', handle_submit);
515
561
  form.addEventListener('input', handle_input);
562
+ form.addEventListener('focusout', handle_focusout);
516
563
  form.addEventListener('reset', handle_reset);
517
564
 
518
565
  return () => {
519
566
  form.removeEventListener('submit', handle_submit);
520
567
  form.removeEventListener('input', handle_input);
568
+ form.removeEventListener('focusout', handle_focusout);
521
569
  form.removeEventListener('reset', handle_reset);
522
570
  element = null;
523
- preflight_schema = undefined;
524
571
  };
525
572
  };
526
573
 
@@ -551,9 +598,10 @@ export function form(id) {
551
598
 
552
599
  const submission = submit(form_data, true);
553
600
 
554
- void submission.finally(() => {
601
+ const decrement = () => {
555
602
  pending_count--;
556
- });
603
+ };
604
+ void submission.then(decrement, decrement);
557
605
 
558
606
  return submission;
559
607
  }
@@ -566,11 +614,16 @@ export function form(id) {
566
614
  (path, value) => {
567
615
  if (path.length === 0) {
568
616
  input = value;
569
- } else {
617
+ } else if (value !== deep_get(input, path)) {
570
618
  deep_set(input, path.map(String), value);
571
619
 
572
620
  const key = build_path_string(path);
573
- touched[key] = true;
621
+
622
+ if (element) {
623
+ touched[key] = true;
624
+ dirty[key] = true;
625
+ can_validate[key] = true;
626
+ }
574
627
  }
575
628
  },
576
629
  (path, all) => {
@@ -584,7 +637,10 @@ export function form(id) {
584
637
  }
585
638
 
586
639
  return issues;
587
- }
640
+ },
641
+ () => touched,
642
+ () => dirty,
643
+ []
588
644
  )
589
645
  },
590
646
  result: {
@@ -593,16 +649,24 @@ export function form(id) {
593
649
  pending: {
594
650
  get: () => pending_count
595
651
  },
652
+ submitted: {
653
+ get: () => submitted
654
+ },
596
655
  preflight: {
597
656
  /** @type {RemoteForm<T, U>['preflight']} */
598
657
  value: (schema) => {
599
658
  preflight_schema = schema;
659
+
660
+ if (key === undefined) {
661
+ shared_preflight_schema = schema;
662
+ }
663
+
600
664
  return instance;
601
665
  }
602
666
  },
603
667
  validate: {
604
668
  /** @type {RemoteForm<any, any>['validate']} */
605
- value: async ({ includeUntouched = false, preflightOnly = false } = {}) => {
669
+ value: async ({ all = false, preflightOnly = false } = {}) => {
606
670
  if (!element) return;
607
671
 
608
672
  const id = ++validate_id;
@@ -620,8 +684,8 @@ export function form(id) {
620
684
  let array = [];
621
685
 
622
686
  const data = convert(form_data);
623
-
624
- const validated = await preflight_schema?.['~standard'].validate(data);
687
+ const schema = preflight_schema ?? shared_preflight_schema;
688
+ const validated = await schema?.['~standard'].validate(data);
625
689
 
626
690
  if (validate_id !== id) {
627
691
  return;
@@ -630,34 +694,31 @@ export function form(id) {
630
694
  if (validated?.issues) {
631
695
  array = validated.issues.map((issue) => normalize_issue(issue, false));
632
696
  } else if (!preflightOnly) {
633
- const response = await fetch(`${base}/${app_dir}/remote/${action_id_without_key}`, {
634
- method: 'POST',
635
- headers: {
636
- 'Content-Type': BINARY_FORM_CONTENT_TYPE,
637
- // Validation should not be and will not be called during rendering, so it's save to use location here
638
- 'x-sveltekit-pathname': location.pathname,
639
- 'x-sveltekit-search': location.search
640
- },
641
- body: serialize_binary_form(data, {
642
- validate_only: true
643
- }).blob
644
- });
645
-
646
- const result = await response.json();
697
+ const result = await remote_request(
698
+ `${base}/${app_dir}/remote/${action_id_without_key}`,
699
+ {
700
+ method: 'POST',
701
+ headers: {
702
+ 'Content-Type': BINARY_FORM_CONTENT_TYPE,
703
+ // Validation should not be and will not be called during rendering, so it's save to use location here
704
+ 'x-sveltekit-pathname': location.pathname,
705
+ 'x-sveltekit-search': location.search
706
+ },
707
+ body: serialize_binary_form(data, {
708
+ validate_only: true
709
+ }).blob
710
+ }
711
+ );
647
712
 
648
713
  if (validate_id !== id) {
649
714
  return;
650
715
  }
651
716
 
652
- if (result.type === 'result') {
653
- array = /** @type {InternalRemoteFormIssue[]} */ (
654
- devalue.parse(result.result, app.decoders)
655
- );
656
- }
717
+ array = /** @type {InternalRemoteFormIssue[]} */ (result._);
657
718
  }
658
719
 
659
- if (!includeUntouched && !submitted) {
660
- array = array.filter((issue) => touched[issue.name]);
720
+ if (!all && !submitted) {
721
+ array = array.filter((issue) => can_validate[issue.name]);
661
722
  }
662
723
 
663
724
  const is_server_validation = !validated?.issues && !preflightOnly;
@@ -748,3 +809,13 @@ function validate_form_data(form_data, enctype) {
748
809
  }
749
810
  }
750
811
  }
812
+
813
+ /**
814
+ * Remove the `n:` or `b:` prefix from a field name
815
+ * @template {string | null} T
816
+ * @param {T} name
817
+ * @returns {T}
818
+ */
819
+ function strip_prefix(name) {
820
+ return /** @type {T} */ (name && name.replace(/^[nb]:/, ''));
821
+ }
@@ -2,9 +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, prerender_responses } from '../client.js';
6
- import { get_remote_request_headers, remote_request } from './shared.svelte.js';
5
+ import { app, _goto, handle_error, prerender_responses } from '../client.js';
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
+ import { noop } from '../../../utils/functions.js';
9
+ import { HttpError } from '@sveltejs/kit/internal';
8
10
 
9
11
  // Initialize Cache API for prerender functions
10
12
  const CACHE_NAME = __SVELTEKIT_DEV__ ? `sveltekit:${Date.now()}` : `sveltekit:${version}`;
@@ -67,7 +69,7 @@ export function prerender(id) {
67
69
  const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
68
70
 
69
71
  if (Object.hasOwn(prerender_responses, cache_key)) {
70
- const data = prerender_responses[cache_key];
72
+ const data = unwrap_node(prerender_responses[cache_key]);
71
73
 
72
74
  if (prerender_cache) {
73
75
  void put(url, devalue.stringify(data, app.encoders));
@@ -77,6 +79,7 @@ export function prerender(id) {
77
79
  }
78
80
 
79
81
  // Do this here, after await Svelte' reactivity context is gone.
82
+ // TODO we really don't want to be sending these specific headers here?
80
83
  const headers = get_remote_request_headers();
81
84
 
82
85
  // Check the Cache API first
@@ -93,14 +96,22 @@ export function prerender(id) {
93
96
  }
94
97
  }
95
98
 
96
- const encoded = await remote_request(url, headers);
99
+ const result = await remote_request(url, { headers });
100
+
101
+ if (result.redirect) {
102
+ // Use internal version to allow redirects to external URLs
103
+ void _goto(result.redirect, {}, 0);
104
+ return;
105
+ }
106
+
107
+ const data = result._;
97
108
 
98
109
  // For successful prerender requests, save to cache
99
110
  if (prerender_cache) {
100
- void put(url, encoded);
111
+ void put(url, devalue.stringify(data, app.encoders));
101
112
  }
102
113
 
103
- return devalue.parse(encoded, app.decoders);
114
+ return data;
104
115
  });
105
116
 
106
117
  prerender_resources.set(cache_key, new WeakRef(resource));
@@ -138,6 +149,7 @@ class Prerender {
138
149
  /** @type {T | undefined} */
139
150
  #current = $state.raw();
140
151
 
152
+ /** @type {App.Error | undefined} */
141
153
  #error = $state.raw(undefined);
142
154
 
143
155
  /**
@@ -152,12 +164,21 @@ class Prerender {
152
164
  this.#error = undefined;
153
165
  return value;
154
166
  },
155
- (error) => {
167
+ async (e) => {
168
+ const error = await handle_error(e, {
169
+ params: {},
170
+ route: { id: null },
171
+ url: new URL(location.href)
172
+ });
156
173
  this.#loading = false;
157
174
  this.#error = error;
158
- throw error;
175
+ throw new HttpError(error.status, error); // so that transformError doesn't transform it again
159
176
  }
160
177
  );
178
+
179
+ // rejections are surfaced via `.error` for reactive consumers — make sure the
180
+ // stored promise (consumed without `await`) never becomes an unhandled rejection
181
+ this.#promise.catch(noop);
161
182
  }
162
183
 
163
184
  /**
@@ -1,10 +1,8 @@
1
1
  /** @import { RemoteQueryFunction } from '@sveltejs/kit' */
2
2
  import { app_dir, base } from '$app/paths/internal/client';
3
- import { app, query_map, query_responses } from '../../client.js';
3
+ import { _goto, query_map } from '../../client.js';
4
4
  import { get_remote_request_headers, QUERY_FUNCTION_ID, remote_request } from '../shared.svelte.js';
5
- import * as devalue from 'devalue';
6
5
  import { DEV } from 'esm-env';
7
- import { hydratable } from 'svelte';
8
6
  import { QueryProxy } from './proxy.js';
9
7
 
10
8
  /**
@@ -25,20 +23,15 @@ export function query(id) {
25
23
 
26
24
  /** @type {RemoteQueryFunction<any, any>} */
27
25
  const wrapper = (arg) => {
28
- return new QueryProxy(id, arg, async (key, payload) => {
29
- if (Object.hasOwn(query_responses, key)) {
30
- const value = query_responses[key];
31
- delete query_responses[key];
32
- return value;
33
- }
34
-
26
+ return new QueryProxy(id, arg, async (payload) => {
35
27
  const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
36
28
 
37
- const serialized = await hydratable(key, () =>
38
- remote_request(url, get_remote_request_headers())
39
- );
29
+ const result = await remote_request(url, { headers: get_remote_request_headers() });
40
30
 
41
- return devalue.parse(serialized, app.decoders);
31
+ if (result.redirect) {
32
+ // Use internal version to allow redirects to external URLs
33
+ await _goto(result.redirect, {}, 0);
34
+ }
42
35
  });
43
36
  };
44
37