@sveltejs/kit 1.0.0-next.230 → 1.0.0-next.234

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.
@@ -2,36 +2,46 @@ import path__default from 'path';
2
2
  import { svelte } from '@sveltejs/vite-plugin-svelte';
3
3
  import vite from 'vite';
4
4
  import { c as create_manifest_data, a as create_app, d as deep_merge } from './index2.js';
5
- import { c as coalesce_to_error, r as resolve_entry, $, S as SVELTE_KIT, a as runtime, b as SVELTE_KIT_ASSETS, l as load_template, g as get_mime_lookup, d as copy_assets, e as get_aliases, p as print_config_conflicts } from '../cli.js';
5
+ import { c as coalesce_to_error, S as SVELTE_KIT_ASSETS, r as resolve_entry, $, a as SVELTE_KIT, b as runtime, l as load_template, g as get_mime_lookup, d as copy_assets, e as get_aliases, p as print_config_conflicts } from '../cli.js';
6
6
  import fs__default from 'fs';
7
7
  import { URL as URL$1 } from 'url';
8
- import { g as get_single_valued_header, r as resolve, i as is_root_relative } from './url.js';
8
+ import { t as to_headers, s as sirv } from './http.js';
9
9
  import { s } from './misc.js';
10
+ import { r as resolve, i as is_root_relative } from './url.js';
10
11
  import { __fetch_polyfill } from '../install-fetch.js';
11
- import { getRawBody } from '../node.js';
12
+ import { getRawBody, setResponse } from '../node.js';
12
13
  import 'sade';
13
14
  import 'child_process';
14
15
  import 'net';
15
16
  import 'os';
17
+ import 'querystring';
16
18
  import 'node:http';
17
19
  import 'node:https';
18
20
  import 'node:zlib';
19
21
  import 'node:stream';
20
22
  import 'node:util';
21
23
  import 'node:url';
24
+ import 'stream';
22
25
 
23
- /** @param {Record<string, any>} obj */
24
- function lowercase_keys(obj) {
25
- /** @type {Record<string, any>} */
26
- const clone = {};
26
+ /**
27
+ * Hash using djb2
28
+ * @param {import('types/hooks').StrictBody} value
29
+ */
30
+ function hash(value) {
31
+ let hash = 5381;
32
+ let i = value.length;
27
33
 
28
- for (const key in obj) {
29
- clone[key.toLowerCase()] = obj[key];
34
+ if (typeof value === 'string') {
35
+ while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
36
+ } else {
37
+ while (i) hash = (hash * 33) ^ value[--i];
30
38
  }
31
39
 
32
- return clone;
40
+ return (hash >>> 0).toString(36);
33
41
  }
34
42
 
43
+ /** @param {Record<string, any>} obj */
44
+
35
45
  /** @param {Record<string, string>} params */
36
46
  function decode_params(params) {
37
47
  for (const key in params) {
@@ -56,11 +66,9 @@ function decode_params(params) {
56
66
 
57
67
  /** @param {string} body */
58
68
  function error(body) {
59
- return {
60
- status: 500,
61
- body,
62
- headers: {}
63
- };
69
+ return new Response(body, {
70
+ status: 500
71
+ });
64
72
  }
65
73
 
66
74
  /** @param {unknown} s */
@@ -89,16 +97,16 @@ function is_text(content_type) {
89
97
  }
90
98
 
91
99
  /**
92
- * @param {import('types/hooks').ServerRequest} request
100
+ * @param {import('types/hooks').RequestEvent} event
93
101
  * @param {import('types/internal').SSREndpoint} route
94
102
  * @param {RegExpExecArray} match
95
- * @returns {Promise<import('types/hooks').ServerResponse | undefined>}
103
+ * @returns {Promise<Response | undefined>}
96
104
  */
97
- async function render_endpoint(request, route, match) {
105
+ async function render_endpoint(event, route, match) {
98
106
  const mod = await route.load();
99
107
 
100
108
  /** @type {import('types/endpoint').RequestHandler} */
101
- const handler = mod[request.method.toLowerCase().replace('delete', 'del')]; // 'delete' is a reserved word
109
+ const handler = mod[event.request.method.toLowerCase().replace('delete', 'del')]; // 'delete' is a reserved word
102
110
 
103
111
  if (!handler) {
104
112
  return;
@@ -107,10 +115,10 @@ async function render_endpoint(request, route, match) {
107
115
  // we're mutating `request` so that we don't have to do { ...request, params }
108
116
  // on the next line, since that breaks the getters that replace path, query and
109
117
  // origin. We could revert that once we remove the getters
110
- request.params = route.params ? decode_params(route.params(match)) : {};
118
+ event.params = route.params ? decode_params(route.params(match)) : {};
111
119
 
112
- const response = await handler(request);
113
- const preface = `Invalid response from route ${request.url.pathname}`;
120
+ const response = await handler(event);
121
+ const preface = `Invalid response from route ${event.url.pathname}`;
114
122
 
115
123
  if (typeof response !== 'object') {
116
124
  return error(`${preface}: expected an object, got ${typeof response}`);
@@ -120,10 +128,11 @@ async function render_endpoint(request, route, match) {
120
128
  return;
121
129
  }
122
130
 
123
- let { status = 200, body, headers = {} } = response;
131
+ const { status = 200, body = {} } = response;
132
+ const headers =
133
+ response.headers instanceof Headers ? response.headers : to_headers(response.headers);
124
134
 
125
- headers = lowercase_keys(headers);
126
- const type = get_single_valued_header(headers, 'content-type');
135
+ const type = headers.get('content-type');
127
136
 
128
137
  if (!is_text(type) && !(body instanceof Uint8Array || is_string(body))) {
129
138
  return error(
@@ -134,19 +143,45 @@ async function render_endpoint(request, route, match) {
134
143
  /** @type {import('types/hooks').StrictBody} */
135
144
  let normalized_body;
136
145
 
137
- // ensure the body is an object
138
- if (
139
- (typeof body === 'object' || typeof body === 'undefined') &&
140
- !(body instanceof Uint8Array) &&
141
- (!type || type.startsWith('application/json'))
142
- ) {
143
- headers = { ...headers, 'content-type': 'application/json; charset=utf-8' };
144
- normalized_body = JSON.stringify(typeof body === 'undefined' ? {} : body);
146
+ if (is_pojo(body) && (!type || type.startsWith('application/json'))) {
147
+ headers.set('content-type', 'application/json; charset=utf-8');
148
+ normalized_body = JSON.stringify(body);
145
149
  } else {
146
150
  normalized_body = /** @type {import('types/hooks').StrictBody} */ (body);
147
151
  }
148
152
 
149
- return { status, body: normalized_body, headers };
153
+ if (
154
+ (typeof normalized_body === 'string' || normalized_body instanceof Uint8Array) &&
155
+ !headers.has('etag')
156
+ ) {
157
+ const cache_control = headers.get('cache-control');
158
+ if (!cache_control || !/(no-store|immutable)/.test(cache_control)) {
159
+ headers.set('etag', `"${hash(normalized_body)}"`);
160
+ }
161
+ }
162
+
163
+ return new Response(normalized_body, {
164
+ status,
165
+ headers
166
+ });
167
+ }
168
+
169
+ /** @param {any} body */
170
+ function is_pojo(body) {
171
+ if (typeof body !== 'object') return false;
172
+
173
+ if (body) {
174
+ if (body instanceof Uint8Array) return false;
175
+
176
+ // body could be a node Readable, but we don't want to import
177
+ // node built-ins, so we use duck typing
178
+ if (body._readableState && body._writableState && body._events) return false;
179
+
180
+ // similarly, it could be a web ReadableStream
181
+ if (body[Symbol.toStringTag] === 'ReadableStream') return false;
182
+ }
183
+
184
+ return true;
150
185
  }
151
186
 
152
187
  var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
@@ -428,23 +463,6 @@ function writable(value, start = noop) {
428
463
  return { set, update, subscribe };
429
464
  }
430
465
 
431
- /**
432
- * Hash using djb2
433
- * @param {import('types/hooks').StrictBody} value
434
- */
435
- function hash(value) {
436
- let hash = 5381;
437
- let i = value.length;
438
-
439
- if (typeof value === 'string') {
440
- while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
441
- } else {
442
- while (i) hash = (hash * 33) ^ value[--i];
443
- }
444
-
445
- return (hash >>> 0).toString(36);
446
- }
447
-
448
466
  /** @type {Record<string, string>} */
449
467
  const escape_json_string_in_html_dict = {
450
468
  '"': '\\"',
@@ -678,8 +696,8 @@ async function render_response({
678
696
  }
679
697
  // prettier-ignore
680
698
  head += Array.from(css)
681
- .map((dep) => `\n\t<link${styles.has(dep) ? ' disabled' : ''} rel="stylesheet" href="${options.prefix + dep}">`)
682
- .join('');
699
+ .map((dep) => `\n\t<link${styles.has(dep) ? ' disabled media="(max-width: 0)"' : ''} rel="stylesheet" href="${options.prefix + dep}">`)
700
+ .join('');
683
701
 
684
702
  if (page_config.router || page_config.hydrate) {
685
703
  head += Array.from(js)
@@ -730,32 +748,29 @@ async function render_response({
730
748
  }
731
749
  }
732
750
 
733
- /** @type {import('types/helper').ResponseHeaders} */
734
- const headers = {
735
- 'content-type': 'text/html'
736
- };
751
+ const segments = url.pathname.slice(options.paths.base.length).split('/').slice(2);
752
+ const assets =
753
+ options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
754
+
755
+ const html = options.template({ head, body, assets });
756
+
757
+ const headers = new Headers({
758
+ 'content-type': 'text/html',
759
+ etag: `"${hash(html)}"`
760
+ });
737
761
 
738
762
  if (maxage) {
739
- headers['cache-control'] = `${is_private ? 'private' : 'public'}, max-age=${maxage}`;
763
+ headers.set('cache-control', `${is_private ? 'private' : 'public'}, max-age=${maxage}`);
740
764
  }
741
765
 
742
766
  if (!options.floc) {
743
- headers['permissions-policy'] = 'interest-cohort=()';
767
+ headers.set('permissions-policy', 'interest-cohort=()');
744
768
  }
745
769
 
746
- const segments = url.pathname.slice(options.paths.base.length).split('/').slice(2);
747
- const assets =
748
- options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
749
-
750
- return {
770
+ return new Response(html, {
751
771
  status,
752
- headers,
753
- body: options.template({
754
- head,
755
- body,
756
- assets
757
- })
758
- };
772
+ headers
773
+ });
759
774
  }
760
775
 
761
776
  /**
@@ -854,7 +869,7 @@ function normalize(loaded) {
854
869
 
855
870
  /**
856
871
  * @param {{
857
- * request: import('types/hooks').ServerRequest;
872
+ * event: import('types/hooks').RequestEvent;
858
873
  * options: import('types/internal').SSRRenderOptions;
859
874
  * state: import('types/internal').SSRRenderState;
860
875
  * route: import('types/internal').SSRPage | null;
@@ -870,7 +885,7 @@ function normalize(loaded) {
870
885
  * @returns {Promise<import('./types').Loaded | undefined>} undefined for fallthrough
871
886
  */
872
887
  async function load_node({
873
- request,
888
+ event,
874
889
  options,
875
890
  state,
876
891
  route,
@@ -941,7 +956,7 @@ async function load_node({
941
956
 
942
957
  opts.headers = new Headers(opts.headers);
943
958
 
944
- const resolved = resolve(request.url.pathname, requested.split('?')[0]);
959
+ const resolved = resolve(event.url.pathname, requested.split('?')[0]);
945
960
 
946
961
  let response;
947
962
 
@@ -977,12 +992,15 @@ async function load_node({
977
992
  if (opts.credentials !== 'omit') {
978
993
  uses_credentials = true;
979
994
 
980
- if (request.headers.cookie) {
981
- opts.headers.set('cookie', request.headers.cookie);
995
+ const cookie = event.request.headers.get('cookie');
996
+ const authorization = event.request.headers.get('authorization');
997
+
998
+ if (cookie) {
999
+ opts.headers.set('cookie', cookie);
982
1000
  }
983
1001
 
984
- if (request.headers.authorization && !opts.headers.has('authorization')) {
985
- opts.headers.set('authorization', request.headers.authorization);
1002
+ if (authorization && !opts.headers.has('authorization')) {
1003
+ opts.headers.set('authorization', authorization);
986
1004
  }
987
1005
  }
988
1006
 
@@ -995,12 +1013,7 @@ async function load_node({
995
1013
  }
996
1014
 
997
1015
  const rendered = await respond(
998
- {
999
- url: new URL(requested, request.url),
1000
- method: opts.method || 'GET',
1001
- headers: Object.fromEntries(opts.headers),
1002
- rawBody: opts.body == null ? null : new TextEncoder().encode(opts.body)
1003
- },
1016
+ new Request(new URL(requested, event.url).href, opts),
1004
1017
  options,
1005
1018
  {
1006
1019
  fetched: requested,
@@ -1013,17 +1026,11 @@ async function load_node({
1013
1026
  state.prerender.dependencies.set(relative, rendered);
1014
1027
  }
1015
1028
 
1016
- // Set-Cookie must be filtered out (done below) and that's the only header value that
1017
- // can be an array so we know we have only simple values
1018
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
1019
- response = new Response(rendered.body, {
1020
- status: rendered.status,
1021
- headers: /** @type {Record<string, string>} */ (rendered.headers)
1022
- });
1029
+ response = rendered;
1023
1030
  } else {
1024
1031
  // we can't load the endpoint from our own manifest,
1025
1032
  // so we need to make an actual HTTP request
1026
- return fetch(new URL(requested, request.url).href, {
1033
+ return fetch(new URL(requested, event.url).href, {
1027
1034
  method: opts.method || 'GET',
1028
1035
  headers: opts.headers
1029
1036
  });
@@ -1046,11 +1053,13 @@ async function load_node({
1046
1053
  // ports do not affect the resolution
1047
1054
  // leading dot prevents mydomain.com matching domain.com
1048
1055
  if (
1049
- `.${new URL(requested).hostname}`.endsWith(`.${request.url.hostname}`) &&
1056
+ `.${new URL(requested).hostname}`.endsWith(`.${event.url.hostname}`) &&
1050
1057
  opts.credentials !== 'omit'
1051
1058
  ) {
1052
1059
  uses_credentials = true;
1053
- opts.headers.set('cookie', request.headers.cookie);
1060
+
1061
+ const cookie = event.request.headers.get('cookie');
1062
+ if (cookie) opts.headers.set('cookie', cookie);
1054
1063
  }
1055
1064
 
1056
1065
  const external_request = new Request(requested, /** @type {RequestInit} */ (opts));
@@ -1159,7 +1168,7 @@ async function load_node({
1159
1168
 
1160
1169
  /**
1161
1170
  * @param {{
1162
- * request: import('types/hooks').ServerRequest;
1171
+ * event: import('types/hooks').RequestEvent;
1163
1172
  * options: SSRRenderOptions;
1164
1173
  * state: SSRRenderState;
1165
1174
  * $session: any;
@@ -1168,15 +1177,7 @@ async function load_node({
1168
1177
  * ssr: boolean;
1169
1178
  * }} opts
1170
1179
  */
1171
- async function respond_with_error({
1172
- request,
1173
- options,
1174
- state,
1175
- $session,
1176
- status,
1177
- error,
1178
- ssr
1179
- }) {
1180
+ async function respond_with_error({ event, options, state, $session, status, error, ssr }) {
1180
1181
  try {
1181
1182
  const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
1182
1183
  const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
@@ -1186,11 +1187,11 @@ async function respond_with_error({
1186
1187
 
1187
1188
  const layout_loaded = /** @type {Loaded} */ (
1188
1189
  await load_node({
1189
- request,
1190
+ event,
1190
1191
  options,
1191
1192
  state,
1192
1193
  route: null,
1193
- url: request.url, // TODO this is redundant, no?
1194
+ url: event.url, // TODO this is redundant, no?
1194
1195
  params,
1195
1196
  node: default_layout,
1196
1197
  $session,
@@ -1201,11 +1202,11 @@ async function respond_with_error({
1201
1202
 
1202
1203
  const error_loaded = /** @type {Loaded} */ (
1203
1204
  await load_node({
1204
- request,
1205
+ event,
1205
1206
  options,
1206
1207
  state,
1207
1208
  route: null,
1208
- url: request.url,
1209
+ url: event.url,
1209
1210
  params,
1210
1211
  node: default_error,
1211
1212
  $session,
@@ -1228,26 +1229,23 @@ async function respond_with_error({
1228
1229
  status,
1229
1230
  error,
1230
1231
  branch: [layout_loaded, error_loaded],
1231
- url: request.url,
1232
+ url: event.url,
1232
1233
  params,
1233
1234
  ssr
1234
1235
  });
1235
1236
  } catch (err) {
1236
1237
  const error = coalesce_to_error(err);
1237
1238
 
1238
- options.handle_error(error, request);
1239
+ options.handle_error(error, event);
1239
1240
 
1240
- return {
1241
- status: 500,
1242
- headers: {},
1243
- body: error.stack
1244
- };
1241
+ return new Response(error.stack, {
1242
+ status: 500
1243
+ });
1245
1244
  }
1246
1245
  }
1247
1246
 
1248
1247
  /**
1249
1248
  * @typedef {import('./types.js').Loaded} Loaded
1250
- * @typedef {import('types/hooks').ServerResponse} ServerResponse
1251
1249
  * @typedef {import('types/internal').SSRNode} SSRNode
1252
1250
  * @typedef {import('types/internal').SSRRenderOptions} SSRRenderOptions
1253
1251
  * @typedef {import('types/internal').SSRRenderState} SSRRenderState
@@ -1255,7 +1253,7 @@ async function respond_with_error({
1255
1253
 
1256
1254
  /**
1257
1255
  * @param {{
1258
- * request: import('types/hooks').ServerRequest;
1256
+ * event: import('types/hooks').RequestEvent;
1259
1257
  * options: SSRRenderOptions;
1260
1258
  * state: SSRRenderState;
1261
1259
  * $session: any;
@@ -1263,10 +1261,10 @@ async function respond_with_error({
1263
1261
  * params: Record<string, string>;
1264
1262
  * ssr: boolean;
1265
1263
  * }} opts
1266
- * @returns {Promise<ServerResponse | undefined>}
1264
+ * @returns {Promise<Response | undefined>}
1267
1265
  */
1268
1266
  async function respond$1(opts) {
1269
- const { request, options, state, $session, route, ssr } = opts;
1267
+ const { event, options, state, $session, route, ssr } = opts;
1270
1268
 
1271
1269
  /** @type {Array<SSRNode | undefined>} */
1272
1270
  let nodes;
@@ -1280,7 +1278,7 @@ async function respond$1(opts) {
1280
1278
  router: true
1281
1279
  },
1282
1280
  status: 200,
1283
- url: request.url,
1281
+ url: event.url,
1284
1282
  stuff: {}
1285
1283
  });
1286
1284
  }
@@ -1292,10 +1290,10 @@ async function respond$1(opts) {
1292
1290
  } catch (err) {
1293
1291
  const error = coalesce_to_error(err);
1294
1292
 
1295
- options.handle_error(error, request);
1293
+ options.handle_error(error, event);
1296
1294
 
1297
1295
  return await respond_with_error({
1298
- request,
1296
+ event,
1299
1297
  options,
1300
1298
  state,
1301
1299
  $session,
@@ -1313,10 +1311,9 @@ async function respond$1(opts) {
1313
1311
  if (!leaf.prerender && state.prerender && !state.prerender.all) {
1314
1312
  // if the page has `export const prerender = true`, continue,
1315
1313
  // otherwise bail out at this point
1316
- return {
1317
- status: 204,
1318
- headers: {}
1319
- };
1314
+ return new Response(undefined, {
1315
+ status: 204
1316
+ });
1320
1317
  }
1321
1318
 
1322
1319
  /** @type {Array<Loaded>} */
@@ -1344,7 +1341,7 @@ async function respond$1(opts) {
1344
1341
  try {
1345
1342
  loaded = await load_node({
1346
1343
  ...opts,
1347
- url: request.url,
1344
+ url: event.url,
1348
1345
  node,
1349
1346
  stuff,
1350
1347
  is_error: false
@@ -1356,12 +1353,12 @@ async function respond$1(opts) {
1356
1353
 
1357
1354
  if (loaded.loaded.redirect) {
1358
1355
  return with_cookies(
1359
- {
1356
+ new Response(undefined, {
1360
1357
  status: loaded.loaded.status,
1361
1358
  headers: {
1362
1359
  location: encodeURI(loaded.loaded.redirect)
1363
1360
  }
1364
- },
1361
+ }),
1365
1362
  set_cookie_headers
1366
1363
  );
1367
1364
  }
@@ -1372,7 +1369,7 @@ async function respond$1(opts) {
1372
1369
  } catch (err) {
1373
1370
  const e = coalesce_to_error(err);
1374
1371
 
1375
- options.handle_error(e, request);
1372
+ options.handle_error(e, event);
1376
1373
 
1377
1374
  status = 500;
1378
1375
  error = e;
@@ -1398,7 +1395,7 @@ async function respond$1(opts) {
1398
1395
  const error_loaded = /** @type {import('./types').Loaded} */ (
1399
1396
  await load_node({
1400
1397
  ...opts,
1401
- url: request.url,
1398
+ url: event.url,
1402
1399
  node: error_node,
1403
1400
  stuff: node_loaded.stuff,
1404
1401
  is_error: true,
@@ -1418,7 +1415,7 @@ async function respond$1(opts) {
1418
1415
  } catch (err) {
1419
1416
  const e = coalesce_to_error(err);
1420
1417
 
1421
- options.handle_error(e, request);
1418
+ options.handle_error(e, event);
1422
1419
 
1423
1420
  continue;
1424
1421
  }
@@ -1430,7 +1427,7 @@ async function respond$1(opts) {
1430
1427
  // for now just return regular error page
1431
1428
  return with_cookies(
1432
1429
  await respond_with_error({
1433
- request,
1430
+ event,
1434
1431
  options,
1435
1432
  state,
1436
1433
  $session,
@@ -1457,7 +1454,7 @@ async function respond$1(opts) {
1457
1454
  await render_response({
1458
1455
  ...opts,
1459
1456
  stuff,
1460
- url: request.url,
1457
+ url: event.url,
1461
1458
  page_config,
1462
1459
  status,
1463
1460
  error,
@@ -1468,7 +1465,7 @@ async function respond$1(opts) {
1468
1465
  } catch (err) {
1469
1466
  const error = coalesce_to_error(err);
1470
1467
 
1471
- options.handle_error(error, request);
1468
+ options.handle_error(error, event);
1472
1469
 
1473
1470
  return with_cookies(
1474
1471
  await respond_with_error({
@@ -1500,41 +1497,41 @@ function get_page_config(leaf, options) {
1500
1497
  }
1501
1498
 
1502
1499
  /**
1503
- * @param {ServerResponse} response
1500
+ * @param {Response} response
1504
1501
  * @param {string[]} set_cookie_headers
1505
1502
  */
1506
1503
  function with_cookies(response, set_cookie_headers) {
1507
1504
  if (set_cookie_headers.length) {
1508
- response.headers['set-cookie'] = set_cookie_headers;
1505
+ set_cookie_headers.forEach((value) => {
1506
+ response.headers.append('set-cookie', value);
1507
+ });
1509
1508
  }
1510
1509
  return response;
1511
1510
  }
1512
1511
 
1513
1512
  /**
1514
- * @param {import('types/hooks').ServerRequest} request
1513
+ * @param {import('types/hooks').RequestEvent} event
1515
1514
  * @param {import('types/internal').SSRPage} route
1516
1515
  * @param {RegExpExecArray} match
1517
1516
  * @param {import('types/internal').SSRRenderOptions} options
1518
1517
  * @param {import('types/internal').SSRRenderState} state
1519
1518
  * @param {boolean} ssr
1520
- * @returns {Promise<import('types/hooks').ServerResponse | undefined>}
1519
+ * @returns {Promise<Response | undefined>}
1521
1520
  */
1522
- async function render_page(request, route, match, options, state, ssr) {
1521
+ async function render_page(event, route, match, options, state, ssr) {
1523
1522
  if (state.initiator === route) {
1524
1523
  // infinite request cycle detected
1525
- return {
1526
- status: 404,
1527
- headers: {},
1528
- body: `Not found: ${request.url.pathname}`
1529
- };
1524
+ return new Response(`Not found: ${event.url.pathname}`, {
1525
+ status: 404
1526
+ });
1530
1527
  }
1531
1528
 
1532
1529
  const params = route.params ? decode_params(route.params(match)) : {};
1533
1530
 
1534
- const $session = await options.hooks.getSession(request);
1531
+ const $session = await options.hooks.getSession(event);
1535
1532
 
1536
1533
  const response = await respond$1({
1537
- request,
1534
+ event,
1538
1535
  options,
1539
1536
  state,
1540
1537
  $session,
@@ -1552,290 +1549,119 @@ async function render_page(request, route, match, options, state, ssr) {
1552
1549
  // rather than render the error page — which could lead to an
1553
1550
  // infinite loop, if the `load` belonged to the root layout,
1554
1551
  // we respond with a bare-bones 500
1555
- return {
1556
- status: 500,
1557
- headers: {},
1558
- body: `Bad request in load function: failed to fetch ${state.fetched}`
1559
- };
1560
- }
1561
- }
1562
-
1563
- function read_only_form_data() {
1564
- /** @type {Map<string, string[]>} */
1565
- const map = new Map();
1566
-
1567
- return {
1568
- /**
1569
- * @param {string} key
1570
- * @param {string} value
1571
- */
1572
- append(key, value) {
1573
- const existing_values = map.get(key);
1574
- if (existing_values) {
1575
- existing_values.push(value);
1576
- } else {
1577
- map.set(key, [value]);
1578
- }
1579
- },
1580
-
1581
- data: new ReadOnlyFormData(map)
1582
- };
1583
- }
1584
-
1585
- class ReadOnlyFormData {
1586
- /** @type {Map<string, string[]>} */
1587
- #map;
1588
-
1589
- /** @param {Map<string, string[]>} map */
1590
- constructor(map) {
1591
- this.#map = map;
1592
- }
1593
-
1594
- /** @param {string} key */
1595
- get(key) {
1596
- const value = this.#map.get(key);
1597
- if (!value) {
1598
- return null;
1599
- }
1600
- return value[0];
1601
- }
1602
-
1603
- /** @param {string} key */
1604
- getAll(key) {
1605
- return this.#map.get(key) || [];
1606
- }
1607
-
1608
- /** @param {string} key */
1609
- has(key) {
1610
- return this.#map.has(key);
1611
- }
1612
-
1613
- *[Symbol.iterator]() {
1614
- for (const [key, value] of this.#map) {
1615
- for (let i = 0; i < value.length; i += 1) {
1616
- yield [key, value[i]];
1617
- }
1618
- }
1619
- }
1620
-
1621
- *entries() {
1622
- for (const [key, value] of this.#map) {
1623
- for (let i = 0; i < value.length; i += 1) {
1624
- yield [key, value[i]];
1625
- }
1626
- }
1627
- }
1628
-
1629
- *keys() {
1630
- for (const [key] of this.#map) yield key;
1631
- }
1632
-
1633
- *values() {
1634
- for (const [, value] of this.#map) {
1635
- for (let i = 0; i < value.length; i += 1) {
1636
- yield value[i];
1637
- }
1638
- }
1639
- }
1640
- }
1641
-
1642
- /**
1643
- * @param {import('types/app').RawBody} raw
1644
- * @param {import('types/helper').RequestHeaders} headers
1645
- */
1646
- function parse_body(raw, headers) {
1647
- if (!raw) return raw;
1648
-
1649
- const content_type = headers['content-type'];
1650
- const [type, ...directives] = content_type ? content_type.split(/;\s*/) : [];
1651
-
1652
- const text = () => new TextDecoder(headers['content-encoding'] || 'utf-8').decode(raw);
1653
-
1654
- switch (type) {
1655
- case 'text/plain':
1656
- return text();
1657
-
1658
- case 'application/json':
1659
- return JSON.parse(text());
1660
-
1661
- case 'application/x-www-form-urlencoded':
1662
- return get_urlencoded(text());
1663
-
1664
- case 'multipart/form-data': {
1665
- const boundary = directives.find((directive) => directive.startsWith('boundary='));
1666
- if (!boundary) throw new Error('Missing boundary');
1667
- return get_multipart(text(), boundary.slice('boundary='.length));
1668
- }
1669
- default:
1670
- return raw;
1671
- }
1672
- }
1673
-
1674
- /** @param {string} text */
1675
- function get_urlencoded(text) {
1676
- const { data, append } = read_only_form_data();
1677
-
1678
- text
1679
- .replace(/\+/g, ' ')
1680
- .split('&')
1681
- .forEach((str) => {
1682
- const [key, value] = str.split('=');
1683
- append(decodeURIComponent(key), decodeURIComponent(value));
1552
+ return new Response(`Bad request in load function: failed to fetch ${state.fetched}`, {
1553
+ status: 500
1684
1554
  });
1685
-
1686
- return data;
1687
- }
1688
-
1689
- /**
1690
- * @param {string} text
1691
- * @param {string} boundary
1692
- */
1693
- function get_multipart(text, boundary) {
1694
- const parts = text.split(`--${boundary}`);
1695
-
1696
- if (parts[0] !== '' || parts[parts.length - 1].trim() !== '--') {
1697
- throw new Error('Malformed form data');
1698
1555
  }
1699
-
1700
- const { data, append } = read_only_form_data();
1701
-
1702
- parts.slice(1, -1).forEach((part) => {
1703
- const match = /\s*([\s\S]+?)\r\n\r\n([\s\S]*)\s*/.exec(part);
1704
- if (!match) {
1705
- throw new Error('Malformed form data');
1706
- }
1707
- const raw_headers = match[1];
1708
- const body = match[2].trim();
1709
-
1710
- let key;
1711
-
1712
- /** @type {Record<string, string>} */
1713
- const headers = {};
1714
- raw_headers.split('\r\n').forEach((str) => {
1715
- const [raw_header, ...raw_directives] = str.split('; ');
1716
- let [name, value] = raw_header.split(': ');
1717
-
1718
- name = name.toLowerCase();
1719
- headers[name] = value;
1720
-
1721
- /** @type {Record<string, string>} */
1722
- const directives = {};
1723
- raw_directives.forEach((raw_directive) => {
1724
- const [name, value] = raw_directive.split('=');
1725
- directives[name] = JSON.parse(value); // TODO is this right?
1726
- });
1727
-
1728
- if (name === 'content-disposition') {
1729
- if (value !== 'form-data') throw new Error('Malformed form data');
1730
-
1731
- if (directives.filename) {
1732
- // TODO we probably don't want to do this automatically
1733
- throw new Error('File upload is not yet implemented');
1734
- }
1735
-
1736
- if (directives.name) {
1737
- key = directives.name;
1738
- }
1739
- }
1740
- });
1741
-
1742
- if (!key) throw new Error('Malformed form data');
1743
-
1744
- append(key, body);
1745
- });
1746
-
1747
- return data;
1748
1556
  }
1749
1557
 
1750
- /** @type {import('@sveltejs/kit/ssr').Respond} */
1751
- async function respond(incoming, options, state = {}) {
1752
- if (incoming.url.pathname !== '/' && options.trailing_slash !== 'ignore') {
1753
- const has_trailing_slash = incoming.url.pathname.endsWith('/');
1558
+ /** @type {import('types/internal').Respond} */
1559
+ async function respond(request, options, state = {}) {
1560
+ const url = new URL(request.url);
1561
+
1562
+ if (url.pathname !== '/' && options.trailing_slash !== 'ignore') {
1563
+ const has_trailing_slash = url.pathname.endsWith('/');
1754
1564
 
1755
1565
  if (
1756
1566
  (has_trailing_slash && options.trailing_slash === 'never') ||
1757
1567
  (!has_trailing_slash &&
1758
1568
  options.trailing_slash === 'always' &&
1759
- !(incoming.url.pathname.split('/').pop() || '').includes('.'))
1569
+ !(url.pathname.split('/').pop() || '').includes('.'))
1760
1570
  ) {
1761
- incoming.url.pathname = has_trailing_slash
1762
- ? incoming.url.pathname.slice(0, -1)
1763
- : incoming.url.pathname + '/';
1571
+ url.pathname = has_trailing_slash ? url.pathname.slice(0, -1) : url.pathname + '/';
1764
1572
 
1765
- if (incoming.url.search === '?') incoming.url.search = '';
1573
+ if (url.search === '?') url.search = '';
1766
1574
 
1767
- return {
1575
+ return new Response(undefined, {
1768
1576
  status: 301,
1769
1577
  headers: {
1770
- location: incoming.url.pathname + incoming.url.search
1578
+ location: url.pathname + url.search
1771
1579
  }
1772
- };
1580
+ });
1773
1581
  }
1774
1582
  }
1775
1583
 
1776
- const headers = lowercase_keys(incoming.headers);
1777
- const request = {
1778
- ...incoming,
1779
- headers,
1780
- body: parse_body(incoming.rawBody, headers),
1781
- params: {},
1782
- locals: {}
1783
- };
1784
-
1785
1584
  const { parameter, allowed } = options.method_override;
1786
- const method_override = incoming.url.searchParams.get(parameter)?.toUpperCase();
1585
+ const method_override = url.searchParams.get(parameter)?.toUpperCase();
1787
1586
 
1788
1587
  if (method_override) {
1789
- if (request.method.toUpperCase() === 'POST') {
1588
+ if (request.method === 'POST') {
1790
1589
  if (allowed.includes(method_override)) {
1791
- request.method = method_override;
1590
+ request = new Proxy(request, {
1591
+ get: (target, property, _receiver) => {
1592
+ if (property === 'method') return method_override;
1593
+ return Reflect.get(target, property, target);
1594
+ }
1595
+ });
1792
1596
  } else {
1793
1597
  const verb = allowed.length === 0 ? 'enabled' : 'allowed';
1794
1598
  const body = `${parameter}=${method_override} is not ${verb}. See https://kit.svelte.dev/docs#configuration-methodoverride`;
1795
1599
 
1796
- return {
1797
- status: 400,
1798
- headers: {},
1799
- body
1800
- };
1600
+ return new Response(body, {
1601
+ status: 400
1602
+ });
1801
1603
  }
1802
1604
  } else {
1803
1605
  throw new Error(`${parameter}=${method_override} is only allowed with POST requests`);
1804
1606
  }
1805
1607
  }
1806
1608
 
1609
+ /** @type {import('types/hooks').RequestEvent} */
1610
+ const event = {
1611
+ request,
1612
+ url,
1613
+ params: {},
1614
+ locals: {}
1615
+ };
1616
+
1807
1617
  // TODO remove this for 1.0
1808
1618
  /**
1809
1619
  * @param {string} property
1810
1620
  * @param {string} replacement
1621
+ * @param {string} suffix
1811
1622
  */
1812
- const print_error = (property, replacement) => {
1813
- Object.defineProperty(request, property, {
1814
- get: () => {
1815
- throw new Error(`request.${property} has been replaced by request.url.${replacement}`);
1816
- }
1817
- });
1623
+ const removed = (property, replacement, suffix = '') => ({
1624
+ get: () => {
1625
+ throw new Error(`event.${property} has been replaced by event.${replacement}` + suffix);
1626
+ }
1627
+ });
1628
+
1629
+ const details = '. See https://github.com/sveltejs/kit/pull/3384 for details';
1630
+
1631
+ const body_getter = {
1632
+ get: () => {
1633
+ throw new Error(
1634
+ 'To access the request body use the text/json/arrayBuffer/formData methods, e.g. `body = await request.json()`' +
1635
+ details
1636
+ );
1637
+ }
1818
1638
  };
1819
1639
 
1820
- print_error('origin', 'origin');
1821
- print_error('path', 'pathname');
1822
- print_error('query', 'searchParams');
1640
+ Object.defineProperties(event, {
1641
+ method: removed('method', 'request.method', details),
1642
+ headers: removed('headers', 'request.headers', details),
1643
+ origin: removed('origin', 'url.origin'),
1644
+ path: removed('path', 'url.pathname'),
1645
+ query: removed('query', 'url.searchParams'),
1646
+ body: body_getter,
1647
+ rawBody: body_getter
1648
+ });
1823
1649
 
1824
1650
  let ssr = true;
1825
1651
 
1826
1652
  try {
1827
1653
  return await options.hooks.handle({
1828
- request,
1829
- resolve: async (request, opts) => {
1654
+ event,
1655
+ resolve: async (event, opts) => {
1830
1656
  if (opts && 'ssr' in opts) ssr = /** @type {boolean} */ (opts.ssr);
1831
1657
 
1832
1658
  if (state.prerender && state.prerender.fallback) {
1833
1659
  return await render_response({
1834
- url: request.url,
1835
- params: request.params,
1660
+ url: event.url,
1661
+ params: event.params,
1836
1662
  options,
1837
1663
  state,
1838
- $session: await options.hooks.getSession(request),
1664
+ $session: await options.hooks.getSession(event),
1839
1665
  page_config: { router: true, hydrate: true },
1840
1666
  stuff: {},
1841
1667
  status: 200,
@@ -1844,7 +1670,12 @@ async function respond(incoming, options, state = {}) {
1844
1670
  });
1845
1671
  }
1846
1672
 
1847
- const decoded = decodeURI(request.url.pathname).replace(options.paths.base, '');
1673
+ let decoded = decodeURI(event.url.pathname);
1674
+
1675
+ if (options.paths.base) {
1676
+ if (!decoded.startsWith(options.paths.base)) return;
1677
+ decoded = decoded.slice(options.paths.base.length) || '/';
1678
+ }
1848
1679
 
1849
1680
  for (const route of options.manifest._.routes) {
1850
1681
  const match = route.pattern.exec(decoded);
@@ -1852,46 +1683,40 @@ async function respond(incoming, options, state = {}) {
1852
1683
 
1853
1684
  const response =
1854
1685
  route.type === 'endpoint'
1855
- ? await render_endpoint(request, route, match)
1856
- : await render_page(request, route, match, options, state, ssr);
1686
+ ? await render_endpoint(event, route, match)
1687
+ : await render_page(event, route, match, options, state, ssr);
1857
1688
 
1858
1689
  if (response) {
1859
- // inject ETags for 200 responses
1860
- if (response.status === 200) {
1861
- const cache_control = get_single_valued_header(response.headers, 'cache-control');
1862
- if (!cache_control || !/(no-store|immutable)/.test(cache_control)) {
1863
- let if_none_match_value = request.headers['if-none-match'];
1864
- // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
1865
- if (if_none_match_value?.startsWith('W/"')) {
1866
- if_none_match_value = if_none_match_value.substring(2);
1867
- }
1690
+ // respond with 304 if etag matches
1691
+ if (response.status === 200 && response.headers.has('etag')) {
1692
+ let if_none_match_value = request.headers.get('if-none-match');
1868
1693
 
1869
- const etag = `"${hash(response.body || '')}"`;
1870
-
1871
- if (if_none_match_value === etag) {
1872
- /** @type {import('types/helper').ResponseHeaders} */
1873
- const headers = { etag };
1874
-
1875
- // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
1876
- for (const key of [
1877
- 'cache-control',
1878
- 'content-location',
1879
- 'date',
1880
- 'expires',
1881
- 'vary'
1882
- ]) {
1883
- if (key in response.headers) {
1884
- headers[key] = /** @type {string} */ (response.headers[key]);
1885
- }
1886
- }
1694
+ // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
1695
+ if (if_none_match_value?.startsWith('W/"')) {
1696
+ if_none_match_value = if_none_match_value.substring(2);
1697
+ }
1887
1698
 
1888
- return {
1889
- status: 304,
1890
- headers
1891
- };
1699
+ const etag = /** @type {string} */ (response.headers.get('etag'));
1700
+
1701
+ if (if_none_match_value === etag) {
1702
+ const headers = new Headers({ etag });
1703
+
1704
+ // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
1705
+ for (const key of [
1706
+ 'cache-control',
1707
+ 'content-location',
1708
+ 'date',
1709
+ 'expires',
1710
+ 'vary'
1711
+ ]) {
1712
+ const value = response.headers.get(key);
1713
+ if (value) headers.set(key, value);
1892
1714
  }
1893
1715
 
1894
- response.headers['etag'] = etag;
1716
+ return new Response(undefined, {
1717
+ status: 304,
1718
+ headers
1719
+ });
1895
1720
  }
1896
1721
  }
1897
1722
 
@@ -1902,28 +1727,34 @@ async function respond(incoming, options, state = {}) {
1902
1727
  // if this request came direct from the user, rather than
1903
1728
  // via a `fetch` in a `load`, render a 404 page
1904
1729
  if (!state.initiator) {
1905
- const $session = await options.hooks.getSession(request);
1730
+ const $session = await options.hooks.getSession(event);
1906
1731
  return await respond_with_error({
1907
- request,
1732
+ event,
1908
1733
  options,
1909
1734
  state,
1910
1735
  $session,
1911
1736
  status: 404,
1912
- error: new Error(`Not found: ${request.url.pathname}`),
1737
+ error: new Error(`Not found: ${event.url.pathname}`),
1913
1738
  ssr
1914
1739
  });
1915
1740
  }
1741
+ },
1742
+
1743
+ // TODO remove for 1.0
1744
+ // @ts-expect-error
1745
+ get request() {
1746
+ throw new Error('request in handle has been replaced with event' + details);
1916
1747
  }
1917
1748
  });
1918
1749
  } catch (/** @type {unknown} */ e) {
1919
1750
  const error = coalesce_to_error(e);
1920
1751
 
1921
- options.handle_error(error, request);
1752
+ options.handle_error(error, event);
1922
1753
 
1923
1754
  try {
1924
- const $session = await options.hooks.getSession(request);
1755
+ const $session = await options.hooks.getSession(event);
1925
1756
  return await respond_with_error({
1926
- request,
1757
+ event,
1927
1758
  options,
1928
1759
  state,
1929
1760
  $session,
@@ -1934,11 +1765,9 @@ async function respond(incoming, options, state = {}) {
1934
1765
  } catch (/** @type {unknown} */ e) {
1935
1766
  const error = coalesce_to_error(e);
1936
1767
 
1937
- return {
1938
- status: 500,
1939
- headers: {},
1940
- body: options.dev ? error.stack : error.message
1941
- };
1768
+ return new Response(options.dev ? error.stack : error.message, {
1769
+ status: 500
1770
+ });
1942
1771
  }
1943
1772
  }
1944
1773
  }
@@ -2057,6 +1886,14 @@ async function create_plugin(config, cwd) {
2057
1886
  vite.watcher.on('add', update_manifest);
2058
1887
  vite.watcher.on('remove', update_manifest);
2059
1888
 
1889
+ const assets = config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base;
1890
+ const asset_server = sirv(config.kit.files.assets, {
1891
+ dev: true,
1892
+ etag: true,
1893
+ maxAge: 0,
1894
+ extensions: []
1895
+ });
1896
+
2060
1897
  return () => {
2061
1898
  remove_html_middlewares(vite.middlewares);
2062
1899
 
@@ -2065,8 +1902,24 @@ async function create_plugin(config, cwd) {
2065
1902
  if (!req.url || !req.method) throw new Error('Incomplete request');
2066
1903
  if (req.url === '/favicon.ico') return not_found(res);
2067
1904
 
2068
- const parsed = new URL$1(req.url, 'http://localhost/');
2069
- if (!parsed.pathname.startsWith(config.kit.paths.base)) return not_found(res);
1905
+ const url = new URL$1(
1906
+ `${vite.config.server.https ? 'https' : 'http'}://${req.headers.host}${req.url}`
1907
+ );
1908
+
1909
+ const decoded = decodeURI(url.pathname);
1910
+
1911
+ if (decoded.startsWith(assets)) {
1912
+ const pathname = decoded.slice(assets.length);
1913
+ const file = config.kit.files.assets + pathname;
1914
+
1915
+ if (fs__default.existsSync(file) && !fs__default.statSync(file).isDirectory()) {
1916
+ req.url = encodeURI(pathname); // don't need query/hash
1917
+ asset_server(req, res);
1918
+ return;
1919
+ }
1920
+ }
1921
+
1922
+ if (!decoded.startsWith(config.kit.paths.base)) return not_found(res);
2070
1923
 
2071
1924
  /** @type {Partial<import('types/internal').Hooks>} */
2072
1925
  const user_hooks = resolve_entry(config.kit.files.hooks)
@@ -2076,7 +1929,7 @@ async function create_plugin(config, cwd) {
2076
1929
  /** @type {import('types/internal').Hooks} */
2077
1930
  const hooks = {
2078
1931
  getSession: user_hooks.getSession || (() => ({})),
2079
- handle: user_hooks.handle || (({ request, resolve }) => resolve(request)),
1932
+ handle: user_hooks.handle || (({ event, resolve }) => resolve(event)),
2080
1933
  handleError:
2081
1934
  user_hooks.handleError ||
2082
1935
  (({ /** @type {Error & { frame?: string }} */ error }) => {
@@ -2110,7 +1963,7 @@ async function create_plugin(config, cwd) {
2110
1963
 
2111
1964
  paths.set_paths({
2112
1965
  base: config.kit.paths.base,
2113
- assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base
1966
+ assets
2114
1967
  });
2115
1968
 
2116
1969
  let body;
@@ -2123,14 +1976,11 @@ async function create_plugin(config, cwd) {
2123
1976
  }
2124
1977
 
2125
1978
  const rendered = await respond(
2126
- {
2127
- url: new URL$1(
2128
- `${vite.config.server.https ? 'https' : 'http'}://${req.headers.host}${req.url}`
2129
- ),
2130
- headers: /** @type {import('types/helper').RequestHeaders} */ (req.headers),
1979
+ new Request(url.href, {
1980
+ headers: /** @type {Record<string, string>} */ (req.headers),
2131
1981
  method: req.method,
2132
- rawBody: body
2133
- },
1982
+ body
1983
+ }),
2134
1984
  {
2135
1985
  amp: config.kit.amp,
2136
1986
  dev: true,
@@ -2139,9 +1989,20 @@ async function create_plugin(config, cwd) {
2139
1989
  vite.ssrFixStacktrace(error);
2140
1990
  return error.stack;
2141
1991
  },
2142
- handle_error: (error, request) => {
1992
+ handle_error: (error, event) => {
2143
1993
  vite.ssrFixStacktrace(error);
2144
- hooks.handleError({ error, request });
1994
+ hooks.handleError({
1995
+ error,
1996
+ event,
1997
+
1998
+ // TODO remove for 1.0
1999
+ // @ts-expect-error
2000
+ get request() {
2001
+ throw new Error(
2002
+ 'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
2003
+ );
2004
+ }
2005
+ });
2145
2006
  },
2146
2007
  hooks,
2147
2008
  hydrate: config.kit.hydrate,
@@ -2149,7 +2010,7 @@ async function create_plugin(config, cwd) {
2149
2010
  method_override: config.kit.methodOverride,
2150
2011
  paths: {
2151
2012
  base: config.kit.paths.base,
2152
- assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base
2013
+ assets
2153
2014
  },
2154
2015
  prefix: '',
2155
2016
  prerender: config.kit.prerender.enabled,
@@ -2214,9 +2075,7 @@ async function create_plugin(config, cwd) {
2214
2075
  );
2215
2076
 
2216
2077
  if (rendered) {
2217
- res.writeHead(rendered.status, rendered.headers);
2218
- if (rendered.body) res.write(rendered.body);
2219
- res.end();
2078
+ setResponse(res, rendered);
2220
2079
  } else {
2221
2080
  not_found(res);
2222
2081
  }
@@ -2352,7 +2211,6 @@ async function dev({ cwd, port, host, https, config }) {
2352
2211
  }),
2353
2212
  await create_plugin(config, cwd)
2354
2213
  ],
2355
- publicDir: config.kit.files.assets,
2356
2214
  base: '/'
2357
2215
  });
2358
2216