|
| 1 | +# Architecture |
| 2 | + |
| 3 | +This document describes the high-level architecture, package structure, and common patterns of the `@nahkies/openapi-code-generator` monorepo. |
| 4 | + |
| 5 | +## High-Level Overview |
| 6 | + |
| 7 | +`@nahkies/openapi-code-generator` is a monorepo that produces a CLI tool for generating TypeScript client SDKs and server scaffolding from OpenAPI 3.0, 3.1, and TypeSpec specifications. |
| 8 | + |
| 9 | +The system follows a typical compiler/generator pipeline: |
| 10 | +1. **Loading**: Specifications are loaded and resolved (including remote/local references). |
| 11 | +2. **Validation**: Loaded specifications are validated against their respective schemas using pre-compiled AJV validators. |
| 12 | +3. **Normalization**: The specification is transformed into a normalized Intermediate Representation (IR). |
| 13 | +4. **Building**: Template-specific builders transform the IR into code structures (types, schemas, clients/routers). |
| 14 | +5. **Emission**: The generated structures are formatted and written to the file system. |
| 15 | + |
| 16 | +## Package Structure |
| 17 | + |
| 18 | +- **`packages/openapi-code-generator`**: The core package containing the CLI, loader logic, IR normalization, and code generation templates. |
| 19 | +- **`packages/typescript-common-runtime`**: Shares common types (e.g., `Res`, `StatusCode`) and utilities (e.g., query parsing, body validation) used by all generated TypeScript code. |
| 20 | +- **`packages/typescript-*-runtime`**: Runtime-specific support packages. |
| 21 | + - `typescript-axios-runtime`: Provides utilities for Axios clients. |
| 22 | + - `typescript-fetch-runtime`: Provides `AbstractFetchClient`. |
| 23 | + - `typescript-express-runtime`: Provides utilities for routing and response handling in Express. |
| 24 | + - `typescript-koa-runtime`: Provides utilities for routing and response handling in Koa. |
| 25 | +- **`integration-tests`**: Validates the generator by running it against real-world specifications (Github, Stripe, etc.) and ensuring the output compiles. |
| 26 | +- **`e2e`**: End-to-end tests for the CLI and generated code. |
| 27 | + |
| 28 | +## Core Patterns |
| 29 | + |
| 30 | +### Adaptor Pattern |
| 31 | +The generator uses adaptors to abstract environment-specific logic, allowing it to run in both Node.js and the browser (playground). |
| 32 | +- **`IFsAdaptor`**: Abstraction for file system operations (`NodeFsAdaptor`, `WebFsAdaptor`). |
| 33 | +- **`IFormatter`**: Abstraction for code formatting (`TypescriptFormatterPrettier`, `TypescriptFormatterBiome`). |
| 34 | + |
| 35 | +### Builder Pattern |
| 36 | +Complex code generation is managed through builders that accumulate state and then produce a `CompilationUnit`. |
| 37 | +- **`TypeBuilder`**: Generates TypeScript type definitions. |
| 38 | +- **`SchemaBuilder`**: Generates runtime validation schemas (Joi or Zod). |
| 39 | +- **`ClientBuilder`**: (e.g., `TypescriptFetchClientBuilder`) Generates client-side SDK code. |
| 40 | +- **`RouterBuilder` / `ServerBuilder`**: (e.g., `ExpressRouterBuilder`, `ExpressServerBuilder`) Generates server-side scaffolding and routing. |
| 41 | + |
| 42 | +### Compilation Units |
| 43 | +A `CompilationUnit` (defined in `packages/openapi-code-generator/src/typescript/common/compilation-units.ts`) represents a single file to be emitted. It encapsulates: |
| 44 | +- The target filename. |
| 45 | +- An `ImportBuilder` to manage required imports without duplicates. |
| 46 | +- The raw string of code. |
| 47 | + |
| 48 | +### Intermediate Representation (IR) |
| 49 | +The `Input` class (in `src/core/input.ts`) serves as the IR. It takes a raw loader and provides normalized access to operations, schemas, and servers. This decoupling ensures that templates don't need to handle OpenAPI version differences or complex reference resolution. |
| 50 | + |
| 51 | +## Data Flow |
| 52 | + |
| 53 | +```mermaid |
| 54 | +graph TD |
| 55 | + Spec[OpenAPI/TypeSpec Spec] --> Loader[OpenapiLoader] |
| 56 | + Loader --> Validator[OpenapiValidator] |
| 57 | + Validator --> Input[Input / IR] |
| 58 | + Input --> Generator[Template Generator] |
| 59 | + Generator --> Builders[Builders: Type, Schema, Client, Router] |
| 60 | + Builders --> Units[CompilationUnits] |
| 61 | + Units --> Emitter[TypescriptEmitter] |
| 62 | + Emitter --> Formatter[Formatter: Biome/Prettier] |
| 63 | + Formatter --> FS[Filesystem] |
| 64 | +``` |
| 65 | + |
| 66 | +## Important Invariants & Conventions |
| 67 | + |
| 68 | +### Invariants |
| 69 | +- **No `npx`**: Always use `pnpm run` or `pnpm exec`. |
| 70 | +- **Read-Only Generation**: Never manually edit files in `src/generated/` directories; they are overwritten during generation. |
| 71 | +- **Jest Globals**: Always explicitly import Jest globals (`describe`, `it`, `expect`) from `@jest/globals`. |
| 72 | + |
| 73 | +### Conventions |
| 74 | +- **Co-location**: Unit tests (`*.spec.ts`) are co-located with the source code they test. Use explicit jest imports (`import {describe, it, expect} from "@jest/globals"`). |
| 75 | +- **Import Extensions**: Use `.ts` extensions in imports (e.g., `import {foo} from "./foo.ts"`) to support ESM. |
| 76 | +- **Dependency Migration**: Prefer `pnpm` workspace references (e.g., `"@nahkies/typescript-common-runtime": "workspace:*"`). |
| 77 | + |
| 78 | +## How to add a new feature |
| 79 | + |
| 80 | +1. **Identify the Stage**: Determine if the change belongs in the `Loader`, `Input` (IR), or a specific `Generator`/`Builder`. |
| 81 | +2. **Update IR (if needed)**: If the feature requires new data from the OpenAPI spec, update `src/core/openapi-types-normalized.ts` and the normalization logic in `src/core/input.ts`. |
| 82 | +3. **Update Builders**: Implement the code generation logic in the relevant builders (e.g., `ClientOperationBuilder`). |
| 83 | +4. **Add Unit Tests**: Add a `.spec.ts` next to the modified file. |
| 84 | +5. **Add Integration Spec**: If the feature is a new OpenAPI concept, add a minimal reproduction spec to `integration-tests-definitions`. |
| 85 | +6. **Run Pipeline**: |
| 86 | + - `pnpm build` |
| 87 | + - `pnpm integration:generate` |
| 88 | + - `pnpm integration:validate` |
| 89 | +7. **Documentation**: Update the external documentation in `packages/documentation` if user-facing. |
| 90 | + |
| 91 | +## Common Gotchas |
| 92 | + |
| 93 | +- **Cyclic Dependencies**: When generating schemas (Joi/Zod), the generator must handle cyclic references in the OpenAPI spec. Check `SchemaBuilder`'s handling of `Reference`. For Zod, we have multiple versions (`zod-v3`, `zod-v4`) as Zod v4 handles lazy schemas differently. |
| 94 | +- **Filename Casing**: The generator supports different filename conventions. Ensure your builders respect `config.filenameConvention`. |
| 95 | +- **ESM vs CJS**: The project targets ESM. Relative imports must include file extensions. |
| 96 | +- **Generated Schema Validators**: `src/core/schemas/openapi-*-specification-validator.js` are generated. If you update the meta-schemas, you must run the build script to refresh them. |
| 97 | + |
| 98 | +## Anti-Patterns |
| 99 | + |
| 100 | +- **Direct Spec Access**: Avoid accessing the raw `Loader` or `loader.entryPoint` from within template builders. Use the normalized `Input` (IR) instead. |
| 101 | +- **Hardcoded Filenames**: Avoid hardcoding filenames in builders; use the provided config and `ImportBuilder` to manage paths. |
| 102 | +- **Complex Logic in Templates**: `*.generator.ts` files should be orchestrators. Move complex logic into dedicated `Builder` classes (e.g. `ClientOperationBuilder` or `SchemaBuilder`). |
0 commit comments