Refactor vendor extensions by consolidating and enhancing type safety. Introduce a new common vendor extension and remove outdated examples, improving overall structure and IntelliSense support.

This commit is contained in:
Luke Hagar
2025-09-27 01:52:32 +00:00
parent 20d1a7beeb
commit 2a03954571
9 changed files with 165 additions and 328 deletions

View File

@@ -1,9 +1,4 @@
/**
* Example Vendor Extensions
*/
import { defineConfig } from './index.js';
import { defineConfig, createPositionHelpers } from './index.js';
// Complete vendor configuration with smart positioning
export const config = defineConfig({
@@ -34,3 +29,139 @@ export const config = defineConfig({
}
});
// Example vendor with enhanced type safety
export const exampleEnhanced = defineConfig({
info: {
name: 'Example Enhanced',
website: 'https://example.com',
support: 'support@example.com'
},
extensions: {
// Type-safe context with IntelliSense for available keys
'top-level': (before, after) => {
// IntelliSense will show: 'swagger', 'openapi', 'jsonSchemaDialect', 'info', 'externalDocs', etc.
return {
'x-example-sdk': before('info'), // ✅ Type-safe: 'info' is a valid top-level key
'x-example-config': after('paths'), // ✅ Type-safe: 'paths' is a valid top-level key
// 'x-example-invalid': before('invalidKey'), // ❌ TypeScript error: 'invalidKey' is not a valid top-level key
};
},
'info': (before, after) => {
// IntelliSense will show: 'title', 'version', 'summary', 'description', 'termsOfService', etc.
return {
'x-example-info': after('version'), // ✅ Type-safe: 'version' is a valid info key
'x-example-metadata': before('description'), // ✅ Type-safe: 'description' is a valid info key
};
},
'operation': (before, after) => {
// IntelliSense will show: 'summary', 'operationId', 'description', 'externalDocs', 'tags', etc.
return {
'x-example-retries': after('parameters'), // ✅ Type-safe: 'parameters' is a valid operation key
'x-example-timeout': before('responses'), // ✅ Type-safe: 'responses' is a valid operation key
'x-example-cache': after('servers'), // ✅ Type-safe: 'servers' is a valid operation key
};
},
'schema': (before, after) => {
// IntelliSense will show: '$ref', 'title', 'description', 'type', 'format', 'enum', etc.
return {
'x-example-validation': after('type'), // ✅ Type-safe: 'type' is a valid schema key
'x-example-example': after('example'), // ✅ Type-safe: 'example' is a valid schema key
};
},
'parameter': (before, after) => {
// IntelliSense will show: 'name', 'description', 'in', 'required', 'deprecated', etc.
return {
'x-example-param': after('schema'), // ✅ Type-safe: 'schema' is a valid parameter key
};
},
'response': (before, after) => {
// IntelliSense will show: 'description', 'headers', 'content', 'links'
return {
'x-example-response': after('description'), // ✅ Type-safe: 'description' is a valid response key
};
},
'securityScheme': (before, after) => {
// IntelliSense will show: 'type', 'description', 'name', 'in', 'scheme', etc.
return {
'x-example-auth': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key
};
},
'server': (before, after) => {
// IntelliSense will show: 'url', 'description', 'variables'
return {
'x-example-server': after('url'), // ✅ Type-safe: 'url' is a valid server key
};
},
'tag': (before, after) => {
// IntelliSense will show: 'name', 'description', 'externalDocs'
return {
'x-example-tag': after('name'), // ✅ Type-safe: 'name' is a valid tag key
};
},
'externalDocs': (before, after) => {
// IntelliSense will show: 'description', 'url'
return {
'x-example-docs': after('url'), // ✅ Type-safe: 'url' is a valid external docs key
};
},
'webhook': (before, after) => {
// IntelliSense will show: 'summary', 'operationId', 'description', 'deprecated', etc.
return {
'x-example-webhook': after('operationId'), // ✅ Type-safe: 'operationId' is a valid webhook key
};
},
'definitions': (before, after) => {
// IntelliSense will show schema keys: '$ref', 'title', 'description', 'type', etc.
return {
'x-example-definition': after('type'), // ✅ Type-safe: 'type' is a valid schema key
};
},
'securityDefinitions': (before, after) => {
// IntelliSense will show security scheme keys: 'type', 'description', 'name', etc.
return {
'x-example-security': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key
};
}
}
});
// Alternative approach using the enhanced helper functions
export const exampleWithHelpers = defineConfig({
info: {
name: 'Example With Helpers',
website: 'https://example.com',
support: 'support@example.com'
},
extensions: {
'top-level': (before, after) => {
// You can also use the enhanced helpers for additional functionality
const helpers = createPositionHelpers('top-level');
// Get all available keys for this context
const availableKeys = helpers.getAvailableKeys();
console.log('Available top-level keys:', availableKeys);
// Validate if a key exists
if (helpers.isValidKey('info')) {
console.log('info is a valid top-level key');
}
return {
'x-example-enhanced': before('info'),
'x-example-config': after('paths'),
};
}
}
});

View File

@@ -7,16 +7,14 @@
import { before, after, type KeyMap, type VendorExtensions } from './index.js';
// Import vendor extensions statically
import { common } from './vendor/common.js';
import { speakeasy } from './vendor/speakeasy.js';
import { postman } from './vendor/postman.js';
import { redoc } from './vendor/redoc.js';
// Import vendor extensions statically
const vendorModules = [
// Update this list as new vendors are added
speakeasy,
postman,
redoc
common
];
// Type for vendor module

21
src/extensions/vendor/common.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
import { defineConfig } from "../index.js";
// Function-based extensions with before/after helpers
export const common = defineConfig({
info: {
name: 'Common',
},
extensions: {
'top-level': (before, after) => {
return {
'x-tagGroups': after('tags'),
};
},
'operation': (before, after) => {
return {
'x-badges': after('tags'),
'x-codeSamples': after('schemes'),
};
},
}
});

View File

@@ -1,145 +0,0 @@
/**
* Enhanced Vendor Extensions Example
*
* This example demonstrates the improved IntelliSense and type safety
* for vendor extensions configuration.
*/
import { defineConfig, createPositionHelpers } from "../index.js";
// Example vendor with enhanced type safety
export const exampleEnhanced = defineConfig({
info: {
name: 'Example Enhanced',
website: 'https://example.com',
support: 'support@example.com'
},
extensions: {
// Type-safe context with IntelliSense for available keys
'top-level': (before, after) => {
// IntelliSense will show: 'swagger', 'openapi', 'jsonSchemaDialect', 'info', 'externalDocs', etc.
return {
'x-example-sdk': before('info'), // ✅ Type-safe: 'info' is a valid top-level key
'x-example-config': after('paths'), // ✅ Type-safe: 'paths' is a valid top-level key
// 'x-example-invalid': before('invalidKey'), // ❌ TypeScript error: 'invalidKey' is not a valid top-level key
};
},
'info': (before, after) => {
// IntelliSense will show: 'title', 'version', 'summary', 'description', 'termsOfService', etc.
return {
'x-example-info': after('version'), // ✅ Type-safe: 'version' is a valid info key
'x-example-metadata': before('description'), // ✅ Type-safe: 'description' is a valid info key
};
},
'operation': (before, after) => {
// IntelliSense will show: 'summary', 'operationId', 'description', 'externalDocs', 'tags', etc.
return {
'x-example-retries': after('parameters'), // ✅ Type-safe: 'parameters' is a valid operation key
'x-example-timeout': before('responses'), // ✅ Type-safe: 'responses' is a valid operation key
'x-example-cache': after('servers'), // ✅ Type-safe: 'servers' is a valid operation key
};
},
'schema': (before, after) => {
// IntelliSense will show: '$ref', 'title', 'description', 'type', 'format', 'enum', etc.
return {
'x-example-validation': after('type'), // ✅ Type-safe: 'type' is a valid schema key
'x-example-example': after('example'), // ✅ Type-safe: 'example' is a valid schema key
};
},
'parameter': (before, after) => {
// IntelliSense will show: 'name', 'description', 'in', 'required', 'deprecated', etc.
return {
'x-example-param': after('schema'), // ✅ Type-safe: 'schema' is a valid parameter key
};
},
'response': (before, after) => {
// IntelliSense will show: 'description', 'headers', 'content', 'links'
return {
'x-example-response': after('description'), // ✅ Type-safe: 'description' is a valid response key
};
},
'securityScheme': (before, after) => {
// IntelliSense will show: 'type', 'description', 'name', 'in', 'scheme', etc.
return {
'x-example-auth': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key
};
},
'server': (before, after) => {
// IntelliSense will show: 'url', 'description', 'variables'
return {
'x-example-server': after('url'), // ✅ Type-safe: 'url' is a valid server key
};
},
'tag': (before, after) => {
// IntelliSense will show: 'name', 'description', 'externalDocs'
return {
'x-example-tag': after('name'), // ✅ Type-safe: 'name' is a valid tag key
};
},
'externalDocs': (before, after) => {
// IntelliSense will show: 'description', 'url'
return {
'x-example-docs': after('url'), // ✅ Type-safe: 'url' is a valid external docs key
};
},
'webhook': (before, after) => {
// IntelliSense will show: 'summary', 'operationId', 'description', 'deprecated', etc.
return {
'x-example-webhook': after('operationId'), // ✅ Type-safe: 'operationId' is a valid webhook key
};
},
'definitions': (before, after) => {
// IntelliSense will show schema keys: '$ref', 'title', 'description', 'type', etc.
return {
'x-example-definition': after('type'), // ✅ Type-safe: 'type' is a valid schema key
};
},
'securityDefinitions': (before, after) => {
// IntelliSense will show security scheme keys: 'type', 'description', 'name', etc.
return {
'x-example-security': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key
};
}
}
});
// Alternative approach using the enhanced helper functions
export const exampleWithHelpers = defineConfig({
info: {
name: 'Example With Helpers',
website: 'https://example.com',
support: 'support@example.com'
},
extensions: {
'top-level': (before, after) => {
// You can also use the enhanced helpers for additional functionality
const helpers = createPositionHelpers('top-level');
// Get all available keys for this context
const availableKeys = helpers.getAvailableKeys();
console.log('Available top-level keys:', availableKeys);
// Validate if a key exists
if (helpers.isValidKey('info')) {
console.log('info is a valid top-level key');
}
return {
'x-example-enhanced': before('info'),
'x-example-config': after('paths'),
};
}
}
});

View File

@@ -1,25 +0,0 @@
/**
* Example Vendor Extensions
*/
// Function-based extensions with before/after helpers
export const extensions = {
'top-level': (before: (key: string) => number, after: (key: string) => number) => {
return {
'x-example-before-info': before('info'), // Before 'info'
'x-example-after-paths': after('paths'), // After 'paths'
};
},
'operation': (before: (key: string) => number, after: (key: string) => number) => {
return {
'x-example-before-parameters': before('parameters'), // Before 'parameters'
'x-example-after-responses': after('responses'), // After 'responses'
};
},
'schema': (before: (key: string) => number, after: (key: string) => number) => {
return {
'x-example-validation': after('type'), // After 'type'
'x-example-example': after('example'), // After 'example'
};
}
};

View File

@@ -1,37 +0,0 @@
/**
* Postman Extensions
*
* Postman collection extensions for OpenAPI formatting.
* Website: https://postman.com
*/
import { defineConfig } from "../index.js";
// Function-based extensions with before/after helpers
export const postman = defineConfig({
info: {
name: 'Postman',
website: 'https://postman.com',
support: 'support@postman.com'
},
extensions: {
'top-level': (before, after) => {
return {
'x-postman-collection': before('info'), // Before 'info'
'x-postman-version': after('paths'), // After 'paths'
};
},
'operation': (before, after) => {
return {
'x-postman-test': after('responses'), // After 'responses'
'x-postman-pre-request': before('parameters'), // Before 'parameters'
};
},
'schema': (before, after) => {
return {
'x-postman-example': after('example'), // After 'example'
'x-postman-mock': after('deprecated'), // After 'deprecated'
};
}
}
});

View File

@@ -1,42 +0,0 @@
/**
* Redoc Extensions
*
* Redoc documentation extensions for OpenAPI formatting.
* Website: https://redocly.com
*/
import { defineConfig } from "../index.js";
// Function-based extensions with before/after helpers
export const redoc = defineConfig({
info: {
name: 'Redocly',
website: 'https://redocly.com',
support: 'team@redocly.com'
},
extensions: {
'top-level': (before, after) => {
return {
'x-redoc-version': before('info'), // Before 'info'
'x-redoc-theme': after('paths'), // After 'paths'
};
},
'info': (before, after) => {
return {
'x-redoc-info': after('version'), // After 'version'
};
},
'operation': (before, after) => {
return {
'x-redoc-group': after('tags'), // After 'tags'
'x-redoc-hide': before('responses'), // Before 'responses'
};
},
'schema': (before, after) => {
return {
'x-redoc-example': after('example'), // After 'example'
'x-redoc-readonly': after('deprecated'), // After 'deprecated'
};
}
}
});

View File

@@ -17,72 +17,15 @@ export const speakeasy = defineConfig({
extensions: {
'top-level': (before, after) => {
return {
'x-speakeasy-sdk': before('info'), // Before 'info'
'x-speakeasy-auth': after('paths'), // After 'paths'
};
},
'info': (before, after) => {
return {
'x-speakeasy-info': after('version'), // After 'version'
'x-speakeasy-globals': after('security'),
'x-speakeasy-globals-hidden': after('security'),
};
},
'operation': (before, after) => {
return {
'x-speakeasy-retries': after('parameters'), // After 'parameters'
'x-speakeasy-timeout': before('responses'), // Before 'responses'
'x-speakeasy-cache': after('servers'), // After 'servers'
'x-speakeasy-pagination': after('parameters'),
'x-speakeasy-usage-example': after('deprecated'),
};
},
'schema': (before, after) => {
return {
'x-speakeasy-validation': after('type'), // After 'type'
'x-speakeasy-example': after('example'), // After 'example'
};
},
'parameter': (before, after) => {
return {
'x-speakeasy-param': after('schema'), // After 'schema'
};
},
'response': (before, after) => {
return {
'x-speakeasy-response': after('description'), // After 'description'
};
},
'securityScheme': (before, after) => {
return {
'x-speakeasy-auth': after('type'), // After 'type'
};
},
'server': (before, after) => {
return {
'x-speakeasy-server': after('url'), // After 'url'
};
},
'tag': (before, after) => {
return {
'x-speakeasy-tag': after('name'), // After 'name'
};
},
'externalDocs': (before, after) => {
return {
'x-speakeasy-docs': after('url'), // After 'url'
};
},
'webhook': (before, after) => {
return {
'x-speakeasy-webhook': after('operationId'), // After 'operationId'
};
},
'definitions': (before, after) => {
return {
'x-speakeasy-definition': after('type'), // After 'type'
};
},
'securityDefinitions': (before, after) => {
return {
'x-speakeasy-security': after('type'), // After 'type'
};
}
}
});

View File

@@ -11,8 +11,7 @@ describe('Vendor Extension System', () => {
// Check if extensions were loaded
expect(vendorExtensions['top-level']).toBeDefined();
expect(vendorExtensions['top-level']['x-speakeasy-sdk']).toBe(3); // before('info') = position 3
expect(vendorExtensions['top-level']['x-speakeasy-auth']).toBe(14); // after('paths') = position 14
expect(vendorExtensions['top-level']['x-tagGroups']).toBe(13);
});
@@ -28,12 +27,6 @@ describe('Vendor Extension System', () => {
// Check that extensions have the right structure
expect(vendorExtensions['top-level']).toBeDefined();
expect(vendorExtensions['info']).toBeDefined();
expect(vendorExtensions['operation']).toBeDefined();
expect(vendorExtensions['schema']).toBeDefined();
// Check specific extensions
expect(vendorExtensions['top-level']['x-speakeasy-sdk']).toBe(3); // before('info') = position 3
expect(vendorExtensions['top-level']['x-speakeasy-auth']).toBe(14); // after('paths') = position 14
});
});