@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
@@ -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
  }
@@ -69,15 +69,19 @@ export async function resolve_route(resolved_path, url, manifest) {
69
69
  return text('Server-side route resolution disabled', { status: 400 });
70
70
  }
71
71
 
72
- const matchers = await manifest._.matchers();
73
- const result = find_route(resolved_path, manifest._.client.routes, matchers);
74
-
75
- return create_server_routing_response(
76
- result?.route ?? null,
77
- result?.params ?? {},
78
- url,
79
- manifest._.client
80
- ).response;
72
+ try {
73
+ const matchers = await manifest._.matchers();
74
+ const result = find_route(resolved_path, manifest._.client.routes, matchers);
75
+
76
+ return create_server_routing_response(
77
+ result?.route ?? null,
78
+ result?.params ?? {},
79
+ url,
80
+ manifest._.client
81
+ ).response;
82
+ } catch {
83
+ return text('Error resolving route', { status: 500 });
84
+ }
81
85
  }
82
86
 
83
87
  /**
@@ -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,20 +358,17 @@ 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>[]} */
335
368
  const promises = [];
336
369
 
337
370
  if (state.remote.explicit) {
338
- for (const [remote_key, { internals, promise }] of state.remote.explicit) {
371
+ for (const [remote_key, { internals, fn }] of state.remote.explicit) {
339
372
  // there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
340
373
  // so the client should apply these single-flight updates instead of calling `invalidateAll()`
341
374
  data.r = true;
@@ -344,18 +377,27 @@ export async function collect_remote_data(data, event, state, options) {
344
377
  internals.type === 'query_live' ? 'l' : internals.type[0]
345
378
  );
346
379
 
347
- await promise.then(
348
- (v) => {
349
- ((data[type] ??= {})[remote_key] ??= {}).v = v;
350
- },
351
- async (e) => {
352
- if (e instanceof Redirect) {
353
- // already handled elsewhere
354
- return;
355
- }
380
+ // `fn` is deferred until now so the query runs after any state mutations
381
+ // in the command/form body. If the query was re-awaited in the meantime,
382
+ // `fn` returns the existing (fresh) cache entry rather than re-running.
383
+ // Kick off the query immediately and collect the promise so that multiple
384
+ // explicit refreshes run concurrently rather than serially.
385
+ const promise = fn();
386
+
387
+ promises.push(
388
+ promise.then(
389
+ (v) => {
390
+ ((data[type] ??= {})[remote_key] ??= {}).v = v;
391
+ },
392
+ async (e) => {
393
+ if (e instanceof Redirect) {
394
+ // already handled elsewhere
395
+ return;
396
+ }
356
397
 
357
- ((data[type] ??= {})[remote_key] ??= {}).e = await convert_error(e);
358
- }
398
+ ((data[type] ??= {})[remote_key] ??= {}).e = await convert_error(e);
399
+ }
400
+ )
359
401
  );
360
402
  }
361
403
  }
@@ -468,9 +510,11 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
468
510
  const [hash, name, ...rest] = id.split('/');
469
511
  const action_id = rest.join('/');
470
512
  const remotes = manifest._.remotes;
471
- const module = await remotes[hash]?.();
513
+ const module = Object.hasOwn(remotes, hash) ? await remotes[hash]() : undefined;
472
514
 
473
- let form = /** @type {RemoteForm<any, any>} */ (module?.default[name]);
515
+ let form = /** @type {RemoteForm<any, any>} */ (
516
+ module && Object.hasOwn(module.default, name) ? module.default[name] : undefined
517
+ );
474
518
 
475
519
  if (!form) {
476
520
  event.setHeaders({
@@ -480,6 +524,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
480
524
  });
481
525
  return {
482
526
  type: 'error',
527
+ // We're lying a bit with the types here; this will be transformed into a proper App.Error object later
483
528
  error: new SvelteKitError(
484
529
  405,
485
530
  'Method Not Allowed',
@@ -526,6 +571,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
526
571
 
527
572
  return {
528
573
  type: 'error',
574
+ // @ts-expect-error We're lying a bit with the types here; this will be transformed into a proper App.Error object later
529
575
  error: check_incorrect_fail_use(err)
530
576
  };
531
577
  }
@@ -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
 
@@ -331,14 +336,17 @@ export async function internal_respond(request, options, manifest, state) {
331
336
  }
332
337
 
333
338
  if (!state.prerendering?.fallback) {
334
- // TODO this could theoretically break — should probably be inside a try-catch
335
- const matchers = await manifest._.matchers();
336
- const result = find_route(resolved_path, manifest._.routes, matchers);
337
-
338
- if (result) {
339
- route = result.route;
340
- event.route = { id: route.id };
341
- event.params = result.params;
339
+ try {
340
+ const matchers = await manifest._.matchers();
341
+ const result = find_route(resolved_path, manifest._.routes, matchers);
342
+
343
+ if (result) {
344
+ route = result.route;
345
+ event.route = { id: route.id };
346
+ event.params = result.params;
347
+ }
348
+ } catch (e) {
349
+ return await handle_fatal_error(event, event_state, options, e);
342
350
  }
343
351
  }
344
352
 
@@ -619,54 +627,80 @@ export async function internal_respond(request, options, manifest, state) {
619
627
  invalidated_data_nodes,
620
628
  trailing_slash
621
629
  );
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');
630
+ } else {
631
+ let endpoint;
632
+ if (
633
+ route.endpoint &&
634
+ (!route.page || (!state.prerendering && is_endpoint_request(event)))
635
+ ) {
636
+ endpoint = await route.endpoint();
637
+
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
+ );
645
+ if (!endpoint_can_handle) {
646
+ endpoint = undefined;
647
+ }
643
648
  }
649
+ }
644
650
 
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
- });
651
+ if (endpoint) {
652
+ response = await render_endpoint(event, event_state, endpoint, state);
653
+ } else if (route.page) {
654
+ if (!page_nodes) {
655
+ throw new Error('page_nodes not found. This should never happen');
656
+ } else if (page_methods.has(method)) {
657
+ response = await render_page(
658
+ event,
659
+ event_state,
660
+ route.page,
661
+ options,
662
+ manifest,
663
+ state,
664
+ page_nodes,
665
+ resolve_opts
666
+ );
654
667
  } 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);
668
+ const allowed_methods = new Set(allowed_page_methods);
669
+ const node = await manifest._.nodes[route.page.leaf]();
670
+ if (node?.server?.actions) {
671
+ allowed_methods.add('POST');
672
+ }
673
+
674
+ if (method === 'OPTIONS') {
675
+ // This will deny CORS preflight requests implicitly because we don't
676
+ // add the required CORS headers to the response.
677
+ response = new Response(null, {
678
+ status: 204,
679
+ headers: {
680
+ allow: Array.from(allowed_methods.values()).join(', ')
681
+ }
682
+ });
683
+ } else {
684
+ const mod = [...allowed_methods].reduce((acc, curr) => {
685
+ acc[curr] = true;
686
+ return acc;
687
+ }, /** @type {Record<string, any>} */ ({}));
688
+ response = method_not_allowed(mod, method);
689
+ }
660
690
  }
691
+ } else {
692
+ // a route will always have a page or an endpoint, but TypeScript doesn't know that
693
+ throw new Error('Route is neither page nor endpoint. This should never happen');
661
694
  }
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
695
  }
666
696
 
667
697
  // If the route contains a page and an endpoint, we need to add a
668
698
  // `Vary: Accept` header to the response because of browser caching
669
- if (request.method === 'GET' && route.page && route.endpoint) {
699
+ if (
700
+ (request.method === 'GET' || request.method === 'HEAD') &&
701
+ route.page &&
702
+ route.endpoint
703
+ ) {
670
704
  const vary = response.headers
671
705
  .get('vary')
672
706
  ?.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 `tracing.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,29 +25,34 @@ 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
 
31
32
  /**
32
33
  * The interface that defines `event.locals`, which can be accessed in server [hooks](https://svelte.dev/docs/kit/hooks) (`handle`, and `handleError`), server-only `load` functions, and `+server.js` files.
33
34
  */
35
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
34
36
  export interface Locals {}
35
37
 
36
38
  /**
37
- * Defines the common shape of the [page.data state](https://svelte.dev/docs/kit/$app-state#page) and [$page.data store](https://svelte.dev/docs/kit/$app-stores#page) - that is, the data that is shared between all pages.
39
+ * Defines the common shape of the [page.data state](https://svelte.dev/docs/kit/$app-state#page) - that is, the data that is shared between all pages.
38
40
  * The `Load` and `ServerLoad` functions in `./$types` will be narrowed accordingly.
39
41
  * Use optional properties for data that is only present on specific pages. Do not add an index signature (`[key: string]: any`).
40
42
  */
43
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
41
44
  export interface PageData {}
42
45
 
43
46
  /**
44
47
  * The shape of the `page.state` object, which can be manipulated using the [`pushState`](https://svelte.dev/docs/kit/$app-navigation#pushState) and [`replaceState`](https://svelte.dev/docs/kit/$app-navigation#replaceState) functions from `$app/navigation`.
45
48
  */
49
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
46
50
  export interface PageState {}
47
51
 
48
52
  /**
49
53
  * If your adapter provides [platform-specific context](https://svelte.dev/docs/kit/adapters#Platform-specific-context) via `event.platform`, you can specify it here.
50
54
  */
55
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
51
56
  export interface Platform {}
52
57
  }
53
58
 
@@ -56,8 +61,8 @@ declare namespace App {
56
61
  */
57
62
  declare module '$service-worker' {
58
63
  /**
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.
64
+ * 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.
65
+ * Note that there is a `base` but no `assets`, since service workers cannot be used if `config.paths.assets` is specified.
61
66
  */
62
67
  export const base: string;
63
68
  /**
@@ -66,7 +71,7 @@ declare module '$service-worker' {
66
71
  */
67
72
  export const build: string[];
68
73
  /**
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)
74
+ * 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
75
  */
71
76
  export const files: string[];
72
77
  /**
@@ -75,7 +80,7 @@ declare module '$service-worker' {
75
80
  */
76
81
  export const prerendered: string[];
77
82
  /**
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.
83
+ * 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
84
  */
80
85
  export const version: string;
81
86
  }
@@ -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.tracing.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.