ts-class-to-openapi 1.0.4 → 1.0.5
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 +39 -31
- package/dist/__test__/circular-reference.test.d.ts +1 -0
- package/dist/__test__/entities/circular.entity.d.ts +59 -0
- package/dist/__test__/index.d.ts +3 -0
- package/dist/__test__/ref-pattern.test.d.ts +1 -0
- package/dist/__test__/singleton-behavior.test.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +307 -57
- package/dist/index.js +307 -56
- package/dist/run.js +307 -56
- package/dist/transformer.d.ts +137 -33
- package/dist/types.d.ts +17 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -97,6 +97,12 @@ class SchemaTransformer {
|
|
|
97
97
|
* @private
|
|
98
98
|
*/
|
|
99
99
|
loadedFiles = new Set();
|
|
100
|
+
/**
|
|
101
|
+
* Set of class names currently being processed to prevent circular references
|
|
102
|
+
* Key format: "fileName:className" for uniqueness across different files
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
processingClasses = new Set();
|
|
100
106
|
/**
|
|
101
107
|
* Private constructor for singleton pattern.
|
|
102
108
|
*
|
|
@@ -149,20 +155,19 @@ class SchemaTransformer {
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
/**
|
|
152
|
-
*
|
|
158
|
+
* Transforms a class by its name into an OpenAPI schema object.
|
|
159
|
+
* Considers the context of the calling file to resolve ambiguous class names.
|
|
160
|
+
* Includes circular reference detection to prevent infinite recursion.
|
|
153
161
|
*
|
|
154
|
-
* @param className - The name of the class to
|
|
155
|
-
* @param
|
|
156
|
-
* @returns
|
|
162
|
+
* @param className - The name of the class to transform
|
|
163
|
+
* @param contextFilePath - Optional path to context file for resolving class ambiguity
|
|
164
|
+
* @returns Object containing the class name and its corresponding JSON schema
|
|
165
|
+
* @throws {Error} When the specified class cannot be found
|
|
157
166
|
* @private
|
|
158
167
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return sourceFile ? [sourceFile] : [];
|
|
163
|
-
}
|
|
164
|
-
// Only get source files that are not declaration files and not in node_modules
|
|
165
|
-
return this.program.getSourceFiles().filter(sf => {
|
|
168
|
+
transformByName(className, contextFilePath) {
|
|
169
|
+
// Get all relevant source files (not declaration files and not in node_modules)
|
|
170
|
+
const sourceFiles = this.program.getSourceFiles().filter(sf => {
|
|
166
171
|
if (sf.isDeclarationFile)
|
|
167
172
|
return false;
|
|
168
173
|
if (sf.fileName.includes('.d.ts'))
|
|
@@ -173,23 +178,9 @@ class SchemaTransformer {
|
|
|
173
178
|
this.loadedFiles.add(sf.fileName);
|
|
174
179
|
return true;
|
|
175
180
|
});
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Transforms a class by its name into an OpenAPI schema object.
|
|
179
|
-
* Now considers the context of the calling file to resolve ambiguous class names.
|
|
180
|
-
*
|
|
181
|
-
* @param className - The name of the class to transform
|
|
182
|
-
* @param filePath - Optional path to the file containing the class
|
|
183
|
-
* @param contextFile - Optional context file for resolving class ambiguity
|
|
184
|
-
* @returns Object containing the class name and its corresponding JSON schema
|
|
185
|
-
* @throws {Error} When the specified class cannot be found
|
|
186
|
-
* @private
|
|
187
|
-
*/
|
|
188
|
-
transformByName(className, filePath, contextFile) {
|
|
189
|
-
const sourceFiles = this.getRelevantSourceFiles(className, filePath);
|
|
190
181
|
// If we have a context file, try to find the class in that file first
|
|
191
|
-
if (
|
|
192
|
-
const contextSourceFile = this.program.getSourceFile(
|
|
182
|
+
if (contextFilePath) {
|
|
183
|
+
const contextSourceFile = this.program.getSourceFile(contextFilePath);
|
|
193
184
|
if (contextSourceFile) {
|
|
194
185
|
const classNode = this.findClassByName(contextSourceFile, className);
|
|
195
186
|
if (classNode) {
|
|
@@ -198,15 +189,34 @@ class SchemaTransformer {
|
|
|
198
189
|
if (this.classCache.has(cacheKey)) {
|
|
199
190
|
return this.classCache.get(cacheKey);
|
|
200
191
|
}
|
|
201
|
-
|
|
202
|
-
this.
|
|
203
|
-
|
|
204
|
-
|
|
192
|
+
// Check for circular reference before processing
|
|
193
|
+
if (this.processingClasses.has(cacheKey)) {
|
|
194
|
+
// Return a $ref reference to break circular dependency (OpenAPI 3.1 style)
|
|
195
|
+
return {
|
|
196
|
+
name: className,
|
|
197
|
+
schema: {
|
|
198
|
+
$ref: `#/components/schemas/${className}`,
|
|
199
|
+
description: `Reference to ${className} (circular reference detected)`,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Mark this class as being processed
|
|
204
|
+
this.processingClasses.add(cacheKey);
|
|
205
|
+
try {
|
|
206
|
+
const result = this.transformClass(classNode, contextSourceFile);
|
|
207
|
+
this.classCache.set(cacheKey, result);
|
|
208
|
+
this.cleanupCache();
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
// Always remove from processing set when done
|
|
213
|
+
this.processingClasses.delete(cacheKey);
|
|
214
|
+
}
|
|
205
215
|
}
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
// Fallback to searching all files, but prioritize files that are more likely to be relevant
|
|
209
|
-
const prioritizedFiles = this.prioritizeSourceFiles(sourceFiles,
|
|
219
|
+
const prioritizedFiles = this.prioritizeSourceFiles(sourceFiles, contextFilePath);
|
|
210
220
|
for (const sourceFile of prioritizedFiles) {
|
|
211
221
|
const classNode = this.findClassByName(sourceFile, className);
|
|
212
222
|
if (classNode && sourceFile?.fileName) {
|
|
@@ -215,12 +225,31 @@ class SchemaTransformer {
|
|
|
215
225
|
if (this.classCache.has(cacheKey)) {
|
|
216
226
|
return this.classCache.get(cacheKey);
|
|
217
227
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
// Check for circular reference before processing
|
|
229
|
+
if (this.processingClasses.has(cacheKey)) {
|
|
230
|
+
// Return a $ref reference to break circular dependency (OpenAPI 3.1 style)
|
|
231
|
+
return {
|
|
232
|
+
name: className,
|
|
233
|
+
schema: {
|
|
234
|
+
$ref: `#/components/schemas/${className}`,
|
|
235
|
+
description: `Reference to ${className} (circular reference detected)`,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Mark this class as being processed
|
|
240
|
+
this.processingClasses.add(cacheKey);
|
|
241
|
+
try {
|
|
242
|
+
const result = this.transformClass(classNode, sourceFile);
|
|
243
|
+
// Cache using fileName:className as key for uniqueness
|
|
244
|
+
this.classCache.set(cacheKey, result);
|
|
245
|
+
// Clean up cache if it gets too large
|
|
246
|
+
this.cleanupCache();
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
// Always remove from processing set when done
|
|
251
|
+
this.processingClasses.delete(cacheKey);
|
|
252
|
+
}
|
|
224
253
|
}
|
|
225
254
|
}
|
|
226
255
|
throw new Error(`Class ${className} not found`);
|
|
@@ -230,15 +259,15 @@ class SchemaTransformer {
|
|
|
230
259
|
* Gives priority to files in the same directory or with similar names.
|
|
231
260
|
*
|
|
232
261
|
* @param sourceFiles - Array of source files to prioritize
|
|
233
|
-
* @param
|
|
262
|
+
* @param contextFilePath - Optional path to context file for prioritization
|
|
234
263
|
* @returns Prioritized array of source files
|
|
235
264
|
* @private
|
|
236
265
|
*/
|
|
237
|
-
prioritizeSourceFiles(sourceFiles,
|
|
238
|
-
if (!
|
|
266
|
+
prioritizeSourceFiles(sourceFiles, contextFilePath) {
|
|
267
|
+
if (!contextFilePath) {
|
|
239
268
|
return sourceFiles;
|
|
240
269
|
}
|
|
241
|
-
const contextDir =
|
|
270
|
+
const contextDir = contextFilePath.substring(0, contextFilePath.lastIndexOf('/'));
|
|
242
271
|
return sourceFiles.sort((a, b) => {
|
|
243
272
|
const aDir = a.fileName.substring(0, a.fileName.lastIndexOf('/'));
|
|
244
273
|
const bDir = b.fileName.substring(0, b.fileName.lastIndexOf('/'));
|
|
@@ -283,16 +312,88 @@ class SchemaTransformer {
|
|
|
283
312
|
/**
|
|
284
313
|
* Clears the current singleton instance. Useful for testing or when you need
|
|
285
314
|
* to create a new instance with different configuration.
|
|
315
|
+
* @private
|
|
286
316
|
*/
|
|
287
317
|
static clearInstance() {
|
|
288
|
-
SchemaTransformer.instance =
|
|
318
|
+
SchemaTransformer.instance = undefined;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Flag to prevent recursive disposal calls
|
|
322
|
+
* @private
|
|
323
|
+
*/
|
|
324
|
+
static disposingInProgress = false;
|
|
325
|
+
/**
|
|
326
|
+
* Completely disposes of the current singleton instance and releases all resources.
|
|
327
|
+
* This is a static method that can be called without having an instance reference.
|
|
328
|
+
* Ensures complete memory cleanup regardless of the current state.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```typescript
|
|
332
|
+
* SchemaTransformer.disposeInstance();
|
|
333
|
+
* // All resources released, next getInstance() will create fresh instance
|
|
334
|
+
* ```
|
|
335
|
+
*
|
|
336
|
+
* @public
|
|
337
|
+
*/
|
|
338
|
+
static disposeInstance() {
|
|
339
|
+
// Prevent recursive disposal calls
|
|
340
|
+
if (SchemaTransformer.disposingInProgress) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
SchemaTransformer.disposingInProgress = true;
|
|
344
|
+
try {
|
|
345
|
+
if (SchemaTransformer.instance) {
|
|
346
|
+
SchemaTransformer.instance.dispose();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
// Log any disposal errors but continue with cleanup
|
|
351
|
+
console.warn('Warning during static disposal:', error);
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
// Always ensure the static instance is cleared
|
|
355
|
+
SchemaTransformer.instance = undefined;
|
|
356
|
+
SchemaTransformer.disposingInProgress = false;
|
|
357
|
+
// Force garbage collection for cleanup
|
|
358
|
+
if (global.gc) {
|
|
359
|
+
global.gc();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* @deprecated Use disposeInstance() instead for better clarity
|
|
365
|
+
* @private
|
|
366
|
+
*/
|
|
367
|
+
static dispose() {
|
|
368
|
+
SchemaTransformer.disposeInstance();
|
|
289
369
|
}
|
|
290
370
|
static getInstance(tsConfigPath, options) {
|
|
291
|
-
if (!SchemaTransformer.instance) {
|
|
371
|
+
if (!SchemaTransformer.instance || SchemaTransformer.isInstanceDisposed()) {
|
|
292
372
|
SchemaTransformer.instance = new SchemaTransformer(tsConfigPath, options);
|
|
293
373
|
}
|
|
294
374
|
return SchemaTransformer.instance;
|
|
295
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Internal method to check if current instance is disposed
|
|
378
|
+
* @private
|
|
379
|
+
*/
|
|
380
|
+
static isInstanceDisposed() {
|
|
381
|
+
return SchemaTransformer.instance
|
|
382
|
+
? SchemaTransformer.instance.isDisposed()
|
|
383
|
+
: true;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Transforms a class using the singleton instance
|
|
387
|
+
* @param cls - The class constructor function to transform
|
|
388
|
+
* @param options - Optional configuration for memory management (only used if no instance exists)
|
|
389
|
+
* @returns Object containing the class name and its corresponding JSON schema
|
|
390
|
+
* @public
|
|
391
|
+
*/
|
|
392
|
+
static transformClass(cls, options) {
|
|
393
|
+
// Use the singleton instance instead of creating a temporary one
|
|
394
|
+
const transformer = SchemaTransformer.getInstance(undefined, options);
|
|
395
|
+
return transformer.transform(cls);
|
|
396
|
+
}
|
|
296
397
|
/**
|
|
297
398
|
* Transforms a class constructor function into an OpenAPI schema object.
|
|
298
399
|
*
|
|
@@ -327,31 +428,147 @@ class SchemaTransformer {
|
|
|
327
428
|
clearCache() {
|
|
328
429
|
this.classCache.clear();
|
|
329
430
|
this.loadedFiles.clear();
|
|
431
|
+
this.processingClasses.clear();
|
|
330
432
|
// Force garbage collection hint if available
|
|
331
433
|
if (global.gc) {
|
|
332
434
|
global.gc();
|
|
333
435
|
}
|
|
334
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Completely disposes of the transformer instance and releases all resources.
|
|
439
|
+
* This includes clearing all caches, releasing TypeScript program resources,
|
|
440
|
+
* and resetting the singleton instance.
|
|
441
|
+
*
|
|
442
|
+
* After calling this method, you need to call getInstance() again to get a new instance.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```typescript
|
|
446
|
+
* const transformer = SchemaTransformer.getInstance();
|
|
447
|
+
* // ... use transformer
|
|
448
|
+
* transformer.dispose();
|
|
449
|
+
* // transformer is now unusable, need to get new instance
|
|
450
|
+
* const newTransformer = SchemaTransformer.getInstance();
|
|
451
|
+
* ```
|
|
452
|
+
*
|
|
453
|
+
* @private
|
|
454
|
+
*/
|
|
455
|
+
dispose() {
|
|
456
|
+
try {
|
|
457
|
+
// Clear all caches and sets completely
|
|
458
|
+
this.classCache.clear();
|
|
459
|
+
this.loadedFiles.clear();
|
|
460
|
+
this.processingClasses.clear();
|
|
461
|
+
// Release TypeScript program resources
|
|
462
|
+
// While TypeScript doesn't provide explicit disposal methods,
|
|
463
|
+
// we can help garbage collection by clearing all references
|
|
464
|
+
// Clear all references to TypeScript objects
|
|
465
|
+
// @ts-ignore - We're intentionally setting these to null for cleanup
|
|
466
|
+
this.program = null;
|
|
467
|
+
// @ts-ignore - We're intentionally setting these to null for cleanup
|
|
468
|
+
this.checker = null;
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
// If there's any error during disposal, log it but continue
|
|
472
|
+
console.warn('Warning during transformer disposal:', error);
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
// Force garbage collection for cleanup
|
|
476
|
+
if (global.gc) {
|
|
477
|
+
global.gc();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Completely resets the transformer by disposing current instance and creating a new one.
|
|
483
|
+
* This is useful when you need a fresh start with different TypeScript configuration
|
|
484
|
+
* or want to ensure all resources are properly released and recreated.
|
|
485
|
+
*
|
|
486
|
+
* @param tsConfigPath - Optional path to a specific TypeScript config file for the new instance
|
|
487
|
+
* @param options - Configuration options for memory management for the new instance
|
|
488
|
+
* @returns A fresh SchemaTransformer instance
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```typescript
|
|
492
|
+
* const transformer = SchemaTransformer.getInstance();
|
|
493
|
+
* // ... use transformer
|
|
494
|
+
* const freshTransformer = transformer.reset('./new-tsconfig.json');
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* @private
|
|
498
|
+
*/
|
|
499
|
+
reset(tsConfigPath, options) {
|
|
500
|
+
// Dispose current instance using static method to properly clear instance reference
|
|
501
|
+
SchemaTransformer.disposeInstance();
|
|
502
|
+
// Create and return new instance
|
|
503
|
+
return SchemaTransformer.getInstance(tsConfigPath, options);
|
|
504
|
+
}
|
|
335
505
|
/**
|
|
336
506
|
* Gets memory usage statistics for monitoring and debugging.
|
|
337
507
|
*
|
|
338
|
-
* @returns Object containing cache size
|
|
508
|
+
* @returns Object containing cache size, loaded files count, and processing status
|
|
339
509
|
*
|
|
340
510
|
* @example
|
|
341
511
|
* ```typescript
|
|
342
512
|
* const transformer = SchemaTransformer.getInstance();
|
|
343
513
|
* const stats = transformer.getMemoryStats();
|
|
344
514
|
* console.log(`Cache entries: ${stats.cacheSize}, Files loaded: ${stats.loadedFiles}`);
|
|
515
|
+
* console.log(`Currently processing: ${stats.currentlyProcessing} classes`);
|
|
345
516
|
* ```
|
|
346
517
|
*
|
|
347
|
-
* @
|
|
518
|
+
* @private
|
|
348
519
|
*/
|
|
349
520
|
getMemoryStats() {
|
|
350
521
|
return {
|
|
351
|
-
cacheSize: this.classCache
|
|
352
|
-
loadedFiles: this.loadedFiles
|
|
522
|
+
cacheSize: this.classCache?.size || 0,
|
|
523
|
+
loadedFiles: this.loadedFiles?.size || 0,
|
|
524
|
+
currentlyProcessing: this.processingClasses?.size || 0,
|
|
525
|
+
maxCacheSize: this.maxCacheSize || 0,
|
|
526
|
+
autoCleanup: this.autoCleanup || false,
|
|
527
|
+
isDisposed: !this.program || !this.checker,
|
|
353
528
|
};
|
|
354
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Checks if the transformer instance has been disposed and is no longer usable.
|
|
532
|
+
*
|
|
533
|
+
* @returns True if the instance has been disposed
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* const transformer = SchemaTransformer.getInstance();
|
|
538
|
+
* transformer.dispose();
|
|
539
|
+
* console.log(transformer.isDisposed()); // true
|
|
540
|
+
* ```
|
|
541
|
+
*
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
isDisposed() {
|
|
545
|
+
return (!this.program ||
|
|
546
|
+
!this.checker ||
|
|
547
|
+
!this.classCache ||
|
|
548
|
+
!this.loadedFiles ||
|
|
549
|
+
!this.processingClasses);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Static method to check if there's an active singleton instance.
|
|
553
|
+
*
|
|
554
|
+
* @returns True if there's an active instance, false if disposed or never created
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```typescript
|
|
558
|
+
* console.log(SchemaTransformer.hasActiveInstance()); // false
|
|
559
|
+
* const transformer = SchemaTransformer.getInstance();
|
|
560
|
+
* console.log(SchemaTransformer.hasActiveInstance()); // true
|
|
561
|
+
* SchemaTransformer.dispose();
|
|
562
|
+
* console.log(SchemaTransformer.hasActiveInstance()); // false
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* @private
|
|
566
|
+
*/
|
|
567
|
+
static hasActiveInstance() {
|
|
568
|
+
return (SchemaTransformer.instance !== null &&
|
|
569
|
+
SchemaTransformer.instance !== undefined &&
|
|
570
|
+
!SchemaTransformer.isInstanceDisposed());
|
|
571
|
+
}
|
|
355
572
|
/**
|
|
356
573
|
* Finds a class declaration by name within a source file.
|
|
357
574
|
*
|
|
@@ -782,20 +999,24 @@ class SchemaTransformer {
|
|
|
782
999
|
* Generates an OpenAPI schema from extracted property information.
|
|
783
1000
|
*
|
|
784
1001
|
* @param properties - Array of property information to process
|
|
785
|
-
* @param
|
|
1002
|
+
* @param contextFilePath - Optional context file path for resolving class references
|
|
786
1003
|
* @returns Complete OpenAPI schema object with properties and validation rules
|
|
787
1004
|
* @private
|
|
788
1005
|
*/
|
|
789
|
-
generateSchema(properties,
|
|
1006
|
+
generateSchema(properties, contextFilePath) {
|
|
790
1007
|
const schema = {
|
|
791
1008
|
type: 'object',
|
|
792
1009
|
properties: {},
|
|
793
1010
|
required: [],
|
|
794
1011
|
};
|
|
795
1012
|
for (const property of properties) {
|
|
796
|
-
const { type, format, nestedSchema } = this.mapTypeToSchema(property.type,
|
|
1013
|
+
const { type, format, nestedSchema } = this.mapTypeToSchema(property.type, contextFilePath);
|
|
797
1014
|
if (nestedSchema) {
|
|
798
1015
|
schema.properties[property.name] = nestedSchema;
|
|
1016
|
+
// Skip decorator application for $ref schemas
|
|
1017
|
+
if (this.isRefSchema(nestedSchema)) {
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
799
1020
|
}
|
|
800
1021
|
else {
|
|
801
1022
|
schema.properties[property.name] = { type };
|
|
@@ -818,15 +1039,15 @@ class SchemaTransformer {
|
|
|
818
1039
|
* Handles primitive types, arrays, and nested objects recursively.
|
|
819
1040
|
*
|
|
820
1041
|
* @param type - The TypeScript type string to map
|
|
821
|
-
* @param
|
|
1042
|
+
* @param contextFilePath - Optional context file path for resolving class references
|
|
822
1043
|
* @returns Object containing OpenAPI type, optional format, and nested schema
|
|
823
1044
|
* @private
|
|
824
1045
|
*/
|
|
825
|
-
mapTypeToSchema(type,
|
|
1046
|
+
mapTypeToSchema(type, contextFilePath) {
|
|
826
1047
|
// Handle arrays
|
|
827
1048
|
if (type.endsWith('[]')) {
|
|
828
1049
|
const elementType = type.slice(0, -2);
|
|
829
|
-
const elementSchema = this.mapTypeToSchema(elementType,
|
|
1050
|
+
const elementSchema = this.mapTypeToSchema(elementType, contextFilePath);
|
|
830
1051
|
const items = elementSchema.nestedSchema || {
|
|
831
1052
|
type: elementSchema.type,
|
|
832
1053
|
};
|
|
@@ -887,7 +1108,14 @@ class SchemaTransformer {
|
|
|
887
1108
|
}
|
|
888
1109
|
// Handle nested objects
|
|
889
1110
|
try {
|
|
890
|
-
const nestedResult = this.transformByName(type,
|
|
1111
|
+
const nestedResult = this.transformByName(type, contextFilePath);
|
|
1112
|
+
// Check if it's a $ref schema (circular reference)
|
|
1113
|
+
if (nestedResult.schema.$ref) {
|
|
1114
|
+
return {
|
|
1115
|
+
type: constants.jsPrimitives.Object.value,
|
|
1116
|
+
nestedSchema: nestedResult.schema,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
891
1119
|
return {
|
|
892
1120
|
type: constants.jsPrimitives.Object.value,
|
|
893
1121
|
nestedSchema: nestedResult.schema,
|
|
@@ -898,6 +1126,16 @@ class SchemaTransformer {
|
|
|
898
1126
|
}
|
|
899
1127
|
}
|
|
900
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Checks if a schema is a $ref schema (circular reference).
|
|
1131
|
+
*
|
|
1132
|
+
* @param schema - The schema to check
|
|
1133
|
+
* @returns True if it's a $ref schema
|
|
1134
|
+
* @private
|
|
1135
|
+
*/
|
|
1136
|
+
isRefSchema(schema) {
|
|
1137
|
+
return '$ref' in schema;
|
|
1138
|
+
}
|
|
901
1139
|
/**
|
|
902
1140
|
* Applies class-validator decorators to schema properties.
|
|
903
1141
|
* Maps validation decorators to their corresponding OpenAPI schema constraints.
|
|
@@ -908,6 +1146,10 @@ class SchemaTransformer {
|
|
|
908
1146
|
* @private
|
|
909
1147
|
*/
|
|
910
1148
|
applyDecorators(decorators, schema, propertyName) {
|
|
1149
|
+
// Skip applying decorators to $ref schemas
|
|
1150
|
+
if (this.isRefSchema(schema)) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
911
1153
|
const isArrayType = schema.properties[propertyName].type ===
|
|
912
1154
|
constants.jsPrimitives.Array.value;
|
|
913
1155
|
for (const decorator of decorators) {
|
|
@@ -1229,6 +1471,10 @@ class SchemaTransformer {
|
|
|
1229
1471
|
* @private
|
|
1230
1472
|
*/
|
|
1231
1473
|
applyTypeBasedFormats(property, schema) {
|
|
1474
|
+
// Skip applying type-based formats to $ref schemas
|
|
1475
|
+
if (this.isRefSchema(schema)) {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1232
1478
|
const propertyName = property.name;
|
|
1233
1479
|
const propertyType = property.type.toLowerCase();
|
|
1234
1480
|
const propertySchema = schema.properties[propertyName];
|
|
@@ -1263,6 +1509,10 @@ class SchemaTransformer {
|
|
|
1263
1509
|
* @private
|
|
1264
1510
|
*/
|
|
1265
1511
|
determineRequiredStatus(property, schema) {
|
|
1512
|
+
// Skip determining required status for $ref schemas
|
|
1513
|
+
if (this.isRefSchema(schema)) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1266
1516
|
const propertyName = property.name;
|
|
1267
1517
|
// Check if already marked as required by IsNotEmpty or ArrayNotEmpty decorator
|
|
1268
1518
|
const isAlreadyRequired = schema.required.includes(propertyName);
|
|
@@ -1303,7 +1553,8 @@ class SchemaTransformer {
|
|
|
1303
1553
|
* @public
|
|
1304
1554
|
*/
|
|
1305
1555
|
function transform(cls, options) {
|
|
1306
|
-
return SchemaTransformer.
|
|
1556
|
+
return SchemaTransformer.transformClass(cls, options);
|
|
1307
1557
|
}
|
|
1308
1558
|
|
|
1559
|
+
exports.SchemaTransformer = SchemaTransformer;
|
|
1309
1560
|
exports.transform = transform;
|