OpenAPI specification merging, deduplication, and conflict resolution utilities
npm install @bernierllc/openapi-mergerOpenAPI specification merging and deduplication utilities that combine multiple documents into a single coherent contract while preserving lineage and resolving conflicts.
- 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
``bash`
npm install @bernierllc/openapi-merger
`typescript
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);
`
The main class for merging OpenAPI specifications with configurable strategies and options.
#### Constructor
`typescript`
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.
`typescript
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 warnings
`
Returns:
- 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.
`typescript
const conflicts = merger.detectConflicts([specA, specB]);
conflicts.forEach(conflict => {
console.log(Conflict in ${conflict.type}: ${conflict.description});`
});
#### FIRST_WINS
First occurrence wins for conflicts:
`typescript`
const merger = new OpenAPIMerger({ strategy: MergeStrategy.FIRST_WINS });
const result = merger.merge([spec1, spec2, spec3]);
// spec1 values preserved on conflict
#### LAST_WINS
Last occurrence wins for conflicts:
`typescript`
const merger = new OpenAPIMerger({ strategy: MergeStrategy.LAST_WINS });
const result = merger.merge([spec1, spec2, spec3]);
// spec3 values override earlier specs
#### MERGE_DEEP
Deep merge with intelligent conflict resolution:
`typescript`
const merger = new OpenAPIMerger({ strategy: MergeStrategy.MERGE_DEEP });
const result = merger.merge([spec1, spec2, spec3]);
// Deep merges objects, concatenates arrays
#### FAIL_ON_CONFLICT
Strict mode that fails on any conflict:
`typescript`
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:
`typescript`
const merger = new OpenAPIMerger({
strategy: MergeStrategy.CUSTOM,
conflictResolver: (path, values) => {
// Custom logic to resolve conflicts
return values[0]; // Example: use first value
}
});
#### optimizeSchemaRefs(spec: OpenAPIObject): OptimizationResult
Replaces duplicate schemas with references to canonical definitions:
`typescript
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 specification
`
#### validateComponentIntegrity(spec: OpenAPIObject): IntegrityReport
Validates component references and identifies issues:
`typescript
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 status
`
Track lineage of merged components:
`typescript
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 file
`
#### normalizeComponentNames(components: Components): Components
Sanitizes component names for consistency:
`typescript
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' }
// }
`
`typescript
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 /products
`
`typescript
const conflicts = merger.detectConflicts([spec1, spec2, spec3]);
if (conflicts.length > 0) {
console.log('Found conflicts:');
conflicts.forEach(conflict => {
console.log(- ${conflict.type}: ${conflict.path}); Description: ${conflict.description}
console.log();`
});
}
`typescript
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);Updated ${optimized.referencesUpdated} references
console.log();`
`typescript
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'
`
`bash``
npm test
- 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.
UNLICENSED – Internal Bernier LLC package.