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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | 13x 13x 13x 13x 170x 170x 27x 27x 1x 26x 26x 26x 26x 282x 282x 282x 282x 1x 281x 281x 1918x 1918x 20x 281x 6x 6x 6x 1x 6x 7x 7x 7x 4x 3x 3x 2x 1x 1x 1x 1x | import { MetadataRegistry } from "./MetadataRegistry";
import { ValidationEngine, defaultValidationEngine } from "./ValidationEngine";
import type { ValidationResult } from "./types";
import 'reflect-metadata';
/**
* SchemaReflector provides utilities for inspecting and validating
* schema metadata from entity classes.
*/
export class SchemaReflector {
/**
* Creates a new SchemaReflector
* @param registry The metadata registry to use (if not provided, a new one will be created)
* @param validationEngine The validation engine to use (if not provided, the default one will be used)
*/
constructor(
private registry: MetadataRegistry = new MetadataRegistry(),
private validationEngine: ValidationEngine = defaultValidationEngine
) {}
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 errors: string[] = [];
// Check if entity is decorated
const isEntity = Reflect.hasMetadata("entity:options", constructor);
if (!isEntity) {
return {
valid: false,
errors: ["Entity class is not properly decorated"],
};
}
// Get all properties from reflect-metadata
const registeredProperties: string[] = Reflect.getMetadata("entity:properties", constructor) || [];
// Validate required properties
for (const propertyKey of registeredProperties) {
const propertyOptions = Reflect.getMetadata("property:options", entity, propertyKey);
if (
propertyOptions?.required &&
(entity[propertyKey] === undefined || entity[propertyKey] === null)
) {
errors.push(`Required property '${propertyKey}' is missing`);
}
}
return {
valid: errors.length === 0,
errors,
};
}
/**
* Validates an entity using the new ValidationEngine
* This method uses all validation decorators (@Min, @Max, @Email, etc.)
*/
async validateEntityWithRules(entity: any): Promise<ValidationResult> {
// First run basic validation (required properties)
const basicValidation = this.validateEntity(entity);
// Then run validation rules
const ruleValidation = await this.validationEngine.validate(entity);
// Combine results
const allErrors = [
...basicValidation.errors.map(error => ({
property: 'unknown',
value: undefined,
message: error,
rule: 'required',
target: entity.constructor.name,
})),
...ruleValidation.errors,
];
return {
isValid: basicValidation.valid && ruleValidation.isValid,
errors: allErrors,
};
}
/**
* Validates a specific property of an entity
*/
async validateProperty(entity: any, propertyKey: string): Promise<ValidationResult> {
return await this.validationEngine.validateProperty(entity, propertyKey);
}
/**
* 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
}
}
|