184 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			184 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
Object.defineProperty(exports, "__esModule", { value: true });
 | 
						|
exports.CronField = void 0;
 | 
						|
/**
 | 
						|
 * Represents a field within a cron expression.
 | 
						|
 * This is a base class and should not be instantiated directly.
 | 
						|
 * @class CronField
 | 
						|
 */
 | 
						|
class CronField {
 | 
						|
    #hasLastChar = false;
 | 
						|
    #hasQuestionMarkChar = false;
 | 
						|
    #wildcard = false;
 | 
						|
    #values = [];
 | 
						|
    options = { rawValue: '' };
 | 
						|
    /**
 | 
						|
     * Returns the minimum value allowed for this field.
 | 
						|
     */
 | 
						|
    /* istanbul ignore next */ static get min() {
 | 
						|
        /* istanbul ignore next */
 | 
						|
        throw new Error('min must be overridden');
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns the maximum value allowed for this field.
 | 
						|
     */
 | 
						|
    /* istanbul ignore next */ static get max() {
 | 
						|
        /* istanbul ignore next */
 | 
						|
        throw new Error('max must be overridden');
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns the allowed characters for this field.
 | 
						|
     */
 | 
						|
    /* istanbul ignore next */ static get chars() {
 | 
						|
        /* istanbul ignore next - this is overridden */
 | 
						|
        return Object.freeze([]);
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns the regular expression used to validate this field.
 | 
						|
     */
 | 
						|
    static get validChars() {
 | 
						|
        return /^[?,*\dH/-]+$|^.*H\(\d+-\d+\)\/\d+.*$|^.*H\(\d+-\d+\).*$|^.*H\/\d+.*$/;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns the constraints for this field.
 | 
						|
     */
 | 
						|
    static get constraints() {
 | 
						|
        return { min: this.min, max: this.max, chars: this.chars, validChars: this.validChars };
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * CronField constructor. Initializes the field with the provided values.
 | 
						|
     * @param {number[] | string[]} values - Values for this field
 | 
						|
     * @param {CronFieldOptions} [options] - Options provided by the parser
 | 
						|
     * @throws {TypeError} if the constructor is called directly
 | 
						|
     * @throws {Error} if validation fails
 | 
						|
     */
 | 
						|
    constructor(values, options = { rawValue: '' }) {
 | 
						|
        if (!Array.isArray(values)) {
 | 
						|
            throw new Error(`${this.constructor.name} Validation error, values is not an array`);
 | 
						|
        }
 | 
						|
        if (!(values.length > 0)) {
 | 
						|
            throw new Error(`${this.constructor.name} Validation error, values contains no values`);
 | 
						|
        }
 | 
						|
        /* istanbul ignore next */
 | 
						|
        this.options = {
 | 
						|
            ...options,
 | 
						|
            rawValue: options.rawValue ?? '',
 | 
						|
        };
 | 
						|
        this.#values = values.sort(CronField.sorter);
 | 
						|
        this.#wildcard = this.options.wildcard !== undefined ? this.options.wildcard : this.#isWildcardValue();
 | 
						|
        this.#hasLastChar = this.options.rawValue.includes('L') || values.includes('L');
 | 
						|
        this.#hasQuestionMarkChar = this.options.rawValue.includes('?') || values.includes('?');
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns the minimum value allowed for this field.
 | 
						|
     * @returns {number}
 | 
						|
     */
 | 
						|
    get min() {
 | 
						|
        // return the static value from the child class
 | 
						|
        return this.constructor.min;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns the maximum value allowed for this field.
 | 
						|
     * @returns {number}
 | 
						|
     */
 | 
						|
    get max() {
 | 
						|
        // return the static value from the child class
 | 
						|
        return this.constructor.max;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns an array of allowed special characters for this field.
 | 
						|
     * @returns {string[]}
 | 
						|
     */
 | 
						|
    get chars() {
 | 
						|
        // return the frozen static value from the child class
 | 
						|
        return this.constructor.chars;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Indicates whether this field has a "last" character.
 | 
						|
     * @returns {boolean}
 | 
						|
     */
 | 
						|
    get hasLastChar() {
 | 
						|
        return this.#hasLastChar;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Indicates whether this field has a "question mark" character.
 | 
						|
     * @returns {boolean}
 | 
						|
     */
 | 
						|
    get hasQuestionMarkChar() {
 | 
						|
        return this.#hasQuestionMarkChar;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Indicates whether this field is a wildcard.
 | 
						|
     * @returns {boolean}
 | 
						|
     */
 | 
						|
    get isWildcard() {
 | 
						|
        return this.#wildcard;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Returns an array of allowed values for this field.
 | 
						|
     * @returns {CronFieldType}
 | 
						|
     */
 | 
						|
    get values() {
 | 
						|
        return this.#values;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Helper function to sort values in ascending order.
 | 
						|
     * @param {number | string} a - First value to compare
 | 
						|
     * @param {number | string} b - Second value to compare
 | 
						|
     * @returns {number} - A negative, zero, or positive value, depending on the sort order
 | 
						|
     */
 | 
						|
    static sorter(a, b) {
 | 
						|
        const aIsNumber = typeof a === 'number';
 | 
						|
        const bIsNumber = typeof b === 'number';
 | 
						|
        if (aIsNumber && bIsNumber)
 | 
						|
            return a - b;
 | 
						|
        if (!aIsNumber && !bIsNumber)
 | 
						|
            return a.localeCompare(b);
 | 
						|
        return aIsNumber ? /* istanbul ignore next - A will always be a number until L-2 is supported */ -1 : 1;
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Serializes the field to an object.
 | 
						|
     * @returns {SerializedCronField}
 | 
						|
     */
 | 
						|
    serialize() {
 | 
						|
        return {
 | 
						|
            wildcard: this.#wildcard,
 | 
						|
            values: this.#values,
 | 
						|
        };
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Validates the field values against the allowed range and special characters.
 | 
						|
     * @throws {Error} if validation fails
 | 
						|
     */
 | 
						|
    validate() {
 | 
						|
        let badValue;
 | 
						|
        const charsString = this.chars.length > 0 ? ` or chars ${this.chars.join('')}` : '';
 | 
						|
        const charTest = (value) => (char) => new RegExp(`^\\d{0,2}${char}$`).test(value);
 | 
						|
        const rangeTest = (value) => {
 | 
						|
            badValue = value;
 | 
						|
            return typeof value === 'number' ? value >= this.min && value <= this.max : this.chars.some(charTest(value));
 | 
						|
        };
 | 
						|
        const isValidRange = this.#values.every(rangeTest);
 | 
						|
        if (!isValidRange) {
 | 
						|
            throw new Error(`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`);
 | 
						|
        }
 | 
						|
        // check for duplicate value in this.#values array
 | 
						|
        const duplicate = this.#values.find((value, index) => this.#values.indexOf(value) !== index);
 | 
						|
        if (duplicate) {
 | 
						|
            throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    /**
 | 
						|
     * Determines if the field is a wildcard based on the values.
 | 
						|
     * When options.rawValue is not empty, it checks if the raw value is a wildcard, otherwise it checks if all values in the range are included.
 | 
						|
     * @returns {boolean}
 | 
						|
     */
 | 
						|
    #isWildcardValue() {
 | 
						|
        if (this.options.rawValue.length > 0) {
 | 
						|
            return ['*', '?'].includes(this.options.rawValue);
 | 
						|
        }
 | 
						|
        return Array.from({ length: this.max - this.min + 1 }, (_, i) => i + this.min).every((value) => this.#values.includes(value));
 | 
						|
    }
 | 
						|
}
 | 
						|
exports.CronField = CronField;
 |