arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
168 lines (167 loc) • 5.93 kB
JavaScript
/**
* Generate TypeScript interfaces from OpenAPI schemas
*/
export function generateTypes(spec) {
let output = `/**
* Auto-generated TypeScript types from OpenAPI spec
* DO NOT EDIT - This file is generated by Arela
* @see https://swagger.io/specification/
*/
`;
// Extract all schemas from components
const schemas = spec.components?.schemas || {};
for (const [name, schema] of Object.entries(schemas)) {
output += generateInterface(name, schema);
output += '\n\n';
}
// Generate request/response types for each endpoint
for (const [path, methods] of Object.entries(spec.paths)) {
for (const [method, operation] of Object.entries(methods)) {
if (typeof operation !== 'object' || !operation)
continue;
if (operation.requestBody) {
output += generateRequestType(path, method, operation);
output += '\n\n';
}
if (operation.responses) {
output += generateResponseType(path, method, operation);
output += '\n\n';
}
}
}
return output;
}
function generateInterface(name, schema) {
const properties = schema.properties || {};
const required = schema.required || [];
let output = `export interface ${name} {\n`;
for (const [propName, propSchema] of Object.entries(properties)) {
const isRequired = required.includes(propName);
const tsType = schemaToTypeScript(propSchema);
const description = propSchema.description ? ` /** ${propSchema.description} */\n` : '';
output += `${description} ${propName}${isRequired ? '' : '?'}: ${tsType};\n`;
}
output += '}';
return output;
}
export function schemaToTypeScript(schema) {
if (!schema)
return 'any';
// Handle $ref
if (schema.$ref) {
const refName = schema.$ref.split('/').pop();
return refName || 'any';
}
// Handle oneOf, anyOf, allOf
if (schema.oneOf) {
return schema.oneOf.map((s) => schemaToTypeScript(s)).join(' | ');
}
if (schema.anyOf) {
return schema.anyOf.map((s) => schemaToTypeScript(s)).join(' | ');
}
if (schema.allOf) {
return schema.allOf.map((s) => schemaToTypeScript(s)).join(' & ');
}
// Handle enum
if (schema.enum) {
return schema.enum.map((e) => `'${e}'`).join(' | ');
}
switch (schema.type) {
case 'string':
if (schema.format === 'date')
return 'string'; // or Date
if (schema.format === 'date-time')
return 'string'; // or Date
return 'string';
case 'number':
case 'integer':
return 'number';
case 'boolean':
return 'boolean';
case 'null':
return 'null';
case 'array':
if (!schema.items)
return 'any[]';
return `${schemaToTypeScript(schema.items)}[]`;
case 'object':
if (schema.additionalProperties === true) {
return 'Record<string, any>';
}
if (typeof schema.additionalProperties === 'object') {
return `Record<string, ${schemaToTypeScript(schema.additionalProperties)}>`;
}
if (schema.properties) {
const props = Object.entries(schema.properties)
.map(([key, val]) => `${key}${!schema.required?.includes(key) ? '?' : ''}: ${schemaToTypeScript(val)}`)
.join('; ');
return `{ ${props} }`;
}
return '{ [key: string]: any }';
default:
return 'any';
}
}
function generateRequestType(path, method, operation) {
const operationId = operation.operationId || generateOperationId(path, method);
const typeName = `${capitalize(operationId)}Request`;
const requestBody = operation.requestBody;
if (!requestBody)
return '';
const contentValue = Object.values(requestBody.content)?.[0];
const schema = contentValue?.schema;
if (!schema)
return '';
const type = schemaToTypeScript(schema);
return `export type ${typeName} = ${type};`;
}
function generateResponseType(path, method, operation) {
const operationId = operation.operationId || generateOperationId(path, method);
const typeName = `${capitalize(operationId)}Response`;
const responses = operation.responses || {};
const successResponse = responses['200'] || responses['201'] || Object.values(responses)[0];
if (!successResponse)
return '';
const contentValue = Object.values(successResponse.content)?.[0];
const schema = contentValue?.schema;
if (!schema)
return '';
const type = schemaToTypeScript(schema);
return `export type ${typeName} = ${type};`;
}
function generateOperationId(path, method) {
const parts = path.split('/').filter((p) => p && !p.startsWith('{'));
const resource = parts[parts.length - 1] || 'resource';
const action = getActionFromMethod(method);
return `${action}${capitalize(singularize(resource))}`;
}
function getActionFromMethod(method) {
const m = method.toLowerCase();
switch (m) {
case 'get':
return 'get';
case 'post':
return 'create';
case 'put':
return 'update';
case 'patch':
return 'patch';
case 'delete':
return 'delete';
default:
return 'call';
}
}
function singularize(word) {
if (word.endsWith('ies'))
return word.slice(0, -3) + 'y';
if (word.endsWith('es'))
return word.slice(0, -2);
if (word.endsWith('s'))
return word.slice(0, -1);
return word;
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
//# sourceMappingURL=type-generator.js.map