diff --git a/.changeset/sour-cherries-play.md b/.changeset/sour-cherries-play.md new file mode 100644 index 000000000000..17baa59e14a5 --- /dev/null +++ b/.changeset/sour-cherries-play.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Tighten up params typings, fix load function typings, add event typings to generated types diff --git a/packages/kit/src/core/sync/write_types.js b/packages/kit/src/core/sync/write_types.js index 0eb629f45e70..34ec6f33e7bf 100644 --- a/packages/kit/src/core/sync/write_types.js +++ b/packages/kit/src/core/sync/write_types.js @@ -246,12 +246,18 @@ function write_types_for_dir(config, manifest_data, routes_dir, dir, groups, ts) exports.push(`export type PageData = ${data};`); if (load) { - exports.push(`export type PageLoad = ${load};`); + exports.push( + `export type PageLoad | void = Record | void> = ${load};` + ); + exports.push('export type PageLoadEvent = Parameters[0];'); } exports.push(`export type PageServerData = ${server_data};`); if (server_load) { - exports.push(`export type PageServerLoad = ${server_load};`); + exports.push( + `export type PageServerLoad | void = Record | void> = ${server_load};` + ); + exports.push('export type PageServerLoadEvent = Parameters[0];'); } if (group.leaf.server) { @@ -292,12 +298,18 @@ function write_types_for_dir(config, manifest_data, routes_dir, dir, groups, ts) exports.push(`export type LayoutData = ${data};`); if (load) { - exports.push(`export type LayoutLoad = ${load};`); + exports.push( + `export type LayoutLoad | void = Record | void> = ${load};` + ); + exports.push('export type LayoutLoadEvent = Parameters[0];'); } exports.push(`export type LayoutServerData = ${server_data};`); if (server_load) { - exports.push(`export type LayoutServerLoad = ${server_load};`); + exports.push( + `export type LayoutServerLoad | void = Record | void> = ${server_load};` + ); + exports.push('export type LayoutServerLoadEvent = Parameters[0];'); } } @@ -311,9 +323,15 @@ function write_types_for_dir(config, manifest_data, routes_dir, dir, groups, ts) /** @type {string[]} */ const load_exports = []; + /** @type {string[]} */ + const load_event_exports = []; + /** @type {string[]} */ const server_load_exports = []; + /** @type {string[]} */ + const server_load_event_exports = []; + for (const [name, node] of group.named_layouts) { const { data, server_data, load, server_load, written_proxies } = process_node( ts, @@ -326,26 +344,39 @@ function write_types_for_dir(config, manifest_data, routes_dir, dir, groups, ts) data_exports.push(`export type ${name} = ${data};`); server_data_exports.push(`export type ${name} = ${server_data};`); if (load) { - load_exports.push(`export type ${name} = ${load};`); + load_exports.push( + `export type ${name} | void = Record | void> = ${load};` + ); + load_event_exports.push(`export type ${name} = Parameters[0];`); } if (server_load) { - server_load_exports.push(`export type ${name} = ${load};`); + server_load_exports.push( + `export type ${name} | void = Record | void> = ${load};` + ); + server_load_event_exports.push( + `export type ${name} = Parameters[0];` + ); } } exports.push(`\nexport namespace LayoutData {\n\t${data_exports.join('\n\t')}\n}`); exports.push(`\nexport namespace LayoutLoad {\n\t${load_exports.join('\n\t')}\n}`); + exports.push(`\nexport namespace LayoutLoadEvent {\n\t${load_event_exports.join('\n\t')}\n}`); exports.push( `\nexport namespace LayoutServerData {\n\t${server_data_exports.join('\n\t')}\n}` ); exports.push( `\nexport namespace LayoutServerLoad {\n\t${server_load_exports.join('\n\t')}\n}` ); + exports.push( + `\nexport namespace LayoutServerLoadEvent {\n\t${server_load_event_exports.join('\n\t')}\n}` + ); } } if (group.endpoint) { exports.push(`export type RequestHandler = Kit.RequestHandler;`); + exports.push(`export type RequestEvent = Kit.RequestEvent;`); } const output = [imports.join('\n'), declarations.join('\n'), exports.join('\n')] @@ -384,7 +415,7 @@ function process_node(ts, node, outdir, params, groups) { } server_data = get_data_type(node.server, 'load', 'null', proxy); - server_load = `Kit.ServerLoad<${params}, ${get_parent_type('LayoutServerData')}>`; + server_load = `Kit.ServerLoad<${params}, ${get_parent_type('LayoutServerData')}, OutputData>`; if (proxy) { const types = []; @@ -415,7 +446,7 @@ function process_node(ts, node, outdir, params, groups) { } data = get_data_type(node.shared, 'load', server_data, proxy); - load = `Kit.Load<${params}, ${server_data}, ${get_parent_type('LayoutData')}>`; + load = `Kit.Load<${params}, ${server_data}, ${get_parent_type('LayoutData')}, OutputData>`; } else { data = server_data; } @@ -468,7 +499,7 @@ function process_node(ts, node, outdir, params, groups) { parent = parent_layout.parent; } - let parent_str = parent_imports[0] || 'null'; + let parent_str = parent_imports[0] || 'Record'; for (let i = 1; i < parent_imports.length; i++) { // Omit is necessary because a parent could have a property with the same key which would // cause a type conflict. At runtime the child overwrites the parent property in this case, diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 4600be85a1d9..f500b14d3831 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -448,7 +448,7 @@ export function create_client({ target, base, trailing_slash }) { * url: URL; * params: Record; * routeId: string | null; - * server_data: import('types').JSONObject | null; + * server_data: Record | null; * }} options * @returns {Promise} */ diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts index 493d2fb6e432..bfcbc3dd59c8 100644 --- a/packages/kit/src/runtime/client/types.d.ts +++ b/packages/kit/src/runtime/client/types.d.ts @@ -6,7 +6,7 @@ import { prefetch, prefetchRoutes } from '$app/navigation'; -import { CSRPageNode, CSRRoute, JSONObject } from 'types'; +import { CSRPageNode, CSRRoute } from 'types'; import { HttpError } from '../../index/private.js'; import { SerializedHttpError } from '../server/page/types.js'; @@ -92,7 +92,7 @@ export interface ServerDataRedirected { export interface ServerDataLoaded { type: 'data'; nodes: Array<{ - data?: JSONObject | null; // TODO or `-1` to indicate 'reuse cached data'? + data?: Record | null; // TODO or `-1` to indicate 'reuse cached data'? status?: number; message?: string; error?: { diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js index ab6cbfc027e6..338791b4d0b1 100644 --- a/packages/kit/src/runtime/server/endpoint.js +++ b/packages/kit/src/runtime/server/endpoint.js @@ -29,7 +29,9 @@ export async function render_endpoint(event, route) { return method_not_allowed(mod, method); } - const response = await handler(event); + const response = await handler( + /** @type {import('types').RequestEvent>} */ (event) + ); if (!(response instanceof Response)) { return new Response( diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 3d7c8c2ebb3d..625fc053953b 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -257,7 +257,7 @@ export async function respond(request, options, state) { event, node, parent: async () => { - /** @type {import('types').JSONObject} */ + /** @type {Record} */ const data = {}; for (let j = 0; j < i; j += 1) { const parent = await promises[j]; diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index c168cf56df71..9840a417c660 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -137,7 +137,7 @@ export async function render_page(event, route, options, state, resolve_opts) { /** @type {Error | null} */ let load_error = null; - /** @type {Array>} */ + /** @type {Array | null>>} */ const server_promises = nodes.map((node, i) => { if (load_error) { // if an error happens immediately, don't bother with the rest of the nodes @@ -156,7 +156,7 @@ export async function render_page(event, route, options, state, resolve_opts) { event, node, parent: async () => { - /** @type {import('types').JSONObject} */ + /** @type {Record} */ const data = {}; for (let j = 0; j < i; j += 1) { Object.assign(data, await server_promises[j]); diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js index 04872ad72bb4..ef73137fc27e 100644 --- a/packages/kit/src/runtime/server/page/load_data.js +++ b/packages/kit/src/runtime/server/page/load_data.js @@ -5,7 +5,7 @@ import { LoadURL, PrerenderingURL } from '../../../utils/url.js'; * @param {{ * event: import('types').RequestEvent; * node: import('types').SSRNode | undefined; - * parent: () => Promise; + * parent: () => Promise>; * }} opts */ export async function load_server_data({ event, node, parent }) { @@ -37,7 +37,7 @@ export async function load_server_data({ event, node, parent }) { * fetcher: typeof fetch; * node: import('types').SSRNode | undefined; * parent: () => Promise>; - * server_data_promise: Promise; + * server_data_promise: Promise | null>; * state: import('types').SSRState; * }} opts */ @@ -70,7 +70,7 @@ export async function load_data({ event, fetcher, node, parent, server_data_prom /** @param {Record} object */ async function unwrap_promises(object) { - /** @type {import('types').JSONObject} */ + /** @type {Record} */ const unwrapped = {}; for (const key in object) { diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 835b0b7b0986..31d9f494a8dd 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -86,7 +86,7 @@ export async function render_response({ /** @type {import('types').Page} */ page: { error, - params: event.params, + params: /** @type {Record} */ (event.params), routeId: event.routeId, status, url: state.prerendering ? new PrerenderingURL(event.url) : event.url, diff --git a/packages/kit/src/runtime/server/page/types.d.ts b/packages/kit/src/runtime/server/page/types.d.ts index 556496ee3486..c3230ddf995a 100644 --- a/packages/kit/src/runtime/server/page/types.d.ts +++ b/packages/kit/src/runtime/server/page/types.d.ts @@ -1,4 +1,4 @@ -import { JSONValue, ResponseHeaders, SSRNode, CspDirectives } from 'types'; +import { ResponseHeaders, SSRNode, CspDirectives } from 'types'; import { HttpError } from '../../../index/private'; export interface Fetched { @@ -21,7 +21,7 @@ export interface FetchState { export type Loaded = { node: SSRNode; data: Record | null; - server_data: JSONValue; + server_data: any; }; type CspMode = 'hash' | 'nonce' | 'auto'; diff --git a/packages/kit/src/utils/escape.js b/packages/kit/src/utils/escape.js index 051cc9d053a7..959a160677c2 100644 --- a/packages/kit/src/utils/escape.js +++ b/packages/kit/src/utils/escape.js @@ -37,7 +37,7 @@ const render_json_payload_script_regex = new RegExp( * Attribute names must be type-checked so we don't need to escape them. * * @param {import('types').PayloadScriptAttributes} attrs A list of attributes to be added to the element. - * @param {import('types').JSONValue} payload The data to be carried by the element. Must be serializable to JSON. + * @param {any} payload The data to be carried by the element. Must be serializable to JSON. * @returns {string} The raw HTML of a script element carrying the JSON payload. * @example const html = render_json_payload_script({ type: 'data', url: '/data.json' }, { foo: 'bar' }); */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8070cd969347..54ad5152d7ce 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -7,8 +7,6 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces'; import { AdapterEntry, CspDirectives, - JSONObject, - JSONValue, Logger, MaybePromise, Prerendered, @@ -194,18 +192,18 @@ export interface HandleError { * rather than using `Load` directly. */ export interface Load< - Params extends Record = Record, - InputData extends JSONObject | null = JSONObject | null, - ParentData extends Record | null = Record | null, - OutputData extends Record = Record + Params extends Partial> = Partial>, + InputData extends Record | null = Record | null, + ParentData extends Record = Record, + OutputData extends Record | void = Record | void > { - (event: LoadEvent): MaybePromise; + (event: LoadEvent): MaybePromise; } export interface LoadEvent< - Params extends Record = Record, - Data extends JSONObject | null = JSONObject | null, - ParentData extends Record | null = Record | null + Params extends Partial> = Partial>, + Data extends Record | null = Record | null, + ParentData extends Record = Record > { fetch(info: RequestInfo, init?: RequestInit): Promise; params: Params; @@ -235,7 +233,9 @@ export interface ParamMatcher { (param: string): boolean; } -export interface RequestEvent = Record> { +export interface RequestEvent< + Params extends Partial> = Partial> +> { clientAddress: string; locals: App.Locals; params: Params; @@ -260,8 +260,6 @@ export interface ResolveOptions { transformPageChunk?: (input: { html: string; done: boolean }) => MaybePromise; } -export type ResponseBody = JSONValue | Uint8Array | ReadableStream | Error; - export class Server { constructor(manifest: SSRManifest); init(options: ServerInitOptions): void; @@ -295,21 +293,23 @@ export interface SSRManifest { * rather than using `ServerLoad` directly. */ export interface ServerLoad< - Params extends Record = Record, - ParentData extends JSONObject | null = JSONObject | null, - OutputData extends JSONObject | void = JSONObject | void + Params extends Partial> = Partial>, + ParentData extends Record = Record, + OutputData extends Record | void = Record | void > { - (event: ServerLoadEvent): MaybePromise; + (event: ServerLoadEvent): MaybePromise; } export interface ServerLoadEvent< - Params extends Record = Record, - ParentData extends JSONObject | null = JSONObject | null + Params extends Partial> = Partial>, + ParentData extends Record = Record > extends RequestEvent { parent: () => Promise; } -export interface Action = Record> { +export interface Action< + Params extends Partial> = Partial> +> { (event: RequestEvent): MaybePromise< | { status?: number; errors: Record; location?: never } | { status?: never; errors?: never; location: string } diff --git a/packages/kit/types/private.d.ts b/packages/kit/types/private.d.ts index 7537f63efe25..f517091c29a5 100644 --- a/packages/kit/types/private.d.ts +++ b/packages/kit/types/private.d.ts @@ -26,15 +26,6 @@ export interface AdapterEntry { }) => MaybePromise; } -// TODO is this still used? -export type BodyValidator = { - [P in keyof T]: T[P] extends { [k: string]: unknown } - ? BodyValidator // recurse when T[P] is an object - : T[P] extends BigInt | Function | Symbol - ? never - : T[P]; -}; - // Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts // // MIT License @@ -145,20 +136,6 @@ export interface CspDirectives { export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; -export interface JSONObject { - [key: string]: JSONValue; -} - -export type JSONValue = - | string - | number - | boolean - | null - | undefined - | ToJSON - | JSONValue[] - | JSONObject; - export interface Logger { (msg: string): void; success(msg: string): void; @@ -229,8 +206,4 @@ export interface RouteSegment { rest: boolean; } -export interface ToJSON { - toJSON(...args: any[]): Exclude; -} - export type TrailingSlash = 'never' | 'always' | 'ignore';