Skip to content

@ahoo-wang/fetcher-generator

@ahoo-wang/fetcher-generator 包是一个 CLI 工具,用于读取 OpenAPI 3.x 规范(JSON、YAML 或 URL)并生成完全类型化的 TypeScript 代码——包括模型接口/枚举、基于装饰器的 API 客户端以及 Wow CQRS 特定的命令/查询客户端。基于 ts-morph 进行代码生成,使用 commander 进行 CLI 参数解析。

安装

bash
pnpm add -D @ahoo-wang/fetcher-generator

架构概览

mermaid
graph TB
    subgraph Input
        direction TB
        OA["OpenAPI 3.x Spec<br>(JSON / YAML / URL)"]
    end

    subgraph Pipeline["Code Generation Pipeline"]
        direction TB
        PARSE["parseOpenAPI()"]
        RESOLVE["AggregateResolver<br>extracts bounded contexts"]
        MODEL["ModelGenerator<br>TS interfaces & enums"]
        CLIENT["ClientGenerator"]
        API["ApiClientGenerator<br>REST API clients"]
        CMD["CommandClientGenerator<br>Wow command clients"]
        QRY["QueryClientGenerator<br>Wow query clients"]
        INDEX["Index Generator<br>barrel exports"]
        OPT["Optimize<br>format + imports"]
    end

    subgraph Output
        direction TB
        TYPES["**/types.ts<br>interfaces, enums, type aliases"]
        APICLIENT["*ApiClient.ts<br>decorator-based REST clients"]
        CMDCLIENT["commandClient.ts<br>CommandClient + StreamCommandClient"]
        QRYCLIENT["queryClient.ts<br>QueryClientFactory"]
        INDICES["**/index.ts<br>barrel exports"]
    end

    OA --> PARSE
    PARSE --> RESOLVE
    RESOLVE --> MODEL
    RESOLVE --> CLIENT
    MODEL --> TYPES
    CLIENT --> API
    CLIENT --> CMD
    CLIENT --> QRY
    API --> APICLIENT
    CMD --> CMDCLIENT
    QRY --> QRYCLIENT
    MODEL --> INDEX
    API --> INDEX
    CMD --> INDEX
    QRY --> INDEX
    INDEX --> INDICES
    INDICES --> OPT

    style Input fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Pipeline fill:#161b22,stroke:#30363d,color:#e6edf3
    style Output fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

CLI 使用方式

fetcher-generator 二进制程序读取 OpenAPI 规范文件并将 TypeScript 源文件写入输出目录。

命令

命令描述
generate从 OpenAPI 规范生成 TypeScript 代码
-v, --version显示版本信息

Generate 选项

选项简写必填默认值描述
--input <file>-i--OpenAPI 规范文件路径或 URL
--output <path>-osrc/generated输出目录
--config <file>-c./fetcher-generator.config.json配置文件路径
--ts-config-file-path <file>-t--TypeScript 配置文件

CLI 命令示例

bash
# 从本地 YAML 规范生成
npx fetcher-generator generate -i ./openapi.yaml -o ./src/generated

# 从远程 URL 生成
npx fetcher-generator generate -i https://api.example.com/v3/api-docs -o ./src/generated

# 使用自定义配置和 tsconfig 生成
npx fetcher-generator generate -i ./spec.json -o ./src/api -c ./generator.config.json -t ./tsconfig.json

来源: packages/generator/src/cli.ts

配置

生成器会读取一个可选的 JSON 配置文件(默认为 ./fetcher-generator.config.json):

typescript
// GeneratorConfiguration
{
  "apiClients": {
    "TagName": {
      "ignorePathParameters": ["tenantId", "ownerId"]
    }
  }
}

默认情况下,生成 API 客户端方法时会忽略 tenantIdownerId 路径参数,因为它们由 CoSec 资源归属拦截器 处理。

来源: packages/generator/src/types.ts:73-87

代码生成流水线

步骤 1 -- 解析 OpenAPI 规范

CodeGenerator 将输入规范(JSON、YAML 或 URL)解析为类型化的 OpenAPI 对象。支持文件路径和 HTTP/HTTPS URL。

来源: packages/generator/src/index.ts:45-142

步骤 2 -- 解析限界上下文聚合

AggregateResolver 遍历所有操作以提取 Wow 限界上下文聚合定义。它识别以下内容:

  • 命令 -- 响应模式包含 wow.CommandOk 的操作
  • 状态 -- 快照状态查询操作(.snapshot_state.single
  • 事件 -- 事件流列表查询(.event.list_query
  • 字段 -- 快照计数操作(.snapshot.count
mermaid
sequenceDiagram
autonumber

    participant AR as AggregateResolver
    participant OPS as Operations
    participant AGG as AggregateDefinition

    AR->>OPS: extractOperationEndpoints(paths)
    loop For each operation
        AR->>AR: commands(path, op)
        AR->>AR: state(op)
        AR->>AR: events(op)
        AR->>AR: fields(op)
    end
    AR->>AGG: resolve() -> BoundedContextAggregates
    Note over AR,AGG: Maps contextAlias -> Set of AggregateDefinition

来源: packages/generator/src/aggregate/aggregateResolver.ts:52-289

OpenAPI 规范约定

为使生成器正确识别 Wow 模式,你的 OpenAPI 规范必须遵循以下约定:

operationId 后缀

生成器通过 operationId 后缀识别操作类型:

操作类型必需后缀生成的客户端
状态快照.snapshot_state.singleSnapshotQueryClient
事件查询.event.list_queryEventStreamQueryClient
计数查询.snapshot.countQueryClientFactory(字段 schema)
命令(无需后缀)CommandClient
yaml
# 示例:shop.cart 聚合操作
paths:
  /cart/{ownerId}/snapshot_state/single:
    get:
      operationId: shop.cart.snapshot_state.single  # 三段式:context.aggregate.suffix
      tags: [shop.cart]                              # 标签必须是 context.aggregate
  /cart/{ownerId}/event/list_query:
    get:
      operationId: shop.cart.event.list_query
      tags: [shop.cart]
  /cart/{ownerId}/{aggregateId}/snapshot/count:
    get:
      operationId: shop.cart.snapshot.count
      tags: [shop.cart]
  /cart/{ownerId}/{aggregateId}/{commandName}:
    post:
      operationId: shop.cart.add_item               # 三段式:context.aggregate.command
      tags: [shop.cart]
      responses:
        '200':
          $ref: '#/components/responses/wow.CommandOk'  # 响应级别的 $ref,而非 schema 级别

标签命名

标签使用 {context}.{aggregate} 格式定义限界上下文聚合。共享同一标签的所有操作属于同一聚合,并被分组到单个客户端类中。

保留 Schema

  • wow. 前缀的 schema(如 wow.CommandOkwow.CommandResult)保留给内部框架类型——生成器会特殊处理它们,不会为其生成客户端模型。

来源: packages/generator/README.md#operation-patterns

步骤 3 -- 生成模型

ModelGenerator 遍历所有 OpenAPI 组件模式并生成 TypeScript 类型。它会过滤掉 Wow 框架内部模式(以 wow. 为前缀)以及聚合生成的后缀类型。

TypeGenerator 处理以下模式类型:

模式类型生成的 TypeScript
enumenum 声明
带属性的 object带类型化属性的 interface
allOfinterface extends(交叉类型)
oneOf / anyOf类型别名(联合类型)
arrayArray<T> 类型别名
additionalProperties带索引签名的 Record<K, V>
基本类型stringnumberboolean

来源: packages/generator/src/model/typeGenerator.ts:50-337

步骤 4 -- 生成客户端

ClientGenerator 协调三个子生成器:

mermaid
graph LR
    subgraph ClientGenerator
        direction LR
        API["ApiClientGenerator"]
        CMD["CommandClientGenerator"]
        QRY["QueryClientGenerator"]
    end

    API --> |"REST API classes<br>per tag"| APICLASS["UserApiClient<br>OrderApiClient"]
    CMD --> |"Command classes<br>per aggregate"| CMDCLASS["CartCommandClient<br>CartStreamCommandClient"]
    QRY --> |"Query factory<br>per aggregate"| QRYCLASS["cartQueryClientFactory"]

    style ClientGenerator fill:#161b22,stroke:#30363d,color:#e6edf3
    style APICLASS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CMDCLASS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style QRYCLASS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

来源: packages/generator/src/client/clientGenerator.ts

API 客户端生成器

根据 OpenAPI 标签生成基于装饰器的 API 客户端类。每个类包含:

  • @api() 类装饰器,指定基础路径
  • @get/@post/@put/@delete/@patch 方法装饰器,指定端点路径
  • @path 参数装饰器,用于路径模板变量
  • @request 参数装饰器,用于请求体
  • @attribute 参数装饰器,用于拦截器属性

名为 wowActuator 或与聚合名称匹配的标签会被排除。

来源: packages/generator/src/client/apiClientGenerator.ts:73-598

命令客户端生成器

为每个 Wow 聚合生成命令客户端。对每个聚合,它生成:

  1. XxxCommandEndpointPaths 枚举 -- 将命令名称映射到 URL 路径
  2. XxxCommand 类型别名 -- 使用 CommandBody<T> 包装的类型化命令体
  3. XxxCommandClient 类 -- 通过 @post 装饰器发送命令
  4. XxxStreamCommandClient 类 -- 在命令客户端基础上扩展 SSE 流式传输

来源: packages/generator/src/client/commandClientGenerator.ts:60-434

查询客户端生成器

为每个聚合生成查询客户端工厂:

  • 领域事件类型联合(XxxDomainEventType
  • 领域事件标题枚举(XxxDomainEventTypeMapTitle
  • 配置了上下文别名、聚合名称和资源归属的 QueryClientFactory 实例

来源: packages/generator/src/client/queryClientGenerator.ts:35-226

步骤 5 -- 索引和优化

所有生成器运行完成后,代码生成器会:

  1. 为每个输出目录递归创建 index.ts 桶导出文件
  2. 使用 formatText() 格式化所有源文件
  3. 使用 organizeImports() 整理导入语句
  4. 使用 fixMissingImports() 修复缺失的导入
  5. 将 ts-morph 项目保存到磁盘

来源: packages/generator/src/index.ts:157-262

生成输出结构

对于一个包含限界上下文 example 和聚合 Cart 的规范:

src/generated/
  index.ts                          # 桶导出
  tsconfig.json                     # 生成代码的 TypeScript 配置
  example/
    index.ts                        # 桶导出
    boundedContext.ts               # export const EXAMPLE_BOUNDED_CONTEXT_ALIAS = 'example';
    types.ts                        # 限界上下文的共享类型
    CartApiClient.ts                # @api() 类(每个 OpenAPI 标签一个)
    OrderApiClient.ts               # @api() 类(每个 OpenAPI 标签一个)
    cart/
      index.ts
      types.ts                      # CartState、AddCartItem、CartItemAdded 等
      commandClient.ts              # CartCommandClient、CartStreamCommandClient
      queryClient.ts                # cartQueryClientFactory
    order/
      ...

编程式使用

typescript
import { CodeGenerator } from '@ahoo-wang/fetcher-generator';

const generator = new CodeGenerator({
  inputPath: './openapi.yaml',
  outputDir: './src/generated',
  tsConfigFilePath: './tsconfig.json',
  // `logger` 为必填,且必须实现完整的 Logger 接口
  // (info、success、error、progress、progressWithCount)。
  // 全局 `console` 不是合法取值,ConsoleLogger 也不属于公开 API,
  // 因此必须提供自定义实现:
  logger: {
    info(message, ...params) { console.log(message, ...params); },
    success(message, ...params) { console.log('✓', message, ...params); },
    error(message, ...params) { console.error(message, ...params); },
    progress(message, level, ...params) { console.log(message, ...params); },
    progressWithCount(current, total, message, level, ...params) {
      console.log(`${message} (${current}/${total})`, ...params);
    },
  },
});

await generator.generate();

主要导出

包入口(index.ts)仅公开导出以下符号;下表其余类型为生成流水线使用的内部实现类型。

导出描述
CodeGenerator主协调器类(公开)
DEFAULT_CONFIG_PATH默认配置文件路径(./fetcher-generator.config.json,公开)
GeneratorOptions代码生成配置选项(内部,types.ts
GenerateContext在生成流水线中传递的上下文对象(内部,generateContext.ts
Generator所有生成器阶段实现的接口(内部,generateContext.ts
Logger用于进度报告的日志接口(内部,types.ts
GeneratorConfigurationAPI 客户端生成的逐标签配置(内部,types.ts

依赖

交叉引用

  • Decorator -- 生成的 API 客户端使用装饰器包中的 @api@get@post 及参数装饰器
  • Wow -- 命令和查询客户端面向 Wow CQRS 框架的命令和快照端点
  • EventStream -- 生成的流式客户端使用 JsonEventStreamResultExtractor 处理 SSE 结果
  • OpenAPI -- 生成器使用 openapi 包中的 OpenAPI 类型定义

基于 Apache License 2.0 发布。