Comprehensive tooling for working with OpenAPI definitions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advanced schema dereferencing with circular reference detection and resolution for OpenAPI definitions.
Resolve all $ref pointers in the OpenAPI definition to eliminate references and circular dependencies.
/**
* Dereference the OpenAPI definition to resolve all $ref pointers
* @param opts - Dereferencing options
* @returns Promise resolving when dereferencing is complete
*/
dereference(opts?: {
/** Callback function called when dereferencing completes (for debugging) */
cb?: () => void;
/** Preserve component schema names as JSON Schema titles */
preserveRefAsJSONSchemaTitle?: boolean;
}): Promise<any>;Usage Examples:
import Oas from "oas";
const oas = new Oas(definitionWithRefs);
// Basic dereferencing
await oas.dereference();
// With title preservation for code generation
await oas.dereference({
preserveRefAsJSONSchemaTitle: true
});
// With completion callback
await oas.dereference({
cb: () => console.log("Dereferencing complete!")
});
// After dereferencing, all $ref pointers are resolved
const dereferencedDef = oas.getDefinition();
// No more { $ref: "#/components/schemas/User" } - actual schema objects insteadRetrieve circular reference pointers detected during dereferencing.
/**
* Get circular reference pointers found during dereferencing
* @returns Array of JSON pointer strings that form circular references
* @throws Error if dereference() hasn't been called first
*/
getCircularReferences(): string[];Usage Examples:
// Must dereference first
await oas.dereference();
// Get circular references
const circularRefs = oas.getCircularReferences();
if (circularRefs.length > 0) {
console.log("Found circular references:");
circularRefs.forEach(ref => {
console.log(` ${ref}`);
});
// Handle circular references in your application
console.log("These schemas reference themselves or form reference cycles");
} else {
console.log("No circular references found");
}
// Example circular reference:
// User schema -> Profile schema -> User schema
// Results in: ["#/components/schemas/User", "#/components/schemas/Profile"]When generating code or documentation, preserve original schema names:
await oas.dereference({
preserveRefAsJSONSchemaTitle: true
});
const definition = oas.getDefinition();
// Original: { $ref: "#/components/schemas/User" }
// Becomes: { title: "User", type: "object", properties: {...} }
// Useful for code generators that need original type names
const schemas = definition.components?.schemas;
Object.entries(schemas || {}).forEach(([name, schema]) => {
if (schema.title) {
console.log(`Schema ${name} preserved as title: ${schema.title}`);
}
});The dereferencing system handles multiple concurrent calls efficiently:
// Multiple calls to dereference() return the same promise
const promise1 = oas.dereference();
const promise2 = oas.dereference(); // Returns same promise as promise1
const promise3 = oas.dereference(); // Returns same promise as promise1
// All resolve when dereferencing completes
await Promise.all([promise1, promise2, promise3]);
console.log("All dereferencing calls completed");The library preserves useful metadata during dereferencing:
await oas.dereference();
const definition = oas.getDefinition();
// Check for preserved reference names
function findPreservedRefs(obj: any, path = ""): void {
if (obj && typeof obj === 'object') {
if (obj['x-readme-ref-name']) {
console.log(`${path}: originally ${obj['x-readme-ref-name']}`);
}
Object.entries(obj).forEach(([key, value]) => {
findPreservedRefs(value, path ? `${path}.${key}` : key);
});
}
}
findPreservedRefs(definition);await oas.dereference();
const circularRefs = oas.getCircularReferences();
// Analyze circular reference patterns
const refCounts = circularRefs.reduce((acc, ref) => {
acc[ref] = (acc[ref] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log("Circular reference frequency:", refCounts);
// Most commonly referenced schemas in cycles
const mostCircular = Object.entries(refCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5);
console.log("Most circular schemas:", mostCircular);Common patterns that create circular references:
# Self-referencing schema
components:
schemas:
Node:
type: object
properties:
children:
type: array
items:
$ref: '#/components/schemas/Node' # Circular!
# Mutual reference cycle
components:
schemas:
User:
type: object
properties:
profile:
$ref: '#/components/schemas/Profile'
Profile:
type: object
properties:
user:
$ref: '#/components/schemas/User' # Circular!// After dereferencing, circular refs remain as $ref pointers
await oas.dereference();
const definition = oas.getDefinition();
// Find remaining $ref pointers (these are circular)
function findRemainingRefs(obj: any): string[] {
const refs: string[] = [];
if (obj && typeof obj === 'object') {
if (obj.$ref && typeof obj.$ref === 'string') {
refs.push(obj.$ref);
}
Object.values(obj).forEach(value => {
refs.push(...findRemainingRefs(value));
});
}
return refs;
}
const remainingRefs = findRemainingRefs(definition);
console.log("Remaining $ref pointers (circular):", remainingRefs);try {
await oas.dereference();
} catch (error) {
console.error("Dereferencing failed:", error.message);
// Common issues:
// - Broken $ref pointers
// - Invalid JSON Schema
// - Network issues (for external refs - though disabled by default)
}// Broken references are handled gracefully
const definitionWithBrokenRefs = {
openapi: "3.1.0",
info: { title: "API", version: "1.0.0" },
paths: {
"/test": {
get: {
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/NonExistent" } // Broken ref
}
}
}
}
}
}
}
};
const oas = new Oas(definitionWithBrokenRefs);
await oas.dereference(); // Doesn't throw, handles gracefully
// Broken refs remain as $ref pointers
const circularRefs = oas.getCircularReferences(); // []For large APIs with many references:
// Dereferencing can increase memory usage significantly
const beforeSize = JSON.stringify(oas.getDefinition()).length;
await oas.dereference();
const afterSize = JSON.stringify(oas.getDefinition()).length;
const expansion = ((afterSize - beforeSize) / beforeSize * 100).toFixed(1);
console.log(`Definition expanded by ${expansion}% after dereferencing`);
console.log(`Before: ${beforeSize} chars, After: ${afterSize} chars`);// Prepare definition for code generation
await oas.dereference({
preserveRefAsJSONSchemaTitle: true
});
// Now all schemas are fully resolved with preserved names
const schemas = oas.getDefinition().components?.schemas || {};
Object.entries(schemas).forEach(([name, schema]) => {
generateTypeDefinition(name, schema);
});
function generateTypeDefinition(name: string, schema: any) {
// Use schema.title for the original reference name if available
const typeName = schema.title || name;
console.log(`Generating type: ${typeName}`);
// Schema is fully dereferenced - no $ref pointers to resolve
if (schema.properties) {
Object.entries(schema.properties).forEach(([prop, propSchema]) => {
console.log(` ${prop}: ${propSchema.type || 'unknown'}`);
});
}
}// Generate docs with circular reference warnings
await oas.dereference();
const circularRefs = oas.getCircularReferences();
// Warn about circular references in documentation
if (circularRefs.length > 0) {
console.log("⚠️ Circular References Detected:");
circularRefs.forEach(ref => {
const schemaName = ref.split('/').pop();
console.log(` ${schemaName} contains circular references`);
});
}// Ensure definition is fully dereferenced before validation
if (!oas.dereferencing?.complete) {
await oas.dereference();
}
// Now safe to validate without worrying about $ref resolution
const definition = oas.getDefinition();
validateOpenAPIDefinition(definition);Install with Tessl CLI
npx tessl i tessl/npm-oasdocs