@sveltejs/kit 2.53.4 → 2.55.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.53.4",
3
+ "version": "2.55.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -23,7 +23,7 @@
23
23
  "@types/cookie": "^0.6.0",
24
24
  "acorn": "^8.14.1",
25
25
  "cookie": "^0.6.0",
26
- "devalue": "^5.6.3",
26
+ "devalue": "^5.6.4",
27
27
  "esm-env": "^1.2.2",
28
28
  "kleur": "^4.1.5",
29
29
  "magic-string": "^0.30.5",
@@ -133,23 +133,24 @@ const options = object(
133
133
  server: boolean(false)
134
134
  }),
135
135
  remoteFunctions: boolean(false),
136
- forkPreloads: boolean(false)
136
+ forkPreloads: boolean(false),
137
+ handleRenderingErrors: boolean(false)
137
138
  }),
138
139
 
139
140
  files: object({
140
- src: deprecate(string('src')),
141
- assets: deprecate(string('static')),
141
+ src: string('src'),
142
+ assets: string('static'),
142
143
  hooks: object({
143
- client: deprecate(string(null)),
144
- server: deprecate(string(null)),
145
- universal: deprecate(string(null))
144
+ client: string(null),
145
+ server: string(null),
146
+ universal: string(null)
146
147
  }),
147
- lib: deprecate(string(null)),
148
- params: deprecate(string(null)),
149
- routes: deprecate(string(null)),
150
- serviceWorker: deprecate(string(null)),
151
- appTemplate: deprecate(string(null)),
152
- errorTemplate: deprecate(string(null))
148
+ lib: string(null),
149
+ params: string(null),
150
+ routes: string(null),
151
+ serviceWorker: string(null),
152
+ appTemplate: string(null),
153
+ errorTemplate: string(null)
153
154
  }),
154
155
 
155
156
  inlineStyleThreshold: number(0),
@@ -33,7 +33,7 @@ export function create(config) {
33
33
 
34
34
  write_client_manifest(config.kit, manifest_data, `${output}/client`);
35
35
  write_server(config, output);
36
- write_root(manifest_data, output);
36
+ write_root(manifest_data, config, output);
37
37
  write_all_types(config, manifest_data);
38
38
  write_non_ambient(config.kit, manifest_data);
39
39
 
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { GENERATED_COMMENT } from '../../constants.js';
3
+ import { posixify } from '../../utils/filesystem.js';
3
4
  import { write_if_changed } from './utils.js';
4
5
  import { s } from '../../utils/misc.js';
5
6
  import { get_route_segments } from '../../utils/routing.js';
@@ -24,25 +25,25 @@ function get_pathnames_for_trailing_slash(pathname, route) {
24
25
  return [pathname];
25
26
  }
26
27
 
27
- /** @type {({ trailingSlash?: import('types').TrailingSlash } | null)[]} */
28
- const routes = [];
29
-
30
- if (route.leaf) routes.push(route.leaf.page_options ?? null);
31
- if (route.endpoint) routes.push(route.endpoint.page_options);
32
-
33
28
  /** @type {Set<string>} */
34
29
  const pathnames = new Set();
35
30
 
36
- for (const page_options of routes) {
37
- if (page_options === null || page_options.trailingSlash === 'ignore') {
31
+ /**
32
+ * @param {{ trailingSlash?: import('types').TrailingSlash } | null | undefined} page_options
33
+ */
34
+ const add_pathnames = (page_options) => {
35
+ if (page_options === null || page_options?.trailingSlash === 'ignore') {
38
36
  pathnames.add(pathname);
39
37
  pathnames.add(pathname + '/');
40
- } else if (page_options.trailingSlash === 'always') {
38
+ } else if (page_options?.trailingSlash === 'always') {
41
39
  pathnames.add(pathname + '/');
42
40
  } else {
43
41
  pathnames.add(pathname);
44
42
  }
45
- }
43
+ };
44
+
45
+ if (route.leaf) add_pathnames(route.leaf.page_options ?? null);
46
+ if (route.endpoint) add_pathnames(route.endpoint.page_options);
46
47
 
47
48
  return Array.from(pathnames);
48
49
  }
@@ -81,8 +82,66 @@ export {};
81
82
  /**
82
83
  * Generate app types interface extension
83
84
  * @param {import('types').ManifestData} manifest_data
85
+ * @param {import('types').ValidatedKitConfig} config
84
86
  */
85
- function generate_app_types(manifest_data) {
87
+ function generate_app_types(manifest_data, config) {
88
+ /** @param {string} matcher */
89
+ const path_to_matcher = (matcher) =>
90
+ posixify(path.relative(config.outDir, path.join(config.files.params, matcher + '.js')));
91
+
92
+ /** @type {Map<string, string>} */
93
+ const matcher_types = new Map();
94
+
95
+ /** @param {string | undefined} matcher */
96
+ const get_matcher_type = (matcher) => {
97
+ if (!matcher) return 'string';
98
+
99
+ let type = matcher_types.get(matcher);
100
+ if (!type) {
101
+ type = `MatcherParam<typeof import('${path_to_matcher(matcher)}').match>`;
102
+ matcher_types.set(matcher, type);
103
+ }
104
+
105
+ return type;
106
+ };
107
+
108
+ /** @param {Set<string> | null} matchers */
109
+ const get_matchers_type = (matchers) => {
110
+ if (matchers === null) return 'string';
111
+
112
+ return Array.from(matchers)
113
+ .map((matcher) => get_matcher_type(matcher))
114
+ .join(' | ');
115
+ };
116
+
117
+ /** @type {Set<string>} */
118
+ const route_ids = new Set(manifest_data.routes.map((route) => route.id));
119
+
120
+ /**
121
+ * @param {string} id
122
+ * @returns {string[]}
123
+ */
124
+ const get_ancestor_route_ids = (id) => {
125
+ /** @type {string[]} */
126
+ const ancestors = [];
127
+
128
+ if (route_ids.has('/')) {
129
+ ancestors.push('/');
130
+ }
131
+
132
+ let current = '';
133
+ for (const segment of id.slice(1).split('/')) {
134
+ if (!segment) continue;
135
+
136
+ current += '/' + segment;
137
+ if (route_ids.has(current)) {
138
+ ancestors.push(current);
139
+ }
140
+ }
141
+
142
+ return ancestors;
143
+ };
144
+
86
145
  /** @type {Set<string>} */
87
146
  const pathnames = new Set();
88
147
 
@@ -92,49 +151,96 @@ function generate_app_types(manifest_data) {
92
151
  /** @type {string[]} */
93
152
  const layouts = [];
94
153
 
154
+ /** @type {Map<string, Map<string, { optional: boolean, matchers: Set<string> | null }>>} */
155
+ const layout_params_by_route = new Map(
156
+ manifest_data.routes.map((route) => [
157
+ route.id,
158
+ new Map(
159
+ route.params.map((p) => [
160
+ p.name,
161
+ { optional: p.optional, matchers: p.matcher ? new Set([p.matcher]) : null }
162
+ ])
163
+ )
164
+ ])
165
+ );
166
+
95
167
  for (const route of manifest_data.routes) {
168
+ const ancestors = get_ancestor_route_ids(route.id);
169
+
170
+ for (const ancestor_id of ancestors) {
171
+ const ancestor_params = layout_params_by_route.get(ancestor_id);
172
+ if (!ancestor_params) continue;
173
+
174
+ for (const p of route.params) {
175
+ const matcher = p.matcher ?? null;
176
+ const entry = ancestor_params.get(p.name);
177
+ if (!entry) {
178
+ ancestor_params.set(p.name, {
179
+ optional: true,
180
+ matchers: matcher === null ? null : new Set([matcher])
181
+ });
182
+ continue;
183
+ }
184
+
185
+ if (entry.matchers === null) continue;
186
+
187
+ if (matcher === null) {
188
+ entry.matchers = null;
189
+ continue;
190
+ }
191
+
192
+ entry.matchers.add(matcher);
193
+ }
194
+ }
195
+ }
196
+
197
+ for (const route of manifest_data.routes) {
198
+ const pathname = remove_group_segments(route.id);
199
+ let normalized_pathname = pathname;
200
+
201
+ /** @type {(path: string) => string} */
202
+ let serialise = s;
203
+
96
204
  if (route.params.length > 0) {
97
- const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`);
205
+ const params = route.params.map((p) => {
206
+ const type = get_matcher_type(p.matcher);
207
+ return `${p.name}${p.optional ? '?:' : ':'} ${type}`;
208
+ });
98
209
  const route_type = `${s(route.id)}: { ${params.join('; ')} }`;
99
210
 
100
211
  dynamic_routes.push(route_type);
101
212
 
102
- const pathname = remove_group_segments(route.id);
103
- const replaced_pathname = replace_required_params(replace_optional_params(pathname));
213
+ normalized_pathname = replace_required_params(replace_optional_params(pathname));
214
+ serialise = (p) => `\`${p}\` & {}`;
215
+ }
104
216
 
105
- for (const p of get_pathnames_for_trailing_slash(replaced_pathname, route)) {
106
- pathnames.add(`\`${p}\` & {}`);
107
- }
108
- } else {
109
- const pathname = remove_group_segments(route.id);
110
- for (const p of get_pathnames_for_trailing_slash(pathname, route)) {
111
- pathnames.add(s(p));
112
- }
217
+ for (const p of get_pathnames_for_trailing_slash(normalized_pathname, route)) {
218
+ pathnames.add(serialise(p));
113
219
  }
114
220
 
115
- /** @type {Map<string, boolean>} */
116
- const child_params = new Map(route.params.map((p) => [p.name, p.optional]));
221
+ let layout_type = 'Record<string, never>';
117
222
 
118
- for (const child of manifest_data.routes.filter((r) => r.id.startsWith(route.id))) {
119
- for (const p of child.params) {
120
- if (!child_params.has(p.name)) {
121
- child_params.set(p.name, true); // always optional
122
- }
123
- }
124
- }
223
+ const layout_params = layout_params_by_route.get(route.id);
224
+ if (layout_params) {
225
+ const params = Array.from(layout_params)
226
+ .map(([name, { optional, matchers }]) => {
227
+ const type = get_matchers_type(matchers);
228
+ return `${name}${optional ? '?:' : ':'} ${type}`;
229
+ })
230
+ .join('; ');
125
231
 
126
- const layout_params = Array.from(child_params)
127
- .map(([name, optional]) => `${name}${optional ? '?:' : ':'} string`)
128
- .join('; ');
232
+ if (params.length > 0) layout_type = `{ ${params} }`;
233
+ }
129
234
 
130
- const layout_type = `${s(route.id)}: ${layout_params.length > 0 ? `{ ${layout_params} }` : 'Record<string, never>'}`;
131
- layouts.push(layout_type);
235
+ layouts.push(`${s(route.id)}: ${layout_type}`);
132
236
  }
133
237
 
134
238
  const assets = manifest_data.assets.map((asset) => s('/' + asset.file));
135
239
 
136
240
  return [
137
241
  'declare module "$app/types" {',
242
+ '\ttype MatcherParam<M> = M extends (param : string) => param is (infer U extends string) ? U : string;',
243
+ '',
138
244
  '\texport interface AppTypes {',
139
245
  `\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`,
140
246
  `\t\tRouteParams(): {\n\t\t\t${dynamic_routes.join(';\n\t\t\t')}\n\t\t};`,
@@ -153,7 +259,7 @@ function generate_app_types(manifest_data) {
153
259
  * @param {import('types').ManifestData} manifest_data
154
260
  */
155
261
  export function write_non_ambient(config, manifest_data) {
156
- const app_types = generate_app_types(manifest_data);
262
+ const app_types = generate_app_types(manifest_data, config);
157
263
  const content = [template, app_types].join('\n\n');
158
264
 
159
265
  write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), content);
@@ -2,11 +2,14 @@ import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';
2
2
 
3
3
  /**
4
4
  * @param {import('types').ManifestData} manifest_data
5
+ * @param {import('types').ValidatedConfig} config
5
6
  * @param {string} output
6
7
  */
7
- export function write_root(manifest_data, output) {
8
+ export function write_root(manifest_data, config, output) {
8
9
  // TODO remove default layout altogether
9
10
 
11
+ const use_boundaries = config.kit.experimental.handleRenderingErrors && isSvelte5Plus();
12
+
10
13
  const max_depth = Math.max(
11
14
  ...manifest_data.routes.map((route) =>
12
15
  route.page ? route.page.layouts.filter(Boolean).length + 1 : 0
@@ -20,8 +23,38 @@ export function write_root(manifest_data, output) {
20
23
  }
21
24
 
22
25
  let l = max_depth;
26
+ /** @type {string} */
27
+ let pyramid;
23
28
 
24
- let pyramid = dedent`
29
+ if (isSvelte5Plus() && use_boundaries) {
30
+ // with the @const we force the data[depth] access to be derived, which is important to not fire updates needlessly
31
+ // TODO in Svelte 5 we should rethink the client.js side, we can likely make data a $state and only update indexes that changed there, simplifying this a lot
32
+ pyramid = dedent`
33
+ {#snippet pyramid(depth)}
34
+ {@const Pyramid = constructors[depth]}
35
+ {#snippet failed(error)}
36
+ {@const ErrorPage = errors[depth]}
37
+ <ErrorPage {error} />
38
+ {/snippet}
39
+ <svelte:boundary failed={errors[depth] ? failed : undefined}>
40
+ {#if constructors[depth + 1]}
41
+ {@const d = data[depth]}
42
+ <!-- svelte-ignore binding_property_non_reactive -->
43
+ <Pyramid bind:this={components[depth]} data={d} {form} params={page.params}>
44
+ {@render pyramid(depth + 1)}
45
+ </Pyramid>
46
+ {:else}
47
+ {@const d = data[depth]}
48
+ <!-- svelte-ignore binding_property_non_reactive -->
49
+ <Pyramid bind:this={components[depth]} data={d} {form} params={page.params} {error} />
50
+ {/if}
51
+ </svelte:boundary>
52
+ {/snippet}
53
+
54
+ {@render pyramid(0)}
55
+ `;
56
+ } else {
57
+ pyramid = dedent`
25
58
  ${
26
59
  isSvelte5Plus()
27
60
  ? `<!-- svelte-ignore binding_property_non_reactive -->
@@ -29,8 +62,8 @@ export function write_root(manifest_data, output) {
29
62
  : `<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} params={page.params} />`
30
63
  }`;
31
64
 
32
- while (l--) {
33
- pyramid = dedent`
65
+ while (l--) {
66
+ pyramid = dedent`
34
67
  {#if constructors[${l + 1}]}
35
68
  ${
36
69
  isSvelte5Plus()
@@ -57,6 +90,7 @@ export function write_root(manifest_data, output) {
57
90
 
58
91
  {/if}
59
92
  `;
93
+ }
60
94
  }
61
95
 
62
96
  write_if_changed(
@@ -72,9 +106,10 @@ export function write_root(manifest_data, output) {
72
106
  ${
73
107
  isSvelte5Plus()
74
108
  ? dedent`
75
- let { stores, page, constructors, components = [], form, ${levels
109
+ let { stores, page, constructors, components = [], form, ${use_boundaries ? 'errors = [], error, ' : ''}${levels
76
110
  .map((l) => `data_${l} = null`)
77
111
  .join(', ')} } = $props();
112
+ ${use_boundaries ? `let data = $derived({${levels.map((l) => `'${l}': data_${l}`).join(', ')}})` : ''}
78
113
  `
79
114
  : dedent`
80
115
  export let stores;
@@ -108,7 +143,7 @@ export function write_root(manifest_data, output) {
108
143
  isSvelte5Plus()
109
144
  ? dedent`
110
145
  $effect(() => {
111
- stores;page;constructors;components;form;${levels.map((l) => `data_${l}`).join(';')};
146
+ stores;page;constructors;components;form;${use_boundaries ? 'errors;error;' : ''}${levels.map((l) => `data_${l}`).join(';')};
112
147
  stores.page.notify();
113
148
  });
114
149
  `
@@ -50,6 +50,7 @@ export const options = {
50
50
  root,
51
51
  service_worker: ${has_service_worker},
52
52
  service_worker_options: ${config.kit.serviceWorker.register ? s(config.kit.serviceWorker.options) : 'null'},
53
+ server_error_boundaries: ${s(!!config.kit.experimental.handleRenderingErrors)},
53
54
  templates: {
54
55
  app: ({ head, body, assets, nonce, env }) => ${s(template)
55
56
  .replace('%sveltekit.head%', '" + head + "')
@@ -199,11 +199,7 @@ function update_types(config, routes, route, to_delete = new Set()) {
199
199
 
200
200
  // returns the predicate of a matcher's type guard - or string if there is no type guard
201
201
  declarations.push(
202
- // TS complains on infer U, which seems weird, therefore ts-ignore it
203
- [
204
- '// @ts-ignore',
205
- 'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;'
206
- ].join('\n')
202
+ 'type MatcherParam<M> = M extends (param : string) => param is (infer U extends string) ? U : string;'
207
203
  );
208
204
 
209
205
  declarations.push(
@@ -276,7 +276,7 @@ export function isValidationError(e) {
276
276
  * @since 2.18.0
277
277
  */
278
278
  export function normalizeUrl(url) {
279
- url = new URL(url, 'http://internal');
279
+ url = new URL(url, 'a://a');
280
280
 
281
281
  const is_route_resolution = has_resolution_suffix(url.pathname);
282
282
  const is_data_request = has_data_suffix(url.pathname);
@@ -513,79 +513,88 @@ export interface KitConfig {
513
513
  * @default false
514
514
  */
515
515
  forkPreloads?: boolean;
516
+
517
+ /**
518
+ * Whether to enable the experimental handling of rendering errors.
519
+ * When enabled, `<svelte:boundary>` is used to wrap components at each level
520
+ * where there's an `+error.svelte`, rendering the error page if the component fails.
521
+ * In addition, error boundaries also work on the server and the error object goes through `handleError`.
522
+ * @default false
523
+ */
524
+ handleRenderingErrors?: boolean;
516
525
  };
517
526
  /**
518
527
  * Where to find various files within your project.
519
- * @deprecated
528
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
520
529
  */
521
530
  files?: {
522
531
  /**
523
- * the location of your source code
524
- * @deprecated
532
+ * The location of your source code.
533
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
525
534
  * @default "src"
526
535
  * @since 2.28
527
536
  */
528
537
  src?: string;
529
538
  /**
530
- * a place to put static files that should have stable URLs and undergo no processing, such as `favicon.ico` or `manifest.json`
531
- * @deprecated
539
+ * A place to put static files that should have stable URLs and undergo no processing, such as `favicon.ico` or `manifest.json`.
540
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
532
541
  * @default "static"
533
542
  */
534
543
  assets?: string;
535
544
  hooks?: {
536
545
  /**
537
546
  * The location of your client [hooks](https://svelte.dev/docs/kit/hooks).
538
- * @deprecated
547
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
539
548
  * @default "src/hooks.client"
540
549
  */
541
550
  client?: string;
542
551
  /**
543
552
  * The location of your server [hooks](https://svelte.dev/docs/kit/hooks).
544
- * @deprecated
553
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
545
554
  * @default "src/hooks.server"
546
555
  */
547
556
  server?: string;
548
557
  /**
549
558
  * The location of your universal [hooks](https://svelte.dev/docs/kit/hooks).
550
- * @deprecated
559
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
551
560
  * @default "src/hooks"
552
561
  * @since 2.3.0
553
562
  */
554
563
  universal?: string;
555
564
  };
556
565
  /**
557
- * your app's internal library, accessible throughout the codebase as `$lib`
558
- * @deprecated
566
+ * Your app's internal library, accessible throughout the codebase as `$lib`.
567
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
559
568
  * @default "src/lib"
560
569
  */
561
570
  lib?: string;
562
571
  /**
563
- * a directory containing [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching)
564
- * @deprecated
572
+ * A directory containing [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching).
573
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
565
574
  * @default "src/params"
566
575
  */
567
576
  params?: string;
568
577
  /**
569
- * the files that define the structure of your app (see [Routing](https://svelte.dev/docs/kit/routing))
570
- * @deprecated
578
+ * The files that define the structure of your app (see [Routing](https://svelte.dev/docs/kit/routing)).
579
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
571
580
  * @default "src/routes"
572
581
  */
573
582
  routes?: string;
574
583
  /**
575
- * the location of your service worker's entry point (see [Service workers](https://svelte.dev/docs/kit/service-workers))
576
- * @deprecated
584
+ * The location of your service worker's entry point (see [Service workers](https://svelte.dev/docs/kit/service-workers)).
585
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
577
586
  * @default "src/service-worker"
578
587
  */
579
588
  serviceWorker?: string;
580
589
  /**
581
- * the location of the template for HTML responses
582
- * @deprecated
590
+ * The location of the template for HTML responses.
591
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
583
592
  * @default "src/app.html"
584
593
  */
585
594
  appTemplate?: string;
586
595
  /**
587
- * the location of the template for fallback error responses
588
- * @deprecated
596
+ * The location of the template for fallback error responses.
597
+ * @deprecated this feature is still supported, but it's generally recommended to use [monorepos](https://levelup.video/tutorials/monorepos-with-pnpm) instead
589
598
  * @default "src/error.html"
590
599
  */
591
600
  errorTemplate?: string;
@@ -1236,7 +1245,7 @@ export interface NavigationBase {
1236
1245
  */
1237
1246
  to: NavigationTarget | null;
1238
1247
  /**
1239
- * Whether or not the navigation will result in the page being unloaded (i.e. not a client-side navigation)
1248
+ * Whether or not the navigation will result in the page being unloaded (i.e. not a client-side navigation).
1240
1249
  */
1241
1250
  willUnload: boolean;
1242
1251
  /**
@@ -1249,11 +1258,7 @@ export interface NavigationBase {
1249
1258
  export interface NavigationEnter extends NavigationBase {
1250
1259
  /**
1251
1260
  * The type of navigation:
1252
- * - `form`: The user submitted a `<form method="GET">`
1253
- * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1254
- * - `link`: Navigation was triggered by a link click
1255
- * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1256
- * - `popstate`: Navigation was triggered by back/forward navigation
1261
+ * - `enter`: The app has hydrated/started
1257
1262
  */
1258
1263
  type: 'enter';
1259
1264
 
@@ -1268,16 +1273,29 @@ export interface NavigationEnter extends NavigationBase {
1268
1273
  event?: undefined;
1269
1274
  }
1270
1275
 
1271
- export interface NavigationExternal extends NavigationBase {
1276
+ export type NavigationExternal = NavigationGoto | NavigationLeave;
1277
+
1278
+ export interface NavigationGoto extends NavigationBase {
1272
1279
  /**
1273
1280
  * The type of navigation:
1274
- * - `form`: The user submitted a `<form method="GET">`
1275
- * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1276
- * - `link`: Navigation was triggered by a link click
1277
1281
  * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1278
- * - `popstate`: Navigation was triggered by back/forward navigation
1279
1282
  */
1280
- type: Exclude<NavigationType, 'enter' | 'popstate' | 'link' | 'form'>;
1283
+ type: 'goto';
1284
+
1285
+ // TODO 3.0 remove this property, so that it only exists when type is 'popstate'
1286
+ // (would possibly be a breaking change to do it prior to that)
1287
+ /**
1288
+ * In case of a history back/forward navigation, the number of steps to go back/forward
1289
+ */
1290
+ delta?: undefined;
1291
+ }
1292
+
1293
+ export interface NavigationLeave extends NavigationBase {
1294
+ /**
1295
+ * The type of navigation:
1296
+ * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1297
+ */
1298
+ type: 'leave';
1281
1299
 
1282
1300
  // TODO 3.0 remove this property, so that it only exists when type is 'popstate'
1283
1301
  // (would possibly be a breaking change to do it prior to that)
@@ -1291,10 +1309,6 @@ export interface NavigationFormSubmit extends NavigationBase {
1291
1309
  /**
1292
1310
  * The type of navigation:
1293
1311
  * - `form`: The user submitted a `<form method="GET">`
1294
- * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1295
- * - `link`: Navigation was triggered by a link click
1296
- * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1297
- * - `popstate`: Navigation was triggered by back/forward navigation
1298
1312
  */
1299
1313
  type: 'form';
1300
1314
 
@@ -1314,10 +1328,6 @@ export interface NavigationFormSubmit extends NavigationBase {
1314
1328
  export interface NavigationPopState extends NavigationBase {
1315
1329
  /**
1316
1330
  * The type of navigation:
1317
- * - `form`: The user submitted a `<form method="GET">`
1318
- * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1319
- * - `link`: Navigation was triggered by a link click
1320
- * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1321
1331
  * - `popstate`: Navigation was triggered by back/forward navigation
1322
1332
  */
1323
1333
  type: 'popstate';
@@ -1336,11 +1346,7 @@ export interface NavigationPopState extends NavigationBase {
1336
1346
  export interface NavigationLink extends NavigationBase {
1337
1347
  /**
1338
1348
  * The type of navigation:
1339
- * - `form`: The user submitted a `<form method="GET">`
1340
- * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1341
1349
  * - `link`: Navigation was triggered by a link click
1342
- * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1343
- * - `popstate`: Navigation was triggered by back/forward navigation
1344
1350
  */
1345
1351
  type: 'link';
1346
1352
 
@@ -1377,13 +1383,6 @@ export type BeforeNavigate = Navigation & {
1377
1383
  * The argument passed to [`onNavigate`](https://svelte.dev/docs/kit/$app-navigation#onNavigate) callbacks.
1378
1384
  */
1379
1385
  export type OnNavigate = Navigation & {
1380
- /**
1381
- * The type of navigation:
1382
- * - `form`: The user submitted a `<form method="GET">`
1383
- * - `link`: Navigation was triggered by a link click
1384
- * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1385
- * - `popstate`: Navigation was triggered by back/forward navigation
1386
- */
1387
1386
  type: Exclude<NavigationType, 'enter' | 'leave'>;
1388
1387
  /**
1389
1388
  * Since `onNavigate` callbacks are called immediately before a client-side navigation, they will never be called with a navigation that unloads the page.
@@ -1395,14 +1394,6 @@ export type OnNavigate = Navigation & {
1395
1394
  * The argument passed to [`afterNavigate`](https://svelte.dev/docs/kit/$app-navigation#afterNavigate) callbacks.
1396
1395
  */
1397
1396
  export type AfterNavigate = (Navigation | NavigationEnter) & {
1398
- /**
1399
- * The type of navigation:
1400
- * - `enter`: The app has hydrated/started
1401
- * - `form`: The user submitted a `<form method="GET">`
1402
- * - `link`: Navigation was triggered by a link click
1403
- * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1404
- * - `popstate`: Navigation was triggered by back/forward navigation
1405
- */
1406
1397
  type: Exclude<NavigationType, 'leave'>;
1407
1398
  /**
1408
1399
  * Since `afterNavigate` callbacks are called after a navigation completes, they will never be called with a navigation that unloads the page.