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

api-definition-reduction.mddocs/

API Definition Reduction

Tools for extracting subsets of large OpenAPI definitions by tags or specific paths, creating focused API definitions.

Capabilities

Reduce API Definition

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 paths

Reduction Strategies

Tag-Based Reduction

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

Path-Specific Reduction

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

Combined Reduction

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

Component and Reference Management

Automatic Component Cleanup

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 kept

Reference Preservation

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

Deep Reference Resolution

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

Advanced Reduction Patterns

Multi-Version API Creation

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

Client SDK Generation

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

Documentation Splitting

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

Testing Subset Creation

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

Error Handling and Validation

Input Validation

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

Complete Path Removal

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?"
}

Validation Requirements

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

Integration Examples

Build Pipeline Integration

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

Dynamic API Serving

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