@sveltejs/kit 3.0.0-next.3 → 3.0.0-next.6

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 (68) hide show
  1. package/package.json +17 -17
  2. package/src/core/adapt/builder.js +18 -4
  3. package/src/core/adapt/index.js +1 -4
  4. package/src/core/config/index.js +64 -5
  5. package/src/core/config/options.js +73 -21
  6. package/src/core/env.js +35 -4
  7. package/src/core/postbuild/analyse.js +8 -12
  8. package/src/core/postbuild/crawl.js +22 -6
  9. package/src/core/postbuild/prerender.js +40 -23
  10. package/src/core/sync/create_manifest_data/index.js +23 -5
  11. package/src/core/sync/write_client_manifest.js +7 -0
  12. package/src/core/sync/write_server.js +13 -10
  13. package/src/core/sync/write_tsconfig.js +2 -4
  14. package/src/exports/index.js +32 -10
  15. package/src/exports/internal/env.js +1 -1
  16. package/src/exports/internal/index.js +6 -5
  17. package/src/exports/node/index.js +57 -6
  18. package/src/exports/public.d.ts +218 -114
  19. package/src/exports/vite/build/build_server.js +6 -1
  20. package/src/exports/vite/dev/index.js +10 -20
  21. package/src/exports/vite/index.js +306 -222
  22. package/src/exports/vite/preview/index.js +15 -7
  23. package/src/exports/vite/utils.js +8 -10
  24. package/src/runtime/app/env/types.d.ts +1 -1
  25. package/src/runtime/app/forms.js +22 -5
  26. package/src/runtime/app/paths/client.js +1 -3
  27. package/src/runtime/app/paths/public.d.ts +0 -28
  28. package/src/runtime/app/paths/server.js +7 -3
  29. package/src/runtime/app/server/remote/form.js +10 -3
  30. package/src/runtime/app/server/remote/query.js +3 -6
  31. package/src/runtime/app/server/remote/requested.js +8 -4
  32. package/src/runtime/app/server/remote/shared.js +5 -7
  33. package/src/runtime/client/client.js +190 -95
  34. package/src/runtime/client/fetcher.js +3 -2
  35. package/src/runtime/client/remote-functions/form.svelte.js +134 -24
  36. package/src/runtime/client/remote-functions/prerender.svelte.js +8 -2
  37. package/src/runtime/client/remote-functions/query/index.js +3 -2
  38. package/src/runtime/client/remote-functions/query/instance.svelte.js +26 -10
  39. package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
  40. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +26 -9
  41. package/src/runtime/client/remote-functions/shared.svelte.js +17 -7
  42. package/src/runtime/client/types.d.ts +9 -1
  43. package/src/runtime/client/utils.js +1 -1
  44. package/src/runtime/form-utils.js +84 -16
  45. package/src/runtime/server/cookie.js +22 -33
  46. package/src/runtime/server/csrf.js +65 -0
  47. package/src/runtime/server/data/index.js +7 -7
  48. package/src/runtime/server/env_module.js +0 -5
  49. package/src/runtime/server/index.js +1 -1
  50. package/src/runtime/server/page/actions.js +41 -26
  51. package/src/runtime/server/page/index.js +2 -1
  52. package/src/runtime/server/page/render.js +21 -23
  53. package/src/runtime/server/page/respond_with_error.js +7 -8
  54. package/src/runtime/server/remote.js +64 -27
  55. package/src/runtime/server/respond.js +81 -50
  56. package/src/runtime/server/utils.js +7 -7
  57. package/src/runtime/telemetry/otel.js +1 -1
  58. package/src/types/ambient.d.ts +5 -4
  59. package/src/types/global-private.d.ts +4 -4
  60. package/src/types/internal.d.ts +8 -10
  61. package/src/types/private.d.ts +33 -1
  62. package/src/types/synthetic/$lib.md +1 -1
  63. package/src/utils/error.js +11 -3
  64. package/src/utils/shared-iterator.js +5 -0
  65. package/src/utils/url.js +99 -2
  66. package/src/version.js +1 -1
  67. package/types/index.d.ts +340 -186
  68. package/types/index.d.ts.map +10 -9
@@ -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) {
@@ -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
@@ -374,7 +378,13 @@ export async function render_response({
374
378
  if (page_config.csr && client) {
375
379
  const route = client.routes?.find((r) => r.id === event.route.id) ?? null;
376
380
 
377
- if (client.uses_env_dynamic_public && state.prerendering) {
381
+ // when serving a prerendered page in an app that uses runtime public env vars, we must
382
+ // import the env.js module so that it evaluates before any user code can evaluate.
383
+ // TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed
384
+ // https://github.com/sveltejs/kit/pull/11601
385
+ const load_env_eagerly = client.uses_env_dynamic_public && !!state.prerendering;
386
+
387
+ if (load_env_eagerly) {
378
388
  modulepreloads.add(`${paths.app_dir}/env.js`);
379
389
  }
380
390
 
@@ -386,7 +396,7 @@ export async function render_response({
386
396
  /** @type {(path: string) => void} */
387
397
  let add_preload;
388
398
 
389
- // see the kit.output.preloadStrategy option for details on why we have multiple options here
399
+ // see the output.preloadStrategy option for details on why we have multiple options here
390
400
  if (options.link_header_preload && !state.prerendering) {
391
401
  add_preload = (path) =>
392
402
  link_headers.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
@@ -411,12 +421,6 @@ export async function render_response({
411
421
 
412
422
  const blocks = [];
413
423
 
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
424
  const properties = [`base: ${base_expression}`];
421
425
 
422
426
  if (paths.assets) {
@@ -438,7 +442,7 @@ export async function render_response({
438
442
 
439
443
  if (Object.keys(options.hooks.transport).length > 0) {
440
444
  if (client.inline) {
441
- app_declaration = `const app = __sveltekit_${options.version_hash}.app.app;`;
445
+ app_declaration = `const app = ${global}.app.app;`;
442
446
  } else if (client.app) {
443
447
  app_declaration = `const app = await import(${s(prefixed(client.app))});`;
444
448
  } else {
@@ -501,7 +505,7 @@ export async function render_response({
501
505
  `error: ${serialized.error}`
502
506
  ];
503
507
 
504
- if (status !== 200) {
508
+ if (status !== 200 && !error) {
505
509
  hydrate.push(`status: ${status}`);
506
510
  }
507
511
 
@@ -555,12 +559,9 @@ export async function render_response({
555
559
  }
556
560
 
557
561
  if (options.service_worker) {
558
- let opts = __SVELTEKIT_DEV__ ? ", { type: 'module' }" : '';
562
+ let opts = ", { type: 'module' }";
559
563
  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
- }
564
+ const service_worker_options = { ...options.service_worker_options, type: 'module' };
564
565
  opts = `, ${s(service_worker_options)}`;
565
566
  }
566
567
 
@@ -579,11 +580,8 @@ export async function render_response({
579
580
  }`);
580
581
  }
581
582
 
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
583
  const init_app = `
585
584
  {
586
- ${DEV ? `import('${paths.base}/@vite/client')` : ''}
587
585
  ${blocks.join('\n\n\t\t\t\t\t')}
588
586
  }
589
587
  `;
@@ -2,7 +2,6 @@ import { Redirect } from '@sveltejs/kit/internal';
2
2
  import { render_response } from './render.js';
3
3
  import { load_data, load_server_data } from './load_data.js';
4
4
  import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
5
- import { get_status } from '../../../utils/error.js';
6
5
  import { PageNodes } from '../../../utils/page_nodes.js';
7
6
  import { server_data_serializer } from './data_serializer.js';
8
7
 
@@ -90,6 +89,8 @@ export async function respond_with_error({
90
89
  );
91
90
  }
92
91
 
92
+ const transformed = await handle_error_and_jsonify(event, event_state, options, error);
93
+
93
94
  return await render_response({
94
95
  options,
95
96
  manifest,
@@ -98,8 +99,8 @@ export async function respond_with_error({
98
99
  ssr,
99
100
  csr
100
101
  },
101
- status,
102
- error: await handle_error_and_jsonify(event, event_state, options, error),
102
+ status: transformed.status,
103
+ error: transformed,
103
104
  branch,
104
105
  error_components: [],
105
106
  fetched,
@@ -115,10 +116,8 @@ export async function respond_with_error({
115
116
  return redirect_response(e.status, e.location);
116
117
  }
117
118
 
118
- return static_error_page(
119
- options,
120
- get_status(e),
121
- (await handle_error_and_jsonify(event, event_state, options, e)).message
122
- );
119
+ const transformed = await handle_error_and_jsonify(event, event_state, options, e);
120
+
121
+ return static_error_page(options, transformed.status, transformed.message);
123
122
  }
124
123
  }
@@ -2,7 +2,7 @@
2
2
  /** @import { RemoteFormInternals, RemoteFunctionData, RemoteFunctionResponse, RemoteInternals, RequestState, SSROptions } from 'types' */
3
3
 
4
4
  import { json, error } from '@sveltejs/kit';
5
- import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
5
+ import { Redirect, SvelteKitError } from '@sveltejs/kit/internal';
6
6
  import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
7
7
  import { app_dir, base } from '$app/paths/internal/server';
8
8
  import { is_form_content_type } from '../../utils/http.js';
@@ -14,6 +14,13 @@ import { DEV } from 'esm-env';
14
14
  import { record_span } from '../telemetry/record_span.js';
15
15
  import { deserialize_binary_form } from '../form-utils.js';
16
16
 
17
+ /**
18
+ * How long (in milliseconds) to wait after the last message was sent before
19
+ * sending a `: keep-alive` SSE comment, to prevent proxies/load balancers with
20
+ * an idle timeout from closing an otherwise-quiet `query.live` connection.
21
+ */
22
+ const KEEP_ALIVE_INTERVAL = 30_000;
23
+
17
24
  /** @type {typeof handle_remote_call_internal} */
18
25
  export async function handle_remote_call(event, state, options, manifest, id) {
19
26
  return record_span({
@@ -41,10 +48,10 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
41
48
  const [hash, name, additional_args] = id.split('/');
42
49
  const remotes = manifest._.remotes;
43
50
 
44
- if (!remotes[hash]) error(404);
51
+ if (!Object.hasOwn(remotes, hash)) error(404);
45
52
 
46
53
  const module = await remotes[hash]();
47
- const fn = module.default[name];
54
+ const fn = Object.hasOwn(module.default, name) ? module.default[name] : undefined;
48
55
 
49
56
  if (!fn) error(404);
50
57
 
@@ -57,6 +64,9 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
57
64
  'sveltekit.remote.call.name': internals.name
58
65
  });
59
66
 
67
+ /** @type {HeadersInit | undefined} */
68
+ const headers = state.prerendering ? undefined : { 'cache-control': 'private, no-store' };
69
+
60
70
  try {
61
71
  /** @type {RemoteFunctionData} */
62
72
  const data = {};
@@ -79,22 +89,43 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
79
89
 
80
90
  const encoder = new TextEncoder();
81
91
 
92
+ let closed = false;
93
+
94
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
95
+ let keep_alive;
96
+
97
+ /**
98
+ * (Re)schedule the keep-alive comment. Called whenever a message is sent, so
99
+ * that a keep-alive is only emitted once `KEEP_ALIVE_INTERVAL` has elapsed
100
+ * without any other activity.
101
+ * @param {ReadableStreamDefaultController} controller
102
+ */
103
+ function schedule_keep_alive(controller) {
104
+ clearTimeout(keep_alive);
105
+ keep_alive = setTimeout(() => {
106
+ if (closed || event.request.signal.aborted) return;
107
+ // SSE comments (lines starting with `:`) are ignored by the client
108
+ controller.enqueue(encoder.encode(': keep-alive\n\n'));
109
+ schedule_keep_alive(controller);
110
+ }, KEEP_ALIVE_INTERVAL);
111
+ }
112
+
82
113
  /**
83
114
  * @param {ReadableStreamDefaultController} controller
84
115
  * @param {any} payload
85
116
  */
86
117
  function send(controller, payload) {
87
118
  controller.enqueue(encoder.encode('data: ' + JSON.stringify(payload) + '\n\n'));
119
+ schedule_keep_alive(controller);
88
120
  }
89
121
 
90
- let closed = false;
91
-
92
122
  /** @type {string | undefined} */
93
123
  let result = undefined;
94
124
 
95
125
  async function cancel() {
96
126
  if (closed) return;
97
127
  closed = true;
128
+ clearTimeout(keep_alive);
98
129
  await generator.return(undefined);
99
130
  }
100
131
 
@@ -102,6 +133,9 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
102
133
 
103
134
  return new Response(
104
135
  new ReadableStream({
136
+ start(controller) {
137
+ schedule_keep_alive(controller);
138
+ },
105
139
  async pull(controller) {
106
140
  if (event.request.signal.aborted) {
107
141
  await cancel();
@@ -137,15 +171,16 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
137
171
  location: error.location
138
172
  });
139
173
  } else {
140
- const status =
141
- error instanceof HttpError || error instanceof SvelteKitError
142
- ? error.status
143
- : 500;
174
+ const transformed = await handle_error_and_jsonify(
175
+ event,
176
+ state,
177
+ options,
178
+ error
179
+ );
144
180
 
145
181
  send(controller, {
146
182
  type: 'error',
147
- error: await handle_error_and_jsonify(event, state, options, error),
148
- status
183
+ error: transformed
149
184
  });
150
185
  }
151
186
  }
@@ -226,7 +261,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
226
261
  /** @type {RemoteFunctionResponse} */ ({
227
262
  type: 'result',
228
263
  data: stringify(data, transport)
229
- })
264
+ }),
265
+ { headers }
230
266
  );
231
267
  }
232
268
 
@@ -275,7 +311,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
275
311
  /** @type {RemoteFunctionResponse} */ ({
276
312
  type: 'result',
277
313
  data: stringify(data, transport)
278
- })
314
+ }),
315
+ { headers }
279
316
  );
280
317
  } catch (error) {
281
318
  if (error instanceof Redirect) {
@@ -285,23 +322,22 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
285
322
  /** @type {RemoteFunctionResponse} */ ({
286
323
  type: 'result',
287
324
  data: stringify(data, transport)
288
- })
325
+ }),
326
+ { headers }
289
327
  );
290
328
  }
291
329
 
292
- const status =
293
- error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
330
+ const transformed = await handle_error_and_jsonify(event, state, options, error);
294
331
 
295
332
  return json(
296
333
  /** @type {RemoteFunctionResponse} */ ({
297
334
  type: 'error',
298
- error: await handle_error_and_jsonify(event, state, options, error),
299
- status
335
+ error: transformed
300
336
  }),
301
337
  {
302
338
  // By setting a non-200 during prerendering we fail the prerender process (unless handleHttpError handles it).
303
339
  // Errors at runtime will be passed to the client and are handled there
304
- status: state.prerendering ? status : undefined,
340
+ status: state.prerendering ? transformed.status : undefined,
305
341
  headers: {
306
342
  'cache-control': 'private, no-store'
307
343
  }
@@ -322,13 +358,10 @@ export async function collect_remote_data(data, event, state, options) {
322
358
  /**
323
359
  *
324
360
  * @param {unknown} error
325
- * @returns {Promise<[status: number, error: App.Error]>}
361
+ * @returns {Promise<App.Error>}
326
362
  */
327
- async function convert_error(error) {
328
- const status =
329
- error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
330
-
331
- return [status, await handle_error_and_jsonify(event, state, options, error)];
363
+ function convert_error(error) {
364
+ return handle_error_and_jsonify(event, state, options, error);
332
365
  }
333
366
 
334
367
  /** @type {Promise<any>[]} */
@@ -468,9 +501,11 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
468
501
  const [hash, name, ...rest] = id.split('/');
469
502
  const action_id = rest.join('/');
470
503
  const remotes = manifest._.remotes;
471
- const module = await remotes[hash]?.();
504
+ const module = Object.hasOwn(remotes, hash) ? await remotes[hash]() : undefined;
472
505
 
473
- let form = /** @type {RemoteForm<any, any>} */ (module?.default[name]);
506
+ let form = /** @type {RemoteForm<any, any>} */ (
507
+ module && Object.hasOwn(module.default, name) ? module.default[name] : undefined
508
+ );
474
509
 
475
510
  if (!form) {
476
511
  event.setHeaders({
@@ -480,6 +515,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
480
515
  });
481
516
  return {
482
517
  type: 'error',
518
+ // We're lying a bit with the types here; this will be transformed into a proper App.Error object later
483
519
  error: new SvelteKitError(
484
520
  405,
485
521
  'Method Not Allowed',
@@ -526,6 +562,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
526
562
 
527
563
  return {
528
564
  type: 'error',
565
+ // @ts-expect-error We're lying a bit with the types here; this will be transformed into a proper App.Error object later
529
566
  error: check_incorrect_fail_use(err)
530
567
  };
531
568
  }
@@ -8,7 +8,7 @@ import { is_endpoint_request, render_endpoint } from './endpoint.js';
8
8
  import { render_page } from './page/index.js';
9
9
  import { render_response } from './page/render.js';
10
10
  import { respond_with_error } from './page/respond_with_error.js';
11
- import { is_form_content_type } from '../../utils/http.js';
11
+ import { get_self_origin, is_csrf_forbidden, is_remote_forbidden } from './csrf.js';
12
12
  import {
13
13
  handle_fatal_error,
14
14
  has_prerendered_path,
@@ -72,21 +72,26 @@ export async function internal_respond(request, options, manifest, state) {
72
72
 
73
73
  if (!DEV) {
74
74
  const request_origin = request.headers.get('origin');
75
+ const self_origin = get_self_origin(options.paths_origin, url.origin);
75
76
 
76
77
  if (remote_id) {
77
- if (request.method !== 'GET' && request_origin !== url.origin) {
78
+ if (
79
+ is_remote_forbidden({
80
+ request,
81
+ request_origin,
82
+ self_origin
83
+ })
84
+ ) {
78
85
  const message = 'Cross-site remote requests are forbidden';
79
86
  return json({ message }, { status: 403 });
80
87
  }
81
88
  } else if (options.csrf_check_origin) {
82
- const forbidden =
83
- is_form_content_type(request) &&
84
- (request.method === 'POST' ||
85
- request.method === 'PUT' ||
86
- request.method === 'PATCH' ||
87
- request.method === 'DELETE') &&
88
- request_origin !== url.origin &&
89
- (!request_origin || !options.csrf_trusted_origins.includes(request_origin));
89
+ const forbidden = is_csrf_forbidden({
90
+ request,
91
+ request_origin,
92
+ self_origin,
93
+ trusted_origins: options.csrf_trusted_origins
94
+ });
90
95
 
91
96
  if (forbidden) {
92
97
  const message = `Cross-site ${request.method} form submissions are forbidden`;
@@ -319,7 +324,7 @@ export async function internal_respond(request, options, manifest, state) {
319
324
  return resolve_route(resolved_path, new URL(request.url), manifest);
320
325
  }
321
326
 
322
- if (resolved_path === `/${app_dir}/env.js` || resolved_path === `/${app_dir}/env.script.js`) {
327
+ if (resolved_path === `/${app_dir}/env.js`) {
323
328
  return get_public_env(request);
324
329
  }
325
330
 
@@ -619,54 +624,80 @@ export async function internal_respond(request, options, manifest, state) {
619
624
  invalidated_data_nodes,
620
625
  trailing_slash
621
626
  );
622
- } else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
623
- response = await render_endpoint(event, event_state, await route.endpoint(), state);
624
- } else if (route.page) {
625
- if (!page_nodes) {
626
- throw new Error('page_nodes not found. This should never happen');
627
- } else if (page_methods.has(method)) {
628
- response = await render_page(
629
- event,
630
- event_state,
631
- route.page,
632
- options,
633
- manifest,
634
- state,
635
- page_nodes,
636
- resolve_opts
637
- );
638
- } else {
639
- const allowed_methods = new Set(allowed_page_methods);
640
- const node = await manifest._.nodes[route.page.leaf]();
641
- if (node?.server?.actions) {
642
- allowed_methods.add('POST');
627
+ } else {
628
+ let endpoint;
629
+ if (
630
+ route.endpoint &&
631
+ (!route.page || (!state.prerendering && is_endpoint_request(event)))
632
+ ) {
633
+ endpoint = await route.endpoint();
634
+
635
+ // Prefer rendering the page if the endpoint can't handle this GET or HEAD request
636
+ if (route.page && (method === 'GET' || method === 'HEAD')) {
637
+ const endpoint_can_handle = !!(
638
+ endpoint.GET ||
639
+ endpoint.fallback ||
640
+ (method === 'HEAD' && endpoint.HEAD)
641
+ );
642
+ if (!endpoint_can_handle) {
643
+ endpoint = undefined;
644
+ }
643
645
  }
646
+ }
644
647
 
645
- if (method === 'OPTIONS') {
646
- // This will deny CORS preflight requests implicitly because we don't
647
- // add the required CORS headers to the response.
648
- response = new Response(null, {
649
- status: 204,
650
- headers: {
651
- allow: Array.from(allowed_methods.values()).join(', ')
652
- }
653
- });
648
+ if (endpoint) {
649
+ response = await render_endpoint(event, event_state, endpoint, state);
650
+ } else if (route.page) {
651
+ if (!page_nodes) {
652
+ throw new Error('page_nodes not found. This should never happen');
653
+ } else if (page_methods.has(method)) {
654
+ response = await render_page(
655
+ event,
656
+ event_state,
657
+ route.page,
658
+ options,
659
+ manifest,
660
+ state,
661
+ page_nodes,
662
+ resolve_opts
663
+ );
654
664
  } else {
655
- const mod = [...allowed_methods].reduce((acc, curr) => {
656
- acc[curr] = true;
657
- return acc;
658
- }, /** @type {Record<string, any>} */ ({}));
659
- response = method_not_allowed(mod, method);
665
+ const allowed_methods = new Set(allowed_page_methods);
666
+ const node = await manifest._.nodes[route.page.leaf]();
667
+ if (node?.server?.actions) {
668
+ allowed_methods.add('POST');
669
+ }
670
+
671
+ if (method === 'OPTIONS') {
672
+ // This will deny CORS preflight requests implicitly because we don't
673
+ // add the required CORS headers to the response.
674
+ response = new Response(null, {
675
+ status: 204,
676
+ headers: {
677
+ allow: Array.from(allowed_methods.values()).join(', ')
678
+ }
679
+ });
680
+ } else {
681
+ const mod = [...allowed_methods].reduce((acc, curr) => {
682
+ acc[curr] = true;
683
+ return acc;
684
+ }, /** @type {Record<string, any>} */ ({}));
685
+ response = method_not_allowed(mod, method);
686
+ }
660
687
  }
688
+ } else {
689
+ // a route will always have a page or an endpoint, but TypeScript doesn't know that
690
+ throw new Error('Route is neither page nor endpoint. This should never happen');
661
691
  }
662
- } else {
663
- // a route will always have a page or an endpoint, but TypeScript doesn't know that
664
- throw new Error('Route is neither page nor endpoint. This should never happen');
665
692
  }
666
693
 
667
694
  // If the route contains a page and an endpoint, we need to add a
668
695
  // `Vary: Accept` header to the response because of browser caching
669
- if (request.method === 'GET' && route.page && route.endpoint) {
696
+ if (
697
+ (request.method === 'GET' || request.method === 'HEAD') &&
698
+ route.page &&
699
+ route.endpoint
700
+ ) {
670
701
  const vary = response.headers
671
702
  .get('vary')
672
703
  ?.split(',')
@@ -75,8 +75,8 @@ export function static_error_page(options, status, message) {
75
75
  */
76
76
  export async function handle_fatal_error(event, state, options, error) {
77
77
  error = error instanceof HttpError ? error : coalesce_to_error(error);
78
- const status = get_status(error);
79
78
  const body = await handle_error_and_jsonify(event, state, options, error);
79
+ const status = body.status;
80
80
 
81
81
  // ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
82
82
  const type = negotiate(event.request.headers.get('accept') || 'text/html', [
@@ -113,11 +113,11 @@ export async function handle_error_and_jsonify(event, state, options, error) {
113
113
  const status = get_status(error);
114
114
  const message = get_message(error);
115
115
 
116
- return (
117
- (await with_request_store({ event, state }, () =>
118
- options.hooks.handleError({ error, event, status, message })
119
- )) ?? { message }
120
- );
116
+ const body = (await with_request_store({ event, state }, () =>
117
+ options.hooks.handleError({ error, event, status, message })
118
+ )) ?? { message };
119
+
120
+ return { ...body, status: get_status(body, error) };
121
121
  }
122
122
 
123
123
  /**
@@ -228,7 +228,7 @@ export function clean_up_stack_trace(error) {
228
228
  return line.replace(/\((.+)(:\d+:\d+)\)$/, (_, file, loc) => `(${relative(file)}${loc})`);
229
229
  });
230
230
 
231
- // progressive enhancement for people who haven't configured kit.files.src to something else
231
+ // progressive enhancement for people who haven't configured files.src to something else
232
232
  const last_line_from_src_code = stack_trace.findLastIndex((line) => /\(src[\\/]/.test(line));
233
233
 
234
234
  if (last_line_from_src_code === -1) {
@@ -15,7 +15,7 @@ if (__SVELTEKIT_SERVER_TRACING_ENABLED__) {
15
15
  })
16
16
  .catch(() => {
17
17
  throw new Error(
18
- 'Tracing is enabled (see `config.kit.experimental.instrumentation.server` in your svelte.config.js), but `@opentelemetry/api` is not available. This error will likely resolve itself when you set up your tracing instrumentation in `instrumentation.server.js`. For more information, see https://svelte.dev/docs/kit/observability#opentelemetry-api'
18
+ 'Tracing is enabled (see the SvelteKit plugin `experimental.instrumentation.server` option in your vite.config.js), but `@opentelemetry/api` is not available. This error will likely resolve itself when you set up your tracing instrumentation in `instrumentation.server.js`. For more information, see https://svelte.dev/docs/kit/observability#opentelemetry-api'
19
19
  );
20
20
  });
21
21
  }
@@ -25,6 +25,7 @@ declare namespace App {
25
25
  * Defines the common shape of expected and unexpected errors. Expected errors are thrown using the `error` function. Unexpected errors are handled by the `handleError` hooks which should return this shape.
26
26
  */
27
27
  export interface Error {
28
+ status: number;
28
29
  message: string;
29
30
  }
30
31
 
@@ -56,8 +57,8 @@ declare namespace App {
56
57
  */
57
58
  declare module '$service-worker' {
58
59
  /**
59
- * The `base` path of the deployment. Typically this is equivalent to `config.kit.paths.base`, but it is calculated from `location.pathname` meaning that it will continue to work correctly if the site is deployed to a subdirectory.
60
- * Note that there is a `base` but no `assets`, since service workers cannot be used if `config.kit.paths.assets` is specified.
60
+ * The `base` path of the deployment. Typically this is equivalent to `config.paths.base`, but it is calculated from `location.pathname` meaning that it will continue to work correctly if the site is deployed to a subdirectory.
61
+ * Note that there is a `base` but no `assets`, since service workers cannot be used if `config.paths.assets` is specified.
61
62
  */
62
63
  export const base: string;
63
64
  /**
@@ -66,7 +67,7 @@ declare module '$service-worker' {
66
67
  */
67
68
  export const build: string[];
68
69
  /**
69
- * An array of URL strings representing the files in your static directory, or whatever directory is specified by `config.kit.files.assets`. You can customize which files are included from `static` directory using [`config.kit.serviceWorker.files`](https://svelte.dev/docs/kit/configuration#serviceWorker)
70
+ * An array of URL strings representing the files in your static directory, or whatever directory is specified by `config.files.assets`. You can customize which files are included from `static` directory using [`config.serviceWorker.files`](https://svelte.dev/docs/kit/configuration#serviceWorker)
70
71
  */
71
72
  export const files: string[];
72
73
  /**
@@ -75,7 +76,7 @@ declare module '$service-worker' {
75
76
  */
76
77
  export const prerendered: string[];
77
78
  /**
78
- * See [`config.kit.version`](https://svelte.dev/docs/kit/configuration#version). It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
79
+ * See [`config.version`](https://svelte.dev/docs/kit/configuration#version). It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
79
80
  */
80
81
  export const version: string;
81
82
  }
@@ -15,13 +15,13 @@ declare global {
15
15
  const __SVELTEKIT_PATHS_ASSETS__: string;
16
16
  const __SVELTEKIT_PATHS_BASE__: string;
17
17
  const __SVELTEKIT_PATHS_RELATIVE__: boolean;
18
- /** True if `config.kit.experimental.instrumentation.server` is `true` */
18
+ /** True if `config.experimental.instrumentation.server` is `true` */
19
19
  const __SVELTEKIT_SERVER_TRACING_ENABLED__: boolean;
20
- /** True if `config.kit.experimental.forkPreloads` is `true` */
20
+ /** True if `config.experimental.forkPreloads` is `true` */
21
21
  const __SVELTEKIT_FORK_PRELOADS__: boolean;
22
- /** True if `config.kit.router.resolution === 'client'` */
22
+ /** True if `config.router.resolution === 'client'` */
23
23
  const __SVELTEKIT_CLIENT_ROUTING__: boolean;
24
- /** True if `config.kit.router.type === 'hash'` */
24
+ /** True if `config.router.type === 'hash'` */
25
25
  const __SVELTEKIT_HASH_ROUTING__: boolean;
26
26
  /**
27
27
  * True if any node in the manifest has a server load function.