Medusa TSDoc 写作规范全解:writing-tsdocs 技能与 Medusa 自定义标签体系实践
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
本文基于 Medusa 仓库内置的writing-tsdocs技能文档(SKILL.md),系统讲解如何为 Medusa 的 TypeScript 源码补充 TypeDoc(TSDoc)注释:覆盖 HTTP 类型、API 路由、UI 组件、数据模型、服务接口、JS SDK、抽象 Provider、Workflow SDK、core-flows 工作流/步骤与事件常量等十类文件,并结合 tsdoc.json 中定义的 Medusa 自定义标签(@expandable、@featureFlag、@eventPayload等)与仓库中的真实使用证据,给出一套可直接落地的 TSDoc 写作方法论。
一、技能定位:writing-tsdocs 是什么
writing-tsdocs是 Medusa 仓库.claude/skills/目录下定义的一个 AI 技能(Skill),其职责是:为 Medusa 代码库中的 TypeScript 源文件添加和更新 TypeDoc 注释。技能描述明确列出了它适用的文件范围:
- HTTP 类型:packages/core/types/src/http
- API 路由:packages/medusa/src/api
- UI 组件:packages/design-system/ui/src/components
- 数据模型:packages/modules 下各模块的
src/models/ - 服务接口与类型:packages/core/types/src
- JS SDK:packages/core/js-sdk/src
- 抽象 Provider 与工作流工具:packages/core/utils/src
- Workflow SDK 组合函数:packages/core/workflows-sdk/src
- core-flows 工作流与步骤:packages/core/core-flows/src
- 事件常量:packages/core/utils/src/core-flows/events.ts
技能的工作方式是"主文档 + 参考文件":SKILL.md 规定总约束与快速参考,针对每一种文件类型,再加载reference/目录下对应的 7 份参考文档。
二、硬性约束:写错就是事故
技能文档用CRITICAL标注了六条不可违反的约束,违反任何一条都会产生"不正确或损坏的文档":
- 永远不要为未导出的项写文档—— 只处理
export的 interface、type、function、class; - 永远不要给测试文件加 TSDoc—— 跳过
*.spec.ts、*.test.ts和__tests__/目录; - 永远不要编造
@since版本号—— 只能使用提示词(prompt)中明确给出的版本; - 永远不要删除或修改已有 TSDoc—— 只在缺失处补充;
- 永远不要修改逻辑—— 只增改注释块;
- Medusa 自定义标签只能用 tsdoc.json 中定义的那些:
@expandable、@featureFlag、@since、@apiIgnore、@schema、@tags、@version、@keep、@customNamespace、@namespaceMember。
这些约束背后的工程考量:TSDoc 是 API 文档的"源头数据",文档生成工具会把它直接编译进对外发布的 API Reference。版本号错误、标签未注册、文档了内部实现,都会直接污染生成产物。
三、文件类型与参考文档的映射关系
技能规定:在写任何 TSDoc 之前,先加载该文件类型对应的参考文档。映射表如下:
| 路径模式 | 加载的参考文档 |
|---|---|
packages/core/types/src/http/ | reference/http-types.md |
packages/medusa/src/api/admin/或/store/ | reference/api-routes.md |
packages/design-system/ui/src/components/ | reference/ui-components.md |
packages/modules/*/src/models/ | reference/data-models.md |
packages/core/types/src/(非 http)、packages/core/js-sdk/src/、packages/core/utils/src/(abstract provider)、packages/core/workflows-sdk/src/utils/composer/ | reference/service-interfaces.md |
packages/core/core-flows/src/(workflows / steps) | reference/workflows-steps.md |
packages/core/utils/src/core-flows/events.ts | reference/events.md |
四、基础格式与各类型"文档深度矩阵"
4.1 TSDoc 块的基础格式
最基本的格式就是标准 JSDoc 块注释——先一句简要描述,再为每个属性单独写注释:
/** * Brief description. */ export interface Foo { /** * The foo's ID. */ id: string }4.2 按文件类型决定"写到多深"
技能用一张矩阵规定了十种文件类型各自的文档范围与关键标签。这是全文档体系中最核心的"深度控制"原则:并非所有文件都值得(也需要)完整 JSDoc:
| 文件类型 | 文档范围 | Medusa 关键标签 |
|---|---|---|
| HTTP 类型 | 每个导出的 interface/type + 全部属性 | 嵌套对象加@expandable |
| API 路由 | 仅导出的 handler(极简) | @featureFlag、@since |
| UI 组件 | 组件 + 全部 props | 纯描述 |
| 数据模型 | 模型 + 每个属性 | 新增项加@since |
| 服务接口 | 每个方法完整文档 | @param、@returns、@example |
| JS SDK | 每个公共方法 | @param、@returns、@example、@tags |
| Provider | 类 + 每个抽象方法 | @param、@returns、@example |
| Workflow SDK | 每个导出函数 | @param、@returns、@example |
| core-flows 工作流 | 工作流导出 + hooks | @summary、@featureFlag、@since |
| core-flows 步骤 | 步骤导出 + 输入类型 | @featureFlag、@since、@example |
| 事件 | 每个事件常量 | @eventPayload、@featureFlag、@since |
标准 TSDoc/JSDoc 标签(@param、@returns、@example、@deprecated、@remarks等)始终允许使用。
五、Medusa 自定义标签体系:以 tsdoc.json 为准
技能文档中提到的自定义标签,其权威定义位于 www/utils/packages/typedoc-config/tsdoc.json。该文件通过"extends": ["typedoc/tsdoc.json"]继承标准 TSDoc 标签集,再用tagDefinitions扩展 Medusa 专属标签。技能速查表中列出的十个标签及用途:
| 标签 | 语法种类 | 使用场景 |
|---|---|---|
@featureFlag <name> | block | 该导出需要开启某个 feature flag 才可用 |
@expandable | modifier | 该属性是可通过 API 查询展开的嵌套对象 |
@since <version> | block | 该导出在此版本新增(仅使用提示词给定的版本) |
@apiIgnore | modifier | 从 API 文档输出中排除 |
@tags <name> | block | SDK 方法分类 |
@schema | block | 自定义 schema 文档 |
@keep | modifier | 文档生成时保留该属性 |
@customNamespace | block | 分配到自定义文档命名空间 |
此外,从 tsdoc.json 的完整定义看,仓库中还注册了更多标签,供特定文档场景使用:
@eventPayload(block)——事件常量专用,描述事件负载结构;@eventName、@workflows、@workflowEvent、@workflowLock(block)——工作流/事件关联文档;@version(block)、@namespaceMember(modifier)、@parentIgnore(block)、@typeParamDefinition(block);@excludeExternal、@mainSignature、@docHideSignature(modifier)——控制继承属性与签名在生成文档中的呈现,前两者在 UI 组件文档中实际使用(见下文)。
这一点值得注意:技能文档的速查表是"写作白名单",tsdoc.json 才是"注册全集"。写作者应遵循更严格的那份清单,而读者可以依据 tsdoc.json 理解生成文档中出现的其它标签。
六、十类文件的写作细则
6.1 HTTP 类型:@expandable 与"可展开实体"的判断
HTTP 类型文件位于packages/core/types/src/http/,定义 Medusa REST API 的请求/响应形状。规则要点(详见 reference/http-types.md):
- 每个导出的
interface/type用一句描述;每个属性用对 API 消费者有意义的简短描述; - 对"另一个可通过
fields查询参数展开获取的 interface"类型的属性使用@expandable; - 联合/枚举语义字段用项目符号逐一解释取值;
- 不得对 HTTP 类型使用
@param、@returns、@since——它们是数据形状,不是方法。
@expandable的判断标准是这个标签的核心:被引用类型必须是"完整实体",可通过?fields=+xxx展开获取;标量与内嵌值对象(embedded value object)不加。参考文档给出的经典对比:
export interface AdminOrder { /** * The associated shipping methods. * @expandable */ shipping_methods?: AdminOrderShippingMethod[] /** * The associated customer. * @expandable */ customer?: AdminCustomer | null /** * The order's total amounts. */ summary: BaseOrderSummary // 内嵌值对象——不可独立获取,不加 @expandable }仓库中的实际使用情况印证了这一规范:在packages/core/types/src/http/下,@expandable已大量出现在 order/common.ts、cart/common.ts、payment/common.ts、collection/common.ts 等核心类型文件中,仅 order 一个文件就有多处使用。
对于语义明确的字符串状态字段,规范给出统一写法:
/** * The order's status: * - `pending` — the order is awaiting payment * - `completed` — the order has been fulfilled and paid * - `cancelled` — the order has been cancelled * - `archived` — the order has been archived */ status: OrderStatus而继承BaseFilterable的过滤器接口,则按"Filter by xxx."句式逐个说明过滤属性:
/** * The filters to apply when listing orders. */ export interface AdminOrderFilters extends BaseFilterable<AdminOrderFilters> { /** * Filter by order IDs. */ id?: string | string[] /** * Filter by order status. */ status?: OrderStatus | OrderStatus[] }参考文档还附了一个完整的 Before/After 示例(BaseOrderAdjustmentLine接口从裸字段到逐属性注释),可作为 HTTP 类型文档的样板:接口级一句 "The order adjustment line's details.",属性级明确id归属("The ID of the order this adjustment belongs to.")、created_at/updated_at用 "The date the adjustment was created/last updated." 的固定句式。
6.2 API 路由:极简主义与 Feature Flag 探测
API 路由文件位于packages/medusa/src/api/admin/与packages/medusa/src/api/store/,导出GET、POST、DELETE、PATCH等命名 handler。规则是反直觉但明确的"越少越好"(详见 reference/api-routes.md):
- 只给导出的 handler 函数加 TSDoc;
- 只允许
@featureFlag和/或@since,不写完整方法文档; - 若 handler 既无 feature flag 又不是新增的,则不加任何 TSDoc;
- 不加
@param、@returns、@example和描述。
Feature flag 的探测方法:查看 handler 函数体内是否调用FeatureFlag.isFeatureEnabled(...),@featureFlag的值取 flag 的 key 字符串(如IndexEngineFeatureFlag.key对应"index_engine");同时检查路由是否基于 flag 在路由注册配置或中间件中条件注册。
| 条件 | 添加内容 |
|---|---|
handler 使用FeatureFlag.isFeatureEnabled(XFlag.key) | @featureFlag <flag_key> |
| handler 是本次 diff 中的新增 | @since <version> |
| handler 是既有的且无 feature flag | 什么都不加 |
典型输出:
/** * @since 2.14.0 * @featureFlag view_configurations */ export const GET = async ( req: AuthenticatedMedusaRequest, res: MedusaResponse<HttpTypes.AdminViewConfigurationListResponse> ) => { /* ... */ }路由文件中偶尔导出的 validator 或 helper 不属于模块公共面,无需 TSDoc。
6.3 UI 组件:内联 prop 注释、Radix 与 @excludeExternal/@keep
UI 组件位于packages/design-system/ui/src/components/。规则(详见 reference/ui-components.md):
- 组件用一句描述;props 以内联注释写在解构参数列表中,而不是给
Propsinterface 单独写注释(除非该 interface 本身被导出); - 基于 Radix UI 的组件,在组件文档中指明所基于的 Radix 原语;纯透传 Radix props、无自定义属性的薄封装组件,只写组件级描述即可;
- 不给组件加
@param/@returns/@since(除非组件是新增且版本已给定)。
内联 prop 文档的正确/错误对照:
// 正确——内联 prop 文档 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ( { /** * The button's visual style. */ variant = "primary", /** * The button's size. */ size = "base", /** * Whether to render as the child element instead of a `button`. */ asChild = false, /** * Whether to show a loading spinner. */ isLoading = false, disabled, className, children, ...props }, ref ) => { /* ... */ } )对于Props继承自原生 HTML 元素类型(如React.ComponentPropsWithoutRef<"div">)的组件,继承来的className、children、style、id等大量属性不该出现在生成文档中。规范给出两级控制:
@excludeExternal:加在组件 TSDoc 块上,抑制所有继承的外部属性;@keep:若某个被继承的属性在本组件中有非平凡行为(如disabled触发自定义视觉状态、onInvalid接入了内部校验),在该 prop 的内联注释中加@keep(可附@defaultValue)使其保留;而className、style、id这类通用透传属性绝不@keep。
若Props只含自定义属性(没有 extends 外部类型),则可省略@excludeExternal。
6.4 数据模型:DML 实体与 @since 的"diff 纪律"
数据模型位于packages/modules/*/src/models/,使用 DML(Data Model Language)的model.define(...)定义实体。规则(详见 reference/data-models.md):
- 模型级
const用一句实体描述(完整句或简短名词短语均可); - 每个属性一句简短描述;
@since <version>只加在本次 diff 中新增(仅出现在新增行)的属性或模型上;- 字符串格式不直观的属性(locale 码、货币码、时区)用
@example说明; - 主键
id不写文档(约定俗成); - 关系属性统一句式:"The associated [related model]."
/** * A locale supported by a store. * @since 2.14.0 */ const StoreLocale = model.define("StoreLocale", { /** * The BCP 47 language tag code of the locale. * @example "en-US" */ locale_code: model.text().searchable(), /** * The associated store. */ store: model.belongsTo(() => Store, { mappedBy: "supported_locales" }).nullable(), })关系属性的三种标准句式:
/** The associated store. */ store: model.belongsTo(() => Store, { mappedBy: "supported_locales" }), /** The product's variants. */ variants: model.hasMany(() => ProductVariant, { mappedBy: "product" }), /** The variant's images. */ images: model.manyToMany(() => ProductImage, { mappedBy: "variants" }),反例清单同样明确:不要给主键id: model.id({ prefix: "pv" }).primaryKey()写文档,不要给本次提交之前就存在的属性补@since。
6.5 服务接口、JS SDK、Provider、Workflow SDK:同一套"完整 JSDoc"模式
这四类文件共用同一参考文档(reference/service-interfaces.md),都采用完整 JSDoc模式:
- 接口/类本身一句描述;
- 每个公共方法写:描述 +
@param+@returns+@example; - JS SDK 方法额外加
@tags <category>(与 SDK 模块分类对应,如@tags products); - 方法描述以 "This method [verb phrase]." 开头;
- 固定格式:
@param {Type} name - Description.、@returns {Promise<Type>} Description of resolved value.。
完整的 retrieve 方法样板:
/** * This method retrieves a product by its ID. * * @param {string} productId - The ID of the product to retrieve. * @param {FindConfig<ProductDTO>} config - The configurations determining how the product is retrieved. Its properties, such as `select` or `relations`, accept the attributes or relations associated with a product. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. * @returns {Promise<ProductDTO>} The retrieved product. * * @example * const product = await productModuleService.retrieveProduct("prod_123") */ retrieveProduct( productId: string, config?: FindConfig<ProductDTO>, sharedContext?: Context ): Promise<ProductDTO>几个值得注意的细节:
sharedContext参数有固定标准描述:"A context used to share resources, such as transaction manager, between the application and the module."——它几乎出现在所有模块服务方法上,统一措辞可避免文档漂移;- 列表方法的
@example应展示多个真实场景(按 ID 列表、带relations查询),多个场景用标签分段; - 抽象 Provider 方法的
@example以"继承该抽象类的实现类"形式给出,如class MyPaymentProvider extends AbstractPaymentProvider { async initiatePayment(input) { ... } }; - Workflow SDK 组合函数(如
createStep)也要为compensateFn这类可选参数用[compensateFn]方括号标注。
6.6 core-flows 工作流与步骤:@summary、hooks 与 step 输入类型
工作流与步骤文件位于packages/core/core-flows/src/,分别用createWorkflow(...)与createStep(...)创建(详见 reference/workflows-steps.md)。
工作流文档要求:
- 一段描述,说明工作流做什么、何时被使用,并链接到执行它的 API 路由(如适用);
@example展示如何调用.run();@summary一行摘要;- 每个
hooks.*钩子一条@property hooks.xxx条目; - 新增时加
@since,受 flag 控制时加@featureFlag。
export const myWorkflowId = "my-workflow" /** * This workflow [does X]. It's executed by the [Some API Route]. * * You can use this workflow within your own customizations or custom workflows, * allowing you to wrap custom logic around [the operation]. * * @example * const { result } = await myWorkflow(container) * .run({ input: { id: "foo_123" } }) * * @summary * * [One-line summary of what the workflow does.] * * @property hooks.myHook - This hook is called [when/before/after X]. You can * use it to [describe what customization is possible]. */ export const myWorkflow = createWorkflow(myWorkflowId, (input) => { /* ... */ })钩子文档的标准句式:"This hook is called before/after X. You can use it to ....",逐钩子一条:
* @property hooks.validate - This hook is called before all operations. You can * use it to validate the input or perform any custom validation logic. * * @property hooks.setPricingContext - This hook is called after the cart is * retrieved. You can use it to pass custom pricing context to the workflow.步骤文档要求:描述步骤做什么(含抛错/返回条件)、@example展示调用方式、@since/@featureFlag(如适用),并为步骤的输入类型 interface 逐属性写描述,且用{@link stepName}交叉引用关联步骤:
/** * The input for the {@link validateCartStep}. */ export interface ValidateCartStepInput { /** * The cart to validate. */ cart: CartWorkflowDTO | CartDTO }对仅受 flag/版本控制的简单步骤,允许只写"标签型"文档:
/** * @since 2.10.3 * @featureFlag view_configurations */ export const createViewConfigurationStep = createStep(/* ... */)明确不写文档的对象:*Id字符串常量(如export const addToCartWorkflowId = "add-to-cart")、模块内部未导出的 helper、以及createStep第三个参数(补偿函数——属于内部实现细节)。
6.7 事件常量:@eventPayload 负载结构规范
事件常量位于 packages/core/utils/src/core-flows/events.ts,按命名空间对象分组导出(如CartWorkflowEvents、OrderWorkflowEvents)。规则(详见 reference/events.md):
- 每个事件常量写"何时被发出"的描述("Emitted when [resource] is [action].");
- 必须包含
@eventPayload展示负载结构(TypeScript 代码块,每个属性带内联注释); - 事件在本次 diff 中新增时加
@since,且@since置于@eventPayload之前,使版本信息先出现; - 受 flag 控制时加
@featureFlag; - 命名空间对象本身一般不写文档,除非它确实缺描述且同文件其它命名空间已统一使用
@category+@customNamespace模式。
@eventPayload的一个精细规则:字符串 ID 不标注类型,非字符串值必须用(type)标注:
/** * Emitted when an order's fulfillment is created. * * @eventPayload * ```ts * { * order_id, // The ID of the order * fulfillment_id, // The ID of the fulfillment * no_notification, // (boolean) Whether to notify the customer * } * ``` */ FULFILLMENT_CREATED: "order.fulfillment_created",完整的 Before/After 示例(OrderWorkflowEvents三个事件从裸常量到带@eventPayload的注释)是事件文档的基准样板。
仓库实证:当前 events.ts 中已存在 83 处@eventPayload注释,说明该规范已在事件常量文档化中被系统性执行;而@featureFlag也已在packages/medusa/src/api/admin/下多条路由(如admin/index/details/route.ts、admin/locales/route.ts等)中落地。
七、常见错误清单(Common Mistakes)
技能文档最后给出了一份自查清单,覆盖五类高频错误:
- 为未导出或
private项写了文档; - 提示词未提供版本号时使用了
@since; - 属性描述超过 2 句话;
- 给非方法导出(interface、type)加了
@param/@returns; - 文档化了
id字段却没有指明它属于哪个资源。
这份清单与前述各参考文档的"What NOT to Document"章节互为呼应,可作为提交前的最后一道检查。
八、体系总结:一套"按类型分级"的文档工程方法
把 SKILL.md 与 7 份参考文档合起来看,Medusa 的 TSDoc 体系有三个可迁移到其它大型 TypeScript 项目的设计思想:
- 文档深度与公共面成正比:API 路由 handler 只标 flag/版本,HTTP 类型逐属性注释,服务接口才需要完整 JSDoc +
@example。文档预算跟着"消费者是谁"走,而不是对所有代码一刀切; - 自定义标签 = 文档生成器的可编程开关:
@expandable驱动前端字段展开、@featureFlag驱动文档按 flag 分组、@apiIgnore/@excludeExternal/@keep控制呈现粒度、@eventPayload把事件负载结构嵌入文档——标签不是装饰,而是文档构建流水线的一等输入,其注册必须集中在 tsdoc.json 单点管理; - 事实纪律:
@since只认提示词给定的版本、只给 diff 新增项打标签、不碰测试文件、不改逻辑——保证文档注释永远可以追溯到一个真实的变更来源。
对维护者而言,这套规范的最佳使用路径是:确认目标文件落在第三节的映射表中哪一行,加载对应参考文档,按第四节的深度矩阵确定文档范围,用第五节的标签表约束自定义标签,最后过一遍第七节的常见错误清单——即可获得与仓库现有 83 处@eventPayload、遍布 http 类型的@expandable同一水准的 TSDoc 输出。
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考