klasik 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/generator/generator.d.ts.map +1 -1
- package/dist/generator/generator.js +8 -2
- package/dist/generator/generator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/ARCHITECTURE.md +845 -0
- package/docs/JSDOC_ESCAPING.md +280 -0
- package/docs/json-schema-support.md +2 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -860,4 +860,4 @@ Built with:
|
|
|
860
860
|
|
|
861
861
|
---
|
|
862
862
|
|
|
863
|
-
**Note:** Klasik
|
|
863
|
+
**Note:** Klasik is a complete rebuild with AST-based code generation, replacing the string manipulation approach from v1. See [ARCHITECTURE.md](ARCHITECTURE.md) for technical details.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EACL,QAAQ,EAET,MAAM,aAAa,CAAC;AAErB,OAAO,EAAmC,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAQ9F;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,OAAO,CAAmB;gBAEtB,OAAO,EAAE,gBAAgB;IAqBrC;;OAEG;IACG,QAAQ,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EACL,QAAQ,EAET,MAAM,aAAa,CAAC;AAErB,OAAO,EAAmC,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAQ9F;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,OAAO,CAAmB;gBAEtB,OAAO,EAAE,gBAAgB;IAqBrC;;OAEG;IACG,QAAQ,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyC3C;;OAEG;YACW,cAAc;IAY5B;;OAEG;YACW,aAAa;IAqE3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAuBtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA+B1B;;OAEG;YACW,mBAAmB;IA0CjC;;OAEG;YACW,mBAAmB;IA8BjC;;OAEG;YACW,gBAAgB;IA+B9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAczB;;OAEG;IACH,OAAO,CAAC,aAAa;IAQrB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAgB3B"}
|
|
@@ -87,10 +87,12 @@ class Generator {
|
|
|
87
87
|
console.log(`Generating ${ir.schemas.size} model(s)...`);
|
|
88
88
|
await this.generateModels(ir, context);
|
|
89
89
|
// Generate API client if in full mode and operations exist
|
|
90
|
+
let hasApiClient = false;
|
|
90
91
|
if (this.options.mode === 'full' && ir.operations.size > 0) {
|
|
91
92
|
const { ApiClientGenerator } = await Promise.resolve().then(() => __importStar(require('../generators/api-client-generator')));
|
|
92
93
|
const apiClientGenerator = new ApiClientGenerator(this.options);
|
|
93
94
|
await apiClientGenerator.generateFullClient(ir);
|
|
95
|
+
hasApiClient = true;
|
|
94
96
|
}
|
|
95
97
|
// Run after generation hooks
|
|
96
98
|
await this.pluginRunner.runAfterGeneration(context, ir);
|
|
@@ -98,7 +100,7 @@ class Generator {
|
|
|
98
100
|
console.log('Saving generated files...');
|
|
99
101
|
await this.project.save();
|
|
100
102
|
// Generate package.json
|
|
101
|
-
await this.generatePackageJson(context);
|
|
103
|
+
await this.generatePackageJson(context, hasApiClient);
|
|
102
104
|
// Generate tsconfig.json
|
|
103
105
|
await this.generateTsConfig();
|
|
104
106
|
console.log('✅ Code generation completed successfully!');
|
|
@@ -252,7 +254,7 @@ class Generator {
|
|
|
252
254
|
/**
|
|
253
255
|
* Generate package.json
|
|
254
256
|
*/
|
|
255
|
-
async generatePackageJson(context) {
|
|
257
|
+
async generatePackageJson(context, hasApiClient) {
|
|
256
258
|
const packageJson = {
|
|
257
259
|
name: 'generated-models',
|
|
258
260
|
version: '1.0.0',
|
|
@@ -262,6 +264,10 @@ class Generator {
|
|
|
262
264
|
type: this.options.esm ? 'module' : 'commonjs',
|
|
263
265
|
dependencies: {},
|
|
264
266
|
};
|
|
267
|
+
// Add axios if API client was generated
|
|
268
|
+
if (hasApiClient) {
|
|
269
|
+
packageJson.dependencies['axios'] = '^1.6.0';
|
|
270
|
+
}
|
|
265
271
|
// Run plugin hooks to modify package.json
|
|
266
272
|
await this.pluginRunner.runModifyPackageJson(packageJson, context);
|
|
267
273
|
// Write package.json
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAmC;AACnC,uCAAyB;AACzB,2CAA6B;AAK7B,+DAA2D;AAC3D,6DAA8F;AAC9F,kEAA2E;AAC3E,kFAA6E;AAC7E,4EAAuE;AACvE,8EAAyE;AACzE,oDAA0F;AAC1F,iEAA4D;AAE5D;;GAEG;AACH,MAAa,SAAS;IAMpB,YAAY,OAAyB;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,8BAA8B;QAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,kBAAO,CAAC;YACzB,eAAe,EAAE;gBACf,MAAM,EAAE,EAAE,EAAE,SAAS;gBACrB,MAAM,EAAE,EAAE,EAAE,SAAS;gBACrB,WAAW,EAAE,IAAI;gBACjB,sBAAsB,EAAE,IAAI;gBAC5B,qBAAqB,EAAE,IAAI;aAC5B;YACD,qBAAqB,EAAE,KAAK;SAC7B,CAAC,CAAC;QAEH,2BAA2B;QAC3B,IAAI,CAAC,cAAc,GAAG,IAAI,iCAAc,EAAE,CAAC;QAC3C,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,IAAI,+BAAY,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC;IACrE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAY;QACzB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAE3C,0BAA0B;QAC1B,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAE7B,4BAA4B;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAErC,8BAA8B;QAC9B,MAAM,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAEzD,kBAAkB;QAClB,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,IAAI,cAAc,CAAC,CAAC;QACzD,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAEvC,2DAA2D;QAC3D,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC3D,MAAM,EAAE,kBAAkB,EAAE,GAAG,wDAAa,oCAAoC,GAAC,CAAC;YAClF,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAChE,MAAM,kBAAkB,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
# Klasik Architecture
|
|
2
|
+
|
|
3
|
+
This document provides technical implementation details for developers who want to understand, modify, or contribute to Klasik.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Why ts-morph?](#why-ts-morph)
|
|
9
|
+
- [Why Plugin Architecture?](#why-plugin-architecture)
|
|
10
|
+
- [Why Intermediate Representation (IR)?](#why-intermediate-representation-ir)
|
|
11
|
+
- [Architecture Components](#architecture-components)
|
|
12
|
+
- [Code Generation Pipeline](#code-generation-pipeline)
|
|
13
|
+
- [Plugin System](#plugin-system)
|
|
14
|
+
- [Testing Strategy](#testing-strategy)
|
|
15
|
+
- [Development Guide](#development-guide)
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
- **Type Safety**: Generated code is parsed and validated as TypeScript AST
|
|
20
|
+
- **Maintainability**: AST manipulation is more robust than string concatenation
|
|
21
|
+
- **Extensibility**: Plugin architecture allows easy feature additions
|
|
22
|
+
- **Format Agnostic**: Unified IR handles OpenAPI, CRDs, and JSON Schema
|
|
23
|
+
|
|
24
|
+
## Why ts-morph?
|
|
25
|
+
|
|
26
|
+
### Problem with String-Based Generation
|
|
27
|
+
|
|
28
|
+
The original Klasik used template strings and manual code construction:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Original Klasik v1 approach
|
|
32
|
+
let code = `export class ${className} {\n`;
|
|
33
|
+
code += ` ${propertyName}: ${propertyType};\n`;
|
|
34
|
+
code += `}\n`;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Issues:**
|
|
38
|
+
- ❌ No syntax validation until runtime
|
|
39
|
+
- ❌ Hard to maintain complex structures (decorators, imports, etc.)
|
|
40
|
+
- ❌ String escaping complexity
|
|
41
|
+
- ❌ Difficult to refactor or modify generated code
|
|
42
|
+
- ❌ No IDE support for template content
|
|
43
|
+
|
|
44
|
+
### Solution: AST-Based Generation
|
|
45
|
+
|
|
46
|
+
Klasik uses ts-morph to build proper TypeScript Abstract Syntax Trees:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Klasik approach
|
|
50
|
+
const classDeclaration = sourceFile.addClass({
|
|
51
|
+
name: className,
|
|
52
|
+
isExported: true
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
classDeclaration.addProperty({
|
|
56
|
+
name: propertyName,
|
|
57
|
+
type: propertyType,
|
|
58
|
+
decorators: [
|
|
59
|
+
{ name: 'Expose', arguments: [] },
|
|
60
|
+
{ name: 'ApiProperty', arguments: ['{ type: String }'] }
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Benefits:**
|
|
66
|
+
- ✅ TypeScript-validated AST construction
|
|
67
|
+
- ✅ Automatic formatting and indentation
|
|
68
|
+
- ✅ Type-safe API for code manipulation
|
|
69
|
+
- ✅ Built-in import management
|
|
70
|
+
- ✅ Easier to test and maintain
|
|
71
|
+
- ✅ IDE autocomplete and type checking
|
|
72
|
+
|
|
73
|
+
### Performance Considerations
|
|
74
|
+
|
|
75
|
+
While AST manipulation is slightly slower than string concatenation, the benefits far outweigh the cost:
|
|
76
|
+
|
|
77
|
+
- **Correctness**: Generated code is always syntactically valid
|
|
78
|
+
- **Maintainability**: Changes to generation logic are easier and safer
|
|
79
|
+
- **Debugging**: AST nodes can be inspected and validated
|
|
80
|
+
- **Testing**: Unit tests can verify AST structure without running TypeScript compiler
|
|
81
|
+
|
|
82
|
+
## Why Plugin Architecture?
|
|
83
|
+
|
|
84
|
+
### Problem with Monolithic Code Generation
|
|
85
|
+
|
|
86
|
+
Original Klasik had all generation logic in a single large module:
|
|
87
|
+
- Hard to add new features without breaking existing code
|
|
88
|
+
- Difficult to test individual features in isolation
|
|
89
|
+
- No way to selectively enable/disable features
|
|
90
|
+
- Code duplication across different generators
|
|
91
|
+
|
|
92
|
+
### Solution: Hook-Based Plugin System
|
|
93
|
+
|
|
94
|
+
Klasik uses a priority-ordered hook system:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
interface Plugin {
|
|
98
|
+
name: string;
|
|
99
|
+
priority: number;
|
|
100
|
+
|
|
101
|
+
hooks: {
|
|
102
|
+
beforeGeneration?(context: GenerationContext): void;
|
|
103
|
+
onSchemaLoad?(schema: SchemaIR): void;
|
|
104
|
+
onClassGeneration?(classNode: ClassDeclaration, schema: ObjectSchema): void;
|
|
105
|
+
onPropertyGeneration?(property: PropertyDeclaration, field: Field): void;
|
|
106
|
+
afterGeneration?(context: GenerationContext): void;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Benefits:**
|
|
112
|
+
- ✅ **Modularity**: Each feature is a self-contained plugin
|
|
113
|
+
- ✅ **Testability**: Plugins can be tested independently
|
|
114
|
+
- ✅ **Extensibility**: Add new features without modifying core
|
|
115
|
+
- ✅ **Flexibility**: Users can enable/disable features via CLI flags
|
|
116
|
+
- ✅ **Priority Control**: Plugins execute in defined order
|
|
117
|
+
|
|
118
|
+
### Plugin Examples
|
|
119
|
+
|
|
120
|
+
**1. NestJS Swagger Plugin**
|
|
121
|
+
```typescript
|
|
122
|
+
class NestJSSwaggerPlugin implements Plugin {
|
|
123
|
+
name = 'nestjs-swagger';
|
|
124
|
+
priority = 100;
|
|
125
|
+
|
|
126
|
+
hooks = {
|
|
127
|
+
onPropertyGeneration(property, field) {
|
|
128
|
+
property.addDecorator({
|
|
129
|
+
name: 'ApiProperty',
|
|
130
|
+
arguments: [`{ type: ${field.type}, required: ${field.required} }`]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**2. Class Validator Plugin**
|
|
138
|
+
```typescript
|
|
139
|
+
class ClassValidatorPlugin implements Plugin {
|
|
140
|
+
name = 'class-validator';
|
|
141
|
+
priority = 90;
|
|
142
|
+
|
|
143
|
+
hooks = {
|
|
144
|
+
onPropertyGeneration(property, field) {
|
|
145
|
+
if (field.required) {
|
|
146
|
+
property.addDecorator({ name: 'IsNotEmpty' });
|
|
147
|
+
}
|
|
148
|
+
if (field.type === 'string') {
|
|
149
|
+
property.addDecorator({ name: 'IsString' });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**3. ESM Plugin**
|
|
157
|
+
```typescript
|
|
158
|
+
class ESMPlugin implements Plugin {
|
|
159
|
+
name = 'esm';
|
|
160
|
+
priority = 200; // Runs after others
|
|
161
|
+
|
|
162
|
+
hooks = {
|
|
163
|
+
afterGeneration(context) {
|
|
164
|
+
// Add .js extensions to all imports
|
|
165
|
+
for (const sourceFile of context.project.getSourceFiles()) {
|
|
166
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
167
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
168
|
+
if (moduleSpecifier.startsWith('./') && !moduleSpecifier.endsWith('.js')) {
|
|
169
|
+
importDecl.setModuleSpecifier(moduleSpecifier + '.js');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Plugin Priority System
|
|
179
|
+
|
|
180
|
+
Plugins execute in priority order (lower numbers first):
|
|
181
|
+
|
|
182
|
+
1. **Core plugins** (priority 0-50): Base class-transformer decorators
|
|
183
|
+
2. **Feature plugins** (priority 50-150): NestJS, class-validator
|
|
184
|
+
3. **Transform plugins** (priority 150-200): ESM, custom templates
|
|
185
|
+
4. **Finalization plugins** (priority 200+): Formatting, validation
|
|
186
|
+
|
|
187
|
+
## Why Intermediate Representation (IR)?
|
|
188
|
+
|
|
189
|
+
### Problem with Format-Specific Generators
|
|
190
|
+
|
|
191
|
+
Without IR, each input format needs its own generator:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
OpenAPI → OpenAPI Generator → TypeScript
|
|
195
|
+
CRD → CRD Generator → TypeScript
|
|
196
|
+
JSON Schema → JSON Schema Generator → TypeScript
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This leads to:
|
|
200
|
+
- ❌ Code duplication across generators
|
|
201
|
+
- ❌ Inconsistent output for similar schemas
|
|
202
|
+
- ❌ Hard to add new input formats
|
|
203
|
+
- ❌ Difficult to maintain shared features
|
|
204
|
+
|
|
205
|
+
### Solution: Unified IR Layer
|
|
206
|
+
|
|
207
|
+
Klasik uses a unified Intermediate Representation:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
OpenAPI ──┐
|
|
211
|
+
├──→ SchemaIR ──→ TypeScript Generator ──→ TypeScript
|
|
212
|
+
CRD ──────┤
|
|
213
|
+
│
|
|
214
|
+
JSON Schema ─┘
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**IR Structure:**
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
interface SchemaIR {
|
|
221
|
+
schemas: Map<string, ObjectSchema>;
|
|
222
|
+
metadata: {
|
|
223
|
+
sourceFormat: 'openapi' | 'crd' | 'jsonschema';
|
|
224
|
+
version: string;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface ObjectSchema {
|
|
229
|
+
name: string;
|
|
230
|
+
description?: string;
|
|
231
|
+
fields: Field[];
|
|
232
|
+
required: string[];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
interface Field {
|
|
236
|
+
name: string;
|
|
237
|
+
type: string;
|
|
238
|
+
description?: string;
|
|
239
|
+
required: boolean;
|
|
240
|
+
array?: boolean;
|
|
241
|
+
enum?: string[];
|
|
242
|
+
format?: string;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Benefits:**
|
|
247
|
+
- ✅ **Single Source of Truth**: One generator for all formats
|
|
248
|
+
- ✅ **Consistency**: Same TypeScript output for equivalent schemas
|
|
249
|
+
- ✅ **Extensibility**: Add new formats by implementing IR converter
|
|
250
|
+
- ✅ **Testability**: Test converters and generator separately
|
|
251
|
+
- ✅ **Optimization**: Transform IR before generation (deduplication, normalization)
|
|
252
|
+
|
|
253
|
+
### IR Conversion Flow
|
|
254
|
+
|
|
255
|
+
**OpenAPI to IR:**
|
|
256
|
+
```typescript
|
|
257
|
+
OpenAPILoader.load(spec)
|
|
258
|
+
→ OpenAPIConverter.toIR(spec)
|
|
259
|
+
→ SchemaIR
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**CRD to IR:**
|
|
263
|
+
```typescript
|
|
264
|
+
CRDLoader.load(yaml)
|
|
265
|
+
→ CRDConverter.toIR(crd)
|
|
266
|
+
→ SchemaIR
|
|
267
|
+
→ mergeIRs() // Deduplication for multiple CRDs
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**JSON Schema to IR:**
|
|
271
|
+
```typescript
|
|
272
|
+
JSONSchemaLoader.load(schema)
|
|
273
|
+
→ JSONSchemaConverter.toIR(schema)
|
|
274
|
+
→ SchemaIR
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Architecture Components
|
|
278
|
+
|
|
279
|
+
### 1. Loaders (`src/loaders/`)
|
|
280
|
+
|
|
281
|
+
Responsible for fetching and parsing input specifications:
|
|
282
|
+
|
|
283
|
+
- **SpecLoader**: Fetches from URL or file, detects format (JSON/YAML)
|
|
284
|
+
- **CRDLoader**: Loads Kubernetes CRDs, handles multi-document YAML
|
|
285
|
+
- **JSONSchemaLoader**: Loads JSON Schema files
|
|
286
|
+
|
|
287
|
+
**Key Features:**
|
|
288
|
+
- HTTP/HTTPS support with custom headers
|
|
289
|
+
- File system support
|
|
290
|
+
- Automatic format detection
|
|
291
|
+
- External reference resolution (`--resolve-refs`)
|
|
292
|
+
|
|
293
|
+
### 2. Converters (`src/converters/`)
|
|
294
|
+
|
|
295
|
+
Transform input specifications to IR:
|
|
296
|
+
|
|
297
|
+
- **OpenAPIConverter**: OpenAPI 3.0 → SchemaIR
|
|
298
|
+
- **CRDConverter**: Kubernetes CRD → SchemaIR
|
|
299
|
+
- **JSONSchemaConverter**: JSON Schema → SchemaIR
|
|
300
|
+
|
|
301
|
+
**Responsibilities:**
|
|
302
|
+
- Schema traversal and extraction
|
|
303
|
+
- Type mapping (OpenAPI types → TypeScript types)
|
|
304
|
+
- Reference resolution
|
|
305
|
+
- Metadata extraction
|
|
306
|
+
|
|
307
|
+
### 3. Intermediate Representation (`src/ir/`)
|
|
308
|
+
|
|
309
|
+
Core data structures:
|
|
310
|
+
|
|
311
|
+
- **SchemaIR**: Top-level container
|
|
312
|
+
- **ObjectSchema**: Class/interface representation
|
|
313
|
+
- **Field**: Property representation
|
|
314
|
+
- **IRHelpers**: Utility functions for IR manipulation
|
|
315
|
+
|
|
316
|
+
### 4. Generator (`src/generator/`)
|
|
317
|
+
|
|
318
|
+
Produces TypeScript code from IR:
|
|
319
|
+
|
|
320
|
+
- **ClassGenerator**: Creates class declarations
|
|
321
|
+
- **PropertyGenerator**: Creates property declarations with decorators
|
|
322
|
+
- **ImportGenerator**: Manages import statements
|
|
323
|
+
- **ExportGenerator**: Creates barrel exports (index.ts)
|
|
324
|
+
|
|
325
|
+
**Uses ts-morph for:**
|
|
326
|
+
- AST construction
|
|
327
|
+
- Import management
|
|
328
|
+
- Formatting and pretty-printing
|
|
329
|
+
- Type-safe code generation
|
|
330
|
+
|
|
331
|
+
### 5. Plugins (`src/plugins/`)
|
|
332
|
+
|
|
333
|
+
Feature extensions:
|
|
334
|
+
|
|
335
|
+
- **CorePlugin**: Base class-transformer decorators (@Expose, @Type)
|
|
336
|
+
- **NestJSSwaggerPlugin**: @ApiProperty decorators (--nestjs-swagger)
|
|
337
|
+
- **ClassValidatorPlugin**: Validation decorators (--class-validator)
|
|
338
|
+
- **ESMPlugin**: .js extension injection (--esm)
|
|
339
|
+
|
|
340
|
+
### 6. CLI (`src/cli/`)
|
|
341
|
+
|
|
342
|
+
Command-line interface:
|
|
343
|
+
|
|
344
|
+
- **generate**: OpenAPI → TypeScript
|
|
345
|
+
- **download**: Download spec without generation
|
|
346
|
+
- **generate-crd**: Kubernetes CRD → TypeScript
|
|
347
|
+
- **generate-jsonschema**: JSON Schema → TypeScript
|
|
348
|
+
|
|
349
|
+
## Code Generation Pipeline
|
|
350
|
+
|
|
351
|
+
### Full Pipeline Flow
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
1. INPUT
|
|
355
|
+
├─ User runs CLI command
|
|
356
|
+
├─ Parse CLI arguments
|
|
357
|
+
└─ Initialize configuration
|
|
358
|
+
|
|
359
|
+
2. LOADING
|
|
360
|
+
├─ SpecLoader fetches specification
|
|
361
|
+
├─ Detect format (JSON/YAML)
|
|
362
|
+
├─ Parse with js-yaml or JSON.parse()
|
|
363
|
+
└─ Resolve external $refs if --resolve-refs
|
|
364
|
+
|
|
365
|
+
3. CONVERSION
|
|
366
|
+
├─ Select converter based on input format
|
|
367
|
+
├─ Convert to SchemaIR
|
|
368
|
+
├─ Merge IRs if multiple inputs (CRDs)
|
|
369
|
+
└─ Normalize and deduplicate
|
|
370
|
+
|
|
371
|
+
4. PLUGIN INITIALIZATION
|
|
372
|
+
├─ Load enabled plugins based on CLI flags
|
|
373
|
+
├─ Sort by priority
|
|
374
|
+
└─ Call beforeGeneration hooks
|
|
375
|
+
|
|
376
|
+
5. GENERATION
|
|
377
|
+
├─ Create ts-morph Project
|
|
378
|
+
├─ For each schema in IR:
|
|
379
|
+
│ ├─ Create SourceFile
|
|
380
|
+
│ ├─ Call onSchemaLoad hooks
|
|
381
|
+
│ ├─ Generate class declaration
|
|
382
|
+
│ ├─ Call onClassGeneration hooks
|
|
383
|
+
│ ├─ For each field:
|
|
384
|
+
│ │ ├─ Generate property
|
|
385
|
+
│ │ └─ Call onPropertyGeneration hooks
|
|
386
|
+
│ └─ Add imports
|
|
387
|
+
├─ Generate index.ts (barrel exports)
|
|
388
|
+
└─ Call afterGeneration hooks
|
|
389
|
+
|
|
390
|
+
6. OUTPUT
|
|
391
|
+
├─ Format all files (prettier via ts-morph)
|
|
392
|
+
├─ Write to disk
|
|
393
|
+
└─ Report statistics
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Example: Generating a Simple Class
|
|
397
|
+
|
|
398
|
+
**Input OpenAPI Schema:**
|
|
399
|
+
```yaml
|
|
400
|
+
components:
|
|
401
|
+
schemas:
|
|
402
|
+
User:
|
|
403
|
+
type: object
|
|
404
|
+
required:
|
|
405
|
+
- id
|
|
406
|
+
- email
|
|
407
|
+
properties:
|
|
408
|
+
id:
|
|
409
|
+
type: string
|
|
410
|
+
format: uuid
|
|
411
|
+
email:
|
|
412
|
+
type: string
|
|
413
|
+
format: email
|
|
414
|
+
name:
|
|
415
|
+
type: string
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Step 1: Convert to IR**
|
|
419
|
+
```typescript
|
|
420
|
+
{
|
|
421
|
+
name: 'User',
|
|
422
|
+
fields: [
|
|
423
|
+
{ name: 'id', type: 'string', required: true, format: 'uuid' },
|
|
424
|
+
{ name: 'email', type: 'string', required: true, format: 'email' },
|
|
425
|
+
{ name: 'name', type: 'string', required: false }
|
|
426
|
+
],
|
|
427
|
+
required: ['id', 'email']
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Step 2: Generate AST**
|
|
432
|
+
```typescript
|
|
433
|
+
const classDecl = sourceFile.addClass({
|
|
434
|
+
name: 'User',
|
|
435
|
+
isExported: true
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Core plugin adds @Expose
|
|
439
|
+
classDecl.addProperty({
|
|
440
|
+
name: 'id',
|
|
441
|
+
type: 'string',
|
|
442
|
+
decorators: [{ name: 'Expose' }, { name: 'IsUUID' }]
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Step 3: Output TypeScript**
|
|
447
|
+
```typescript
|
|
448
|
+
import { Expose } from 'class-transformer';
|
|
449
|
+
import { IsUUID, IsEmail, IsString, IsOptional } from 'class-validator';
|
|
450
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
451
|
+
|
|
452
|
+
export class User {
|
|
453
|
+
@Expose()
|
|
454
|
+
@ApiProperty({ type: String, format: 'uuid', required: true })
|
|
455
|
+
@IsUUID()
|
|
456
|
+
id: string;
|
|
457
|
+
|
|
458
|
+
@Expose()
|
|
459
|
+
@ApiProperty({ type: String, format: 'email', required: true })
|
|
460
|
+
@IsEmail()
|
|
461
|
+
email: string;
|
|
462
|
+
|
|
463
|
+
@Expose()
|
|
464
|
+
@ApiProperty({ type: String, required: false })
|
|
465
|
+
@IsOptional()
|
|
466
|
+
@IsString()
|
|
467
|
+
name?: string;
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Plugin System
|
|
472
|
+
|
|
473
|
+
### Creating a Custom Plugin
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { Plugin, GenerationContext, ObjectSchema, Field } from 'klasik';
|
|
477
|
+
import { ClassDeclaration, PropertyDeclaration } from 'ts-morph';
|
|
478
|
+
|
|
479
|
+
export class CustomPlugin implements Plugin {
|
|
480
|
+
name = 'my-custom-plugin';
|
|
481
|
+
priority = 100;
|
|
482
|
+
|
|
483
|
+
hooks = {
|
|
484
|
+
// Called once before generation starts
|
|
485
|
+
beforeGeneration(context: GenerationContext): void {
|
|
486
|
+
console.log(`Generating ${context.schemas.size} schemas`);
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
// Called for each schema
|
|
490
|
+
onSchemaLoad(schema: ObjectSchema): void {
|
|
491
|
+
// Modify schema before generation
|
|
492
|
+
schema.fields.forEach(field => {
|
|
493
|
+
if (field.name.startsWith('_')) {
|
|
494
|
+
field.name = field.name.slice(1); // Remove leading underscore
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
// Called when class is created
|
|
500
|
+
onClassGeneration(classNode: ClassDeclaration, schema: ObjectSchema): void {
|
|
501
|
+
// Add class-level decorators or JSDoc
|
|
502
|
+
classNode.addJsDoc({
|
|
503
|
+
description: schema.description || `Generated class for ${schema.name}`
|
|
504
|
+
});
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
// Called for each property
|
|
508
|
+
onPropertyGeneration(property: PropertyDeclaration, field: Field): void {
|
|
509
|
+
// Add custom decorators based on field metadata
|
|
510
|
+
if (field.format === 'date-time') {
|
|
511
|
+
property.addDecorator({
|
|
512
|
+
name: 'Transform',
|
|
513
|
+
arguments: ['({ value }) => new Date(value)', { isDecoratorFactory: true }]
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
// Called after all generation is complete
|
|
519
|
+
afterGeneration(context: GenerationContext): void {
|
|
520
|
+
// Post-process all files
|
|
521
|
+
for (const sourceFile of context.project.getSourceFiles()) {
|
|
522
|
+
// Add file-level comments, organize imports, etc.
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Plugin Registration
|
|
530
|
+
|
|
531
|
+
Plugins are registered in the generator configuration:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
const generator = new TypeScriptGenerator({
|
|
535
|
+
plugins: [
|
|
536
|
+
new CorePlugin(),
|
|
537
|
+
new ClassValidatorPlugin(),
|
|
538
|
+
new NestJSSwaggerPlugin(),
|
|
539
|
+
new CustomPlugin()
|
|
540
|
+
]
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Built-in Plugins
|
|
545
|
+
|
|
546
|
+
| Plugin | Priority | Flag | Purpose |
|
|
547
|
+
|--------|----------|------|---------|
|
|
548
|
+
| CorePlugin | 10 | (always) | @Expose, @Type decorators |
|
|
549
|
+
| ClassValidatorPlugin | 50 | --class-validator | @IsString, @IsNumber, etc. |
|
|
550
|
+
| NestJSSwaggerPlugin | 60 | --nestjs-swagger | @ApiProperty decorators |
|
|
551
|
+
| ESMPlugin | 200 | --esm | Add .js extensions to imports |
|
|
552
|
+
|
|
553
|
+
## Testing Strategy
|
|
554
|
+
|
|
555
|
+
### Test Coverage
|
|
556
|
+
|
|
557
|
+
Klasik has comprehensive test coverage across all components:
|
|
558
|
+
|
|
559
|
+
- **Total Tests**: 748 (as of latest build)
|
|
560
|
+
- **Test Framework**: Jest
|
|
561
|
+
- **Coverage Areas**:
|
|
562
|
+
- Loaders (URL fetching, format detection)
|
|
563
|
+
- Converters (OpenAPI, CRD, JSON Schema → IR)
|
|
564
|
+
- Generator (IR → TypeScript AST)
|
|
565
|
+
- Plugins (decorator generation)
|
|
566
|
+
- CLI (command parsing, execution)
|
|
567
|
+
- Integration (end-to-end generation)
|
|
568
|
+
|
|
569
|
+
### Test Organization
|
|
570
|
+
|
|
571
|
+
```
|
|
572
|
+
tests/
|
|
573
|
+
├── unit/
|
|
574
|
+
│ ├── loaders/
|
|
575
|
+
│ │ ├── spec-loader.test.ts
|
|
576
|
+
│ │ ├── crd-loader.test.ts
|
|
577
|
+
│ │ └── jsonschema-loader.test.ts
|
|
578
|
+
│ ├── converters/
|
|
579
|
+
│ │ ├── openapi-converter.test.ts
|
|
580
|
+
│ │ ├── crd-converter.test.ts
|
|
581
|
+
│ │ └── jsonschema-converter.test.ts
|
|
582
|
+
│ ├── generator/
|
|
583
|
+
│ │ ├── class-generator.test.ts
|
|
584
|
+
│ │ └── property-generator.test.ts
|
|
585
|
+
│ └── plugins/
|
|
586
|
+
│ ├── class-validator.test.ts
|
|
587
|
+
│ └── nestjs-swagger.test.ts
|
|
588
|
+
├── integration/
|
|
589
|
+
│ ├── openapi-generation.test.ts
|
|
590
|
+
│ ├── crd-generation.test.ts
|
|
591
|
+
│ └── jsonschema-generation.test.ts
|
|
592
|
+
└── fixtures/
|
|
593
|
+
├── openapi/
|
|
594
|
+
├── crds/
|
|
595
|
+
└── jsonschema/
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Test Examples
|
|
599
|
+
|
|
600
|
+
**Unit Test: Type Conversion**
|
|
601
|
+
```typescript
|
|
602
|
+
describe('OpenAPIConverter', () => {
|
|
603
|
+
it('should convert string type to TypeScript string', () => {
|
|
604
|
+
const field = converter.convertField({
|
|
605
|
+
type: 'string',
|
|
606
|
+
description: 'User name'
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
expect(field.type).toBe('string');
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('should convert array type with items', () => {
|
|
613
|
+
const field = converter.convertField({
|
|
614
|
+
type: 'array',
|
|
615
|
+
items: { type: 'string' }
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
expect(field.type).toBe('string');
|
|
619
|
+
expect(field.array).toBe(true);
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**Integration Test: Full Generation**
|
|
625
|
+
```typescript
|
|
626
|
+
describe('CRD Generation', () => {
|
|
627
|
+
it('should generate TypeScript from ArgoCD Application CRD', async () => {
|
|
628
|
+
const result = await generateFromCRD({
|
|
629
|
+
url: 'fixtures/application-crd.yaml',
|
|
630
|
+
output: 'tmp/output',
|
|
631
|
+
nestjsSwagger: true,
|
|
632
|
+
classValidator: true
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
expect(result.filesGenerated).toBeGreaterThan(0);
|
|
636
|
+
|
|
637
|
+
const applicationFile = fs.readFileSync('tmp/output/models/application.ts', 'utf-8');
|
|
638
|
+
expect(applicationFile).toContain('export class Application');
|
|
639
|
+
expect(applicationFile).toContain('@ApiProperty');
|
|
640
|
+
expect(applicationFile).toContain('@IsOptional');
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Running Tests
|
|
646
|
+
|
|
647
|
+
```bash
|
|
648
|
+
# Run all tests
|
|
649
|
+
npm test
|
|
650
|
+
|
|
651
|
+
# Run with coverage
|
|
652
|
+
npm run test:coverage
|
|
653
|
+
|
|
654
|
+
# Run specific test file
|
|
655
|
+
npm test -- spec-loader.test.ts
|
|
656
|
+
|
|
657
|
+
# Run in watch mode
|
|
658
|
+
npm test -- --watch
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
## Development Guide
|
|
662
|
+
|
|
663
|
+
### Project Structure
|
|
664
|
+
|
|
665
|
+
```
|
|
666
|
+
klasik-2/
|
|
667
|
+
├── src/
|
|
668
|
+
│ ├── cli/ # Command-line interface
|
|
669
|
+
│ │ ├── commands/ # CLI command implementations
|
|
670
|
+
│ │ └── index.ts # Main CLI entry point
|
|
671
|
+
│ ├── loaders/ # Specification loaders
|
|
672
|
+
│ ├── converters/ # Format converters to IR
|
|
673
|
+
│ ├── ir/ # Intermediate Representation
|
|
674
|
+
│ ├── generator/ # TypeScript code generator
|
|
675
|
+
│ ├── plugins/ # Plugin implementations
|
|
676
|
+
│ └── utils/ # Shared utilities
|
|
677
|
+
├── tests/ # Test suites
|
|
678
|
+
├── docs/ # Additional documentation
|
|
679
|
+
└── examples/ # Usage examples
|
|
680
|
+
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Building from Source
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
# Clone repository
|
|
687
|
+
git clone https://github.com/your-org/klasik-2.git
|
|
688
|
+
cd klasik-2
|
|
689
|
+
|
|
690
|
+
# Install dependencies
|
|
691
|
+
npm install
|
|
692
|
+
|
|
693
|
+
# Build TypeScript
|
|
694
|
+
npm run build
|
|
695
|
+
|
|
696
|
+
# Run CLI locally
|
|
697
|
+
node dist/cli/index.js generate --help
|
|
698
|
+
|
|
699
|
+
# Run tests
|
|
700
|
+
npm test
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Making Changes
|
|
704
|
+
|
|
705
|
+
1. **Add New Feature**:
|
|
706
|
+
- Create plugin in `src/plugins/`
|
|
707
|
+
- Register plugin in generator configuration
|
|
708
|
+
- Add CLI flag if needed
|
|
709
|
+
- Write unit tests
|
|
710
|
+
- Update documentation
|
|
711
|
+
|
|
712
|
+
2. **Add New Input Format**:
|
|
713
|
+
- Create loader in `src/loaders/`
|
|
714
|
+
- Create converter in `src/converters/`
|
|
715
|
+
- Add CLI command in `src/cli/commands/`
|
|
716
|
+
- Write integration tests
|
|
717
|
+
- Update README with examples
|
|
718
|
+
|
|
719
|
+
3. **Fix Bug**:
|
|
720
|
+
- Write failing test that reproduces bug
|
|
721
|
+
- Fix the issue
|
|
722
|
+
- Verify test passes
|
|
723
|
+
- Check for regression with full test suite
|
|
724
|
+
|
|
725
|
+
### Debugging
|
|
726
|
+
|
|
727
|
+
**Debug Generated Code:**
|
|
728
|
+
```typescript
|
|
729
|
+
// Enable verbose logging
|
|
730
|
+
const generator = new TypeScriptGenerator({ verbose: true });
|
|
731
|
+
|
|
732
|
+
// Inspect IR before generation
|
|
733
|
+
console.log(JSON.stringify(schemaIR, null, 2));
|
|
734
|
+
|
|
735
|
+
// Inspect AST after generation
|
|
736
|
+
const sourceFile = project.getSourceFile('user.ts');
|
|
737
|
+
console.log(sourceFile.getFullText());
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
**Debug Plugin Execution:**
|
|
741
|
+
```typescript
|
|
742
|
+
class DebugPlugin implements Plugin {
|
|
743
|
+
name = 'debug';
|
|
744
|
+
priority = 1; // Run first
|
|
745
|
+
|
|
746
|
+
hooks = {
|
|
747
|
+
onPropertyGeneration(property, field) {
|
|
748
|
+
console.log(`Generating property: ${field.name} (${field.type})`);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Performance Optimization
|
|
755
|
+
|
|
756
|
+
**IR Caching:**
|
|
757
|
+
```typescript
|
|
758
|
+
// Cache converted IR for repeated generation
|
|
759
|
+
const irCache = new Map<string, SchemaIR>();
|
|
760
|
+
|
|
761
|
+
if (irCache.has(specUrl)) {
|
|
762
|
+
schemaIR = irCache.get(specUrl);
|
|
763
|
+
} else {
|
|
764
|
+
schemaIR = await converter.toIR(spec);
|
|
765
|
+
irCache.set(specUrl, schemaIR);
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Parallel Generation:**
|
|
770
|
+
```typescript
|
|
771
|
+
// Generate multiple files in parallel
|
|
772
|
+
const promises = Array.from(schemaIR.schemas.entries()).map(([name, schema]) => {
|
|
773
|
+
return generateClass(schema);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
await Promise.all(promises);
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
## Design Decisions
|
|
780
|
+
|
|
781
|
+
### Why Not Use openapi-generator?
|
|
782
|
+
|
|
783
|
+
The official openapi-generator has limitations:
|
|
784
|
+
- Java-based (requires JVM)
|
|
785
|
+
- Heavy dependencies
|
|
786
|
+
- Difficult to customize
|
|
787
|
+
- Inconsistent TypeScript output
|
|
788
|
+
- No CRD or JSON Schema support
|
|
789
|
+
|
|
790
|
+
Klasik provides:
|
|
791
|
+
- Pure TypeScript/Node.js (no JVM)
|
|
792
|
+
- Lightweight and fast
|
|
793
|
+
- Easy plugin system for customization
|
|
794
|
+
- Consistent, high-quality output
|
|
795
|
+
- Multi-format support
|
|
796
|
+
|
|
797
|
+
### Why class-transformer?
|
|
798
|
+
|
|
799
|
+
class-transformer provides:
|
|
800
|
+
- Serialization/deserialization with @Type decorators
|
|
801
|
+
- Nested object support
|
|
802
|
+
- Transform hooks
|
|
803
|
+
- Integration with NestJS and other frameworks
|
|
804
|
+
|
|
805
|
+
Alternative approaches (plain interfaces) lack runtime type information.
|
|
806
|
+
|
|
807
|
+
### Why Mustache Templates?
|
|
808
|
+
|
|
809
|
+
Custom templates use Mustache because:
|
|
810
|
+
- Simple, logic-less syntax
|
|
811
|
+
- Easy to learn
|
|
812
|
+
- No arbitrary code execution (security)
|
|
813
|
+
- Works with any text format
|
|
814
|
+
|
|
815
|
+
Users can override default templates for custom output formats.
|
|
816
|
+
|
|
817
|
+
## Future Enhancements
|
|
818
|
+
|
|
819
|
+
Potential improvements for future versions:
|
|
820
|
+
|
|
821
|
+
1. **GraphQL Support**: Add GraphQL schema → TypeScript converter
|
|
822
|
+
2. **Zod Integration**: Generate Zod schemas alongside classes
|
|
823
|
+
3. **Async API Support**: Support AsyncAPI specifications
|
|
824
|
+
4. **Watch Mode**: Regenerate on spec file changes
|
|
825
|
+
5. **Incremental Generation**: Only regenerate changed schemas
|
|
826
|
+
6. **Source Maps**: Map generated code back to spec locations
|
|
827
|
+
7. **Custom Validators**: Plugin API for custom validation decorators
|
|
828
|
+
8. **gRPC Support**: Generate from Protocol Buffers
|
|
829
|
+
|
|
830
|
+
## Contributing
|
|
831
|
+
|
|
832
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
|
833
|
+
- Code style guidelines
|
|
834
|
+
- Pull request process
|
|
835
|
+
- Development workflow
|
|
836
|
+
- Release process
|
|
837
|
+
|
|
838
|
+
## References
|
|
839
|
+
|
|
840
|
+
- [ts-morph Documentation](https://ts-morph.com/)
|
|
841
|
+
- [OpenAPI Specification](https://swagger.io/specification/)
|
|
842
|
+
- [Kubernetes CRD Documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)
|
|
843
|
+
- [JSON Schema Specification](https://json-schema.org/)
|
|
844
|
+
- [class-transformer](https://github.com/typestack/class-transformer)
|
|
845
|
+
- [class-validator](https://github.com/typestack/class-validator)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# JSDoc Escaping Strategy
|
|
2
|
+
|
|
3
|
+
This document describes the JSDoc/TSDoc escaping strategy used in Klasik to prevent malformed comments in generated code.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
JSDoc comments in generated TypeScript code need careful handling of special characters to avoid breaking the comment syntax while preserving readability and functionality.
|
|
8
|
+
|
|
9
|
+
## Escaping Rules
|
|
10
|
+
|
|
11
|
+
### Characters That Are Escaped
|
|
12
|
+
|
|
13
|
+
1. **Comment Delimiters**
|
|
14
|
+
- `*/` → `*\/` - Closing comment delimiter (prevents premature comment termination)
|
|
15
|
+
- `/*` → `/\*` - Opening comment delimiter (prevents nested comment issues)
|
|
16
|
+
|
|
17
|
+
2. **At Symbol (Context-Aware)**
|
|
18
|
+
- `@` at start of line or after whitespace → `\@` - Prevents false JSDoc tags
|
|
19
|
+
- `@` in middle of text (e.g., `user@example.com`) → Preserved
|
|
20
|
+
|
|
21
|
+
3. **Newlines**
|
|
22
|
+
- `\n` → `\n * ` - Adds proper JSDoc line prefix for multiline comments
|
|
23
|
+
|
|
24
|
+
### Characters That Are Preserved
|
|
25
|
+
|
|
26
|
+
These characters are intentionally **not escaped** because they're essential for JSDoc functionality and readability:
|
|
27
|
+
|
|
28
|
+
1. **Backticks** (`)
|
|
29
|
+
- Used for inline code examples: `` `const foo = "bar"` ``
|
|
30
|
+
- Preserved for markdown-style code formatting
|
|
31
|
+
|
|
32
|
+
2. **Quotes** (`"` and `'`)
|
|
33
|
+
- Used in descriptions and examples
|
|
34
|
+
- No escaping needed within JSDoc comments
|
|
35
|
+
|
|
36
|
+
3. **Angle Brackets** (`<` and `>`)
|
|
37
|
+
- Used for generic type references: `Array<string>`, `Map<K, V>`
|
|
38
|
+
- Essential for TypeScript type documentation
|
|
39
|
+
|
|
40
|
+
4. **Curly Braces** (`{` and `}`)
|
|
41
|
+
- Used in JSDoc type annotations: `@type {string}`
|
|
42
|
+
- Used in object descriptions: `{key: value}`
|
|
43
|
+
|
|
44
|
+
5. **Slashes** (`/`)
|
|
45
|
+
- Only escaped when part of comment delimiters (`/*` or `*/`)
|
|
46
|
+
- Regular slashes preserved for paths and regex patterns
|
|
47
|
+
|
|
48
|
+
6. **Asterisks** (`*`)
|
|
49
|
+
- Only escaped when part of comment delimiters (`*/`)
|
|
50
|
+
- Regular asterisks preserved for emphasis
|
|
51
|
+
|
|
52
|
+
## Implementation
|
|
53
|
+
|
|
54
|
+
### Files
|
|
55
|
+
|
|
56
|
+
The escaping logic is implemented in two files:
|
|
57
|
+
|
|
58
|
+
1. **`src/generators/tsdoc-generator.ts`**
|
|
59
|
+
- `escapeJSDoc()` method
|
|
60
|
+
- Used for property docs, method docs, parameter docs
|
|
61
|
+
|
|
62
|
+
2. **`src/builders/class-builder.ts`**
|
|
63
|
+
- `escapeJsDocText()` method
|
|
64
|
+
- Used for class descriptions
|
|
65
|
+
|
|
66
|
+
Both implementations use the **same escaping logic** to ensure consistency.
|
|
67
|
+
|
|
68
|
+
### Code Example
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
/**
|
|
72
|
+
* Escape JSDoc special characters
|
|
73
|
+
*
|
|
74
|
+
* Handles special characters that could break JSDoc syntax:
|
|
75
|
+
* - Comment delimiters: */ and /*
|
|
76
|
+
* - Newlines: adds proper JSDoc line prefix
|
|
77
|
+
*
|
|
78
|
+
* Characters preserved for JSDoc functionality:
|
|
79
|
+
* - Backticks (`) for inline code
|
|
80
|
+
* - Angle brackets (<>) for type references
|
|
81
|
+
* - Curly braces ({}) for @type tags
|
|
82
|
+
* - @ symbol (context-aware escaping)
|
|
83
|
+
*/
|
|
84
|
+
private escapeJSDoc(text: string): string {
|
|
85
|
+
if (!text) return text;
|
|
86
|
+
|
|
87
|
+
return text
|
|
88
|
+
// Escape closing comment delimiter (MUST be first to avoid double-escaping)
|
|
89
|
+
.replace(/\*\//g, '*\\/')
|
|
90
|
+
// Escape opening comment delimiter
|
|
91
|
+
.replace(/\/\*/g, '/\\*')
|
|
92
|
+
// Escape @ at start of line to prevent false JSDoc tags
|
|
93
|
+
// Only escape @ when it's at the start of a line or after whitespace
|
|
94
|
+
.replace(/(^|\n)\s*@/g, '$1\\@')
|
|
95
|
+
// Handle newlines by adding JSDoc line prefix
|
|
96
|
+
.replace(/\n/g, '\n * ');
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Examples
|
|
101
|
+
|
|
102
|
+
### Example 1: Comment Delimiters
|
|
103
|
+
|
|
104
|
+
**Input:**
|
|
105
|
+
```javascript
|
|
106
|
+
description: "This function /* does something */ important"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Output:**
|
|
110
|
+
```javascript
|
|
111
|
+
/**
|
|
112
|
+
* This function /\* does something *\/ important
|
|
113
|
+
*/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Example 2: Inline Code with Backticks
|
|
117
|
+
|
|
118
|
+
**Input:**
|
|
119
|
+
```javascript
|
|
120
|
+
description: "Use `const foo = "bar"` for constants"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Output:**
|
|
124
|
+
```javascript
|
|
125
|
+
/**
|
|
126
|
+
* Use `const foo = "bar"` for constants
|
|
127
|
+
*/
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Example 3: Type References
|
|
131
|
+
|
|
132
|
+
**Input:**
|
|
133
|
+
```javascript
|
|
134
|
+
description: "Returns Array<User> or Map<string, User>"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Output:**
|
|
138
|
+
```javascript
|
|
139
|
+
/**
|
|
140
|
+
* Returns Array<User> or Map<string, User>
|
|
141
|
+
*/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Example 4: Email Addresses
|
|
145
|
+
|
|
146
|
+
**Input:**
|
|
147
|
+
```javascript
|
|
148
|
+
description: "Email like @username or user@example.com"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Output:**
|
|
152
|
+
```javascript
|
|
153
|
+
/**
|
|
154
|
+
* Email like \@username or user@example.com
|
|
155
|
+
*/
|
|
156
|
+
```
|
|
157
|
+
(Note: `@username` at start is escaped, but `user@example.com` is preserved)
|
|
158
|
+
|
|
159
|
+
### Example 5: Multiline Descriptions
|
|
160
|
+
|
|
161
|
+
**Input:**
|
|
162
|
+
```javascript
|
|
163
|
+
description: "Line 1\nLine 2 with `code`\nLine 3"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Output:**
|
|
167
|
+
```javascript
|
|
168
|
+
/**
|
|
169
|
+
* Line 1
|
|
170
|
+
* Line 2 with `code`
|
|
171
|
+
* Line 3
|
|
172
|
+
*/
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Example 6: Complex Example
|
|
176
|
+
|
|
177
|
+
**Input:**
|
|
178
|
+
```javascript
|
|
179
|
+
description: "Example: `const regex = /pattern/;` creates /* comment */ with types like Array<T>"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Output:**
|
|
183
|
+
```javascript
|
|
184
|
+
/**
|
|
185
|
+
* Example: `const regex = /pattern/;` creates /\* comment *\/ with types like Array<T>
|
|
186
|
+
*/
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Design Rationale
|
|
190
|
+
|
|
191
|
+
### Why Not Escape Everything?
|
|
192
|
+
|
|
193
|
+
We could escape all special characters, but this would harm readability:
|
|
194
|
+
|
|
195
|
+
❌ **Over-Escaping Approach:**
|
|
196
|
+
```javascript
|
|
197
|
+
/**
|
|
198
|
+
* Use \`backticks\` for code like Array\<string\> with \{key: value\}
|
|
199
|
+
*/
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
✅ **Our Approach:**
|
|
203
|
+
```javascript
|
|
204
|
+
/**
|
|
205
|
+
* Use `backticks` for code like Array<string> with {key: value}
|
|
206
|
+
*/
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Escape Order Matters
|
|
210
|
+
|
|
211
|
+
The order of replacements is critical:
|
|
212
|
+
|
|
213
|
+
1. **`*/` must be escaped FIRST** - Otherwise, escaping `/*` could create `*\/` which might be further processed
|
|
214
|
+
2. **`/*` escaping** - After closing delimiter is safe
|
|
215
|
+
3. **`@` escaping** - Context-aware, only at line start
|
|
216
|
+
4. **Newlines** - Last, adds JSDoc line prefix
|
|
217
|
+
|
|
218
|
+
### Context-Aware Escaping
|
|
219
|
+
|
|
220
|
+
The `@` symbol is only escaped when it could be mistaken for a JSDoc tag:
|
|
221
|
+
|
|
222
|
+
- `@param` at line start → escaped to `\@param`
|
|
223
|
+
- `user@example.com` in text → preserved
|
|
224
|
+
- `@username` at start → escaped to `\@username`
|
|
225
|
+
|
|
226
|
+
## Testing
|
|
227
|
+
|
|
228
|
+
Comprehensive test coverage ensures the escaping works correctly:
|
|
229
|
+
|
|
230
|
+
### Test Files
|
|
231
|
+
|
|
232
|
+
1. **`src/generators/__tests__/tsdoc-generator.test.ts`**
|
|
233
|
+
- 45 tests covering all property and method doc scenarios
|
|
234
|
+
- Tests for all special characters and edge cases
|
|
235
|
+
|
|
236
|
+
2. **`src/builders/__tests__/class-builder.test.ts`**
|
|
237
|
+
- 45 tests covering class-level documentation
|
|
238
|
+
- Tests for escaping in class descriptions
|
|
239
|
+
|
|
240
|
+
### Test Coverage
|
|
241
|
+
|
|
242
|
+
- ✅ Comment delimiters (`/*` and `*/`)
|
|
243
|
+
- ✅ Backticks for inline code
|
|
244
|
+
- ✅ Single and double quotes
|
|
245
|
+
- ✅ Angle brackets for types
|
|
246
|
+
- ✅ Curly braces
|
|
247
|
+
- ✅ At symbols (@)
|
|
248
|
+
- ✅ Multiline descriptions
|
|
249
|
+
- ✅ Complex combinations
|
|
250
|
+
- ✅ Pattern constraints
|
|
251
|
+
- ✅ Example values
|
|
252
|
+
- ✅ Default values
|
|
253
|
+
- ✅ Parameter descriptions
|
|
254
|
+
- ✅ Request body descriptions
|
|
255
|
+
|
|
256
|
+
## Maintenance
|
|
257
|
+
|
|
258
|
+
When modifying the escaping logic:
|
|
259
|
+
|
|
260
|
+
1. **Update both files** - `tsdoc-generator.ts` and `class-builder.ts` must use identical logic
|
|
261
|
+
2. **Update tests** - Add test cases for new scenarios
|
|
262
|
+
3. **Verify generated code** - Run generation and inspect output
|
|
263
|
+
4. **Check TypeScript compilation** - Ensure generated code compiles without errors
|
|
264
|
+
5. **Update this document** - Keep documentation in sync with implementation
|
|
265
|
+
|
|
266
|
+
## Related Issues
|
|
267
|
+
|
|
268
|
+
This implementation addresses the following concerns:
|
|
269
|
+
|
|
270
|
+
- Prevents malformed JSDoc comments that break TypeScript compilation
|
|
271
|
+
- Maintains readability of generated documentation
|
|
272
|
+
- Preserves JSDoc functionality (tags, types, inline code)
|
|
273
|
+
- Handles edge cases like nested structures and multiline text
|
|
274
|
+
- Consistent behavior across all generated code (models, APIs, properties)
|
|
275
|
+
|
|
276
|
+
## References
|
|
277
|
+
|
|
278
|
+
- [TSDoc Specification](https://tsdoc.org/)
|
|
279
|
+
- [JSDoc Documentation](https://jsdoc.app/)
|
|
280
|
+
- [TypeScript JSDoc Reference](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# JSON Schema Support Matrix
|
|
2
2
|
|
|
3
|
-
This document outlines which JSON Schema features are supported by Klasik
|
|
3
|
+
This document outlines which JSON Schema features are supported by Klasik for generating TypeScript models.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Klasik
|
|
7
|
+
Klasik provides solid support for common JSON Schema Draft 7 features but lacks support for some advanced patterns like `patternProperties`, `not`, and `dependencies`. This affects complex real-world schemas like GitHub Workflow but works well for most standard schemas.
|
|
8
8
|
|
|
9
9
|
## Supported Features
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klasik",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "TypeScript code generator from OpenAPI/CRD/JSON Schema - rebuilt from ground up with AST-based generation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -37,13 +37,10 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"axios": "^1.6.0",
|
|
39
39
|
"chalk": "^4.1.2",
|
|
40
|
-
"class-transformer": "^0.5.1",
|
|
41
|
-
"class-validator": "^0.14.0",
|
|
42
40
|
"commander": "^11.0.0",
|
|
43
41
|
"js-yaml": "^4.1.0",
|
|
44
42
|
"mustache": "^4.2.0",
|
|
45
43
|
"ora": "^5.4.1",
|
|
46
|
-
"reflect-metadata": "^0.2.0",
|
|
47
44
|
"ts-morph": "^21.0.0"
|
|
48
45
|
},
|
|
49
46
|
"devDependencies": {
|
|
@@ -51,7 +48,10 @@
|
|
|
51
48
|
"@types/js-yaml": "^4.0.9",
|
|
52
49
|
"@types/mustache": "^4.2.0",
|
|
53
50
|
"@types/node": "^20.0.0",
|
|
51
|
+
"class-transformer": "^0.5.1",
|
|
52
|
+
"class-validator": "^0.14.0",
|
|
54
53
|
"jest": "^29.5.0",
|
|
54
|
+
"reflect-metadata": "^0.2.0",
|
|
55
55
|
"ts-jest": "^29.1.0",
|
|
56
56
|
"typescript": "^5.0.0"
|
|
57
57
|
}
|