@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.
Files changed (91) hide show
  1. package/package.json +23 -18
  2. package/src/core/adapt/builder.js +29 -43
  3. package/src/core/adapt/index.js +1 -4
  4. package/src/core/config/index.js +135 -71
  5. package/src/core/config/options.js +291 -244
  6. package/src/core/config/types.d.ts +1 -1
  7. package/src/core/env.js +120 -5
  8. package/src/core/generate_manifest/index.js +12 -15
  9. package/src/core/postbuild/analyse.js +8 -12
  10. package/src/core/postbuild/crawl.js +22 -6
  11. package/src/core/postbuild/prerender.js +40 -23
  12. package/src/core/sync/create_manifest_data/index.js +29 -49
  13. package/src/core/sync/write_client_manifest.js +14 -14
  14. package/src/core/sync/write_non_ambient.js +10 -7
  15. package/src/core/sync/write_root.js +9 -30
  16. package/src/core/sync/write_server.js +13 -11
  17. package/src/core/sync/write_tsconfig.js +2 -4
  18. package/src/core/sync/write_types/index.js +11 -10
  19. package/src/exports/index.js +34 -12
  20. package/src/exports/internal/client.js +5 -0
  21. package/src/exports/internal/env.js +1 -1
  22. package/src/exports/internal/index.js +1 -90
  23. package/src/exports/internal/{event.js → server/event.js} +1 -2
  24. package/src/exports/internal/server/index.js +33 -0
  25. package/src/exports/internal/shared.js +89 -0
  26. package/src/exports/node/index.js +58 -7
  27. package/src/exports/params.js +63 -0
  28. package/src/exports/public.d.ts +299 -145
  29. package/src/exports/url.js +84 -0
  30. package/src/exports/vite/build/build_server.js +6 -1
  31. package/src/exports/vite/dev/index.js +38 -37
  32. package/src/exports/vite/index.js +498 -353
  33. package/src/exports/vite/preview/index.js +16 -8
  34. package/src/exports/vite/utils.js +7 -11
  35. package/src/runtime/app/env/types.d.ts +1 -1
  36. package/src/runtime/app/forms.js +22 -5
  37. package/src/runtime/app/paths/client.js +4 -10
  38. package/src/runtime/app/paths/public.d.ts +0 -28
  39. package/src/runtime/app/paths/server.js +8 -4
  40. package/src/runtime/app/server/remote/form.js +10 -3
  41. package/src/runtime/app/server/remote/query.js +9 -18
  42. package/src/runtime/app/server/remote/requested.js +8 -4
  43. package/src/runtime/app/server/remote/shared.js +5 -7
  44. package/src/runtime/app/state/client.js +1 -2
  45. package/src/runtime/app/stores.js +13 -76
  46. package/src/runtime/client/client.js +205 -167
  47. package/src/runtime/client/fetcher.js +3 -2
  48. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  49. package/src/runtime/client/remote-functions/form.svelte.js +122 -31
  50. package/src/runtime/client/remote-functions/prerender.svelte.js +17 -4
  51. package/src/runtime/client/remote-functions/query/index.js +3 -2
  52. package/src/runtime/client/remote-functions/query/instance.svelte.js +27 -11
  53. package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
  54. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +42 -15
  55. package/src/runtime/client/remote-functions/shared.svelte.js +18 -9
  56. package/src/runtime/client/state.svelte.js +66 -49
  57. package/src/runtime/client/types.d.ts +10 -2
  58. package/src/runtime/client/utils.js +0 -96
  59. package/src/runtime/form-utils.js +99 -18
  60. package/src/runtime/server/cookie.js +22 -33
  61. package/src/runtime/server/csrf.js +65 -0
  62. package/src/runtime/server/data/index.js +7 -7
  63. package/src/runtime/server/env_module.js +0 -5
  64. package/src/runtime/server/index.js +2 -2
  65. package/src/runtime/server/page/actions.js +41 -26
  66. package/src/runtime/server/page/index.js +2 -1
  67. package/src/runtime/server/page/load_data.js +1 -1
  68. package/src/runtime/server/page/render.js +36 -47
  69. package/src/runtime/server/page/respond_with_error.js +7 -8
  70. package/src/runtime/server/page/server_routing.js +13 -9
  71. package/src/runtime/server/remote.js +85 -39
  72. package/src/runtime/server/respond.js +92 -58
  73. package/src/runtime/server/utils.js +7 -7
  74. package/src/runtime/telemetry/otel.js +1 -1
  75. package/src/types/ambient.d.ts +10 -5
  76. package/src/types/global-private.d.ts +4 -4
  77. package/src/types/internal.d.ts +17 -20
  78. package/src/types/private.d.ts +33 -1
  79. package/src/types/synthetic/$lib.md +1 -1
  80. package/src/utils/error.js +12 -4
  81. package/src/utils/mime.js +9 -0
  82. package/src/utils/params.js +66 -0
  83. package/src/utils/routing.js +84 -38
  84. package/src/utils/shared-iterator.js +5 -0
  85. package/src/utils/streaming.js +14 -4
  86. package/src/utils/url.js +20 -2
  87. package/src/version.js +1 -1
  88. package/types/index.d.ts +434 -269
  89. package/types/index.d.ts.map +19 -15
  90. package/src/exports/internal/server.js +0 -22
  91. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { parse, serialize } from 'cookie';
1
+ import { parseCookie, parseSetCookie, stringifySetCookie } from 'cookie';
2
2
  import { DEV } from 'esm-env';
3
3
  import { normalize_path, resolve } from '../../utils/url.js';
4
4
  import { add_data_suffix } from '../pathname.js';
@@ -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
- parse(header, { decode: (value) => value })
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').SerializeOptions} */
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 = parse(header, { decode: opts?.decode });
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 = parse(header, { decode: opts?.decode });
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
- * @param {string} name
163
- * @param {string} value
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 serialize(name, value, { ...defaults, ...options, path });
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
- parse(header, { decode: (value) => value })
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 serialized = serialize(name, value, cookie.options);
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 { name, value, options } = new_cookie;
300
- headers.append('set-cookie', serialize(name, value, options));
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('set-cookie', serialize(name, value, { ...options, path }));
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 { HttpError, SvelteKitError, Redirect } from '@sveltejs/kit/internal';
2
+ import { Redirect } from '@sveltejs/kit/internal';
3
3
  import { normalize_error } from '../../../utils/error.js';
4
4
  import { once } from '../../../utils/functions.js';
5
5
  import { server_data_serializer_json } from '../page/data_serializer.js';
@@ -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: await handle_error_and_jsonify(event, event_state, options, 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
- return json_response(await handle_error_and_jsonify(event, event_state, options, error), 500);
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 { get_status, normalize_error } from '../../../utils/error.js';
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: await handle_error_and_jsonify(event, event_state, options, no_actions_error)
44
+ error
43
45
  },
44
46
  {
45
- status: no_actions_error.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
- type: 'failure',
67
- status: data.status,
68
- // @ts-expect-error we assign a string to what is supposed to be an object. That's ok
69
- // because we don't use the object outside, and this way we have better code navigation
70
- // through knowing where the related interface is used.
71
- data: stringify_action_response(
72
- data.data,
73
- /** @type {string} */ (event.route.id),
74
- options.hooks.transport
75
- )
76
- });
77
- } else {
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: data ? 200 : 204,
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: await handle_error_and_jsonify(
100
- event,
101
- event_state,
102
- options,
103
- check_incorrect_fail_use(err)
104
- )
116
+ error: transformed
105
117
  },
106
118
  {
107
- status: get_status(err)
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
- const action = actions[name];
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.kit.csp.mode === "nonce"');
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
- const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2);
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 = get_status(e);
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
- const rendered =
242
- options.async && 'then' in maybe_promise
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
- const { head, html, css, hashes } = /** @type {ReturnType<typeof options.root.render>} */ (
259
- options.async ? await rendered : rendered
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, html, css, hashes };
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: '', html: '', css: { code: '', map: null }, hashes: { script: [] } };
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.html;
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
- if (client.uses_env_dynamic_public && state.prerendering) {
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 kit.output.preloadStrategy option for details on why we have multiple options here
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 = __sveltekit_${options.version_hash}.app.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 = __SVELTEKIT_DEV__ ? ", { type: 'module' }" : '';
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
  `;