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

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 (88) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +3 -1
  4. package/src/core/adapt/builder.js +3 -6
  5. package/src/core/config/index.js +6 -1
  6. package/src/core/config/options.js +54 -104
  7. package/src/core/env.js +25 -11
  8. package/src/core/postbuild/fallback.js +2 -1
  9. package/src/core/postbuild/prerender.js +75 -28
  10. package/src/core/postbuild/queue.js +2 -1
  11. package/src/core/sync/sync.js +11 -14
  12. package/src/core/sync/utils.js +21 -1
  13. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
  14. package/src/core/sync/write_client_manifest.js +4 -12
  15. package/src/core/sync/write_env.js +6 -4
  16. package/src/core/sync/write_server.js +12 -17
  17. package/src/core/sync/write_tsconfig/index.js +245 -0
  18. package/src/core/sync/write_tsconfig/utils.js +162 -0
  19. package/src/core/sync/write_types/index.js +80 -88
  20. package/src/core/utils.js +28 -3
  21. package/src/exports/env/index.js +68 -3
  22. package/src/exports/internal/env.js +6 -3
  23. package/src/exports/internal/server/index.js +3 -1
  24. package/src/exports/params.js +9 -4
  25. package/src/exports/public.d.ts +69 -71
  26. package/src/exports/vite/dev/index.js +112 -56
  27. package/src/exports/vite/index.js +483 -258
  28. package/src/exports/vite/module_ids.js +2 -2
  29. package/src/exports/vite/preview/index.js +3 -2
  30. package/src/exports/vite/utils.js +21 -10
  31. package/src/runner.js +13 -0
  32. package/src/runtime/app/forms.js +4 -0
  33. package/src/runtime/app/manifest/index.js +1 -0
  34. package/src/runtime/app/paths/client.js +30 -30
  35. package/src/runtime/app/paths/internal/client.js +26 -0
  36. package/src/runtime/app/paths/server.js +23 -10
  37. package/src/runtime/app/paths/types.d.ts +11 -19
  38. package/src/runtime/app/server/index.js +1 -1
  39. package/src/runtime/app/server/remote/command.js +12 -7
  40. package/src/runtime/app/server/remote/form.js +29 -26
  41. package/src/runtime/app/server/remote/prerender.js +9 -2
  42. package/src/runtime/app/server/remote/query.js +6 -5
  43. package/src/runtime/app/server/remote/shared.js +48 -30
  44. package/src/runtime/app/service-worker/index.js +24 -0
  45. package/src/runtime/app/state/index.js +1 -1
  46. package/src/runtime/client/client.js +46 -9
  47. package/src/runtime/client/ndjson.js +1 -1
  48. package/src/runtime/client/remote-functions/form.svelte.js +43 -51
  49. package/src/runtime/client/remote-functions/query/index.js +2 -2
  50. package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
  51. package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
  52. package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
  53. package/src/runtime/client/state.svelte.js +54 -21
  54. package/src/runtime/client/stream.js +3 -2
  55. package/src/runtime/form-utils.js +89 -54
  56. package/src/runtime/server/cookie.js +1 -1
  57. package/src/runtime/server/data/index.js +32 -29
  58. package/src/runtime/server/errors.js +135 -0
  59. package/src/runtime/server/fetch.js +1 -1
  60. package/src/runtime/server/index.js +20 -10
  61. package/src/runtime/server/internal.js +25 -0
  62. package/src/runtime/server/page/actions.js +4 -3
  63. package/src/runtime/server/page/data_serializer.js +10 -10
  64. package/src/runtime/server/page/index.js +3 -2
  65. package/src/runtime/server/page/load_data.js +1 -1
  66. package/src/runtime/server/page/render.js +22 -35
  67. package/src/runtime/server/page/respond_with_error.js +6 -5
  68. package/src/runtime/server/{remote.js → remote-functions.js} +67 -36
  69. package/src/runtime/server/respond.js +58 -20
  70. package/src/runtime/server/sourcemaps.js +183 -0
  71. package/src/runtime/server/utils.js +12 -157
  72. package/src/types/ambient-private.d.ts +10 -0
  73. package/src/types/ambient.d.ts +22 -27
  74. package/src/types/global-private.d.ts +12 -5
  75. package/src/types/internal.d.ts +19 -9
  76. package/src/types/private.d.ts +8 -0
  77. package/src/utils/escape.js +9 -25
  78. package/src/utils/features.js +1 -1
  79. package/src/utils/fork.js +7 -2
  80. package/src/utils/page_nodes.js +3 -5
  81. package/src/utils/url.js +12 -0
  82. package/src/version.js +1 -1
  83. package/types/index.d.ts +152 -120
  84. package/types/index.d.ts.map +6 -1
  85. package/src/core/sync/write_ambient.js +0 -18
  86. package/src/core/sync/write_tsconfig.js +0 -258
  87. package/src/runtime/shared-server.js +0 -7
  88. /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
@@ -4,11 +4,12 @@
4
4
  * stream closes.
5
5
  * @param {ReadableStreamDefaultReader<Uint8Array>} reader
6
6
  * @param {string} delimiter
7
+ * @param {TextDecoderOptions} [options]
7
8
  */
8
- export async function* read_stream(reader, delimiter) {
9
+ export async function* read_stream(reader, delimiter, options) {
9
10
  let done = false;
10
11
  let buffer = '';
11
- const decoder = new TextDecoder();
12
+ const decoder = new TextDecoder(undefined, options);
12
13
 
13
14
  while (true) {
14
15
  let split = buffer.indexOf(delimiter);
@@ -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
  }
@@ -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} */
@@ -4,9 +4,10 @@ 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';
6
6
  import { load_server_data } from '../page/load_data.js';
7
- import { handle_error_and_jsonify } from '../utils.js';
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
  /**
@@ -0,0 +1,135 @@
1
+ import { json, text } from '@sveltejs/kit';
2
+ import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
3
+ import { with_request_store } from '@sveltejs/kit/internal/server';
4
+ import { coalesce_to_error, get_message, get_status } from '../../utils/error.js';
5
+ import { negotiate } from '../../utils/http.js';
6
+ import { fix_stack_trace } from './internal.js';
7
+ import { escape_html } from '../../utils/escape.js';
8
+
9
+ /**
10
+ * @param {import('@sveltejs/kit').RequestEvent} event
11
+ * @param {import('types').RequestState} state
12
+ * @param {import('types').SSROptions} options
13
+ * @param {unknown} error
14
+ */
15
+ export async function handle_fatal_error(event, state, options, error) {
16
+ error = error instanceof HttpError ? error : coalesce_to_error(error);
17
+ const body = await handle_error_and_jsonify(event, state, options, error);
18
+ const status = body.status;
19
+
20
+ // sec-fetch-dest would be nicer, but non-browser clients and plain HTTP hosts don't send it
21
+ const type = negotiate(event.request.headers.get('accept') || 'text/html', [
22
+ 'application/json',
23
+ 'text/html'
24
+ ]);
25
+
26
+ if (event.isDataRequest || type === 'application/json') {
27
+ return json(body, {
28
+ status
29
+ });
30
+ }
31
+
32
+ return static_error_page(options, status, body.message);
33
+ }
34
+
35
+ /**
36
+ * @param {import('@sveltejs/kit').RequestEvent} event
37
+ * @param {import('types').RequestState} state
38
+ * @param {import('types').SSROptions} options
39
+ * @param {any} error
40
+ * @returns {App.Error | Promise<App.Error>}
41
+ */
42
+ export function handle_error_and_jsonify(event, state, options, error) {
43
+ if (error instanceof HttpError) {
44
+ // @ts-expect-error custom user errors may not have a message field if App.Error is overwritten
45
+ return { message: 'Unknown Error', ...error.body };
46
+ }
47
+
48
+ let e = error;
49
+ while (e instanceof Error) {
50
+ fix_stack_trace(e);
51
+ e = e.cause;
52
+ }
53
+
54
+ const status = get_status(error);
55
+ const message = get_message(error);
56
+
57
+ // TODO 4.0 await this, rather than handling the non-Promise case
58
+ let result;
59
+ try {
60
+ result = with_request_store({ event, state }, () =>
61
+ options.hooks.handleError({ error, event, status, message })
62
+ ) ?? { status, message };
63
+ } catch (hook_error) {
64
+ log_handle_error_hook_failure(error, hook_error);
65
+ return { status, message: 'Internal Error' };
66
+ }
67
+
68
+ if (result instanceof Promise) {
69
+ if (!__SVELTEKIT_SUPPORTS_ASYNC__ && state.is_in_render) {
70
+ console.warn(
71
+ `To use an async \`handleError\` hook to handle errors that occur during rendering, you must enable \`compilerOptions.experimental.async\` in the SvelteKit plugin of your Vite config. The returned error has been replaced with a generic object`
72
+ );
73
+
74
+ // we're discarding the result, but we still need to prevent an unhandled
75
+ // rejection if the user's async `handleError` hook rejects
76
+ result.catch((hook_error) => log_handle_error_hook_failure(error, hook_error));
77
+
78
+ return {
79
+ status,
80
+ message: 'Internal Error'
81
+ };
82
+ }
83
+
84
+ return result.then(
85
+ (body) => {
86
+ body ??= { status, message };
87
+ return { ...body, status: get_status(body, error) };
88
+ },
89
+ (hook_error) => {
90
+ log_handle_error_hook_failure(error, hook_error);
91
+ return { status, message: 'Internal Error' };
92
+ }
93
+ );
94
+ }
95
+
96
+ return { ...result, status: get_status(result, error) };
97
+ }
98
+
99
+ /**
100
+ * @param {unknown} error
101
+ * @param {unknown} hook_error
102
+ */
103
+ function log_handle_error_hook_failure(error, hook_error) {
104
+ const failure = new Error('The `handleError` hook failed', {
105
+ cause: coalesce_to_error(hook_error)
106
+ });
107
+ failure.stack = failure.message;
108
+ console.error(failure);
109
+ if (error instanceof SvelteKitError) {
110
+ console.error(`Original error: ${error.status} ${error.text}: ${error.message}`);
111
+ } else {
112
+ console.error('Original error:', error);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Return as a response that renders the error.html
118
+ *
119
+ * @param {import('types').SSROptions} options
120
+ * @param {number} status
121
+ * @param {string} message
122
+ */
123
+ export function static_error_page(options, status, message) {
124
+ let page = options.templates.error({ status, message: escape_html(message) });
125
+
126
+ if (__SVELTEKIT_DEV__) {
127
+ // inject Vite HMR client, for easier debugging
128
+ page = page.replace('</head>', '<script type="module" src="/@vite/client"></script></head>');
129
+ }
130
+
131
+ return text(page, {
132
+ headers: { 'content-type': 'text/html; charset=utf-8' },
133
+ status
134
+ });
135
+ }
@@ -2,7 +2,7 @@ import { parseSetCookie } from 'cookie';
2
2
  import { noop } from '../../utils/functions.js';
3
3
  import { respond } from './respond.js';
4
4
  import * as paths from '$app/paths/internal/server';
5
- import { read_implementation } from '__sveltekit/server';
5
+ import { read_implementation } from './internal.js';
6
6
  import { has_prerendered_path } from './utils.js';
7
7
 
8
8
  /**
@@ -2,10 +2,10 @@ import { noop } from '../../utils/functions.js';
2
2
  import { IN_WEBCONTAINER } from './constants.js';
3
3
  import { respond } from './respond.js';
4
4
  import { options, get_hooks } from '__SERVER__/internal.js';
5
- import { format_server_error } from './utils.js';
6
- import { set_read_implementation, set_manifest } from '__sveltekit/server';
5
+ import { set_read_implementation, set_manifest } from './internal.js';
7
6
  import { set_env } from '__sveltekit/env';
8
7
  import { set_app } from './app.js';
8
+ import { SvelteKitError } from '@sveltejs/kit/internal';
9
9
 
10
10
  /** @type {Promise<any>} */
11
11
  let init_promise;
@@ -107,13 +107,23 @@ export class Server {
107
107
  handle: module.handle || (({ event, resolve }) => resolve(event)),
108
108
  handleError:
109
109
  module.handleError ||
110
- (({ status, error, event }) => {
111
- const error_message = format_server_error(
112
- status,
113
- /** @type {Error} */ (error),
114
- event
115
- );
116
- console.error(error_message);
110
+ (({ error }) => {
111
+ if (error instanceof SvelteKitError) {
112
+ // don't log stack traces for 404s etc, it's all internal gubbins
113
+ return;
114
+ }
115
+
116
+ let e = error;
117
+ while (e instanceof Error) {
118
+ if (e.stack) {
119
+ console.error(e.stack);
120
+ }
121
+ e = e.cause;
122
+ }
123
+
124
+ if (e) {
125
+ console.error(String(e));
126
+ }
117
127
  }),
118
128
  handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)),
119
129
  handleValidationError:
@@ -164,7 +174,7 @@ export class Server {
164
174
  * @param {Request} request
165
175
  * @param {import('types').RequestOptions} options
166
176
  */
167
- async respond(request, options) {
177
+ respond(request, options) {
168
178
  return respond(request, this.#options, this.#manifest, {
169
179
  ...options,
170
180
  error: false,
@@ -0,0 +1,25 @@
1
+ /** @import { SSRManifest } from '@sveltejs/kit'; */
2
+
3
+ /**
4
+ * @type {((path: string) => ReadableStream<any>) | null}
5
+ */
6
+ export let read_implementation = null;
7
+
8
+ export let manifest = /** @type {SSRManifest} */ (/** @type {unknown} */ (null));
9
+
10
+ /**
11
+ * @param {(path: string) => ReadableStream<any>} fn
12
+ */
13
+ export function set_read_implementation(fn) {
14
+ read_implementation = fn;
15
+ }
16
+
17
+ /**
18
+ *
19
+ * @param {SSRManifest} value
20
+ */
21
+ export function set_manifest(value) {
22
+ manifest = value;
23
+ }
24
+
25
+ export { fix_stack_trace, set_fix_stack_trace } from './sourcemaps.js';
@@ -7,7 +7,8 @@ import { HttpError, Redirect, ActionFailure, SvelteKitError } from '@sveltejs/ki
7
7
  import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
8
8
  import { normalize_error } from '../../../utils/error.js';
9
9
  import { is_form_content_type, negotiate } from '../../../utils/http.js';
10
- import { create_replacer, handle_error_and_jsonify } from '../utils.js';
10
+ import { create_replacer, with_version_header } from '../utils.js';
11
+ import { handle_error_and_jsonify } from '../errors.js';
11
12
  import { record_span } from '../../telemetry/record_span.js';
12
13
 
13
14
  /** @param {RequestEvent} event */
@@ -94,7 +95,7 @@ export async function handle_action_json_request(event, event_state, options, se
94
95
  });
95
96
  } else {
96
97
  // no data returned — use 204 No Content (without a body, per the spec)
97
- return new Response(null, { status: 204 });
98
+ return with_version_header(new Response(null, { status: 204 }));
98
99
  }
99
100
  } catch (e) {
100
101
  const err = normalize_error(e);
@@ -147,7 +148,7 @@ export function action_json_redirect(redirect) {
147
148
  * @param {ResponseInit} [init]
148
149
  */
149
150
  function action_json(data, init) {
150
- return json(data, init);
151
+ return with_version_header(json(data, init));
151
152
  }
152
153
 
153
154
  /**
@@ -1,12 +1,8 @@
1
1
  import * as devalue from 'devalue';
2
2
  import { compact } from '../../../utils/array.js';
3
3
  import { create_async_iterator } from '../../../utils/streaming.js';
4
- import {
5
- clarify_devalue_error,
6
- get_global_name,
7
- handle_error_and_jsonify,
8
- serialize_uses
9
- } from '../utils.js';
4
+ import { clarify_devalue_error, get_global_name, serialize_uses } from '../utils.js';
5
+ import { handle_error_and_jsonify } from '../errors.js';
10
6
 
11
7
  /**
12
8
  * If the serialized data contains promises, `chunks` will be an
@@ -45,12 +41,14 @@ export function server_data_serializer(event, event_state, options) {
45
41
  let str;
46
42
  try {
47
43
  str = devalue.uneval(error ? [, error] : [data], replacer);
48
- } catch {
44
+ } catch (e) {
49
45
  error = await handle_error_and_jsonify(
50
46
  event,
51
47
  event_state,
52
48
  options,
53
- new Error(`Failed to serialize promise while rendering ${event.route.id}`)
49
+ new Error(`Failed to serialize promise while rendering ${event.route.id}`, {
50
+ cause: e
51
+ })
54
52
  );
55
53
  str = devalue.uneval([, error], replacer);
56
54
  }
@@ -164,12 +162,14 @@ export function server_data_serializer_json(event, event_state, options) {
164
162
  let str;
165
163
  try {
166
164
  str = devalue.stringify(value, reducers);
167
- } catch {
165
+ } catch (e) {
168
166
  const error = await handle_error_and_jsonify(
169
167
  event,
170
168
  event_state,
171
169
  options,
172
- new Error(`Failed to serialize promise while rendering ${event.route.id}`)
170
+ new Error(`Failed to serialize promise while rendering ${event.route.id}`, {
171
+ cause: e
172
+ })
173
173
  );
174
174
 
175
175
  key = 'error';