From bae962ed551b98e8e4e729b986d50d85a2d774f8 Mon Sep 17 00:00:00 2001 From: Jesse Dijkstra Date: Tue, 14 Jul 2026 15:27:05 +0200 Subject: [PATCH 1/2] fix(helpers): preserve tuple response types Keep tuple arity and element positions through Readable and Writable transformations so generated clients retain fixed-length schema contracts. --- .changeset/bright-helpers-smile.md | 5 +++++ .../test/read-write-visibility/read-write.test.ts | 8 ++++++++ packages/openapi-typescript-helpers/src/index.ts | 8 ++++---- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 .changeset/bright-helpers-smile.md diff --git a/.changeset/bright-helpers-smile.md b/.changeset/bright-helpers-smile.md new file mode 100644 index 000000000..a62fa8e47 --- /dev/null +++ b/.changeset/bright-helpers-smile.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript-helpers": patch +--- + +Preserve tuple arity when deriving readable and writable types. diff --git a/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts b/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts index b699887af..a2f5af1ee 100644 --- a/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts +++ b/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts @@ -1,8 +1,16 @@ +import type { Readable, Writable } from "openapi-typescript-helpers"; import { describe, expect, expectTypeOf, test } from "vitest"; import { createObservedClient } from "../helpers.js"; import type { paths } from "./schemas/read-write.js"; describe("readOnly/writeOnly", () => { + test("preserves tuple arity in readable and writable types", () => { + type Availability = ["digital", "print"] | ["digital", "print", "presentation"]; + + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + describe("deeply nested $Read unwrapping through $Read", () => { test("$Read should continue recursion when unwrapping $Read", async () => { // This tests the fix for a bug where Readable<$Read> returned U directly diff --git a/packages/openapi-typescript-helpers/src/index.ts b/packages/openapi-typescript-helpers/src/index.ts index 391e0a694..f5fe76cff 100644 --- a/packages/openapi-typescript-helpers/src/index.ts +++ b/packages/openapi-typescript-helpers/src/index.ts @@ -221,8 +221,8 @@ export type Readable = ? never : T extends $Read ? Readable - : T extends (infer E)[] - ? Readable[] + : T extends readonly unknown[] + ? { [K in keyof T]: Readable } : T extends object ? { [K in keyof T as NonNullable extends $Write ? never : K]: Readable } : T; @@ -238,8 +238,8 @@ export type Writable = ? never : T extends $Write ? Writable - : T extends (infer E)[] - ? Writable[] + : T extends readonly unknown[] + ? { [K in keyof T]: Writable } : T extends object ? { [K in keyof T as NonNullable extends $Read ? never : K]: Writable } & { [K in keyof T as NonNullable extends $Read ? K : never]?: never; From abf73b85703bf78c174855c354026ef81610f735 Mon Sep 17 00:00:00 2001 From: Jesse Dijkstra Date: Tue, 14 Jul 2026 15:34:55 +0200 Subject: [PATCH 2/2] test(fetch): use neutral tuple fixture --- .../test/read-write-visibility/read-write.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts b/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts index a2f5af1ee..5e89bfae8 100644 --- a/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts +++ b/packages/openapi-fetch/test/read-write-visibility/read-write.test.ts @@ -5,10 +5,10 @@ import type { paths } from "./schemas/read-write.js"; describe("readOnly/writeOnly", () => { test("preserves tuple arity in readable and writable types", () => { - type Availability = ["digital", "print"] | ["digital", "print", "presentation"]; + type Tuple = ["a", "b"] | ["a", "b", "c"]; - expectTypeOf>().toEqualTypeOf(); - expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); }); describe("deeply nested $Read unwrapping through $Read", () => {