首页
/ Strapi @strapi/openapi 扩展实战:为新装配层级添加 Context Factory

Strapi @strapi/openapi 扩展实战:为新装配层级添加 Context Factory

2026-09-04 16:23:34作者:蔡丛锟

本文基于 Strapi 官方贡献指南 Context Factory 编写,讲解当 @strapi/openapi 包的 OpenAPI 文档生成流水线需要引入一个新的装配(assembly)层级时,如何按官方模式定义上下文数据类型、创建 Context Factory 并将其接入组合装配器。读完后你将能够理解 Context / AbstractContextFactory 在生成流水线中的职责,独立走完「定义类型 → 建工厂 → 导出 → 在组合装配器中使用」的完整四步流程,并掌握 timer 与 registries 在父子上下文之间共享的机制。

背景:Context Factory 在生成流水线中的位置

@strapi/openapi 采用「路由收集 → 上下文初始化 → 装配 → 后处理」的流水线生成 OpenAPI 3.1 文档。OpenAPIGeneratorgenerate() 方法按固定顺序执行:收集路由并用 DocumentContextFactory 创建顶层 DocumentContext、启动计时器(_bootstrap)、依次运行 pre-processors、逐个运行 document 级 assemblers、运行 post-processors,最后由 _finalize() 停止计时器并把耗时写入 output.stats.time

装配器(assembler)按 OpenAPI 文档结构组织成一棵树,从源码 assemblers/types.ts 可以确认四个现有层级及其上下文类型:

装配层级 输出数据形状 上下文类型 对应 Factory
Document Partial<OpenAPIV3_1.Document> DocumentContext DocumentContextFactory
Path Partial<OpenAPIV3_1.PathsObject> PathContext PathContextFactory
PathItem Partial<OpenAPIV3_1.PathItemObject> PathItemContext PathItemContextFactory
Operation Partial<OpenAPIV3_1.OperationObject> OperationContext OperationContextFactory

这四个上下文类型集中定义在 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 / TimeStatsL8-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() 同时生成全零的 statsL29-L34)。

两个依赖——RegistriesFactoryTimerFactory——通过 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(惯例上作为带默认值的构造参数,如 OperationAssemblercontextFactory: 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() 里逐项把 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-L52Object.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 承载。

相关路径:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341