# All files / framework/models/decorators Field.ts

0% Statements 0/43
0% Branches 0/44
0% Functions 0/7
0% Lines 0/43

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

     import { FieldConfig, ValidationError } from '../../types/models';

    export function Field(config: FieldConfig) {
      return function (target: any, propertyKey: string) {
        // Initialize fields map if it doesn't exist
        if (!target.constructor.fields) {
          target.constructor.fields = new Map();
        }

        // Store field configuration
        target.constructor.fields.set(propertyKey, config);

        // Create getter/setter with validation and transformation
        const privateKey = `_${propertyKey}`;

        // Store the current descriptor (if any) - for future use
        const _currentDescriptor = Object.getOwnPropertyDescriptor(target, propertyKey);

        Object.defineProperty(target, propertyKey, {
          get() {
            return this[privateKey];
          },
          set(value) {
            // Apply transformation first
            const transformedValue = config.transform ? config.transform(value) : value;

            // Validate the field value
            const validationResult = validateFieldValue(transformedValue, config, propertyKey);
            if (!validationResult.valid) {
              throw new ValidationError(validationResult.errors);
            }

            // Set the value and mark as dirty
            this[privateKey] = transformedValue;
            if (this._isDirty !== undefined) {
              this._isDirty = true;
            }
          },
          enumerable: true,
          configurable: true,
        });

        // Set default value if provided
        if (config.default !== undefined) {
          Object.defineProperty(target, privateKey, {
            value: config.default,
            writable: true,
            enumerable: false,
            configurable: true,
          });
        }
      };
    }

    function validateFieldValue(
      value: any,
      config: FieldConfig,
      fieldName: string,
    ): { valid: boolean; errors: string[] } {
      const errors: string[] = [];

      // Required validation
      if (config.required && (value === undefined || value === null || value === '')) {
        errors.push(`${fieldName} is required`);
        return { valid: false, errors };
      }

      // Skip further validation if value is empty and not required
      if (value === undefined || value === null) {
        return { valid: true, errors: [] };
      }

      // Type validation
      if (!isValidType(value, config.type)) {
        errors.push(`${fieldName} must be of type ${config.type}`);
      }

      // Custom validation
      if (config.validate) {
        const customResult = config.validate(value);
        if (customResult === false) {
          errors.push(`${fieldName} failed custom validation`);
        } else if (typeof customResult === 'string') {
          errors.push(customResult);
        }
      }

      return { valid: errors.length === 0, errors };
    }

    function isValidType(value: any, expectedType: FieldConfig['type']): boolean {
      switch (expectedType) {
        case 'string':
          return typeof value === 'string';
        case 'number':
          return typeof value === 'number' && !isNaN(value);
        case 'boolean':
          return typeof value === 'boolean';
        case 'array':
          return Array.isArray(value);
        case 'object':
          return typeof value === 'object' && !Array.isArray(value);
        case 'date':
          return value instanceof Date || (typeof value === 'number' && !isNaN(value));
        default:
          return true;
      }
    }

    // Utility function to get field configuration
    export function getFieldConfig(target: any, propertyKey: string): FieldConfig | undefined {
      if (!target.constructor.fields) {
        return undefined;
      }
      return target.constructor.fields.get(propertyKey);
    }

    // Export the decorator type for TypeScript
    export type FieldDecorator = (config: FieldConfig) => (target: any, propertyKey: string) => void;