From cb2bb7f924e942ba72128cc425a4e77c85353865 Mon Sep 17 00:00:00 2001 From: Deep Singhvi Date: Fri, 27 Sep 2024 07:46:20 -0400 Subject: [PATCH] fix(docs): cache resolved types (#1551) --- .../app/src/resolver/ApiDefinitionResolver.ts | 21 ++++++- .../app/src/resolver/ApiEndpointResolver.ts | 6 +- .../ui/app/src/resolver/ApiTypeResolver.ts | 58 ++++++++++++++----- .../__test__/ApiDefinitionResolver.test.ts | 5 -- .../ui/app/src/util/resolveDocsContent.ts | 11 ++-- .../src/pages/api/fern-docs/resolve-api.ts | 9 --- .../ui/docs-bundle/src/server/DocsCache.ts | 38 +++++++++++- 7 files changed, 108 insertions(+), 40 deletions(-) diff --git a/packages/ui/app/src/resolver/ApiDefinitionResolver.ts b/packages/ui/app/src/resolver/ApiDefinitionResolver.ts index fab0a80892..39c222b945 100644 --- a/packages/ui/app/src/resolver/ApiDefinitionResolver.ts +++ b/packages/ui/app/src/resolver/ApiDefinitionResolver.ts @@ -50,6 +50,24 @@ export interface ApiDefinitionResolverCache { apiDefinitionId: FernRegistry.ApiDefinitionId; pageId: DocsV1Read.PageId; }): Promise; + + putResolvedTypeDeclaration({ + apiDefinitionId, + typeId, + type, + }: { + apiDefinitionId: FernRegistry.ApiDefinitionId; + typeId: FernRegistry.TypeId; + type: ResolvedTypeDefinition; + }): Promise; + + getResolvedTypeDeclaration({ + apiDefinitionId, + typeId, + }: { + apiDefinitionId: FernRegistry.ApiDefinitionId; + typeId: FernNavigation.TypeId; + }): Promise; } export class ApiDefinitionResolver { @@ -57,7 +75,6 @@ export class ApiDefinitionResolver { collector: FernNavigation.NodeCollector, root: FernNavigation.ApiReferenceNode, holder: FernNavigation.ApiDefinitionHolder, - typeResolver: ApiTypeResolver, pages: Record, mdxOptions: FernSerializeMdxOptions | undefined, featureFlags: FeatureFlags, @@ -68,7 +85,7 @@ export class ApiDefinitionResolver { collector, root, holder, - typeResolver, + new ApiTypeResolver(root.apiDefinitionId, holder.api.types, mdxOptions, serializeMdx, cache), pages, featureFlags, mdxOptions, diff --git a/packages/ui/app/src/resolver/ApiEndpointResolver.ts b/packages/ui/app/src/resolver/ApiEndpointResolver.ts index 4c88f8d6ac..a8c96beb59 100644 --- a/packages/ui/app/src/resolver/ApiEndpointResolver.ts +++ b/packages/ui/app/src/resolver/ApiEndpointResolver.ts @@ -123,7 +123,11 @@ export class ApiEndpointResolver { (endpoint.errorsV2 ?? []).map(async (error): Promise => { const [shape, description] = await Promise.all([ error.type != null - ? this.typeResolver.resolveTypeShape(undefined, error.type, undefined, undefined) + ? this.typeResolver.resolveTypeShape({ + typeShape: error.type, + id: `${endpoint.id}-${error.statusCode}-${error.name}`, + name: error.name, + }) : ({ type: "unknown" } as ResolvedTypeDefinition), this.serializeMdx(error.description, { files: this.mdxOptions?.files, diff --git a/packages/ui/app/src/resolver/ApiTypeResolver.ts b/packages/ui/app/src/resolver/ApiTypeResolver.ts index 18eb527140..1d0a332f2b 100644 --- a/packages/ui/app/src/resolver/ApiTypeResolver.ts +++ b/packages/ui/app/src/resolver/ApiTypeResolver.ts @@ -1,8 +1,10 @@ +import { FernNavigation } from "@fern-api/fdr-sdk"; import type { APIV1Read } from "@fern-api/fdr-sdk/client/types"; import { visitDiscriminatedUnion } from "@fern-ui/core-utils"; import { once } from "lodash-es"; import { MDX_SERIALIZER } from "../mdx/bundler"; import { FernSerializeMdxOptions } from "../mdx/types"; +import { ApiDefinitionResolverCache } from "./ApiDefinitionResolver"; import { NonOptionalTypeShapeWithReference, ResolvedObjectProperty, @@ -13,37 +15,57 @@ import { export class ApiTypeResolver { public constructor( + private apiDefinitionId: APIV1Read.ApiDefinitionId, private types: Record, private mdxOptions: FernSerializeMdxOptions | undefined, private serializeMdx: MDX_SERIALIZER, + private cache?: ApiDefinitionResolverCache, ) {} public resolve = once(async (): Promise> => { return Object.fromEntries( await Promise.all( Object.entries(this.types).map(async ([key, value]) => { - return [key, await this.resolveTypeDefinition(value)]; + return [key, await this.resolveTypeDefinition(key, value)]; }), ), ); }); - public resolveTypeDefinition(typeDefinition: APIV1Read.TypeDefinition): Promise { - return this.resolveTypeShape( - typeDefinition.name, - typeDefinition.shape, - typeDefinition.description, - typeDefinition.availability, - ); + public resolveTypeDefinition( + id: string, + typeDefinition: APIV1Read.TypeDefinition, + ): Promise { + return this.resolveTypeShape({ + id, + name: typeDefinition.name, + typeShape: typeDefinition.shape, + description: typeDefinition.description, + availability: typeDefinition.availability, + }); } - public resolveTypeShape( - name: string | undefined, - typeShape: APIV1Read.TypeShape, - description?: string, - availability?: APIV1Read.Availability, - ): Promise { - return visitDiscriminatedUnion(typeShape, "type")._visit>({ + public async resolveTypeShape({ + id, + name, + typeShape, + description, + availability, + }: { + id: string; + name: string | undefined; + typeShape: APIV1Read.TypeShape; + description?: string; + availability?: APIV1Read.Availability; + }): Promise { + const cached = await this.cache?.getResolvedTypeDeclaration({ + apiDefinitionId: this.apiDefinitionId, + typeId: FernNavigation.TypeId(id), + }); + if (cached != null) { + return cached; + } + const computed = await visitDiscriminatedUnion(typeShape, "type")._visit>({ object: async (object) => ({ type: "object", name, @@ -129,6 +151,12 @@ export class ApiTypeResolver { description: undefined, }), }); + await this.cache?.putResolvedTypeDeclaration({ + apiDefinitionId: this.apiDefinitionId, + type: computed, + typeId: FernNavigation.TypeId(id), + }); + return computed; } private typeRefCache = new WeakMap>(); diff --git a/packages/ui/app/src/resolver/__test__/ApiDefinitionResolver.test.ts b/packages/ui/app/src/resolver/__test__/ApiDefinitionResolver.test.ts index ba54f79310..52efddc9a2 100644 --- a/packages/ui/app/src/resolver/__test__/ApiDefinitionResolver.test.ts +++ b/packages/ui/app/src/resolver/__test__/ApiDefinitionResolver.test.ts @@ -5,7 +5,6 @@ import path from "path"; import { DEFAULT_FEATURE_FLAGS } from "../../atoms"; import { serializeMdx } from "../../mdx/bundler"; import { ApiDefinitionResolver } from "../ApiDefinitionResolver"; -import { ApiTypeResolver } from "../ApiTypeResolver"; import { ResolvedEndpointDefinition } from "../types"; describe("resolveApiDefinition", () => { @@ -15,7 +14,6 @@ describe("resolveApiDefinition", () => { const fixture = JSON.parse(content) as APIV1Read.ApiDefinition; const holder = FernNavigation.ApiDefinitionHolder.create(fixture); - const typeResolver = new ApiTypeResolver(fixture.types, undefined, serializeMdx); // mocked node const v1 = FernNavigation.V1.ApiReferenceNavigationConverter.convert( @@ -43,7 +41,6 @@ describe("resolveApiDefinition", () => { collector, node, holder, - typeResolver, {}, undefined, DEFAULT_FEATURE_FLAGS, @@ -58,7 +55,6 @@ describe("resolveApiDefinition", () => { const fixture = JSON.parse(content) as APIV1Read.ApiDefinition; const holder = FernNavigation.ApiDefinitionHolder.create(fixture); - const typeResolver = new ApiTypeResolver(fixture.types, undefined, serializeMdx); // mocked node const v1 = FernNavigation.V1.ApiReferenceNavigationConverter.convert( @@ -86,7 +82,6 @@ describe("resolveApiDefinition", () => { collector, node, holder, - typeResolver, {}, undefined, DEFAULT_FEATURE_FLAGS, diff --git a/packages/ui/app/src/util/resolveDocsContent.ts b/packages/ui/app/src/util/resolveDocsContent.ts index 95efba7001..598a62f474 100644 --- a/packages/ui/app/src/util/resolveDocsContent.ts +++ b/packages/ui/app/src/util/resolveDocsContent.ts @@ -176,12 +176,13 @@ export async function resolveDocsContent({ const parent = found.parents[found.parents.length - 1]; api = pruner.prune(parent?.type === "endpointPair" ? parent : node); const holder = FernNavigation.ApiDefinitionHolder.create(api); - const typeResolver = new ApiTypeResolver(api.types, mdxOptions, serializeMdx); + const typeResolver = new ApiTypeResolver(apiReference.apiDefinitionId, api.types, mdxOptions, serializeMdx); + const resolvedTypes = await typeResolver.resolve(); const defResolver = new ApiEndpointResolver( found.collector, holder, typeResolver, - await typeResolver.resolve(), + resolvedTypes, featureFlags, mdxOptions, serializeMdx, @@ -191,7 +192,7 @@ export async function resolveDocsContent({ slug: found.node.slug, api: apiReference.apiDefinitionId, auth: api.auth, - types: await typeResolver.resolve(), + types: resolvedTypes, item: await visitDiscriminatedUnion(node)._visit>({ endpoint: async (endpoint) => { if (parent?.type === "endpointPair") { @@ -212,12 +213,10 @@ export async function resolveDocsContent({ }; } const holder = FernNavigation.ApiDefinitionHolder.create(api); - const typeResolver = new ApiTypeResolver(api.types, mdxOptions, serializeMdx); const apiDefinition = await ApiDefinitionResolver.resolve( found.collector, apiReference, holder, - typeResolver, pages, mdxOptions, featureFlags, @@ -299,14 +298,12 @@ async function resolveMarkdownPage( ]; } const holder = FernNavigation.ApiDefinitionHolder.create(definition); - const typeResolver = new ApiTypeResolver(definition.types, mdxOptions, serializeMdx); return [ apiNode.title, await ApiDefinitionResolver.resolve( found.collector, apiNode, holder, - typeResolver, pages, mdxOptions, featureFlags, diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts index c2a1a233c3..ffc36dc79d 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts @@ -7,7 +7,6 @@ import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { ApiDefinitionHolder } from "@fern-api/fdr-sdk/navigation"; import { ApiDefinitionResolver, - ApiTypeResolver, provideRegistryService, serializeMdx, setMdxBundler, @@ -88,18 +87,10 @@ const resolveApiHandler: NextApiHandler = async ( return; } const holder = ApiDefinitionHolder.create(api); - const typeResolver = new ApiTypeResolver( - api.types, - { - files: docs.definition.jsFiles, - }, - serializeMdxWithCaching, - ); const resolved = ApiDefinitionResolver.resolve( collector, apiReference, holder, - typeResolver, docs.definition.pages, { files: docs.definition.jsFiles }, featureFlags, diff --git a/packages/ui/docs-bundle/src/server/DocsCache.ts b/packages/ui/docs-bundle/src/server/DocsCache.ts index c928bc6edf..2d54264caa 100644 --- a/packages/ui/docs-bundle/src/server/DocsCache.ts +++ b/packages/ui/docs-bundle/src/server/DocsCache.ts @@ -1,5 +1,10 @@ import type { APIV1Read, FernNavigation } from "@fern-api/fdr-sdk"; -import { ApiDefinitionResolverCache, ResolvedApiPageMetadata, ResolvedEndpointDefinition } from "@fern-ui/ui"; +import { + ApiDefinitionResolverCache, + ResolvedApiPageMetadata, + ResolvedEndpointDefinition, + ResolvedTypeDefinition, +} from "@fern-ui/ui"; import { kv } from "@vercel/kv"; const DEPLOYMENT_ID = process.env.VERCEL_DEPLOYMENT_ID ?? "development"; @@ -95,4 +100,35 @@ export class DocsKVCache implements ApiDefinitionResolverCache { }): string { return `${PREFIX}:${this.domain}:${apiDefinitionId}:api-page:${pageId}`; } + + async putResolvedTypeDeclaration({ + apiDefinitionId, + typeId, + type, + }: { + apiDefinitionId: APIV1Read.ApiDefinitionId; + typeId: APIV1Read.TypeId; + type: ResolvedTypeDefinition; + }): Promise { + await kv.set(this.getResolvedTypeId({ apiDefinitionId, typeId }), type); + } + async getResolvedTypeDeclaration({ + apiDefinitionId, + typeId, + }: { + apiDefinitionId: APIV1Read.ApiDefinitionId; + typeId: APIV1Read.TypeId; + }): Promise { + return await kv.get(this.getResolvedTypeId({ apiDefinitionId, typeId })); + } + + private getResolvedTypeId({ + apiDefinitionId, + typeId, + }: { + apiDefinitionId: APIV1Read.ApiDefinitionId; + typeId: APIV1Read.TypeId; + }): string { + return `${PREFIX}:${this.domain}:${apiDefinitionId}:type:${typeId}`; + } }