@sveltejs/kit 1.30.3 → 2.0.0

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 (57) hide show
  1. package/package.json +24 -24
  2. package/src/core/adapt/builder.js +8 -1
  3. package/src/core/config/index.js +9 -1
  4. package/src/core/config/options.js +1 -12
  5. package/src/core/postbuild/analyse.js +98 -80
  6. package/src/core/postbuild/prerender.js +11 -9
  7. package/src/core/sync/sync.js +2 -0
  8. package/src/core/sync/write_non_ambient.js +42 -0
  9. package/src/core/sync/write_server.js +3 -3
  10. package/src/core/sync/write_tsconfig.js +27 -78
  11. package/src/core/sync/write_types/index.js +1 -1
  12. package/src/exports/hooks/sequence.js +1 -1
  13. package/src/exports/index.js +88 -71
  14. package/src/exports/node/index.js +21 -24
  15. package/src/exports/node/polyfills.js +5 -34
  16. package/src/exports/public.d.ts +82 -61
  17. package/src/exports/vite/dev/index.js +11 -19
  18. package/src/exports/vite/graph_analysis/index.js +2 -4
  19. package/src/exports/vite/index.js +73 -14
  20. package/src/exports/vite/module_ids.js +7 -0
  21. package/src/exports/vite/preview/index.js +56 -130
  22. package/src/runtime/app/forms.js +2 -35
  23. package/src/runtime/app/navigation.js +28 -15
  24. package/src/runtime/app/paths.js +2 -29
  25. package/src/runtime/client/client.js +449 -199
  26. package/src/runtime/client/constants.js +5 -1
  27. package/src/runtime/client/session-storage.js +7 -5
  28. package/src/runtime/client/singletons.js +7 -1
  29. package/src/runtime/client/types.d.ts +6 -2
  30. package/src/runtime/client/utils.js +12 -10
  31. package/src/runtime/control.js +16 -8
  32. package/src/runtime/server/cookie.js +38 -61
  33. package/src/runtime/server/data/index.js +6 -4
  34. package/src/runtime/server/env_module.js +29 -0
  35. package/src/runtime/server/fetch.js +7 -6
  36. package/src/runtime/server/index.js +23 -20
  37. package/src/runtime/server/page/actions.js +24 -15
  38. package/src/runtime/server/page/index.js +6 -8
  39. package/src/runtime/server/page/load_data.js +58 -40
  40. package/src/runtime/server/page/render.js +12 -7
  41. package/src/runtime/server/page/respond_with_error.js +4 -4
  42. package/src/runtime/server/page/types.d.ts +1 -1
  43. package/src/runtime/server/respond.js +14 -12
  44. package/src/runtime/server/utils.js +11 -8
  45. package/src/runtime/shared-server.js +19 -2
  46. package/src/types/ambient.d.ts +7 -1
  47. package/src/types/internal.d.ts +4 -1
  48. package/src/types/synthetic/$env+dynamic+private.md +2 -0
  49. package/src/types/synthetic/$env+dynamic+public.md +2 -0
  50. package/src/utils/error.js +17 -1
  51. package/src/utils/routing.js +47 -1
  52. package/src/utils/url.js +45 -27
  53. package/src/version.js +1 -1
  54. package/types/index.d.ts +166 -115
  55. package/types/index.d.ts.map +9 -5
  56. package/src/utils/platform.js +0 -1
  57. package/src/utils/promises.js +0 -61
@@ -1,6 +1,5 @@
1
- import { disable_search, make_trackable } from '../../../utils/url.js';
2
- import { unwrap_promises } from '../../../utils/promises.js';
3
1
  import { DEV } from 'esm-env';
2
+ import { disable_search, make_trackable } from '../../../utils/url.js';
4
3
  import { validate_depends } from '../../shared.js';
5
4
 
6
5
  /**
@@ -10,39 +9,49 @@ import { validate_depends } from '../../shared.js';
10
9
  * state: import('types').SSRState;
11
10
  * node: import('types').SSRNode | undefined;
12
11
  * parent: () => Promise<Record<string, any>>;
13
- * track_server_fetches: boolean;
14
12
  * }} opts
15
13
  * @returns {Promise<import('types').ServerDataNode | null>}
16
14
  */
17
- export async function load_server_data({
18
- event,
19
- state,
20
- node,
21
- parent,
22
- // TODO 2.0: Remove this
23
- track_server_fetches
24
- }) {
15
+ export async function load_server_data({ event, state, node, parent }) {
25
16
  if (!node?.server) return null;
26
17
 
27
18
  let done = false;
19
+ let is_tracking = true;
28
20
 
29
21
  const uses = {
30
22
  dependencies: new Set(),
31
23
  params: new Set(),
32
24
  parent: false,
33
25
  route: false,
34
- url: false
26
+ url: false,
27
+ search_params: new Set()
35
28
  };
36
29
 
37
- const url = make_trackable(event.url, () => {
38
- if (DEV && done && !uses.url) {
39
- console.warn(
40
- `${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
41
- );
42
- }
30
+ const url = make_trackable(
31
+ event.url,
32
+ () => {
33
+ if (DEV && done && !uses.url) {
34
+ console.warn(
35
+ `${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
36
+ );
37
+ }
43
38
 
44
- uses.url = true;
45
- });
39
+ if (is_tracking) {
40
+ uses.url = true;
41
+ }
42
+ },
43
+ (param) => {
44
+ if (DEV && done && !uses.search_params.has(param)) {
45
+ console.warn(
46
+ `${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
47
+ );
48
+ }
49
+
50
+ if (is_tracking) {
51
+ uses.search_params.add(param);
52
+ }
53
+ }
54
+ );
46
55
 
47
56
  if (state.prerendering) {
48
57
  disable_search(url);
@@ -59,11 +68,7 @@ export async function load_server_data({
59
68
  );
60
69
  }
61
70
 
62
- // TODO 2.0: Remove this
63
- if (track_server_fetches) {
64
- uses.dependencies.add(url.href);
65
- }
66
-
71
+ // Note: server fetches are not added to uses.depends due to security concerns
67
72
  return event.fetch(info, init);
68
73
  },
69
74
  /** @param {string[]} deps */
@@ -94,7 +99,9 @@ export async function load_server_data({
94
99
  );
95
100
  }
96
101
 
97
- uses.params.add(key);
102
+ if (is_tracking) {
103
+ uses.params.add(key);
104
+ }
98
105
  return target[/** @type {string} */ (key)];
99
106
  }
100
107
  }),
@@ -105,7 +112,9 @@ export async function load_server_data({
105
112
  );
106
113
  }
107
114
 
108
- uses.parent = true;
115
+ if (is_tracking) {
116
+ uses.parent = true;
117
+ }
109
118
  return parent();
110
119
  },
111
120
  route: new Proxy(event.route, {
@@ -118,23 +127,32 @@ export async function load_server_data({
118
127
  );
119
128
  }
120
129
 
121
- uses.route = true;
130
+ if (is_tracking) {
131
+ uses.route = true;
132
+ }
122
133
  return target[/** @type {'id'} */ (key)];
123
134
  }
124
135
  }),
125
- url
136
+ url,
137
+ untrack(fn) {
138
+ is_tracking = false;
139
+ try {
140
+ return fn();
141
+ } finally {
142
+ is_tracking = true;
143
+ }
144
+ }
126
145
  });
127
146
 
128
- const data = result ? await unwrap_promises(result, node.server_id) : null;
129
147
  if (__SVELTEKIT_DEV__) {
130
- validate_load_response(data, node.server_id);
148
+ validate_load_response(result, node.server_id);
131
149
  }
132
150
 
133
151
  done = true;
134
152
 
135
153
  return {
136
154
  type: 'data',
137
- data,
155
+ data: result ?? null,
138
156
  uses,
139
157
  slash: node.server.trailingSlash
140
158
  };
@@ -178,15 +196,15 @@ export async function load_data({
178
196
  fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
179
197
  setHeaders: event.setHeaders,
180
198
  depends: () => {},
181
- parent
199
+ parent,
200
+ untrack: (fn) => fn()
182
201
  });
183
202
 
184
- const data = result ? await unwrap_promises(result, node.universal_id) : null;
185
203
  if (__SVELTEKIT_DEV__) {
186
- validate_load_response(data, node.universal_id);
204
+ validate_load_response(result, node.universal_id);
187
205
  }
188
206
 
189
- return data;
207
+ return result ?? null;
190
208
  }
191
209
 
192
210
  /**
@@ -398,10 +416,10 @@ function validate_load_response(data, id) {
398
416
  typeof data !== 'object'
399
417
  ? `a ${typeof data}`
400
418
  : data instanceof Response
401
- ? 'a Response object'
402
- : Array.isArray(data)
403
- ? 'an array'
404
- : 'a non-plain object'
419
+ ? 'a Response object'
420
+ : Array.isArray(data)
421
+ ? 'an array'
422
+ : 'a non-plain object'
405
423
  }, but must return a plain object at the top level (i.e. \`return {...}\`)`
406
424
  );
407
425
  }
@@ -8,7 +8,7 @@ import { s } from '../../../utils/misc.js';
8
8
  import { Csp } from './csp.js';
9
9
  import { uneval_action_response } from './actions.js';
10
10
  import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js';
11
- import { public_env } from '../../shared-server.js';
11
+ import { public_env, safe_public_env } from '../../shared-server.js';
12
12
  import { text } from '../../../exports/index.js';
13
13
  import { create_async_iterator } from '../../../utils/streaming.js';
14
14
  import { SVELTE_KIT_ASSETS } from '../../../constants.js';
@@ -95,7 +95,7 @@ export async function render_response({
95
95
  let base_expression = s(paths.base);
96
96
 
97
97
  // if appropriate, use relative paths for greater portability
98
- if (paths.relative !== false && !state.prerendering?.fallback) {
98
+ if (paths.relative && !state.prerendering?.fallback) {
99
99
  const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2);
100
100
 
101
101
  base = segments.map(() => '..').join('/') || '.';
@@ -141,7 +141,8 @@ export async function render_response({
141
141
  status,
142
142
  url: event.url,
143
143
  data,
144
- form: form_value
144
+ form: form_value,
145
+ state: {}
145
146
  };
146
147
 
147
148
  // use relative paths during rendering, so that the resulting HTML is as
@@ -276,6 +277,10 @@ export async function render_response({
276
277
  }
277
278
 
278
279
  if (page_config.csr) {
280
+ if (client.uses_env_dynamic_public && state.prerendering) {
281
+ modulepreloads.add(`${options.app_dir}/env.js`);
282
+ }
283
+
279
284
  const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter(
280
285
  (path) => resolve_opts.preload({ type: 'js', path })
281
286
  );
@@ -295,7 +300,7 @@ export async function render_response({
295
300
  const properties = [
296
301
  paths.assets && `assets: ${s(paths.assets)}`,
297
302
  `base: ${base_expression}`,
298
- `env: ${s(public_env)}`
303
+ `env: ${!client.uses_env_dynamic_public || state.prerendering ? null : s(public_env)}`
299
304
  ].filter(Boolean);
300
305
 
301
306
  if (chunks) {
@@ -431,7 +436,7 @@ export async function render_response({
431
436
  body,
432
437
  assets,
433
438
  nonce: /** @type {string} */ (csp.nonce),
434
- env: public_env
439
+ env: safe_public_env
435
440
  });
436
441
 
437
442
  // TODO flush chunks as early as we can
@@ -467,7 +472,7 @@ export async function render_response({
467
472
  ? text(transformed, {
468
473
  status,
469
474
  headers
470
- })
475
+ })
471
476
  : new Response(
472
477
  new ReadableStream({
473
478
  async start(controller) {
@@ -485,7 +490,7 @@ export async function render_response({
485
490
  'content-type': 'text/html'
486
491
  }
487
492
  }
488
- );
493
+ );
489
494
  }
490
495
 
491
496
  /**
@@ -2,7 +2,8 @@ import { render_response } from './render.js';
2
2
  import { load_data, load_server_data } from './load_data.js';
3
3
  import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
4
4
  import { get_option } from '../../../utils/options.js';
5
- import { HttpError, Redirect } from '../../control.js';
5
+ import { Redirect } from '../../control.js';
6
+ import { get_status } from '../../../utils/error.js';
6
7
 
7
8
  /**
8
9
  * @typedef {import('./types.js').Loaded} Loaded
@@ -49,8 +50,7 @@ export async function respond_with_error({
49
50
  event,
50
51
  state,
51
52
  node: default_layout,
52
- parent: async () => ({}),
53
- track_server_fetches: options.track_server_fetches
53
+ parent: async () => ({})
54
54
  });
55
55
 
56
56
  const server_data = await server_data_promise;
@@ -104,7 +104,7 @@ export async function respond_with_error({
104
104
 
105
105
  return static_error_page(
106
106
  options,
107
- e instanceof HttpError ? e.status : 500,
107
+ get_status(e),
108
108
  (await handle_error_and_jsonify(event, options, e)).message
109
109
  );
110
110
  }
@@ -32,5 +32,5 @@ export interface CspOpts {
32
32
  export interface Cookie {
33
33
  name: string;
34
34
  value: string;
35
- options: CookieSerializeOptions;
35
+ options: CookieSerializeOptions & { path: string };
36
36
  }
@@ -18,7 +18,7 @@ import { exec } from '../../utils/routing.js';
18
18
  import { redirect_json_response, render_data } from './data/index.js';
19
19
  import { add_cookies_to_headers, get_cookies } from './cookie.js';
20
20
  import { create_fetch } from './fetch.js';
21
- import { Redirect, NotFound } from '../control.js';
21
+ import { HttpError, Redirect, SvelteKitError } from '../control.js';
22
22
  import {
23
23
  validate_layout_exports,
24
24
  validate_layout_server_exports,
@@ -27,9 +27,10 @@ import {
27
27
  validate_server_exports
28
28
  } from '../../utils/exports.js';
29
29
  import { get_option } from '../../utils/options.js';
30
- import { error, json, text } from '../../exports/index.js';
30
+ import { json, text } from '../../exports/index.js';
31
31
  import { action_json_redirect, is_action_json_request } from './page/actions.js';
32
32
  import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js';
33
+ import { get_public_env } from './env_module.js';
33
34
 
34
35
  /* global __SVELTEKIT_ADAPTER_NAME__ */
35
36
 
@@ -67,7 +68,10 @@ export async function respond(request, options, manifest, state) {
67
68
  request.headers.get('origin') !== url.origin;
68
69
 
69
70
  if (forbidden) {
70
- const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
71
+ const csrf_error = new HttpError(
72
+ 403,
73
+ `Cross-site ${request.method} form submissions are forbidden`
74
+ );
71
75
  if (request.headers.get('accept') === 'application/json') {
72
76
  return json(csrf_error.body, { status: csrf_error.status });
73
77
  }
@@ -95,6 +99,10 @@ export async function respond(request, options, manifest, state) {
95
99
  decoded = decoded.slice(base.length) || '/';
96
100
  }
97
101
 
102
+ if (decoded === `/${options.app_dir}/env.js`) {
103
+ return get_public_env(request);
104
+ }
105
+
98
106
  const is_data_request = has_data_suffix(decoded);
99
107
  /** @type {boolean[] | undefined} */
100
108
  let invalidated_data_nodes;
@@ -341,8 +349,8 @@ export async function respond(request, options, manifest, state) {
341
349
  const response = is_data_request
342
350
  ? redirect_json_response(e)
343
351
  : route?.page && is_action_json_request(event)
344
- ? action_json_redirect(e)
345
- : redirect_response(e.status, e.location);
352
+ ? action_json_redirect(e)
353
+ : redirect_response(e.status, e.location);
346
354
  add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
347
355
  return response;
348
356
  }
@@ -356,12 +364,6 @@ export async function respond(request, options, manifest, state) {
356
364
  async function resolve(event, opts) {
357
365
  try {
358
366
  if (opts) {
359
- if ('ssr' in opts) {
360
- throw new Error(
361
- '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'
362
- );
363
- }
364
-
365
367
  resolve_opts = {
366
368
  transformPageChunk: opts.transformPageChunk || default_transform,
367
369
  filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
@@ -480,7 +482,7 @@ export async function respond(request, options, manifest, state) {
480
482
  manifest,
481
483
  state,
482
484
  status: 404,
483
- error: new NotFound(event.url.pathname),
485
+ error: new SvelteKitError(404, 'Not Found', `Not found: ${event.url.pathname}`),
484
486
  resolve_opts
485
487
  });
486
488
  }
@@ -1,8 +1,8 @@
1
1
  import { DEV } from 'esm-env';
2
2
  import { json, text } from '../../exports/index.js';
3
- import { coalesce_to_error } from '../../utils/error.js';
3
+ import { coalesce_to_error, get_message, get_status } from '../../utils/error.js';
4
4
  import { negotiate } from '../../utils/http.js';
5
- import { HttpError, NotFound } from '../control.js';
5
+ import { HttpError } from '../control.js';
6
6
  import { fix_stack_trace } from '../shared-server.js';
7
7
  import { ENDPOINT_METHODS } from '../../constants.js';
8
8
 
@@ -70,7 +70,7 @@ export function static_error_page(options, status, message) {
70
70
  */
71
71
  export async function handle_fatal_error(event, options, error) {
72
72
  error = error instanceof HttpError ? error : coalesce_to_error(error);
73
- const status = error instanceof HttpError ? error.status : 500;
73
+ const status = get_status(error);
74
74
  const body = await handle_error_and_jsonify(event, options, error);
75
75
 
76
76
  // ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
@@ -103,11 +103,10 @@ export async function handle_error_and_jsonify(event, options, error) {
103
103
  fix_stack_trace(error);
104
104
  }
105
105
 
106
- return (
107
- (await options.hooks.handleError({ error, event })) ?? {
108
- message: event.route.id === null && error instanceof NotFound ? 'Not Found' : 'Internal Error'
109
- }
110
- );
106
+ const status = get_status(error);
107
+ const message = get_message(error);
108
+
109
+ return (await options.hooks.handleError({ error, event, status, message })) ?? { message };
111
110
  }
112
111
 
113
112
  /**
@@ -149,6 +148,10 @@ export function stringify_uses(node) {
149
148
  uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
150
149
  }
151
150
 
151
+ if (node.uses && node.uses.search_params.size > 0) {
152
+ uses.push(`"search_params":${JSON.stringify(Array.from(node.uses.search_params))}`);
153
+ }
154
+
152
155
  if (node.uses && node.uses.params.size > 0) {
153
156
  uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`);
154
157
  }
@@ -1,9 +1,21 @@
1
- /** @type {Record<string, string>} */
1
+ /**
2
+ * `$env/dynamic/private`
3
+ * @type {Record<string, string>}
4
+ */
2
5
  export let private_env = {};
3
6
 
4
- /** @type {Record<string, string>} */
7
+ /**
8
+ * `$env/dynamic/public`. When prerendering, this will be a proxy that forbids reads
9
+ * @type {Record<string, string>}
10
+ */
5
11
  export let public_env = {};
6
12
 
13
+ /**
14
+ * The same as `public_env`, but without the proxy. Use for `%sveltekit.env.PUBLIC_FOO%`
15
+ * @type {Record<string, string>}
16
+ */
17
+ export let safe_public_env = {};
18
+
7
19
  /** @param {any} error */
8
20
  export let fix_stack_trace = (error) => error?.stack;
9
21
 
@@ -17,6 +29,11 @@ export function set_public_env(environment) {
17
29
  public_env = environment;
18
30
  }
19
31
 
32
+ /** @type {(environment: Record<string, string>) => void} */
33
+ export function set_safe_public_env(environment) {
34
+ safe_public_env = environment;
35
+ }
36
+
20
37
  /** @param {(error: Error) => string} value */
21
38
  export function set_fix_stack_trace(value) {
22
39
  fix_stack_trace = value;
@@ -7,6 +7,7 @@
7
7
  * // interface Error {}
8
8
  * // interface Locals {}
9
9
  * // interface PageData {}
10
+ * // interface PageState {}
10
11
  * // interface Platform {}
11
12
  * }
12
13
  * }
@@ -39,6 +40,11 @@ declare namespace App {
39
40
  */
40
41
  export interface PageData {}
41
42
 
43
+ /**
44
+ * The shape of the `$page.state` object, which can be manipulated using the [`pushState`](https://kit.svelte.dev/docs/modules#$app-navigation-pushstate) and [`replaceState`](https://kit.svelte.dev/docs/modules#$app-navigation-replacestate) functions from `$app/navigation`.
45
+ */
46
+ export interface PageState {}
47
+
42
48
  /**
43
49
  * If your adapter provides [platform-specific context](https://kit.svelte.dev/docs/adapters#platform-specific-context) via `event.platform`, you can specify it here.
44
50
  */
@@ -101,7 +107,7 @@ declare module '__sveltekit/paths' {
101
107
  * > If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during `vite dev` or `vite preview`, since the assets don't yet live at their eventual URL.
102
108
  */
103
109
  export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets';
104
- export let relative: boolean | undefined; // TODO in 2.0, make this a `boolean` that defaults to `true`
110
+ export let relative: boolean;
105
111
  export function reset(): void;
106
112
  export function override(paths: { base: string; assets: string }): void;
107
113
  export function set_assets(path: string): void;
@@ -31,6 +31,7 @@ export interface ServerInternalModule {
31
31
  set_assets(path: string): void;
32
32
  set_private_env(environment: Record<string, string>): void;
33
33
  set_public_env(environment: Record<string, string>): void;
34
+ set_safe_public_env(environment: Record<string, string>): void;
34
35
  set_version(version: string): void;
35
36
  set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void;
36
37
  }
@@ -59,6 +60,7 @@ export interface BuildData {
59
60
  imports: string[];
60
61
  stylesheets: string[];
61
62
  fonts: string[];
63
+ uses_env_dynamic_public: boolean;
62
64
  } | null;
63
65
  server_manifest: import('vite').Manifest;
64
66
  }
@@ -330,10 +332,10 @@ export interface SSRNode {
330
332
  export type SSRNodeLoader = () => Promise<SSRNode>;
331
333
 
332
334
  export interface SSROptions {
335
+ app_dir: string;
333
336
  app_template_contains_nonce: boolean;
334
337
  csp: ValidatedConfig['kit']['csp'];
335
338
  csrf_check_origin: boolean;
336
- track_server_fetches: boolean;
337
339
  embedded: boolean;
338
340
  env_public_prefix: string;
339
341
  env_private_prefix: string;
@@ -408,6 +410,7 @@ export interface Uses {
408
410
  parent: boolean;
409
411
  route: boolean;
410
412
  url: boolean;
413
+ search_params: Set<string>;
411
414
  }
412
415
 
413
416
  export type ValidatedConfig = RecursiveRequired<Config>;
@@ -2,6 +2,8 @@ This module provides access to runtime environment variables, as defined by the
2
2
 
3
3
  This module cannot be imported into client-side code.
4
4
 
5
+ Dynamic environment variables cannot be used during prerendering.
6
+
5
7
  ```ts
6
8
  import { env } from '$env/dynamic/private';
7
9
  console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
@@ -2,6 +2,8 @@ Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dyn
2
2
 
3
3
  Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
4
4
 
5
+ Dynamic environment variables cannot be used during prerendering.
6
+
5
7
  ```ts
6
8
  import { env } from '$env/dynamic/public';
7
9
  console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
@@ -1,3 +1,5 @@
1
+ import { HttpError, SvelteKitError } from '../runtime/control.js';
2
+
1
3
  /**
2
4
  * @param {unknown} err
3
5
  * @return {Error}
@@ -16,7 +18,21 @@ export function coalesce_to_error(err) {
16
18
  * @param {unknown} error
17
19
  */
18
20
  export function normalize_error(error) {
19
- return /** @type {import('../runtime/control.js').Redirect | import('../runtime/control.js').HttpError | Error} */ (
21
+ return /** @type {import('../runtime/control.js').Redirect | HttpError | SvelteKitError | Error} */ (
20
22
  error
21
23
  );
22
24
  }
25
+
26
+ /**
27
+ * @param {unknown} error
28
+ */
29
+ export function get_status(error) {
30
+ return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
31
+ }
32
+
33
+ /**
34
+ * @param {unknown} error
35
+ */
36
+ export function get_message(error) {
37
+ return error instanceof SvelteKitError ? error.text : 'Internal Error';
38
+ }
@@ -91,7 +91,7 @@ export function parse_route_id(id) {
91
91
  return '/' + result;
92
92
  })
93
93
  .join('')}/?$`
94
- );
94
+ );
95
95
 
96
96
  return { pattern, params };
97
97
  }
@@ -214,3 +214,49 @@ function escape(str) {
214
214
  .replace(/[.*+?^${}()|\\]/g, '\\$&')
215
215
  );
216
216
  }
217
+
218
+ const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
219
+
220
+ /**
221
+ * Populate a route ID with params to resolve a pathname.
222
+ * @example
223
+ * ```js
224
+ * resolveRoute(
225
+ * `/blog/[slug]/[...somethingElse]`,
226
+ * {
227
+ * slug: 'hello-world',
228
+ * somethingElse: 'something/else'
229
+ * }
230
+ * ); // `/blog/hello-world/something/else`
231
+ * ```
232
+ * @param {string} id
233
+ * @param {Record<string, string | undefined>} params
234
+ * @returns {string}
235
+ */
236
+ export function resolve_route(id, params) {
237
+ const segments = get_route_segments(id);
238
+ return (
239
+ '/' +
240
+ segments
241
+ .map((segment) =>
242
+ segment.replace(basic_param_pattern, (_, optional, rest, name) => {
243
+ const param_value = params[name];
244
+
245
+ // This is nested so TS correctly narrows the type
246
+ if (!param_value) {
247
+ if (optional) return '';
248
+ if (rest && param_value !== undefined) return '';
249
+ throw new Error(`Missing parameter '${name}' in route ${id}`);
250
+ }
251
+
252
+ if (param_value.startsWith('/') || param_value.endsWith('/'))
253
+ throw new Error(
254
+ `Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
255
+ );
256
+ return param_value;
257
+ })
258
+ )
259
+ .filter(Boolean)
260
+ .join('/')
261
+ );
262
+ }