UNPKG

odata-active-record-core

Version:

Core Active Record implementation for OData - The easiest way to interact with OData APIs

373 lines 14.8 kB
/** * Interface Generator for OData Active Record * Automatically generates TypeScript interfaces from entity schemas */ export class InterfaceGenerator { generatedInterfaces = new Map(); /** * Generate an interface from an entity schema */ generateInterface(entityName, schema, options = {}) { const { format = 'typescript', useTypes = format === 'typescript', useJSDoc = true } = options; const interfaceName = this.formatInterfaceName(entityName); let interfaceCode = ''; // Add JSDoc comment if (useJSDoc) { interfaceCode += `/**\n * Auto-generated interface for ${entityName} entity\n */\n`; } // Generate interface based on format if (format === 'typescript') { interfaceCode += `export interface ${interfaceName} {\n`; } else if (format === 'javascript') { interfaceCode += `/** @typedef {Object} ${interfaceName} */\n`; interfaceCode += `/** @type {${interfaceName}} */\n`; interfaceCode += `const ${interfaceName} = {};\n\n`; interfaceCode += `export { ${interfaceName} };\n\n`; interfaceCode += `/**\n * ${interfaceName} type definition\n */\n`; interfaceCode += `/**\n`; } else if (format === 'jsx') { interfaceCode += `export interface ${interfaceName} {\n`; } // Add ID field if not present in schema if (!schema.fields.id) { if (useJSDoc) { interfaceCode += ' /** Auto-generated primary key */\n'; } if (format === 'typescript' || format === 'jsx') { interfaceCode += ' id?: number;\n\n'; } else if (format === 'javascript') { interfaceCode += ' * @property {number} [id] - Auto-generated primary key\n'; } } // Generate fields from schema Object.entries(schema.fields).forEach(([fieldName, fieldDef]) => { const fieldComment = this.generateFieldComment(fieldDef, format, useJSDoc); const fieldType = this.getTypeScriptType(fieldDef); const isOptional = fieldDef.nullable || fieldName === 'id'; const optionalMarker = isOptional ? '?' : ''; if (format === 'typescript' || format === 'jsx') { if (fieldComment) { interfaceCode += ` ${fieldComment}\n`; } interfaceCode += ` ${fieldName}${optionalMarker}: ${fieldType};\n\n`; } else if (format === 'javascript') { interfaceCode += ` * @property {${fieldType}} ${isOptional ? '[' : ''}${fieldName}${isOptional ? ']' : ''} - ${this.getFieldDescription(fieldDef)}\n`; } }); if (format === 'typescript' || format === 'jsx') { interfaceCode += '}\n'; } else if (format === 'javascript') { interfaceCode += ' */\n'; } // Store the generated interface this.generatedInterfaces.set(interfaceName, interfaceCode); return interfaceCode; } /** * Generate interfaces for multiple entities */ generateInterfaces(entities, options = {}) { const { format = 'typescript' } = options; let allInterfaces = `/**\n * Auto-generated interfaces for OData Active Record entities\n * Generated on: ${new Date().toISOString()}\n * Format: ${format}\n */\n\n`; Object.entries(entities).forEach(([entityName, schema]) => { allInterfaces += this.generateInterface(entityName, schema, options); allInterfaces += '\n'; }); return allInterfaces; } /** * Generate interfaces from namespace entities */ generateFromNamespace(namespace) { const entities = {}; // Extract entities from namespace (this would depend on the actual namespace structure) if (namespace.entities) { Object.entries(namespace.entities).forEach(([name, entity]) => { if (entity.schema) { entities[name] = entity.schema; } }); } return this.generateInterfaces(entities); } /** * Generate a complete file with all interfaces */ generateFile(entities, options = {}) { const { fileName = 'generated-interfaces.ts', format = 'typescript', addImports = true, addExports = true, addComments = true, useTypes = format === 'typescript', useJSDoc = true } = options; const fileExtension = format === 'typescript' ? '.ts' : format === 'javascript' ? '.js' : '.tsx'; const actualFileName = fileName.endsWith(fileExtension) ? fileName : `${fileName}${fileExtension}`; let fileContent = ''; if (addComments) { fileContent += `/**\n`; fileContent += ` * Auto-generated ${format} interfaces\n`; fileContent += ` * File: ${actualFileName}\n`; fileContent += ` * Generated on: ${new Date().toISOString()}\n`; fileContent += ` * Source: OData Active Record schemas\n`; fileContent += ` */\n\n`; } if (addImports && format === 'typescript') { fileContent += `// Import types if needed\n`; fileContent += `import type { IEntitySchema } from 'odata-active-record-contracts';\n\n`; } fileContent += this.generateInterfaces(entities, { format, useTypes, useJSDoc }); if (addExports && format === 'typescript') { fileContent += `\n// Export all interfaces\n`; fileContent += `export type {\n`; Object.keys(entities).forEach(entityName => { const interfaceName = this.formatInterfaceName(entityName); fileContent += ` ${interfaceName},\n`; }); fileContent += `};\n`; } return fileContent; } /** * Generate interfaces with additional utility types */ generateWithUtilities(entities, options = {}) { const { includeInputTypes = true, includeUpdateTypes = true, includePartialTypes = true, includeQueryTypes = true, format = 'typescript', useTypes = format === 'typescript', useJSDoc = true } = options; let content = this.generateInterfaces(entities, { format, useTypes, useJSDoc }); if (includeInputTypes) { content += this.generateInputTypes(entities, { format, useTypes, useJSDoc }); } if (includeUpdateTypes) { content += this.generateUpdateTypes(entities, { format, useTypes, useJSDoc }); } if (includePartialTypes) { content += this.generatePartialTypes(entities, { format, useTypes, useJSDoc }); } if (includeQueryTypes) { content += this.generateQueryTypes(entities, { format, useTypes, useJSDoc }); } return content; } /** * Generate input types (for create operations) */ generateInputTypes(entities, options = {}) { const { format = 'typescript', useJSDoc = true } = options; if (format !== 'typescript') { return ''; // Only TypeScript supports utility types } let content = '\n/** Input types for create operations */\n'; Object.entries(entities).forEach(([entityName, schema]) => { const interfaceName = this.formatInterfaceName(entityName); const inputTypeName = `${interfaceName}Input`; content += `export type ${inputTypeName} = Omit<${interfaceName}, 'id'>;\n`; }); return content; } /** * Generate update types (for update operations) */ generateUpdateTypes(entities, options = {}) { const { format = 'typescript' } = options; if (format !== 'typescript') { return ''; // Only TypeScript supports utility types } let content = '\n/** Update types for update operations */\n'; Object.entries(entities).forEach(([entityName, schema]) => { const interfaceName = this.formatInterfaceName(entityName); const updateTypeName = `${interfaceName}Update`; content += `export type ${updateTypeName} = Partial<Omit<${interfaceName}, 'id'>> & { id: number };\n`; }); return content; } /** * Generate partial types (for partial updates) */ generatePartialTypes(entities, options = {}) { const { format = 'typescript' } = options; if (format !== 'typescript') { return ''; // Only TypeScript supports utility types } let content = '\n/** Partial types for flexible operations */\n'; Object.entries(entities).forEach(([entityName, schema]) => { const interfaceName = this.formatInterfaceName(entityName); const partialTypeName = `${interfaceName}Partial`; content += `export type ${partialTypeName} = Partial<${interfaceName}>;\n`; }); return content; } /** * Generate query types (for where clauses) */ generateQueryTypes(entities, options = {}) { const { format = 'typescript' } = options; if (format !== 'typescript') { return ''; // Only TypeScript supports utility types } let content = '\n/** Query types for where clauses */\n'; Object.entries(entities).forEach(([entityName, schema]) => { const interfaceName = this.formatInterfaceName(entityName); const queryTypeName = `${interfaceName}Query`; content += `export type ${queryTypeName} = {\n`; Object.entries(schema.fields).forEach(([fieldName, fieldDef]) => { const fieldType = this.getTypeScriptType(fieldDef); content += ` ${fieldName}?: ${fieldType};\n`; }); content += `};\n`; }); return content; } /** * Get TypeScript type from field definition */ getTypeScriptType(fieldDef) { switch (fieldDef.type) { case 'string': return 'string'; case 'number': return 'number'; case 'boolean': return 'boolean'; case 'date': return 'Date'; case 'json': return 'any'; case 'array': return 'any[]'; default: return 'unknown'; } } /** * Generate field comment from field definition */ generateFieldComment(fieldDef, format = 'typescript', useJSDoc = true) { if (!useJSDoc) return ''; const comments = []; if (fieldDef.primary) { comments.push('Primary key'); } if (fieldDef.autoIncrement) { comments.push('Auto-increment'); } if (fieldDef.nullable === false) { comments.push('Required'); } if (fieldDef.defaultValue !== undefined) { comments.push(`Default: ${JSON.stringify(fieldDef.defaultValue)}`); } if (fieldDef.validation) { comments.push(`Validation: ${fieldDef.validation}`); } if (comments.length === 0) { return ''; } return ` /** ${comments.join(', ')} */`; } /** * Get field description for JSDoc */ getFieldDescription(fieldDef) { const descriptions = []; if (fieldDef.primary) { descriptions.push('Primary key'); } if (fieldDef.autoIncrement) { descriptions.push('Auto-increment'); } if (fieldDef.nullable === false) { descriptions.push('Required'); } if (fieldDef.defaultValue !== undefined) { descriptions.push(`Default: ${JSON.stringify(fieldDef.defaultValue)}`); } if (fieldDef.validation) { descriptions.push(`Validation: ${fieldDef.validation}`); } return descriptions.length > 0 ? descriptions.join(', ') : 'Field'; } /** * Format entity name to interface name */ formatInterfaceName(entityName) { // Convert to PascalCase return entityName.charAt(0).toUpperCase() + entityName.slice(1); } /** * Get all generated interfaces */ getAllGeneratedInterfaces() { return new Map(this.generatedInterfaces); } /** * Clear generated interfaces */ clearGeneratedInterfaces() { this.generatedInterfaces.clear(); } } /** * CLI-friendly interface generator */ export class CLIInterfaceGenerator extends InterfaceGenerator { /** * Generate interfaces from command line arguments */ generateFromCLI(args) { const { entities = {}, options = {} } = args; if (Object.keys(entities).length === 0) { throw new Error('No entities provided for interface generation'); } return this.generateWithUtilities(entities, options); } /** * Generate and save to file */ generateAndSave(entities, outputPath, options = {}) { const content = this.generateWithUtilities(entities, options); // In a real implementation, this would write to file console.log(`Generated interfaces for ${Object.keys(entities).length} entities`); console.log(`Output would be saved to: ${outputPath}`); console.log(`Content length: ${content.length} characters`); } } /** * Utility functions for easy interface generation */ export const InterfaceUtils = { /** * Quick interface generation from a single entity */ quickGenerate(entityName, schema, options = {}) { const generator = new InterfaceGenerator(); return generator.generateInterface(entityName, schema, options); }, /** * Generate interfaces from namespace */ fromNamespace(namespace, options = {}) { const generator = new InterfaceGenerator(); return generator.generateFromNamespace(namespace); }, /** * Generate complete file */ generateFile(entities, options = {}) { const generator = new InterfaceGenerator(); return generator.generateFile(entities, options); }, /** * Generate with all utilities */ generateWithAll(entities, options = {}) { const generator = new InterfaceGenerator(); return generator.generateWithUtilities(entities, { includeInputTypes: true, includeUpdateTypes: true, includePartialTypes: true, includeQueryTypes: true, ...options }); } }; //# sourceMappingURL=interface-generator.js.map