All files SchemaReflector.ts

100% Statements 30/30
80.95% Branches 17/21
100% Functions 5/5
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 1094x 4x   4x 76x     2x 2x 1x     1x 1x   1x     1x                   3x 3x 3x   3x 1x             2x 4x       1x       2x                                 4x 4x 1x       3x 3x   3x     1x 2x     1x       1x                                            
import { MetadataRegistry } from "./MetadataRegistry";
import 'reflect-metadata';
 
export class SchemaReflector {
  private registry = MetadataRegistry.getInstance();
 
  getEntitySchema(entity: Function): any {
    const metadata = this.registry.getEntityMetadata(entity);
    if (!metadata) {
      throw new Error(`Class ${entity.name} is not decorated as an Entity`);
    }
 
    const properties = this.registry.getAllProperties(entity) || new Map();
    const idProperties = this.registry.getIdProperties(entity) || new Set();
    const relationships =
      this.registry.getAllRelationships(entity) || new Map();
 
    // Build schema representation
    return {
      name: metadata.name || entity.name,
      description: metadata.description,
      properties: Object.fromEntries(properties),
      idProperties: Array.from(idProperties),
      relationships: Object.fromEntries(relationships),
    };
  }
 
  validateEntity(entity: any): { valid: boolean; errors: string[] } {
    const constructor = entity.constructor;
    const properties = this.registry.getAllProperties(constructor);
    const errors: string[] = [];
 
    if (!properties) {
      return {
        valid: false,
        errors: ["Entity class is not properly decorated"],
      };
    }
 
    // Validate required properties
    for (const [propertyKey, metadata] of properties.entries()) {
      if (
        metadata.required &&
        (entity[propertyKey] === undefined || entity[propertyKey] === null)
      ) {
        errors.push(`Required property '${propertyKey}' is missing`);
      }
    }
 
    return {
      valid: errors.length === 0,
      errors,
    };
  }
  
  /**
   * Infer relationship type based on property type
   * This method can be used to automatically determine relationship types
   * based on TypeScript's metadata when using TypeScript with emitDecoratorMetadata
   * 
   * @param target The target object (prototype)
   * @param propertyKey The property name
   * @returns The inferred relationship type or undefined if it cannot be determined
   */
  inferRelationshipType(target: any, propertyKey: string): 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many' | undefined {
    // First check if explicit relationship type is defined via decorators
    const explicitType = Reflect.getMetadata('relationship:type', target, propertyKey);
    if (explicitType) {
      return explicitType as any;
    }
    
    // Try to infer from property design type (requires emitDecoratorMetadata in tsconfig)
    const designType = Reflect.getMetadata('design:type', target, propertyKey);
    if (designType) {
      // Check if it's an array
      if (designType === Array) {
        // It's a "to-many" relationship, but we can't distinguish 
        // between one-to-many and many-to-many without more context
        return undefined;
      } else if (designType.prototype && this.registry.getEntityMetadata(designType)) {
        // It's a "to-one" relationship to an entity, but we can't distinguish
        // between one-to-one and many-to-one without more context
        return undefined;
      }
    }
    
    return undefined;
  }
  
  /**
   * Automatically detect and register relationships from property types
   * This method would be used to enhance the reflection capabilities in the future
   * 
   * @param entityClass The entity class to analyze
   */
  detectRelationships(entityClass: Function): void {
    // This is a placeholder for future implementation
    // It would scan all properties of the class, and for those that are entities
    // or arrays of entities, it would register appropriate relationships
 
    // Implementation would require:
    // 1. Get all properties from class prototype
    // 2. For each property, check if it's an entity type or array of entity type
    // 3. Register appropriate relationship metadata
    
    // Note: This would require emitDecoratorMetadata and appropriate tsconfig settings
  }
}