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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +3 -1
  4. package/src/core/adapt/builder.js +3 -6
  5. package/src/core/config/index.js +6 -1
  6. package/src/core/config/options.js +54 -104
  7. package/src/core/env.js +25 -11
  8. package/src/core/postbuild/fallback.js +2 -1
  9. package/src/core/postbuild/prerender.js +75 -28
  10. package/src/core/postbuild/queue.js +2 -1
  11. package/src/core/sync/sync.js +11 -14
  12. package/src/core/sync/utils.js +21 -1
  13. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
  14. package/src/core/sync/write_client_manifest.js +4 -12
  15. package/src/core/sync/write_env.js +6 -4
  16. package/src/core/sync/write_server.js +12 -17
  17. package/src/core/sync/write_tsconfig/index.js +245 -0
  18. package/src/core/sync/write_tsconfig/utils.js +162 -0
  19. package/src/core/sync/write_types/index.js +80 -88
  20. package/src/core/utils.js +28 -3
  21. package/src/exports/env/index.js +68 -3
  22. package/src/exports/internal/env.js +6 -3
  23. package/src/exports/internal/server/index.js +3 -1
  24. package/src/exports/params.js +9 -4
  25. package/src/exports/public.d.ts +69 -71
  26. package/src/exports/vite/dev/index.js +112 -56
  27. package/src/exports/vite/index.js +483 -258
  28. package/src/exports/vite/module_ids.js +2 -2
  29. package/src/exports/vite/preview/index.js +3 -2
  30. package/src/exports/vite/utils.js +21 -10
  31. package/src/runner.js +13 -0
  32. package/src/runtime/app/forms.js +4 -0
  33. package/src/runtime/app/manifest/index.js +1 -0
  34. package/src/runtime/app/paths/client.js +30 -30
  35. package/src/runtime/app/paths/internal/client.js +26 -0
  36. package/src/runtime/app/paths/server.js +23 -10
  37. package/src/runtime/app/paths/types.d.ts +11 -19
  38. package/src/runtime/app/server/index.js +1 -1
  39. package/src/runtime/app/server/remote/command.js +12 -7
  40. package/src/runtime/app/server/remote/form.js +29 -26
  41. package/src/runtime/app/server/remote/prerender.js +9 -2
  42. package/src/runtime/app/server/remote/query.js +6 -5
  43. package/src/runtime/app/server/remote/shared.js +48 -30
  44. package/src/runtime/app/service-worker/index.js +24 -0
  45. package/src/runtime/app/state/index.js +1 -1
  46. package/src/runtime/client/client.js +46 -9
  47. package/src/runtime/client/ndjson.js +1 -1
  48. package/src/runtime/client/remote-functions/form.svelte.js +43 -51
  49. package/src/runtime/client/remote-functions/query/index.js +2 -2
  50. package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
  51. package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
  52. package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
  53. package/src/runtime/client/state.svelte.js +54 -21
  54. package/src/runtime/client/stream.js +3 -2
  55. package/src/runtime/form-utils.js +89 -54
  56. package/src/runtime/server/cookie.js +1 -1
  57. package/src/runtime/server/data/index.js +32 -29
  58. package/src/runtime/server/errors.js +135 -0
  59. package/src/runtime/server/fetch.js +1 -1
  60. package/src/runtime/server/index.js +20 -10
  61. package/src/runtime/server/internal.js +25 -0
  62. package/src/runtime/server/page/actions.js +4 -3
  63. package/src/runtime/server/page/data_serializer.js +10 -10
  64. package/src/runtime/server/page/index.js +3 -2
  65. package/src/runtime/server/page/load_data.js +1 -1
  66. package/src/runtime/server/page/render.js +22 -35
  67. package/src/runtime/server/page/respond_with_error.js +6 -5
  68. package/src/runtime/server/{remote.js → remote-functions.js} +67 -36
  69. package/src/runtime/server/respond.js +58 -20
  70. package/src/runtime/server/sourcemaps.js +183 -0
  71. package/src/runtime/server/utils.js +12 -157
  72. package/src/types/ambient-private.d.ts +10 -0
  73. package/src/types/ambient.d.ts +22 -27
  74. package/src/types/global-private.d.ts +12 -5
  75. package/src/types/internal.d.ts +19 -9
  76. package/src/types/private.d.ts +8 -0
  77. package/src/utils/escape.js +9 -25
  78. package/src/utils/features.js +1 -1
  79. package/src/utils/fork.js +7 -2
  80. package/src/utils/page_nodes.js +3 -5
  81. package/src/utils/url.js +12 -0
  82. package/src/version.js +1 -1
  83. package/types/index.d.ts +152 -120
  84. package/types/index.d.ts.map +6 -1
  85. package/src/core/sync/write_ambient.js +0 -18
  86. package/src/core/sync/write_tsconfig.js +0 -258
  87. package/src/runtime/shared-server.js +0 -7
  88. /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
@@ -7,7 +7,8 @@ import { compact } from '../../../utils/array.js';
7
7
  import { get_status, normalize_error } from '../../../utils/error.js';
8
8
  import { noop } from '../../../utils/functions.js';
9
9
  import { add_data_suffix } from '../../pathname.js';
10
- import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
10
+ import { redirect_response } from '../utils.js';
11
+ import { static_error_page, handle_error_and_jsonify } from '../errors.js';
11
12
  import {
12
13
  handle_action_json_request,
13
14
  handle_action_request,
@@ -19,7 +20,7 @@ import { load_data, load_server_data } from './load_data.js';
19
20
  import { render_response } from './render.js';
20
21
  import { respond_with_error } from './respond_with_error.js';
21
22
  import { DEV } from 'esm-env';
22
- import { get_remote_action, handle_remote_form_post } from '../remote.js';
23
+ import { get_remote_action, handle_remote_form_post } from '../remote-functions.js';
23
24
  import { PageNodes } from '../../../utils/page_nodes.js';
24
25
 
25
26
  /**
@@ -465,7 +465,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
465
465
  const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
466
466
  if (!included) {
467
467
  throw new Error(
468
- `Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://svelte.dev/docs/kit/hooks#Server-hooks-handle (at ${event.route.id})`
468
+ `Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://svelte.dev/docs/kit/hooks#handle (at ${event.route.id})`
469
469
  );
470
470
  }
471
471
  }
@@ -14,14 +14,10 @@ import { create_server_routing_response, generate_route_object } from './server_
14
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
- import {
18
- count_non_ssi_comments,
19
- create_replacer,
20
- get_global_name,
21
- handle_error_and_jsonify
22
- } from '../utils.js';
17
+ import { count_non_ssi_comments, create_replacer, get_global_name } from '../utils.js';
18
+ import { handle_error_and_jsonify } from '../errors.js';
23
19
  import * as env from '__sveltekit/env';
24
- import { collect_remote_data } from '../remote.js';
20
+ import { collect_remote_data } from '../remote-functions.js';
25
21
  import Root from '../../components/root.svelte';
26
22
  import { render } from 'svelte/server';
27
23
 
@@ -308,6 +304,19 @@ export async function render_response({
308
304
  head.add_style(style, attributes);
309
305
  }
310
306
 
307
+ /**
308
+ * see the `output.linkHeaderPreload` option for details on why we have multiple options here
309
+ * @param {string} path
310
+ * @param {string[]} attributes
311
+ */
312
+ const add_preload = (path, attributes) => {
313
+ if (options.link_header_preload && !state.prerendering) {
314
+ link_headers.add(`<${encodeURI(path)}>; ${attributes.join('; ')}; nopush`);
315
+ } else {
316
+ head.add_link_tag(path, attributes);
317
+ }
318
+ };
319
+
311
320
  for (const dep of stylesheets) {
312
321
  const path = prefixed(dep);
313
322
 
@@ -332,18 +341,7 @@ export async function render_response({
332
341
  if (resolve_opts.preload({ type: 'font', path })) {
333
342
  const ext = dep.slice(dep.lastIndexOf('.') + 1);
334
343
 
335
- if (options.link_header_preload && !state.prerendering) {
336
- link_headers.add(
337
- `<${encodeURI(path)}>; rel="preload"; as="font"; type="font/${ext}"; crossorigin; nopush`
338
- );
339
- } else {
340
- head.add_link_tag(path, [
341
- 'rel="preload"',
342
- 'as="font"',
343
- `type="font/${ext}"`,
344
- 'crossorigin'
345
- ]);
346
- }
344
+ add_preload(path, ['rel="preload"', 'as="font"', `type="font/${ext}"`, 'crossorigin']);
347
345
  }
348
346
  }
349
347
 
@@ -372,23 +370,12 @@ export async function render_response({
372
370
  }
373
371
 
374
372
  if (!client.inline) {
375
- const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter(
376
- (path) => resolve_opts.preload({ type: 'js', path })
377
- );
378
-
379
- /** @type {(path: string) => void} */
380
- let add_preload;
373
+ for (const dep of modulepreloads) {
374
+ const path = prefixed(dep);
381
375
 
382
- // see the output.preloadStrategy option for details on why we have multiple options here
383
- if (options.link_header_preload && !state.prerendering) {
384
- add_preload = (path) =>
385
- link_headers.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
386
- } else {
387
- add_preload = (path) => head.add_link_tag(path, ['rel="modulepreload"']);
388
- }
389
-
390
- for (const path of included_modulepreloads) {
391
- add_preload(path);
376
+ if (resolve_opts.preload({ type: 'js', path })) {
377
+ add_preload(path, ['rel="modulepreload"']);
378
+ }
392
379
  }
393
380
  }
394
381
 
@@ -1,7 +1,8 @@
1
1
  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
- import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
4
+ import { redirect_response } from '../utils.js';
5
+ import { handle_error_and_jsonify, static_error_page } from '../errors.js';
5
6
  import { PageNodes } from '../../../utils/page_nodes.js';
6
7
  import { server_data_serializer } from './data_serializer.js';
7
8
 
@@ -33,12 +34,12 @@ export async function respond_with_error({
33
34
  }) {
34
35
  // reroute to the fallback page to prevent an infinite chain of requests.
35
36
  if (event.request.headers.get('x-sveltekit-error')) {
36
- return static_error_page(options, status, /** @type {Error} */ (error).message);
37
+ const transformed = await handle_error_and_jsonify(event, event_state, options, error);
38
+ return static_error_page(options, status, transformed.message);
37
39
  }
38
40
 
39
41
  /** @type {import('./types.js').Fetched[]} */
40
42
  const fetched = [];
41
-
42
43
  try {
43
44
  const branch = [];
44
45
  const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
@@ -46,6 +47,8 @@ export async function respond_with_error({
46
47
  const ssr = nodes.ssr();
47
48
  const csr = nodes.csr();
48
49
  const data_serializer = server_data_serializer(event, event_state, options);
50
+ // Do this here first in case the awaits below before rendering themselves error
51
+ const transformed = await handle_error_and_jsonify(event, event_state, options, error);
49
52
 
50
53
  if (ssr) {
51
54
  state.error = true;
@@ -89,8 +92,6 @@ export async function respond_with_error({
89
92
  );
90
93
  }
91
94
 
92
- const transformed = await handle_error_and_jsonify(event, event_state, options, error);
93
-
94
95
  return await render_response({
95
96
  options,
96
97
  manifest,
@@ -7,12 +7,13 @@ 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';
9
9
  import { create_remote_key, parse_remote_arg, split_remote_key, stringify } from '../shared.js';
10
- import { handle_error_and_jsonify } from './utils.js';
10
+ import { handle_error_and_jsonify } from './errors.js';
11
11
  import { normalize_error } from '../../utils/error.js';
12
12
  import { check_incorrect_fail_use } from './page/actions.js';
13
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
+ import { with_version_header } from './utils.js';
16
17
 
17
18
  /**
18
19
  * How long (in milliseconds) to wait after the last message was sent before
@@ -28,11 +29,12 @@ export async function handle_remote_call(event, state, options, manifest, id) {
28
29
  attributes: {
29
30
  'sveltekit.remote.call.id': id
30
31
  },
31
- fn: (current) => {
32
+ fn: async (current) => {
32
33
  const traced_event = merge_tracing(event, current);
33
- return with_request_store({ event: traced_event, state }, () =>
34
+ const response = await with_request_store({ event: traced_event, state }, () =>
34
35
  handle_remote_call_internal(traced_event, state, options, manifest, id)
35
36
  );
37
+ return with_version_header(response);
36
38
  }
37
39
  });
38
40
  }
@@ -240,7 +242,11 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
240
242
  );
241
243
  }
242
244
 
243
- const { data: input, meta, form_data } = await deserialize_binary_form(event.request);
245
+ const {
246
+ data: input,
247
+ meta,
248
+ form_data
249
+ } = await deserialize_binary_form(event.request, internals.id);
244
250
  state.remote.requested = create_requested_map(meta.remote_refreshes);
245
251
 
246
252
  // If this is a keyed form instance (created via form.for(key)), add the key to the form data (unless already set)
@@ -368,43 +374,64 @@ export async function collect_remote_data(data, event, state, options) {
368
374
  /** @type {Promise<any>[]} */
369
375
  const promises = [];
370
376
 
377
+ // Keys the explicit pass has serialized. Invoking a query's `fn` there can, as a
378
+ // side effect, register the same query in `state.remote.implicit` (via
379
+ // `get_response`), so we skip those keys in the implicit pass below to avoid
380
+ // processing them twice.
381
+ /** @type {Set<string>} */
382
+ const processed = new Set();
383
+
371
384
  if (state.remote.explicit) {
372
- for (const [remote_key, { internals, fn }] of state.remote.explicit) {
373
- // there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
374
- // so the client should apply these single-flight updates instead of calling `invalidateAll()`
375
- data.r = true;
385
+ const { explicit } = state.remote;
376
386
 
377
- const type = /** @type {'p' | 'q' | 'l'} */ (
378
- internals.type === 'query_live' ? 'l' : internals.type[0]
379
- );
387
+ /** @type {Promise<void>[]} */
388
+ const inflight = [];
389
+
390
+ const drain = () => {
391
+ for (const [remote_key, { internals, fn }] of explicit) {
392
+ explicit.delete(remote_key);
393
+ if (processed.has(remote_key)) continue;
394
+ processed.add(remote_key);
395
+
396
+ // there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
397
+ // so the client should apply these single-flight updates instead of calling `invalidateAll()`
398
+ data.r = true;
380
399
 
381
- // `fn` is deferred until now so the query runs after any state mutations
382
- // in the command/form body. If the query was re-awaited in the meantime,
383
- // `fn` returns the existing (fresh) cache entry rather than re-running.
384
- // Kick off the query immediately and collect the promise so that multiple
385
- // explicit refreshes run concurrently rather than serially.
386
- const promise = fn();
387
-
388
- promises.push(
389
- promise.then(
390
- (v) => {
391
- ((data[type] ??= {})[remote_key] ??= {}).v = v;
392
- },
393
- async (e) => {
394
- if (e instanceof Redirect) {
395
- // already handled elsewhere
396
- return;
400
+ const type = /** @type {'p' | 'q' | 'l'} */ (
401
+ internals.type === 'query_live' ? 'l' : internals.type[0]
402
+ );
403
+
404
+ // `fn` is deferred until now so the query runs after any state mutations
405
+ // in the command/form body. If the query was re-awaited in the meantime,
406
+ // `fn` returns the existing (fresh) cache entry rather than re-running.
407
+ inflight.push(
408
+ fn().then(
409
+ (v) => {
410
+ // a fresh value replaces the node entirely, so a re-run can't leave
411
+ // a stale error from a previous run alongside the new value
412
+ (data[type] ??= {})[remote_key] = { v };
413
+ drain();
414
+ },
415
+ async (e) => {
416
+ if (!(e instanceof Redirect)) {
417
+ // (a Redirect is already handled elsewhere)
418
+ (data[type] ??= {})[remote_key] = { e: await convert_error(e) };
419
+ }
420
+ drain();
397
421
  }
422
+ )
423
+ );
424
+ }
425
+ };
398
426
 
399
- ((data[type] ??= {})[remote_key] ??= {}).e = await convert_error(e);
400
- }
401
- )
402
- );
427
+ drain();
428
+
429
+ // `inflight` grows as settles drain newly-refreshed queries
430
+ for (const promise of inflight) {
431
+ await promise;
403
432
  }
404
433
  }
405
434
 
406
- await Promise.all(promises);
407
-
408
435
  if (state.remote.implicit) {
409
436
  for (const [internals, record] of state.remote.implicit) {
410
437
  // Private (non-exported) remote functions have no `id` and must never be
@@ -416,6 +443,10 @@ export async function collect_remote_data(data, event, state, options) {
416
443
  // form outputs are registered under the client-side action id directly
417
444
  const remote_key = internals.type === 'form' ? key : create_remote_key(internals.id, key);
418
445
 
446
+ // already serialized by the explicit pass (which always awaits and wins),
447
+ // so don't reprocess it here with the implicit "still loading" heuristic
448
+ if (processed.has(remote_key)) continue;
449
+
419
450
  const type = /** @type {'p' | 'q' | 'l' | 'f'} */ (
420
451
  internals.type === 'query_live' ? 'l' : internals.type[0]
421
452
  );
@@ -540,9 +571,9 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
540
571
  }
541
572
 
542
573
  try {
543
- const fn = /** @type {RemoteFormInternals} */ (/** @type {any} */ (form).__).fn;
574
+ const __ = /** @type {RemoteFormInternals} */ (/** @type {any} */ (form).__);
544
575
 
545
- const { data, meta, form_data } = await deserialize_binary_form(event.request);
576
+ const { data, meta, form_data } = await deserialize_binary_form(event.request, __.id);
546
577
 
547
578
  if (action_id && !('id' in data)) {
548
579
  data.id = JSON.parse(decodeURIComponent(action_id));
@@ -550,7 +581,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
550
581
 
551
582
  await with_request_store(
552
583
  { event, state: { ...state, is_in_remote_form_or_command: true } },
553
- () => fn(data, meta, form_data)
584
+ () => __.fn(data, meta, form_data)
554
585
  );
555
586
 
556
587
  // We don't want the data to appear on `let { form } = $props()`, which is why we're not returning it.
@@ -9,13 +9,14 @@ 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
11
  import { get_self_origin, is_csrf_forbidden, is_remote_forbidden } from './csrf.js';
12
+ import { has_prerendered_path, method_not_allowed, redirect_response } from './utils.js';
13
+ import { handle_fatal_error } from './errors.js';
12
14
  import {
13
- handle_fatal_error,
14
- has_prerendered_path,
15
- method_not_allowed,
16
- redirect_response
17
- } from './utils.js';
18
- import { decode_pathname, disable_search, normalize_path } from '../../utils/url.js';
15
+ decode_pathname,
16
+ disable_search,
17
+ normalize_path,
18
+ relative_pathname
19
+ } from '../../utils/url.js';
19
20
  import { find_route } from '../../utils/routing.js';
20
21
  import { redirect_json_response, render_data } from './data/index.js';
21
22
  import { add_cookies_to_headers, get_cookies } from './cookie.js';
@@ -36,7 +37,7 @@ import {
36
37
  strip_resolution_suffix
37
38
  } from '../pathname.js';
38
39
  import { server_data_serializer } from './page/data_serializer.js';
39
- import { get_remote_id, handle_remote_call } from './remote.js';
40
+ import { get_remote_id, handle_remote_call } from './remote-functions.js';
40
41
  import { record_span } from '../telemetry/record_span.js';
41
42
  import { otel } from '../telemetry/otel.js';
42
43
 
@@ -49,6 +50,28 @@ const default_filter = () => false;
49
50
  /** @type {import('types').RequiredResolveOptions['preload']} */
50
51
  const default_preload = ({ type }) => type === 'js' || type === 'css';
51
52
 
53
+ // `Sec-Fetch-Dest` values for subresource requests that can never render an HTML error page
54
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Dest
55
+ const non_html_fetch_destinations = new Set([
56
+ 'audio',
57
+ 'audioworklet',
58
+ 'font',
59
+ 'image',
60
+ 'json',
61
+ 'manifest',
62
+ 'paintworklet',
63
+ 'report',
64
+ 'script',
65
+ 'serviceworker',
66
+ 'sharedworker',
67
+ 'style',
68
+ 'track',
69
+ 'video',
70
+ 'webidentity',
71
+ 'worker',
72
+ 'xslt'
73
+ ]);
74
+
52
75
  const page_methods = new Set(['GET', 'HEAD', 'POST']);
53
76
 
54
77
  const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
@@ -113,6 +136,8 @@ export async function internal_respond(request, options, manifest, state) {
113
136
  /** @type {boolean[] | undefined} */
114
137
  let invalidated_data_nodes;
115
138
 
139
+ let skip_route_resolution = false;
140
+
116
141
  if (is_route_resolution_request) {
117
142
  /**
118
143
  * If the request is for a route resolution, first modify the URL, then continue as normal
@@ -130,8 +155,15 @@ export async function internal_respond(request, options, manifest, state) {
130
155
  .map((node) => node === '1');
131
156
  url.searchParams.delete(INVALIDATED_PARAM);
132
157
  } else if (remote_id) {
133
- url.pathname = request.headers.get('x-sveltekit-pathname') ?? base;
134
- url.search = request.headers.get('x-sveltekit-search') ?? '';
158
+ // query clients don't send these headers, leaving `event.url` as the endpoint URL
159
+ const pathname = request.headers.get('x-sveltekit-pathname');
160
+
161
+ if (pathname === null) {
162
+ skip_route_resolution = true;
163
+ } else {
164
+ url.pathname = pathname;
165
+ url.search = request.headers.get('x-sveltekit-search') ?? '';
166
+ }
135
167
  }
136
168
 
137
169
  /** @type {Record<string, string>} */
@@ -162,6 +194,7 @@ export async function internal_respond(request, options, manifest, state) {
162
194
  is_in_remote_function: false,
163
195
  is_in_remote_form_or_command: false,
164
196
  is_in_remote_query: false,
197
+ is_in_remote_prerender: false,
165
198
  is_in_render: false,
166
199
  is_in_universal_load: false
167
200
  };
@@ -335,7 +368,7 @@ export async function internal_respond(request, options, manifest, state) {
335
368
  return text('Not found', { status: 404, headers });
336
369
  }
337
370
 
338
- if (!state.prerendering?.fallback) {
371
+ if (!state.prerendering?.fallback && !skip_route_resolution) {
339
372
  try {
340
373
  const matchers = await manifest._.matchers();
341
374
  const result = find_route(resolved_path, manifest._.routes, matchers);
@@ -382,10 +415,9 @@ export async function internal_respond(request, options, manifest, state) {
382
415
  status: 308,
383
416
  headers: {
384
417
  'x-sveltekit-normalize': '1',
418
+ // relative so (possibly invisible) path prefixes are preserved
385
419
  location:
386
- // ensure paths starting with '//' are not treated as protocol-relative
387
- (normalized.startsWith('//') ? url.origin + normalized : normalized) +
388
- (url.search === '?' ? '' : url.search)
420
+ relative_pathname(url.pathname, normalized) + (url.search === '?' ? '' : url.search)
389
421
  }
390
422
  });
391
423
  }
@@ -635,13 +667,12 @@ export async function internal_respond(request, options, manifest, state) {
635
667
  ) {
636
668
  endpoint = await route.endpoint();
637
669
 
638
- // Prefer rendering the page if the endpoint can't handle this GET or HEAD request
639
- if (route.page && (method === 'GET' || method === 'HEAD')) {
640
- const endpoint_can_handle = !!(
641
- endpoint.GET ||
642
- endpoint.fallback ||
643
- (method === 'HEAD' && endpoint.HEAD)
644
- );
670
+ // Prefer rendering the page if the endpoint can't handle this GET, HEAD, or POST request
671
+ if (route.page && (method === 'GET' || method === 'HEAD' || method === 'POST')) {
672
+ const endpoint_can_handle =
673
+ method === 'POST'
674
+ ? !!(endpoint.POST || endpoint.fallback)
675
+ : !!(endpoint.GET || endpoint.fallback || (method === 'HEAD' && endpoint.HEAD));
645
676
  if (!endpoint_can_handle) {
646
677
  endpoint = undefined;
647
678
  }
@@ -737,6 +768,13 @@ export async function internal_respond(request, options, manifest, state) {
737
768
  // if this request came direct from the user, rather than
738
769
  // via our own `fetch`, render a 404 page
739
770
  if (state.depth === 0) {
771
+ if (non_html_fetch_destinations.has(event.request.headers.get('sec-fetch-dest') ?? '')) {
772
+ return text('Not Found', {
773
+ status: 404,
774
+ headers: { vary: 'Sec-Fetch-Dest' }
775
+ });
776
+ }
777
+
740
778
  return await respond_with_error({
741
779
  event,
742
780
  event_state,
@@ -0,0 +1,183 @@
1
+ /* eslint-disable n/prefer-global/process */
2
+
3
+ // using `getBuiltinModule` rather than `import` makes this safe to run in non-Node-compatible environments
4
+ const fs = globalThis.process?.getBuiltinModule?.('node:fs');
5
+ const url = globalThis.process?.getBuiltinModule?.('node:url');
6
+ const path = globalThis.process?.getBuiltinModule?.('node:path');
7
+ const module = globalThis.process?.getBuiltinModule?.('node:module');
8
+
9
+ const cwd = globalThis.process?.cwd?.();
10
+
11
+ /** @type {(file: string) => string} */
12
+ const relative = cwd ? (file) => path.relative(cwd, file) : (file) => file;
13
+
14
+ /**
15
+ * Applies sourcemaps, makes paths relative to the cwd, and truncates
16
+ * non-user code from the bottom of the stack
17
+ * @param {Error} error
18
+ * @returns void
19
+ */
20
+ export let fix_stack_trace = (error) => {
21
+ if (!error.stack || !fs) return;
22
+
23
+ let end = 0;
24
+
25
+ error.stack = error.stack
26
+ .split('\n')
27
+ .map((line, i) => {
28
+ const match = line.match(/^ {4}at.+(file:\/\/\/.*):(\d+):(\d+)(\)?)$/);
29
+
30
+ if (!match) {
31
+ if (!line.includes('node:internal/')) {
32
+ end = i + 1;
33
+ }
34
+
35
+ return line;
36
+ }
37
+
38
+ const file = url.fileURLToPath(match[1]);
39
+ const traced = trace(file, Number(match[2]) - 1, Number(match[3]) - 1);
40
+
41
+ // truncate non-user code from the bottom of the stack (but leave it in otherwise)
42
+ if (!/[\\/]node_modules[\\/]/.test(traced?.file ?? file)) {
43
+ end = i + 1;
44
+ }
45
+
46
+ if (traced?.line) {
47
+ const location = `${match[1]}:${match[2]}:${match[3]}`;
48
+ const original = `${relative(traced.file)}:${traced.line}:${traced.column}`;
49
+
50
+ return line.replace(location, original);
51
+ }
52
+
53
+ if (traced) {
54
+ return `${line.replace(match[1], relative(file))} [${traced.file}]`;
55
+ }
56
+
57
+ return line;
58
+ })
59
+ .slice(0, end)
60
+ .join('\n');
61
+ };
62
+
63
+ /**
64
+ * Override the implementation of fix_stack_trace (for using during dev)
65
+ * @param {(error: Error) => void} fn
66
+ */
67
+ export function set_fix_stack_trace(fn) {
68
+ fix_stack_trace = fn;
69
+ }
70
+
71
+ /** @type {Map<string, { map: import('node:module').SourceMap; directory: string } | null>} */
72
+ const source_maps = new Map();
73
+
74
+ /** @type {Map<string, Array<string | undefined>>} */
75
+ const source_regions = new Map();
76
+
77
+ /** @param {string} file */
78
+ function get_source_map(file) {
79
+ if (source_maps.has(file)) {
80
+ return source_maps.get(file);
81
+ }
82
+
83
+ try {
84
+ let source;
85
+ let directory = path.dirname(file);
86
+
87
+ const code = fs.readFileSync(file, 'utf8');
88
+ const matches = Array.from(code.matchAll(/\/\/[#@]\s*sourceMappingURL=(\S+)/g));
89
+ const url = matches.at(-1)?.[1];
90
+
91
+ if (url?.startsWith('data:')) {
92
+ const comma = url.indexOf(',');
93
+ const metadata = url.slice(5, comma);
94
+ const data = url.slice(comma + 1);
95
+ source = metadata.endsWith(';base64')
96
+ ? Buffer.from(data, 'base64').toString()
97
+ : decodeURIComponent(data);
98
+ } else {
99
+ const map_file = url
100
+ ? path.resolve(path.dirname(file), decodeURIComponent(url))
101
+ : `${file}.map`;
102
+ if (fs.existsSync(map_file)) {
103
+ directory = path.dirname(map_file);
104
+ source = fs.readFileSync(map_file, 'utf8');
105
+ }
106
+ }
107
+
108
+ if (source) {
109
+ const source_map = { map: new module.SourceMap(JSON.parse(source)), directory };
110
+ source_maps.set(file, source_map);
111
+
112
+ return source_map;
113
+ }
114
+ } catch {
115
+ // failure could be for any reason and this is best-effort, ignore
116
+ }
117
+
118
+ source_maps.set(file, null);
119
+ return null;
120
+ }
121
+
122
+ /**
123
+ *
124
+ * @param {string} file
125
+ * @param {number} line
126
+ * @param {number} column
127
+ * @returns {null | { file: string, line?: number, column?: number }}
128
+ */
129
+ function trace(file, line, column) {
130
+ const source_map = get_source_map(file);
131
+ if (!source_map) return null;
132
+
133
+ const entry = source_map.map.findEntry(line, column);
134
+
135
+ if (
136
+ entry &&
137
+ 'originalSource' in entry &&
138
+ entry.originalSource &&
139
+ typeof entry.originalLine === 'number' &&
140
+ typeof entry.originalColumn === 'number'
141
+ ) {
142
+ const source = entry.originalSource.startsWith('file:')
143
+ ? url.fileURLToPath(entry.originalSource)
144
+ : path.resolve(source_map.directory, entry.originalSource);
145
+
146
+ const traced = {
147
+ file: source,
148
+ line: entry.originalLine + 1,
149
+ column: entry.originalColumn + 1
150
+ };
151
+
152
+ // keep going, in case we are running code that was bundled a second time by an adapter
153
+ return trace(traced.file, traced.line - 1, traced.column - 1) ?? traced;
154
+ }
155
+
156
+ let regions = source_regions.get(file);
157
+ if (!regions) {
158
+ /** @type {string | undefined} */
159
+ let source;
160
+ regions = fs
161
+ .readFileSync(file, 'utf8')
162
+ .split('\n')
163
+ .map((line) => {
164
+ // Vite will merge multiple files into one but add region markers
165
+ // with the original file names, which we try to extract here.
166
+ const start = line.match(/^\/\/#region (.+)$/);
167
+ if (start) source = start[1];
168
+ if (line === '//#endregion') source = undefined;
169
+ return source;
170
+ });
171
+ source_regions.set(file, regions);
172
+ }
173
+
174
+ const source = regions[line];
175
+
176
+ if (source) {
177
+ return {
178
+ file: source
179
+ };
180
+ }
181
+
182
+ return null;
183
+ }