@sveltejs/kit 1.0.0-next.360 → 1.0.0-next.363

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.
@@ -1140,7 +1140,11 @@ function create_client({ target, session, base, trailing_slash }) {
1140
1140
  props = res.status === 204 ? {} : await res.json();
1141
1141
  } else {
1142
1142
  status = res.status;
1143
- error = new Error('Failed to load data');
1143
+ try {
1144
+ error = await res.json();
1145
+ } catch (e) {
1146
+ error = new Error('Failed to load data');
1147
+ }
1144
1148
  }
1145
1149
  }
1146
1150
 
@@ -20,6 +20,60 @@ function to_headers(object) {
20
20
  return headers;
21
21
  }
22
22
 
23
+ /**
24
+ * Given an Accept header and a list of possible content types, pick
25
+ * the most suitable one to respond with
26
+ * @param {string} accept
27
+ * @param {string[]} types
28
+ */
29
+ function negotiate(accept, types) {
30
+ const parts = accept
31
+ .split(',')
32
+ .map((str, i) => {
33
+ const match = /([^/]+)\/([^;]+)(?:;q=([0-9.]+))?/.exec(str);
34
+ if (match) {
35
+ const [, type, subtype, q = '1'] = match;
36
+ return { type, subtype, q: +q, i };
37
+ }
38
+
39
+ throw new Error(`Invalid Accept header: ${accept}`);
40
+ })
41
+ .sort((a, b) => {
42
+ if (a.q !== b.q) {
43
+ return b.q - a.q;
44
+ }
45
+
46
+ if ((a.subtype === '*') !== (b.subtype === '*')) {
47
+ return a.subtype === '*' ? 1 : -1;
48
+ }
49
+
50
+ if ((a.type === '*') !== (b.type === '*')) {
51
+ return a.type === '*' ? 1 : -1;
52
+ }
53
+
54
+ return a.i - b.i;
55
+ });
56
+
57
+ let accepted;
58
+ let min_priority = Infinity;
59
+
60
+ for (const mimetype of types) {
61
+ const [type, subtype] = mimetype.split('/');
62
+ const priority = parts.findIndex(
63
+ (part) =>
64
+ (part.type === type || part.type === '*') &&
65
+ (part.subtype === subtype || part.subtype === '*')
66
+ );
67
+
68
+ if (priority !== -1 && priority < min_priority) {
69
+ accepted = mimetype;
70
+ min_priority = priority;
71
+ }
72
+ }
73
+
74
+ return accepted;
75
+ }
76
+
23
77
  /**
24
78
  * Hash using djb2
25
79
  * @param {import('types').StrictBody} value
@@ -95,6 +149,47 @@ function normalize_request_method(event) {
95
149
  return method === 'delete' ? 'del' : method; // 'delete' is a reserved word
96
150
  }
97
151
 
152
+ /**
153
+ * Serialize an error into a JSON string, by copying its `name`, `message`
154
+ * and (in dev) `stack`, plus any custom properties, plus recursively
155
+ * serialized `cause` properties. This is necessary because
156
+ * `JSON.stringify(error) === '{}'`
157
+ * @param {Error} error
158
+ * @param {(error: Error) => string | undefined} get_stack
159
+ */
160
+ function serialize_error(error, get_stack) {
161
+ return JSON.stringify(clone_error(error, get_stack));
162
+ }
163
+
164
+ /**
165
+ * @param {Error} error
166
+ * @param {(error: Error) => string | undefined} get_stack
167
+ */
168
+ function clone_error(error, get_stack) {
169
+ const {
170
+ name,
171
+ message,
172
+ // this should constitute 'using' a var, since it affects `custom`
173
+ // eslint-disable-next-line
174
+ stack,
175
+ // @ts-expect-error i guess typescript doesn't know about error.cause yet
176
+ cause,
177
+ ...custom
178
+ } = error;
179
+
180
+ /** @type {Record<string, any>} */
181
+ const object = { name, message, stack: get_stack(error) };
182
+
183
+ if (cause) object.cause = clone_error(cause, get_stack);
184
+
185
+ for (const key in custom) {
186
+ // @ts-expect-error
187
+ object[key] = custom[key];
188
+ }
189
+
190
+ return object;
191
+ }
192
+
98
193
  /** @param {string} body */
99
194
  function error(body) {
100
195
  return new Response(body, {
@@ -132,9 +227,10 @@ function is_text(content_type) {
132
227
  /**
133
228
  * @param {import('types').RequestEvent} event
134
229
  * @param {{ [method: string]: import('types').RequestHandler }} mod
230
+ * @param {import('types').SSROptions} options
135
231
  * @returns {Promise<Response>}
136
232
  */
137
- async function render_endpoint(event, mod) {
233
+ async function render_endpoint(event, mod, options) {
138
234
  const method = normalize_request_method(event);
139
235
 
140
236
  /** @type {import('types').RequestHandler} */
@@ -193,9 +289,12 @@ async function render_endpoint(event, mod) {
193
289
 
194
290
  const type = headers.get('content-type');
195
291
 
196
- if (!is_text(type) && !(body instanceof Uint8Array || is_string(body))) {
292
+ if (
293
+ !is_text(type) &&
294
+ !(body instanceof Uint8Array || body instanceof ReadableStream || is_string(body))
295
+ ) {
197
296
  return error(
198
- `${preface}: body must be an instance of string or Uint8Array if content-type is not a supported textual content-type`
297
+ `${preface}: body must be an instance of string, Uint8Array or ReadableStream if content-type is not a supported textual content-type`
199
298
  );
200
299
  }
201
300
 
@@ -204,7 +303,8 @@ async function render_endpoint(event, mod) {
204
303
 
205
304
  if (is_pojo(body) && (!type || type.startsWith('application/json'))) {
206
305
  headers.set('content-type', 'application/json; charset=utf-8');
207
- normalized_body = JSON.stringify(body);
306
+ normalized_body =
307
+ body instanceof Error ? serialize_error(body, options.get_stack) : JSON.stringify(body);
208
308
  } else {
209
309
  normalized_body = /** @type {import('types').StrictBody} */ (body);
210
310
  }
@@ -1310,7 +1410,7 @@ async function render_response({
1310
1410
  trailing_slash: ${s(options.trailing_slash)},
1311
1411
  hydrate: ${resolve_opts.ssr && page_config.hydrate ? `{
1312
1412
  status: ${status},
1313
- error: ${serialize_error(error)},
1413
+ error: ${error && serialize_error(error, e => e.stack)},
1314
1414
  nodes: [${branch.map(({ node }) => node.index).join(', ')}],
1315
1415
  params: ${devalue(event.params)},
1316
1416
  routeId: ${s(event.routeId)}
@@ -1431,10 +1531,6 @@ async function render_response({
1431
1531
  headers.set('cache-control', `${is_private ? 'private' : 'public'}, max-age=${cache.maxage}`);
1432
1532
  }
1433
1533
 
1434
- if (!options.floc) {
1435
- headers.set('permissions-policy', 'interest-cohort=()');
1436
- }
1437
-
1438
1534
  if (!state.prerendering) {
1439
1535
  const csp_header = csp.get_header();
1440
1536
  if (csp_header) {
@@ -1461,22 +1557,6 @@ function try_serialize(data, fail) {
1461
1557
  }
1462
1558
  }
1463
1559
 
1464
- // Ensure we return something truthy so the client will not re-render the page over the error
1465
-
1466
- /** @param {(Error & {frame?: string} & {loc?: object}) | undefined | null} error */
1467
- function serialize_error(error) {
1468
- if (!error) return null;
1469
- let serialized = try_serialize(error);
1470
- if (!serialized) {
1471
- const { name, message, stack } = error;
1472
- serialized = try_serialize({ ...error, name, message, stack });
1473
- }
1474
- if (!serialized) {
1475
- serialized = '{}';
1476
- }
1477
- return serialized;
1478
- }
1479
-
1480
1560
  /*!
1481
1561
  * cookie
1482
1562
  * Copyright(c) 2012-2014 Roman Shtylman
@@ -2175,6 +2255,7 @@ async function load_node({
2175
2255
  for (const [key, value] of event.request.headers) {
2176
2256
  if (
2177
2257
  key !== 'authorization' &&
2258
+ key !== 'connection' &&
2178
2259
  key !== 'cookie' &&
2179
2260
  key !== 'host' &&
2180
2261
  key !== 'if-none-match' &&
@@ -2481,22 +2562,23 @@ async function load_shadow_data(route, event, options, prerender) {
2481
2562
  };
2482
2563
 
2483
2564
  if (!is_get) {
2484
- const result = await handler(event);
2485
-
2486
- // TODO remove for 1.0
2487
- // @ts-expect-error
2488
- if (result.fallthrough) {
2489
- throw new Error(
2490
- 'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
2491
- );
2492
- }
2493
-
2494
- const { status, headers, body } = validate_shadow_output(result);
2565
+ const { status, headers, body } = validate_shadow_output(await handler(event));
2566
+ add_cookies(/** @type {string[]} */ (data.cookies), headers);
2495
2567
  data.status = status;
2496
2568
 
2497
- add_cookies(/** @type {string[]} */ (data.cookies), headers);
2569
+ // explicit errors cause an error page...
2570
+ if (body instanceof Error) {
2571
+ if (status < 400) {
2572
+ data.status = 500;
2573
+ data.error = new Error('A non-error status code was returned with an error body');
2574
+ } else {
2575
+ data.error = body;
2576
+ }
2498
2577
 
2499
- // Redirects are respected...
2578
+ return data;
2579
+ }
2580
+
2581
+ // ...redirects are respected...
2500
2582
  if (status >= 300 && status < 400) {
2501
2583
  data.redirect = /** @type {string} */ (
2502
2584
  headers instanceof Headers ? headers.get('location') : headers.location
@@ -2512,20 +2594,21 @@ async function load_shadow_data(route, event, options, prerender) {
2512
2594
 
2513
2595
  const get = (method === 'head' && mod.head) || mod.get;
2514
2596
  if (get) {
2515
- const result = await get(event);
2516
-
2517
- // TODO remove for 1.0
2518
- // @ts-expect-error
2519
- if (result.fallthrough) {
2520
- throw new Error(
2521
- 'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
2522
- );
2523
- }
2524
-
2525
- const { status, headers, body } = validate_shadow_output(result);
2597
+ const { status, headers, body } = validate_shadow_output(await get(event));
2526
2598
  add_cookies(/** @type {string[]} */ (data.cookies), headers);
2527
2599
  data.status = status;
2528
2600
 
2601
+ if (body instanceof Error) {
2602
+ if (status < 400) {
2603
+ data.status = 500;
2604
+ data.error = new Error('A non-error status code was returned with an error body');
2605
+ } else {
2606
+ data.error = body;
2607
+ }
2608
+
2609
+ return data;
2610
+ }
2611
+
2529
2612
  if (status >= 400) {
2530
2613
  data.error = new Error('Failed to load data');
2531
2614
  return data;
@@ -2572,6 +2655,14 @@ function add_cookies(target, headers) {
2572
2655
  * @param {import('types').ShadowEndpointOutput} result
2573
2656
  */
2574
2657
  function validate_shadow_output(result) {
2658
+ // TODO remove for 1.0
2659
+ // @ts-expect-error
2660
+ if (result.fallthrough) {
2661
+ throw new Error(
2662
+ 'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
2663
+ );
2664
+ }
2665
+
2575
2666
  const { status = 200, body = {} } = result;
2576
2667
  let headers = result.headers || {};
2577
2668
 
@@ -2586,7 +2677,9 @@ function validate_shadow_output(result) {
2586
2677
  }
2587
2678
 
2588
2679
  if (!is_pojo(body)) {
2589
- throw new Error('Body returned from endpoint request handler must be a plain object');
2680
+ throw new Error(
2681
+ 'Body returned from endpoint request handler must be a plain object or an Error'
2682
+ );
2590
2683
  }
2591
2684
 
2592
2685
  return { status, headers, body };
@@ -2979,7 +3072,7 @@ async function render_page(event, route, options, state, resolve_opts) {
2979
3072
  ]);
2980
3073
 
2981
3074
  if (type === 'application/json') {
2982
- return render_endpoint(event, await route.shadow());
3075
+ return render_endpoint(event, await route.shadow(), options);
2983
3076
  }
2984
3077
  }
2985
3078
 
@@ -2995,58 +3088,6 @@ async function render_page(event, route, options, state, resolve_opts) {
2995
3088
  });
2996
3089
  }
2997
3090
 
2998
- /**
2999
- * @param {string} accept
3000
- * @param {string[]} types
3001
- */
3002
- function negotiate(accept, types) {
3003
- const parts = accept
3004
- .split(',')
3005
- .map((str, i) => {
3006
- const match = /([^/]+)\/([^;]+)(?:;q=([0-9.]+))?/.exec(str);
3007
- if (match) {
3008
- const [, type, subtype, q = '1'] = match;
3009
- return { type, subtype, q: +q, i };
3010
- }
3011
-
3012
- throw new Error(`Invalid Accept header: ${accept}`);
3013
- })
3014
- .sort((a, b) => {
3015
- if (a.q !== b.q) {
3016
- return b.q - a.q;
3017
- }
3018
-
3019
- if ((a.subtype === '*') !== (b.subtype === '*')) {
3020
- return a.subtype === '*' ? 1 : -1;
3021
- }
3022
-
3023
- if ((a.type === '*') !== (b.type === '*')) {
3024
- return a.type === '*' ? 1 : -1;
3025
- }
3026
-
3027
- return a.i - b.i;
3028
- });
3029
-
3030
- let accepted;
3031
- let min_priority = Infinity;
3032
-
3033
- for (const mimetype of types) {
3034
- const [type, subtype] = mimetype.split('/');
3035
- const priority = parts.findIndex(
3036
- (part) =>
3037
- (part.type === type || part.type === '*') &&
3038
- (part.subtype === subtype || part.subtype === '*')
3039
- );
3040
-
3041
- if (priority !== -1 && priority < min_priority) {
3042
- accepted = mimetype;
3043
- min_priority = priority;
3044
- }
3045
- }
3046
-
3047
- return accepted;
3048
- }
3049
-
3050
3091
  /**
3051
3092
  * @param {RegExpMatchArray} match
3052
3093
  * @param {string[]} names
@@ -3277,7 +3318,7 @@ async function respond(request, options, state) {
3277
3318
  let response;
3278
3319
 
3279
3320
  if (is_data_request && route.type === 'page' && route.shadow) {
3280
- response = await render_endpoint(event, await route.shadow());
3321
+ response = await render_endpoint(event, await route.shadow(), options);
3281
3322
 
3282
3323
  // loading data for a client-side transition is a special case
3283
3324
  if (request.headers.has('x-sveltekit-load')) {
@@ -3299,7 +3340,7 @@ async function respond(request, options, state) {
3299
3340
  } else {
3300
3341
  response =
3301
3342
  route.type === 'endpoint'
3302
- ? await render_endpoint(event, await route.load())
3343
+ ? await render_endpoint(event, await route.load(), options)
3303
3344
  : await render_page(event, route, options, state, resolve_opts);
3304
3345
  }
3305
3346
 
@@ -3383,6 +3424,18 @@ async function respond(request, options, state) {
3383
3424
 
3384
3425
  options.handle_error(error, event);
3385
3426
 
3427
+ const type = negotiate(event.request.headers.get('accept') || 'text/html', [
3428
+ 'text/html',
3429
+ 'application/json'
3430
+ ]);
3431
+
3432
+ if (is_data_request || type === 'application/json') {
3433
+ return new Response(serialize_error(error, options.get_stack), {
3434
+ status: 500,
3435
+ headers: { 'content-type': 'application/json; charset=utf-8' }
3436
+ });
3437
+ }
3438
+
3386
3439
  try {
3387
3440
  const $session = await options.hooks.getSession(event);
3388
3441
  return await respond_with_error({
@@ -239,8 +239,6 @@ const options = object(
239
239
  template: string(join('src', 'app.html'))
240
240
  }),
241
241
 
242
- floc: boolean(false),
243
-
244
242
  // TODO: remove this for the 1.0 release
245
243
  headers: error(
246
244
  (keypath) =>
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ function handle_error(e) {
18
18
  process.exit(1);
19
19
  }
20
20
 
21
- const prog = sade('svelte-kit').version('1.0.0-next.360');
21
+ const prog = sade('svelte-kit').version('1.0.0-next.363');
22
22
 
23
23
  prog
24
24
  .command('package')
@@ -3495,7 +3495,7 @@ class RedirectHandler$2 {
3495
3495
  return this.handler.onHeaders(statusCode, headers, resume, statusText)
3496
3496
  }
3497
3497
 
3498
- const { origin, pathname, search } = util$b.parseURL(new URL(this.location, this.opts.origin));
3498
+ const { origin, pathname, search } = util$b.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
3499
3499
  const path = search ? `${pathname}${search}` : pathname;
3500
3500
 
3501
3501
  // Remove headers referring to the original URL.
@@ -9510,8 +9510,7 @@ function requireRequest () {
9510
9510
  webidl.converters.RequestInit = webidl.dictionaryConverter([
9511
9511
  {
9512
9512
  key: 'method',
9513
- converter: webidl.converters.ByteString,
9514
- defaultValue: 'GET'
9513
+ converter: webidl.converters.ByteString
9515
9514
  },
9516
9515
  {
9517
9516
  key: 'headers',
@@ -10261,7 +10260,7 @@ function requireFetch () {
10261
10260
  }
10262
10261
 
10263
10262
  // https://fetch.spec.whatwg.org/#fetch-method
10264
- async function fetch (input, init = undefined) {
10263
+ async function fetch (input, init = {}) {
10265
10264
  if (arguments.length < 1) {
10266
10265
  throw new TypeError(
10267
10266
  `Failed to execute 'fetch' on 'Window': 1 argument required, but only ${arguments.length} present.`
package/dist/vite.js CHANGED
@@ -353,7 +353,6 @@ export class Server {
353
353
  this.options = {
354
354
  csp: ${s(config.kit.csp)},
355
355
  dev: false,
356
- floc: ${config.kit.floc},
357
356
  get_stack: error => String(error), // for security
358
357
  handle_error: (error, event) => {
359
358
  this.options.hooks.handleError({
@@ -2312,7 +2311,6 @@ async function dev(vite, svelte_config) {
2312
2311
  {
2313
2312
  csp: svelte_config.kit.csp,
2314
2313
  dev: true,
2315
- floc: svelte_config.kit.floc,
2316
2314
  get_stack: (error) => {
2317
2315
  return fix_stack_trace(error);
2318
2316
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.360",
3
+ "version": "1.0.0-next.363",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -43,8 +43,8 @@
43
43
  "svelte-preprocess": "^4.10.6",
44
44
  "svelte2tsx": "~0.5.10",
45
45
  "tiny-glob": "^0.2.9",
46
- "typescript": "^4.7.2",
47
- "undici": "^5.6.0",
46
+ "typescript": "^4.7.4",
47
+ "undici": "^5.6.1",
48
48
  "uvu": "^0.5.3",
49
49
  "vite": "^2.9.13"
50
50
  },
@@ -81,7 +81,7 @@
81
81
  },
82
82
  "types": "types/index.d.ts",
83
83
  "engines": {
84
- "node": ">=16.7"
84
+ "node": ">=16.9"
85
85
  },
86
86
  "scripts": {
87
87
  "build": "rollup -c && node scripts/cp.js src/runtime/components assets/components && npm run types",
package/types/index.d.ts CHANGED
@@ -117,7 +117,6 @@ export interface KitConfig {
117
117
  serviceWorker?: string;
118
118
  template?: string;
119
119
  };
120
- floc?: boolean;
121
120
  inlineStyleThreshold?: number;
122
121
  methodOverride?: {
123
122
  parameter?: string;
@@ -268,7 +267,7 @@ export interface ResolveOptions {
268
267
  transformPage?: ({ html }: { html: string }) => MaybePromise<string>;
269
268
  }
270
269
 
271
- export type ResponseBody = JSONValue | Uint8Array | ReadableStream;
270
+ export type ResponseBody = JSONValue | Uint8Array | ReadableStream | Error;
272
271
 
273
272
  export class Server {
274
273
  constructor(manifest: SSRManifest);
@@ -176,13 +176,6 @@ export interface ShadowEndpointOutput<Output extends JSONObject = JSONObject> {
176
176
  body?: Output;
177
177
  }
178
178
 
179
- /**
180
- * The route key of a page with a matching endpoint — used to ensure the
181
- * client loads data from the right endpoint during client-side navigation
182
- * rather than a different route that happens to match the path
183
- */
184
- type ShadowKey = string;
185
-
186
179
  export interface ShadowRequestHandler<Output extends JSONObject = JSONObject> {
187
180
  (event: RequestEvent): MaybePromise<ShadowEndpointOutput<Output>>;
188
181
  }
@@ -244,7 +237,6 @@ export type SSRNodeLoader = () => Promise<SSRNode>;
244
237
  export interface SSROptions {
245
238
  csp: ValidatedConfig['kit']['csp'];
246
239
  dev: boolean;
247
- floc: boolean;
248
240
  get_stack: (error: Error) => string | undefined;
249
241
  handle_error(error: Error & { frame?: string }, event: RequestEvent): void;
250
242
  hooks: Hooks;