Skip to content

Commit d63bb85

Browse files
authored
[fix] prevent never for data types, notice moved/deleted files (#7002)
Fixes #6950
1 parent 84ad168 commit d63bb85

File tree

16 files changed

+190
-127
lines changed

16 files changed

+190
-127
lines changed

.changeset/blue-lizards-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[fix] prevent data types from becoming type `never`, notice moved/deleted files

packages/kit/src/core/sync/write_types/index.js

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,82 @@ export async function write_all_types(config, manifest_data) {
4646
}
4747
}
4848

49+
// Read/write meta data on each invocation, not once per node process,
50+
// it could be invoked by another process in the meantime.
51+
const meta_data_file = `${types_dir}/route_meta_data.json`;
52+
const has_meta_data = fs.existsSync(meta_data_file);
53+
let meta_data = has_meta_data
54+
? /** @type {Record<string, string[]>} */ (JSON.parse(fs.readFileSync(meta_data_file, 'utf-8')))
55+
: {};
4956
const routes_map = create_routes_map(manifest_data);
5057
// For each directory, write $types.d.ts
5158
for (const route of manifest_data.routes) {
52-
update_types(config, routes_map, route);
59+
if (!route.leaf && !route.layout && !route.endpoint) continue; // nothing to do
60+
61+
const outdir = path.join(config.kit.outDir, 'types', routes_dir, route.id);
62+
63+
// check if the types are out of date
64+
/** @type {string[]} */
65+
const input_files = [];
66+
67+
/** @type {import('types').PageNode | null} */
68+
let node = route.leaf;
69+
while (node) {
70+
if (node.shared) input_files.push(node.shared);
71+
if (node.server) input_files.push(node.server);
72+
node = node.parent ?? null;
73+
}
74+
75+
/** @type {import('types').PageNode | null} */
76+
node = route.layout;
77+
while (node) {
78+
if (node.shared) input_files.push(node.shared);
79+
if (node.server) input_files.push(node.server);
80+
node = node.parent ?? null;
81+
}
82+
83+
if (route.endpoint) {
84+
input_files.push(route.endpoint.file);
85+
}
86+
87+
try {
88+
fs.mkdirSync(outdir, { recursive: true });
89+
} catch {}
90+
91+
const output_files = compact(
92+
fs.readdirSync(outdir).map((name) => {
93+
const stats = fs.statSync(path.join(outdir, name));
94+
if (stats.isDirectory()) return;
95+
return {
96+
name,
97+
updated: stats.mtimeMs
98+
};
99+
})
100+
);
101+
102+
const source_last_updated = Math.max(
103+
// ctimeMs includes move operations whereas mtimeMs does not
104+
...input_files.map((file) => fs.statSync(file).ctimeMs)
105+
);
106+
const types_last_updated = Math.max(...output_files.map((file) => file.updated));
107+
108+
const should_generate =
109+
// source files were generated more recently than the types
110+
source_last_updated > types_last_updated ||
111+
// no meta data file exists yet
112+
!has_meta_data ||
113+
// some file was deleted
114+
!meta_data[route.id]?.every((file) => input_files.includes(file));
115+
116+
if (should_generate) {
117+
// track which old files end up being surplus to requirements
118+
const to_delete = new Set(output_files.map((file) => file.name));
119+
update_types(config, routes_map, route, to_delete);
120+
meta_data[route.id] = input_files;
121+
}
53122
}
123+
124+
fs.writeFileSync(meta_data_file, JSON.stringify(meta_data, null, '\t'));
54125
}
55126

56127
/**
@@ -72,6 +143,7 @@ export async function write_types(config, manifest_data, file) {
72143

73144
const route = manifest_data.routes.find((route) => route.id === id);
74145
if (!route) return; // this shouldn't ever happen
146+
if (!route.leaf && !route.layout && !route.endpoint) return; // nothing to do
75147

76148
update_types(config, create_routes_map(manifest_data), route);
77149
}
@@ -96,60 +168,12 @@ function create_routes_map(manifest_data) {
96168
* @param {import('types').ValidatedConfig} config
97169
* @param {Map<import('types').PageNode, import('types').RouteData>} routes
98170
* @param {import('types').RouteData} route
171+
* @param {Set<string>} [to_delete]
99172
*/
100-
function update_types(config, routes, route) {
101-
if (!route.leaf && !route.layout && !route.endpoint) return; // nothing to do
102-
173+
function update_types(config, routes, route, to_delete = new Set()) {
103174
const routes_dir = posixify(path.relative('.', config.kit.files.routes));
104175
const outdir = path.join(config.kit.outDir, 'types', routes_dir, route.id);
105176

106-
// first, check if the types are out of date
107-
const input_files = [];
108-
109-
/** @type {import('types').PageNode | null} */
110-
let node = route.leaf;
111-
while (node) {
112-
if (node.shared) input_files.push(node.shared);
113-
if (node.server) input_files.push(node.server);
114-
node = node.parent ?? null;
115-
}
116-
117-
/** @type {import('types').PageNode | null} */
118-
node = route.layout;
119-
while (node) {
120-
if (node.shared) input_files.push(node.shared);
121-
if (node.server) input_files.push(node.server);
122-
node = node.parent ?? null;
123-
}
124-
125-
if (route.endpoint) {
126-
input_files.push(route.endpoint.file);
127-
}
128-
129-
try {
130-
fs.mkdirSync(outdir, { recursive: true });
131-
} catch {}
132-
133-
const output_files = compact(
134-
fs.readdirSync(outdir).map((name) => {
135-
const stats = fs.statSync(path.join(outdir, name));
136-
if (stats.isDirectory()) return;
137-
return {
138-
name,
139-
updated: stats.mtimeMs
140-
};
141-
})
142-
);
143-
144-
const source_last_updated = Math.max(...input_files.map((file) => fs.statSync(file).mtimeMs));
145-
const types_last_updated = Math.max(...output_files.map((file) => file?.updated));
146-
147-
// types were generated more recently than the source files, so don't regenerate
148-
if (types_last_updated > source_last_updated) return;
149-
150-
// track which old files end up being surplus to requirements
151-
const to_delete = new Set(output_files.map((file) => file.name));
152-
153177
// now generate new types
154178
const imports = [`import type * as Kit from '@sveltejs/kit';`];
155179

@@ -180,7 +204,7 @@ function update_types(config, routes, route) {
180204
`type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>`
181205
);
182206
// null & {} == null, we need to prevent that in some situations
183-
declarations.push(`type EnsureParentData<T> = T extends null | undefined ? {} : T;`);
207+
declarations.push(`type EnsureDefined<T> = T extends null | undefined ? {} : T;`);
184208
}
185209

186210
if (route.leaf) {
@@ -329,9 +353,13 @@ function process_node(node, outdir, is_page, all_pages_have_load = true) {
329353
written_proxies.push(`proxy${path.basename(node.shared)}`);
330354
}
331355

332-
const type = get_data_type(node.shared, `${parent_type} & ${prefix}ServerData`, proxy);
356+
const type = get_data_type(
357+
node.shared,
358+
`${parent_type} & EnsureDefined<${prefix}ServerData>`,
359+
proxy
360+
);
333361

334-
data = `Expand<Omit<${parent_type}, keyof ${type}> & ${type}>`;
362+
data = `Expand<Omit<${parent_type}, keyof ${type}> & EnsureDefined<${type}>>`;
335363

336364
const output_data_shape =
337365
!is_page && all_pages_have_load
@@ -345,7 +373,7 @@ function process_node(node, outdir, is_page, all_pages_have_load = true) {
345373
} else if (server_data === 'null') {
346374
data = `Expand<${parent_type}>`;
347375
} else {
348-
data = `Expand<Omit<${parent_type}, keyof ${prefix}ServerData> & ${prefix}ServerData>`;
376+
data = `Expand<Omit<${parent_type}, keyof ${prefix}ServerData> & EnsureDefined<${prefix}ServerData>>`;
349377
}
350378

351379
exports.push(`export type ${prefix}Data = ${data};`);
@@ -396,14 +424,14 @@ function get_parent_type(node, type) {
396424
parent = parent.parent;
397425
}
398426

399-
let parent_str = `EnsureParentData<${parent_imports[0] || '{}'}>`;
427+
let parent_str = `EnsureDefined<${parent_imports[0] || '{}'}>`;
400428
for (let i = 1; i < parent_imports.length; i++) {
401429
// Omit is necessary because a parent could have a property with the same key which would
402430
// cause a type conflict. At runtime the child overwrites the parent property in this case,
403431
// so reflect that in the type definition.
404-
// EnsureParentData is necessary because {something: string} & null becomes null.
432+
// EnsureDefined is necessary because {something: string} & null becomes null.
405433
// Output types of server loads can be null but when passed in through the `parent` parameter they are the empty object instead.
406-
parent_str = `Omit<${parent_str}, keyof ${parent_imports[i]}> & EnsureParentData<${parent_imports[i]}>`;
434+
parent_str = `Omit<${parent_str}, keyof ${parent_imports[i]}> & EnsureDefined<${parent_imports[i]}>`;
407435
}
408436
return parent_str;
409437
}

packages/kit/src/core/sync/write_types/test/layout-advanced/_expected/$types.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ type OutputDataShape<T> = MaybeWithVoid<
1111
Partial<Pick<App.PageData, keyof T & keyof App.PageData>> &
1212
Record<string, any>
1313
>;
14-
type EnsureParentData<T> = T extends null | undefined ? {} : T;
14+
type EnsureDefined<T> = T extends null | undefined ? {} : T;
1515
type LayoutParams = RouteParams & {};
16-
type LayoutParentData = EnsureParentData<{}>;
16+
type LayoutParentData = EnsureDefined<{}>;
1717

1818
export type LayoutServerData = null;
1919
export type LayoutLoad<
@@ -29,7 +29,9 @@ export type LayoutData = Expand<
2929
Awaited<ReturnType<typeof import('../../../../../../../../+layout.js').load>>
3030
>
3131
> &
32-
Kit.AwaitedProperties<
33-
Awaited<ReturnType<typeof import('../../../../../../../../+layout.js').load>>
32+
EnsureDefined<
33+
Kit.AwaitedProperties<
34+
Awaited<ReturnType<typeof import('../../../../../../../../+layout.js').load>>
35+
>
3436
>
3537
>;

packages/kit/src/core/sync/write_types/test/layout-advanced/_expected/(main)/$types.d.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ type OutputDataShape<T> = MaybeWithVoid<
1111
Partial<Pick<App.PageData, keyof T & keyof App.PageData>> &
1212
Record<string, any>
1313
>;
14-
type EnsureParentData<T> = T extends null | undefined ? {} : T;
15-
type PageParentData = EnsureParentData<import('../$types.js').LayoutData>;
14+
type EnsureDefined<T> = T extends null | undefined ? {} : T;
15+
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
1616
type LayoutParams = RouteParams & {};
17-
type LayoutServerParentData = EnsureParentData<import('../$types.js').LayoutServerData>;
18-
type LayoutParentData = EnsureParentData<import('../$types.js').LayoutData>;
17+
type LayoutServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
18+
type LayoutParentData = EnsureDefined<import('../$types.js').LayoutData>;
1919

2020
export type PageServerData = null;
2121
export type PageLoad<
@@ -29,8 +29,10 @@ export type PageData = Expand<
2929
Awaited<ReturnType<typeof import('../../../../../../../../../(main)/+page.js').load>>
3030
>
3131
> &
32-
Kit.AwaitedProperties<
33-
Awaited<ReturnType<typeof import('../../../../../../../../../(main)/+page.js').load>>
32+
EnsureDefined<
33+
Kit.AwaitedProperties<
34+
Awaited<ReturnType<typeof import('../../../../../../../../../(main)/+page.js').load>>
35+
>
3436
>
3537
>;
3638
export type LayoutServerLoad<
@@ -44,4 +46,6 @@ export type LayoutServerData = Expand<
4446
Awaited<ReturnType<typeof import('../../../../../../../../../(main)/+layout.server.js').load>>
4547
>
4648
>;
47-
export type LayoutData = Expand<Omit<LayoutParentData, keyof LayoutServerData> & LayoutServerData>;
49+
export type LayoutData = Expand<
50+
Omit<LayoutParentData, keyof LayoutServerData> & EnsureDefined<LayoutServerData>
51+
>;

packages/kit/src/core/sync/write_types/test/layout-advanced/_expected/(main)/sub/$types.d.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ type OutputDataShape<T> = MaybeWithVoid<
1111
Partial<Pick<App.PageData, keyof T & keyof App.PageData>> &
1212
Record<string, any>
1313
>;
14-
type EnsureParentData<T> = T extends null | undefined ? {} : T;
14+
type EnsureDefined<T> = T extends null | undefined ? {} : T;
1515
type PageParentData = Omit<
16-
EnsureParentData<import('../../$types.js').LayoutData>,
16+
EnsureDefined<import('../../$types.js').LayoutData>,
1717
keyof import('../$types.js').LayoutData
1818
> &
19-
EnsureParentData<import('../$types.js').LayoutData>;
19+
EnsureDefined<import('../$types.js').LayoutData>;
2020

2121
export type PageServerData = null;
2222
export type PageLoad<
@@ -30,7 +30,9 @@ export type PageData = Expand<
3030
Awaited<ReturnType<typeof import('../../../../../../../../../../(main)/sub/+page.js').load>>
3131
>
3232
> &
33-
Kit.AwaitedProperties<
34-
Awaited<ReturnType<typeof import('../../../../../../../../../../(main)/sub/+page.js').load>>
33+
EnsureDefined<
34+
Kit.AwaitedProperties<
35+
Awaited<ReturnType<typeof import('../../../../../../../../../../(main)/sub/+page.js').load>>
36+
>
3537
>
3638
>;

packages/kit/src/core/sync/write_types/test/layout/_expected/$types.d.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ type OutputDataShape<T> = MaybeWithVoid<
1111
Partial<Pick<App.PageData, keyof T & keyof App.PageData>> &
1212
Record<string, any>
1313
>;
14-
type EnsureParentData<T> = T extends null | undefined ? {} : T;
15-
type PageServerParentData = EnsureParentData<LayoutServerData>;
16-
type PageParentData = EnsureParentData<LayoutData>;
14+
type EnsureDefined<T> = T extends null | undefined ? {} : T;
15+
type PageServerParentData = EnsureDefined<LayoutServerData>;
16+
type PageParentData = EnsureDefined<LayoutData>;
1717
type LayoutParams = RouteParams & {};
18-
type LayoutServerParentData = EnsureParentData<{}>;
19-
type LayoutParentData = EnsureParentData<{}>;
18+
type LayoutServerParentData = EnsureDefined<{}>;
19+
type LayoutParentData = EnsureDefined<{}>;
2020

2121
export type PageServerLoad<
2222
OutputData extends (Partial<App.PageData> & Record<string, any>) | void =
@@ -41,8 +41,10 @@ export type PageData = Expand<
4141
Awaited<ReturnType<typeof import('../../../../../../../../+page.js').load>>
4242
>
4343
> &
44-
Kit.AwaitedProperties<
45-
Awaited<ReturnType<typeof import('../../../../../../../../+page.js').load>>
44+
EnsureDefined<
45+
Kit.AwaitedProperties<
46+
Awaited<ReturnType<typeof import('../../../../../../../../+page.js').load>>
47+
>
4648
>
4749
>;
4850
export type Action = Kit.Action<RouteParams>;
@@ -71,8 +73,10 @@ export type LayoutData = Expand<
7173
Awaited<ReturnType<typeof import('../../../../../../../../+layout.js').load>>
7274
>
7375
> &
74-
Kit.AwaitedProperties<
75-
Awaited<ReturnType<typeof import('../../../../../../../../+layout.js').load>>
76+
EnsureDefined<
77+
Kit.AwaitedProperties<
78+
Awaited<ReturnType<typeof import('../../../../../../../../+layout.js').load>>
79+
>
7680
>
7781
>;
7882
export type RequestEvent = Kit.RequestEvent<RouteParams>;

packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/_expected/$types.d.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ type OutputDataShape<T> = MaybeWithVoid<
1111
Partial<Pick<App.PageData, keyof T & keyof App.PageData>> &
1212
Record<string, any>
1313
>;
14-
type EnsureParentData<T> = T extends null | undefined ? {} : T;
15-
type PageServerParentData = EnsureParentData<LayoutServerData>;
16-
type PageParentData = EnsureParentData<LayoutData>;
14+
type EnsureDefined<T> = T extends null | undefined ? {} : T;
15+
type PageServerParentData = EnsureDefined<LayoutServerData>;
16+
type PageParentData = EnsureDefined<LayoutData>;
1717
type LayoutParams = RouteParams & {};
18-
type LayoutParentData = EnsureParentData<{}>;
18+
type LayoutParentData = EnsureDefined<{}>;
1919

2020
export type PageServerLoad<
2121
OutputData extends (Partial<App.PageData> & Record<string, any>) | void =
@@ -40,8 +40,10 @@ export type PageData = Expand<
4040
Awaited<ReturnType<typeof import('../../../../../../../../+page.js').load>>
4141
>
4242
> &
43-
Kit.AwaitedProperties<
44-
Awaited<ReturnType<typeof import('../../../../../../../../+page.js').load>>
43+
EnsureDefined<
44+
Kit.AwaitedProperties<
45+
Awaited<ReturnType<typeof import('../../../../../../../../+page.js').load>>
46+
>
4547
>
4648
>;
4749
export type Action = Kit.Action<RouteParams>;

0 commit comments

Comments
 (0)