@sveltejs/kit 1.0.12 → 1.0.13

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/core/adapt/builder.js +1 -1
  3. package/src/core/{prerender → postbuild}/crawl.js +0 -0
  4. package/src/core/{prerender → postbuild}/entities.js +0 -0
  5. package/src/core/{prerender → postbuild}/fallback.js +10 -7
  6. package/src/core/postbuild/index.js +104 -0
  7. package/src/core/{prerender → postbuild}/prerender.js +32 -106
  8. package/src/core/{prerender → postbuild}/queue.js +0 -0
  9. package/src/core/sync/sync.js +10 -0
  10. package/src/core/sync/write_client_manifest.js +1 -1
  11. package/src/core/sync/write_server.js +89 -0
  12. package/src/core/utils.js +0 -9
  13. package/src/exports/vite/build/build_server.js +11 -163
  14. package/src/exports/vite/build/build_service_worker.js +3 -2
  15. package/src/exports/vite/build/utils.js +1 -0
  16. package/src/exports/vite/dev/index.js +72 -114
  17. package/src/exports/vite/index.js +26 -27
  18. package/src/exports/vite/preview/index.js +11 -12
  19. package/src/runtime/app/environment.js +1 -1
  20. package/src/runtime/app/paths.js +1 -1
  21. package/src/runtime/client/client.js +1 -1
  22. package/src/runtime/client/start.js +1 -2
  23. package/src/runtime/client/utils.js +1 -2
  24. package/src/runtime/control.js +23 -5
  25. package/src/runtime/server/ambient.d.ts +8 -0
  26. package/src/runtime/server/cookie.js +4 -5
  27. package/src/runtime/server/data/index.js +3 -2
  28. package/src/runtime/server/endpoint.js +5 -5
  29. package/src/runtime/server/fetch.js +11 -9
  30. package/src/runtime/server/index.js +54 -395
  31. package/src/runtime/server/page/csp.js +9 -11
  32. package/src/runtime/server/page/index.js +13 -8
  33. package/src/runtime/server/page/load_data.js +2 -3
  34. package/src/runtime/server/page/render.js +21 -19
  35. package/src/runtime/server/page/respond_with_error.js +20 -13
  36. package/src/runtime/server/page/types.d.ts +0 -1
  37. package/src/runtime/server/respond.js +419 -0
  38. package/src/runtime/server/utils.js +21 -4
  39. package/src/runtime/shared.js +28 -0
  40. package/types/internal.d.ts +23 -37
  41. package/src/runtime/env.js +0 -12
  42. package/src/runtime/paths.js +0 -11
@@ -1,12 +1,14 @@
1
1
  import * as devalue from 'devalue';
2
2
  import { readable, writable } from 'svelte/store';
3
+ import { DEV } from 'esm-env';
3
4
  import { hash } from '../../hash.js';
4
5
  import { serialize_data } from './serialize_data.js';
5
6
  import { s } from '../../../utils/misc.js';
6
7
  import { Csp } from './csp.js';
7
8
  import { uneval_action_response } from './actions.js';
8
9
  import { clarify_devalue_error } from '../utils.js';
9
- import { DEV } from 'esm-env';
10
+ import { assets, base, version } from '../../shared.js';
11
+ import { env } from '../../env-public.js';
10
12
 
11
13
  // TODO rename this function/module
12
14
 
@@ -21,6 +23,7 @@ const updated = {
21
23
  * branch: Array<import('./types').Loaded>;
22
24
  * fetched: Array<import('./types').Fetched>;
23
25
  * options: import('types').SSROptions;
26
+ * manifest: import('types').SSRManifest;
24
27
  * state: import('types').SSRState;
25
28
  * page_config: { ssr: boolean; csr: boolean };
26
29
  * status: number;
@@ -34,6 +37,7 @@ export async function render_response({
34
37
  branch,
35
38
  fetched,
36
39
  options,
40
+ manifest,
37
41
  state,
38
42
  page_config,
39
43
  status,
@@ -52,11 +56,11 @@ export async function render_response({
52
56
  }
53
57
  }
54
58
 
55
- const { entry } = options.manifest._;
59
+ const { entry } = manifest._;
56
60
 
57
61
  const stylesheets = new Set(entry.stylesheets);
58
62
  const modulepreloads = new Set(entry.imports);
59
- const fonts = new Set(options.manifest._.entry.fonts);
63
+ const fonts = new Set(manifest._.entry.fonts);
60
64
 
61
65
  /** @type {Set<string>} */
62
66
  const link_header_preloads = new Set();
@@ -130,7 +134,6 @@ export async function render_response({
130
134
  let body = rendered.html;
131
135
 
132
136
  const csp = new Csp(options.csp, {
133
- dev: options.dev,
134
137
  prerender: !!state.prerendering
135
138
  });
136
139
 
@@ -140,23 +143,23 @@ export async function render_response({
140
143
  * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
141
144
  * @type {string}
142
145
  */
143
- let assets;
146
+ let resolved_assets;
144
147
 
145
- if (options.paths.assets) {
148
+ if (assets) {
146
149
  // if an asset path is specified, use it
147
- assets = options.paths.assets;
150
+ resolved_assets = assets;
148
151
  } else if (state.prerendering?.fallback) {
149
152
  // if we're creating a fallback page, asset paths need to be root-relative
150
- assets = options.paths.base;
153
+ resolved_assets = base;
151
154
  } else {
152
155
  // otherwise we want asset paths to be relative to the page, so that they
153
156
  // will work in odd contexts like IPFS, the internet archive, and so on
154
- const segments = event.url.pathname.slice(options.paths.base.length).split('/').slice(2);
155
- assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
157
+ const segments = event.url.pathname.slice(base.length).split('/').slice(2);
158
+ resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
156
159
  }
157
160
 
158
161
  /** @param {string} path */
159
- const prefixed = (path) => (path.startsWith('/') ? path : `${assets}/${path}`);
162
+ const prefixed = (path) => (path.startsWith('/') ? path : `${resolved_assets}/${path}`);
160
163
 
161
164
  const serialized = { data: '', form: 'null', error: 'null' };
162
165
 
@@ -203,8 +206,7 @@ export async function render_response({
203
206
  if (inline_styles.size > 0) {
204
207
  const content = Array.from(inline_styles.values()).join('\n');
205
208
 
206
- const attributes = [];
207
- if (options.dev) attributes.push(' data-sveltekit');
209
+ const attributes = __SVELTEKIT_DEV__ ? [' data-sveltekit'] : [];
208
210
  if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
209
211
 
210
212
  csp.add_style(content);
@@ -250,10 +252,10 @@ export async function render_response({
250
252
 
251
253
  if (page_config.csr) {
252
254
  const opts = [
253
- `env: ${s(options.public_env)}`,
254
- `paths: ${s(options.paths)}`,
255
+ `env: ${s(env)}`,
256
+ `paths: ${s({ assets, base })}`,
255
257
  `target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
256
- `version: ${s(options.version)}`
258
+ `version: ${s(version)}`
257
259
  ];
258
260
 
259
261
  if (page_config.ssr) {
@@ -315,7 +317,7 @@ export async function render_response({
315
317
  }
316
318
 
317
319
  if (options.service_worker) {
318
- const opts = options.dev ? `, { type: 'module' }` : '';
320
+ const opts = __SVELTEKIT_DEV__ ? `, { type: 'module' }` : '';
319
321
 
320
322
  // we use an anonymous function instead of an arrow function to support
321
323
  // older browsers (https://github.com/sveltejs/kit/pull/5417)
@@ -355,10 +357,10 @@ export async function render_response({
355
357
  // add the content after the script/css links so the link elements are parsed first
356
358
  head += rendered.head;
357
359
 
358
- const html = options.app_template({
360
+ const html = options.templates.app({
359
361
  head,
360
362
  body,
361
- assets,
363
+ assets: resolved_assets,
362
364
  nonce: /** @type {string} */ (csp.nonce)
363
365
  });
364
366
 
@@ -11,27 +11,34 @@ import { HttpError, Redirect } from '../../control.js';
11
11
 
12
12
  /**
13
13
  * @typedef {import('./types.js').Loaded} Loaded
14
- * @typedef {import('types').SSROptions} SSROptions
15
- * @typedef {import('types').SSRState} SSRState
16
14
  */
17
15
 
18
16
  /**
19
17
  * @param {{
20
18
  * event: import('types').RequestEvent;
21
- * options: SSROptions;
22
- * state: SSRState;
19
+ * options: import('types').SSROptions;
20
+ * manifest: import('types').SSRManifest;
21
+ * state: import('types').SSRState;
23
22
  * status: number;
24
23
  * error: unknown;
25
24
  * resolve_opts: import('types').RequiredResolveOptions;
26
25
  * }} opts
27
26
  */
28
- export async function respond_with_error({ event, options, state, status, error, resolve_opts }) {
27
+ export async function respond_with_error({
28
+ event,
29
+ options,
30
+ manifest,
31
+ state,
32
+ status,
33
+ error,
34
+ resolve_opts
35
+ }) {
29
36
  /** @type {import('./types').Fetched[]} */
30
37
  const fetched = [];
31
38
 
32
39
  try {
33
40
  const branch = [];
34
- const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
41
+ const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
35
42
  const ssr = get_option([default_layout], 'ssr') ?? true;
36
43
  const csr = get_option([default_layout], 'csr') ?? true;
37
44
 
@@ -40,7 +47,6 @@ export async function respond_with_error({ event, options, state, status, error,
40
47
 
41
48
  const server_data_promise = load_server_data({
42
49
  event,
43
- options,
44
50
  state,
45
51
  node: default_layout,
46
52
  parent: async () => ({})
@@ -66,7 +72,7 @@ export async function respond_with_error({ event, options, state, status, error,
66
72
  data
67
73
  },
68
74
  {
69
- node: await options.manifest._.nodes[1](), // 1 is always the root error
75
+ node: await manifest._.nodes[1](), // 1 is always the root error
70
76
  data: null,
71
77
  server_data: null
72
78
  }
@@ -75,6 +81,7 @@ export async function respond_with_error({ event, options, state, status, error,
75
81
 
76
82
  return await render_response({
77
83
  options,
84
+ manifest,
78
85
  state,
79
86
  page_config: {
80
87
  ssr,
@@ -87,17 +94,17 @@ export async function respond_with_error({ event, options, state, status, error,
87
94
  event,
88
95
  resolve_opts
89
96
  });
90
- } catch (error) {
97
+ } catch (e) {
91
98
  // Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
92
99
  // we end up here.
93
- if (error instanceof Redirect) {
94
- return redirect_response(error.status, error.location);
100
+ if (e instanceof Redirect) {
101
+ return redirect_response(e.status, e.location);
95
102
  }
96
103
 
97
104
  return static_error_page(
98
105
  options,
99
- error instanceof HttpError ? error.status : 500,
100
- (await handle_error_and_jsonify(event, options, error)).message
106
+ e instanceof HttpError ? e.status : 500,
107
+ (await handle_error_and_jsonify(event, options, e)).message
101
108
  );
102
109
  }
103
110
  }
@@ -24,7 +24,6 @@ export interface CspConfig {
24
24
  }
25
25
 
26
26
  export interface CspOpts {
27
- dev: boolean;
28
27
  prerender: boolean;
29
28
  }
30
29
 
@@ -0,0 +1,419 @@
1
+ import { DEV } from 'esm-env';
2
+ import { is_endpoint_request, render_endpoint } from './endpoint.js';
3
+ import { render_page } from './page/index.js';
4
+ import { render_response } from './page/render.js';
5
+ import { respond_with_error } from './page/respond_with_error.js';
6
+ import { is_form_content_type } from '../../utils/http.js';
7
+ import { GENERIC_ERROR, get_option, handle_fatal_error, redirect_response } from './utils.js';
8
+ import {
9
+ decode_pathname,
10
+ decode_params,
11
+ disable_search,
12
+ has_data_suffix,
13
+ normalize_path,
14
+ strip_data_suffix
15
+ } from '../../utils/url.js';
16
+ import { exec } from '../../utils/routing.js';
17
+ import { INVALIDATED_PARAM, redirect_json_response, render_data } from './data/index.js';
18
+ import { add_cookies_to_headers, get_cookies } from './cookie.js';
19
+ import { create_fetch } from './fetch.js';
20
+ import { Redirect } from '../control.js';
21
+ import {
22
+ validate_common_exports,
23
+ validate_page_server_exports,
24
+ validate_server_exports
25
+ } from '../../utils/exports.js';
26
+ import { error, json } from '../../exports/index.js';
27
+ import * as paths from '../shared.js';
28
+
29
+ /* global __SVELTEKIT_ADAPTER_NAME__ */
30
+
31
+ /** @type {import('types').RequiredResolveOptions['transformPageChunk']} */
32
+ const default_transform = ({ html }) => html;
33
+
34
+ /** @type {import('types').RequiredResolveOptions['filterSerializedResponseHeaders']} */
35
+ const default_filter = () => false;
36
+
37
+ /** @type {import('types').RequiredResolveOptions['preload']} */
38
+ const default_preload = ({ type }) => type === 'js' || type === 'css';
39
+
40
+ /** @type {import('types').Respond} */
41
+ export async function respond(request, options, manifest, state) {
42
+ /** URL but stripped from the potential `/__data.json` suffix and its search param */
43
+ let url = new URL(request.url);
44
+
45
+ if (options.csrf_check_origin) {
46
+ const forbidden =
47
+ request.method === 'POST' &&
48
+ request.headers.get('origin') !== url.origin &&
49
+ is_form_content_type(request);
50
+
51
+ if (forbidden) {
52
+ const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
53
+ if (request.headers.get('accept') === 'application/json') {
54
+ return json(csrf_error.body, { status: csrf_error.status });
55
+ }
56
+ return new Response(csrf_error.body.message, { status: csrf_error.status });
57
+ }
58
+ }
59
+
60
+ let decoded;
61
+ try {
62
+ decoded = decode_pathname(url.pathname);
63
+ } catch {
64
+ return new Response('Malformed URI', { status: 400 });
65
+ }
66
+
67
+ /** @type {import('types').SSRRoute | null} */
68
+ let route = null;
69
+
70
+ /** @type {Record<string, string>} */
71
+ let params = {};
72
+
73
+ if (paths.base && !state.prerendering?.fallback) {
74
+ if (!decoded.startsWith(paths.base)) {
75
+ return new Response('Not found', { status: 404 });
76
+ }
77
+ decoded = decoded.slice(paths.base.length) || '/';
78
+ }
79
+
80
+ const is_data_request = has_data_suffix(decoded);
81
+ /** @type {boolean[] | undefined} */
82
+ let invalidated_data_nodes;
83
+ if (is_data_request) {
84
+ decoded = strip_data_suffix(decoded) || '/';
85
+ url.pathname = strip_data_suffix(url.pathname) || '/';
86
+ invalidated_data_nodes = url.searchParams.get(INVALIDATED_PARAM)?.split('_').map(Boolean);
87
+ url.searchParams.delete(INVALIDATED_PARAM);
88
+ }
89
+
90
+ if (!state.prerendering?.fallback) {
91
+ // TODO this could theoretically break — should probably be inside a try-catch
92
+ const matchers = await manifest._.matchers();
93
+
94
+ for (const candidate of manifest._.routes) {
95
+ const match = candidate.pattern.exec(decoded);
96
+ if (!match) continue;
97
+
98
+ const matched = exec(match, candidate.params, matchers);
99
+ if (matched) {
100
+ route = candidate;
101
+ params = decode_params(matched);
102
+ break;
103
+ }
104
+ }
105
+ }
106
+
107
+ /** @type {import('types').TrailingSlash | void} */
108
+ let trailing_slash = undefined;
109
+
110
+ /** @type {Record<string, string>} */
111
+ const headers = {};
112
+
113
+ /** @type {import('types').RequestEvent} */
114
+ const event = {
115
+ // @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself
116
+ cookies: null,
117
+ // @ts-expect-error
118
+ fetch: null,
119
+ getClientAddress:
120
+ state.getClientAddress ||
121
+ (() => {
122
+ throw new Error(
123
+ `${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue`
124
+ );
125
+ }),
126
+ locals: {},
127
+ params,
128
+ platform: state.platform,
129
+ request,
130
+ route: { id: route?.id ?? null },
131
+ setHeaders: (new_headers) => {
132
+ for (const key in new_headers) {
133
+ const lower = key.toLowerCase();
134
+ const value = new_headers[key];
135
+
136
+ if (lower === 'set-cookie') {
137
+ throw new Error(
138
+ `Use \`event.cookies.set(name, value, options)\` instead of \`event.setHeaders\` to set cookies`
139
+ );
140
+ } else if (lower in headers) {
141
+ throw new Error(`"${key}" header is already set`);
142
+ } else {
143
+ headers[lower] = value;
144
+
145
+ if (state.prerendering && lower === 'cache-control') {
146
+ state.prerendering.cache = /** @type {string} */ (value);
147
+ }
148
+ }
149
+ }
150
+ },
151
+ url,
152
+ isDataRequest: is_data_request
153
+ };
154
+
155
+ /** @type {import('types').RequiredResolveOptions} */
156
+ let resolve_opts = {
157
+ transformPageChunk: default_transform,
158
+ filterSerializedResponseHeaders: default_filter,
159
+ preload: default_preload
160
+ };
161
+
162
+ try {
163
+ // determine whether we need to redirect to add/remove a trailing slash
164
+ if (route && !is_data_request) {
165
+ if (route.page) {
166
+ const nodes = await Promise.all([
167
+ // we use == here rather than === because [undefined] serializes as "[null]"
168
+ ...route.page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
169
+ manifest._.nodes[route.page.leaf]()
170
+ ]);
171
+
172
+ if (DEV) {
173
+ const layouts = nodes.slice(0, -1);
174
+ const page = nodes.at(-1);
175
+
176
+ for (const layout of layouts) {
177
+ if (layout) {
178
+ validate_common_exports(layout.server, /** @type {string} */ (layout.server_id));
179
+ validate_common_exports(
180
+ layout.universal,
181
+ /** @type {string} */ (layout.universal_id)
182
+ );
183
+ }
184
+ }
185
+
186
+ if (page) {
187
+ validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
188
+ validate_common_exports(page.universal, /** @type {string} */ (page.universal_id));
189
+ }
190
+ }
191
+
192
+ trailing_slash = get_option(nodes, 'trailingSlash');
193
+ } else if (route.endpoint) {
194
+ const node = await route.endpoint();
195
+ trailing_slash = node.trailingSlash;
196
+
197
+ if (DEV) {
198
+ validate_server_exports(node, /** @type {string} */ (route.endpoint_id));
199
+ }
200
+ }
201
+
202
+ const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
203
+
204
+ if (normalized !== url.pathname && !state.prerendering?.fallback) {
205
+ return new Response(undefined, {
206
+ status: 301,
207
+ headers: {
208
+ 'x-sveltekit-normalize': '1',
209
+ location:
210
+ // ensure paths starting with '//' are not treated as protocol-relative
211
+ (normalized.startsWith('//') ? url.origin + normalized : normalized) +
212
+ (url.search === '?' ? '' : url.search)
213
+ }
214
+ });
215
+ }
216
+ }
217
+
218
+ const { cookies, new_cookies, get_cookie_header } = get_cookies(
219
+ request,
220
+ url,
221
+ trailing_slash ?? 'never'
222
+ );
223
+
224
+ event.cookies = cookies;
225
+ event.fetch = create_fetch({ event, options, manifest, state, get_cookie_header });
226
+
227
+ if (state.prerendering && !state.prerendering.fallback) disable_search(url);
228
+
229
+ const response = await options.hooks.handle({
230
+ event,
231
+ resolve: (event, opts) =>
232
+ resolve(event, opts).then((response) => {
233
+ // add headers/cookies here, rather than inside `resolve`, so that we
234
+ // can do it once for all responses instead of once per `return`
235
+ for (const key in headers) {
236
+ const value = headers[key];
237
+ response.headers.set(key, /** @type {string} */ (value));
238
+ }
239
+
240
+ add_cookies_to_headers(response.headers, Object.values(new_cookies));
241
+
242
+ if (state.prerendering && event.route.id !== null) {
243
+ response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id));
244
+ }
245
+
246
+ return response;
247
+ })
248
+ });
249
+
250
+ // respond with 304 if etag matches
251
+ if (response.status === 200 && response.headers.has('etag')) {
252
+ let if_none_match_value = request.headers.get('if-none-match');
253
+
254
+ // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
255
+ if (if_none_match_value?.startsWith('W/"')) {
256
+ if_none_match_value = if_none_match_value.substring(2);
257
+ }
258
+
259
+ const etag = /** @type {string} */ (response.headers.get('etag'));
260
+
261
+ if (if_none_match_value === etag) {
262
+ const headers = new Headers({ etag });
263
+
264
+ // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
265
+ for (const key of [
266
+ 'cache-control',
267
+ 'content-location',
268
+ 'date',
269
+ 'expires',
270
+ 'vary',
271
+ 'set-cookie'
272
+ ]) {
273
+ const value = response.headers.get(key);
274
+ if (value) headers.set(key, value);
275
+ }
276
+
277
+ return new Response(undefined, {
278
+ status: 304,
279
+ headers
280
+ });
281
+ }
282
+ }
283
+
284
+ // Edge case: If user does `return Response(30x)` in handle hook while processing a data request,
285
+ // we need to transform the redirect response to a corresponding JSON response.
286
+ if (is_data_request && response.status >= 300 && response.status <= 308) {
287
+ const location = response.headers.get('location');
288
+ if (location) {
289
+ return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location));
290
+ }
291
+ }
292
+
293
+ return response;
294
+ } catch (e) {
295
+ if (e instanceof Redirect) {
296
+ if (is_data_request) {
297
+ return redirect_json_response(e);
298
+ } else {
299
+ return redirect_response(e.status, e.location);
300
+ }
301
+ }
302
+ return await handle_fatal_error(event, options, e);
303
+ }
304
+
305
+ /**
306
+ *
307
+ * @param {import('types').RequestEvent} event
308
+ * @param {import('types').ResolveOptions} [opts]
309
+ */
310
+ async function resolve(event, opts) {
311
+ try {
312
+ if (opts) {
313
+ if ('ssr' in opts) {
314
+ throw new Error(
315
+ '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'
316
+ );
317
+ }
318
+
319
+ resolve_opts = {
320
+ transformPageChunk: opts.transformPageChunk || default_transform,
321
+ filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
322
+ preload: opts.preload || default_preload
323
+ };
324
+ }
325
+
326
+ if (state.prerendering?.fallback) {
327
+ return await render_response({
328
+ event,
329
+ options,
330
+ manifest,
331
+ state,
332
+ page_config: { ssr: false, csr: true },
333
+ status: 200,
334
+ error: null,
335
+ branch: [],
336
+ fetched: [],
337
+ resolve_opts
338
+ });
339
+ }
340
+
341
+ if (route) {
342
+ /** @type {Response} */
343
+ let response;
344
+
345
+ if (is_data_request) {
346
+ response = await render_data(
347
+ event,
348
+ route,
349
+ options,
350
+ manifest,
351
+ state,
352
+ invalidated_data_nodes,
353
+ trailing_slash ?? 'never'
354
+ );
355
+ } else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
356
+ response = await render_endpoint(event, await route.endpoint(), state);
357
+ } else if (route.page) {
358
+ response = await render_page(
359
+ event,
360
+ route,
361
+ route.page,
362
+ options,
363
+ manifest,
364
+ state,
365
+ resolve_opts
366
+ );
367
+ } else {
368
+ // a route will always have a page or an endpoint, but TypeScript
369
+ // doesn't know that
370
+ throw new Error('This should never happen');
371
+ }
372
+
373
+ return response;
374
+ }
375
+
376
+ if (state.initiator === GENERIC_ERROR) {
377
+ return new Response('Internal Server Error', {
378
+ status: 500
379
+ });
380
+ }
381
+
382
+ // if this request came direct from the user, rather than
383
+ // via a `fetch` in a `load`, render a 404 page
384
+ if (!state.initiator) {
385
+ return await respond_with_error({
386
+ event,
387
+ options,
388
+ manifest,
389
+ state,
390
+ status: 404,
391
+ error: new Error(`Not found: ${event.url.pathname}`),
392
+ resolve_opts
393
+ });
394
+ }
395
+
396
+ if (state.prerendering) {
397
+ return new Response('not found', { status: 404 });
398
+ }
399
+
400
+ // we can't load the endpoint from our own manifest,
401
+ // so we need to make an actual HTTP request
402
+ return await fetch(request);
403
+ } catch (e) {
404
+ // TODO if `e` is instead named `error`, some fucked up Vite transformation happens
405
+ // and I don't even know how to describe it. need to investigate at some point
406
+
407
+ // HttpError from endpoint can end up here - TODO should it be handled there instead?
408
+ return await handle_fatal_error(event, options, e);
409
+ } finally {
410
+ event.cookies.set = () => {
411
+ throw new Error('Cannot use `cookies.set(...)` after the response has been generated');
412
+ };
413
+
414
+ event.setHeaders = () => {
415
+ throw new Error('Cannot use `setHeaders(...)` after the response has been generated');
416
+ };
417
+ }
418
+ }
419
+ }
@@ -3,6 +3,7 @@ import { coalesce_to_error } from '../../utils/error.js';
3
3
  import { negotiate } from '../../utils/http.js';
4
4
  import { has_data_suffix } from '../../utils/url.js';
5
5
  import { HttpError } from '../control.js';
6
+ import { fix_stack_trace } from '../shared.js';
6
7
 
7
8
  /** @param {any} body */
8
9
  export function is_pojo(body) {
@@ -74,7 +75,7 @@ export function get_option(nodes, option) {
74
75
  * @param {string} message
75
76
  */
76
77
  export function static_error_page(options, status, message) {
77
- return new Response(options.error_template({ status, message }), {
78
+ return new Response(options.templates.error({ status, message }), {
78
79
  headers: { 'content-type': 'text/html; charset=utf-8' },
79
80
  status
80
81
  });
@@ -110,13 +111,29 @@ export async function handle_fatal_error(event, options, error) {
110
111
  * @param {import('types').RequestEvent} event
111
112
  * @param {import('types').SSROptions} options
112
113
  * @param {any} error
113
- * @returns {import('types').MaybePromise<App.Error>}
114
+ * @returns {Promise<App.Error>}
114
115
  */
115
- export function handle_error_and_jsonify(event, options, error) {
116
+ export async function handle_error_and_jsonify(event, options, error) {
116
117
  if (error instanceof HttpError) {
117
118
  return error.body;
118
119
  } else {
119
- return options.handle_error(error, event);
120
+ if (__SVELTEKIT_DEV__) {
121
+ error = new Proxy(error, {
122
+ get: (target, property) => {
123
+ if (property === 'stack') {
124
+ return fix_stack_trace(target.stack);
125
+ }
126
+
127
+ return Reflect.get(target, property, target);
128
+ }
129
+ });
130
+ }
131
+
132
+ return (
133
+ (await options.hooks.handleError({ error, event })) ?? {
134
+ message: event.route.id != null ? 'Internal Error' : 'Not Found'
135
+ }
136
+ );
120
137
  }
121
138
  }
122
139