diff --git a/apps/website/src/app/api/[package]/[version]/[item]/route.ts b/apps/website/src/app/api/[package]/[version]/[item]/route.ts new file mode 100644 index 000000000..da2343fe5 --- /dev/null +++ b/apps/website/src/app/api/[package]/[version]/[item]/route.ts @@ -0,0 +1,35 @@ +import { generatePath } from '@discordjs/api-extractor-utils'; +import { tryResolveSummaryText } from '@discordjs/scripts'; +import type { ApiDeclaredItem, ApiItemContainerMixin } from '@microsoft/api-extractor-model'; +import type { NextRequest } from 'next/server'; +import { NextResponse } from 'next/server'; +import { memberPredicate } from '~/components/documentation/util'; +import { fetchMember } from '~/util/fetchMember'; +import { resolveMembers } from '~/util/members'; + +export const revalidate = 3_600; + +export async function GET(_: NextRequest, { params }: { params: { item: string; package: string; version: string } }) { + const member = await fetchMember({ + package: params.package, + version: params.version, + item: params.item, + }); + + if (!member) { + return new Response(null, { status: 404 }); + } + + return NextResponse.json({ + name: member.displayName, + kind: member.kind, + summary: tryResolveSummaryText(member as ApiDeclaredItem) ?? '', + path: generatePath(member.getHierarchy(), params.version), + members: resolveMembers(member as ApiItemContainerMixin, memberPredicate).map((member) => ({ + name: member.item.displayName, + kind: member.item.kind, + summary: tryResolveSummaryText(member.item as ApiDeclaredItem) ?? '', + path: generatePath(member.item.getHierarchy(), params.version), + })), + }); +} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx b/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx index 3254bf45c..863043dbd 100644 --- a/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx +++ b/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx @@ -12,8 +12,9 @@ import type { ApiPropertySignature, ApiTypeAlias, ApiVariable, + ApiFunction, } from '@microsoft/api-extractor-model'; -import { ApiItemKind, ApiModel, ApiFunction } from '@microsoft/api-extractor-model'; +import { ApiItemKind, ApiModel } from '@microsoft/api-extractor-model'; import { notFound } from 'next/navigation'; import type { Metadata } from 'next/types'; import { fetchModelJSON } from '~/app/docAPI'; @@ -23,14 +24,10 @@ import { TypeAlias } from '~/components/model/TypeAlias'; import { Variable } from '~/components/model/Variable'; import { Enum } from '~/components/model/enum/Enum'; import { Function } from '~/components/model/function/Function'; -import { OVERLOAD_SEPARATOR, PACKAGES } from '~/util/constants'; -import { findMember, findMemberByKey } from '~/util/model'; - -export interface ItemRouteParams { - item: string; - package: string; - version: string; -} +import { OVERLOAD_SEPARATOR } from '~/util/constants'; +import type { ItemRouteParams } from '~/util/fetchMember'; +import { fetchMember } from '~/util/fetchMember'; +import { findMember } from '~/util/model'; async function fetchHeadMember({ package: packageName, version, item }: ItemRouteParams): Promise { const modelJSON = await fetchModelJSON(packageName, version); @@ -124,35 +121,6 @@ export async function generateStaticParams({ params: { package: packageName, ver })); } -async function fetchMember({ package: packageName, version: branchName = 'main', item }: ItemRouteParams) { - if (!PACKAGES.includes(packageName)) { - notFound(); - } - - const model = new ApiModel(); - - if (branchName === 'main') { - const modelJSONFiles = await Promise.all(PACKAGES.map(async (pkg) => fetchModelJSON(pkg, branchName))); - - for (const modelJSONFile of modelJSONFiles) { - addPackageToModel(model, modelJSONFile); - } - } else { - const modelJSON = await fetchModelJSON(packageName, branchName); - addPackageToModel(model, modelJSON); - } - - const [memberName, overloadIndex] = decodeURIComponent(item).split(OVERLOAD_SEPARATOR); - - // eslint-disable-next-line prefer-const - let { containerKey, displayName: name } = findMember(model, packageName, memberName) ?? {}; - if (name && overloadIndex && !Number.isNaN(Number.parseInt(overloadIndex, 10))) { - containerKey = ApiFunction.getContainerKey(name, Number.parseInt(overloadIndex, 10)); - } - - return memberName && containerKey ? findMemberByKey(model, packageName, containerKey) ?? null : null; -} - function Member({ member }: { member?: ApiItem }) { switch (member?.kind) { case 'Class': diff --git a/apps/website/src/components/documentation/util.ts b/apps/website/src/components/documentation/util.ts index c26e66b8d..10561ab29 100644 --- a/apps/website/src/components/documentation/util.ts +++ b/apps/website/src/components/documentation/util.ts @@ -29,7 +29,9 @@ export function resolveItemURI(item: ApiItem): string { : `${item.parent.displayName}${OVERLOAD_SEPARATOR}${item.parent.kind}${METHOD_SEPARATOR}${item.displayName}`; } -function memberPredicate(item: ApiItem): item is ApiMethod | ApiMethodSignature | ApiProperty | ApiPropertySignature { +export function memberPredicate( + item: ApiItem, +): item is ApiMethod | ApiMethodSignature | ApiProperty | ApiPropertySignature { return ( item.kind === ApiItemKind.Property || item.kind === ApiItemKind.PropertySignature || diff --git a/apps/website/src/util/fetchMember.ts b/apps/website/src/util/fetchMember.ts new file mode 100644 index 000000000..324e5b7e9 --- /dev/null +++ b/apps/website/src/util/fetchMember.ts @@ -0,0 +1,41 @@ +import { addPackageToModel } from '@discordjs/scripts'; +import { ApiModel, ApiFunction } from '@microsoft/api-extractor-model'; +import { notFound } from 'next/navigation'; +import { OVERLOAD_SEPARATOR, PACKAGES } from './constants'; +import { findMember, findMemberByKey } from './model'; +import { fetchModelJSON } from '~/app/docAPI'; + +export interface ItemRouteParams { + item: string; + package: string; + version: string; +} + +export async function fetchMember({ package: packageName, version: branchName = 'main', item }: ItemRouteParams) { + if (!PACKAGES.includes(packageName)) { + notFound(); + } + + const model = new ApiModel(); + + if (branchName === 'main') { + const modelJSONFiles = await Promise.all(PACKAGES.map(async (pkg) => fetchModelJSON(pkg, branchName))); + + for (const modelJSONFile of modelJSONFiles) { + addPackageToModel(model, modelJSONFile); + } + } else { + const modelJSON = await fetchModelJSON(packageName, branchName); + addPackageToModel(model, modelJSON); + } + + const [memberName, overloadIndex] = decodeURIComponent(item).split(OVERLOAD_SEPARATOR); + + // eslint-disable-next-line prefer-const + let { containerKey, displayName: name } = findMember(model, packageName, memberName) ?? {}; + if (name && overloadIndex && !Number.isNaN(Number.parseInt(overloadIndex, 10))) { + containerKey = ApiFunction.getContainerKey(name, Number.parseInt(overloadIndex, 10)); + } + + return memberName && containerKey ? findMemberByKey(model, packageName, containerKey) ?? null : null; +}