UNPKG

veffect

Version:

powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha

457 lines (455 loc) 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sequence = exports.parse = exports.next = exports.match = exports.make = exports.isParseError = exports.isCron = exports.equals = exports.TypeId = exports.ParseErrorTypeId = exports.Equivalence = void 0; var Either = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./Either.js")); var Equal = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./Equal.js")); var equivalence = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./Equivalence.js")); var _Function = /*#__PURE__*/require("./Function.js"); var Hash = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./Hash.js")); var _Inspectable = /*#__PURE__*/require("./Inspectable.js"); var N = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./Number.js")); var _Pipeable = /*#__PURE__*/require("./Pipeable.js"); var _Predicate = /*#__PURE__*/require("./Predicate.js"); var ReadonlyArray = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./ReadonlyArray.js")); var String = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("./String.js")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * @since 2.0.0 */ /** * @since 2.0.0 * @category symbols */ const TypeId = exports.TypeId = /*#__PURE__*/Symbol.for("effect/Cron"); const CronProto = { [TypeId]: TypeId, [Equal.symbol](that) { return isCron(that) && equals(this, that); }, [Hash.symbol]() { return (0, _Function.pipe)(Hash.array(ReadonlyArray.fromIterable(this.minutes)), Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.hours))), Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.days))), Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.months))), Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.weekdays))), Hash.cached(this)); }, toString() { return (0, _Inspectable.format)(this.toJSON()); }, toJSON() { return { _id: "Cron", minutes: ReadonlyArray.fromIterable(this.minutes), hours: ReadonlyArray.fromIterable(this.hours), days: ReadonlyArray.fromIterable(this.days), months: ReadonlyArray.fromIterable(this.months), weekdays: ReadonlyArray.fromIterable(this.weekdays) }; }, [_Inspectable.NodeInspectSymbol]() { return this.toJSON(); }, pipe() { return (0, _Pipeable.pipeArguments)(this, arguments); } }; /** * Checks if a given value is a `Cron` instance. * * @param u - The value to check. * * @since 2.0.0 * @category guards */ const isCron = u => (0, _Predicate.hasProperty)(u, TypeId); /** * Creates a `Cron` instance from. * * @param constraints - The cron constraints. * * @since 2.0.0 * @category constructors */ exports.isCron = isCron; const make = ({ days, hours, minutes, months, weekdays }) => { const o = Object.create(CronProto); o.minutes = new Set(ReadonlyArray.sort(minutes, N.Order)); o.hours = new Set(ReadonlyArray.sort(hours, N.Order)); o.days = new Set(ReadonlyArray.sort(days, N.Order)); o.months = new Set(ReadonlyArray.sort(months, N.Order)); o.weekdays = new Set(ReadonlyArray.sort(weekdays, N.Order)); return o; }; /** * @since 2.0.0 * @category symbol */ exports.make = make; const ParseErrorTypeId = exports.ParseErrorTypeId = /*#__PURE__*/Symbol.for("effect/Cron/errors/ParseError"); const ParseErrorProto = { _tag: "ParseError", [ParseErrorTypeId]: ParseErrorTypeId }; const ParseError = (message, input) => { const o = Object.create(ParseErrorProto); o.message = message; if (input !== undefined) { o.input = input; } return o; }; /** * Returns `true` if the specified value is an `ParseError`, `false` otherwise. * * @param u - The value to check. * * @since 2.0.0 * @category guards */ const isParseError = u => (0, _Predicate.hasProperty)(u, ParseErrorTypeId); /** * Parses a cron expression into a `Cron` instance. * * @param cron - The cron expression to parse. * * @example * import * as Cron from "effect/Cron" * import * as Either from "effect/Either" * * // At 04:00 on every day-of-month from 8 through 14. * assert.deepStrictEqual(Cron.parse("0 4 8-14 * *"), Either.right(Cron.make({ * minutes: [0], * hours: [4], * days: [8, 9, 10, 11, 12, 13, 14], * months: [], * weekdays: [] * }))) * * @since 2.0.0 * @category constructors */ exports.isParseError = isParseError; const parse = cron => { const segments = cron.split(" ").filter(String.isNonEmpty); if (segments.length !== 5) { return Either.left(ParseError(`Invalid number of segments in cron expression`, cron)); } const [minutes, hours, days, months, weekdays] = segments; return Either.all({ minutes: parseSegment(minutes, minuteOptions), hours: parseSegment(hours, hourOptions), days: parseSegment(days, dayOptions), months: parseSegment(months, monthOptions), weekdays: parseSegment(weekdays, weekdayOptions) }).pipe(Either.map(segments => make(segments))); }; /** * Checks if a given `Date` falls within an active `Cron` time window. * * @param cron - The `Cron` instance. * @param date - The `Date` to check against. * * @example * import * as Cron from "effect/Cron" * import * as Either from "effect/Either" * * const cron = Either.getOrThrow(Cron.parse("0 4 8-14 * *")) * assert.deepStrictEqual(Cron.match(cron, new Date("2021-01-08 04:00:00")), true) * assert.deepStrictEqual(Cron.match(cron, new Date("2021-01-08 05:00:00")), false) * * @since 2.0.0 */ exports.parse = parse; const match = (cron, date) => { const { days, hours, minutes, months, weekdays } = cron; const minute = date.getMinutes(); if (minutes.size !== 0 && !minutes.has(minute)) { return false; } const hour = date.getHours(); if (hours.size !== 0 && !hours.has(hour)) { return false; } const month = date.getMonth() + 1; if (months.size !== 0 && !months.has(month)) { return false; } if (days.size === 0 && weekdays.size === 0) { return true; } const day = date.getDate(); if (weekdays.size === 0) { return days.has(day); } const weekday = date.getDay(); if (days.size === 0) { return weekdays.has(weekday); } return days.has(day) || weekdays.has(weekday); }; /** * Returns the next run `Date` for the given `Cron` instance. * * Uses the current time as a starting point if no value is provided for `now`. * * @example * import * as Cron from "effect/Cron" * import * as Either from "effect/Either" * * const after = new Date("2021-01-01 00:00:00") * const cron = Either.getOrThrow(Cron.parse("0 4 8-14 * *")) * assert.deepStrictEqual(Cron.next(cron, after), new Date("2021-01-08 04:00:00")) * * @param cron - The `Cron` instance. * @param now - The `Date` to start searching from. * * @since 2.0.0 */ exports.match = match; const next = (cron, now) => { const { days, hours, minutes, months, weekdays } = cron; const restrictMinutes = minutes.size !== 0; const restrictHours = hours.size !== 0; const restrictDays = days.size !== 0; const restrictMonths = months.size !== 0; const restrictWeekdays = weekdays.size !== 0; const current = now ? new Date(now.getTime()) : new Date(); // Increment by one minute to ensure we don't match the current date. current.setMinutes(current.getMinutes() + 1); current.setSeconds(0); current.setMilliseconds(0); // Only search 8 years into the future. const limit = new Date(current).setFullYear(current.getFullYear() + 8); while (current.getTime() <= limit) { if (restrictMonths && !months.has(current.getMonth() + 1)) { current.setMonth(current.getMonth() + 1); current.setDate(1); current.setHours(0); current.setMinutes(0); continue; } if (restrictDays && restrictWeekdays) { if (!days.has(current.getDate()) && !weekdays.has(current.getDay())) { current.setDate(current.getDate() + 1); current.setHours(0); current.setMinutes(0); continue; } } else if (restrictDays) { if (!days.has(current.getDate())) { current.setDate(current.getDate() + 1); current.setHours(0); current.setMinutes(0); continue; } } else if (restrictWeekdays) { if (!weekdays.has(current.getDay())) { current.setDate(current.getDate() + 1); current.setHours(0); current.setMinutes(0); continue; } } if (restrictHours && !hours.has(current.getHours())) { current.setHours(current.getHours() + 1); current.setMinutes(0); continue; } if (restrictMinutes && !minutes.has(current.getMinutes())) { current.setMinutes(current.getMinutes() + 1); continue; } return current; } throw new Error("Unable to find next cron date"); }; /** * Returns an `IterableIterator` which yields the sequence of `Date`s that match the `Cron` instance. * * @param cron - The `Cron` instance. * @param now - The `Date` to start searching from. * * @since 2.0.0 */ exports.next = next; const sequence = function* (cron, now) { while (true) { yield now = next(cron, now); } }; /** * @category instances * @since 2.0.0 */ exports.sequence = sequence; const Equivalence = exports.Equivalence = /*#__PURE__*/equivalence.make((self, that) => restrictionsEquals(self.minutes, that.minutes) && restrictionsEquals(self.hours, that.hours) && restrictionsEquals(self.days, that.days) && restrictionsEquals(self.months, that.months) && restrictionsEquals(self.weekdays, that.weekdays)); const restrictionsArrayEquals = /*#__PURE__*/equivalence.array(equivalence.number); const restrictionsEquals = (self, that) => restrictionsArrayEquals(ReadonlyArray.fromIterable(self), ReadonlyArray.fromIterable(that)); /** * Checks if two `Cron`s are equal. * * @since 2.0.0 * @category predicates */ const equals = exports.equals = /*#__PURE__*/(0, _Function.dual)(2, (self, that) => Equivalence(self, that)); const minuteOptions = { segment: "minute", min: 0, max: 59 }; const hourOptions = { segment: "hour", min: 0, max: 23 }; const dayOptions = { segment: "day", min: 1, max: 31 }; const monthOptions = { segment: "month", min: 1, max: 12, aliases: { jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6, jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12 } }; const weekdayOptions = { segment: "weekday", min: 0, max: 6, aliases: { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 } }; const parseSegment = (input, options) => { const capacity = options.max - options.min + 1; const values = new Set(); const fields = input.split(","); for (const field of fields) { const [raw, step] = splitStep(field); if (raw === "*" && step === undefined) { return Either.right(new Set()); } if (step !== undefined) { if (!Number.isInteger(step)) { return Either.left(ParseError(`Expected step value to be a positive integer`, input)); } if (step < 1) { return Either.left(ParseError(`Expected step value to be greater than 0`, input)); } if (step > options.max) { return Either.left(ParseError(`Expected step value to be less than ${options.max}`, input)); } } if (raw === "*") { for (let i = options.min; i <= options.max; i += step ?? 1) { values.add(i); } } else { const [left, right] = splitRange(raw, options.aliases); if (!Number.isInteger(left)) { return Either.left(ParseError(`Expected a positive integer`, input)); } if (left < options.min || left > options.max) { return Either.left(ParseError(`Expected a value between ${options.min} and ${options.max}`, input)); } if (right === undefined) { values.add(left); } else { if (!Number.isInteger(right)) { return Either.left(ParseError(`Expected a positive integer`, input)); } if (right < options.min || right > options.max) { return Either.left(ParseError(`Expected a value between ${options.min} and ${options.max}`, input)); } if (left > right) { return Either.left(ParseError(`Invalid value range`, input)); } for (let i = left; i <= right; i += step ?? 1) { values.add(i); } } } if (values.size >= capacity) { return Either.right(new Set()); } } return Either.right(values); }; const splitStep = input => { const seperator = input.indexOf("/"); if (seperator !== -1) { return [input.slice(0, seperator), Number(input.slice(seperator + 1))]; } return [input, undefined]; }; const splitRange = (input, aliases) => { const seperator = input.indexOf("-"); if (seperator !== -1) { return [aliasOrValue(input.slice(0, seperator), aliases), aliasOrValue(input.slice(seperator + 1), aliases)]; } return [aliasOrValue(input, aliases), undefined]; }; function aliasOrValue(field, aliases) { return aliases?.[field.toLocaleLowerCase()] ?? Number(field); } //# sourceMappingURL=Cron.js.map