Skip to content

Commit cc9347f

Browse files
benjamin.ecksteinduncanbeevers
authored andcommitted
feat(openapi-typescript): add TypeScript 6 support
- Widen peer dep: `^5.x` → `^5.x || ^6.x` - Upgrade devDependency in openapi-typescript-helpers: 5.9.3 → 6.0.2 - Build, tests, and lint all pass with TS6 - No code changes needed Closes #2723
1 parent 0cc7ee7 commit cc9347f

12 files changed

Lines changed: 211 additions & 131 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"openapi-typescript": patch
3+
"openapi-typescript-helpers": patch
4+
---
5+
6+
Add TypeScript 6 support.
7+
8+
- `openapi-typescript`: widen the `typescript` peer dependency to `^5.x || ^6.x`.
9+
- `openapi-typescript-helpers`: fix `Readable<T>` and `Writable<T>` so callable types (`Date`, `RegExp`, functions, and class instance methods) are preserved through the recursive mapped type. Without this, the mapped type recursed into method signatures and collapsed them to `{}` under `--strict`, breaking patterns like `Readable<{ createdAt: Date }>.createdAt.toISOString()`. Reproduces on both TS 5 and TS 6.

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ jobs:
2828
strategy:
2929
matrix:
3030
node-version: [22, 24]
31+
typescript-version: ["5", "6"]
3132
steps:
3233
- uses: actions/checkout@v6
3334
- uses: actions/setup-node@v6
3435
with:
3536
node-version: ${{ matrix.node-version }}
3637
- uses: pnpm/action-setup@v5
37-
with:
38-
run_install: true
38+
- name: Override catalog to TypeScript 5 (backward-compat check)
39+
if: matrix.typescript-version == '5'
40+
run: |
41+
sed -i 's/^ typescript: \^6\..*/ typescript: ^5.9.3/' pnpm-workspace.yaml
42+
grep '^ typescript:' pnpm-workspace.yaml
43+
- run: pnpm install --no-frozen-lockfile
3944
- run: pnpm test
4045
test-e2e:
4146
runs-on: ubuntu-latest

packages/openapi-fetch/test/helpers.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ export function createObservedClient<T extends {}, M extends MediaType = MediaTy
2727
* Convert a Headers object to a plain object for easier comparison
2828
*/
2929
export function headersToObj(headers: Headers | Record<string, string>): Record<string, string> {
30-
const iter =
31-
headers instanceof Headers
32-
? headers
33-
// @ts-expect-error FIXME: this is a missing "lib" in tsconfig.json but dunno what
34-
.entries()
35-
: Object.entries(headers);
3630
const result: Record<string, string> = {};
37-
for (const [k, v] of iter) {
38-
result[k] = v;
31+
if (headers instanceof Headers) {
32+
headers.forEach((value, key) => {
33+
result[key] = value;
34+
});
35+
} else {
36+
for (const [key, value] of Object.entries(headers)) {
37+
result[key] = value;
38+
}
3939
}
4040
return result;
4141
}

packages/openapi-fetch/test/no-strict-null-checks/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"extends": "../../tsconfig.json",
33
"compilerOptions": {
4+
"rootDir": "../..",
45
"strictNullChecks": false
56
},
67
"include": ["."],

packages/openapi-fetch/tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"compilerOptions": {
33
"allowSyntheticDefaultImports": true,
44
"declaration": true,
5-
"downlevelIteration": false,
65
"esModuleInterop": true,
76
"lib": ["ESNext", "DOM"],
87
"module": "NodeNext",

packages/openapi-typescript-helpers/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@
3434
"lint:ts": "tsc --noEmit"
3535
},
3636
"devDependencies": {
37-
"typescript": "5.9.3"
37+
"typescript": "catalog:"
3838
}
3939
}

packages/openapi-typescript-helpers/src/index.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,11 @@ export type Readable<T> =
223223
? Readable<U>
224224
: T extends (infer E)[]
225225
? Readable<E>[]
226-
: T extends object
227-
? { [K in keyof T as NonNullable<T[K]> extends $Write<any> ? never : K]: Readable<T[K]> }
228-
: T;
226+
: T extends (...args: never[]) => unknown
227+
? T
228+
: T extends object
229+
? { [K in keyof T as NonNullable<T[K]> extends $Write<any> ? never : K]: Readable<T[K]> }
230+
: T;
229231

230232
/**
231233
* Resolve type for writing (requests): strips $Read properties, unwraps $Write
@@ -240,8 +242,10 @@ export type Writable<T> =
240242
? Writable<U>
241243
: T extends (infer E)[]
242244
? Writable<E>[]
243-
: T extends object
244-
? { [K in keyof T as NonNullable<T[K]> extends $Read<any> ? never : K]: Writable<T[K]> } & {
245-
[K in keyof T as NonNullable<T[K]> extends $Read<any> ? K : never]?: never;
246-
}
247-
: T;
245+
: T extends (...args: never[]) => unknown
246+
? T
247+
: T extends object
248+
? { [K in keyof T as NonNullable<T[K]> extends $Read<any> ? never : K]: Writable<T[K]> } & {
249+
[K in keyof T as NonNullable<T[K]> extends $Read<any> ? K : never]?: never;
250+
}
251+
: T;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Type-level tests for Readable<T> / Writable<T> built-in object passthrough.
3+
*
4+
* These are pure compile-time assertions checked by `tsc --noEmit`. If any
5+
* assertion is wrong, the file fails to type-check.
6+
*/
7+
8+
import type { $Read, $Write, Readable, Writable } from "../src/index.js";
9+
10+
// Bidirectional structural assignability. Looser than the parametric `(<T>() => …)`
11+
// trick but enough for "the resulting shape preserves X" — mapped-type-produced
12+
// objects are structurally identical to their literal twins but fail strict
13+
// parametric equality.
14+
type Equals<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
15+
type Expect<T extends true> = T;
16+
17+
// --- Date passthrough ---
18+
19+
type _ReadableDate = Expect<Equals<Readable<Date>, Date>>;
20+
type _WritableDate = Expect<Equals<Writable<Date>, Date>>;
21+
22+
// $Read<Date> unwraps to Date, not to a structurally-mapped Date prototype
23+
type _ReadableReadDate = Expect<Equals<Readable<$Read<Date>>, Date>>;
24+
type _WritableWriteDate = Expect<Equals<Writable<$Write<Date>>, Date>>;
25+
26+
// Date inside an object field stays Date
27+
type _ReadableObjectWithDate = Expect<Equals<Readable<{ created: Date }>, { created: Date }>>;
28+
type _WritableObjectWithDate = Expect<Equals<Writable<{ created: Date }>, { created: Date }>>;
29+
30+
// --- RegExp passthrough ---
31+
32+
type _ReadableRegExp = Expect<Equals<Readable<RegExp>, RegExp>>;
33+
type _WritableRegExp = Expect<Equals<Writable<RegExp>, RegExp>>;
34+
35+
type _ReadableObjectWithRegExp = Expect<Equals<Readable<{ pattern: RegExp }>, { pattern: RegExp }>>;
36+
37+
// --- Function passthrough ---
38+
39+
type Fn = (x: number) => string;
40+
41+
type _ReadableFn = Expect<Equals<Readable<Fn>, Fn>>;
42+
type _WritableFn = Expect<Equals<Writable<Fn>, Fn>>;
43+
44+
type _ReadableObjectWithFn = Expect<Equals<Readable<{ handler: Fn }>, { handler: Fn }>>;
45+
46+
// --- Negative control: plain object still gets recursive treatment ---
47+
48+
// Plain nested object's $Write marker is still stripped from Readable
49+
type _ReadableStripsWrite = Expect<
50+
Equals<
51+
Readable<{ id: number; password: $Write<string> }>,
52+
{ id: number }
53+
>
54+
>;
55+
56+
// Plain nested object's $Read marker is still stripped from Writable
57+
type _WritableStripsRead = Expect<
58+
Equals<
59+
Writable<{ id: $Read<number>; name: string }>,
60+
{ name: string } & { id?: never }
61+
>
62+
>;

packages/openapi-typescript-helpers/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"compilerOptions": {
44
"skipLibCheck": false
55
},
6-
"include": ["src"]
6+
"include": ["src", "test"]
77
}

packages/openapi-typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"version": "pnpm run build"
6060
},
6161
"peerDependencies": {
62-
"typescript": "^5.x"
62+
"typescript": "^5.x || ^6.x"
6363
},
6464
"dependencies": {
6565
"@redocly/openapi-core": "^1.34.6",

0 commit comments

Comments
 (0)