
TypeSpec Versioning 装饰器全解析用 typespec/versioning 声明版本化 API【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespecTypeSpec 的typespec/versioning库通过一组声明式装饰器added、removed、renamedFrom、madeOptional、useDependency、versioned等来描述 API 在多个版本之间的演进历史让同一份 TypeSpec 源码同时表达当前状态与历史变更。本文以该库官方参考文档website/src/content/docs/docs/libraries/versioning/reference/decorators.md为骨架逐一讲解全部 9 个装饰器的签名、适用目标、参数语义与实战示例并结合仓库源码packages/versioning/src/decorators.ts、packages/versioning/src/versioning.ts揭示其底层实现原理。读完本文你将能够为自己的服务声明多版本 API、跟踪属性与类型的演进并让 OpenAPI 等 emitter 按版本输出正确契约。装饰器概览typespec/versioning导出的所有装饰器都位于TypeSpec.Versioning命名空间下。在使用前需安装依赖并引入# 在 spec 项目中安装 npm install typespec/versioningimport typespec/versioning;9 个装饰器按职责可分为三类装饰器作用适用目标versioned声明命名空间由哪个枚举定义版本NamespaceuseDependency声明对已版本化依赖库的版本选择EnumMember \| Namespaceadded/removed声明目标在哪个版本被添加 / 移除模型、属性、操作、枚举、联合、标量、接口等renamedFrom声明目标在哪个版本被重命名同上madeOptional/madeRequired声明属性在哪个版本变为可选 / 必选ModelPropertytypeChangedFrom/returnTypeChangedFrom声明属性类型 / 操作返回类型在哪个版本变更ModelProperty/Operation从源码看每个装饰器的实现都遵循同一模式先通过checkIsVersion校验传入的EnumMember是否属于某个已versioned的枚举packages/versioning/src/decorators.ts再将结果写入program.stateMap。校验失败时会抛出version-not-found诊断其消息为The provided version ... from ... is not declared as a version enum. Use versioned(...) on the containing namespace.见 packages/versioning/src/lib.ts。这意味着所有版本装饰器的第一个参数必须来自versioned声明的版本枚举。声明版本体系versionedversioned是版本化的起点它把一个命名空间与一个描述版本序列的枚举绑定TypeSpec.Versioning.versioned(versions: Enum)目标Namespace参数名称类型说明versionsEnum描述受支持版本的枚举示例versioned(Versions) namespace MyService; enum Versions { v1, v2, v3, }底层实现中$versioned会为该命名空间构建一个VersionMap其中每个枚举成员被包装为Version对象含name、value、enumMember、index、namespace字段并存入program.stateMap(VersioningStateKeys.versions)packages/versioning/src/decorators.ts。index按枚举声明顺序从 0 递增是后续版本先后比较的基准枚举成员解析出的值必须唯一否则触发version-duplicate诊断packages/versioning/src/lib.ts。在实战中versioned通常与service配合使用。官方教程给出了更完整的形态website/src/content/docs/docs/libraries/versioning/guide.mdservice(#{ title: Contoso Widget Manager }) versioned(Contoso.WidgetManager.Versions) namespace Contoso.WidgetManager; enum Versions { v1, v2, }声明库依赖版本useDependency当你的服务依赖另一个已版本化的 TypeSpec 库例如 Azure.Core时useDependency用来声明用哪个版本的库TypeSpec.Versioning.useDependency(...versionRecords: EnumMember[])目标EnumMember | Namespace参数名称类型说明versionRecordsEnumMember[]目标命名空间或版本所依赖的库版本可多个未版本化服务声明在命名空间上useDependency(MyLib.Versions.v1_1) namespace NonVersionedService;此时整个服务固定使用MyLib的v1_1版本。版本化服务声明在版本枚举成员上versioned(Versions) namespace MyService1; enum Version { useDependency(MyLib.Versions.v1_1) // V1 use lib v1_1 v1, useDependency(MyLib.Versions.v1_1) // V2 use lib v1_1 v2, useDependency(MyLib.Versions.v2) // V3 use lib v2 v3, }这样即可建立服务版本 → 依赖库版本的映射关系v1/v2 对应MyLib的v1_1v3 起升级到v2。从实现看$useDependency对Namespace与EnumMember两类目标分别写入useDependencyNamespace与useDependencyEnum两个 stateMappackages/versioning/src/decorators.ts。需要注意的是useDependency只能用在未版本化的命名空间上对于已versioned的命名空间必须放在版本枚举成员上否则会触发incompatible-versioned-namespace-use-dependency错误packages/versioning/src/lib.ts。依赖解析发生在resolveVersions/resolveDependencyVersions中以根命名空间的每个版本为起点沿getVersionDependencies得到的依赖图逐层解析出每个依赖命名空间应使用的具体版本最终产出VersionResolution[]packages/versioning/src/versioning.ts。emitter 正是基于这份解析结果按版本输出契约。添加与移除added 与 removedadded标识目标在哪个版本被添加TypeSpec.Versioning.added(version: EnumMember)目标Model | ModelProperty | Operation | Enum | EnumMember | Union | UnionVariant | Scalar | Interface参数名称类型说明versionEnumMember目标被添加的版本示例added(Versions.v2) op addedInV2(): void; added(Versions.v2) model AlsoAddedInV2 {} model Foo { name: string; added(Versions.v3) addedInV3: string; }removed标识目标在哪个版本被移除签名、目标与参数结构同added对称TypeSpec.Versioning.removed(version: EnumMember)目标Model | ModelProperty | Operation | Enum | EnumMember | Union | UnionVariant | Scalar | Interface参数名称类型说明versionEnumMember目标被移除的版本示例removed(Versions.v2) op removedInV2(): void; removed(Versions.v2) model AlsoRemovedInV2 {} model Foo { name: string; removed(Versions.v3) removedInV3: string; }底层原理可用性状态机两个装饰器在实现上是镜像的$added把版本追加到addedOn状态数组$removed追加到removedOn状态数组并且每次追加后都按index升序排序保证版本记录有序packages/versioning/src/decorators.ts。真正计算某个类型在某个版本是否可用的是getAvailabilityMappackages/versioning/src/versioning.ts它把每个版本归入四种状态状态含义Unavailable该版本中目标尚不存在Added该版本中目标首次出现Available该版本中目标可用且不是首次出现Removed该版本起目标被移除计算时会结合父类型model / interface的添加与移除信息做隐式继承处理例如一个没有任何版本装饰器的类型会继承父类型的添加版本若某类型先被移除后被重新添加则在其添加版本之前还会继承父版本的可用性resolveWhenFirstAdded、resolveRemoved见 packages/versioning/src/versioning.ts。官方教程中的例子验证了这一点v3 中把name重命名为description并改为可选后v3 的 OpenAPI 输出description而 v1/v2 的 OpenAPI 仍输出name且为必选website/src/content/docs/docs/libraries/versioning/guide.md。重命名renamedFrom标识目标在哪个版本被重命名并保留旧名称TypeSpec.Versioning.renamedFrom(version: EnumMember, oldName: valueof string)目标Model | ModelProperty | Operation | Enum | EnumMember | Union | UnionVariant | Scalar | Interface参数名称类型说明versionEnumMember目标被重命名的版本oldNamevalueof string目标之前的名称示例renamedFrom(Versions.v2, oldName) op newName(): void;实现要点packages/versioning/src/decorators.tsoldName不能是空字符串否则触发invalid-renamed-from-value错误renamedFrom.oldName cannot be empty string.多个重命名记录按版本升序存入renamedFrom状态数组并通过getRenamedFrom/getRenamedFromVersions供查询若重命名后的名称与同版本已有属性冲突会触发renamed-duplicate-property错误。可选性变更madeOptional 与 madeRequiredmadeOptional标识属性在哪个版本变为可选TypeSpec.Versioning.madeOptional(version: EnumMember)目标ModelProperty参数名称类型说明versionEnumMember目标变为可选的版本示例model Foo { name: string; madeOptional(Versions.v2) nickname?: string; }madeRequired标识属性在哪个版本变为必选TypeSpec.Versioning.madeRequired(version: EnumMember)目标ModelProperty参数名称类型说明versionEnumMember目标变为必选的版本示例model Foo { name: string; madeRequired(Versions.v2) nickname: string; }两个装饰器的实现都是把版本直接写入madeOptional/madeRequired两个 stateMappackages/versioning/src/decorators.ts。同时校验器会检查声明与声明结果的一致性packages/versioning/src/lib.ts被madeOptional标记的属性在当前代码里必须是可选的name?写法否则报made-optional-not-optional被madeRequired标记的属性在当前代码里必须是必选的否则报made-required-optional。这与版本化库的核心理念一致TypeSpec 源码永远表达 API 的当前状态装饰器只是记录这个状态是从哪个版本开始生效的。类型变更typeChangedFrom 与 returnTypeChangedFromtypeChangedFrom声明模型属性的类型从某个版本开始变更同时保持更早版本使用旧类型TypeSpec.Versioning.typeChangedFrom(version: EnumMember, oldType: unknown)目标ModelProperty参数名称类型说明versionEnumMember类型变更生效的版本从该版本起使用新类型更早版本使用旧类型oldTypeunknown指定版本之前使用的旧类型示例model Foo { // In v1: id is a string // In v2: id is an int32 typeChangedFrom(Versions.v2, string) id: int32; }returnTypeChangedFrom声明操作的返回类型从某个版本开始变更更早版本保持旧返回类型TypeSpec.Versioning.returnTypeChangedFrom(version: EnumMember, oldType: unknown)目标Operation参数名称类型说明versionEnumMember返回类型变更生效的版本从该版本起使用新返回类型更早版本使用旧返回类型oldTypeunknown指定版本之前使用的旧返回类型示例// In v1: returns a string // In v2: returns an int32 returnTypeChangedFrom(Versions.v2, string) op getUserId(): int32;实现层面两者都把(版本 → 旧类型)的映射写入各自 stateMap并按版本index排序packages/versioning/src/decorators.ts。查询函数getTypeChangedFrom与getReturnTypeChangedFrom返回该映射getAvailabilityMap在计算可用性时也会读取它们作为版本信息存在的依据packages/versioning/src/versioning.ts。组合使用一个完整的版本化示例将上述装饰器组合起来即可表达真实世界的 API 演进。以下改编自官方教程website/src/content/docs/docs/libraries/versioning/guide.mdusing TypeSpec.Versioning; using TypeSpec.Http; using TypeSpec.Rest; service(#{ title: Contoso Widget Manager }) versioned(Contoso.WidgetManager.Versions) namespace Contoso.WidgetManager; enum Versions { v1, v2, // v2 新增 get 操作 v3, // v3 重命名并可选化 description } model Widget { key id: string; // v3 起由 name 重命名为 description并变为可选 renamedFrom(Versions.v3, name) madeOptional(Versions.v3) description?: string; } route(/widget) op list(): Widget[] | Error; // v2 才引入的操作 added(Versions.v2) route(/widget/{id}) op get(...Resource.KeysOfWidget): Widget | Error;这段代码生成 v3 的 OpenAPI 时Widget包含id必选与description可选而 v1/v2 的 OpenAPI 中仍然是name必选。装饰器越多历史版本的信息越完整这正是 emitter 能够按版本输出不同契约的基础。相关文档与源码索引官方参考文档website/src/content/docs/docs/libraries/versioning/reference/decorators.md入门教程含 OpenAPI 输出对比website/src/content/docs/docs/libraries/versioning/guide.md库概览与安装website/src/content/docs/docs/libraries/versioning/reference/index.mdx装饰器 TypeSpec 声明packages/versioning/lib/decorators.tsp装饰器实现stateMap 读写与校验packages/versioning/src/decorators.ts版本可用性计算Availability 状态机packages/versioning/src/versioning.ts诊断信息定义错误码与消息模板packages/versioning/src/lib.ts版本时间线建模packages/versioning/src/versioning-timeline.ts版本解析结果类型定义packages/versioning/src/types.ts【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考