@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.
- package/package.json +1 -1
- package/src/cli.js +8 -8
- package/src/constants.js +3 -1
- package/src/core/adapt/builder.js +3 -6
- package/src/core/config/index.js +6 -1
- package/src/core/config/options.js +54 -104
- package/src/core/env.js +25 -11
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +75 -28
- package/src/core/postbuild/queue.js +2 -1
- package/src/core/sync/sync.js +11 -14
- package/src/core/sync/utils.js +21 -1
- package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
- package/src/core/sync/write_client_manifest.js +4 -12
- package/src/core/sync/write_env.js +6 -4
- package/src/core/sync/write_server.js +12 -17
- package/src/core/sync/write_tsconfig/index.js +245 -0
- package/src/core/sync/write_tsconfig/utils.js +162 -0
- package/src/core/sync/write_types/index.js +80 -88
- package/src/core/utils.js +28 -3
- package/src/exports/env/index.js +68 -3
- package/src/exports/internal/env.js +6 -3
- package/src/exports/internal/server/index.js +3 -1
- package/src/exports/params.js +9 -4
- package/src/exports/public.d.ts +69 -71
- package/src/exports/vite/dev/index.js +112 -56
- package/src/exports/vite/index.js +483 -258
- package/src/exports/vite/module_ids.js +2 -2
- package/src/exports/vite/preview/index.js +3 -2
- package/src/exports/vite/utils.js +21 -10
- package/src/runner.js +13 -0
- package/src/runtime/app/forms.js +4 -0
- package/src/runtime/app/manifest/index.js +1 -0
- package/src/runtime/app/paths/client.js +30 -30
- package/src/runtime/app/paths/internal/client.js +26 -0
- package/src/runtime/app/paths/server.js +23 -10
- package/src/runtime/app/paths/types.d.ts +11 -19
- package/src/runtime/app/server/index.js +1 -1
- package/src/runtime/app/server/remote/command.js +12 -7
- package/src/runtime/app/server/remote/form.js +29 -26
- package/src/runtime/app/server/remote/prerender.js +9 -2
- package/src/runtime/app/server/remote/query.js +6 -5
- package/src/runtime/app/server/remote/shared.js +48 -30
- package/src/runtime/app/service-worker/index.js +24 -0
- package/src/runtime/app/state/index.js +1 -1
- package/src/runtime/client/client.js +46 -9
- package/src/runtime/client/ndjson.js +1 -1
- package/src/runtime/client/remote-functions/form.svelte.js +43 -51
- package/src/runtime/client/remote-functions/query/index.js +2 -2
- package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
- package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
- package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
- package/src/runtime/client/state.svelte.js +54 -21
- package/src/runtime/client/stream.js +3 -2
- package/src/runtime/form-utils.js +89 -54
- package/src/runtime/server/cookie.js +1 -1
- package/src/runtime/server/data/index.js +32 -29
- package/src/runtime/server/errors.js +135 -0
- package/src/runtime/server/fetch.js +1 -1
- package/src/runtime/server/index.js +20 -10
- package/src/runtime/server/internal.js +25 -0
- package/src/runtime/server/page/actions.js +4 -3
- package/src/runtime/server/page/data_serializer.js +10 -10
- package/src/runtime/server/page/index.js +3 -2
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +22 -35
- package/src/runtime/server/page/respond_with_error.js +6 -5
- package/src/runtime/server/{remote.js → remote-functions.js} +67 -36
- package/src/runtime/server/respond.js +58 -20
- package/src/runtime/server/sourcemaps.js +183 -0
- package/src/runtime/server/utils.js +12 -157
- package/src/types/ambient-private.d.ts +10 -0
- package/src/types/ambient.d.ts +22 -27
- package/src/types/global-private.d.ts +12 -5
- package/src/types/internal.d.ts +19 -9
- package/src/types/private.d.ts +8 -0
- package/src/utils/escape.js +9 -25
- package/src/utils/features.js +1 -1
- package/src/utils/fork.js +7 -2
- package/src/utils/page_nodes.js +3 -5
- package/src/utils/url.js +12 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +152 -120
- package/types/index.d.ts.map +6 -1
- package/src/core/sync/write_ambient.js +0 -18
- package/src/core/sync/write_tsconfig.js +0 -258
- package/src/runtime/shared-server.js +0 -7
- /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
|
|
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}
|
|
15
|
+
* @param {{ name: string; type: 'number' | 'boolean' | null }} field
|
|
16
16
|
* @param {any} value
|
|
17
17
|
*/
|
|
18
|
-
export function set_nested_value(object,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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 (
|
|
42
|
-
const is_array = key.endsWith('[]');
|
|
79
|
+
for (const field_name of data.keys()) {
|
|
43
80
|
/** @type {any[]} */
|
|
44
|
-
|
|
81
|
+
const values = data.getAll(field_name);
|
|
45
82
|
|
|
46
|
-
|
|
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
|
-
|
|
86
|
+
const entries = values.filter(
|
|
50
87
|
(entry) => typeof entry === 'string' || entry.name !== '' || entry.size > 0
|
|
51
88
|
);
|
|
52
|
-
if (
|
|
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 (
|
|
63
|
-
throw new Error(
|
|
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(
|
|
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(
|
|
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({},
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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(
|
|
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({},
|
|
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:'
|
|
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 '../
|
|
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
|
|
127
|
-
new
|
|
128
|
-
|
|
129
|
-
controller
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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 '
|
|
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 {
|
|
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
|
-
(({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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';
|