diff --git a/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml b/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml new file mode 100644 index 000000000000..72c73b9ba0d7 --- /dev/null +++ b/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml @@ -0,0 +1,6 @@ +- summary: | + Fix `PAYLOAD_TOO_LARGE` failures when publishing docs for large APIs with dynamic snippets. The + dynamic IRs were being sent inline in the `registerApiDefinition` request once per SDK language, + even though the registry only needs the language names to issue upload URLs (the IRs are uploaded + to S3 separately). + type: fix diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts new file mode 100644 index 000000000000..9de5ad0d6ae2 --- /dev/null +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import { toRegisterDynamicIRsInput } from "../toRegisterDynamicIRsInput.js"; + +describe("toRegisterDynamicIRsInput", () => { + it("returns undefined when there are no dynamic IRs", () => { + expect(toRegisterDynamicIRsInput(undefined)).toBeUndefined(); + }); + + it("preserves the language keys", () => { + const result = toRegisterDynamicIRsInput({ + python: { dynamicIR: { types: {} } }, + typescript: { dynamicIR: { types: {} } } + }); + + expect(Object.keys(result ?? {}).sort()).toEqual(["python", "typescript"]); + }); + + it("strips the IR bodies so they are not sent in the registration request", () => { + const dynamicIR = { types: { User: { name: "User" } } }; + const result = toRegisterDynamicIRsInput({ python: { dynamicIR }, go: { dynamicIR } }); + + expect(result).toEqual({ python: {}, go: {} }); + expect(JSON.stringify(result)).not.toContain("User"); + }); +}); diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts index 9be6aec06e2a..e350eaf52d8a 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts @@ -69,6 +69,7 @@ import { normalizeRepoUrlToHttps } from "./normalizeRepoUrl.js"; import { publishDocsViaLedger } from "./publishDocsLedger.js"; import { publishDocsViaLedgerPreview } from "./publishDocsLedgerPreview.js"; import { retryWithBackoff } from "./retryWithBackoff.js"; +import { toRegisterDynamicIRsInput } from "./toRegisterDynamicIRsInput.js"; import { asyncPool } from "./utils/asyncPool.js"; const MEASURE_IMAGE_BATCH_SIZE = 10; @@ -479,16 +480,22 @@ export async function publishDocs({ const effectiveApiName = apiName ?? getOriginalName(ir.apiName); + const registerApiDefinitionRequest = { + orgId: CjsFdrSdk.OrgId(organization), + apiId: CjsFdrSdk.ApiId(effectiveApiName), + definition: apiDefinition, + dynamicIRs: toRegisterDynamicIRsInput(dynamicIRsByLanguage) + }; + context.logger.debug( + `registerApiDefinition request body for ${effectiveApiName}: ${Buffer.byteLength( + JSON.stringify(registerApiDefinitionRequest) + )} bytes, ${Object.keys(dynamicIRsByLanguage ?? {}).length} dynamic IR language(s) (IR bodies are uploaded separately)` + ); + let response; try { response = await retryWithBackoff({ - fn: () => - fdr.api.register.registerApiDefinition({ - orgId: CjsFdrSdk.OrgId(organization), - apiId: CjsFdrSdk.ApiId(effectiveApiName), - definition: apiDefinition, - dynamicIRs: dynamicIRsByLanguage - }), + fn: () => fdr.api.register.registerApiDefinition(registerApiDefinitionRequest), maxRetries: REGISTER_MAX_RETRIES, baseDelayMs: REGISTER_BASE_DELAY_MS, jitterFactor: REGISTER_JITTER_FACTOR, diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts new file mode 100644 index 000000000000..8e2028f42e94 --- /dev/null +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts @@ -0,0 +1,18 @@ +import type { APIV1Write } from "@fern-api/fdr-sdk"; + +type DynamicIr = APIV1Write.DynamicIr; + +/** + * FDR only reads the language keys of `dynamicIRs` when registering an API definition — it mints one + * presigned upload URL per language, and the IRs themselves are uploaded directly to S3 afterwards. + * Sending the IR bodies inline duplicates the entire IR once per language in the registration request + * body, which can exceed the server's request size limit for large APIs. + */ +export function toRegisterDynamicIRsInput( + dynamicIRsByLanguage: Record | undefined +): Record | undefined { + if (dynamicIRsByLanguage == null) { + return undefined; + } + return Object.fromEntries(Object.keys(dynamicIRsByLanguage).map((language) => [language, {}])); +}