@bernierllc/openapi-merger
v1.2.2
Published
OpenAPI specification merging, deduplication, and conflict resolution utilities
Readme
@bernierllc/openapi-merger
OpenAPI specification merging and deduplication utilities that combine multiple documents into a single coherent contract while preserving lineage and resolving conflicts.
Features
- Deterministic merge strategies including first-wins, last-wins, and deep merge
- Intelligent component deduplication with reference updates
- Conflict detection for paths, components, info metadata, and security requirements
- Source tracking annotations for merged artifacts
- Component optimization helpers for schema consolidation and integrity checks
Installation
npm install @bernierllc/openapi-mergerUsage
import { MergeStrategy, OpenAPIMerger } from '@bernierllc/openapi-merger';
const merger = new OpenAPIMerger({
strategy: MergeStrategy.MERGE_DEEP,
deduplicateComponents: true,
validateOutput: true
});
const result = merger.merge([specA, specB, specC]);
console.log(result.merged.paths);
console.log(result.conflicts);
console.log(result.deduplicated);API Reference
OpenAPIMerger Class
The main class for merging OpenAPI specifications with configurable strategies and options.
Constructor
const merger = new OpenAPIMerger(config?: MergerConfig);Configuration Options:
strategy- Merge strategy (FIRST_WINS, LAST_WINS, MERGE_DEEP, FAIL_ON_CONFLICT, CUSTOM)deduplicateComponents- Enable automatic component deduplication (default: true)validateOutput- Validate merged output for integrity (default: true)preserveOrder- Maintain source order in merged output (default: false)conflictResolver- Custom resolver function for CUSTOM strategy
merge(specs: OpenAPIObject[]): MergeResult
Merges multiple OpenAPI specifications into a single coherent document.
const result = merger.merge([specA, specB, specC]);
console.log(result.merged); // Merged OpenAPI specification
console.log(result.conflicts); // Array of detected conflicts
console.log(result.deduplicated); // Deduplication statistics
console.log(result.warnings); // Validation warningsReturns:
merged- Combined OpenAPI specificationconflicts- Array of conflict descriptionsdeduplicated- Component deduplication statisticswarnings- Validation warnings and notices
detectConflicts(specs: OpenAPIObject[]): ConflictReport
Analyzes specifications for conflicts without performing the merge.
const conflicts = merger.detectConflicts([specA, specB]);
conflicts.forEach(conflict => {
console.log(`Conflict in ${conflict.type}: ${conflict.description}`);
});Merge Strategies
FIRST_WINS
First occurrence wins for conflicts:
const merger = new OpenAPIMerger({ strategy: MergeStrategy.FIRST_WINS });
const result = merger.merge([spec1, spec2, spec3]);
// spec1 values preserved on conflictLAST_WINS
Last occurrence wins for conflicts:
const merger = new OpenAPIMerger({ strategy: MergeStrategy.LAST_WINS });
const result = merger.merge([spec1, spec2, spec3]);
// spec3 values override earlier specsMERGE_DEEP
Deep merge with intelligent conflict resolution:
const merger = new OpenAPIMerger({ strategy: MergeStrategy.MERGE_DEEP });
const result = merger.merge([spec1, spec2, spec3]);
// Deep merges objects, concatenates arraysFAIL_ON_CONFLICT
Strict mode that fails on any conflict:
const merger = new OpenAPIMerger({ strategy: MergeStrategy.FAIL_ON_CONFLICT });
try {
const result = merger.merge([spec1, spec2, spec3]);
} catch (error) {
console.error('Merge failed due to conflicts:', error);
}CUSTOM
Custom resolution logic:
const merger = new OpenAPIMerger({
strategy: MergeStrategy.CUSTOM,
conflictResolver: (path, values) => {
// Custom logic to resolve conflicts
return values[0]; // Example: use first value
}
});Component Optimization
optimizeSchemaRefs(spec: OpenAPIObject): OptimizationResult
Replaces duplicate schemas with references to canonical definitions:
import { componentOptimizer } from '@bernierllc/openapi-merger';
const result = componentOptimizer.optimizeSchemaRefs(spec);
console.log(result.duplicatesRemoved); // Count of removed duplicates
console.log(result.referencesUpdated); // Count of updated references
console.log(result.optimized); // Optimized specificationvalidateComponentIntegrity(spec: OpenAPIObject): IntegrityReport
Validates component references and identifies issues:
const integrity = componentOptimizer.validateComponentIntegrity(spec);
console.log(integrity.missingRefs); // References to non-existent components
console.log(integrity.unusedComponents); // Unused component definitions
console.log(integrity.valid); // Overall validity statusSource Tracking
Track lineage of merged components:
import { SourceTracker } from '@bernierllc/openapi-merger';
const tracker = new SourceTracker();
tracker.track('/paths/users', 'source-api-v1.yaml');
tracker.track('/paths/products', 'source-api-v2.yaml');
const sourceMap = tracker.getSourceMap();
console.log(sourceMap); // Maps each path to its source fileUtility Functions
normalizeComponentNames(components: Components): Components
Sanitizes component names for consistency:
import { OpenAPIMerger } from '@bernierllc/openapi-merger';
const normalized = OpenAPIMerger.normalizeComponentNames({
'User Schema': { type: 'object' },
'product-model': { type: 'object' }
});
// Returns:
// {
// 'UserSchema': { type: 'object' },
// 'productModel': { type: 'object' }
// }Examples
Basic Merge
import { OpenAPIMerger, MergeStrategy } from '@bernierllc/openapi-merger';
const merger = new OpenAPIMerger({
strategy: MergeStrategy.MERGE_DEEP,
deduplicateComponents: true
});
const spec1 = {
openapi: '3.0.0',
info: { title: 'API v1', version: '1.0.0' },
paths: {
'/users': { get: { /* ... */ } }
}
};
const spec2 = {
openapi: '3.0.0',
info: { title: 'API v2', version: '2.0.0' },
paths: {
'/products': { get: { /* ... */ } }
}
};
const result = merger.merge([spec1, spec2]);
console.log(result.merged.paths); // Contains both /users and /productsConflict Detection
const conflicts = merger.detectConflicts([spec1, spec2, spec3]);
if (conflicts.length > 0) {
console.log('Found conflicts:');
conflicts.forEach(conflict => {
console.log(`- ${conflict.type}: ${conflict.path}`);
console.log(` Description: ${conflict.description}`);
});
}Component Optimization
import { componentOptimizer } from '@bernierllc/openapi-merger';
// First merge specs
const merged = merger.merge([spec1, spec2, spec3]);
// Then optimize the result
const optimized = componentOptimizer.optimizeSchemaRefs(merged.merged);
console.log(`Removed ${optimized.duplicatesRemoved} duplicate schemas`);
console.log(`Updated ${optimized.referencesUpdated} references`);Source Tracking
import { SourceTracker } from '@bernierllc/openapi-merger';
const tracker = new SourceTracker();
// Track during merge
const merger = new OpenAPIMerger({
strategy: MergeStrategy.MERGE_DEEP,
sourceTracker: tracker
});
const result = merger.merge([
{ source: 'api-v1.yaml', spec: spec1 },
{ source: 'api-v2.yaml', spec: spec2 }
]);
// Get source information
const sourceMap = tracker.getSourceMap();
console.log(sourceMap['/paths/users']); // 'api-v1.yaml'
console.log(sourceMap['/paths/products']); // 'api-v2.yaml'Testing
npm testIntegration Status
- Logger integration: not-applicable - This is a pure utility package with no runtime logging requirements. Does not use @bernierllc/logger as it has no logging needs. The detectLogger pattern is not applicable to this pure transformation utility.
- Docs-Suite: ready - Exports TypeDoc-compatible API documentation
- NeverHub integration: not-applicable - Pure transformation utility with no service discovery or event bus requirements. Does not use @bernierllc/neverhub-adapter as it has no event bus needs. The detectNeverHub pattern is not applicable to this stateless utility.
License
UNLICENSED – Internal Bernier LLC package.
