CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-oas

Comprehensive tooling for working with OpenAPI definitions

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

schema-dereferencing-references.mddocs/

Schema Dereferencing and References

Advanced schema dereferencing with circular reference detection and resolution for OpenAPI definitions.

Capabilities

Dereference API Definition

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 instead

Get Circular References

Retrieve 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"]

Advanced Dereferencing Features

Preserving Reference Names

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}`);
  }
});

Multi-Promise Handling

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");

Reference Metadata Preservation

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);

Circular Reference Handling

Detection and Management

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);

Circular Reference Patterns

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!

Working with Circular Schemas

// 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);

Error Handling and Edge Cases

Dereferencing Errors

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)
}

Invalid Reference Handling

// 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(); // []

Memory Management

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`);

Integration Patterns

Code Generation Pipeline

// 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'}`);
    });
  }
}

Documentation Generation

// 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`);
  });
}

Validation Pipeline

// 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-oas

docs

analysis-metrics.md

api-definition-reduction.md

extensions-customization.md

index.md

openapi-definition-management.md

operation-discovery-analysis.md

parameter-handling-json-schema.md

request-response-management.md

schema-dereferencing-references.md

security-authentication.md

server-url-management.md

utils.md

tile.json