Skip to content

Commit 2aed9b6

Browse files
committed
Finish implementing the parts of FUTURE.md that make this codebase decoupled ; [README.md] Majorly update (+mermaid!
🧜‍♀️)
1 parent 6f81f99 commit 2aed9b6

21 files changed

+1280
-633
lines changed

EXTENDING.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Guide to Extending the Code Generator
2+
3+
This document provides instructions on how to extend the generator to support new frameworks (like React or Vue) and new HTTP clients (like Axios).
4+
5+
## Architectural Overview
6+
7+
The generator is built on a clean, three-layer architecture designed for extensibility:
8+
9+
1. **Core Layer (`src/core`)**: Parses the OpenAPI specification. This layer is completely framework-agnostic.
10+
2. **Analysis / IR Layer (`src/analysis`)**: Analyzes the parsed spec and converts it into a framework-agnostic **Intermediate Representation (IR)**. This IR provides a simple, abstract model of services, forms, lists, and validation rules. This is the key to decoupling.
11+
3. **Generation Layer (`src/generators`)**: Consumes the IR and generates framework-specific code. All framework-specific logic is isolated here.
12+
13+
To add support for a new technology, you will primarily be working in the **Generation Layer**.
14+
15+
---
16+
17+
## How to Add a New Framework (e.g., React)
18+
19+
Adding a new framework like React involves creating a new set of generators that consume the existing IR from the `src/analysis` layer and emit React-specific code (e.g., TypeScript with JSX).
20+
21+
### 1. Create the Framework Directory
22+
23+
Create a new directory for your framework inside `src/generators`.
24+
25+
```
26+
src/generators/
27+
└── react/
28+
├── admin/
29+
│ ├── form-component.generator.ts
30+
│ └── list-component.generator.ts
31+
├── service/
32+
│ ├── service.generator.ts
33+
│ └── service-method.generator.ts
34+
└── react-client.generator.ts
35+
```
36+
37+
### 2. Implement the Main Client Generator
38+
39+
Create `src/generators/react/react-client.generator.ts`. This file will be the main orchestrator for your framework's code generation. It must implement the `IClientGenerator` interface. You can use `src/generators/angular/angular-client.generator.ts` as a reference.
40+
41+
```typescript
42+
// src/generators/react/react-client.generator.ts
43+
44+
import { Project } from 'ts-morph';
45+
import { SwaggerParser } from '@src/core/parser.js';
46+
import { GeneratorConfig } from '@src/core/types/index.js';
47+
import { AbstractClientGenerator } from '../../core/generator.js';
48+
import { TypeGenerator } from '../shared/type.generator.js'; // Re-use this!
49+
50+
export class ReactClientGenerator extends AbstractClientGenerator {
51+
public async generate(project: Project, parser: SwaggerParser, config: GeneratorConfig, outputDir: string): Promise<void> {
52+
// 1. Generate Models (re-use the shared generator)
53+
new TypeGenerator(parser, project, config).generate(outputDir);
54+
console.log('✅ Models generated.');
55+
56+
// 2. Generate React Hooks for Services
57+
// const serviceGenerator = new ReactServiceGenerator(...);
58+
// serviceGenerator.generateAll(outputDir);
59+
60+
// 3. Generate React Admin Components (if applicable)
61+
// if (config.options.admin) {
62+
// const adminGenerator = new ReactAdminGenerator(...);
63+
// adminGenerator.generate(outputDir);
64+
// }
65+
66+
console.log(`🎉 React client generation complete!`);
67+
}
68+
}
69+
```
70+
71+
### 3. Wire Up the New Generator
72+
73+
You need to tell the main entry points about your new generator.
74+
75+
#### In `src/cli.ts`:
76+
77+
Add `'react'` to the `framework` option's choices.
78+
79+
```typescript
80+
// src/cli.ts
81+
// ... inside the 'from_openapi' command definition
82+
.addOption(new Option('--framework <framework>', 'Target framework').choices(['angular', 'react', 'vue']))
83+
```
84+
85+
#### In `src/index.ts`:
86+
87+
Add a `case` for `'react'` in the `getGeneratorFactory` function.
88+
89+
```typescript
90+
// src/index.ts
91+
import { ReactClientGenerator } from './generators/react/react-client.generator.js';
92+
93+
function getGeneratorFactory(framework: string): IClientGenerator {
94+
switch (framework) {
95+
case 'angular':
96+
return new AngularClientGenerator();
97+
case 'react':
98+
return new ReactClientGenerator(); // Add this line
99+
// ...
100+
}
101+
}
102+
```
103+
104+
### 4. Implement the Service Generator
105+
106+
Your React service generator will generate hooks instead of Angular services.
107+
108+
1. Create `src/generators/react/service/service-method.generator.ts`.
109+
2. Use the `ServiceMethodAnalyzer` from `src/analysis` to get the `ServiceMethodModel` (the IR).
110+
3. Use this model to generate a custom hook (e.g., `useGetUserById`) that uses an HTTP client like `fetch` or `axios`.
111+
112+
**Example Logic:**
113+
```typescript
114+
// Inside your React Service Method Generator
115+
import { ServiceMethodAnalyzer } from '@src/analysis/service-method-analyzer.ts';
116+
// ...
117+
const analyzer = new ServiceMethodAnalyzer(this.config, this.parser);
118+
const model = analyzer.analyze(operation); // model is the framework-agnostic IR
119+
120+
// Now, generate React-specific hook code from the 'model'
121+
const hookCode = `
122+
export const use${pascalCase(model.methodName)} = () => {
123+
// ... logic to call fetch/axios using model.urlTemplate, model.httpMethod etc.
124+
};
125+
`;
126+
```
127+
128+
### 5. Implement Admin UI Generators (Optional)
129+
130+
If you want to generate an admin UI:
131+
132+
1. Use `FormModelBuilder` and `ListModelBuilder` from `src/analysis` to get the `FormAnalysisResult` and `ListViewModel`.
133+
2. Create React-specific generators that consume this IR to produce JSX.
134+
3. You will need to create a **React-specific renderer for validation**. For example, create a `ValidationRenderer` that converts the `ValidationRule[]` IR into a `Yup` schema for use with Formik.
135+
136+
---
137+
138+
## How to Add a New HTTP Client (e.g., Axios)
139+
140+
This change is much simpler as it's localized within a specific framework's generator. Here’s how to do it for the existing Angular generator.
141+
142+
### 1. Locate the HTTP Call Logic
143+
144+
The code that makes the actual HTTP request is located in:
145+
`src/generators/angular/service/service-method.generator.ts`
146+
147+
Specifically, look inside the `emitMethodBody` private method.
148+
149+
### 2. Find the `http.request` Lines
150+
151+
At the end of the `emitMethodBody` method, you will find lines like these:
152+
153+
```typescript
154+
// src/generators/angular/service/service-method.generator.ts
155+
156+
// ... inside emitMethodBody
157+
// 10. HTTP Call
158+
// ...
159+
if (isStandardBody) {
160+
if (httpMethod === 'query') {
161+
lines.push(`return this.http.request('QUERY', url, { ...requestOptions, body: ${bodyArgument} } as any);`);
162+
} else {
163+
lines.push(`return this.http.${httpMethod}(url, ${bodyArgument}, requestOptions as any);`);
164+
}
165+
} else if (isStandardNonBody) {
166+
lines.push(`return this.http.${httpMethod}(url, requestOptions as any);`);
167+
} else {
168+
lines.push(`return this.http.request('${model.httpMethod}', url, requestOptions as any);`);
169+
}
170+
```
171+
172+
### 3. Replace the Logic
173+
174+
Replace the `this.http.*` calls with your desired client's syntax.
175+
176+
**To switch to Axios, you would:**
177+
178+
1. Change the imports at the top of `src/generators/angular/service/service.generator.ts` to import `axios` and `from` from `rxjs` (to wrap the Promise in an Observable).
179+
2. Modify the `emitMethodBody` logic to build an `axios` config object and make the call.
180+
181+
**Example Change (Conceptual):**
182+
183+
```typescript
184+
// Conceptual change in service-method.generator.ts
185+
186+
// ... build url, params, headers, bodyArgument ...
187+
188+
// OLD:
189+
// lines.push(`return this.http.get(url, requestOptions as any);`);
190+
191+
// NEW (for Axios):
192+
lines.push(`const config = { headers, params };`);
193+
lines.push(`return from(axios.get(url, config));`);
194+
```
195+
196+
You would need to adapt the `requestOptions` object to the format expected by Axios. You could also add a configuration option in `config.ts` to let the user choose which HTTP client to generate code for.

0 commit comments

Comments
 (0)