Skip to content

@ahoo-wang/fetcher-generator

The @ahoo-wang/fetcher-generator package is a CLI tool that reads OpenAPI 3.x specifications (JSON, YAML, or URL) and generates fully typed TypeScript code -- model interfaces/enums, decorator-based API clients, and Wow CQRS-specific command/query clients. Built on ts-morph for code generation and commander for CLI parsing.

Installation

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

Architecture Overview

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["model/*.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 Usage

The fetcher-generator binary reads an OpenAPI spec and writes TypeScript source files into an output directory.

Commands

CommandDescription
generateGenerate TypeScript code from an OpenAPI specification
-v, --versionShow version

Generate Options

OptionShortRequiredDefaultDescription
--input <file>-iYes--OpenAPI spec file path or URL
--output <path>-oNosrc/generatedOutput directory
--config <file>-cNo./fetcher-generator.config.jsonConfiguration file path
--ts-config-file-path <file>-tNo--TypeScript configuration file

Example CLI Commands

bash
# Generate from a local YAML spec
npx fetcher-generator generate -i ./openapi.yaml -o ./src/generated

# Generate from a remote URL
npx fetcher-generator generate -i https://api.example.com/v3/api-docs -o ./src/generated

# Generate with a custom config and tsconfig
npx fetcher-generator generate -i ./spec.json -o ./src/api -c ./generator.config.json -t ./tsconfig.json

Source: packages/generator/src/cli.ts

Configuration

The generator reads an optional JSON configuration file (default ./fetcher-generator.config.json):

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

By default, tenantId and ownerId path parameters are ignored when generating API client methods, since they are handled by the CoSec resource attribution interceptor.

Source: packages/generator/src/types.ts:73-87

Code Generation Pipeline

Step 1 -- Parse OpenAPI Specification

The CodeGenerator parses the input spec (JSON, YAML, or URL) into a typed OpenAPI object. It supports file paths and HTTP/HTTPS URLs.

Source: packages/generator/src/index.ts:45-142

Step 2 -- Resolve Bounded Context Aggregates

The AggregateResolver walks all operations to extract Wow bounded context aggregate definitions. It identifies:

  • Commands -- operations with wow.CommandOk response schemas
  • State -- snapshot state query operations (.snapshot_state.single)
  • Events -- event stream list queries (.event.list_query)
  • Fields -- snapshot count operations (.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

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

Step 3 -- Generate Models

The ModelGenerator iterates all OpenAPI component schemas and produces TypeScript types. It filters out Wow framework internal schemas (prefixed with wow.) and aggregate-generated suffix types.

The TypeGenerator handles these schema patterns:

Schema PatternGenerated TypeScript
enumenum declaration
object with propertiesinterface with typed properties
allOfinterface extends (intersection)
oneOf / anyOftype alias (union)
arrayArray<T> type alias
additionalPropertiesRecord<K, V> with index signature
Primitive typesstring, number, boolean

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

Step 4 -- Generate Clients

The ClientGenerator orchestrates three sub-generators:

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

Source: packages/generator/src/client/clientGenerator.ts

API Client Generator

Generates decorator-based API client classes from OpenAPI tags. Each class has:

  • @api() class decorator with base path
  • @get/@post/@put/@delete/@patch method decorators with endpoint paths
  • @path parameter decorators for path template variables
  • @request parameter decorator for the request body
  • @attribute parameter decorator for interceptor attributes

Tags named wow, Actuator, or matching aggregate names are excluded.

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

Command Client Generator

Generates command clients for each Wow aggregate. For every aggregate, it produces:

  1. XxxCommandEndpointPaths enum -- maps command names to URL paths
  2. XxxCommand type aliases -- typed command bodies wrapped with CommandBody<T>
  3. XxxCommandClient class -- sends commands via @post decorators
  4. XxxStreamCommandClient class -- extends command client with SSE streaming

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

Query Client Generator

Generates query client factories for each aggregate:

  • Domain event type union (XxxDomainEventType)
  • Domain event title enum (XxxDomainEventTypeMapTitle)
  • QueryClientFactory instance configured with context alias, aggregate name, and resource attribution

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

Step 5 -- Index and Optimize

After all generators run, the code generator:

  1. Recursively creates index.ts barrel export files for every output directory
  2. Formats all source files with formatText()
  3. Organizes imports with organizeImports()
  4. Fixes missing imports with fixMissingImports()
  5. Saves the ts-morph project to disk

Source: packages/generator/src/index.ts:157-262

Generated Output Structure

For a spec with bounded context example and aggregate Cart:

src/generated/
  index.ts                          # barrel export
  example/
    index.ts                        # barrel export
    boundedContext.ts                # export const example = 'example';
    model/
      index.ts
      CartState.ts                  # interface CartState { ... }
      AddCartItem.ts                # interface AddCartItem { ... }
      CartItemAdded.ts              # interface CartItemAdded { ... }
      ...
    cart/
      index.ts
      commandClient.ts              # CartCommandClient, CartStreamCommandClient
      queryClient.ts                # cartQueryClientFactory
    order/
      ...
  UserApiClient.ts                  # @api() class
  OrderApiClient.ts                 # @api() class

Programmatic Usage

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

const generator = new CodeGenerator({
  inputPath: './openapi.yaml',
  outputDir: './src/generated',
  tsConfigFilePath: './tsconfig.json',
  logger: console,
});

await generator.generate();

Key Exports

ExportDescription
CodeGeneratorMain orchestrator class
GeneratorOptionsConfiguration options for code generation
GenerateContextContext object passed through the generation pipeline
GeneratorInterface implemented by all generator stages
LoggerLogging interface for progress reporting
GeneratorConfigurationPer-tag configuration for API client generation
DEFAULT_CONFIG_PATHDefault config file path (./fetcher-generator.config.json)

Dependencies

Cross-References

  • Decorator -- Generated API clients use @api, @get, @post, and parameter decorators from the decorator package
  • Wow -- Command and query clients target the Wow CQRS framework's command and snapshot endpoints
  • EventStream -- Generated streaming clients use JsonEventStreamResultExtractor for SSE results
  • OpenAPI -- The generator consumes OpenAPI type definitions from the openapi package

Released under the Apache License 2.0.