feat(paginate): Return exact types from paginate() (#8229)
* 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>
This commit is contained in:
parent
57e9a28063
commit
ffc9e2d3de
5 changed files with 53 additions and 13 deletions
5
.changeset/shiny-dryers-swim.md
Normal file
5
.changeset/shiny-dryers-swim.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Paginate will now return exact types instead of a naive Record
|
2
packages/astro/client.d.ts
vendored
2
packages/astro/client.d.ts
vendored
|
@ -57,7 +57,7 @@ declare module 'astro:assets' {
|
||||||
};
|
};
|
||||||
|
|
||||||
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
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<
|
type ImgAttributes = WithRequired<
|
||||||
Omit<import('./types').HTMLAttributes<'img'>, 'src' | 'width' | 'height'>,
|
Omit<import('./types').HTMLAttributes<'img'>, 'src' | 'width' | 'height'>,
|
||||||
'alt'
|
'alt'
|
||||||
|
|
|
@ -1600,7 +1600,9 @@ export type GetStaticPaths = (
|
||||||
* const { slug } = Astro.params as Params;
|
* 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>
|
? R extends Array<infer U>
|
||||||
? U extends { params: infer P }
|
? U extends { params: infer P }
|
||||||
? P
|
? P
|
||||||
|
@ -1631,7 +1633,9 @@ export type InferGetStaticParamsType<T> = T extends () => infer R | Promise<infe
|
||||||
* const { propA, propB } = Astro.props;
|
* 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>
|
? R extends Array<infer U>
|
||||||
? U extends { props: infer P }
|
? U extends { props: infer P }
|
||||||
? P
|
? P
|
||||||
|
@ -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)
|
* [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`) */
|
/** the number of items per-page (default: `10`) */
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
/** key: value object of page params (ex: `{ tag: 'javascript' }`) */
|
/** key: value object of page params (ex: `{ tag: 'javascript' }`) */
|
||||||
params?: Params;
|
params?: PaginateParams;
|
||||||
/** object of props to forward to `page` result */
|
/** object of props to forward to `page` result */
|
||||||
props?: Props;
|
props?: PaginateProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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>;
|
export type Params = Record<string, string | undefined>;
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import type {
|
import type {
|
||||||
GetStaticPathsResult,
|
|
||||||
Page,
|
Page,
|
||||||
PaginateFunction,
|
PaginateFunction,
|
||||||
|
PaginateOptions,
|
||||||
Params,
|
Params,
|
||||||
Props,
|
Props,
|
||||||
RouteData,
|
RouteData,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
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(
|
return function paginateUtility(
|
||||||
data: any[],
|
data: any[],
|
||||||
args: { pageSize?: number; params?: Params; props?: Props } = {}
|
args: PaginateOptions<Props, Params> = {}
|
||||||
) {
|
): ReturnType<PaginateFunction> {
|
||||||
let { pageSize: _pageSize, params: _params, props: _props } = args;
|
let { pageSize: _pageSize, params: _params, props: _props } = args;
|
||||||
const pageSize = _pageSize || 10;
|
const pageSize = _pageSize || 10;
|
||||||
const paramName = 'page';
|
const paramName = 'page';
|
||||||
|
@ -31,7 +33,7 @@ export function generatePaginateFunction(routeMatch: RouteData): PaginateFunctio
|
||||||
}
|
}
|
||||||
const lastPage = Math.max(1, Math.ceil(data.length / pageSize));
|
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 pageNum = num + 1;
|
||||||
const start = pageSize === Infinity ? 0 : (pageNum - 1) * pageSize; // currentPage is 1-indexed
|
const start = pageSize === Infinity ? 0 : (pageNum - 1) * pageSize; // currentPage is 1-indexed
|
||||||
const end = Math.min(start + pageSize, data.length);
|
const end = Math.min(start + pageSize, data.length);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type {
|
||||||
GetStaticPathsItem,
|
GetStaticPathsItem,
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPathsResultKeyed,
|
GetStaticPathsResultKeyed,
|
||||||
|
PaginateFunction,
|
||||||
Params,
|
Params,
|
||||||
RouteData,
|
RouteData,
|
||||||
RuntimeMode,
|
RuntimeMode,
|
||||||
|
@ -50,7 +51,9 @@ export async function callGetStaticPaths({
|
||||||
// Calculate your static paths.
|
// Calculate your static paths.
|
||||||
let staticPaths: GetStaticPathsResult = [];
|
let staticPaths: GetStaticPathsResult = [];
|
||||||
staticPaths = await mod.getStaticPaths({
|
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() {
|
rss() {
|
||||||
throw new AstroError(AstroErrorData.GetStaticPathsRemovedRSSHelper);
|
throw new AstroError(AstroErrorData.GetStaticPathsRemovedRSSHelper);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue