From fbf7c822e1dfa3b5019313b7016af79eaebd936e Mon Sep 17 00:00:00 2001 From: MarkXian Date: Fri, 14 Aug 2026 14:37:16 +0800 Subject: [PATCH 1/2] Preserve raw JSON in cached OpenAPI specs --- .changeset/preserve-openapi-raw-json.md | 5 +++++ packages/effect/src/unstable/httpapi/OpenApi.ts | 6 +++++- .../effect/test/unstable/httpapi/OpenApi.test.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .changeset/preserve-openapi-raw-json.md diff --git a/.changeset/preserve-openapi-raw-json.md b/.changeset/preserve-openapi-raw-json.md new file mode 100644 index 00000000000..8718904d1c6 --- /dev/null +++ b/.changeset/preserve-openapi-raw-json.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `JSON.rawJSON` values when cloning cached OpenAPI specs. diff --git a/packages/effect/src/unstable/httpapi/OpenApi.ts b/packages/effect/src/unstable/httpapi/OpenApi.ts index c7ff5bff44f..e6291722dc9 100644 --- a/packages/effect/src/unstable/httpapi/OpenApi.ts +++ b/packages/effect/src/unstable/httpapi/OpenApi.ts @@ -227,11 +227,15 @@ const compileSchemas: CompileSchemas = (asts) => ) ) +const jsonWithRawJSON = JSON as unknown as { isRawJSON?: (value: unknown) => boolean } + +const isJsonRawJSON = (value: unknown): boolean => jsonWithRawJSON.isRawJSON?.(value) === true + const cloneOpenAPISpec = (value: A): A => { if (Array.isArray(value)) { return value.map(cloneOpenAPISpec) as A } - if (value !== null && typeof value === "object") { + if (value !== null && typeof value === "object" && !isJsonRawJSON(value)) { const out: Record = {} for (const key of Object.keys(value)) { InternalRecord.assignProperty(out, key, cloneOpenAPISpec((value as Record)[key])) diff --git a/packages/effect/test/unstable/httpapi/OpenApi.test.ts b/packages/effect/test/unstable/httpapi/OpenApi.test.ts index c0de5e47d22..687e3dc4ac4 100644 --- a/packages/effect/test/unstable/httpapi/OpenApi.test.ts +++ b/packages/effect/test/unstable/httpapi/OpenApi.test.ts @@ -97,6 +97,22 @@ describe("OpenApi", () => { assert.isUndefined(third.paths["/resource"]!.get!.summary) }) + it("preserves JSON.rawJSON values when cloning cached specs", () => { + const rawJson = (JSON as { rawJSON: (value: string) => unknown }).rawJSON("9223372036854775807") + const Api = HttpApi.make("Api").annotate(OpenApi.Transform, (spec) => ({ + ...spec, + info: { + ...spec.info, + "x-raw": rawJson + } + })) + + const expected = `{"title":"Api","version":"0.0.1","x-raw":9223372036854775807}` + + assert.strictEqual(JSON.stringify(OpenApi.fromApi(Api).info), expected) + assert.strictEqual(JSON.stringify(OpenApi.fromApi(Api).info), expected) + }) + it("isolates the cached spec from external override mutations", () => { const info = { title: "Api", version: "1.0.0" } const Api = HttpApi.make("Api").annotate(OpenApi.Override, { info }) From c4045cecaa637aff9011ceb603878e7b01454faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=BC=E5=81=A5=E8=81=AA?= Date: Fri, 14 Aug 2026 15:19:03 +0800 Subject: [PATCH 2/2] Fix raw JSON test type cast --- packages/effect/test/unstable/httpapi/OpenApi.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effect/test/unstable/httpapi/OpenApi.test.ts b/packages/effect/test/unstable/httpapi/OpenApi.test.ts index 687e3dc4ac4..872730a8433 100644 --- a/packages/effect/test/unstable/httpapi/OpenApi.test.ts +++ b/packages/effect/test/unstable/httpapi/OpenApi.test.ts @@ -98,7 +98,7 @@ describe("OpenApi", () => { }) it("preserves JSON.rawJSON values when cloning cached specs", () => { - const rawJson = (JSON as { rawJSON: (value: string) => unknown }).rawJSON("9223372036854775807") + const rawJson = (JSON as unknown as { rawJSON: (value: string) => unknown }).rawJSON("9223372036854775807") const Api = HttpApi.make("Api").annotate(OpenApi.Transform, (spec) => ({ ...spec, info: {