@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
@@ -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
 
@@ -145,10 +150,7 @@ export async function deserialize_binary_form(request) {
145
150
  throw deserialize_error('no body');
146
151
  }
147
152
 
148
- // TODO: remove this workaround once we upgrade to TS 6.0
149
- const reader = /** @type {ReadableStreamDefaultReader<Uint8Array<ArrayBuffer>>} */ (
150
- request.body.getReader()
151
- );
153
+ const reader = request.body.getReader();
152
154
 
153
155
  /** @type {Array<Promise<Uint8Array<ArrayBuffer> | undefined>>} */
154
156
  const chunks = [];
@@ -319,7 +321,7 @@ export async function deserialize_binary_form(request) {
319
321
  const chunk = await get_chunk(chunks.length);
320
322
  has_more = !!chunk;
321
323
  }
322
- })();
324
+ })().catch(noop); // prevent unhandled rejection potentially crashing the process
323
325
 
324
326
  return { data, meta, form_data: null };
325
327
  }
@@ -503,6 +505,10 @@ export function deep_set(object, keys, value) {
503
505
  }
504
506
 
505
507
  if (!exists) {
508
+ if (value === DELETE_KEY) {
509
+ // don't create the nested structure if we want to delete the key anyway
510
+ return;
511
+ }
506
512
  current[key] = is_array ? [] : {};
507
513
  }
508
514
 
@@ -511,7 +517,12 @@ export function deep_set(object, keys, value) {
511
517
 
512
518
  const final_key = keys[keys.length - 1];
513
519
  check_prototype_pollution(final_key);
514
- current[final_key] = value;
520
+
521
+ if (value === DELETE_KEY) {
522
+ delete current[final_key];
523
+ } else {
524
+ current[final_key] = value;
525
+ }
515
526
  }
516
527
 
517
528
  /**
@@ -608,18 +619,53 @@ function get_type_prefix(field_type, is_array, input_value) {
608
619
  return '';
609
620
  }
610
621
 
622
+ /**
623
+ * A deep-clone implementation specifically for form data, where
624
+ * we don't need to worry about cycles and whatnot
625
+ * @param {any} value
626
+ * @returns {any}
627
+ */
628
+ function deep_clone(value) {
629
+ if (value !== null && typeof value === 'object') {
630
+ if (value instanceof Date) {
631
+ return new Date(value.getTime());
632
+ }
633
+
634
+ if (value instanceof File) {
635
+ return value;
636
+ }
637
+
638
+ if (Array.isArray(value)) {
639
+ return value.map(deep_clone);
640
+ }
641
+
642
+ /** @type {Record<string, any>} */
643
+ const clone = {};
644
+ for (const key of Object.keys(value)) {
645
+ clone[key] = deep_clone(value[key]);
646
+ }
647
+
648
+ return clone;
649
+ }
650
+
651
+ return value;
652
+ }
653
+
611
654
  /**
612
655
  * Creates a proxy-based field accessor for form data
613
656
  * @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
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
616
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
617
662
  * @param {(string | number)[]} path - Current access path
618
663
  * @returns {any} Proxy object with name(), value(), and issues() methods
619
664
  */
620
- export function create_field_proxy(target, get_input, set_input, get_issues, path = []) {
665
+ export function create_field_proxy(target, get, set, get_issues, get_touched, get_dirty, path) {
621
666
  const get_value = () => {
622
- return deep_get(get_input(), path);
667
+ const value = deep_get(get(), path);
668
+ return deep_clone(value);
623
669
  };
624
670
 
625
671
  return new Proxy(target, {
@@ -628,24 +674,26 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
628
674
 
629
675
  // Handle array access like jobs[0]
630
676
  if (/^\d+$/.test(prop)) {
631
- return create_field_proxy({}, get_input, set_input, get_issues, [
677
+ return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, [
632
678
  ...path,
633
679
  parseInt(prop, 10)
634
680
  ]);
635
681
  }
636
682
 
637
683
  const key = build_path_string(path);
684
+ const next = [...path, prop];
638
685
 
639
686
  if (prop === 'set') {
640
687
  const set_func = function (/** @type {any} */ newValue) {
641
- set_input(path, newValue);
688
+ set(path, newValue);
642
689
  return newValue;
643
690
  };
644
- return create_field_proxy(set_func, get_input, set_input, get_issues, [...path, prop]);
691
+
692
+ return create_field_proxy(set_func, get, set, get_issues, get_touched, get_dirty, next);
645
693
  }
646
694
 
647
695
  if (prop === 'value') {
648
- return create_field_proxy(get_value, get_input, set_input, get_issues, [...path, prop]);
696
+ return create_field_proxy(get_value, get, set, get_issues, get_touched, get_dirty, next);
649
697
  }
650
698
 
651
699
  if (prop === 'issues' || prop === 'allIssues') {
@@ -659,15 +707,45 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
659
707
  }));
660
708
  }
661
709
 
662
- return all_issues
710
+ const issues = all_issues
663
711
  ?.filter((issue) => issue.name === key)
664
712
  ?.map((issue) => ({
665
713
  path: issue.path,
666
714
  message: issue.message
667
715
  }));
716
+
717
+ return issues?.length ? issues : undefined;
718
+ };
719
+
720
+ return create_field_proxy(issues_func, get, set, get_issues, get_touched, get_dirty, next);
721
+ }
722
+
723
+ if (prop === 'touched' || prop === 'dirty') {
724
+ const fn = () => {
725
+ const object = prop === 'dirty' ? get_dirty() : get_touched();
726
+
727
+ if (key === '') {
728
+ return Object.keys(object).length > 0;
729
+ }
730
+
731
+ if (Object.hasOwn(object, key)) {
732
+ return true;
733
+ }
734
+
735
+ for (const candidate in object) {
736
+ if (!Object.hasOwn(object, candidate)) continue;
737
+ if (!candidate.startsWith(key)) continue;
738
+
739
+ const next = candidate[key.length];
740
+ if (next === '.' || next === '[') {
741
+ return true;
742
+ }
743
+ }
744
+
745
+ return false;
668
746
  };
669
747
 
670
- return create_field_proxy(issues_func, get_input, set_input, get_issues, [...path, prop]);
748
+ return create_field_proxy(fn, get, set, get_issues, get_touched, get_dirty, next);
671
749
  }
672
750
 
673
751
  if (prop === 'as') {
@@ -833,11 +911,11 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
833
911
  });
834
912
  };
835
913
 
836
- return create_field_proxy(as_func, get_input, set_input, get_issues, [...path, 'as']);
914
+ return create_field_proxy(as_func, get, set, get_issues, get_touched, get_dirty, next);
837
915
  }
838
916
 
839
917
  // Handle property access (nested fields)
840
- return create_field_proxy({}, get_input, set_input, get_issues, [...path, prop]);
918
+ return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, next);
841
919
  }
842
920
  });
843
921
  }
@@ -1,4 +1,4 @@
1
- import { parse, serialize } from 'cookie';
1
+ import { parseCookie, parseSetCookie, stringifySetCookie } from 'cookie';
2
2
  import { DEV } from 'esm-env';
3
3
  import { normalize_path, resolve } from '../../utils/url.js';
4
4
  import { add_data_suffix } from '../pathname.js';
@@ -12,10 +12,11 @@ import { text_encoder } from '../utils.js';
12
12
  const cookie_paths = {};
13
13
 
14
14
  /**
15
- * Cookies that are larger than this size (including the name and other
16
- * attributes) are discarded by browsers
15
+ * Cookies whose name and value combined are larger than this size are
16
+ * discarded by browsers. This is the limit codified for the name/value pair
17
+ * in RFC 6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20#section-5.6-7.5.1
17
18
  */
18
- const MAX_COOKIE_SIZE = 4129;
19
+ const MAX_COOKIE_SIZE = 4096;
19
20
 
20
21
  /**
21
22
  * Generates a unique key for a cookie based on its domain, path, and name in
@@ -39,16 +40,35 @@ function generate_cookie_key(domain, path, name) {
39
40
  export function get_cookies(request, url) {
40
41
  const header = request.headers.get('cookie') ?? '';
41
42
  const initial_cookies = /** @type {Record<string, string>} */ (
42
- parse(header, { decode: (value) => value })
43
+ parseCookie(header, { decode: (value) => value })
43
44
  );
44
45
 
46
+ /** @type {ReturnType<typeof parseCookie> | undefined} */
47
+ let default_cookies;
48
+
49
+ /**
50
+ * The header never changes during the request, so the default-decode parse is cached
51
+ * @param {import('cookie').ParseOptions} [opts]
52
+ */
53
+ function parse_header(opts) {
54
+ return opts?.decode ? parseCookie(header, opts) : (default_cookies ??= parseCookie(header));
55
+ }
56
+
57
+ /** @param {import('./page/types.js').Cookie} cookie */
58
+ function matches_url(cookie) {
59
+ return (
60
+ domain_matches(url.hostname, cookie.options.domain) &&
61
+ path_matches(url.pathname, cookie.options.path)
62
+ );
63
+ }
64
+
45
65
  /** @type {string | undefined} */
46
66
  let normalized_url;
47
67
 
48
68
  /** @type {Map<string, import('./page/types.js').Cookie>} */
49
69
  const new_cookies = new Map();
50
70
 
51
- /** @type {import('cookie').SerializeOptions} */
71
+ /** @type {Omit<import('cookie').SetCookie, 'name' | 'value'>} */
52
72
  const defaults = {
53
73
  httpOnly: true,
54
74
  path: '/',
@@ -63,28 +83,25 @@ export function get_cookies(request, url) {
63
83
  // typescript users. `@type {import('@sveltejs/kit').Cookies}` above is not
64
84
  // sufficient to do so.
65
85
 
66
- /**
67
- * @param {string} name
68
- * @param {import('cookie').ParseOptions} [opts]
69
- */
70
86
  get(name, opts) {
71
87
  // Look for the most specific matching cookie from new_cookies
72
- const best_match = Array.from(new_cookies.values())
73
- .filter((c) => {
74
- return (
75
- c.name === name &&
76
- domain_matches(url.hostname, c.options.domain) &&
77
- path_matches(url.pathname, c.options.path)
78
- );
79
- })
80
- .sort((a, b) => b.options.path.length - a.options.path.length)[0];
88
+ /** @type {import('./page/types.js').Cookie | undefined} */
89
+ let best_match;
90
+ for (const c of new_cookies.values()) {
91
+ if (
92
+ c.name === name &&
93
+ matches_url(c) &&
94
+ (!best_match || c.options.path.length > best_match.options.path.length)
95
+ ) {
96
+ best_match = c;
97
+ }
98
+ }
81
99
 
82
100
  if (best_match) {
83
101
  return best_match.options.maxAge === 0 ? undefined : best_match.value;
84
102
  }
85
103
 
86
- const req_cookies = parse(header, { decode: opts?.decode });
87
- const cookie = req_cookies[name]; // the decoded string or undefined
104
+ const cookie = parse_header(opts)[name]; // the decoded string or undefined
88
105
 
89
106
  // in development, if the cookie was set during this session with `cookies.set`,
90
107
  // but at a different path, warn the user. (ignore cookies from request headers,
@@ -106,20 +123,15 @@ export function get_cookies(request, url) {
106
123
  return cookie;
107
124
  },
108
125
 
109
- /**
110
- * @param {import('cookie').ParseOptions} [opts]
111
- */
112
126
  getAll(opts) {
113
- const cookies = parse(header, { decode: opts?.decode });
127
+ // copy, so the cached parse isn't mutated below
128
+ const cookies = { ...parse_header(opts) };
114
129
 
115
130
  // Group cookies by name and find the most specific one for each name
116
131
  const lookup = new Map();
117
132
 
118
133
  for (const c of new_cookies.values()) {
119
- if (
120
- domain_matches(url.hostname, c.options.domain) &&
121
- path_matches(url.pathname, c.options.path)
122
- ) {
134
+ if (matches_url(c)) {
123
135
  const existing = lookup.get(c.name);
124
136
 
125
137
  // If no existing cookie or this one has a more specific (longer) path, use this one
@@ -131,7 +143,13 @@ export function get_cookies(request, url) {
131
143
 
132
144
  // Add the most specific cookies to the result
133
145
  for (const c of lookup.values()) {
134
- cookies[c.name] = c.value;
146
+ // tombstones (deleted cookies) shadow request-header cookies,
147
+ // mirroring the behavior of `get()`
148
+ if (c.options.maxAge === 0) {
149
+ delete cookies[c.name];
150
+ } else {
151
+ cookies[c.name] = c.value;
152
+ }
135
153
  }
136
154
 
137
155
  return /** @type {Array<{ name: string; value: string }>} */ (
@@ -141,29 +159,17 @@ export function get_cookies(request, url) {
141
159
  );
142
160
  },
143
161
 
144
- /**
145
- * @param {string} name
146
- * @param {string} value
147
- * @param {import('cookie').SerializeOptions} options
148
- */
149
162
  set(name, value, options) {
150
163
  set_internal(name, value, { ...defaults, ...options });
151
164
  },
152
165
 
153
- /**
154
- * @param {string} name
155
- * @param {import('cookie').SerializeOptions} options
156
- */
157
166
  delete(name, options) {
158
167
  cookies.set(name, '', { ...options, maxAge: 0 });
159
168
  },
160
169
 
161
- /**
162
- * @param {string} name
163
- * @param {string} value
164
- * @param {import('cookie').SerializeOptions} options
165
- */
166
- serialize(name, value, options) {
170
+ parse: parseSetCookie,
171
+
172
+ serialize(name, value, { encode, ...options }) {
167
173
  let path = options.path ?? '/';
168
174
 
169
175
  if (!options.domain || options.domain === url.hostname) {
@@ -173,7 +179,7 @@ export function get_cookies(request, url) {
173
179
  path = resolve(normalized_url, path);
174
180
  }
175
181
 
176
- return serialize(name, value, { ...defaults, ...options, path });
182
+ return stringifySetCookie({ name, value, ...defaults, ...options, path }, { encode });
177
183
  }
178
184
  };
179
185
 
@@ -200,7 +206,7 @@ export function get_cookies(request, url) {
200
206
  // explicit header has highest precedence
201
207
  if (header) {
202
208
  const parsed = /** @type {Record<string, string>} */ (
203
- parse(header, { decode: (value) => value })
209
+ parseCookie(header, { decode: (value) => value })
204
210
  );
205
211
  for (const name in parsed) {
206
212
  combined_cookies[name] = parsed[name];
@@ -238,8 +244,12 @@ export function get_cookies(request, url) {
238
244
  new_cookies.set(cookie_key, cookie);
239
245
 
240
246
  if (DEV) {
241
- const serialized = serialize(name, value, cookie.options);
242
- if (text_encoder.encode(serialized).byteLength > MAX_COOKIE_SIZE) {
247
+ const size =
248
+ // only the name/value pair counts towards MAX_COOKIE_SIZE, not the other attributes
249
+ text_encoder.encode(name).byteLength +
250
+ text_encoder.encode((options.encode ?? encodeURIComponent)(value)).byteLength;
251
+
252
+ if (size > MAX_COOKIE_SIZE) {
243
253
  throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
244
254
  }
245
255
 
@@ -296,15 +306,22 @@ export function path_matches(path, constraint) {
296
306
  */
297
307
  export function add_cookies_to_headers(headers, cookies) {
298
308
  for (const new_cookie of cookies) {
299
- const { name, value, options } = new_cookie;
300
- headers.append('set-cookie', serialize(name, value, options));
309
+ const {
310
+ name,
311
+ value,
312
+ options: { encode, ...options }
313
+ } = new_cookie;
314
+ headers.append('set-cookie', stringifySetCookie({ name, value, ...options }, { encode }));
301
315
 
302
316
  // special case — for routes ending with .html, the route data lives in a sibling
303
317
  // `.html__data.json` file rather than a child `/__data.json` file, which means
304
318
  // we need to duplicate the cookie
305
319
  if (options.path.endsWith('.html')) {
306
320
  const path = add_data_suffix(options.path);
307
- headers.append('set-cookie', serialize(name, value, { ...options, path }));
321
+ headers.append(
322
+ 'set-cookie',
323
+ stringifySetCookie({ name, value, ...options, path }, { encode })
324
+ );
308
325
  }
309
326
  }
310
327
  }
@@ -0,0 +1,65 @@
1
+ import { is_form_content_type } from '../../utils/http.js';
2
+
3
+ const mutating_form_methods = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
4
+
5
+ /**
6
+ * The origin SvelteKit treats as "self" when validating the `Origin` header on
7
+ * cross-site requests.
8
+ *
9
+ * By default (`paths.origin` is `undefined`), SvelteKit derives the origin
10
+ * from `request.url` (which is set by the adapter, and ultimately by the
11
+ * platform). When `paths.origin` is configured — for example so that a preview
12
+ * deployment whose URL isn't known at build time, or an app behind a reverse
13
+ * proxy, can declare a canonical origin — that value takes precedence.
14
+ *
15
+ * @param {string | undefined} paths_origin the configured `kit.paths.origin`
16
+ * @param {string} url_origin the origin derived from `request.url`
17
+ * @returns {string}
18
+ */
19
+ export function get_self_origin(paths_origin, url_origin) {
20
+ return paths_origin || url_origin;
21
+ }
22
+
23
+ /**
24
+ * Determines whether a non-remote request should be rejected as a cross-site
25
+ * form submission (CSRF). Used by `respond.js` to gate form `POST`/`PUT`/
26
+ * `PATCH`/`DELETE` requests whose `Origin` header doesn't match the app's
27
+ * self-origin (and isn't in `trusted_origins`).
28
+ *
29
+ * @param {{
30
+ * request: Request;
31
+ * request_origin: string | null;
32
+ * self_origin: string;
33
+ * trusted_origins: string[];
34
+ * }} input
35
+ * @returns {boolean}
36
+ */
37
+ export function is_csrf_forbidden({ request, request_origin, self_origin, trusted_origins }) {
38
+ return (
39
+ (!request.headers.get('content-type') || is_form_content_type(request)) &&
40
+ mutating_form_methods.has(request.method) &&
41
+ request_origin !== self_origin &&
42
+ (!request_origin || !trusted_origins.includes(request_origin))
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Determines whether a remote-function request should be rejected as cross-site.
48
+ *
49
+ * Unlike form submissions, remote functions accept any content type (e.g.
50
+ * `application/json`), so the check is solely on the request method and origin:
51
+ * a non-`GET` request is forbidden when its `Origin` header doesn't match the
52
+ * app's self-origin. Unlike `is_csrf_forbidden`, entries in `trusted_origins`
53
+ * are *not* honoured — remote function endpoints are an implementation detail,
54
+ * not a public API, so cross-origin calls are forbidden regardless.
55
+ *
56
+ * @param {{
57
+ * request: Request;
58
+ * request_origin: string | null;
59
+ * self_origin: string;
60
+ * }} input
61
+ * @returns {boolean}
62
+ */
63
+ export function is_remote_forbidden({ request, request_origin, self_origin }) {
64
+ return request.method !== 'GET' && request_origin !== self_origin;
65
+ }
@@ -1,5 +1,5 @@
1
1
  import { text } from '@sveltejs/kit';
2
- import { HttpError, SvelteKitError, Redirect } from '@sveltejs/kit/internal';
2
+ import { Redirect } from '@sveltejs/kit/internal';
3
3
  import { normalize_error } from '../../../utils/error.js';
4
4
  import { once } from '../../../utils/functions.js';
5
5
  import { server_data_serializer_json } from '../page/data_serializer.js';
@@ -96,31 +96,25 @@ export async function render_data(
96
96
  return fn();
97
97
  });
98
98
 
99
- let length = promises.length;
100
- const nodes = await Promise.all(
101
- promises.map((p, i) =>
102
- p.catch(async (error) => {
99
+ const data_serializer = server_data_serializer_json(event, event_state, options);
100
+ await Promise.all(
101
+ promises.map(async (p, i) => {
102
+ const node = await p.catch(async (error) => {
103
103
  if (error instanceof Redirect) {
104
104
  throw error;
105
105
  }
106
106
 
107
- // Math.min because array isn't guaranteed to resolve in order
108
- length = Math.min(length, i + 1);
107
+ const transformed = await handle_error_and_jsonify(event, event_state, options, error);
109
108
 
110
109
  return /** @type {import('types').ServerErrorNode} */ ({
111
110
  type: 'error',
112
- error: await handle_error_and_jsonify(event, event_state, options, error),
113
- status:
114
- error instanceof HttpError || error instanceof SvelteKitError
115
- ? error.status
116
- : undefined
111
+ error: transformed
117
112
  });
118
- })
119
- )
120
- );
113
+ });
121
114
 
122
- const data_serializer = server_data_serializer_json(event, event_state, options);
123
- for (let i = 0; i < nodes.length; i++) data_serializer.add_node(i, nodes[i]);
115
+ data_serializer.add_node(i, node);
116
+ })
117
+ );
124
118
  const { data, chunks } = data_serializer.get_data();
125
119
 
126
120
  if (!chunks) {
@@ -156,7 +150,8 @@ export async function render_data(
156
150
  if (error instanceof Redirect) {
157
151
  return redirect_json_response(error);
158
152
  } else {
159
- return json_response(await handle_error_and_jsonify(event, event_state, options, error), 500);
153
+ const transformed = await handle_error_and_jsonify(event, event_state, options, error);
154
+ return json_response(transformed, transformed.status);
160
155
  }
161
156
  }
162
157
  }
@@ -182,6 +177,7 @@ export function redirect_json_response(redirect) {
182
177
  return json_response(
183
178
  /** @type {import('types').ServerRedirectNode} */ ({
184
179
  type: 'redirect',
180
+ status: redirect.status,
185
181
  location: redirect.location
186
182
  })
187
183
  );
@@ -16,7 +16,6 @@ let headers;
16
16
  */
17
17
  export function get_public_env(request) {
18
18
  const env = rendered_env;
19
- const script = request.url.endsWith('.script.js');
20
19
 
21
20
  payload ??= devalue.uneval(env);
22
21
  etag ??= `W/${Date.now()}`;
@@ -29,9 +28,5 @@ export function get_public_env(request) {
29
28
  return new Response(undefined, { status: 304, headers });
30
29
  }
31
30
 
32
- if (script) {
33
- return new Response(`globalThis.__sveltekit_sw={env:${payload}}`, { headers });
34
- }
35
-
36
31
  return new Response(`export const env=${payload}`, { headers });
37
32
  }
@@ -27,7 +27,7 @@ export class Server {
27
27
  this.#manifest = manifest;
28
28
 
29
29
  // Since AsyncLocalStorage is not working in webcontainers, we don't reset `sync_store`
30
- // in `src/exports/internal/event.js` and handle only one request at a time.
30
+ // in `src/exports/internal/server/event.js` and handle only one request at a time.
31
31
  if (IN_WEBCONTAINER) {
32
32
  const respond = this.respond.bind(this);
33
33
 
@@ -120,7 +120,7 @@ export class Server {
120
120
  module.handleValidationError ||
121
121
  (({ issues }) => {
122
122
  console.error('Remote function schema validation failed:', issues);
123
- return { message: 'Bad Request' };
123
+ return { message: 'Bad Request', status: 400 };
124
124
  }),
125
125
  reroute: module.reroute || noop,
126
126
  transport: module.transport || {}