Strapi @strapi/openapi 扩展实战:为新装配层级添加 Context Factory
Strapi strapi/openapi 扩展实战为新装配层级添加 Context Factory【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi本文基于 Strapi 官方贡献指南 Context Factory 编写讲解当strapi/openapi包的 OpenAPI 文档生成流水线需要引入一个新的装配assembly层级时如何按官方模式定义上下文数据类型、创建 Context Factory 并将其接入组合装配器。读完后你将能够理解 Context /AbstractContextFactory在生成流水线中的职责独立走完「定义类型 → 建工厂 → 导出 → 在组合装配器中使用」的完整四步流程并掌握 timer 与 registries 在父子上下文之间共享的机制。背景Context Factory 在生成流水线中的位置strapi/openapi采用「路由收集 → 上下文初始化 → 装配 → 后处理」的流水线生成 OpenAPI 3.1 文档。OpenAPIGenerator 的generate()方法按固定顺序执行收集路由并用DocumentContextFactory创建顶层DocumentContext、启动计时器_bootstrap、依次运行 pre-processors、逐个运行 document 级 assemblers、运行 post-processors最后由_finalize()停止计时器并把耗时写入output.stats.time。装配器assembler按 OpenAPI 文档结构组织成一棵树从源码 assemblers/types.ts 可以确认四个现有层级及其上下文类型装配层级输出数据形状上下文类型对应 FactoryDocumentPartialOpenAPIV3_1.DocumentDocumentContextDocumentContextFactoryPathPartialOpenAPIV3_1.PathsObjectPathContextPathContextFactoryPathItemPartialOpenAPIV3_1.PathItemObjectPathItemContextPathItemContextFactoryOperationPartialOpenAPIV3_1.OperationObjectOperationContextOperationContextFactory这四个上下文类型集中定义在 src/types.ts四个工厂类则位于 src/context/factories/。每个组合装配器如OperationAssembler在向下装配时都会用自己层级的 Context Factory 为下一级创建独立的 typed context——这正是本文要讲解的扩展点。整体流水线可参考 Architecture 文档各扩展点的分工总览见 Contributing Overview。什么时候需要新增一个 Context Factory官方文档给出的判断准则是引入一个带有独立输出形状output shape的新装配层级时才需要新增对应的工厂类。每个 assembler 层级都运行在由匹配工厂创建的 typed context 上DocumentContext、OperationContext等。只是在某个现有层级上添加叶子装配器leaf assembler时直接复用现有工厂即可例如操作级装配器统一复用OperationContextFactory。这个准则与源码结构一致现有四层的工厂类以 OperationContextFactory 为例都只泛型绑定了各自的 OpenAPI 数据片段装配器与工厂一一对应。只有当你想在Document与Path之间或树的其他位置插入一个产出全新 OpenAPI 结构片段的层级时才需要照此模式新增一套「类型 工厂」。Context 类型体系各字段的作用新增工厂前必须先理解 src/context/types.ts 中的三个核心类型export interface ContextOutputT { data: T; // 本层级装配产出的 OpenAPI 数据片段 stats: Stats; // 耗时统计由 Timer 填充 } export interface ContextT unknown { routes: Core.Route[]; // 待文档化的全部路由必填 strapi: Core.Strapi; // Strapi 实例必填 timer: Timer; // 计时器可来自父级 registries: ContextRegistries; // 共享注册表可来自父级 output: ContextOutputT; } export type PartialContextT PartialPickContextT, timer | registries RequiredPickContextT, strapi | routes; export interface ContextFactoryT { create(context: PartialContextT, defaultValue: T): ContextT; }要点PartialContextT通过Required/Partial精确约束了工厂入参strapi和routes是必填的只有timer和registries两个字段允许缺省——缺省正是「从父级共享或新建」的开关。ContextT的泛型参数T决定该层级output.data的形状这也是官方文档强调「新装配层级需要自己的 output shape」的类型学依据。Stats/TimeStatsL8-L16持有startTime、endTime、elapsedTime由 Timer 提供start()在已启动时抛错、stop()在未启动时抛错、reset()清零状态机式的约束保证了每个 context 的计时语义清晰。ContextRegistries定义为ReturnTypeRegistriesFactory[createAll]详见下文 Registries 一节。AbstractContextFactory所有工厂的公共基类新增工厂时你并不直接实现ContextFactoryT接口而是继承 AbstractContextFactory。官方文档对AbstractContextFactory.create()行为的描述在源码中可以逐条印证public create(context: PartialContextT, defaultValue: T): ContextT { const { strapi, routes } context; // Allow overrides to share registries and timer in case the context is used in sub-assemblers const timer context.timer ?? this._timerFactory.create(); const registries context.registries ?? this._registriesFactory.createAll(); // Default output initialized with the given default value const output this.createDefaultOutput(defaultValue); return { strapi, routes, timer, registries, output }; }strapi和routes必填直接从入参取用timer和registries遵循「父级提供则复用、否则新建」的短路逻辑L20-L21注释也明确说明这是为了子装配器sub-assemblers共享同一 timer 与 registriesoutput.data初始化为传给super.create()的defaultValue由createDefaultOutput()同时生成全零的statsL29-L34。两个依赖——RegistriesFactory与 TimerFactory——通过protected构造器注入因此子类工厂只需在构造器中super(registriesFactory, timerFactory)并保持默认参数new RegistriesFactory()/new TimerFactory()以便装配器可以零参实例化。四步走添加一个新的 Context Factory以下四步完整继承自官方指南并对照仓库中的真实实现加以说明。假设你需要一个名为Widget的新装配层级。第 1 步在src/types.ts中定义上下文数据类型在 src/types.ts 中按现有模式追加数据片段类型与 context 别名import type { Context } from ./context; export type WidgetContextData Partial{ widgets: Recordstring, unknown }; export type WidgetContext ContextWidgetContextData;对照现有实现这里的模式完全一致DocumentContextData PartialOpenAPIV3_1.Document、PathItemContextData PartialOpenAPIV3_1.PathItemObject等L4-L14。数据片段通常取openapi-types中对应结构对象的Partial形态context 别名则通过ContextT绑定该片段。第 2 步创建src/context/factories/widget.ts照 OperationContextFactory 的结构编写import { RegistriesFactory } from ../../registries; import type { WidgetContext, WidgetContextData } from ../../types; import { TimerFactory } from ../../utils; import type { PartialContext } from ../types; import { AbstractContextFactory } from ./abstract; export class WidgetContextFactory extends AbstractContextFactoryWidgetContextData { constructor( registriesFactory: RegistriesFactory new RegistriesFactory(), timerFactory: TimerFactory new TimerFactory() ) { super(registriesFactory, timerFactory); } create(context: PartialContextWidgetContextData): WidgetContext { return super.create(context, {}); } }两个关键细节类泛型是数据片段类型WidgetContextData而非WidgetContext基类用它推导output.data的形状create()的返回值类型再收窄为完整的WidgetContext与 DocumentContextFactory 的写法相同路径对应 src/context/factories/document.ts。super.create(context, {})中的{}是defaultValueoutput.data从空对象开始累积各叶子装配器随后向其中填充字段。OpenAPI 文档片段都适合以{}为初始值。第 3 步从src/context/factories/index.ts导出当前 factories/index.ts 导出了五个成员AbstractContextFactory加四个层级工厂export { AbstractContextFactory } from ./abstract; export { DocumentContextFactory } from ./document; export { OperationContextFactory } from ./operation; export { PathContextFactory } from ./path; export { PathItemContextFactory } from ./path-item;在其后追加一行即可export { WidgetContextFactory } from ./widget;第 4 步在组合装配器中使用组合装配器构造时接收自己的 Context Factory惯例上作为带默认值的构造参数如 OperationAssembler 的contextFactory: OperationContextFactory new OperationContextFactory()在assemble()中为子层级创建 context 时把父上下文的共享 props 透传下去使整棵装配树共享同一个 timer 与 registries。官方文档给出的显式写法const childContext this._contextFactory.create({ strapi: context.strapi, routes: context.routes, timer: context.timer, registries: context.registries, });仓库中存在两种等价的实际写法可作为参考显式构造 initPropsPathItemAssembler_createPathItemContext()里逐项把strapi、registries、routes、timer从父PathContext拷入PartialContextPathItemContextData再调用create(initProps)解构透传OperationAssemblerconst { output, ...defaultContextProps } context;剔除掉本层级的output后直接把剩余字段传给this._contextFactory.create(defaultContextProps)。注意两种写法都刻意排除了父级的output——子 context 的output.data必须从自己的defaultValue重新开始装配完成后组合装配器再把childContext.output.data合并回父级输出如 operation.ts L46-L52 中Object.assign(output.data, { [methodIndex]: operationObject })。各层级的注入链DocumentAssemblerFactory→PathAssemblerFactory→PathItemAssemblerFactory→OperationAssemblerFactory展示了同一模式如何逐层传递例如 PathItemAssemblerFactory._createOperationAssembler。Registries预留的共享装配状态官方文档对 registries 的说明是RegistriesFactory.createAll()目前返回空对象ContextRegistries是空接口registries 是为「未来的共享装配状态」如去重后的 schema、跨装配器缓存预留的扩展位当前无需任何额外配置。对照当前源码情况与该描述基本一致但已出现具体字段RegistriesFactory.createAll() 返回{ extractedComponentSchemas: {} }即一个预留用于存放已抽取组件 schema 的空记录相应地ContextRegistries 是通过ReturnTypeRegistriesFactory[createAll]推导出的结构类型而非字面空接口。从源码结构看「跨装配器共享去重 schema」这一官方设想已经在 registries 的数据结构中落地了入口只是当前装配流程尚未向其写入内容。实际结论不变新增 Context Factory 时无需为 registries 做任何特殊配置AbstractContextFactory会自动为顶层 context 创建一份并沿装配树向下共享。小结与延伸阅读新增 Context Factory 只发生在「引入新装配层级」时叶子装配器一律复用现有工厂四步流程为在 src/types.ts 定义*ContextData与*Context别名 → 在 src/context/factories/ 下继承AbstractContextFactory建工厂并默认初始化 output 为{}→ 更新 barrel 导出 → 在组合装配器中透传strapi/routes/timer/registries创建子 context工厂的复用语义由PartialContextT的类型约束保证必填两字段、可共享两字段计时与共享状态分别由Timer和Registries承载。相关路径官方指南Context Factory、Assemblers、Processors、Testing、Architecture核心源码AbstractContextFactory、Context 类型定义、Timer、RegistriesFactory、OpenAPIGenerator【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考