@sveltejs/kit 2.54.0 → 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.54.0",
3
+ "version": "2.55.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -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);
@@ -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(
@@ -11,7 +11,7 @@ function context_dev(name) {
11
11
  return context();
12
12
  } catch {
13
13
  throw new Error(
14
- `Can only read '${name}' on the server during rendering (not in e.g. \`load\` functions), as it is bound to the current request via component context. This prevents state from leaking between users.` +
14
+ `Can only read '${name}' on the server during rendering (not in e.g. \`load\` functions), as it is bound to the current request via component context. This prevents state from leaking between users. ` +
15
15
  'For more information, see https://svelte.dev/docs/kit/state-management#avoid-shared-state-on-the-server'
16
16
  );
17
17
  }
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // generated during release, do not modify
2
2
 
3
3
  /** @type {string} */
4
- export const VERSION = '2.54.0';
4
+ export const VERSION = '2.55.0';