ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

openapi-typescript Node.js API 实战指南:程序化类型生成、transform 钩子扩展与源码管线解析

openapi-typescript Node.js API 实战指南:程序化类型生成、transform 钩子扩展与源码管线解析 开发工具代码生成后端【免费下载链接】openapi-typescriptGenerate TypeScript types from OpenAPI 3 specs项目地址https://gitcode.com/gh_mirrors/op/openapi-typescript点击查看免费下载本文基于 openapi-typescript 仓库中的 Node.js API 文档讲解在 Node 环境中以编程方式调用openapiTS()生成 TypeScript 类型的完整方案三种输入方式JSON 对象、本地文件、远程 URL的用法、Node API 独有选项transform/postTransform/inject/cwd等的语义与示例并结合仓库源码剖析openapiTS()从输入解析、Redocly 校验打包到 TypeScript AST 输出的完整管线帮助读者在构建脚本、代码生成工具或大型应用集成中灵活定制类型生成。Node.js API 的定位动态 schema 与应用内集成CLI 适合一个 schema 文件生成一个类型文件的静态场景而 Node API 面向的是两类更复杂的需求原文档定位动态生成的 schemaschema 在运行时才存在由上游服务返回、由配置拼装而成无法先落盘成文件更大应用的内部环节类型生成只是构建流水线中的一步需要拿到 AST 做二次加工、需要与其他库组合、需要自定义校验与转换逻辑。从源码看Node API 的入口函数就是 src/index.ts 中的默认导出openapiTS()// packages/openapi-typescript/src/index.ts#L44-L47 export default async function openapiTS( source: string | URL | OpenAPI3 | Buffer | Readable, options: OpenAPITSOptions {} as PartialOpenAPITSOptions, ): Promisets.Node[] {注意两点关键设计它是 async 函数因为远程 URL 拉取、Redocly 校验与打包都是异步过程返回值不是字符串而是ts.Node[]TypeScript AST 节点数组。你可以自由遍历、修改、裁剪 AST也可以原样打印成字符串落盘——这正是unlimited flexibility无限灵活性的来源。安装与环境准备npm i --save-dev openapi-typescript原文档推荐在package.json中声明type: module以启用 Node ESM 获得最佳体验。这与仓库自身的发布形态一致当前 package.json 中声明了type: modulemain指向./dist/index.mjs并同时在exports中提供require./dist/index.cjs与import./dist/index.mjs两种入口CJS 与 ESM 均可使用。另外注意peerDependencies中声明了typescript: ^5.x——如果你要使用transform等钩子直接操作 AST项目里需要有typescript包可用。三种输入方式openapiTS()接受JSON 对象、string本地文件路径或 URL、URL作为输入。原文档给出的三个示例import fs from node:fs; import openapiTS from openapi-typescript; // 示例 1加载 [object] 作为 schema仅 JSON const schema await fs.promises.readFile(spec.json, utf8); // 必须是 OpenAPI JSON const output await openapiTS(JSON.parse(schema)); // 示例 2加载 [string] 作为本地文件YAML 或 JSONv4.0 起支持 const localPath new URL(./spec.yaml, import.meta.url); // 可以是 YAML 或 JSON const output await openapiTS(localPath); // 示例 3加载 [string] 作为远程 URLYAML 或 JSONv4.0 起支持 const output await openapiTS(https://myurl.com/v1/openapi.yaml);对应到源码输入的分发逻辑集中在 src/lib/redoc.ts 的parseSchema()L29–L83输入形态源码分支处理方式URL实例schema instanceof URL交给 Redocly 的resolver.resolveDocument()拉取解析Readable流schema instanceof Readable收集为完整字符串后再递归解析Bufferschema instanceof Buffer转成 utf8 字符串后再解析string以http(s):///file://开头URL 分支构造URL后按文件/远程加载YAML 或 JSONstring首字符为{JSON 分支用parse-json按内存 JSON 处理string其他内容YAML 分支走makeDocumentFromString()按 YAML 解析object非数组对象分支直接作为内存中的 OpenAPI 文档其他—抛出Expected string, object, or Buffer错误两点版本差异值得注意6.x 文档提示Node.js API 不支持内联 YAML 字符串需借助 js-yaml 自行转成 JSON但通过 URL 加载 YAML 依然支持。从当前仓库代码看这一限制已放宽parseSchema()会把非路径、非 JSON 开头的字符串直接按 YAML 解析且 test/node-api.test.ts 中存在input string YAML测试用例验证了这一行为6.x 文档的示例 2 以字符串文件路径传入当前版本中URL实例new URL(./spec.yaml, import.meta.url)是更推荐的本文件写法测试套件中input URL local、input URL remote、input object、input buffer等用例覆盖了各类输入形态可作为行为基线参考。返回值TypeScript AST 与 astToString()openapiTS()返回Promisets.Node[]。拿到 AST 后通常用仓库导出的astToString()助手将其打印为字符串——从源码看src/lib/ts.ts 中它是一个基于 TypeScript Compiler API 的薄封装内部通过ts.createSourceFile()挂载节点数组再用ts.createPrinter()输出因此打印效果与 TS 官方格式化一致保留注释、统一换行。一个典型的生成并落盘流程import fs from node:fs; import openapiTS, { astToString } from openapi-typescript; const ast await openapiTS(new URL(./my-schema.yaml, import.meta.url)); const contents astToString(ast); fs.writeFileSync(./my-schema.ts, contents);这也是 CLI 的内部做法bin/cli.js 中一行核心逻辑即return ${COMMENT_HEADER}${astToString(await openapiTS(schema, config))}——先调用 Node API 生成 AST再拼上文件头注释、打印成字符串写入输出文件。换句话说CLI 就是 Node API 之上的一层薄封装。关于文件头默认注释常量COMMENT_HEADER定义在 src/index.ts#L29-L34This file was auto-generated by openapi-typescript. Do not make direct changes to the file.。需要说明的是6.x 文档将commentHeader列为 Node API 可覆盖的选项而在当前仓库版本中Node API 返回的是纯 AST文件头由调用方如 CLI自行拼接openapiTS()本身不强制附带头部注释——程序化使用时你完全可以自己决定文件开头写什么。Node API 选项总览原文档说明Node API 支持全部 CLI 选项的camelCase形式完整 CLI 参数表见 CLI 文档另外还提供若干 Node 独有选项。6.x 文档列出的 Node 独享选项NameTypeDefaultDescriptioncommentHeaderstring覆盖默认的 This file was auto-generated … 文件头注释injectstring向文件开头注入任意 TypeScript 类型transformFunction在特定场景下覆盖默认的 Schema Object → TypeScript 转换器postTransformFunction同transform但在 TypeScript 转换之后运行cwdstring \| URL可选提供当前工作目录用于解析远程$ref仅内存 JSON 对象场景需要当前仓库版本中全部可选项的类型定义集中在 src/types.ts 的OpenAPITSOptions接口openapiTS()在 src/index.ts#L69-L101 中将它们收敛为GlobalContext对象并逐项设定默认值。几个值得留意的默认值与 CLI 文档对应alphabetize/arrayLength/enum/exportType/immutable/pathParamsAsTypes/rootTypes等布尔开关默认均为falsedefaultNonNullable默认为true带default值的属性不视为可空silent默认为false设为true可抑制告警输出源码注释标明necessary for STDOUT即生成到标准输出时需要它避免日志污染输出当前版本还新增了transformProperty逐条修改属性签名、redocly直接传入 Redocly 配置见下、makePathsEnum、generatePathParams、readWriteMarkers等选项均为 6.x 文档时期之后的扩展。Redocly 配置校验与打包的底层openapiTS()内部使用 Redoclyredocly/openapi-core完成 schema 的拉取、校验与$ref打包。从 src/index.ts#L52-L61 看如果你没有提供redocly选项它会创建一个默认配置const redoc options.redocly ?? (await createConfig( { rules: { operation-operationId-unique: { severity: error }, // operationID 重复直接报错 }, }, { extends: [minimal] }, ));即默认继承minimal规则集并把operation-operationId-unique提升为 error因为重复的 operationId 会导致生成的operations映射出现键冲突。当前版本允许通过redocly选项传入用redocly/openapi-core的createConfig()/loadConfig()构造的完整配置以自定义 lint 规则与远程解析行为。transform / postTransform定制类型转换这是 Node API 最有价值的两个钩子。原文档的表述用transform()和postTransform()覆盖默认的 Schema Object 转换器为 schema 中非标准的部分提供定制transform()在转换为 TypeScript 之前运行此时你操作的是原始 OpenAPI 节点postTransform()在转换之后运行此时你操作的是 TypeScript AST。示例 1Date类型假设 schema 中有这样的属性properties: updated_at: type: string format: date-time默认情况下 openapiTS 会生成updated_at?: string;因为format本身是非标准、可任意定义的库无法确定你想要的目标类型。用transform可以增强它6.x 文档写法返回字符串const types openapiTS(mySchema, { transform(schemaObject, metadata): string { if (format in schemaObject schemaObject.format date-time) { return schemaObject.nullable ? Date | null : Date; } }, });效果- updated_at?: string; updated_at?: Date;需要指出一个版本演进在当前仓库版本中OpenAPITSOptions[transform]的签名src/types.ts#L641要求返回ts.TypeNode或直接构造的 AST 节点或{ schema, questionToken }对象而不是字符串。test/node-api.test.ts 中的options transform用例展示了当前版本的对应写法import ts from typescript; const DATE ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(Date)); const ast await openapiTS(mySchema, { transform(schemaObject) { if (format in schemaObject schemaObject.format date-time) { return DATE; // 返回 AST 节点 } }, });测试同时断言了生成结果中包含Date: Date;并保留/** Format: date-time */注释与 6.x 文档描述的语义一致只是产出字符串变成了产出 AST 节点——这也与返回值是 AST的整体设计更自洽。示例 2Blob类型文件上传另一类常见定制是文件上传请求体是multipart/form-data其中某些字段是Blob。示例 schemaBody_file_upload: type: object; properties: file: type: string; format: binary;用同样的模式转换6.x 文档写法const types openapiTS(mySchema, { transform(schemaObject, metadata): string { if (format in schemaObject schemaObject.format binary) { return schemaObject.nullable ? Blob | null : Blob; } }, });结果 diff- file?: string; file?: Blob;对应地测试套件中有options transform with blob与options transform with optional blob property两个用例前者对format: binary返回BLOB节点断言生成content: { application/json: Blob }后者演示了给属性附加?可选标记的能力——返回{ schema: BLOB, questionToken: true }形式的TransformObject类型定义见 src/types.ts#L460-L463从而把必选属性改造成可选的file?: Blob;。这个{ schema, questionToken }返回形态是 6.x 文档中没有展开、但当前版本明确支持的特性。postTransform操作转换后的 ASTpostTransform(type, options)接收的是已经转换好的ts.TypeNode适合做基于路径/命名约定的重命名、包裹等。测试用例options postTransform中有两个典型用法postTransform(_type, options) { // 1) 基于路径判断把 Date schema 替换成自定义的 DateOrTime 类型 if (options.path?.includes(Date)) { return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(DateOrTime)); } // 2) 直接读取当前 schema 对象当前版本上 options.schema 已直接提供 // 把标记了 x-string-enum-to-set 的 string enum 包成 Set... const schema options.schema; // … }注意源码注释特别说明以前需要options.ctx.resolve(options.path)反查 schema现在options.schema已直接挂在参数上——这是当前版本对 6.x 时期 API 的一处简化。metadata 参数transform / postTransform 的上下文你的 schema 中出现的任何Schema Object包括远程 schema在转换为 TypeScript AST 节点之前都会经过transform转换之后都会经过postTransform。两个钩子的第二个参数6.x 文档称metadata携带有用的上下文对应当前版本 src/types.ts#L732-L736 的TransformNodeOptions接口属性说明metadata.path指向当前 schema 对象的$refURI 字符串如#/components/schemas/Usermetadata.schema正在被转换的 schema 对象本身在postTransform中提供metadata.ctxGlobalContext对象包含全部选项开关、discriminator 扫描结果以及一个resolve($ref)辅助函数可在转换过程中按$ref取回任意节点其中ctx.resolve来自 src/index.ts#L98-L100底层是 src/lib/utils.ts 的resolveRef()它按 JSON Pointer 逐段下钻遇到指向$ref的$ref会递归追踪并用visited列表防循环引用发现循环会告警并返回undefined。因为transform允许产出任意 TypeScript 代码甚至自定义类型除了判断format你还能基于path、description、x-*扩展字段做几乎任意的定制——测试中的x-string-enum-to-set扩展键正是这类玩法的实例。inject向文件头部注入类型inject选项类型string用于把任意 TypeScript 声明注入到生成文件的开头。从源码看openapiTS()在构造GlobalContext时直接透传该值src/index.ts#L92最终的 AST 组装函数 src/transform/index.ts#L38-L41 会先用stringToAST(ctx.inject)把这段字符串解析为 AST 节点并优先于paths/webhooks/components/$defs四大根节点压入输出if (ctx.inject) { const injectNodes stringToAST(ctx.inject) as ts.Node[]; type.push(...injectNodes); }典型用途是预声明工具类型例如export type NullableT T | null;供后续transform产出的类型引用。stringToAST的实现src/lib/ts.ts#L273-L281就是ts.createSourceFile(...).statements前提是注入的内容必须是合法的 TypeScript。cwd为内存 schema 解析远程 $refcwdstring | URL用于帮助解析相对/远程$ref。在 src/index.ts#L65 中它会被规范化为file://URL 传给 Redocly 的 resolver当输入是 URL 时则以该 URL 本身为基准src/lib/redoc.ts#L114-L117。因此文档特别注明这个选项主要服务于内存中的 JSON 对象输入——没有文件路径可以充当基准时你才需要显式告诉它以哪个目录为根去解析相对引用。源码纵深openapiTS() 的完整管线把前面的碎片拼起来一次openapiTS(source, options)调用在当前仓库中经历四个阶段均在 src/index.ts#L44-L108 中可见输入解析parseSchema按前述分发表把URL/ 字符串 / 对象 /Buffer/Readable统一解析成 Redocly 的Documentsrc/lib/redoc.ts#L29-L83校验与打包validateAndBundle在 src/lib/redoc.ts#L107-L163 中依次完成——版本门禁openapi版本必须是3.x 3或 4抛错遇到swagger字段则提示 Unsupported Swagger version: 2.xlint调用 Redocly 的lintDocument()severity 为error的问题会被_processProblems()聚合成异常抛出warn级别的仅打印告警silent: true时静默bundle调用bundle({ dereference: false, ... })把多文件$ref打平为单一 schema但保留引用结构不展开后续生成时靠ctx.resolve()惰性取用构建 GlobalContext把全部选项收敛为带默认值的GlobalContext并顺带执行scanDiscriminators()src/lib/utils.ts#L230-L365——两遍遍历 schema为discriminator oneOf结构自动补写判别枚举属性这是生成联合类型可辨识性的底层支撑AST 转换transformSchemasrc/transform/index.ts#L29-L115 按paths→webhooks→components→$defs四个根节点分发给各自的transform*子模块空根节点输出export type xxx Recordstring, never;占位operations类型若未被生成则补一个空operationsinject内容排最前makePathsEnum启用时追加ApiPaths枚举。整个过程有performance.now()计时设置DEBUGopenapi-ts环境变量可通过debug()src/lib/utils.ts#L68-L85查看各阶段耗时与进度。测试套件中的行为基线如果想用可执行的文档来核对上述行为test/node-api.test.ts 是最好的参照。它按describe(Node.js API)组织覆盖输入形态input string YAML、input string JSON、input string URL远程、input URL remote、input URL local、input object、input buffer——每种输入都断言生成相同结构的paths / webhooks / components / $defs / operations五个导出块选项行为exportTypeinterfacevstype、pathParamsAsTypes静态路径键 vs[path: /user/${string}]模板字面量键等钩子行为transformDate、Blob、questionToken三个变体、postTransformDateOrTime重命名、Set...包装、transformProperty基于minLength/pattern/minimum/maximum/format生成minLength 1、pattern ...等 JSDoc 校验注解返回undefined时属性保持不变枚举行为enum选项生成真正的 TSenum含x-enum-varnames/x-enum-descriptions对枚举成员命名与注释的支持enumValues生成元组形式的值数组。其中transformProperty的 JSDoc 注解用例options transformProperty JSDoc validation annotations产出的/** minLength 1 \n * pattern ^[a-zA-Z0-9]$ */效果展示了在 Node API 中把 schema 校验约束翻译为类型层元信息的完整链路。小结与延伸阅读Node API 的核心心智模型是输入多态 输出 AST输入可以是对象、文件、URL、Buffer、流输出的ts.Node[]交给astToString()落盘或交给你的代码继续加工选项层面CLI 的每个 flag 都有camelCase对应物Node 独有的transform/postTransform/transformProperty/inject/cwd/silent则覆盖了改类型、改属性、注类型、控输出四类定制需求遇到行为疑问时优先查 test/node-api.test.ts行为基线与 src/types.ts选项与上下文类型定义再深入 src/lib/redoc.ts解析/校验/打包与 src/transform/index.tsAST 组装。原文档Node.js API 文档CLI 参数完整表CLI 文档。注本文以仓库中 6.x 版本文档为主体展开凡与当前仓库源码v7.x存在 API 演进差异之处均已注明并以源码与测试为准。赞分享开发工具代码生成后端【免费下载链接】openapi-typescriptGenerate TypeScript types from OpenAPI 3 specs项目地址https://gitcode.com/gh_mirrors/op/openapi-typescript点击查看免费下载相关推荐Oh-My-Posh终极路径配置指南彻底解决命令失效与主题加载难题Oh My Posh终极路径配置指南彻底解决命令失效与主题加载难题 Oh My Posh作为最灵活、低延迟的跨平台终端提示符渲染器其强大的主题定制功能深受开CLI开发工具KubeSphere 中的 OpenAPI 定义生成kube-openapi 代码生成器标记、扩展与自定义类型实战KubeSphere 中的 OpenAPI 定义生成kube openapi 代码生成器标记、扩展与自定义类型实战 本篇文章以 KubeSphere 仓库 v云原生容器编排后端微服务多集群DevOps可观测性AI 技能openapi-typescript从OpenAPI规范生成TypeScript类型的终极指南openapi typescript从OpenAPI规范生成TypeScript类型的终极指南 本文深入探讨了openapi typescript工具如何将O开发工具代码生成后端上一篇快速上手RxRelay5分钟掌握三种Relay的核心用法下一篇ConsistentID用户界面开发基于Gradio构建交互式生成工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表