@sveltejs/kit 1.0.0-next.457 → 1.0.0-next.458

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.457",
3
+ "version": "1.0.0-next.458",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -1,4 +1,4 @@
1
- import { HttpError, Redirect } from '../control.js';
1
+ import { Redirect } from '../control.js';
2
2
  import { check_method_names, method_not_allowed } from './utils.js';
3
3
 
4
4
  /**
@@ -39,9 +39,8 @@ export async function render_endpoint(event, mod, state) {
39
39
  );
40
40
 
41
41
  if (!(response instanceof Response)) {
42
- return new Response(
43
- `Invalid response from route ${event.url.pathname}: handler should return a Response object`,
44
- { status: 500 }
42
+ throw new Error(
43
+ `Invalid response from route ${event.url.pathname}: handler should return a Response object`
45
44
  );
46
45
  }
47
46
 
@@ -52,15 +51,13 @@ export async function render_endpoint(event, mod, state) {
52
51
 
53
52
  return response;
54
53
  } catch (error) {
55
- if (error instanceof HttpError) {
56
- return new Response(error.message, { status: error.status });
57
- } else if (error instanceof Redirect) {
54
+ if (error instanceof Redirect) {
58
55
  return new Response(undefined, {
59
56
  status: error.status,
60
- headers: { Location: error.location }
57
+ headers: { location: error.location }
61
58
  });
62
- } else {
63
- throw error;
64
59
  }
60
+
61
+ throw error;
65
62
  }
66
63
  }
@@ -3,10 +3,9 @@ import { render_page } from './page/index.js';
3
3
  import { render_response } from './page/render.js';
4
4
  import { respond_with_error } from './page/respond_with_error.js';
5
5
  import { coalesce_to_error } from '../../utils/error.js';
6
- import { serialize_error, GENERIC_ERROR, static_error_page } from './utils.js';
6
+ import { GENERIC_ERROR, handle_fatal_error } from './utils.js';
7
7
  import { decode_params, disable_search, normalize_path } from '../../utils/url.js';
8
8
  import { exec } from '../../utils/routing.js';
9
- import { negotiate } from '../../utils/http.js';
10
9
  import { render_data } from './data/index.js';
11
10
  import { DATA_SUFFIX } from '../../constants.js';
12
11
 
@@ -190,175 +189,151 @@ export async function respond(request, options, state) {
190
189
  transformPageChunk: default_transform
191
190
  };
192
191
 
193
- try {
194
- const response = await options.hooks.handle({
195
- event,
196
- resolve: async (event, opts) => {
197
- if (opts) {
198
- // TODO remove for 1.0
199
- if ('transformPage' in opts) {
200
- throw new Error(
201
- 'transformPage has been replaced by transformPageChunk — see https://github.com/sveltejs/kit/pull/5657 for more information'
202
- );
203
- }
204
-
205
- if ('ssr' in opts) {
206
- throw new Error(
207
- 'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
208
- );
209
- }
210
-
211
- resolve_opts = {
212
- transformPageChunk: opts.transformPageChunk || default_transform
213
- };
192
+ /**
193
+ *
194
+ * @param {import('types').RequestEvent} event
195
+ * @param {import('types').ResolveOptions} [opts]
196
+ */
197
+ async function resolve(event, opts) {
198
+ try {
199
+ if (opts) {
200
+ // TODO remove for 1.0
201
+ if ('transformPage' in opts) {
202
+ throw new Error(
203
+ 'transformPage has been replaced by transformPageChunk — see https://github.com/sveltejs/kit/pull/5657 for more information'
204
+ );
214
205
  }
215
206
 
216
- if (state.prerendering?.fallback) {
217
- return await render_response({
218
- event,
219
- options,
220
- state,
221
- page_config: { ssr: false, csr: true },
222
- status: 200,
223
- error: null,
224
- branch: [],
225
- fetched: [],
226
- validation_errors: undefined,
227
- cookies: [],
228
- resolve_opts
229
- });
207
+ if ('ssr' in opts) {
208
+ throw new Error(
209
+ 'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
210
+ );
230
211
  }
231
212
 
232
- if (route) {
233
- /** @type {Response} */
234
- let response;
235
-
236
- if (is_data_request) {
237
- response = await render_data(event, route, options, state);
238
- } else if (route.page) {
239
- response = await render_page(event, route, route.page, options, state, resolve_opts);
240
- } else if (route.endpoint) {
241
- response = await render_endpoint(event, await route.endpoint(), state);
242
- } else {
243
- // a route will always have a page or an endpoint, but TypeScript
244
- // doesn't know that
245
- throw new Error('This should never happen');
246
- }
213
+ resolve_opts = {
214
+ transformPageChunk: opts.transformPageChunk || default_transform
215
+ };
216
+ }
247
217
 
248
- if (!is_data_request) {
249
- // we only want to set cookies on __data.js requests, we don't
250
- // want to cache stuff erroneously etc
251
- for (const key in headers) {
252
- const value = headers[key];
253
- response.headers.set(key, /** @type {string} */ (value));
254
- }
255
- }
218
+ if (state.prerendering?.fallback) {
219
+ return await render_response({
220
+ event,
221
+ options,
222
+ state,
223
+ page_config: { ssr: false, csr: true },
224
+ status: 200,
225
+ error: null,
226
+ branch: [],
227
+ fetched: [],
228
+ validation_errors: undefined,
229
+ cookies: [],
230
+ resolve_opts
231
+ });
232
+ }
233
+
234
+ if (route) {
235
+ /** @type {Response} */
236
+ let response;
256
237
 
257
- for (const cookie of cookies) {
258
- response.headers.append('set-cookie', cookie);
238
+ if (is_data_request) {
239
+ response = await render_data(event, route, options, state);
240
+ } else if (route.page) {
241
+ response = await render_page(event, route, route.page, options, state, resolve_opts);
242
+ } else if (route.endpoint) {
243
+ response = await render_endpoint(event, await route.endpoint(), state);
244
+ } else {
245
+ // a route will always have a page or an endpoint, but TypeScript
246
+ // doesn't know that
247
+ throw new Error('This should never happen');
248
+ }
249
+
250
+ if (!is_data_request) {
251
+ // we only want to set cookies on __data.js requests, we don't
252
+ // want to cache stuff erroneously etc
253
+ for (const key in headers) {
254
+ const value = headers[key];
255
+ response.headers.set(key, /** @type {string} */ (value));
259
256
  }
257
+ }
260
258
 
261
- // respond with 304 if etag matches
262
- if (response.status === 200 && response.headers.has('etag')) {
263
- let if_none_match_value = request.headers.get('if-none-match');
259
+ for (const cookie of cookies) {
260
+ response.headers.append('set-cookie', cookie);
261
+ }
264
262
 
265
- // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
266
- if (if_none_match_value?.startsWith('W/"')) {
267
- if_none_match_value = if_none_match_value.substring(2);
268
- }
263
+ // respond with 304 if etag matches
264
+ if (response.status === 200 && response.headers.has('etag')) {
265
+ let if_none_match_value = request.headers.get('if-none-match');
269
266
 
270
- const etag = /** @type {string} */ (response.headers.get('etag'));
267
+ // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
268
+ if (if_none_match_value?.startsWith('W/"')) {
269
+ if_none_match_value = if_none_match_value.substring(2);
270
+ }
271
271
 
272
- if (if_none_match_value === etag) {
273
- const headers = new Headers({ etag });
272
+ const etag = /** @type {string} */ (response.headers.get('etag'));
274
273
 
275
- // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
276
- for (const key of ['cache-control', 'content-location', 'date', 'expires', 'vary']) {
277
- const value = response.headers.get(key);
278
- if (value) headers.set(key, value);
279
- }
274
+ if (if_none_match_value === etag) {
275
+ const headers = new Headers({ etag });
280
276
 
281
- return new Response(undefined, {
282
- status: 304,
283
- headers
284
- });
277
+ // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
278
+ for (const key of ['cache-control', 'content-location', 'date', 'expires', 'vary']) {
279
+ const value = response.headers.get(key);
280
+ if (value) headers.set(key, value);
285
281
  }
286
- }
287
282
 
288
- return response;
283
+ return new Response(undefined, {
284
+ status: 304,
285
+ headers
286
+ });
287
+ }
289
288
  }
290
289
 
291
- if (state.initiator === GENERIC_ERROR) {
292
- return new Response('Internal Server Error', {
293
- status: 500
294
- });
295
- }
290
+ return response;
291
+ }
296
292
 
297
- // if this request came direct from the user, rather than
298
- // via a `fetch` in a `load`, render a 404 page
299
- if (!state.initiator) {
300
- return await respond_with_error({
301
- event,
302
- options,
303
- state,
304
- status: 404,
305
- error: new Error(`Not found: ${event.url.pathname}`),
306
- resolve_opts
307
- });
308
- }
293
+ if (state.initiator === GENERIC_ERROR) {
294
+ return new Response('Internal Server Error', {
295
+ status: 500
296
+ });
297
+ }
309
298
 
310
- if (state.prerendering) {
311
- return new Response('not found', { status: 404 });
312
- }
299
+ // if this request came direct from the user, rather than
300
+ // via a `fetch` in a `load`, render a 404 page
301
+ if (!state.initiator) {
302
+ return await respond_with_error({
303
+ event,
304
+ options,
305
+ state,
306
+ status: 404,
307
+ error: new Error(`Not found: ${event.url.pathname}`),
308
+ resolve_opts
309
+ });
310
+ }
313
311
 
314
- // we can't load the endpoint from our own manifest,
315
- // so we need to make an actual HTTP request
316
- return await fetch(request);
317
- },
312
+ if (state.prerendering) {
313
+ return new Response('not found', { status: 404 });
314
+ }
318
315
 
316
+ // we can't load the endpoint from our own manifest,
317
+ // so we need to make an actual HTTP request
318
+ return await fetch(request);
319
+ } catch (e) {
320
+ const error = coalesce_to_error(e);
321
+ return handle_fatal_error(event, options, error);
322
+ }
323
+ }
324
+
325
+ try {
326
+ return await options.hooks.handle({
327
+ event,
328
+ resolve,
319
329
  // TODO remove for 1.0
320
330
  // @ts-expect-error
321
331
  get request() {
322
332
  throw new Error('request in handle has been replaced with event' + details);
323
333
  }
324
334
  });
325
-
326
- // TODO for 1.0, change the error message to point to docs rather than PR
327
- if (response && !(response instanceof Response)) {
328
- throw new Error('handle must return a Response object' + details);
329
- }
330
-
331
- return response;
332
335
  } catch (/** @type {unknown} */ e) {
333
336
  const error = coalesce_to_error(e);
334
-
335
- options.handle_error(error, event);
336
-
337
- const type = negotiate(event.request.headers.get('accept') || 'text/html', [
338
- 'text/html',
339
- 'application/json'
340
- ]);
341
-
342
- if (is_data_request || type === 'application/json') {
343
- return new Response(serialize_error(error, options.get_stack), {
344
- status: 500,
345
- headers: { 'content-type': 'application/json; charset=utf-8' }
346
- });
347
- }
348
-
349
- // TODO is this necessary? should we just return a plain 500 at this point?
350
- try {
351
- return await respond_with_error({
352
- event,
353
- options,
354
- state,
355
- status: 500,
356
- error,
357
- resolve_opts
358
- });
359
- } catch (/** @type {unknown} */ e) {
360
- const error = coalesce_to_error(e);
361
- return static_error_page(options, 500, error.message);
362
- }
337
+ return handle_fatal_error(event, options, error);
363
338
  }
364
339
  }
@@ -334,7 +334,7 @@ export async function render_page(event, route, page, options, state, resolve_op
334
334
  });
335
335
  } catch (error) {
336
336
  // if we end up here, it means the data loaded successfull
337
- // but the page failed to render
337
+ // but the page failed to render, or that a prerendering error occurred
338
338
  options.handle_error(/** @type {Error} */ (error), event);
339
339
 
340
340
  return await respond_with_error({
@@ -1,4 +1,6 @@
1
1
  import { devalue } from 'devalue';
2
+ import { DATA_SUFFIX } from '../../constants.js';
3
+ import { negotiate } from '../../utils/http.js';
2
4
  import { HttpError } from '../control.js';
3
5
 
4
6
  /** @param {any} body */
@@ -175,3 +177,33 @@ export function static_error_page(options, status, message) {
175
177
  status
176
178
  });
177
179
  }
180
+
181
+ /**
182
+ * @param {import('types').RequestEvent} event
183
+ * @param {import('types').SSROptions} options
184
+ * @param {Error} error
185
+ */
186
+ export function handle_fatal_error(event, options, error) {
187
+ let status = 500;
188
+
189
+ if (error instanceof HttpError) {
190
+ status = error.status;
191
+ } else {
192
+ options.handle_error(error, event);
193
+ }
194
+
195
+ // ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
196
+ const type = negotiate(event.request.headers.get('accept') || 'text/html', [
197
+ 'text/html',
198
+ 'application/json'
199
+ ]);
200
+
201
+ if (event.url.pathname.endsWith(DATA_SUFFIX) || type === 'application/json') {
202
+ return new Response(serialize_error(error, options.get_stack), {
203
+ status,
204
+ headers: { 'content-type': 'application/json; charset=utf-8' }
205
+ });
206
+ }
207
+
208
+ return static_error_page(options, status, error.message);
209
+ }