Skip to content

Commit

Permalink
fix(docs): cache resolved types (#1551)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Sep 27, 2024
1 parent cb53af9 commit cb2bb7f
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 40 deletions.
21 changes: 19 additions & 2 deletions packages/ui/app/src/resolver/ApiDefinitionResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,31 @@ export interface ApiDefinitionResolverCache {
apiDefinitionId: FernRegistry.ApiDefinitionId;
pageId: DocsV1Read.PageId;
}): Promise<ResolvedApiPageMetadata | null | undefined>;

putResolvedTypeDeclaration({
apiDefinitionId,
typeId,
type,
}: {
apiDefinitionId: FernRegistry.ApiDefinitionId;
typeId: FernRegistry.TypeId;
type: ResolvedTypeDefinition;
}): Promise<void>;

getResolvedTypeDeclaration({
apiDefinitionId,
typeId,
}: {
apiDefinitionId: FernRegistry.ApiDefinitionId;
typeId: FernNavigation.TypeId;
}): Promise<ResolvedTypeDefinition | null | undefined>;
}

export class ApiDefinitionResolver {
public static async resolve(
collector: FernNavigation.NodeCollector,
root: FernNavigation.ApiReferenceNode,
holder: FernNavigation.ApiDefinitionHolder,
typeResolver: ApiTypeResolver,
pages: Record<string, DocsV1Read.PageContent>,
mdxOptions: FernSerializeMdxOptions | undefined,
featureFlags: FeatureFlags,
Expand All @@ -68,7 +85,7 @@ export class ApiDefinitionResolver {
collector,
root,
holder,
typeResolver,
new ApiTypeResolver(root.apiDefinitionId, holder.api.types, mdxOptions, serializeMdx, cache),
pages,
featureFlags,
mdxOptions,
Expand Down
6 changes: 5 additions & 1 deletion packages/ui/app/src/resolver/ApiEndpointResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ export class ApiEndpointResolver {
(endpoint.errorsV2 ?? []).map(async (error): Promise<ResolvedError> => {
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,
Expand Down
58 changes: 43 additions & 15 deletions packages/ui/app/src/resolver/ApiTypeResolver.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -13,37 +15,57 @@ import {

export class ApiTypeResolver {
public constructor(
private apiDefinitionId: APIV1Read.ApiDefinitionId,
private types: Record<string, APIV1Read.TypeDefinition>,
private mdxOptions: FernSerializeMdxOptions | undefined,
private serializeMdx: MDX_SERIALIZER,
private cache?: ApiDefinitionResolverCache,
) {}

public resolve = once(async (): Promise<Record<string, ResolvedTypeDefinition>> => {
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<ResolvedTypeDefinition> {
return this.resolveTypeShape(
typeDefinition.name,
typeDefinition.shape,
typeDefinition.description,
typeDefinition.availability,
);
public resolveTypeDefinition(
id: string,
typeDefinition: APIV1Read.TypeDefinition,
): Promise<ResolvedTypeDefinition> {
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<ResolvedTypeDefinition> {
return visitDiscriminatedUnion(typeShape, "type")._visit<Promise<ResolvedTypeDefinition>>({
public async resolveTypeShape({
id,
name,
typeShape,
description,
availability,
}: {
id: string;
name: string | undefined;
typeShape: APIV1Read.TypeShape;
description?: string;
availability?: APIV1Read.Availability;
}): Promise<ResolvedTypeDefinition> {
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<Promise<ResolvedTypeDefinition>>({
object: async (object) => ({
type: "object",
name,
Expand Down Expand Up @@ -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<APIV1Read.TypeReference, Promise<ResolvedTypeShape>>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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(
Expand Down Expand Up @@ -43,7 +41,6 @@ describe("resolveApiDefinition", () => {
collector,
node,
holder,
typeResolver,
{},
undefined,
DEFAULT_FEATURE_FLAGS,
Expand All @@ -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(
Expand Down Expand Up @@ -86,7 +82,6 @@ describe("resolveApiDefinition", () => {
collector,
node,
holder,
typeResolver,
{},
undefined,
DEFAULT_FEATURE_FLAGS,
Expand Down
11 changes: 4 additions & 7 deletions packages/ui/app/src/util/resolveDocsContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Promise<ResolvedApiEndpoint>>({
endpoint: async (endpoint) => {
if (parent?.type === "endpointPair") {
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
38 changes: 37 additions & 1 deletion packages/ui/docs-bundle/src/server/DocsCache.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<void> {
await kv.set(this.getResolvedTypeId({ apiDefinitionId, typeId }), type);
}
async getResolvedTypeDeclaration({
apiDefinitionId,
typeId,
}: {
apiDefinitionId: APIV1Read.ApiDefinitionId;
typeId: APIV1Read.TypeId;
}): Promise<ResolvedTypeDefinition | null | undefined> {
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}`;
}
}

0 comments on commit cb2bb7f

Please sign in to comment.