Strapi @strapi/openapi 扩展实战:为新装配层级添加 Context Factory
2026/9/5 16:57:42 网站建设 项目流程

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 可以确认四个现有层级及其上下文类型:

装配层级输出数据形状上下文类型对应 Factory
DocumentPartial<OpenAPIV3_1.Document>DocumentContextDocumentContextFactory
PathPartial<OpenAPIV3_1.PathsObject>PathContextPathContextFactory
PathItemPartial<OpenAPIV3_1.PathItemObject>PathItemContextPathItemContextFactory
OperationPartial<OpenAPIV3_1.OperationObject>OperationContextOperationContextFactory

这四个上下文类型集中定义在 src/types.ts,四个工厂类则位于 src/context/factories/。每个组合装配器(如OperationAssembler)在向下装配时,都会用自己层级的 Context Factory 为下一级创建独立的 typed context——这正是本文要讲解的扩展点。整体流水线可参考 Architecture 文档,各扩展点的分工总览见 Contributing Overview。

什么时候需要新增一个 Context Factory

官方文档给出的判断准则是:

  • 引入一个带有独立输出形状(output shape)的新装配层级时,才需要新增对应的工厂类。每个 assembler 层级都运行在由匹配工厂创建的 typed context 上(DocumentContextOperationContext等)。
  • 只是在某个现有层级上添加叶子装配器(leaf assembler)时,直接复用现有工厂即可,例如操作级装配器统一复用OperationContextFactory

这个准则与源码结构一致:现有四层的工厂类(以 OperationContextFactory 为例)都只泛型绑定了各自的 OpenAPI 数据片段,装配器与工厂一一对应。只有当你想在DocumentPath之间(或树的其他位置)插入一个产出全新 OpenAPI 结构片段的层级时,才需要照此模式新增一套「类型 + 工厂」。

Context 类型体系:各字段的作用

新增工厂前必须先理解 src/context/types.ts 中的三个核心类型:

export interface ContextOutput<T> { data: T; // 本层级装配产出的 OpenAPI 数据片段 stats: Stats; // 耗时统计,由 Timer 填充 } export interface Context<T = unknown> { routes: Core.Route[]; // 待文档化的全部路由(必填) strapi: Core.Strapi; // Strapi 实例(必填) timer: Timer; // 计时器,可来自父级 registries: ContextRegistries; // 共享注册表,可来自父级 output: ContextOutput<T>; } export type PartialContext<T> = Partial<Pick<Context<T>, 'timer' | 'registries'>> & Required<Pick<Context<T>, 'strapi' | 'routes'>>; export interface ContextFactory<T> { create(context: PartialContext<T>, defaultValue: T): Context<T>; }

要点:

  • PartialContext<T>通过Required/Partial精确约束了工厂入参:strapiroutes必填的,只有timerregistries两个字段允许缺省——缺省正是「从父级共享或新建」的开关。
  • Context<T>的泛型参数T决定该层级output.data的形状,这也是官方文档强调「新装配层级需要自己的 output shape」的类型学依据。
  • Stats/TimeStats(L8-L16)持有startTimeendTimeelapsedTime,由 Timer 提供:start()在已启动时抛错、stop()在未启动时抛错、reset()清零,状态机式的约束保证了每个 context 的计时语义清晰。
  • ContextRegistries定义为ReturnType<RegistriesFactory['createAll']>,详见下文 Registries 一节。

AbstractContextFactory:所有工厂的公共基类

新增工厂时你并不直接实现ContextFactory<T>接口,而是继承 AbstractContextFactory。官方文档对AbstractContextFactory.create()行为的描述,在源码中可以逐条印证:

public create(context: PartialContext<T>, defaultValue: T): Context<T> { 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 }; }
  • strapiroutes必填,直接从入参取用;
  • timerregistries遵循「父级提供则复用、否则新建」的短路逻辑(L20-L21),注释也明确说明这是为了子装配器(sub-assemblers)共享同一 timer 与 registries;
  • output.data初始化为传给super.create()defaultValue,由createDefaultOutput()同时生成全零的stats(L29-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: Record<string, unknown> }>; export type WidgetContext = Context<WidgetContextData>;

对照现有实现,这里的模式完全一致:DocumentContextData = Partial<OpenAPIV3_1.Document>PathItemContextData = Partial<OpenAPIV3_1.PathItemObject>等(L4-L14)。数据片段通常取openapi-types中对应结构对象的Partial形态,context 别名则通过Context<T>绑定该片段。

第 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 AbstractContextFactory<WidgetContextData> { constructor( registriesFactory: RegistriesFactory = new RegistriesFactory(), timerFactory: TimerFactory = new TimerFactory() ) { super(registriesFactory, timerFactory); } create(context: PartialContext<WidgetContextData>): 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, });

仓库中存在两种等价的实际写法,可作为参考:

  • 显式构造 initProps(PathItemAssembler):_createPathItemContext()里逐项把strapiregistriesroutestimer从父PathContext拷入PartialContext<PathItemContextData>,再调用create(initProps)
  • 解构透传(OperationAssembler):const { 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 }))。各层级的注入链(DocumentAssemblerFactoryPathAssemblerFactoryPathItemAssemblerFactoryOperationAssemblerFactory)展示了同一模式如何逐层传递,例如 PathItemAssemblerFactory._createOperationAssembler。

Registries:预留的共享装配状态

官方文档对 registries 的说明是:RegistriesFactory.createAll()目前返回空对象,ContextRegistries是空接口,registries 是为「未来的共享装配状态」(如去重后的 schema、跨装配器缓存)预留的扩展位,当前无需任何额外配置。

对照当前源码,情况与该描述基本一致,但已出现具体字段:RegistriesFactory.createAll() 返回{ extractedComponentSchemas: {} },即一个预留用于存放已抽取组件 schema 的空记录;相应地,ContextRegistries 是通过ReturnType<RegistriesFactory['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;
  • 工厂的复用语义由PartialContext<T>的类型约束保证(必填两字段、可共享两字段),计时与共享状态分别由TimerRegistries承载。

相关路径:

  • 官方指南: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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询