@sveltejs/kit 3.0.0-next.4 → 3.0.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +23 -18
- package/src/core/adapt/builder.js +29 -43
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +135 -71
- package/src/core/config/options.js +291 -244
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +120 -5
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/postbuild/analyse.js +8 -12
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +40 -23
- package/src/core/sync/create_manifest_data/index.js +29 -49
- package/src/core/sync/write_client_manifest.js +14 -14
- package/src/core/sync/write_non_ambient.js +10 -7
- package/src/core/sync/write_root.js +9 -30
- package/src/core/sync/write_server.js +13 -11
- package/src/core/sync/write_tsconfig.js +2 -4
- package/src/core/sync/write_types/index.js +11 -10
- package/src/exports/index.js +34 -12
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +1 -1
- package/src/exports/internal/index.js +1 -90
- package/src/exports/internal/{event.js → server/event.js} +1 -2
- package/src/exports/internal/server/index.js +33 -0
- package/src/exports/internal/shared.js +89 -0
- package/src/exports/node/index.js +58 -7
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +299 -145
- package/src/exports/url.js +84 -0
- package/src/exports/vite/build/build_server.js +6 -1
- package/src/exports/vite/dev/index.js +38 -37
- package/src/exports/vite/index.js +498 -353
- package/src/exports/vite/preview/index.js +16 -8
- package/src/exports/vite/utils.js +7 -11
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/forms.js +22 -5
- package/src/runtime/app/paths/client.js +4 -10
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +8 -4
- package/src/runtime/app/server/remote/form.js +10 -3
- package/src/runtime/app/server/remote/query.js +9 -18
- package/src/runtime/app/server/remote/requested.js +8 -4
- package/src/runtime/app/server/remote/shared.js +5 -7
- package/src/runtime/app/state/client.js +1 -2
- package/src/runtime/app/stores.js +13 -76
- package/src/runtime/client/client.js +205 -167
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/form.svelte.js +122 -31
- package/src/runtime/client/remote-functions/prerender.svelte.js +17 -4
- package/src/runtime/client/remote-functions/query/index.js +3 -2
- package/src/runtime/client/remote-functions/query/instance.svelte.js +27 -11
- package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +42 -15
- package/src/runtime/client/remote-functions/shared.svelte.js +18 -9
- package/src/runtime/client/state.svelte.js +66 -49
- package/src/runtime/client/types.d.ts +10 -2
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/form-utils.js +99 -18
- package/src/runtime/server/cookie.js +22 -33
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +7 -7
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/index.js +2 -2
- package/src/runtime/server/page/actions.js +41 -26
- package/src/runtime/server/page/index.js +2 -1
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +36 -47
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/page/server_routing.js +13 -9
- package/src/runtime/server/remote.js +85 -39
- package/src/runtime/server/respond.js +92 -58
- package/src/runtime/server/utils.js +7 -7
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +4 -4
- package/src/types/internal.d.ts +17 -20
- package/src/types/private.d.ts +33 -1
- package/src/types/synthetic/$lib.md +1 -1
- package/src/utils/error.js +12 -4
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +84 -38
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +20 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +434 -269
- package/types/index.d.ts.map +19 -15
- package/src/exports/internal/server.js +0 -22
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
@@ -39,7 +39,7 @@ function generate_cookie_key(domain, path, name) {
|
|
|
39
39
|
export function get_cookies(request, url) {
|
|
40
40
|
const header = request.headers.get('cookie') ?? '';
|
|
41
41
|
const initial_cookies = /** @type {Record<string, string>} */ (
|
|
42
|
-
|
|
42
|
+
parseCookie(header, { decode: (value) => value })
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
/** @type {string | undefined} */
|
|
@@ -48,7 +48,7 @@ export function get_cookies(request, url) {
|
|
|
48
48
|
/** @type {Map<string, import('./page/types.js').Cookie>} */
|
|
49
49
|
const new_cookies = new Map();
|
|
50
50
|
|
|
51
|
-
/** @type {import('cookie').
|
|
51
|
+
/** @type {Omit<import('cookie').SetCookie, 'name' | 'value'>} */
|
|
52
52
|
const defaults = {
|
|
53
53
|
httpOnly: true,
|
|
54
54
|
path: '/',
|
|
@@ -63,10 +63,6 @@ export function get_cookies(request, url) {
|
|
|
63
63
|
// typescript users. `@type {import('@sveltejs/kit').Cookies}` above is not
|
|
64
64
|
// sufficient to do so.
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* @param {string} name
|
|
68
|
-
* @param {import('cookie').ParseOptions} [opts]
|
|
69
|
-
*/
|
|
70
66
|
get(name, opts) {
|
|
71
67
|
// Look for the most specific matching cookie from new_cookies
|
|
72
68
|
const best_match = Array.from(new_cookies.values())
|
|
@@ -83,7 +79,7 @@ export function get_cookies(request, url) {
|
|
|
83
79
|
return best_match.options.maxAge === 0 ? undefined : best_match.value;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
const req_cookies =
|
|
82
|
+
const req_cookies = parseCookie(header, { decode: opts?.decode });
|
|
87
83
|
const cookie = req_cookies[name]; // the decoded string or undefined
|
|
88
84
|
|
|
89
85
|
// in development, if the cookie was set during this session with `cookies.set`,
|
|
@@ -106,11 +102,8 @@ export function get_cookies(request, url) {
|
|
|
106
102
|
return cookie;
|
|
107
103
|
},
|
|
108
104
|
|
|
109
|
-
/**
|
|
110
|
-
* @param {import('cookie').ParseOptions} [opts]
|
|
111
|
-
*/
|
|
112
105
|
getAll(opts) {
|
|
113
|
-
const cookies =
|
|
106
|
+
const cookies = parseCookie(header, { decode: opts?.decode });
|
|
114
107
|
|
|
115
108
|
// Group cookies by name and find the most specific one for each name
|
|
116
109
|
const lookup = new Map();
|
|
@@ -141,29 +134,17 @@ export function get_cookies(request, url) {
|
|
|
141
134
|
);
|
|
142
135
|
},
|
|
143
136
|
|
|
144
|
-
/**
|
|
145
|
-
* @param {string} name
|
|
146
|
-
* @param {string} value
|
|
147
|
-
* @param {import('cookie').SerializeOptions} options
|
|
148
|
-
*/
|
|
149
137
|
set(name, value, options) {
|
|
150
138
|
set_internal(name, value, { ...defaults, ...options });
|
|
151
139
|
},
|
|
152
140
|
|
|
153
|
-
/**
|
|
154
|
-
* @param {string} name
|
|
155
|
-
* @param {import('cookie').SerializeOptions} options
|
|
156
|
-
*/
|
|
157
141
|
delete(name, options) {
|
|
158
142
|
cookies.set(name, '', { ...options, maxAge: 0 });
|
|
159
143
|
},
|
|
160
144
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
* @param {import('cookie').SerializeOptions} options
|
|
165
|
-
*/
|
|
166
|
-
serialize(name, value, options) {
|
|
145
|
+
parse: parseSetCookie,
|
|
146
|
+
|
|
147
|
+
serialize(name, value, { encode, ...options }) {
|
|
167
148
|
let path = options.path ?? '/';
|
|
168
149
|
|
|
169
150
|
if (!options.domain || options.domain === url.hostname) {
|
|
@@ -173,7 +154,7 @@ export function get_cookies(request, url) {
|
|
|
173
154
|
path = resolve(normalized_url, path);
|
|
174
155
|
}
|
|
175
156
|
|
|
176
|
-
return
|
|
157
|
+
return stringifySetCookie({ name, value, ...defaults, ...options, path }, { encode });
|
|
177
158
|
}
|
|
178
159
|
};
|
|
179
160
|
|
|
@@ -200,7 +181,7 @@ export function get_cookies(request, url) {
|
|
|
200
181
|
// explicit header has highest precedence
|
|
201
182
|
if (header) {
|
|
202
183
|
const parsed = /** @type {Record<string, string>} */ (
|
|
203
|
-
|
|
184
|
+
parseCookie(header, { decode: (value) => value })
|
|
204
185
|
);
|
|
205
186
|
for (const name in parsed) {
|
|
206
187
|
combined_cookies[name] = parsed[name];
|
|
@@ -238,7 +219,8 @@ export function get_cookies(request, url) {
|
|
|
238
219
|
new_cookies.set(cookie_key, cookie);
|
|
239
220
|
|
|
240
221
|
if (DEV) {
|
|
241
|
-
const
|
|
222
|
+
const { encode, ...rest } = cookie.options;
|
|
223
|
+
const serialized = stringifySetCookie({ name, value, ...rest }, { encode });
|
|
242
224
|
if (text_encoder.encode(serialized).byteLength > MAX_COOKIE_SIZE) {
|
|
243
225
|
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
|
|
244
226
|
}
|
|
@@ -296,15 +278,22 @@ export function path_matches(path, constraint) {
|
|
|
296
278
|
*/
|
|
297
279
|
export function add_cookies_to_headers(headers, cookies) {
|
|
298
280
|
for (const new_cookie of cookies) {
|
|
299
|
-
const {
|
|
300
|
-
|
|
281
|
+
const {
|
|
282
|
+
name,
|
|
283
|
+
value,
|
|
284
|
+
options: { encode, ...options }
|
|
285
|
+
} = new_cookie;
|
|
286
|
+
headers.append('set-cookie', stringifySetCookie({ name, value, ...options }, { encode }));
|
|
301
287
|
|
|
302
288
|
// special case — for routes ending with .html, the route data lives in a sibling
|
|
303
289
|
// `.html__data.json` file rather than a child `/__data.json` file, which means
|
|
304
290
|
// we need to duplicate the cookie
|
|
305
291
|
if (options.path.endsWith('.html')) {
|
|
306
292
|
const path = add_data_suffix(options.path);
|
|
307
|
-
headers.append(
|
|
293
|
+
headers.append(
|
|
294
|
+
'set-cookie',
|
|
295
|
+
stringifySetCookie({ name, value, ...options, path }, { encode })
|
|
296
|
+
);
|
|
308
297
|
}
|
|
309
298
|
}
|
|
310
299
|
}
|
|
@@ -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
|
+
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 {
|
|
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';
|
|
@@ -107,13 +107,11 @@ export async function render_data(
|
|
|
107
107
|
// Math.min because array isn't guaranteed to resolve in order
|
|
108
108
|
length = Math.min(length, i + 1);
|
|
109
109
|
|
|
110
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
111
|
+
|
|
110
112
|
return /** @type {import('types').ServerErrorNode} */ ({
|
|
111
113
|
type: 'error',
|
|
112
|
-
error:
|
|
113
|
-
status:
|
|
114
|
-
error instanceof HttpError || error instanceof SvelteKitError
|
|
115
|
-
? error.status
|
|
116
|
-
: undefined
|
|
114
|
+
error: transformed
|
|
117
115
|
});
|
|
118
116
|
})
|
|
119
117
|
)
|
|
@@ -156,7 +154,8 @@ export async function render_data(
|
|
|
156
154
|
if (error instanceof Redirect) {
|
|
157
155
|
return redirect_json_response(error);
|
|
158
156
|
} else {
|
|
159
|
-
|
|
157
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
158
|
+
return json_response(transformed, transformed.status);
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
161
|
}
|
|
@@ -182,6 +181,7 @@ export function redirect_json_response(redirect) {
|
|
|
182
181
|
return json_response(
|
|
183
182
|
/** @type {import('types').ServerRedirectNode} */ ({
|
|
184
183
|
type: 'redirect',
|
|
184
|
+
status: redirect.status,
|
|
185
185
|
location: redirect.location
|
|
186
186
|
})
|
|
187
187
|
);
|
|
@@ -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 || {}
|
|
@@ -5,7 +5,7 @@ import { DEV } from 'esm-env';
|
|
|
5
5
|
import { json } from '@sveltejs/kit';
|
|
6
6
|
import { HttpError, Redirect, ActionFailure, SvelteKitError } from '@sveltejs/kit/internal';
|
|
7
7
|
import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
|
|
8
|
-
import {
|
|
8
|
+
import { normalize_error } from '../../../utils/error.js';
|
|
9
9
|
import { is_form_content_type, negotiate } from '../../../utils/http.js';
|
|
10
10
|
import { create_replacer, handle_error_and_jsonify } from '../utils.js';
|
|
11
11
|
import { record_span } from '../../telemetry/record_span.js';
|
|
@@ -36,13 +36,15 @@ export async function handle_action_json_request(event, event_state, options, se
|
|
|
36
36
|
`POST method not allowed. No form actions exist for ${DEV ? `the page at ${event.route.id}` : 'this page'}`
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
+
const error = await handle_error_and_jsonify(event, event_state, options, no_actions_error);
|
|
40
|
+
|
|
39
41
|
return action_json(
|
|
40
42
|
{
|
|
41
43
|
type: 'error',
|
|
42
|
-
error
|
|
44
|
+
error
|
|
43
45
|
},
|
|
44
46
|
{
|
|
45
|
-
status:
|
|
47
|
+
status: error.status,
|
|
46
48
|
headers: {
|
|
47
49
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
|
48
50
|
// "The server must generate an Allow header field in a 405 status code response"
|
|
@@ -62,22 +64,27 @@ export async function handle_action_json_request(event, event_state, options, se
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
if (data instanceof ActionFailure) {
|
|
65
|
-
return action_json(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
data
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
return action_json(
|
|
68
|
+
{
|
|
69
|
+
type: 'failure',
|
|
70
|
+
status: data.status,
|
|
71
|
+
// @ts-expect-error we assign a string to what is supposed to be an object. That's ok
|
|
72
|
+
// because we don't use the object outside, and this way we have better code navigation
|
|
73
|
+
// through knowing where the related interface is used.
|
|
74
|
+
data: stringify_action_response(
|
|
75
|
+
data.data,
|
|
76
|
+
/** @type {string} */ (event.route.id),
|
|
77
|
+
options.hooks.transport
|
|
78
|
+
)
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
status: data.status
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
} else if (data) {
|
|
78
85
|
return action_json({
|
|
79
86
|
type: 'success',
|
|
80
|
-
status:
|
|
87
|
+
status: 200,
|
|
81
88
|
// @ts-expect-error see comment above
|
|
82
89
|
data: stringify_action_response(
|
|
83
90
|
data,
|
|
@@ -85,6 +92,9 @@ export async function handle_action_json_request(event, event_state, options, se
|
|
|
85
92
|
options.hooks.transport
|
|
86
93
|
)
|
|
87
94
|
});
|
|
95
|
+
} else {
|
|
96
|
+
// no data returned — use 204 No Content (without a body, per the spec)
|
|
97
|
+
return new Response(null, { status: 204 });
|
|
88
98
|
}
|
|
89
99
|
} catch (e) {
|
|
90
100
|
const err = normalize_error(e);
|
|
@@ -93,18 +103,20 @@ export async function handle_action_json_request(event, event_state, options, se
|
|
|
93
103
|
return action_json_redirect(err);
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
const transformed = await handle_error_and_jsonify(
|
|
107
|
+
event,
|
|
108
|
+
event_state,
|
|
109
|
+
options,
|
|
110
|
+
check_incorrect_fail_use(err)
|
|
111
|
+
);
|
|
112
|
+
|
|
96
113
|
return action_json(
|
|
97
114
|
{
|
|
98
115
|
type: 'error',
|
|
99
|
-
error:
|
|
100
|
-
event,
|
|
101
|
-
event_state,
|
|
102
|
-
options,
|
|
103
|
-
check_incorrect_fail_use(err)
|
|
104
|
-
)
|
|
116
|
+
error: transformed
|
|
105
117
|
},
|
|
106
118
|
{
|
|
107
|
-
status:
|
|
119
|
+
status: transformed.status
|
|
108
120
|
}
|
|
109
121
|
);
|
|
110
122
|
}
|
|
@@ -163,6 +175,7 @@ export async function handle_action_request(event, event_state, server) {
|
|
|
163
175
|
});
|
|
164
176
|
return {
|
|
165
177
|
type: 'error',
|
|
178
|
+
// We're lying a bit with the types here; this will be transformed into a proper App.Error object later
|
|
166
179
|
error: new SvelteKitError(
|
|
167
180
|
405,
|
|
168
181
|
'Method Not Allowed',
|
|
@@ -207,6 +220,7 @@ export async function handle_action_request(event, event_state, server) {
|
|
|
207
220
|
|
|
208
221
|
return {
|
|
209
222
|
type: 'error',
|
|
223
|
+
// @ts-expect-error We're lying a bit with the types here; this will be transformed into a proper App.Error object later
|
|
210
224
|
error: check_incorrect_fail_use(err)
|
|
211
225
|
};
|
|
212
226
|
}
|
|
@@ -243,11 +257,12 @@ async function call_action(event, event_state, actions) {
|
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
246
|
-
|
|
247
|
-
if (!action) {
|
|
260
|
+
if (!Object.hasOwn(actions, name)) {
|
|
248
261
|
throw new SvelteKitError(404, 'Not Found', `No action with name '${name}' found`);
|
|
249
262
|
}
|
|
250
263
|
|
|
264
|
+
const action = actions[name];
|
|
265
|
+
|
|
251
266
|
if (!is_form_content_type(event.request)) {
|
|
252
267
|
throw new SvelteKitError(
|
|
253
268
|
415,
|
|
@@ -269,6 +269,7 @@ export async function render_page(
|
|
|
269
269
|
if (state.prerendering && should_prerender_data) {
|
|
270
270
|
const body = JSON.stringify({
|
|
271
271
|
type: 'redirect',
|
|
272
|
+
status: err.status,
|
|
272
273
|
location: err.location
|
|
273
274
|
});
|
|
274
275
|
|
|
@@ -281,8 +282,8 @@ export async function render_page(
|
|
|
281
282
|
return redirect_response(err.status, err.location);
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
const status = get_status(err);
|
|
285
285
|
const error = await handle_error_and_jsonify(event, event_state, options, err);
|
|
286
|
+
const status = error.status;
|
|
286
287
|
|
|
287
288
|
while (i--) {
|
|
288
289
|
if (page.errors[i]) {
|
|
@@ -377,7 +377,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
void push_fetched(base64_encode(result), true);
|
|
380
|
-
})();
|
|
380
|
+
})().catch(noop); // prevent unhandled rejection potentially crashing the process
|
|
381
381
|
|
|
382
382
|
return (teed_body = b);
|
|
383
383
|
}
|
|
@@ -11,7 +11,7 @@ import { uneval_action_response } from './actions.js';
|
|
|
11
11
|
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
|
12
12
|
import { SCHEME } from '../../../utils/url.js';
|
|
13
13
|
import { create_server_routing_response, generate_route_object } from './server_routing.js';
|
|
14
|
-
import { add_resolution_suffix } from '../../pathname.js';
|
|
14
|
+
import { add_data_suffix, add_resolution_suffix } from '../../pathname.js';
|
|
15
15
|
import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server';
|
|
16
16
|
import { text_encoder } from '../../utils.js';
|
|
17
17
|
import {
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
get_global_name,
|
|
21
21
|
handle_error_and_jsonify
|
|
22
22
|
} from '../utils.js';
|
|
23
|
-
import { get_status } from '../../../utils/error.js';
|
|
24
23
|
import * as env from '__sveltekit/env';
|
|
25
24
|
import { collect_remote_data } from '../remote.js';
|
|
26
25
|
|
|
@@ -68,7 +67,7 @@ export async function render_response({
|
|
|
68
67
|
}) {
|
|
69
68
|
if (state.prerendering) {
|
|
70
69
|
if (options.csp.mode === 'nonce') {
|
|
71
|
-
throw new Error('Cannot use prerendering if config.
|
|
70
|
+
throw new Error('Cannot use prerendering if config.csp.mode === "nonce"');
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
if (options.app_template_contains_nonce) {
|
|
@@ -92,7 +91,7 @@ export async function render_response({
|
|
|
92
91
|
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
|
|
93
92
|
const inline_styles = new Map();
|
|
94
93
|
|
|
95
|
-
/** @type {ReturnType<typeof options.root.render
|
|
94
|
+
/** @type {Awaited<ReturnType<typeof options.root.render>>} */
|
|
96
95
|
let rendered;
|
|
97
96
|
|
|
98
97
|
const form_value =
|
|
@@ -119,7 +118,12 @@ export async function render_response({
|
|
|
119
118
|
// if appropriate, use relative paths for greater portability
|
|
120
119
|
if (paths.relative) {
|
|
121
120
|
if (!state.prerendering?.fallback) {
|
|
122
|
-
|
|
121
|
+
// the relative path depth must reflect the URL the browser is actually at, which
|
|
122
|
+
// for a data request includes the `__data.json` suffix that was stripped during routing
|
|
123
|
+
const pathname = event.isDataRequest
|
|
124
|
+
? add_data_suffix(event.url.pathname)
|
|
125
|
+
: event.url.pathname;
|
|
126
|
+
const segments = pathname.slice(paths.base.length).split('/').slice(2);
|
|
123
127
|
|
|
124
128
|
base = segments.map(() => '..').join('/') || '.';
|
|
125
129
|
|
|
@@ -200,7 +204,7 @@ export async function render_response({
|
|
|
200
204
|
|
|
201
205
|
const transformed = await handle_error_and_jsonify(event, event_state, options, e);
|
|
202
206
|
props.page.error = props.error = error = transformed;
|
|
203
|
-
props.page.status = status =
|
|
207
|
+
props.page.status = status = transformed.status;
|
|
204
208
|
return transformed;
|
|
205
209
|
}
|
|
206
210
|
: undefined
|
|
@@ -234,36 +238,27 @@ export async function render_response({
|
|
|
234
238
|
// portable as possible, but reset afterwards
|
|
235
239
|
if (paths.relative) paths.override({ base, assets });
|
|
236
240
|
|
|
237
|
-
const maybe_promise = options.root.render(props, render_opts);
|
|
238
241
|
// We have to invoke .then eagerly here in order to kick off rendering: it's only starting on access,
|
|
239
242
|
// and `await maybe_promise` would eagerly access the .then property but call its function only after a tick, which is too late
|
|
240
243
|
// for the paths.reset() below and for any eager getRequestEvent() calls during rendering without AsyncLocalStorage available.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
? /** @type {ReturnType<typeof options.root.render> & Promise<any>} */ (
|
|
244
|
-
maybe_promise
|
|
245
|
-
).then((r) => r)
|
|
246
|
-
: maybe_promise;
|
|
247
|
-
|
|
248
|
-
// TODO 3.0 remove options.async
|
|
249
|
-
if (options.async) {
|
|
250
|
-
// we reset this synchronously, rather than after async rendering is complete,
|
|
251
|
-
// to avoid cross-talk between requests. This is a breaking change for
|
|
252
|
-
// anyone who opts into async SSR, since `base` and `assets` will no
|
|
253
|
-
// longer be relative to the current pathname.
|
|
254
|
-
// TODO 3.0 remove `base` and `assets` in favour of `resolve(...)` and `asset(...)`
|
|
255
|
-
paths.reset();
|
|
256
|
-
}
|
|
244
|
+
// TODO use render from 'svelte/server' here
|
|
245
|
+
const rendered = options.root.render(props, render_opts).then((r) => r);
|
|
257
246
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
247
|
+
// we reset this synchronously, rather than after async rendering is complete,
|
|
248
|
+
// to avoid cross-talk between requests. This is a breaking change for
|
|
249
|
+
// anyone who opts into async SSR, since `base` and `assets` will no
|
|
250
|
+
// longer be relative to the current pathname.
|
|
251
|
+
// TODO 3.0 remove `base` and `assets` in favour of `resolve(...)` and `asset(...)`
|
|
252
|
+
paths.reset();
|
|
253
|
+
|
|
254
|
+
// @ts-expect-error the legacy `render` API only returns html still, but the new API uses body
|
|
255
|
+
const { head, html: body, css, hashes } = await rendered;
|
|
261
256
|
|
|
262
257
|
if (hashes) {
|
|
263
258
|
csp.add_script_hashes(hashes.script);
|
|
264
259
|
}
|
|
265
260
|
|
|
266
|
-
return { head,
|
|
261
|
+
return { head, body, css, hashes };
|
|
267
262
|
});
|
|
268
263
|
} finally {
|
|
269
264
|
if (DEV) {
|
|
@@ -273,7 +268,7 @@ export async function render_response({
|
|
|
273
268
|
paths.reset(); // just in case `options.root.render(...)` failed
|
|
274
269
|
}
|
|
275
270
|
} else {
|
|
276
|
-
rendered = { head: '',
|
|
271
|
+
rendered = { head: '', body: '', css: { code: '', map: null }, hashes: { script: [] } };
|
|
277
272
|
}
|
|
278
273
|
|
|
279
274
|
for (const { node } of branch) {
|
|
@@ -294,7 +289,7 @@ export async function render_response({
|
|
|
294
289
|
}
|
|
295
290
|
|
|
296
291
|
const head = new Head(rendered.head);
|
|
297
|
-
let body = rendered.
|
|
292
|
+
let body = rendered.body;
|
|
298
293
|
|
|
299
294
|
/** @param {string} path */
|
|
300
295
|
const prefixed = (path) => {
|
|
@@ -374,7 +369,13 @@ export async function render_response({
|
|
|
374
369
|
if (page_config.csr && client) {
|
|
375
370
|
const route = client.routes?.find((r) => r.id === event.route.id) ?? null;
|
|
376
371
|
|
|
377
|
-
|
|
372
|
+
// when serving a prerendered page in an app that uses runtime public env vars, we must
|
|
373
|
+
// import the env.js module so that it evaluates before any user code can evaluate.
|
|
374
|
+
// TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed
|
|
375
|
+
// https://github.com/sveltejs/kit/pull/11601
|
|
376
|
+
const load_env_eagerly = client.uses_env_dynamic_public && !!state.prerendering;
|
|
377
|
+
|
|
378
|
+
if (load_env_eagerly) {
|
|
378
379
|
modulepreloads.add(`${paths.app_dir}/env.js`);
|
|
379
380
|
}
|
|
380
381
|
|
|
@@ -386,7 +387,7 @@ export async function render_response({
|
|
|
386
387
|
/** @type {(path: string) => void} */
|
|
387
388
|
let add_preload;
|
|
388
389
|
|
|
389
|
-
// see the
|
|
390
|
+
// see the output.preloadStrategy option for details on why we have multiple options here
|
|
390
391
|
if (options.link_header_preload && !state.prerendering) {
|
|
391
392
|
add_preload = (path) =>
|
|
392
393
|
link_headers.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
|
|
@@ -411,12 +412,6 @@ export async function render_response({
|
|
|
411
412
|
|
|
412
413
|
const blocks = [];
|
|
413
414
|
|
|
414
|
-
// when serving a prerendered page in an app that uses $app/env/public, we must
|
|
415
|
-
// import the env.js module so that it evaluates before any user code can evaluate.
|
|
416
|
-
// TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed
|
|
417
|
-
// https://github.com/sveltejs/kit/pull/11601
|
|
418
|
-
const load_env_eagerly = client.uses_env_dynamic_public && state.prerendering; // TODO implement uses_env_dynamic_public
|
|
419
|
-
|
|
420
415
|
const properties = [`base: ${base_expression}`];
|
|
421
416
|
|
|
422
417
|
if (paths.assets) {
|
|
@@ -438,7 +433,7 @@ export async function render_response({
|
|
|
438
433
|
|
|
439
434
|
if (Object.keys(options.hooks.transport).length > 0) {
|
|
440
435
|
if (client.inline) {
|
|
441
|
-
app_declaration = `const app =
|
|
436
|
+
app_declaration = `const app = ${global}.app.app;`;
|
|
442
437
|
} else if (client.app) {
|
|
443
438
|
app_declaration = `const app = await import(${s(prefixed(client.app))});`;
|
|
444
439
|
} else {
|
|
@@ -501,7 +496,7 @@ export async function render_response({
|
|
|
501
496
|
`error: ${serialized.error}`
|
|
502
497
|
];
|
|
503
498
|
|
|
504
|
-
if (status !== 200) {
|
|
499
|
+
if (status !== 200 && !error) {
|
|
505
500
|
hydrate.push(`status: ${status}`);
|
|
506
501
|
}
|
|
507
502
|
|
|
@@ -555,12 +550,9 @@ export async function render_response({
|
|
|
555
550
|
}
|
|
556
551
|
|
|
557
552
|
if (options.service_worker) {
|
|
558
|
-
let opts =
|
|
553
|
+
let opts = ", { type: 'module' }";
|
|
559
554
|
if (options.service_worker_options != null) {
|
|
560
|
-
const service_worker_options = { ...options.service_worker_options };
|
|
561
|
-
if (__SVELTEKIT_DEV__) {
|
|
562
|
-
service_worker_options.type = 'module';
|
|
563
|
-
}
|
|
555
|
+
const service_worker_options = { ...options.service_worker_options, type: 'module' };
|
|
564
556
|
opts = `, ${s(service_worker_options)}`;
|
|
565
557
|
}
|
|
566
558
|
|
|
@@ -579,11 +571,8 @@ export async function render_response({
|
|
|
579
571
|
}`);
|
|
580
572
|
}
|
|
581
573
|
|
|
582
|
-
// we need to eagerly import the Vite client module in development to ensure
|
|
583
|
-
// that Vite global constant replacements are initialised before our code runs
|
|
584
574
|
const init_app = `
|
|
585
575
|
{
|
|
586
|
-
${DEV ? `import('${paths.base}/@vite/client')` : ''}
|
|
587
576
|
${blocks.join('\n\n\t\t\t\t\t')}
|
|
588
577
|
}
|
|
589
578
|
`;
|