资讯动态

TypeSpec 可见性(Visibility)机制完全指南:用一套模型驱动多个 API 视图

发布时间:2026/9/19 2:31:02 来源:尧图企业网站定制
TypeSpec 可见性Visibility机制完全指南用一套模型驱动多个 API 视图【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec导读在 TypeSpec 中定义资源模型时同一个模型往往需要在创建请求、读取响应、更新请求等多个场景中以不同形态出现例如id只允许服务端生成、secretName只能写入不能回读。TypeSpec 的Visibility可见性语言特性正是为此而生——它允许你在一个模型上声明属性在不同上下文中的可见范围由编译器、HTTP 库与各 Emitter 根据操作自动推导出不同视图从而避免为每个场景重复维护一份几乎相同的模型定义。读完本文你将掌握可见性类visibility class、生命周期修饰符Lifecycle.Create/Read/Update/Delete/Query、visibility/removeVisibility/invisible三个核心装饰器、内置生命周期变换模板CreateT/ReadT/UpdateT/CreateOrUpdateT/DeleteT/QueryT、withVisibilityFilter过滤器以及自定义可见性类的完整用法并理解其底层实现原理。本文对应的官方语言基础文档位于 website/src/content/docs/docs/language-basics/visibility.md核心实现位于 packages/compiler/src/core/visibility/core.ts 与 packages/compiler/lib/std/visibility.tsp。基本概念在深入语法之前先厘清三个贯穿全文的核心概念可见性只作用于模型属性model properties。它决定了一个 Emitter 在某个上下文中是否应包含或排除某属性。注意它并不作用于模型本身也不作用于操作。可见性通过可见性类visibility class定义。可见性类本质上就是一个 TypeSpecenum枚举的成员就是可以施加到属性上的可见性修饰符modifiers / flags。任何enum都可以充当可见性类。可见性类拥有默认可见性default visibility。当属性没有显式设置可见性时会使用该可见性类的默认修饰符集合。在 TypeSpec 标准库中默认行为是所有修饰符全部开启即属性默认在所有上下文中可见。从源码看编译器为每个可见性类维护两个关键集合packages/compiler/src/core/visibility/core.ts 中注释所描述的每个可见性类有一个default modifier set未显式指定时使用每个属性有一个active modifier set分析时实际生效。属性粒度的修饰符以MapEnum, SetEnumMember的形式存储在全局可见性仓库visibility store中键是可见性类值是该类下激活的修饰符集合core.ts。生命周期可见性Lifecycle VisibilityTypeSpec 内置了一个标准可见性类即资源生命周期可见性resource lifecycle visibility。它用于声明属性在向 API 端点传递资源或从 API 端点读取资源时是否可见。典型场景某属性只能读、某属性只能写、某属性读写均可。model Example { /** * The unique identifier of this resource. * * The ID is automatically generated by the service, so it cannot be set when the resource is created or updated, * but the server will return it when the resource is read. */ visibility(Lifecycle.Read) id: string; /** * The name of this resource. * * The name can be set when the resource is created, but may not be changed. */ visibility(Lifecycle.Create, Lifecycle.Read) name: string; /** * The description of this resource. * * By default, properties are visible in all lifecycle phases, so this property * is present in all lifecycle phases. */ description: string; }上例中Example模型的每个属性都带有一个生命周期可见性声明指示 Emitter 在创建、更新或读取该资源时包含或排除对应属性id只有Lifecycle.Read——服务端生成创建/更新时不可提交但读取时返回name同时具备Lifecycle.Create与Lifecycle.Read——创建时可设置之后不可变更description未做任何声明——默认在所有生命周期阶段可见。TypeSpec 的 HTTP 库、OpenAPI Emitter 及其它标准功能正是利用Lifecycle可见性根据操作所属的生命周期阶段为同一个Example模型派生不同视图。生命周期可见性如何影响操作的输入与输出下面这个接口展示了生命周期可见性对每个操作输入输出类型的直接影响route(/example) interface Examples { /** * When an operation uses the POST verb, it uses the Create lifecycle visibility to determine which properties * are visible. */ post create(body example: Example): CreatedExample | Error; /** * When an operation uses the GET verb, it uses the Read lifecycle visibility to determine which properties * are visible. */ get read(path id: string): OkExample | Error; /** * When an operation uses the PATCH verb, it uses the Update lifecycle visibility to determine which properties * are visible. */ patch update(path id: string, body example: Example): OkExample | Error; }上述接口生成的 OpenAPIv3 规范如下paths: /example: post: parameters: [] responses: 200: content: application/json: schema: $ref: #/components/schemas/Example requestBody: required: true content: application/json: schema: $ref: #/components/schemas/Example /example/{id}: get: parameters: - name: id in: path required: true schema: type: string responses: 200: content: application/json: schema: $ref: #/components/schemas/Example patch: parameters: - name: id in: path required: true schema: type: string responses: 200: content: application/json: schema: $ref: #/components/schemas/Example requestBody: required: true content: application/json: schema: $ref: #/components/schemas/ExampleUpdate components: schemas: Example: type: object required: - id - name - description properties: id: type: string readOnly: true name: type: string description: type: string ExampleUpdate: type: object properties: description: type: string请特别注意以下几点id属性被标记为readOnly: true因为它只在读取资源时可见从 OpenAPI 语义上讲是只读字段ExampleUpdate模式只包含description一个属性因为它是唯一在更新资源时可见的属性id不可提交、name更新时不可见每个paths条目都根据操作所属的生命周期阶段引用了正确的 schema响应统一引用ExamplePATCH 请求体引用ExampleUpdateTypeSpec 模型只定义了一次输出 schema 的所有差异完全由模型属性的生命周期可见性派生而来——这正是该特性的核心价值单一数据源多视图自动生成。从源码角度印证OpenAPI Emitter 在生成响应体内容时以Visibility.Read作为默认可见性packages/openapi3/src/openapi.ts并在属性被判定为只读时输出readOnly: truepackages/openapi3/src/schema-emitter.ts。判定只读的依据正是该属性是否仅具备Lifecycle.Read修饰符——可见性与 OpenAPI 的readOnly/writeOnly语义在此处完成了等价转换OpenAPI 转 TypeSpec 的转换器也做了反向映射见 packages/openapi3/src/cli/actions/convert/utils/decorators.ts。生命周期修饰符Lifecycle ModifiersLifecycle可见性类提供以下修饰符其标准库定义位于 packages/compiler/lib/std/visibility.tsp修饰符含义典型触发场景Lifecycle.Create资源被创建时该属性可见HTTPPOST操作的请求参数Lifecycle.Read资源被读取时该属性可见HTTPGET操作返回的响应体Lifecycle.Update资源被更新时该属性可见HTTPPATCH或PUT操作的请求参数Lifecycle.Delete资源被删除时该属性可见HTTPDELETE操作的请求参数Lifecycle.Query资源作为查询参数传递时该属性可见HTTPGET操作的请求参数注意不要与用query定义的 HTTP 查询参数混淆关于 HTTP 动词与可见性的默认映射可在 packages/http/src/metadata.ts 中找到精确对应关系GET/HEAD→QueryPOST→CreatePUT→Create | UpdatePATCH→UpdateDELETE→Delete。生命周期可见性变换Lifecycle Transforms除了让 HTTP 库与 Emitter 自动推导视图你还可以显式计算模型在某个特定生命周期阶段下的形态。TypeSpec 为此内置了六个模板CreateT extends Model递归生成T的副本仅保留Create阶段可见的属性ReadT extends Model递归生成T的副本仅保留Read阶段可见的属性UpdateT extends Model递归生成T的副本仅保留Update阶段可见的属性且属性的类型会被替换为CreateOrUpdateT变换CreateOrUpdateT递归生成T的副本仅保留具备Create或Update任一修饰符的属性DeleteT递归生成T的副本仅保留具备Lifecycle.Delete修饰符的属性QueryT递归生成T的副本仅保留具备Lifecycle.Query修饰符的属性。示例model Example { visibility(Lifecycle.Create) id: string; visibility(Lifecycle.Create, Lifecycle.Read) name: string; visibility(Lifecycle.Update) description: string; } model ReadExample is ReadExample; model CreateExample is CreateExample; model UpdateExample is UpdateExample; model CreateOrUpdateExample is CreateOrUpdateExample;使用这些模板后得到的模型不再带有任何Lifecycle可见性修饰符因此后续任何使用生命周期可见性的 Emitter 或库都不会再对它们做进一步变换——变换结果被视为最终形态。这一点在标准库实现中亦有体现UpdateT模板通过applyLifecycleUpdate内部函数实现其中嵌套属性使用Create或Update的any过滤器递归处理packages/compiler/lib/std/visibility.tsp 与 packages/compiler/src/lib/visibility.ts。注意从字符串可见性迁移文档中特别给出了一个兼容性警告本文描述的基于枚举的可见性取代了以往基于字符串的可见性写法。系统虽然向后兼容字符串形式例如visibility(create)但新规范应一律使用基于枚举的可见性字符串形式的可见性可能在 TypeSpec 未来版本中被弃用并移除。可见性修饰符Visibility Modifiers每个属性在每个可见性类下都维护着自己的一组激活修饰符active visibility modifiers可以通过本节的装饰器修改。一个关键原则修改某个可见性类的可见性不会影响其他可见性类——例如你改变了Lifecycle类的可见性绝不会影响任何其他可见性类下激活的修饰符。从实现上看编译器将可见性操作封装为三类底层原语packages/compiler/src/core/visibility/core.tsaddVisibilityModifiers向激活集合添加修饰符、removeVisibilityModifiers从激活集合删除修饰符、clearVisibilityModifiersForClass清空某类下的激活集合。三个装饰器分别对应这三类操作。visibility启用可见性修饰符visibility接受一个可见性修饰符列表并将其**设置启用**到属性上visibility(Lifecycle.Create, Lifecycle.Read) name: string;此例中name属性启用了Create和Read两个修饰符。关键行为叠加而非替换。如果属性上已经显式设置了可见性visibility会把自身的修饰符添加到当前激活集合中而不会替换已有修饰符。例如visibility(Lifecycle.Create) visibility(Lifecycle.Read) name: string;此时name同时具备Create和Read修饰符但没有Update修饰符。visibility从一个空集合出发先加入Create再加入Read对应源码中的addVisibilityModifiers行为core.ts。removeVisibility禁用可见性修饰符removeVisibility接受修饰符列表并将其从属性上移除removeVisibility(Lifecycle.Update) name: string;这个用法与上面的visibility示例在效果上等价——都是让name只具备Create与Read。区别在于实现路径removeVisibility从该可见性类的默认集合出发默认集合是全部五个修饰符然后移除Update对应源码中的removeVisibilityModifiers行为core.ts。如果属性已经显式设置过可见性removeVisibility同样是在当前激活集合上做减法而非替换removeVisibility(Lifecycle.Update) removeVisibility(Lifecycle.Create) id: string;此例中id移除了Update和Create但保留了Read修饰符。invisible禁用某可见性类下的全部修饰符invisible接收一个可见性类作为参数将该类下属性的所有可见性修饰符一次性禁用invisible(Lifecycle) invisible: string;此时invisible属性在Lifecycle可见性类下没有任何激活的修饰符也就是说它在任何生命周期阶段都不可见。对应源码为clearVisibilityModifiersForClasscore.ts。可见性过滤器Visibility FilterswithVisibilityFilter装饰器通过应用一个可见性过滤器来变换模型。过滤器是一个对象它定义了一个属性要被保留所必须满足的修饰符约束支持三个键语义与 JavaScript 的every/some对应all属性必须同时具备列出的全部修饰符若为空集合则视为满足any属性必须至少具备其中一个修饰符若为空集合则永远不满足none属性必须不具备列出的任何修饰符若为空集合则始终满足。示例model Example { visibility(Lifecycle.Create) id: string; visibility(Lifecycle.Create, Lifecycle.Read) name: string; visibility(Lifecycle.Update) description: string; } withVisibilityFilter(#{ all: [Lifecycle.Create, Lifecycle.Read] }) model CreateAndReadExample { ...Example; } withVisibilityFilter(#{ any: [Lifecycle.Create, Lifecycle.Update] }) model CreateOrUpdateExample { ...Example; } withVisibilityFilter(#{ none: [Lifecycle.Update] }) model NonUpdateExample { ...Example; }结果分析CreateAndReadExample是Example的副本只保留同时具备Create和Read的属性——即只有nameCreateOrUpdateExample只保留具备Create或Update任一的属性——即id和nameNonUpdateExample只保留不具备Update的属性——即id和name。实现层面过滤器判定逻辑位于isVisiblepackages/compiler/src/core/visibility/core.ts先校验all缺一即不可见再校验none命中任一即不可见最后才校验any命中任一即可见否则不可见。withVisibilityFilter的变换本身通过子图变换器mutator递归作用于模型及其嵌套/引用类型并会从变换结果中剥离visibility、removeVisibility、invisible等装饰器以及重置相关可见性类避免二次变换packages/compiler/src/lib/visibility.ts。使用建议对于Lifecycle可见性通常应优先使用Create、Read、Update、CreateOrUpdate等内置模板而不是直接使用withVisibilityFilterwithVisibilityFilter更适合创建使用非Lifecycle可见性类或自定义过滤逻辑的模型视图。另外需要注意在标准库中withVisibilityFilter已被标记为弃用#deprecated推荐使用等价的FilterVisibilityM, Filter, NameTemplate模板或Read/Create/Update等生命周期模板见 packages/compiler/lib/std/visibility.tsp。自定义可见性类Visibility Classes由于任何 TypeSpecenum都可以充当可见性类你可以完全自定义一套可见性体系。例如标准库中Lifecycle可见性类的最小定义如下enum Lifecycle { Create, Read, Update, }实际标准库定义了五个成员见 packages/compiler/lib/std/visibility.tsp。该可见性类定义了三个修饰符默认情况下所有属性在该类下同时启用全部三个修饰符。设置类的默认可见性defaultVisibility你可以用defaultVisibility装饰器在枚举上声明某个可见性类的默认可见性defaultVisibility(Example.A) enum Example { A, B, }此例中任何没有显式声明Example可见性修饰符的属性默认都具备A可见性即只具备A而不具备B因为默认集合被显式改成了{A}。实现层面defaultVisibility调用了setDefaultModifierSetForVisibilityClass该函数强制约束每个可见性类的默认修饰符集合只能设置一次且必须在任何操作使用该默认集合之前完成packages/compiler/src/core/visibility/core.ts。同时它会校验传入的修饰符必须是目标枚举的成员否则报错packages/compiler/src/lib/visibility.ts。自定义可见性类的使用边界一个重要的现实约束虽然你可以定义自己的可见性类但 Emitter 不会自动识别它们除非它们被显式编程支持。你可以在自己的 Emitter 中利用自定义可见性类但对标准 Emitter 而言除非这些 Emitter 选择采纳并识别这些类为有意义否则它们不会产生任何效果。Lifecycle是标准可见性类被多个 Emitter 识别。不过你始终可以结合内置的withVisibilityFilter装饰器使用自己的可见性类按需变换模型。从源码可以印证这一边界HTTP 库在把可见性过滤器翻译成内部Visibility位标志时会显式检查modifierConstraint.enum ! Lifecycle并跳过非Lifecycle类的修饰符packages/http/src/metadata.tsOpenAPI Emitter 的响应体内容默认以Visibility.Read为基准生成packages/openapi3/src/openapi.ts。可见标准生态只对Lifecycle类赋予了语义。底层机制速览可见性系统如何运转为了让你对本文所有特性形成闭环认知这里把编译器侧的实现要点汇总如下全部来自 packages/compiler/src/core/visibility/core.ts可见性仓库visibility store以ModelProperty为键、MapEnum, SetEnumMember为值存储每属性每类的激活修饰符集合core.ts。默认集合惰性初始化若某属性在某类下从未被显式设置查询时返回该类默认集合默认集合初始化时取该枚举的全部成员core.ts这解释了默认全可见。密封机制sealing可见性可以被密封——针对程序program、属性或属性内的某个可见性类。一旦密封便不可解除任何修改尝试都会产生visibility-sealed诊断并被忽略core.ts。这在变换类场景如克隆属性后重置可见性中用于防止意外改动。操作级可见性parameterVisibility与returnTypeVisibility可以为操作单独声明参数与返回类型的可见性约束未声明时则由协议库提供默认VisibilityProviderHTTP 库的HttpVisibilityProvider即按动词映射见 packages/http/src/metadata.ts核心编译器以getParameterVisibilityFilter/getReturnTypeVisibilityFilter消费这些配置packages/compiler/src/lib/visibility.ts。变换结果无残留无论使用内置生命周期模板还是withVisibilityFilter变换产物上的可见性装饰器都会被清理或重置保证结果模型是稳定的最终形态。结语可见性是 TypeSpec 一处定义、多处视图 设计理念的集中体现通过在唯一的资源模型上声明Lifecycle修饰符HTTP 库与 OpenAPI Emitter 即可自动为创建、读取、更新、删除、查询等每个生命周期阶段推导出正确的输入/输出 schema同时 OpenAPI 的readOnly等语义也随之自动生成。当你需要更精细的裁剪时visibility/removeVisibility/invisible三个装饰器提供了原子级的修饰符增删六个内置生命周期模板提供了开箱即用的视图变换而withVisibilityFilter与自定义可见性类则为高级场景保留了完全的可扩展空间。建议新编写的规范统一采用本文的枚举式可见性写法以获得面向未来的兼容性。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价