metal-orm 1.0.87 → 1.0.89

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.
@@ -1,20 +1,25 @@
1
1
  import type { TableDef } from '../schema/table.js';
2
- import type { RelationDef } from '../schema/relation.js';
3
- import type { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
2
+ import type { HydrationPlan } from '../core/hydration/types.js';
4
3
  import type { ProjectionNode } from '../query-builder/select-query-state.js';
5
- import { findPrimaryKey } from '../query-builder/hydration-planner.js';
6
4
 
7
5
  import type {
8
6
  OpenApiSchema,
9
7
  OpenApiSchemaBundle,
10
- SchemaExtractionContext,
11
8
  SchemaOptions,
12
9
  OutputSchemaOptions,
13
10
  InputSchemaOptions,
14
- JsonSchemaProperty,
15
- JsonSchemaType
11
+ JsonSchemaProperty
16
12
  } from './schema-types.js';
17
- import { mapColumnType, mapRelationType } from './type-mappers.js';
13
+ import { extractInputSchema } from './schema-extractor-input.js';
14
+ import { extractOutputSchema } from './schema-extractor-output.js';
15
+ import {
16
+ createContext,
17
+ hasComputedProjection,
18
+ registerComponentSchema,
19
+ resolveComponentName,
20
+ resolveSelectedComponentName,
21
+ shouldUseSelectedSchema
22
+ } from './schema-extractor-utils.js';
18
23
 
19
24
  const DEFAULT_MAX_DEPTH = 5;
20
25
 
@@ -29,17 +34,45 @@ export const extractSchema = (
29
34
  ): OpenApiSchemaBundle => {
30
35
  const outputOptions = resolveOutputOptions(options);
31
36
  const outputContext = createContext(outputOptions.maxDepth ?? DEFAULT_MAX_DEPTH);
32
- const output = extractOutputSchema(table, plan, projectionNodes, outputContext, outputOptions);
37
+ if (outputOptions.refMode === 'components') {
38
+ outputContext.components = { schemas: {} };
39
+ }
40
+ const outputSchema = extractOutputSchema(table, plan, projectionNodes, outputContext, outputOptions);
41
+ let output: OpenApiSchema | JsonSchemaProperty = outputSchema;
42
+ const useSelected = shouldUseSelectedSchema(outputOptions, plan, projectionNodes);
43
+ const hasComputedFields = hasComputedProjection(projectionNodes);
44
+ const canUseComponents = outputOptions.refMode === 'components' && outputContext.components && !hasComputedFields;
45
+
46
+ if (canUseComponents) {
47
+ const componentName = useSelected && plan
48
+ ? resolveSelectedComponentName(table, plan, outputOptions)
49
+ : resolveComponentName(table, outputOptions);
50
+ registerComponentSchema(componentName, outputSchema, outputContext);
51
+ if (outputOptions.outputAsRef) {
52
+ output = { $ref: `#/components/schemas/${componentName}` };
53
+ }
54
+ }
33
55
 
34
56
  const inputOptions = resolveInputOptions(options);
35
57
  if (!inputOptions) {
36
- return { output };
58
+ return {
59
+ output,
60
+ components: outputContext.components && Object.keys(outputContext.components.schemas).length
61
+ ? outputContext.components
62
+ : undefined
63
+ };
37
64
  }
38
65
 
39
66
  const inputContext = createContext(inputOptions.maxDepth ?? DEFAULT_MAX_DEPTH);
40
67
  const input = extractInputSchema(table, inputContext, inputOptions);
41
68
 
42
- return { output, input };
69
+ return {
70
+ output,
71
+ input,
72
+ components: outputContext.components && Object.keys(outputContext.components.schemas).length
73
+ ? outputContext.components
74
+ : undefined
75
+ };
43
76
  };
44
77
 
45
78
  const resolveOutputOptions = (options: SchemaOptions): OutputSchemaOptions => ({
@@ -49,7 +82,11 @@ const resolveOutputOptions = (options: SchemaOptions): OutputSchemaOptions => ({
49
82
  includeExamples: options.includeExamples,
50
83
  includeDefaults: options.includeDefaults,
51
84
  includeNullable: options.includeNullable,
52
- maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH
85
+ maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
86
+ refMode: options.refMode ?? 'inline',
87
+ selectedRefMode: options.selectedRefMode ?? 'inline',
88
+ componentName: options.componentName,
89
+ outputAsRef: options.outputAsRef ?? false
53
90
  });
54
91
 
55
92
  const resolveInputOptions = (options: SchemaOptions): InputSchemaOptions | undefined => {
@@ -69,515 +106,12 @@ const resolveInputOptions = (options: SchemaOptions): InputSchemaOptions | undef
69
106
  maxDepth: input.maxDepth ?? options.maxDepth ?? DEFAULT_MAX_DEPTH,
70
107
  omitReadOnly: input.omitReadOnly ?? true,
71
108
  excludePrimaryKey: input.excludePrimaryKey ?? false,
72
- requirePrimaryKey: input.requirePrimaryKey ?? (mode === 'update')
73
- };
74
- };
75
-
76
- const createContext = (maxDepth: number): SchemaExtractionContext => ({
77
- visitedTables: new Set(),
78
- schemaCache: new Map(),
79
- depth: 0,
80
- maxDepth
81
- });
82
-
83
- /**
84
- * Output schema extraction (query results)
85
- */
86
- const extractOutputSchema = (
87
- table: TableDef,
88
- plan: HydrationPlan | undefined,
89
- projectionNodes: ProjectionNode[] | undefined,
90
- context: SchemaExtractionContext,
91
- options: OutputSchemaOptions
92
- ): OpenApiSchema => {
93
- const mode = options.mode ?? 'full';
94
-
95
- const hasComputedFields = projectionNodes && projectionNodes.some(
96
- node => node.type !== 'Column'
97
- );
98
-
99
- if (hasComputedFields) {
100
- return extractFromProjectionNodes(table, projectionNodes!, context, options);
101
- }
102
-
103
- if (mode === 'selected' && plan) {
104
- return extractSelectedSchema(table, plan, context, options);
105
- }
106
-
107
- return extractFullTableSchema(table, context, options);
108
- };
109
-
110
- /**
111
- * Input schema extraction (write payloads)
112
- */
113
- const extractInputSchema = (
114
- table: TableDef,
115
- context: SchemaExtractionContext,
116
- options: InputSchemaOptions
117
- ): OpenApiSchema => {
118
- const cacheKey = `${table.name}:${options.mode ?? 'create'}`;
119
-
120
- if (context.schemaCache.has(cacheKey)) {
121
- return context.schemaCache.get(cacheKey)!;
122
- }
123
-
124
- if (context.visitedTables.has(cacheKey) && context.depth > 0) {
125
- return buildCircularReferenceSchema(table.name, 'input');
126
- }
127
-
128
- context.visitedTables.add(cacheKey);
129
-
130
- const properties: Record<string, JsonSchemaProperty> = {};
131
- const required: string[] = [];
132
- const primaryKey = findPrimaryKey(table);
133
-
134
- for (const [columnName, column] of Object.entries(table.columns)) {
135
- const isPrimary = columnName === primaryKey || column.primary;
136
- if (options.excludePrimaryKey && isPrimary) continue;
137
- if (options.omitReadOnly && isReadOnlyColumn(column)) continue;
138
-
139
- properties[columnName] = mapColumnType(column, options);
140
-
141
- if (options.mode === 'create' && isRequiredForCreate(column)) {
142
- required.push(columnName);
143
- }
144
-
145
- if (options.mode === 'update' && options.requirePrimaryKey && isPrimary) {
146
- required.push(columnName);
147
- }
148
- }
149
-
150
- if (options.includeRelations && context.depth < context.maxDepth) {
151
- for (const [relationName, relation] of Object.entries(table.relations)) {
152
- properties[relationName] = extractInputRelationSchema(
153
- relation,
154
- { ...context, depth: context.depth + 1 },
155
- options
156
- );
157
- }
158
- }
159
-
160
- const schema: OpenApiSchema = {
161
- type: 'object',
162
- properties,
163
- required
164
- };
165
-
166
- context.schemaCache.set(cacheKey, schema);
167
- return schema;
168
- };
169
-
170
- const isReadOnlyColumn = (column: { autoIncrement?: boolean; generated?: string }): boolean =>
171
- Boolean(column.autoIncrement || column.generated === 'always');
172
-
173
- const isRequiredForCreate = (column: { notNull?: boolean; primary?: boolean; default?: unknown; autoIncrement?: boolean; generated?: string }): boolean => {
174
- if (isReadOnlyColumn(column)) return false;
175
- if (column.default !== undefined) return false;
176
- return Boolean(column.notNull || column.primary);
177
- };
178
-
179
- const buildPrimaryKeySchema = (
180
- table: TableDef,
181
- options: InputSchemaOptions
182
- ): JsonSchemaProperty => {
183
- const primaryKey = findPrimaryKey(table);
184
- const column = table.columns[primaryKey];
185
- if (!column) {
186
- return {
187
- anyOf: [
188
- { type: 'string' as JsonSchemaType },
189
- { type: 'number' as JsonSchemaType },
190
- { type: 'integer' as JsonSchemaType }
191
- ]
192
- };
193
- }
194
-
195
- return mapColumnType(column, options);
196
- };
197
-
198
- const extractInputRelationSchema = (
199
- relation: RelationDef,
200
- context: SchemaExtractionContext,
201
- options: InputSchemaOptions
202
- ): JsonSchemaProperty => {
203
- const { type: relationType, isNullable } = mapRelationType(relation.type);
204
- const relationMode = options.relationMode ?? 'mixed';
205
- const allowIds = relationMode !== 'objects';
206
- const allowObjects = relationMode !== 'ids';
207
-
208
- const variants: JsonSchemaProperty[] = [];
209
-
210
- if (allowIds) {
211
- variants.push(buildPrimaryKeySchema(relation.target, options));
212
- }
213
-
214
- if (allowObjects) {
215
- const targetSchema = extractInputSchema(relation.target, context, options);
216
- variants.push(targetSchema as JsonSchemaProperty);
217
- }
218
-
219
- const itemSchema: JsonSchemaProperty =
220
- variants.length === 1 ? variants[0] : { anyOf: variants };
221
-
222
- if (relationType === 'array') {
223
- return {
224
- type: 'array',
225
- items: itemSchema,
226
- nullable: isNullable
227
- };
228
- }
229
-
230
- return {
231
- ...itemSchema,
232
- nullable: isNullable
233
- };
234
- };
235
-
236
- /**
237
- * Extracts schema from projection nodes (handles computed fields)
238
- */
239
- const extractFromProjectionNodes = (
240
- table: TableDef,
241
- projectionNodes: ProjectionNode[],
242
- context: SchemaExtractionContext,
243
- options: OutputSchemaOptions
244
- ): OpenApiSchema => {
245
- const properties: Record<string, JsonSchemaProperty> = {};
246
- const required: string[] = [];
247
- const includeDescriptions = Boolean(options.includeDescriptions);
248
-
249
- for (const node of projectionNodes) {
250
- if (!node || typeof node !== 'object') continue;
251
-
252
- const projection = node as { type: string; alias?: string; fn?: string; value?: unknown };
253
- const propertyName = projection.alias ?? '';
254
-
255
- if (!propertyName) continue;
256
-
257
- if (projection.type === 'Column') {
258
- const columnNode = node as { table: string; name: string };
259
- const column = table.columns[columnNode.name];
260
- if (!column) continue;
261
-
262
- const property = mapColumnType(column, options);
263
- properties[propertyName] = property;
264
-
265
- if (column.notNull || column.primary) {
266
- required.push(propertyName);
267
- }
268
- } else if (projection.type === 'Function' || projection.type === 'WindowFunction') {
269
- const fnNode = node as { fn?: string; name?: string };
270
- const functionName = fnNode.fn?.toUpperCase() ?? fnNode.name?.toUpperCase() ?? '';
271
- const propertySchema = projection.type === 'Function'
272
- ? mapFunctionNodeToSchema(functionName, includeDescriptions)
273
- : mapWindowFunctionToSchema(functionName, includeDescriptions);
274
-
275
- properties[propertyName] = propertySchema;
276
-
277
- const isCountFunction = functionName === 'COUNT';
278
- const isWindowRankFunction = functionName === 'ROW_NUMBER' || functionName === 'RANK';
279
-
280
- if (isCountFunction || isWindowRankFunction) {
281
- required.push(propertyName);
282
- }
283
- } else if (projection.type === 'CaseExpression') {
284
- const propertySchema: JsonSchemaProperty = {
285
- type: 'string' as JsonSchemaType,
286
- nullable: true
287
- };
288
- if (includeDescriptions) {
289
- propertySchema.description = 'Computed CASE expression';
290
- }
291
- properties[propertyName] = propertySchema;
292
- } else if (projection.type === 'ScalarSubquery') {
293
- const propertySchema: JsonSchemaProperty = {
294
- type: 'object' as JsonSchemaType,
295
- nullable: true
296
- };
297
- if (includeDescriptions) {
298
- propertySchema.description = 'Subquery result';
299
- }
300
- properties[propertyName] = propertySchema;
301
- } else if (projection.type === 'CastExpression') {
302
- const propertySchema: JsonSchemaProperty = {
303
- type: 'string' as JsonSchemaType,
304
- nullable: true
305
- };
306
- if (includeDescriptions) {
307
- propertySchema.description = 'CAST expression result';
308
- }
309
- properties[propertyName] = propertySchema;
310
- }
311
- }
312
-
313
- return {
314
- type: 'object',
315
- properties,
316
- required
317
- };
318
- };
319
-
320
- /**
321
- * Maps SQL aggregate functions to OpenAPI types
322
- */
323
- const mapFunctionNodeToSchema = (
324
- functionName: string,
325
- includeDescriptions: boolean
326
- ): JsonSchemaProperty => {
327
- const upperName = functionName.toUpperCase();
328
-
329
- switch (upperName) {
330
- case 'COUNT':
331
- case 'SUM':
332
- case 'AVG':
333
- case 'MIN':
334
- case 'MAX':
335
- return withOptionalDescription({
336
- type: 'number' as JsonSchemaType,
337
- nullable: false
338
- }, includeDescriptions, `${upperName} aggregate function result`);
339
-
340
- case 'GROUP_CONCAT':
341
- case 'STRING_AGG':
342
- case 'ARRAY_AGG':
343
- return withOptionalDescription({
344
- type: 'string' as JsonSchemaType,
345
- nullable: true
346
- }, includeDescriptions, `${upperName} aggregate function result`);
347
-
348
- case 'JSON_ARRAYAGG':
349
- case 'JSON_OBJECTAGG':
350
- return withOptionalDescription({
351
- type: 'object' as JsonSchemaType,
352
- nullable: true
353
- }, includeDescriptions, `${upperName} aggregate function result`);
354
-
355
- default:
356
- return withOptionalDescription({
357
- type: 'string' as JsonSchemaType,
358
- nullable: true
359
- }, includeDescriptions, `Unknown function: ${functionName}`);
360
- }
361
- };
362
-
363
- /**
364
- * Maps SQL window functions to OpenAPI types
365
- */
366
- const mapWindowFunctionToSchema = (
367
- functionName: string,
368
- includeDescriptions: boolean
369
- ): JsonSchemaProperty => {
370
- const upperName = functionName.toUpperCase();
371
-
372
- switch (upperName) {
373
- case 'ROW_NUMBER':
374
- case 'RANK':
375
- case 'DENSE_RANK':
376
- case 'NTILE':
377
- return withOptionalDescription({
378
- type: 'integer' as JsonSchemaType,
379
- nullable: false
380
- }, includeDescriptions, `${upperName} window function result`);
381
-
382
- case 'LAG':
383
- case 'LEAD':
384
- case 'FIRST_VALUE':
385
- case 'LAST_VALUE':
386
- return withOptionalDescription({
387
- type: 'string' as JsonSchemaType,
388
- nullable: true
389
- }, includeDescriptions, `${upperName} window function result`);
390
-
391
- default:
392
- return withOptionalDescription({
393
- type: 'string' as JsonSchemaType,
394
- nullable: true
395
- }, includeDescriptions, `Unknown window function: ${functionName}`);
396
- }
397
- };
398
-
399
- const withOptionalDescription = (
400
- schema: JsonSchemaProperty,
401
- includeDescriptions: boolean,
402
- description: string
403
- ): JsonSchemaProperty => {
404
- if (includeDescriptions) {
405
- return { ...schema, description };
406
- }
407
- return schema;
408
- };
409
-
410
- /**
411
- * Extracts schema with only selected columns and relations
412
- */
413
- const extractSelectedSchema = (
414
- table: TableDef,
415
- plan: HydrationPlan,
416
- context: SchemaExtractionContext,
417
- options: OutputSchemaOptions
418
- ): OpenApiSchema => {
419
- const properties: Record<string, JsonSchemaProperty> = {};
420
- const required: string[] = [];
421
-
422
- plan.rootColumns.forEach(columnName => {
423
- const column = table.columns[columnName];
424
- if (!column) return;
425
-
426
- properties[columnName] = mapColumnType(column, options);
427
-
428
- if (column.notNull || column.primary) {
429
- required.push(columnName);
430
- }
431
- });
432
-
433
- plan.relations.forEach(relationPlan => {
434
- const relation = table.relations[relationPlan.name];
435
- if (!relation) return;
436
-
437
- const relationSchema = extractRelationSchema(
438
- relation,
439
- relationPlan,
440
- relationPlan.columns,
441
- context,
442
- options
443
- );
444
-
445
- properties[relationPlan.name] = relationSchema;
446
-
447
- const { isNullable } = mapRelationType(relation.type);
448
- if (!isNullable && relationPlan.name) {
449
- required.push(relationPlan.name);
450
- }
451
- });
452
-
453
- return {
454
- type: 'object',
455
- properties,
456
- required
109
+ requirePrimaryKey: input.requirePrimaryKey ?? (mode === 'update'),
110
+ excludeRelationForeignKeys: input.excludeRelationForeignKeys ?? false,
111
+ relationSelections: input.relationSelections
457
112
  };
458
113
  };
459
114
 
460
- /**
461
- * Extracts full table schema (all columns, all relations)
462
- */
463
- const extractFullTableSchema = (
464
- table: TableDef,
465
- context: SchemaExtractionContext,
466
- options: OutputSchemaOptions
467
- ): OpenApiSchema => {
468
- const cacheKey = table.name;
469
-
470
- if (context.schemaCache.has(cacheKey)) {
471
- return context.schemaCache.get(cacheKey)!;
472
- }
473
-
474
- if (context.visitedTables.has(cacheKey) && context.depth > 0) {
475
- return buildCircularReferenceSchema(table.name, 'output');
476
- }
477
-
478
- context.visitedTables.add(cacheKey);
479
-
480
- const properties: Record<string, JsonSchemaProperty> = {};
481
- const required: string[] = [];
482
-
483
- Object.entries(table.columns).forEach(([columnName, column]) => {
484
- properties[columnName] = mapColumnType(column, options);
485
-
486
- if (column.notNull || column.primary) {
487
- required.push(columnName);
488
- }
489
- });
490
-
491
- Object.entries(table.relations).forEach(([relationName, relation]) => {
492
- if (context.depth >= context.maxDepth) {
493
- return;
494
- }
495
-
496
- const relationSchema = extractRelationSchema(
497
- relation,
498
- undefined,
499
- [],
500
- { ...context, depth: context.depth + 1 },
501
- options
502
- );
503
-
504
- properties[relationName] = relationSchema;
505
-
506
- const { isNullable } = mapRelationType(relation.type);
507
- if (!isNullable) {
508
- required.push(relationName);
509
- }
510
- });
511
-
512
- const schema: OpenApiSchema = {
513
- type: 'object',
514
- properties,
515
- required
516
- };
517
-
518
- context.schemaCache.set(cacheKey, schema);
519
- return schema;
520
- };
521
-
522
- /**
523
- * Extracts schema for a single relation
524
- */
525
- const extractRelationSchema = (
526
- relation: RelationDef,
527
- relationPlan: HydrationRelationPlan | undefined,
528
- selectedColumns: string[],
529
- context: SchemaExtractionContext,
530
- options: OutputSchemaOptions
531
- ): JsonSchemaProperty => {
532
- const targetTable = relation.target;
533
- const { type: relationType, isNullable } = mapRelationType(relation.type);
534
-
535
- let targetSchema: OpenApiSchema;
536
-
537
- if (relationPlan && selectedColumns.length > 0) {
538
- const plan: HydrationPlan = {
539
- rootTable: targetTable.name,
540
- rootPrimaryKey: relationPlan.targetPrimaryKey,
541
- rootColumns: selectedColumns,
542
- relations: []
543
- };
544
-
545
- targetSchema = extractSelectedSchema(targetTable, plan, context, options);
546
- } else {
547
- targetSchema = extractFullTableSchema(targetTable, context, options);
548
- }
549
-
550
- if (relationType === 'array') {
551
- return {
552
- type: 'array',
553
- items: targetSchema as JsonSchemaProperty,
554
- nullable: isNullable
555
- };
556
- }
557
-
558
- return {
559
- type: 'object' as JsonSchemaType,
560
- properties: targetSchema.properties,
561
- required: targetSchema.required,
562
- nullable: isNullable,
563
- description: targetSchema.description
564
- };
565
- };
566
-
567
- const buildCircularReferenceSchema = (
568
- tableName: string,
569
- kind: 'input' | 'output'
570
- ): OpenApiSchema => ({
571
- type: 'object',
572
- properties: {
573
- _ref: {
574
- type: 'string' as JsonSchemaType,
575
- description: `Circular ${kind} reference to ${tableName}`
576
- }
577
- },
578
- required: []
579
- });
580
-
581
115
  /**
582
116
  * Converts a schema to a JSON string with optional pretty printing
583
117
  */
@@ -1,3 +1,5 @@
1
+ import type { TableDef } from '../schema/table.js';
2
+
1
3
  /**
2
4
  * OpenAPI 3.1 JSON Schema type representation
3
5
  */
@@ -77,6 +79,13 @@ export interface OpenApiSchema {
77
79
  description?: string;
78
80
  }
79
81
 
82
+ /**
83
+ * OpenAPI 3.1 components container
84
+ */
85
+ export interface OpenApiComponents {
86
+ schemas: Record<string, OpenApiSchema>;
87
+ }
88
+
80
89
  /**
81
90
  * Column-level schema flags
82
91
  */
@@ -101,11 +110,24 @@ export interface OutputSchemaOptions extends ColumnSchemaOptions {
101
110
  mode?: 'selected' | 'full';
102
111
  /** Maximum depth for relation recursion */
103
112
  maxDepth?: number;
113
+ /** Inline schemas vs $ref components */
114
+ refMode?: 'inline' | 'components';
115
+ /** Selected schemas inline vs components when refMode is components */
116
+ selectedRefMode?: 'inline' | 'components';
117
+ /** Customize component names */
118
+ componentName?: (table: TableDef) => string;
119
+ /** Emit output schema as a component $ref when refMode is components */
120
+ outputAsRef?: boolean;
104
121
  }
105
122
 
106
123
  export type InputRelationMode = 'ids' | 'objects' | 'mixed';
107
124
  export type InputSchemaMode = 'create' | 'update';
108
125
 
126
+ export interface RelationSelection {
127
+ pick?: string[];
128
+ omit?: string[];
129
+ }
130
+
109
131
  /**
110
132
  * Input schema generation options (write payloads)
111
133
  */
@@ -124,6 +146,10 @@ export interface InputSchemaOptions extends ColumnSchemaOptions {
124
146
  excludePrimaryKey?: boolean;
125
147
  /** Require primary key columns on update payloads */
126
148
  requirePrimaryKey?: boolean;
149
+ /** Remove relation foreign keys pointing to the parent from nested inputs */
150
+ excludeRelationForeignKeys?: boolean;
151
+ /** Per-relation field selection for nested inputs */
152
+ relationSelections?: Record<string, RelationSelection>;
127
153
  }
128
154
 
129
155
  /**
@@ -138,9 +164,10 @@ export interface SchemaOptions extends OutputSchemaOptions {
138
164
  * Input + output schema bundle
139
165
  */
140
166
  export interface OpenApiSchemaBundle {
141
- output: OpenApiSchema;
167
+ output: OpenApiSchema | JsonSchemaProperty;
142
168
  input?: OpenApiSchema;
143
169
  parameters?: OpenApiParameter[];
170
+ components?: OpenApiComponents;
144
171
  }
145
172
 
146
173
  /**
@@ -155,4 +182,6 @@ export interface SchemaExtractionContext {
155
182
  depth: number;
156
183
  /** Maximum depth to recurse */
157
184
  maxDepth: number;
185
+ /** Component registry when using refMode=components */
186
+ components?: OpenApiComponents;
158
187
  }