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