Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .changeset/string-templates-drop-typescript-dep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
"openapi-typescript": major
---

feat: generate TypeScript with raw string templates and drop the TypeScript dependency

`openapi-typescript` no longer uses the TypeScript compiler API (`ts.factory`,
`createPrinter`, `createSourceFile`) at runtime. Generation now builds the
`.d.ts` source with string templates, so the package

- works with **TypeScript 7** (the native compiler), which ships no classic
compiler API — this fixes the `Cannot read properties of undefined (reading
'createKeywordTypeNode')` crash,
- has **no runtime TypeScript dependency**: `typescript` is no longer a peer
dependency and is not resolved at all when generating,
- supports TypeScript 6 in both the standalone and generated read/write
helpers, including callable and readonly collection handling.

`Readable<T>` and `Writable<T>` preserve call signatures and built-in methods
while resolving visibility markers on other data properties. Readonly array
methods and iterators expose resolved elements without making positional or
additional data properties writable. These fixes also apply under TypeScript 5.

Output fixes include:

- `tsUnion()` / `tsIntersection()` no longer emit a redundant single-member
union, so `(string)[][]` is now `string[][]`. Semantically identical.
- A multi-line `x-enum-descriptions` entry no longer leaks a bare token into the
enum body (it used to produce invalid TypeScript); line breaks become spaces.
- With `pathParamsAsTypes`, a URL containing a backtick or `${` no longer breaks
out of the generated template literal type.
- Nested immutable arrays retain their parentheses, and arrays of composed
item types retain their outer array dimension.
- CommonJS declarations now match the actual default and named exports, and
the published package includes the type dependencies needed by Redocly's
public declarations for consumers with `skipLibCheck: false`.

**Breaking changes**

- `openapiTS()` now resolves to a `string` (the generated file body, without the
comment header) instead of an array of AST nodes. The body ends with a newline.
- The transform hooks exchange plain strings instead of AST nodes:
- `transform` returns `string | { schema: string; questionToken: boolean }`
- `postTransform` receives and returns `string`
- `transformProperty` receives and returns `{ name, optional, readonly, type, comment?, indent }`;
use the new `tsComment()` helper to attach JSDoc from that hook
- `GlobalContext.injectFooter` is now a `FooterDeclaration[]` (generated strings,
plus a deferred `OperationsDeclaration` for the `operations` interface)
- Callbacks that used to build AST nodes with a separately installed `typescript`
must return the equivalent type text instead, e.g.
`ts.factory.createTypeReferenceNode("Date")` becomes `"Date"`. `typescript` is
no longer required to use this package at all.
- The AST-oriented helpers `stringToAST()`, `tsModifiers()` and `QUESTION_TOKEN`
were removed and are replaced by string builders: `typeLiteral()`,
`tupleType()`, `propertySignature()`, `indexSignature()`, `typeAlias()`,
`interfaceDecl()`, `enumDecl()`, `INDENT` and `tsComment()`.
- The `inject` option is now emitted verbatim instead of being re-printed by the
TypeScript printer.
- `astToString()` now joins source strings and ensures a trailing newline. AST
nodes and the former printer options argument are no longer supported;
passing options throws instead of silently ignoring them.
- With `postTransform` configured, nonempty object-shaped `$defs` roots use type
aliases so custom mapped types are valid. Root detection accepts surrounding
comments and inline formatting while retaining the empty-root fallback.
16 changes: 16 additions & 0 deletions .changeset/typescript-6-read-write-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"openapi-typescript-helpers": patch
---

Support TypeScript 6 alongside TypeScript 5 and 7.

Fix `Readable<T>` and `Writable<T>` to preserve call signatures and methods on
objects such as `Date` and `RegExp`, while resolving visibility markers on
their other data properties. Callable arguments, return types, and attached
properties remain unchanged. These fixes also apply under TypeScript 5.

Resolve readonly-array methods and iteration through their element types while
preserving readonly indices, length, required numeric properties, and extra
data properties. Retain existing mutable-tuple behavior, support generics and
recursive arrays/tuples, and use the mutable-array `never` rule for excluded
readonly elements.
43 changes: 41 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,43 @@ jobs:
- uses: pnpm/action-setup@v5
with:
run_install: true
- run: pnpm run lint
- run: pnpm --recursive run --if-present lint:js
test-node-versions:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 24]
typescript-version: [5, 6]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- uses: pnpm/action-setup@v5
- name: Install locked dependencies (TypeScript 6)
if: matrix.typescript-version == 6
run: pnpm install --frozen-lockfile
- name: Install TypeScript 5 across the workspace
if: matrix.typescript-version == 5
run: |
sed -i 's/^ typescript: .*/ typescript: ^5.9.3/' pnpm-workspace.yaml
pnpm install --no-frozen-lockfile
- run: pnpm exec tsc --version
- run: pnpm run build
- name: Typecheck packages
run: |
# The helpers package runs its type assertions through pnpm test below.
pnpm --recursive --filter '!openapi-typescript-helpers' run --if-present lint:ts
pnpm --recursive run --if-present lint:ts-no-strict
- run: pnpm test
- name: Typecheck framework examples
run: |
pnpm --filter '@example/openapi-fetch-vue-3' type-check
pnpm --filter '@example/openapi-fetch-sveltekit' check
pnpm --filter '@example/openapi-fetch-nextjs' exec tsc --noEmit
test-consumers:
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
matrix:
node-version: [22, 24]
Expand All @@ -36,7 +70,12 @@ jobs:
- uses: pnpm/action-setup@v5
with:
run_install: true
- run: pnpm test
- run: pnpm --filter openapi-typescript --filter openapi-typescript-helpers build
- name: Test TypeScript 5, 6, 7 and compiler-free consumers
run: |
for version in 5.9.3 6.0.3 7.0.2 none; do
pnpm --filter openapi-typescript test:consumer -- "$version"
done
test-e2e:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ _Note: OpenAPI 2.x is supported with versions `5.x` and previous_
This library requires the latest version of [Node.js](https://nodejs.org) installed (20.x or higher recommended). With that present, run the following in your project:

```bash
npm i -D openapi-typescript typescript
npm i -D openapi-typescript
```

And in your `tsconfig.json`, to load the types properly:
Expand Down
77 changes: 42 additions & 35 deletions docs/ja/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Node APIは、動的に生成されたスキーマを扱う場合や、より大
## セットアップ

```bash
npm i --save-dev openapi-typescript typescript
npm i --save-dev openapi-typescript
```

::: tip 推奨
Expand All @@ -31,18 +31,17 @@ Node.js APIは、`URL`、`string`、またはJSONオブジェクトを入力と

また、 `Readable` ストリームや`Buffer` 型も受け付け、これらは文字列として解決されます(ドキュメント全体がないと検証、バンドル、型生成ができません)。

Node APIはTypeScript の AST を含む `Promise` を返します。その後、必要に応じてASTをトラバース、操作、または修正できます
Node APIは、生成されたTypeScriptソースの文字列を含む `Promise` を返します。そのままファイルに書き込めます。型の生成に `typescript` パッケージは必要ありません

TypeScript ASTを文字列に変換するには、[TypeScriptのprinterのラッパー](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#re-printing-sections-of-a-typescript-file)である `astToString()` ヘルパーを使用できます:
`astToString()` は引き続き利用できますが、現在はソース文字列を受け取り、末尾に改行を追加するヘルパーです。TypeScript ASTやプリンターのオプションは使用できません。

::: code-group

```ts [src/my-project.ts]
import fs from "node:fs";
import openapiTS, { astToString } from "openapi-typescript";
import openapiTS from "openapi-typescript";

const ast = await openapiTS(new URL("./my-schema.yaml", import.meta.url));
const contents = astToString(ast);
const contents = await openapiTS(new URL("./my-schema.yaml", import.meta.url));

// (任意)ファイルに書き込み
fs.writeFileSync("./my-schema.ts", contents);
Expand Down Expand Up @@ -74,7 +73,7 @@ const redocly = await createConfig(
// オプション2: redocly.yamlファイルから読み込み
const redocly = await loadConfig({ configPath: "redocly.yaml" });

const ast = await openapiTS(mySchema, { redocly });
const types = await openapiTS(mySchema, { redocly });
```

:::
Expand All @@ -96,7 +95,7 @@ Node APIは、 `camelCase` 形式で[CLI フラグ](./cli#%E3%83%95%E3%83%A9%E3%
`transform()` と `postTransform()` オプションを使用して、デフォルトのスキーマオブジェクト変換を独自のものに上書きできます。これは、スキーマの特定の部分に対して非標準的な変更を提供する場合に役立ちます。

- `transform()` はTypeScriptへの **変換前** に実行されます(OpenAPIノードを扱います)
- `postTransform()` はTypeScriptへの **変換後** に実行されます(TypeScript ASTを扱います
- `postTransform()` はTypeScriptへの **変換後** に実行されます(生成された型の文字列を扱います

#### 例: `Date` 型

Expand All @@ -115,19 +114,14 @@ properties:

```ts [src/my-project.ts]
import openapiTS from "openapi-typescript";
import ts from "typescript";

const DATE = ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("Date")
); // `Date`
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`
const DATE = "Date"; // `Date`
const NULL = "null"; // `null`

const ast = await openapiTS(mySchema, {
const types = await openapiTS(mySchema, {
transform(schemaObject, metadata) {
if (schemaObject.format === "date-time") {
return schemaObject.nullable
? ts.factory.createUnionTypeNode([DATE, NULL])
: DATE;
return schemaObject.nullable ? `${DATE} | ${NULL}` : DATE;
}
},
});
Expand Down Expand Up @@ -169,19 +163,14 @@ Body_file_upload:

```ts [src/my-project.ts]
import openapiTS from "openapi-typescript";
import ts from "typescript";

const BLOB = ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("Blob")
); // `Blob`
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`
const BLOB = "Blob"; // `Blob`
const NULL = "null"; // `null`

const ast = await openapiTS(mySchema, {
const types = await openapiTS(mySchema, {
transform(schemaObject, metadata) {
if (schemaObject.format === "binary") {
return schemaObject.nullable
? ts.factory.createUnionTypeNode([BLOB, NULL])
: BLOB;
return schemaObject.nullable ? `${BLOB} | ${NULL}` : BLOB;
}
},
});
Expand Down Expand Up @@ -224,20 +213,15 @@ Body_file_upload:

```ts [src/my-project.ts]
import openapiTS from "openapi-typescript";
import ts from "typescript";

const BLOB = ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("Blob")
); // `Blob`
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`
const BLOB = "Blob"; // `Blob`
const NULL = "null"; // `null`

const ast = await openapiTS(mySchema, {
const types = await openapiTS(mySchema, {
transform(schemaObject, metadata) {
if (schemaObject.format === "binary") {
return {
schema: schemaObject.nullable
? ts.factory.createUnionTypeNode([BLOB, NULL])
: BLOB,
schema: schemaObject.nullable ? `${BLOB} | ${NULL}` : BLOB,
questionToken: true,
};
}
Expand All @@ -261,3 +245,26 @@ file?: Blob | null; // [!code ++]
スキーマ内の任意の[Schema Object](https://spec.openapis.org/oas/latest.html#schema-object)は、このフォーマッタを通じて処理されます(リモートのものも含まれます!)。また、追加のコンテキストが役立つ場合があるので、`metadata` パラメータも必ず確認してください。

`format`のチェック以外にも、これを利用する方法は多数あります。この関数は **string** を返す必要があるため、任意のTypeScriptコード(独自のカスタム型も含む)を生成することができます。


### transformProperty

`transformProperty()` はプロパティの型変換後に実行され、`{ name, optional, readonly, type, comment?, indent }` オブジェクトを受け取ります。変更したオブジェクトを返すか、変更しない場合は `undefined` を返してください。`name` は必要に応じて引用符が付いた名前、`type` は型の文字列です。`readonly` を変更すると、スキーマや `immutable` オプションによるデフォルトを上書きできます。

```ts
import openapiTS, { tsComment } from "openapi-typescript";

const types = await openapiTS(mySchema, {
transformProperty(property, schemaObject) {
if (schemaObject.format === "date-time") {
return {
...property,
readonly: true,
comment: tsComment(["@custom timestamp"], property.indent),
};
}
},
});
```

`tsComment()` で作成したコメントの後に、スキーマ由来のJSDocが追加されます。このフックは Schema Object のプロパティと `$defs` に適用されます。
Loading
Loading