
使用 Scalar AsyncAPI Upgrader 将 AsyncAPI 文档一键升级至最新版本【免费下载链接】scalarScalar is an open-source API platform: Modern REST API Client Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar本文围绕 scalar/asyncapi-upgrader 的核心能力展开该包可将 AsyncAPI 1.x 文档依次升级为 2.6、3.0 直至最新的 3.1 版本。它面向需要长期维护 AsyncAPI 描述文件的团队读者读完本文后将掌握该包的完整升级管线、每一步的底层结构变换规则servers / channels / operations / security / messages $ref 重写以及如何用一行代码完成文档迁移并验证结果。一、为什么需要 AsyncAPI 升级器AsyncAPI 规范自 1.x 演进到 3.1结构变化非常大topics变成了channels、servers从数组变成了键值映射、publish/subscribe被提升为顶层operations、server 的url被拆分为host与pathname、消息引用从 components 作用域迁移到 channel 作用域……如果只把文档顶部的asyncapi版本字段改成3.1.0旧文档的其余结构并不符合新版规范解析器与渲染器无法正确处理。这正是 scalar/asyncapi-upgrader 出现的原因。根据其 0.1.0 版本的变更记录CHANGELOG.md该包在初版就实现了三项关键能力将 AsyncAPI1.x 文档真正升级到 2.x处理 servers、channels、parameters、stream、events而不仅是改动版本字符串将 AsyncAPI2.x 文档真正升级到 3.0处理 servers、channels、operations、security/OAuth而不仅是改动版本字符串在3.0 → 3.1升级时将 operation 的messages$ref 从#/components/messages/X重写为 3.1 明确要求的 channel 作用域形式#/channels/{id}/messages/X。后续版本0.1.3–0.1.9仅涉及 README 元数据修正与 npm trusted publishing 重新发布无功能性变化因此 0.1.0 确立的升级能力就是该包的功能全貌。二、整体升级管线一次调用三段迁移包的入口暴露了唯一的upgrade函数入口文件实际实现在 src/upgrade.tsimport type { UnknownObject } from scalar/types/utils import { upgradeFromOneToTwo } from ./1.2-to-2.6 import { upgradeFromTwoToThree } from ./2.6-to-3.0 import { upgradeFromThreeToThreeOne } from ./3.0-to-3.1 /** * Upgrade an AsyncAPI document to the latest version. * 每一步将文档迁移过一个主版本应用该版本要求的结构变换 * 例如 2.x → 3.0 会把 channel 的 publish/subscribe 提升为顶层 operations 映射 * 并把 server 的 url 拆分为 host/pathname与 scalar/openapi-upgrader 的思路一致。 */ export function upgrade(value: UnknownObject): UnknownObject { // AsyncAPI 1.x - 2.6 const asyncapi26 upgradeFromOneToTwo(value) // AsyncAPI 2.x - 3.0 const asyncapi30 upgradeFromTwoToThree(asyncapi26) // AsyncAPI 3.0 - 3.1 return upgradeFromThreeToThreeOne(asyncapi30) }三段管线以流水线方式串联1.x → 2.6.0、2.6.0 → 3.0.0、3.0.0 → 3.1.0。每段升级器都会先检查输入文档的asyncapi版本前缀不匹配的输入会被原样返回不做任何修改因此传入 1.x 文档会完整走完三段最终输出 3.1.0传入 2.x 文档第一段直接放行从 2→3 开始执行传入 3.0 文档只执行最后一段传入 OpenAPI 文档或没有asyncapi字段的对象整体保持不变。这一行为在 src/upgrade.test.ts 中有完整测试覆盖1.2.0、2.6.0、3.0.0 三种输入最终都升级到3.1.0而{ openapi: 3.1.0 }会被原样返回null也不会报错。import { describe, expect, it } from vitest import { upgrade } from ./upgrade describe(upgrade, () { it(upgrades an AsyncAPI 1.x document to 3.1.0, () { const document upgrade({ asyncapi: 1.2.0, info: {} }) expect(document.asyncapi).toBe(3.1.0) }) it(leaves documents without an asyncapi field untouched, () { const document { openapi: 3.1.0 } expect(upgrade(document)).toBe(document) }) })三、第一段1.x → 2.6 的结构变换实现在 src/1.2-to-2.6/upgrade-from-one-to-two.ts入口为upgradeFromOneToTwo。它会将asyncapi字段置为2.6.0并依次执行四个内部变换。3.1 servers数组 → 键值映射scheme → protocolAsyncAPI 1.x 的servers是数组且每个 server 用scheme/schemeVersion描述协议2.x 要求servers是「键 → Server Object」的映射协议字段改名为protocol/protocolVersion。变换逻辑upgrade-from-one-to-two.ts逐项处理每个 server把scheme改名为protocol、schemeVersion改名为protocolVersion其余字段原样保留然后用 server 的description做 slugify 生成映射键没有 description 时回退为server-{index}键冲突时自动追加-2、-3后缀去重。对应的测试用例upgrade-from-one-to-two.test.ts验证了三种典型输入// 输入 { asyncapi: 1.2.0, servers: [ { url: api.example.com, scheme: mqtt, description: Production }, { url: staging.example.com, scheme: mqtt, description: Staging }, ], } // 输出 { servers: { production: { url: api.example.com, protocol: mqtt, description: Production }, staging: { url: staging.example.com, protocol: mqtt, description: Staging }, }, }3.2 topics → channelspublish/subscribe 包一层 message1.x 用topics表示主题2.x 改为channels。变换upgrade-from-one-to-two.ts做了三件事将topics重命名为channels如果存在baseTopic把它作为前缀拼接到每个 channel 名上如baseTopic: smartylighting.streetlights.1.0 topicevent.lighting.measured→ channelsmartylighting.streetlights.1.0.event.lighting.measured随后删除baseTopic1.x 的publish/subscribe直接引用 message{ $ref: ... }或{ oneOf: [...] }2.x 要求包在{ message: ... }里因此变换会把publish: { $ref: ... }重写为publish: { message: { $ref: ... } }。测试用例upgrade-from-one-to-two.test.ts逐一验证了这些规则包括oneOf的包裹// 输入 topics: { user.signup: { publish: { $ref: #/components/messages/userSignedUp }, subscribe: { oneOf: [{ $ref: #/components/messages/userConfirmation }] }, }, } // 输出 channels: { user.signup: { publish: { message: { $ref: #/components/messages/userSignedUp } }, subscribe: { message: { oneOf: [{ $ref: #/components/messages/userConfirmation }] } }, }, }3.3 parameters数组 → 映射1.x 的 channel 参数是[{ name, ... }]数组2.x 要求{ name: { ... } }映射。变换upgrade-from-one-to-two.ts以name为键重组$ref类型的参数则直接取引用路径最后一段作为键。3.4 stream / events → 根 channel/1.x 文档根部的stream与events对象会被合并进channels[/]upgrade-from-one-to-two.tsstream.read应用读取的消息→channels[/].subscribe.message.oneOfstream.write应用写入的消息→channels[/].publish.message.oneOfevents.receive→subscribeevents.send→publish同样用oneOf包裹原stream/events对象被删除。stream中的framing信息在 2.x 没有对应字段会被丢弃。由于 AsyncAPI 1.x 规定topics、stream、events在文档根层互斥合法的输入不会在/channel 上产生冲突源码注释对此有专门说明。四、第二段2.x → 3.0 的结构变换实现在 src/2.6-to-3.0/upgrade-from-two-to-three.ts入口为upgradeFromTwoToThree。这是三段中变换最重的一段servers、OAuth scopes、channels 与 operations 全部要重写且 OAuth 变换必须先于 security 需求变换执行。4.1 serversurl → host / pathnameAsyncAPI 3.0 中 server 不再用url字段而是用host承载主机名可含端口可选pathname承载路径协议信息只保留在protocol字段中。变换upgrade-from-two-to-three.ts先剥掉 URL 的 scheme如mqtt://再把剩余部分按第一个/拆成host与pathnamemqtt://broker.example.com:1883/mqtt → host: broker.example.com:1883, pathname: /mqtt代码注释明确解释协议已经在protocol字段中独占承载若再保留在host上会造成信息重复、违反规范。4.2 OAuthscopes → availableScopes3.0 将 OAuth 流中的scopes字段改名为availableScopes。变换upgrade-from-two-to-three.ts遍历components.securitySchemes中所有type: oauth2方案的每个 flow把scopes重命名为availableScopes。这段必须最先执行因为后面的 security 需求变换4.4要读取重命名后的 scheme。4.3 channels operationspublish/subscribe 提升为顶层 operations这是 2.x → 3.0 最核心的变化upgrade-from-two-to-three.ts每个 channel 的键由 channel 路径 slugify 而来user/{id}/signedup→user-id-signedup见 slugifyChannelPath冲突时追加-2、-3去重channel 对象新增address字段保存原始路径messages字段收纳该 channel 的全部消息键取消息的 $ref 末段或name匿名消息回退为message-0、message-1……原来的publish/subscribe被从 channel 中剥离提升为顶层operations映射。注意语义翻转2.x 的publish应用接收消息映射为 3.0 的action: receive2.x 的subscribe应用发送消息映射为 3.0 的action: send源码注释对此有明确说明upgrade-from-two-to-three.tsoperation 的键优先复用 2.x 的operationId缺失时生成为{action}-{channelId}如receive-user-id-signedupoperation 通过channel: { $ref: #/channels/{channelId} }回指 channel其messages数组则重写为 channel 作用域引用#/channels/{channelId}/messages/{id}见 buildOperationchannel 级servers数组会重写为[{ $ref: #/servers/{name} }]引用形式。2.x 中一条典型的 channelchannels: user/signedup: publish: operationId: onUserSignedUp message: $ref: #/components/messages/userSignedUp升级后变为channels: user-signedup: address: user/signedup messages: userSignedUp: $ref: #/components/messages/userSignedUp operations: onUserSignedUp: action: receive channel: $ref: #/channels/user-signedup messages: - $ref: #/channels/user-signedup/messages/userSignedUp4.4 security普通方案引用、OAuth 方案内联 scopes2.x 的 security 需求是「方案名 → scopes 数组」的映射3.0 要求要么是$ref引用要么是内联的 OAuth 方案加上scopes数组。变换upgrade-from-two-to-three.ts对每个需求取唯一的键名如果对应方案是oauth2且有 scopes输出{ ...scheme, scopes }内联展开其余方案一律输出{ $ref: #/components/securitySchemes/{name} }。channel 与 operation 上的security字段都会被套用这一规则。五、第三段3.0 → 3.1 的 messages $ref 规范化3.1 属于澄清型版本唯一的实质规则收紧是operation 的messages$ref必须使用 channel 作用域形式#/channels/{id}/messages/{name}。但 3.0 规范自己的示例用的是 components 作用域形式#/components/messages/{name}导致大量真实 3.0 文档都写着 components 形式。实现在 src/3.0-to-3.1/upgrade-from-three-to-three-one.ts将版本置为3.1.0后调用rewriteOperationMessageRefs对所有 operation含replyreply 未声明 channel 时沿用父 operation 的 channel执行 $ref 重写匹配#/components/messages/{name}形式的引用重写为#/channels/{channelId}/messages/{name}如果该 channel 的messages映射中还没有这条消息会自动注册一条指向原 components 消息的$refregisterChannelMessage重写后对messages列表做去重重复的$ref条目只保留第一处dedupeRefs。这段逻辑直接回应了 CHANGELOG 中 #9358 的描述将 operationmessages的 $ref 从#/components/messages/X重写为#/channels/{id}/messages/X。六、安装、使用与验证安装包以 ESM 形式发布要求 Node.js 22package.jsonnpm install scalar/asyncapi-upgrader # 或 pnpm add scalar/asyncapi-upgrader基本使用import { upgrade } from scalar/asyncapi-upgrader import fs from node:fs const source JSON.parse(fs.readFileSync(./asyncapi.json, utf-8)) const upgraded upgrade(source) fs.writeFileSync(./asyncapi-3.1.json, JSON.stringify(upgraded, null, 2)) console.log(升级完成新版本, upgraded.asyncapi)整个升级过程原地修改传入对象并返回它函数签名是upgrade(value: UnknownObject): UnknownObject。如果你的文档还没有解析成对象需要先用 AsyncAPI 解析器如asyncapi/parser把 YAML 转成 JSON 对象再传入。直接使用单一升级步骤除统一入口外包还通过子路径导出三个独立升级器package.json适合只做单段迁移的场景import { upgradeFromOneToTwo } from scalar/asyncapi-upgrader/1.2-to-2.6 import { upgradeFromTwoToThree } from scalar/asyncapi-upgrader/2.6-to-3.0 import { upgradeFromThreeToThreeOne } from scalar/asyncapi-upgrader/3.0-to-3.1运行测试仓库内为每个升级步骤都配备了 vitest 测试每一条变换规则对应一个测试用例源码注释明确说明测试是规则的「source of truth」pnpm --filter scalar/asyncapi-upgrader test三个测试文件分别覆盖1.2-to-2.6 测试servers 数组转映射、scheme/protocol 改名、topics 转 channels、baseTopic 前缀拼接、publish/subscribe 包裹 message、parameters 数组转映射、stream/events 合并2.6-to-3.0 测试url 拆分 host/pathname、OAuth scopes 改名、channels/operations 提升、security 引用转换3.0-to-3.1 测试messages $ref 从 components 作用域重写为 channel 作用域。七、注意事项与边界输入必须是合法对象upgrade接受任意对象非 1.x/2.x/3.0 的输入会原样返回null也能安全处理见 upgrade.test.ts。原地修改升级器直接修改传入对象如需保留原始文档请先深拷贝。信息丢失是规范演进的结果1.x 的stream.framing在 2.x 中没有对应字段会被丢弃2.x 的url中的协议部分在 3.0 中不再保留在 host 上。这些不是升级器的缺陷而是 AsyncAPI 规范本身的取舍。版本边界upgrade只保证把文档升级到当前最新 3.1.0如果你的工具链仍依赖 2.x 或 3.0可以分别使用对应的子路径导出避免一次性跨版本带来的兼容性冲击。与 OpenAPI 升级器的关系从源码注释看src/upgrade.ts 明确说明该实现「镜像」了scalar/openapi-upgrader的分步升级思路。两者同为 Scalar 生态中负责规范文档版本迁移的工具本包专攻事件驱动的 AsyncAPI 文档。八、小结scalar/asyncapi-upgrader 用三段式管线解决了 AsyncAPI 跨主版本升级的完整问题1.x → 2.6 完成 servers/channels/parameters/stream/events 的基础重写2.6 → 3.0 完成 channels/operations/security/OAuth 的架构级提升3.0 → 3.1 完成 messages $ref 的规范化。每个版本号的递增都伴随着真实的结构变换而非字符串替换这一点由仓库内三组测试文件逐条验证。对于任何维护 AsyncAPI 文档超过一个主版本的团队这个包都能把「升级 AsyncAPI 文档到最新版本」这件事压缩成一行调用。【免费下载链接】scalarScalar is an open-source API platform: Modern REST API Client Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考