diff --git a/.changeset/shiny-dryers-swim.md b/.changeset/shiny-dryers-swim.md new file mode 100644 index 000000000000..f943b2180903 --- /dev/null +++ b/.changeset/shiny-dryers-swim.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Paginate will now return exact types instead of a naive Record diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index e6389d41555e..9d064c0b1967 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -57,7 +57,7 @@ declare module 'astro:assets' { }; type WithRequired = T & { [P in K]-?: T[P] }; - type Simplify = { [KeyType in keyof T]: T[KeyType] }; + type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; type ImgAttributes = WithRequired< Omit, 'src' | 'width' | 'height'>, 'alt' diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index bc47e07795de..3efbb7ae8242 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1600,7 +1600,9 @@ export type GetStaticPaths = ( * const { slug } = Astro.params as Params; * ``` */ -export type InferGetStaticParamsType = T extends () => infer R | Promise +export type InferGetStaticParamsType = T extends ( + opts?: GetStaticPathsOptions +) => infer R | Promise ? R extends Array ? U extends { params: infer P } ? P @@ -1631,7 +1633,9 @@ export type InferGetStaticParamsType = T extends () => infer R | Promise = T extends () => infer R | Promise +export type InferGetStaticPropsType = T extends ( + opts: GetStaticPathsOptions +) => infer R | Promise ? R extends Array ? U extends { props: infer P } ? P @@ -1678,13 +1682,13 @@ export type MarkdownContent = Record> * * [Astro reference](https://docs.astro.build/en/reference/api-reference/#paginate) */ -export interface PaginateOptions { +export interface PaginateOptions { /** the number of items per-page (default: `10`) */ pageSize?: number; /** key: value object of page params (ex: `{ tag: 'javascript' }`) */ - params?: Params; + params?: PaginateParams; /** object of props to forward to `page` result */ - props?: Props; + props?: PaginateProps; } /** @@ -1718,7 +1722,33 @@ export interface Page { }; } -export type PaginateFunction = (data: any[], args?: PaginateOptions) => GetStaticPathsResult; +type OmitIndexSignature = { + // eslint-disable-next-line @typescript-eslint/ban-types + [KeyType in keyof ObjectType as {} extends Record + ? never + : KeyType]: ObjectType[KeyType]; +}; +// eslint-disable-next-line @typescript-eslint/ban-types +type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; +export type PaginateFunction = < + PaginateData, + AdditionalPaginateProps extends Props, + AdditionalPaginateParams extends Params, +>( + data: PaginateData[], + args?: PaginateOptions +) => { + params: Simplify< + { + page: string | undefined; + } & OmitIndexSignature + >; + props: Simplify< + { + page: Page; + } & OmitIndexSignature + >; +}[]; export type Params = Record; diff --git a/packages/astro/src/core/render/paginate.ts b/packages/astro/src/core/render/paginate.ts index dffabe178269..f8cd8709d60a 100644 --- a/packages/astro/src/core/render/paginate.ts +++ b/packages/astro/src/core/render/paginate.ts @@ -1,18 +1,20 @@ import type { - GetStaticPathsResult, Page, PaginateFunction, + PaginateOptions, Params, Props, RouteData, } from '../../@types/astro'; import { AstroError, AstroErrorData } from '../errors/index.js'; -export function generatePaginateFunction(routeMatch: RouteData): PaginateFunction { +export function generatePaginateFunction( + routeMatch: RouteData +): (...args: Parameters) => ReturnType { return function paginateUtility( data: any[], - args: { pageSize?: number; params?: Params; props?: Props } = {} - ) { + args: PaginateOptions = {} + ): ReturnType { let { pageSize: _pageSize, params: _params, props: _props } = args; const pageSize = _pageSize || 10; const paramName = 'page'; @@ -31,7 +33,7 @@ export function generatePaginateFunction(routeMatch: RouteData): PaginateFunctio } const lastPage = Math.max(1, Math.ceil(data.length / pageSize)); - const result: GetStaticPathsResult = [...Array(lastPage).keys()].map((num) => { + const result = [...Array(lastPage).keys()].map((num) => { const pageNum = num + 1; const start = pageSize === Infinity ? 0 : (pageNum - 1) * pageSize; // currentPage is 1-indexed const end = Math.min(start + pageSize, data.length); diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts index f607cf5d9629..d43ce514b010 100644 --- a/packages/astro/src/core/render/route-cache.ts +++ b/packages/astro/src/core/render/route-cache.ts @@ -3,6 +3,7 @@ import type { GetStaticPathsItem, GetStaticPathsResult, GetStaticPathsResultKeyed, + PaginateFunction, Params, RouteData, RuntimeMode, @@ -50,7 +51,9 @@ export async function callGetStaticPaths({ // Calculate your static paths. let staticPaths: GetStaticPathsResult = []; staticPaths = await mod.getStaticPaths({ - paginate: generatePaginateFunction(route), + // Q: Why the cast? + // A: So users downstream can have nicer typings, we have to make some sacrifice in our internal typings, which necessitate a cast here + paginate: generatePaginateFunction(route) as PaginateFunction, rss() { throw new AstroError(AstroErrorData.GetStaticPathsRemovedRSSHelper); },