Comprehensive tooling for working with OpenAPI definitions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Tools for extracting subsets of large OpenAPI definitions by tags or specific paths, creating focused API definitions.
Extract a subset of an OpenAPI definition based on tags or specific paths and methods.
/**
* Reduce an OpenAPI definition to specific tags or paths
* @param definition - OpenAPI definition to reduce
* @param opts - Reduction options specifying what to keep
* @returns New OpenAPI definition containing only specified elements
* @throws Error if all paths are removed or invalid options provided
*/
function reducer(definition: OASDocument, opts?: ReducerOptions): OASDocument;
interface ReducerOptions {
/** Object mapping paths to arrays of methods (or '*' for all methods) */
paths?: Record<string, string[] | '*'>;
/** Array of tag names to include */
tags?: string[];
}Usage Examples:
import reducer from "oas/reducer";
const originalDef = {
openapi: "3.1.0",
info: { title: "Full API", version: "1.0.0" },
paths: {
"/users": { get: { tags: ["users"] }, post: { tags: ["users"] } },
"/orders": { get: { tags: ["orders"] }, post: { tags: ["orders"] } },
"/admin": { get: { tags: ["admin"] }, delete: { tags: ["admin"] } }
}
};
// Reduce by tags
const userAPI = reducer(originalDef, {
tags: ['users']
});
// Result: Only /users paths included
// Reduce by specific paths and methods
const readOnlyAPI = reducer(originalDef, {
paths: {
'/users': ['get'],
'/orders': ['get']
}
});
// Result: Only GET methods for /users and /orders
// Reduce by paths with all methods
const userAndOrderAPI = reducer(originalDef, {
paths: {
'/users': '*',
'/orders': '*'
}
});
// Result: All methods for /users and /orders pathsExtract operations based on OpenAPI tags for logical grouping:
// Create API subsets by functional area
const userManagementAPI = reducer(fullAPI, {
tags: ['users', 'profiles', 'authentication']
});
const orderProcessingAPI = reducer(fullAPI, {
tags: ['orders', 'payments', 'shipping']
});
const adminAPI = reducer(fullAPI, {
tags: ['admin', 'monitoring', 'configuration']
});
console.log("Created 3 focused API definitions from main API");Extract specific endpoints and methods:
// Create read-only API
const readOnlyAPI = reducer(fullAPI, {
paths: {
'/users': ['get'],
'/users/{id}': ['get'],
'/orders': ['get'],
'/orders/{id}': ['get'],
'/products': ['get'],
'/products/{id}': ['get']
}
});
// Create write-only API for integrations
const writeOnlyAPI = reducer(fullAPI, {
paths: {
'/webhooks': '*',
'/users': ['post', 'put'],
'/orders': ['post', 'put', 'patch']
}
});
// Extract specific workflow
const checkoutAPI = reducer(fullAPI, {
paths: {
'/products': ['get'],
'/cart': '*',
'/checkout': '*',
'/payments': ['post']
}
});Use both tags and paths for fine-grained control:
// First reduce by tags, then by paths
const baseAPI = reducer(fullAPI, {
tags: ['public', 'users']
});
const publicReadAPI = reducer(baseAPI, {
paths: {
'/users': ['get'],
'/users/{id}': ['get'],
'/health': ['get'],
'/status': ['get']
}
});The reducer automatically removes unused components:
const originalComponents = fullAPI.components?.schemas || {};
console.log(`Original schemas: ${Object.keys(originalComponents).length}`);
const reducedAPI = reducer(fullAPI, { tags: ['users'] });
const reducedComponents = reducedAPI.components?.schemas || {};
console.log(`Reduced schemas: ${Object.keys(reducedComponents).length}`);
// Only components referenced by included operations are keptAll $ref pointers used by included operations are preserved:
// If User schema references Address schema, both are kept
const userAPI = reducer(fullAPI, {
paths: { '/users/{id}': ['get'] }
});
// Both User and Address schemas preserved if User references Address
const schemas = userAPI.components?.schemas || {};
console.log("Preserved schemas:", Object.keys(schemas));The reducer follows reference chains to preserve all dependencies:
// Schema dependency chain: User -> Profile -> Address -> Country
// Reducing to just /users endpoint preserves entire chain
const minimalAPI = reducer(fullAPI, {
paths: { '/users/{id}': ['get'] }
});
// All schemas in the dependency chain are preserved
const preservedSchemas = Object.keys(minimalAPI.components?.schemas || {});
console.log("Dependency chain preserved:", preservedSchemas);// Create different API versions from single definition
const v1API = reducer(fullAPI, {
tags: ['v1-users', 'v1-orders']
});
const v2API = reducer(fullAPI, {
tags: ['v2-users', 'v2-orders', 'v2-products']
});
// Save as separate OpenAPI files
writeFileSync('api-v1.json', JSON.stringify(v1API, null, 2));
writeFileSync('api-v2.json', JSON.stringify(v2API, null, 2));// Create focused APIs for different client types
const mobileAPI = reducer(fullAPI, {
tags: ['mobile', 'auth', 'core']
});
const webAPI = reducer(fullAPI, {
tags: ['web', 'auth', 'admin', 'reporting']
});
const partnerAPI = reducer(fullAPI, {
tags: ['partner', 'webhooks', 'sync']
});
// Generate SDKs from focused definitions
generateSDK('mobile', mobileAPI);
generateSDK('web', webAPI);
generateSDK('partner', partnerAPI);// Create focused documentation sets
const userDocs = reducer(fullAPI, {
tags: ['authentication', 'users', 'profiles']
});
const developerDocs = reducer(fullAPI, {
tags: ['webhooks', 'api-keys', 'rate-limits']
});
const adminDocs = reducer(fullAPI, {
tags: ['admin', 'monitoring', 'configuration']
});// Create test-specific API subsets
const smokeTestAPI = reducer(fullAPI, {
paths: {
'/health': ['get'],
'/users': ['get', 'post'],
'/orders': ['get']
}
});
const integrationTestAPI = reducer(fullAPI, {
tags: ['integration', 'webhooks', 'callbacks']
});
const loadTestAPI = reducer(fullAPI, {
paths: {
'/users': ['get'],
'/products': ['get'],
'/search': ['get', 'post']
}
});try {
// Empty reduction options - returns original
const unchanged = reducer(fullAPI, {});
// Case-insensitive matching
const mixedCase = reducer(fullAPI, {
tags: ['Users', 'ORDERS'] // Matches 'users' and 'orders'
});
// Invalid path removal
const filtered = reducer(fullAPI, {
paths: {
'/nonexistent': ['get'], // Ignored if path doesn't exist
'/users': ['get'] // Kept if path exists
}
});
} catch (error) {
console.error("Reduction failed:", error.message);
}try {
// This will throw an error - can't remove all paths
const emptyAPI = reducer(fullAPI, {
tags: ['nonexistent-tag']
});
} catch (error) {
console.error(error.message);
// "All paths in the API definition were removed. Did you supply the right path name to reduce by?"
}// Validate reduction before processing
function validateReduction(definition: OASDocument, opts: ReducerOptions): boolean {
const availableTags = new Set();
const availablePaths = new Set(Object.keys(definition.paths || {}));
// Collect all available tags
Object.values(definition.paths || {}).forEach(pathItem => {
Object.values(pathItem).forEach(operation => {
if ('tags' in operation && Array.isArray(operation.tags)) {
operation.tags.forEach(tag => availableTags.add(tag.toLowerCase()));
}
});
});
// Validate tag options
if (opts.tags) {
const invalidTags = opts.tags.filter(tag => !availableTags.has(tag.toLowerCase()));
if (invalidTags.length > 0) {
console.warn(`Tags not found: ${invalidTags.join(', ')}`);
}
}
// Validate path options
if (opts.paths) {
const invalidPaths = Object.keys(opts.paths).filter(path =>
!availablePaths.has(path.toLowerCase())
);
if (invalidPaths.length > 0) {
console.warn(`Paths not found: ${invalidPaths.join(', ')}`);
}
}
return true;
}
// Use validation before reduction
if (validateReduction(fullAPI, reductionOptions)) {
const reducedAPI = reducer(fullAPI, reductionOptions);
}// Generate multiple API artifacts in build process
async function buildAPIArtifacts(sourceAPI: OASDocument) {
const artifacts = [
{ name: 'public', opts: { tags: ['public'] } },
{ name: 'admin', opts: { tags: ['admin'] } },
{ name: 'mobile', opts: { tags: ['mobile', 'core'] } },
{ name: 'partner', opts: { tags: ['partner', 'webhooks'] } }
];
artifacts.forEach(({ name, opts }) => {
try {
const reducedAPI = reducer(sourceAPI, opts);
const filename = `dist/api-${name}.json`;
writeFileSync(filename, JSON.stringify(reducedAPI, null, 2));
console.log(`✅ Generated ${filename}`);
// Validate generated API
const opCount = Object.keys(reducedAPI.paths || {}).length;
console.log(` ${opCount} paths included`);
} catch (error) {
console.error(`❌ Failed to generate ${name}: ${error.message}`);
}
});
}// Serve different API subsets based on user permissions
function getAPIForUser(user: any, fullAPI: OASDocument): OASDocument {
if (user.role === 'admin') {
return fullAPI; // Full access
}
if (user.role === 'partner') {
return reducer(fullAPI, {
tags: ['partner', 'webhooks', 'public']
});
}
if (user.role === 'developer') {
return reducer(fullAPI, {
tags: ['public', 'auth', 'core']
});
}
// Default: public endpoints only
return reducer(fullAPI, {
tags: ['public']
});
}Install with Tessl CLI
npx tessl i tessl/npm-oasdocs