UNPKG

perfect-validator

Version:

A TypeScript-based validation library that supports both static and dynamic validation with serializable models.

1,511 lines (1,504 loc) 72.1 kB
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } function _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); } function _regeneratorRuntime() { _regeneratorRuntime = function () { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function (t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function (t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(typeof e + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function (e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function () { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function (e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function (t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function (t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function (t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function (t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function (e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } var PerfectValidator; (function (PerfectValidator) { PerfectValidator.ValidationTypes = { STRING: 'S', NUMBER: 'N', BOOLEAN: 'B', LIST: 'L', MAP: 'M', EMAIL: 'EMAIL', URL: 'URL', DATE: 'DATE', PHONE: 'PHONE', REGEX: 'REGEX' }; PerfectValidator.DataTypeDescriptions = { S: 'String type - Text values with optional length and pattern constraints', N: 'Number type - Numeric values with optional range constraints', B: 'Boolean type - True/false values', L: 'List type - Array of values with type validation', M: 'Map type - Object with defined field structure', EMAIL: 'Email type - Valid email address format', URL: 'URL type - Valid URL format', DATE: 'Date type - Valid date format', PHONE: 'Phone type - Valid phone number format', REGEX: 'Regex type - Custom pattern matching' }; })(PerfectValidator || (PerfectValidator = {})); // Type guards function isValidationError(response) { return response !== null && typeof response === 'object' && 'isValid' in response && !response.isValid; } // Constants var VALID_TYPES = ['S', 'N', 'B', 'L', 'M', 'EMAIL', 'URL', 'DATE', 'PHONE', 'REGEX']; // Regex patterns var PATTERNS = { EMAIL: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z]{2,})+$/, URL: /^(https?:\/\/)([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(:[0-9]{1,5})?(\/[^\s]*)?$/, PHONE: /^\+?[1-9]\d{0,2}[-.\s]?\(?\d{2,4}\)?[-.\s]?\d{3,4}[-.\s]?\d{3,4}$/, DATE: /^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])(?:T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-]([01]\d|2[0-3]):[0-5]\d)?)?$/ }; // Helper functions function getNestedValue(obj, path) { // Split path into parts, handling array notation var parts = path.split(/\.|\[|\]/).filter(Boolean); var current = obj; for (var _iterator = _createForOfIteratorHelperLoose(parts), _step; !(_step = _iterator()).done;) { var part = _step.value; if (!current) return undefined; // If it's a number, treat as array index if (/^\d+$/.test(part)) { current = current[parseInt(part, 10)]; } else { current = current[part]; } } return current; } function parseArrayType(type) { if (type.startsWith('L<') && type.endsWith('>')) { return { isArray: true, elementType: type.slice(2, -1) }; } return { isArray: false, elementType: type }; } function isValidationRule(rule) { return typeof rule === 'object' && rule !== null; } function applyDefaults(data, model) { var result = _extends({}, data); function getDefaultValue(rule) { // Only apply defaults if explicitly specified if (rule["default"] !== undefined) { return typeof rule["default"] === 'function' ? rule["default"]() : JSON.parse(JSON.stringify(rule["default"])); } // Don't apply type-based defaults automatically return undefined; } Object.entries(model).forEach(function (_ref) { var key = _ref[0], rule = _ref[1]; if (result[key] === undefined && isValidationRule(rule) && !rule.optional) { result[key] = getDefaultValue(rule); } }); return result; } function validateType(value, type, rule) { switch (type) { case 'S': return typeof value === 'string'; case 'N': if (typeof value !== 'number' || isNaN(value)) return false; return true; case 'B': return typeof value === 'boolean'; case 'L': return Array.isArray(value); case 'M': return typeof value === 'object' && value !== null && !Array.isArray(value); case 'EMAIL': return typeof value === 'string' && PATTERNS.EMAIL.test(value); case 'URL': return typeof value === 'string' && PATTERNS.URL.test(value); case 'DATE': return typeof value === 'string' && PATTERNS.DATE.test(value); case 'PHONE': return typeof value === 'string' && PATTERNS.PHONE.test(value); case 'REGEX': return typeof value === 'string' && (rule != null && rule.pattern ? new RegExp(rule.pattern).test(value) : false); default: return false; } } function getTypeDescription(rule) { if (typeof rule === 'string') return rule; var type = rule.type; if (!type) return 'unknown'; switch (type) { case 'S': return "string" + (rule.minLength ? " (min length: " + rule.minLength + ")" : '') + (rule.maxLength ? " (max length: " + rule.maxLength + ")" : ''); case 'N': return "number" + (rule.min !== undefined ? " (min: " + rule.min + ")" : '') + (rule.max !== undefined ? " (max: " + rule.max + ")" : '') + (rule.decimal ? ' (decimal)' : ''); case 'EMAIL': return 'email address'; case 'URL': return 'URL'; case 'DATE': return 'date (YYYY-MM-DD)'; case 'PHONE': return 'phone number'; case 'B': return 'boolean'; case 'L': return "array" + (rule.items ? " of " + getTypeDescription(rule.items) : ''); case 'REGEX': return "string matching pattern: " + rule.pattern; default: return type; } } function validateDataModel(model) { var errors = []; function validateField(field, path) { // Handle simple string type if (typeof field === 'string') { if (!VALID_TYPES.includes(field)) { errors.push("Invalid type \"" + field + "\" at " + path); } return; } // Must be an object if not a string if (typeof field !== 'object' || field === null) { errors.push("Invalid field definition at " + path); return; } var typedField = field; // Check for valid type if specified if (typedField.type && !VALID_TYPES.includes(typedField.type)) { errors.push("Invalid type \"" + typedField.type + "\" at " + path); } // Validate values array if present if (typedField.values !== undefined) { if (!Array.isArray(typedField.values) || typedField.values.length === 0) { errors.push("Invalid values at " + path); } } // Validate min/max for numbers if (typedField.type === 'N') { if (typedField.min !== undefined && typeof typedField.min !== 'number') { errors.push("Invalid min value at " + path); } if (typedField.max !== undefined && typeof typedField.max !== 'number') { errors.push("Invalid max value at " + path); } if (typedField.min !== undefined && typedField.max !== undefined && typedField.min > typedField.max) { errors.push("Invalid range at " + path); } if (typedField.integer !== undefined && typeof typedField.integer !== 'boolean') { errors.push("Invalid integer flag at " + path); } } // Validate dependencies if (typedField.dependsOn) { var deps = Array.isArray(typedField.dependsOn) ? typedField.dependsOn : [typedField.dependsOn]; deps.forEach(function (dep, index) { var depPath = path + ".dependsOn[" + index + "]"; if (typeof dep !== 'object' || dep === null) { errors.push("Invalid dependency definition at " + depPath); return; } if (!dep.field) { errors.push("Missing field in dependency at " + depPath); } if (!dep.condition || typeof dep.condition !== 'function') { errors.push("Missing or invalid condition function at " + depPath); } if (!dep.validate || typeof dep.validate !== 'function') { errors.push("Missing or invalid validate function at " + depPath); } }); } // Validate nested fields if (typedField.fields) { if (typeof typedField.fields !== 'object' || typedField.fields === null) { errors.push("Invalid fields definition at " + path); return; } Object.entries(typedField.fields).forEach(function (_ref2) { var key = _ref2[0], value = _ref2[1]; validateField(value, path ? path + "." + key : key); }); } // Validate array items if (typedField.items) { validateField(typedField.items, path + ".items"); } } // Validate each field in the model Object.entries(model).forEach(function (_ref3) { var key = _ref3[0], field = _ref3[1]; validateField(field, key); }); return { isValid: errors.length === 0, errors: errors.length > 0 ? errors : null }; } function validateAgainstModel(data, model, allowUnknownFields, parentPath) { if (allowUnknownFields === void 0) { allowUnknownFields = false; } if (parentPath === void 0) { parentPath = ''; } var errors = []; var dataWithDefaults = applyDefaults(data, model); if (typeof dataWithDefaults === 'object' && dataWithDefaults !== null && !allowUnknownFields) { var modelKeys = Object.keys(model); var dataKeys = Object.keys(dataWithDefaults); dataKeys.forEach(function (dataKey) { // Check if a key from the data exists in the model definition if (!modelKeys.includes(dataKey)) { var fieldPath = parentPath ? parentPath + "." + dataKey : dataKey; errors.push({ field: fieldPath, message: "Unexpected field '" + dataKey + "' found in data." }); } }); } // **End of New Check** // If extraneous fields were found, return early if (errors.length > 0) { return { isValid: false, errors: errors }; } function validateValue(value, rule, path) { // Handle undefined for optional fields if (value === undefined) { var isRequiredByDependency = false; // Check if there's a dependency that might make this required if (typeof rule === 'object' && rule.dependsOn) { var deps = Array.isArray(rule.dependsOn) ? rule.dependsOn : [rule.dependsOn]; for (var _iterator2 = _createForOfIteratorHelperLoose(deps), _step2; !(_step2 = _iterator2()).done;) { var dep = _step2.value; var depPath = dep.field.includes('.') ? dep.field : parentPath ? parentPath + "." + dep.field : dep.field; var depValue = getNestedValue(dataWithDefaults, depPath); if (dep.condition(depValue)) { // Check if the dependency explicitly marks this as required if (dep.isRequired) { isRequiredByDependency = true; errors.push({ field: path, message: dep.message || 'Field is required based on dependencies' }); return; } } } } // Only skip validation if field is optional AND not required by dependencies if (typeof rule === 'object' && rule.optional && !isRequiredByDependency) { return; } // If we get here, the field is required if (!isRequiredByDependency) { errors.push({ field: path, message: 'Field is required' }); } return; } var typeIndicator = typeof rule === 'string' ? rule : rule.type; if (!typeIndicator) return; // Parse array type notation var _parseArrayType = parseArrayType(typeIndicator), isArray = _parseArrayType.isArray, elementType = _parseArrayType.elementType; if (isArray) { if (!Array.isArray(value)) { errors.push({ field: path, message: "Expected array of " + elementType + ", got " + typeof value }); return; } value.forEach(function (item, index) { validateValue(item, elementType, path + "[" + index + "]"); }); return; } // Type validation if (!validateType(value, typeIndicator, typeof rule === 'object' ? rule : undefined)) { errors.push({ field: path, message: "Invalid type, expected " + getTypeDescription(rule) + ", got " + typeof value }); return; } if (typeof rule === 'string') return; // Validate constraints if (rule.values && !rule.values.includes(value)) { errors.push({ field: path, message: "Value must be one of: " + rule.values.join(', ') }); } // Number constraints if (rule.type === 'N') { if (rule.min !== undefined && value < rule.min) { errors.push({ field: path, message: "Value must be >= " + rule.min }); } if (rule.max !== undefined && value > rule.max) { errors.push({ field: path, message: "Value must be <= " + rule.max }); } if (rule.integer && !Number.isInteger(value)) { errors.push({ field: path, message: 'Value must be an integer' }); } // Add specific decimal validation error messages if (rule.decimal) { var hasDecimal = value.toString().includes('.'); var isInteger = Number.isInteger(value); if (!hasDecimal && !isInteger) { errors.push({ field: path, message: 'Value must be a decimal number' }); } else if (rule.decimals !== undefined) { var decimalPlaces = (value.toString().split('.')[1] || '').length; if (decimalPlaces !== rule.decimals) { errors.push({ field: path, message: "Value must have exactly " + rule.decimals + " decimal places" }); } } } } // String constraints if (rule.type === 'S') { if (rule.minLength !== undefined && value.length < rule.minLength) { errors.push({ field: path, message: "String length must be >= " + rule.minLength }); } if (rule.maxLength !== undefined && value.length > rule.maxLength) { errors.push({ field: path, message: "String length must be <= " + rule.maxLength }); } } // Custom validation function (standalone validate property) if (typeof rule === 'object' && rule.validate && typeof rule.validate === 'function') { try { var isValid = rule.validate(value); if (!isValid) { errors.push({ field: path, message: rule.message || "Custom validation failed for field " + path }); } } catch (error) { errors.push({ field: path, message: rule.message || "Custom validation error for field " + path + ": " + (error instanceof Error ? error.message : 'Unknown error') }); } } // Handle array validation if (rule.items && Array.isArray(value)) { value.forEach(function (item, index) { validateValue(item, rule.items, path + "[" + index + "]"); }); } // Handle object validation if (rule.fields && typeof value === 'object') { Object.entries(rule.fields).forEach(function (_ref4) { var key = _ref4[0], fieldRule = _ref4[1]; validateValue(value[key], fieldRule, path ? path + "." + key : key); }); } // Dependencies validation if (typeof rule === 'object' && rule.dependsOn) { var _deps = Array.isArray(rule.dependsOn) ? rule.dependsOn : [rule.dependsOn]; _deps.forEach(function (dep, index) { // Get the parent path var parentPath = path.substring(0, path.lastIndexOf('.')); // For cross-object dependencies, use the absolute path directly // If dep.field contains a dot, it's a cross-object reference var depPath = dep.field.includes('.') ? dep.field // Use absolute path as is : parentPath // For same-object references, prepend parent path ? parentPath + "." + dep.field : dep.field; var depValue = getNestedValue(dataWithDefaults, depPath); var conditionResult = dep.condition(depValue); if (conditionResult) { var _isValid = dep.validate(value, depValue, dataWithDefaults); if (!_isValid) { errors.push({ field: path, message: dep.message }); } } }); } } // Validate each field in the model Object.entries(model).forEach(function (_ref5) { var key = _ref5[0], rule = _ref5[1]; validateValue(key.includes('.') || key.includes('[]') ? getNestedValue(dataWithDefaults, key) : dataWithDefaults[key], rule, parentPath ? parentPath + "." + key : key); }); return errors.length > 0 ? { isValid: false, errors: errors } : { isValid: true, data: dataWithDefaults }; } var ValidationTypeParams = { S: { type: 'String', description: 'Text values with optional length and pattern constraints', params: [{ name: 'minLength', type: 'number', description: 'Minimum length of the string' }, { name: 'maxLength', type: 'number', description: 'Maximum length of the string' }, { name: 'values', type: 'string[]', description: 'Array of allowed values' }, { name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'default', type: 'string', description: 'Default value if field is not provided' }] }, N: { type: 'Number', description: 'Numeric values with optional range constraints', params: [{ name: 'min', type: 'number', description: 'Minimum allowed value' }, { name: 'max', type: 'number', description: 'Maximum allowed value' }, { name: 'integer', type: 'boolean', description: 'Whether the number must be an integer' }, { name: 'decimal', type: 'boolean', description: 'can be decimal or an integer' }, { name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'values', type: 'number[]', description: 'Array of allowed values' }, { name: 'default', type: 'number', description: 'Default value if field is not provided' }] }, B: { type: 'Boolean', description: 'True/false values', params: [{ name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'default', type: 'boolean', description: 'Default value if field is not provided' }] }, L: { type: 'Array', description: 'Array of values with type validation', params: [{ name: 'items', type: 'ValidationRule | string', description: 'Validation rule for array items', required: true }, { name: 'minLength', type: 'number', description: 'Minimum array length' }, { name: 'maxLength', type: 'number', description: 'Maximum array length' }, { name: 'values', type: 'any[]', description: 'Array of allowed values' }, { name: 'optional', type: 'boolean', description: 'Whether the field is optional' }] }, M: { type: 'Map', description: 'Object with defined field structure', params: [{ name: 'fields', type: 'Record<string, ValidationRule | string>', description: 'Validation rules for object fields', required: true }, { name: 'optional', type: 'boolean', description: 'Whether the field is optional' }] }, EMAIL: { type: 'Email', description: 'Valid email address format', params: [{ name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'default', type: 'string', description: 'Default value if field is not provided' }] }, URL: { type: 'URL', description: 'Valid URL format', params: [{ name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'default', type: 'string', description: 'Default value if field is not provided' }] }, DATE: { type: 'Date', description: 'Valid date format', params: [{ name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'default', type: 'string', description: 'Default value if field is not provided' }] }, PHONE: { type: 'Phone', description: 'Valid phone number format', params: [{ name: 'optional', type: 'boolean', description: 'Whether the field is optional' }, { name: 'default', type: 'string', description: 'Default value if field is not provided' }] }, REGEX: { type: 'Regex', description: 'Custom pattern matching', params: [{ name: 'pattern', type: 'string', description: 'Regular expression pattern', required: true }, { name: 'optional', type: 'boolean', description: 'Whether the field is optional' }] } }; var deserializeFunction = function deserializeFunction(fnStr) { try { var functionString = typeof fnStr === 'object' ? fnStr.original || fnStr.code : fnStr; // Normalize whitespace and remove 'function' keyword if present var normalizedStr = functionString.trim().replace(/^function\s*[a-zA-Z0-9_]*\s*/, ''); normalizedStr = normalizedStr.replace(/function anonymous/, '').replace(/\s+/g, ' ').trim(); if (normalizedStr.includes('=>')) { // Multi-line arrow with block var blockArrowMatch = normalizedStr.match(/^\s*(?:\((.*?)\)|([^=>\s]+))\s*=>\s*\{([\s\S]*)\}\s*$/); if (blockArrowMatch) { var paramsWithParens = blockArrowMatch[1], singleParam = blockArrowMatch[2], body = blockArrowMatch[3]; var params = paramsWithParens || singleParam; return _construct(Function, params.split(',').map(function (p) { return p.trim(); }).concat([body.trim()])); } // Single-line arrow with expression var simpleArrowMatch = normalizedStr.match(/^\s*(?:\((.*?)\)|([^=>\s]+))\s*=>\s*(.+?)\s*$/); if (simpleArrowMatch) { var _paramsWithParens = simpleArrowMatch[1], _singleParam = simpleArrowMatch[2], expression = simpleArrowMatch[3]; var _params = _paramsWithParens || _singleParam; // Handle potential multi-line expressions without braces var cleanExpression = expression.includes('\n') ? expression.split('\n').map(function (line) { return line.trim(); }).join(' ') : expression.trim(); return _construct(Function, _params.split(',').map(function (p) { return p.trim(); }).concat(["return " + cleanExpression + ";"])); } } // Regular function (now without 'function' keyword) var regularFnMatch = normalizedStr.match(/^\s*\((.*?)\)\s*\{([\s\S]*)\}\s*$/); if (regularFnMatch) { var _params2 = regularFnMatch[1], _body = regularFnMatch[2]; return _construct(Function, _params2.split(',').map(function (p) { return p.trim(); }).concat([_body.trim()])); } throw new Error('Invalid function format'); } catch (error) { if (error instanceof Error) { throw new Error("Failed to deserialize function: " + error.message); } throw new Error('Failed to deserialize function: Unknown error'); } }; // Serialize validation model to string var serializeValidationModel = function serializeValidationModel(model) { function serializeObject(obj) { // Handle functions if (typeof obj === 'function') { return serializeFunction(obj); } // Handle arrays if (Array.isArray(obj)) { return obj.map(function (item) { return serializeObject(item); }); } // Handle objects if (obj && typeof obj === 'object') { var result = {}; for (var _i = 0, _Object$entries = Object.entries(obj); _i < _Object$entries.length; _i++) { var _Object$entries$_i = _Object$entries[_i], key = _Object$entries$_i[0], value = _Object$entries$_i[1]; result[key] = serializeObject(value); } return result; } // Handle primitive values return obj; } try { return JSON.stringify(serializeObject(model)); } catch (error) { if (error instanceof Error) { throw new Error("Failed to serialize model: " + error.message); } throw new Error('Failed to serialize model: Unknown error'); } }; function serializeFunction(fn) { return { __type: 'function', code: fn.toString().replace(/\n/g, ' ').trim(), original: fn.toString().replace(/\n/g, ' ').trim() }; } var deserializeValidationModel = function deserializeValidationModel(serializedModel) { function deserializeObject(obj) { if (obj && obj.__type === 'function' && obj.code) { try { var fn = deserializeFunction(obj.original || obj.code); return fn; } catch (error) { throw error; } } // Handle arrays if (Array.isArray(obj)) { return obj.map(function (item) { return deserializeObject(item); }); } // Handle objects with dependsOn if (obj && typeof obj === 'object') { var result = {}; var _loop = function _loop() { var _Object$entries2$_i = _Object$entries2[_i2], key = _Object$entries2$_i[0], value = _Object$entries2$_i[1]; if (key === 'dependsOn') { // Type guard for dependency object var isDependencyObject = function isDependencyObject(v) { return typeof v === 'object' && v !== null && 'field' in v; }; // Handle both single dependency and array of dependencies if (Array.isArray(value)) { result[key] = value.map(function (dep) { if (!isDependencyObject(dep)) { throw new Error('Invalid dependency object structure'); } return { field: dep.field, message: dep.message, condition: dep.condition ? deserializeObject(dep.condition) : undefined, validate: dep.validate ? deserializeObject(dep.validate) : undefined }; }); } else if (isDependencyObject(value)) { result[key] = { field: value.field, message: value.message, condition: value.condition ? deserializeObject(value.condition) : undefined, validate: value.validate ? deserializeObject(value.validate) : undefined }; } else { throw new Error('Invalid dependency structure'); } } else { result[key] = deserializeObject(value); } }; for (var _i2 = 0, _Object$entries2 = Object.entries(obj); _i2 < _Object$entries2.length; _i2++) { _loop(); } return result; } return obj; } try { var parsed = JSON.parse(serializedModel); var deserialized = deserializeObject(parsed); // Validate the deserialized model var validation = validateDataModel(deserialized); if (!validation.isValid) { var _validation$errors; throw new Error("Invalid model after deserialization: " + ((_validation$errors = validation.errors) == null ? void 0 : _validation$errors.join(', '))); } return deserialized; } catch (error) { throw error; } }; function getValidationTypeParams(type) { var params = ValidationTypeParams[type]; if (!params || params === undefined || params === null) { throw new Error("Invalid validation type: " + type); } return params; } var userProfileModel = { user: { type: 'M', fields: { name: { type: 'S', minLength: 2 }, age: { type: 'N', min: 13 }, email: { type: 'EMAIL' }, subscription: { type: 'M', fields: { plan: { type: 'S', values: ['FREE', 'BASIC', 'PREMIUM'], dependsOn: { field: 'user.age', condition: function condition(age) { return age < 18; }, validate: function validate(plan) { return plan !== 'PREMIUM'; }, message: 'Users under 18 cannot have PREMIUM plan' } }, features: { type: 'L', items: { type: 'S' }, dependsOn: { field: 'user.subscription.plan', condition: function condition(plan) { return plan === 'FREE'; }, validate: function validate(features) { return features.length <= 3; }, message: 'FREE plan can only have up to 3 features' } }, // Payment method with multiple dependencies paymentMethod: { type: 'S', values: ['CREDIT_CARD', 'DEBIT_CARD', 'PAYPAL'], dependsOn: [{ field: 'user.age', condition: function condition(age) { return age < 18; }, validate: function validate(method) { return method === 'DEBIT_CARD'; }, message: 'Users under 18 can only use debit cards' }, { field: 'user.subscription.plan', condition: function condition(plan) { return plan === 'PREMIUM'; }, validate: function validate(method) { return method === 'CREDIT_CARD'; }, message: 'PREMIUM plan requires credit card payment' }] } } } } } }; var testCases = [{ name: 'Valid adult premium user', data: { user: { name: 'John Doe', age: 25, email: 'john@example.com', subscription: { plan: 'PREMIUM', features: ['feature1', 'feature2', 'feature3', 'feature4'], paymentMethod: 'CREDIT_CARD' } } } }, { name: 'Valid teen user with free plan', data: { user: { name: 'Teen User', age: 15, email: 'teen@example.com', subscription: { plan: 'FREE', features: ['feature1', 'feature2'], paymentMethod: 'DEBIT_CARD' } } } }, { name: 'Invalid - Teen trying premium plan', data: { user: { name: 'Teen Premium', age: 16, email: 'teen.premium@example.com', subscription: { plan: 'PREMIUM', features: ['feature1', 'feature2'], paymentMethod: 'DEBIT_CARD' } } } }, { name: 'Invalid - Free plan with too many features', data: { user: { name: 'Free User', age: 20, email: 'free@example.com', subscription: { plan: 'FREE', features: ['feature1', 'feature2', 'feature3', 'feature4'], paymentMethod: 'PAYPAL' } } } }]; var PV = /*#__PURE__*/function () { function PV(storage) { this.storage = storage; } /** * Static validation with direct model and data */ var _proto = PV.prototype; _proto.validateStatic = function validateStatic(data, model, allowUnknownFields) { var modelValidation = this.validateModel(model); if (!modelValidation.isValid && modelValidation.errors) { return { isValid: false, errors: modelValidation.errors.map(function (error) { return { field: 'model', message: error }; }) }; } return validateAgainstModel(data, model, allowUnknownFields); }; PV.getInstance = function getInstance(storage) { if (!PV.instance) { try { PV.instance = new PV(storage); } catch (error) { if (error instanceof Error) { throw new Error("Failed to create PV instance: " + error.message); } throw new Error('Failed to create PV instance: Unknown error'); } } return PV.instance; } /** * Dynamic validation using stored model with optional collection */; _proto.validateDynamic = /*#__PURE__*/ function () { var _validateDynamic = /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(data, modelName, version, collection, allowUnknownFields) { var serializedModel, modelVersion, latestVersion, model; return _regeneratorRuntime().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: if (this.storage) { _context.next = 2; break; } throw new Error('Storage is required for dynamic validation'); case 2: _context.prev = 2; if (!(version !== undefined)) { _context.next = 12; break; } _context.next = 6; return this.storage.getModelVersion(modelName, version, collection); case 6: modelVersion = _context.sent; if (modelVersion) { _context.next = 9; break; } throw new Error("Model " + modelName + " version " + version + " not found"); case 9: serializedModel = modelVersion.model; _context.next = 18; break; case 12: _context.next = 14; return this.storage.getLatestModelVersion(modelName, collection); case 14: latestVersion = _context.sent; if (latestVersion) { _context.next = 17; break; } throw new Error("Model " + modelName + " not found"); case 17: serializedModel = latestVersion.model; case 18: if (serializedModel) { _context.next = 20; break; } throw new Error("Model " + modelName + " not found"); case 20: model = deserializeValidationModel(serializedModel); return _context.abrupt("return", validateAgainstModel(data, model, allowUnknownFields)); case 24: _context.prev = 24; _context.t0 = _context["catch"](2); if (!(_context.t0 instanceof Error)) { _context.next = 28; break; } return _context.abrupt("return", { isValid: false, errors: [{ field: 'model', message: "Failed to load model: " + _context.t0.message }] }); case 28: return _context.abrupt("return", { isValid: false, errors: [{ field: 'model', message: 'Failed to load model: Unknown error' }] }); case 29: case "end": return _context.stop(); } }, _callee, this, [[2, 24]]); })); function validateDynamic(_x, _x2, _x3, _x4, _x5) { return _validateDynamic.apply(this, arguments); } return validateDynamic; }() /** * Store model with validation and optional collection */ ; _proto.storeModel = /*#__PURE__*/ function () { var _storeModel = /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(modelName, model, version, collection) { var modelValidation, _modelValidation$erro, serialized, latestVersion, newVersion; return _regeneratorRuntime().wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: _context2.prev = 0; if (this.storage) { _context2.next = 3; break; } throw new Error('Storage is required for model storage'); case 3: // Validate and store model with optional collection modelValidation = this.validateModel(model); if (modelValidation.isValid) { _context2.next = 6; break; } throw new Error("Model validation failed: " + ((_modelValidation$erro = modelValidation.errors) == null ? void 0 : _modelValidation$erro.join(', '))); case 6: _context2.next = 8; return this.serializeModelSafely(model); case 8: serialized = _context2.sent; _context2.next = 11; return this.deserializeAndValidate(serialized); case 11: if (!(version !== undefined)) { _context2.next = 17; break; } _context2.next = 15; return this.storage.storeModelVersion(modelName, serialized, version, collection); case 15: _context2.next = 23; break;