docs
0
# Schema Dereferencing and References
1
2
Advanced schema dereferencing with circular reference detection and resolution for OpenAPI definitions.
3
4
## Capabilities
5
6
### Dereference API Definition
7
8
Resolve all `$ref` pointers in the OpenAPI definition to eliminate references and circular dependencies.
9
10
```typescript { .api }
11
/**
12
* Dereference the OpenAPI definition to resolve all $ref pointers
13
* @param opts - Dereferencing options
14
* @returns Promise resolving when dereferencing is complete
15
*/
16
dereference(opts?: {
17
/** Callback function called when dereferencing completes (for debugging) */
18
cb?: () => void;
19
/** Preserve component schema names as JSON Schema titles */
20
preserveRefAsJSONSchemaTitle?: boolean;
21
}): Promise<any>;
22
```
23
24
**Usage Examples:**
25
26
```typescript
27
import Oas from "oas";
28
29
const oas = new Oas(definitionWithRefs);
30
31
// Basic dereferencing
32
await oas.dereference();
33
34
// With title preservation for code generation
35
await oas.dereference({
36
preserveRefAsJSONSchemaTitle: true
37
});
38
39
// With completion callback
40
await oas.dereference({
41
cb: () => console.log("Dereferencing complete!")
42
});
43
44
// After dereferencing, all $ref pointers are resolved
45
const dereferencedDef = oas.getDefinition();
46
// No more { $ref: "#/components/schemas/User" } - actual schema objects instead
47
```
48
49
### Get Circular References
50
51
Retrieve circular reference pointers detected during dereferencing.
52
53
```typescript { .api }
54
/**
55
* Get circular reference pointers found during dereferencing
56
* @returns Array of JSON pointer strings that form circular references
57
* @throws Error if dereference() hasn't been called first
58
*/
59
getCircularReferences(): string[];
60
```
61
62
**Usage Examples:**
63
64
```typescript
65
// Must dereference first
66
await oas.dereference();
67
68
// Get circular references
69
const circularRefs = oas.getCircularReferences();
70
71
if (circularRefs.length > 0) {
72
console.log("Found circular references:");
73
circularRefs.forEach(ref => {
74
console.log(` ${ref}`);
75
});
76
77
// Handle circular references in your application
78
console.log("These schemas reference themselves or form reference cycles");
79
} else {
80
console.log("No circular references found");
81
}
82
83
// Example circular reference:
84
// User schema -> Profile schema -> User schema
85
// Results in: ["#/components/schemas/User", "#/components/schemas/Profile"]
86
```
87
88
## Advanced Dereferencing Features
89
90
### Preserving Reference Names
91
92
When generating code or documentation, preserve original schema names:
93
94
```typescript
95
await oas.dereference({
96
preserveRefAsJSONSchemaTitle: true
97
});
98
99
const definition = oas.getDefinition();
100
101
// Original: { $ref: "#/components/schemas/User" }
102
// Becomes: { title: "User", type: "object", properties: {...} }
103
104
// Useful for code generators that need original type names
105
const schemas = definition.components?.schemas;
106
Object.entries(schemas || {}).forEach(([name, schema]) => {
107
if (schema.title) {
108
console.log(`Schema ${name} preserved as title: ${schema.title}`);
109
}
110
});
111
```
112
113
### Multi-Promise Handling
114
115
The dereferencing system handles multiple concurrent calls efficiently:
116
117
```typescript
118
// Multiple calls to dereference() return the same promise
119
const promise1 = oas.dereference();
120
const promise2 = oas.dereference(); // Returns same promise as promise1
121
const promise3 = oas.dereference(); // Returns same promise as promise1
122
123
// All resolve when dereferencing completes
124
await Promise.all([promise1, promise2, promise3]);
125
126
console.log("All dereferencing calls completed");
127
```
128
129
### Reference Metadata Preservation
130
131
The library preserves useful metadata during dereferencing:
132
133
```typescript
134
await oas.dereference();
135
136
const definition = oas.getDefinition();
137
138
// Check for preserved reference names
139
function findPreservedRefs(obj: any, path = ""): void {
140
if (obj && typeof obj === 'object') {
141
if (obj['x-readme-ref-name']) {
142
console.log(`${path}: originally ${obj['x-readme-ref-name']}`);
143
}
144
145
Object.entries(obj).forEach(([key, value]) => {
146
findPreservedRefs(value, path ? `${path}.${key}` : key);
147
});
148
}
149
}
150
151
findPreservedRefs(definition);
152
```
153
154
## Circular Reference Handling
155
156
### Detection and Management
157
158
```typescript
159
await oas.dereference();
160
const circularRefs = oas.getCircularReferences();
161
162
// Analyze circular reference patterns
163
const refCounts = circularRefs.reduce((acc, ref) => {
164
acc[ref] = (acc[ref] || 0) + 1;
165
return acc;
166
}, {} as Record<string, number>);
167
168
console.log("Circular reference frequency:", refCounts);
169
170
// Most commonly referenced schemas in cycles
171
const mostCircular = Object.entries(refCounts)
172
.sort(([,a], [,b]) => b - a)
173
.slice(0, 5);
174
175
console.log("Most circular schemas:", mostCircular);
176
```
177
178
### Circular Reference Patterns
179
180
Common patterns that create circular references:
181
182
```yaml
183
# Self-referencing schema
184
components:
185
schemas:
186
Node:
187
type: object
188
properties:
189
children:
190
type: array
191
items:
192
$ref: '#/components/schemas/Node' # Circular!
193
194
# Mutual reference cycle
195
components:
196
schemas:
197
User:
198
type: object
199
properties:
200
profile:
201
$ref: '#/components/schemas/Profile'
202
Profile:
203
type: object
204
properties:
205
user:
206
$ref: '#/components/schemas/User' # Circular!
207
```
208
209
### Working with Circular Schemas
210
211
```typescript
212
// After dereferencing, circular refs remain as $ref pointers
213
await oas.dereference();
214
const definition = oas.getDefinition();
215
216
// Find remaining $ref pointers (these are circular)
217
function findRemainingRefs(obj: any): string[] {
218
const refs: string[] = [];
219
220
if (obj && typeof obj === 'object') {
221
if (obj.$ref && typeof obj.$ref === 'string') {
222
refs.push(obj.$ref);
223
}
224
225
Object.values(obj).forEach(value => {
226
refs.push(...findRemainingRefs(value));
227
});
228
}
229
230
return refs;
231
}
232
233
const remainingRefs = findRemainingRefs(definition);
234
console.log("Remaining $ref pointers (circular):", remainingRefs);
235
```
236
237
## Error Handling and Edge Cases
238
239
### Dereferencing Errors
240
241
```typescript
242
try {
243
await oas.dereference();
244
} catch (error) {
245
console.error("Dereferencing failed:", error.message);
246
247
// Common issues:
248
// - Broken $ref pointers
249
// - Invalid JSON Schema
250
// - Network issues (for external refs - though disabled by default)
251
}
252
```
253
254
### Invalid Reference Handling
255
256
```typescript
257
// Broken references are handled gracefully
258
const definitionWithBrokenRefs = {
259
openapi: "3.1.0",
260
info: { title: "API", version: "1.0.0" },
261
paths: {
262
"/test": {
263
get: {
264
responses: {
265
"200": {
266
description: "Success",
267
content: {
268
"application/json": {
269
schema: { $ref: "#/components/schemas/NonExistent" } // Broken ref
270
}
271
}
272
}
273
}
274
}
275
}
276
}
277
};
278
279
const oas = new Oas(definitionWithBrokenRefs);
280
await oas.dereference(); // Doesn't throw, handles gracefully
281
282
// Broken refs remain as $ref pointers
283
const circularRefs = oas.getCircularReferences(); // []
284
```
285
286
### Memory Management
287
288
For large APIs with many references:
289
290
```typescript
291
// Dereferencing can increase memory usage significantly
292
const beforeSize = JSON.stringify(oas.getDefinition()).length;
293
294
await oas.dereference();
295
296
const afterSize = JSON.stringify(oas.getDefinition()).length;
297
const expansion = ((afterSize - beforeSize) / beforeSize * 100).toFixed(1);
298
299
console.log(`Definition expanded by ${expansion}% after dereferencing`);
300
console.log(`Before: ${beforeSize} chars, After: ${afterSize} chars`);
301
```
302
303
## Integration Patterns
304
305
### Code Generation Pipeline
306
307
```typescript
308
// Prepare definition for code generation
309
await oas.dereference({
310
preserveRefAsJSONSchemaTitle: true
311
});
312
313
// Now all schemas are fully resolved with preserved names
314
const schemas = oas.getDefinition().components?.schemas || {};
315
316
Object.entries(schemas).forEach(([name, schema]) => {
317
generateTypeDefinition(name, schema);
318
});
319
320
function generateTypeDefinition(name: string, schema: any) {
321
// Use schema.title for the original reference name if available
322
const typeName = schema.title || name;
323
console.log(`Generating type: ${typeName}`);
324
325
// Schema is fully dereferenced - no $ref pointers to resolve
326
if (schema.properties) {
327
Object.entries(schema.properties).forEach(([prop, propSchema]) => {
328
console.log(` ${prop}: ${propSchema.type || 'unknown'}`);
329
});
330
}
331
}
332
```
333
334
### Documentation Generation
335
336
```typescript
337
// Generate docs with circular reference warnings
338
await oas.dereference();
339
const circularRefs = oas.getCircularReferences();
340
341
// Warn about circular references in documentation
342
if (circularRefs.length > 0) {
343
console.log("⚠️ Circular References Detected:");
344
circularRefs.forEach(ref => {
345
const schemaName = ref.split('/').pop();
346
console.log(` ${schemaName} contains circular references`);
347
});
348
}
349
```
350
351
### Validation Pipeline
352
353
```typescript
354
// Ensure definition is fully dereferenced before validation
355
if (!oas.dereferencing?.complete) {
356
await oas.dereference();
357
}
358
359
// Now safe to validate without worrying about $ref resolution
360
const definition = oas.getDefinition();
361
validateOpenAPIDefinition(definition);
362
```