Skip to content

Commit

Permalink
feat(paginate): Return exact types from paginate() (#8229)
Browse files Browse the repository at this point in the history
* feat(paginate): Return exact types from paginate()

* chore: changeset

* fix(types): Fix infer utils while I'm here

---------

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
  • Loading branch information
Princesseuh and natemoo-re authored Aug 28, 2023
1 parent 57e9a28 commit ffc9e2d
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-dryers-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Paginate will now return exact types instead of a naive Record
2 changes: 1 addition & 1 deletion packages/astro/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ declare module 'astro:assets' {
};

type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] };
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
type ImgAttributes = WithRequired<
Omit<import('./types').HTMLAttributes<'img'>, 'src' | 'width' | 'height'>,
'alt'
Expand Down
42 changes: 36 additions & 6 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1600,7 +1600,9 @@ export type GetStaticPaths = (
* const { slug } = Astro.params as Params;
* ```
*/
export type InferGetStaticParamsType<T> = T extends () => infer R | Promise<infer R>
export type InferGetStaticParamsType<T> = T extends (
opts?: GetStaticPathsOptions
) => infer R | Promise<infer R>
? R extends Array<infer U>
? U extends { params: infer P }
? P
Expand Down Expand Up @@ -1631,7 +1633,9 @@ export type InferGetStaticParamsType<T> = T extends () => infer R | Promise<infe
* const { propA, propB } = Astro.props;
* ```
*/
export type InferGetStaticPropsType<T> = T extends () => infer R | Promise<infer R>
export type InferGetStaticPropsType<T> = T extends (
opts: GetStaticPathsOptions
) => infer R | Promise<infer R>
? R extends Array<infer U>
? U extends { props: infer P }
? P
Expand Down Expand Up @@ -1678,13 +1682,13 @@ export type MarkdownContent<T extends Record<string, any> = Record<string, any>>
*
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#paginate)
*/
export interface PaginateOptions {
export interface PaginateOptions<PaginateProps extends Props, PaginateParams extends Params> {
/** 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;
}

/**
Expand Down Expand Up @@ -1718,7 +1722,33 @@ export interface Page<T = any> {
};
}

export type PaginateFunction = (data: any[], args?: PaginateOptions) => GetStaticPathsResult;
type OmitIndexSignature<ObjectType> = {
// eslint-disable-next-line @typescript-eslint/ban-types
[KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
? never
: KeyType]: ObjectType[KeyType];
};
// eslint-disable-next-line @typescript-eslint/ban-types
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
export type PaginateFunction = <
PaginateData,
AdditionalPaginateProps extends Props,
AdditionalPaginateParams extends Params,
>(
data: PaginateData[],
args?: PaginateOptions<AdditionalPaginateProps, AdditionalPaginateParams>
) => {
params: Simplify<
{
page: string | undefined;
} & OmitIndexSignature<AdditionalPaginateParams>
>;
props: Simplify<
{
page: Page<PaginateData>;
} & OmitIndexSignature<AdditionalPaginateProps>
>;
}[];

export type Params = Record<string, string | undefined>;

Expand Down
12 changes: 7 additions & 5 deletions packages/astro/src/core/render/paginate.ts
Original file line number Diff line number Diff line change
@@ -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<PaginateFunction>) => ReturnType<PaginateFunction> {
return function paginateUtility(
data: any[],
args: { pageSize?: number; params?: Params; props?: Props } = {}
) {
args: PaginateOptions<Props, Params> = {}
): ReturnType<PaginateFunction> {
let { pageSize: _pageSize, params: _params, props: _props } = args;
const pageSize = _pageSize || 10;
const paramName = 'page';
Expand All @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion packages/astro/src/core/render/route-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
GetStaticPathsItem,
GetStaticPathsResult,
GetStaticPathsResultKeyed,
PaginateFunction,
Params,
RouteData,
RuntimeMode,
Expand Down Expand Up @@ -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);
},
Expand Down

0 comments on commit ffc9e2d

Please sign in to comment.