- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
3144 lines
83 KiB
JavaScript
3144 lines
83 KiB
JavaScript
import {
|
|
SimpleErrorReporter,
|
|
SimpleMessagesProvider,
|
|
ValidationError,
|
|
helpers
|
|
} from "./chunk-YXNUTVGP.js";
|
|
import {
|
|
fields,
|
|
messages
|
|
} from "./chunk-M2DOTJGC.js";
|
|
import {
|
|
__export
|
|
} from "./chunk-MLKGABMK.js";
|
|
|
|
// src/vine/create_rule.ts
|
|
function createRule(validator, metaData) {
|
|
const rule = {
|
|
validator,
|
|
isAsync: metaData?.isAsync || validator.constructor.name === "AsyncFunction",
|
|
implicit: metaData?.implicit ?? false
|
|
};
|
|
return function(...options) {
|
|
return {
|
|
rule,
|
|
options: options[0]
|
|
};
|
|
};
|
|
}
|
|
|
|
// src/schema/builder.ts
|
|
import Macroable3 from "@poppinss/macroable";
|
|
|
|
// src/schema/base/literal.ts
|
|
import camelcase from "camelcase";
|
|
import Macroable from "@poppinss/macroable";
|
|
|
|
// src/symbols.ts
|
|
var symbols_exports = {};
|
|
__export(symbols_exports, {
|
|
COTYPE: () => COTYPE,
|
|
IS_OF_TYPE: () => IS_OF_TYPE,
|
|
ITYPE: () => ITYPE,
|
|
OTYPE: () => OTYPE,
|
|
PARSE: () => PARSE,
|
|
SUBTYPE: () => SUBTYPE,
|
|
UNIQUE_NAME: () => UNIQUE_NAME,
|
|
VALIDATION: () => VALIDATION
|
|
});
|
|
var UNIQUE_NAME = Symbol.for("schema_name");
|
|
var IS_OF_TYPE = Symbol.for("is_of_type");
|
|
var PARSE = Symbol.for("parse");
|
|
var ITYPE = Symbol.for("opaque_input_type");
|
|
var OTYPE = Symbol.for("opaque_type");
|
|
var COTYPE = Symbol.for("camelcase_opaque_type");
|
|
var VALIDATION = Symbol.for("to_validation");
|
|
var SUBTYPE = Symbol.for("subtype");
|
|
|
|
// src/schema/base/rules.ts
|
|
var requiredWhen = createRule(
|
|
(_, checker, field) => {
|
|
const shouldBeRequired = checker(field);
|
|
if (!field.isDefined && shouldBeRequired) {
|
|
field.report(messages.required, "required", field);
|
|
}
|
|
},
|
|
{
|
|
implicit: true
|
|
}
|
|
);
|
|
|
|
// src/schema/base/literal.ts
|
|
var BaseModifiersType = class extends Macroable {
|
|
/**
|
|
* Mark the field under validation as optional. An optional
|
|
* field allows both null and undefined values.
|
|
*/
|
|
optional(validations) {
|
|
return new OptionalModifier(this, validations);
|
|
}
|
|
/**
|
|
* Mark the field under validation to be null. The null value will
|
|
* be written to the output as well.
|
|
*
|
|
* If `optional` and `nullable` are used together, then both undefined
|
|
* and null values will be allowed.
|
|
*/
|
|
nullable() {
|
|
return new NullableModifier(this);
|
|
}
|
|
/**
|
|
* Apply transform on the final validated value. The transform method may
|
|
* convert the value to any new datatype.
|
|
*/
|
|
transform(transformer) {
|
|
return new TransformModifier(transformer, this);
|
|
}
|
|
};
|
|
var NullableModifier = class _NullableModifier extends BaseModifiersType {
|
|
#parent;
|
|
constructor(parent) {
|
|
super();
|
|
this.#parent = parent;
|
|
}
|
|
/**
|
|
* Creates a fresh instance of the underlying schema type
|
|
* and wraps it inside the nullable modifier
|
|
*/
|
|
clone() {
|
|
return new _NullableModifier(this.#parent.clone());
|
|
}
|
|
/**
|
|
* Compiles to compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
const output = this.#parent[PARSE](propertyName, refs, options);
|
|
output.allowNull = true;
|
|
return output;
|
|
}
|
|
};
|
|
var OptionalModifier = class _OptionalModifier extends BaseModifiersType {
|
|
#parent;
|
|
/**
|
|
* Optional modifier validations list
|
|
*/
|
|
validations;
|
|
constructor(parent, validations) {
|
|
super();
|
|
this.#parent = parent;
|
|
this.validations = validations || [];
|
|
}
|
|
/**
|
|
* Shallow clones the validations. Since, there are no API's to mutate
|
|
* the validation options, we can safely copy them by reference.
|
|
*/
|
|
cloneValidations() {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
options: validation.options,
|
|
rule: validation.rule
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Compiles validations
|
|
*/
|
|
compileValidations(refs) {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
ruleFnId: refs.track({
|
|
validator: validation.rule.validator,
|
|
options: validation.options
|
|
}),
|
|
implicit: validation.rule.implicit,
|
|
isAsync: validation.rule.isAsync
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Push a validation to the validations chain.
|
|
*/
|
|
use(validation) {
|
|
this.validations.push(VALIDATION in validation ? validation[VALIDATION]() : validation);
|
|
return this;
|
|
}
|
|
requiredWhen(otherField, operator, expectedValue) {
|
|
if (typeof otherField === "function") {
|
|
return this.use(requiredWhen(otherField));
|
|
}
|
|
let checker;
|
|
switch (operator) {
|
|
case "=":
|
|
checker = (value) => value === expectedValue;
|
|
break;
|
|
case "!=":
|
|
checker = (value) => value !== expectedValue;
|
|
break;
|
|
case "in":
|
|
checker = (value) => expectedValue.includes(value);
|
|
break;
|
|
case "notIn":
|
|
checker = (value) => !expectedValue.includes(value);
|
|
break;
|
|
case ">":
|
|
checker = (value) => value > expectedValue;
|
|
break;
|
|
case "<":
|
|
checker = (value) => value < expectedValue;
|
|
break;
|
|
case ">=":
|
|
checker = (value) => value >= expectedValue;
|
|
break;
|
|
case "<=":
|
|
checker = (value) => value <= expectedValue;
|
|
}
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
const otherFieldValue = helpers.getNestedValue(otherField, field);
|
|
return checker(otherFieldValue);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when all
|
|
* the other fields are present with value other
|
|
* than `undefined` or `null`.
|
|
*/
|
|
requiredIfExists(fields2) {
|
|
const fieldsToExist = Array.isArray(fields2) ? fields2 : [fields2];
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fieldsToExist.every(
|
|
(otherField) => helpers.exists(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when any
|
|
* one of the other fields are present with non-nullable
|
|
* value.
|
|
*/
|
|
requiredIfAnyExists(fields2) {
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fields2.some(
|
|
(otherField) => helpers.exists(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when all
|
|
* the other fields are missing or their value is
|
|
* `undefined` or `null`.
|
|
*/
|
|
requiredIfMissing(fields2) {
|
|
const fieldsToExist = Array.isArray(fields2) ? fields2 : [fields2];
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fieldsToExist.every(
|
|
(otherField) => helpers.isMissing(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when any
|
|
* one of the other fields are missing.
|
|
*/
|
|
requiredIfAnyMissing(fields2) {
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fields2.some(
|
|
(otherField) => helpers.isMissing(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Creates a fresh instance of the underlying schema type
|
|
* and wraps it inside the optional modifier
|
|
*/
|
|
clone() {
|
|
return new _OptionalModifier(this.#parent.clone(), this.cloneValidations());
|
|
}
|
|
/**
|
|
* Compiles to compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
const output = this.#parent[PARSE](propertyName, refs, options);
|
|
output.isOptional = true;
|
|
output.validations = output.validations.concat(this.compileValidations(refs));
|
|
return output;
|
|
}
|
|
};
|
|
var TransformModifier = class _TransformModifier extends BaseModifiersType {
|
|
#parent;
|
|
#transform;
|
|
constructor(transform, parent) {
|
|
super();
|
|
this.#transform = transform;
|
|
this.#parent = parent;
|
|
}
|
|
/**
|
|
* Creates a fresh instance of the underlying schema type
|
|
* and wraps it inside the transform modifier.
|
|
*/
|
|
clone() {
|
|
return new _TransformModifier(this.#transform, this.#parent.clone());
|
|
}
|
|
/**
|
|
* Compiles to compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
const output = this.#parent[PARSE](propertyName, refs, options);
|
|
output.transformFnId = refs.trackTransformer(this.#transform);
|
|
return output;
|
|
}
|
|
};
|
|
var BaseLiteralType = class extends BaseModifiersType {
|
|
/**
|
|
* Field options
|
|
*/
|
|
options;
|
|
/**
|
|
* Set of validations to run
|
|
*/
|
|
validations;
|
|
constructor(options, validations) {
|
|
super();
|
|
this.options = {
|
|
bail: true,
|
|
allowNull: false,
|
|
isOptional: false,
|
|
...options
|
|
};
|
|
this.validations = validations || [];
|
|
}
|
|
/**
|
|
* Shallow clones the validations. Since, there are no API's to mutate
|
|
* the validation options, we can safely copy them by reference.
|
|
*/
|
|
cloneValidations() {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
options: validation.options,
|
|
rule: validation.rule
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Shallow clones the options
|
|
*/
|
|
cloneOptions() {
|
|
return { ...this.options };
|
|
}
|
|
/**
|
|
* Compiles validations
|
|
*/
|
|
compileValidations(refs) {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
ruleFnId: refs.track({
|
|
validator: validation.rule.validator,
|
|
options: validation.options
|
|
}),
|
|
implicit: validation.rule.implicit,
|
|
isAsync: validation.rule.isAsync
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Define a method to parse the input value. The method
|
|
* is invoked before any validation and hence you must
|
|
* perform type-checking to know the value you are
|
|
* working it.
|
|
*/
|
|
parse(callback) {
|
|
this.options.parse = callback;
|
|
return this;
|
|
}
|
|
/**
|
|
* Push a validation to the validations chain.
|
|
*/
|
|
use(validation) {
|
|
this.validations.push(VALIDATION in validation ? validation[VALIDATION]() : validation);
|
|
return this;
|
|
}
|
|
/**
|
|
* Enable/disable the bail mode. In bail mode, the field validations
|
|
* are stopped after the first error.
|
|
*/
|
|
bail(state) {
|
|
this.options.bail = state;
|
|
return this;
|
|
}
|
|
/**
|
|
* Compiles the schema type to a compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "literal",
|
|
subtype: this[SUBTYPE],
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase(propertyName) : propertyName,
|
|
bail: this.options.bail,
|
|
allowNull: this.options.allowNull,
|
|
isOptional: this.options.isOptional,
|
|
parseFnId: this.options.parse ? refs.trackParser(this.options.parse) : void 0,
|
|
validations: this.compileValidations(refs)
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/any/main.ts
|
|
var VineAny = class _VineAny extends BaseLiteralType {
|
|
constructor(options, validations) {
|
|
super(options, validations);
|
|
}
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "any";
|
|
/**
|
|
* Clones the VineAny schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineAny(this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/enum/rules.ts
|
|
var enumRule = createRule((value, options, field) => {
|
|
const choices = typeof options.choices === "function" ? options.choices(field) : options.choices;
|
|
if (!choices.includes(value)) {
|
|
field.report(messages.enum, "enum", field, { choices });
|
|
}
|
|
});
|
|
|
|
// src/schema/enum/main.ts
|
|
var VineEnum = class _VineEnum extends BaseLiteralType {
|
|
/**
|
|
* Default collection of enum rules
|
|
*/
|
|
static rules = {
|
|
enum: enumRule
|
|
};
|
|
#values;
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "enum";
|
|
/**
|
|
* Returns the enum choices
|
|
*/
|
|
getChoices() {
|
|
return this.#values;
|
|
}
|
|
constructor(values, options, validations) {
|
|
super(options, validations || [enumRule({ choices: values })]);
|
|
this.#values = values;
|
|
}
|
|
/**
|
|
* Clones the VineEnum schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineEnum(this.#values, this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/date/main.ts
|
|
import dayjs2 from "dayjs";
|
|
|
|
// src/schema/date/rules.ts
|
|
import dayjs from "dayjs";
|
|
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
|
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
|
|
import customParseFormat from "dayjs/plugin/customParseFormat.js";
|
|
var DEFAULT_DATE_FORMATS = ["YYYY-MM-DD", "YYYY-MM-DD HH:mm:ss"];
|
|
dayjs.extend(customParseFormat);
|
|
dayjs.extend(isSameOrAfter);
|
|
dayjs.extend(isSameOrBefore);
|
|
var dateRule = createRule((value, options, field) => {
|
|
if (typeof value !== "string" && typeof value !== "number") {
|
|
field.report(messages.date, "date", field);
|
|
return;
|
|
}
|
|
let isTimestampAllowed = false;
|
|
let isISOAllowed = false;
|
|
let formats = options.formats || DEFAULT_DATE_FORMATS;
|
|
if (Array.isArray(formats)) {
|
|
formats = [...formats];
|
|
isTimestampAllowed = formats.includes("x");
|
|
isISOAllowed = formats.includes("iso8601");
|
|
} else if (typeof formats !== "string") {
|
|
formats = { ...formats };
|
|
isTimestampAllowed = formats.format === "x";
|
|
isISOAllowed = formats.format === "iso";
|
|
}
|
|
const valueAsNumber = isTimestampAllowed ? helpers.asNumber(value) : value;
|
|
let dateTime;
|
|
if (isTimestampAllowed && !Number.isNaN(valueAsNumber)) {
|
|
dateTime = dayjs(valueAsNumber);
|
|
} else {
|
|
dateTime = dayjs(value, formats, true);
|
|
}
|
|
if (!dateTime.isValid() && isISOAllowed) {
|
|
dateTime = dayjs(value);
|
|
}
|
|
if (!dateTime.isValid()) {
|
|
field.report(messages.date, "date", field);
|
|
return;
|
|
}
|
|
field.meta.$value = dateTime;
|
|
field.meta.$formats = formats;
|
|
field.mutate(dateTime.toDate(), field);
|
|
});
|
|
var equalsRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const format = options.format || DEFAULT_DATE_FORMATS;
|
|
const dateTime = field.meta.$value;
|
|
const expectedValue = typeof options.expectedValue === "function" ? options.expectedValue(field) : options.expectedValue;
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
throw new Error(`Invalid datetime value "${expectedValue}" provided to the equals rule`);
|
|
}
|
|
if (!dateTime.isSame(expectedDateTime, compare)) {
|
|
field.report(messages["date.equals"], "date.equals", field, {
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var afterRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const format = options.format || DEFAULT_DATE_FORMATS;
|
|
const dateTime = field.meta.$value;
|
|
const expectedValue = typeof options.expectedValue === "function" ? options.expectedValue(field) : options.expectedValue;
|
|
const expectedDateTime = expectedValue === "today" ? dayjs() : expectedValue === "tomorrow" ? dayjs().add(1, "day") : dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
throw new Error(`Invalid datetime value "${expectedValue}" provided to the after rule`);
|
|
}
|
|
if (!dateTime.isAfter(expectedDateTime, compare)) {
|
|
field.report(messages["date.after"], "date.after", field, {
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var afterOrEqualRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const format = options.format || DEFAULT_DATE_FORMATS;
|
|
const dateTime = field.meta.$value;
|
|
const expectedValue = typeof options.expectedValue === "function" ? options.expectedValue(field) : options.expectedValue;
|
|
const expectedDateTime = expectedValue === "today" ? dayjs() : expectedValue === "tomorrow" ? dayjs().add(1, "day") : dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
throw new Error(`Invalid datetime value "${expectedValue}" provided to the afterOrEqual rule`);
|
|
}
|
|
if (!dateTime.isSameOrAfter(expectedDateTime, compare)) {
|
|
field.report(messages["date.afterOrEqual"], "date.afterOrEqual", field, {
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var beforeRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const format = options.format || DEFAULT_DATE_FORMATS;
|
|
const dateTime = field.meta.$value;
|
|
const expectedValue = typeof options.expectedValue === "function" ? options.expectedValue(field) : options.expectedValue;
|
|
const expectedDateTime = expectedValue === "today" ? dayjs() : expectedValue === "yesterday" ? dayjs().subtract(1, "day") : dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
throw new Error(`Invalid datetime value "${expectedValue}" provided to the before rule`);
|
|
}
|
|
if (!dateTime.isBefore(expectedDateTime, compare)) {
|
|
field.report(messages["date.before"], "date.before", field, {
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var beforeOrEqualRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const format = options.format || DEFAULT_DATE_FORMATS;
|
|
const dateTime = field.meta.$value;
|
|
const expectedValue = typeof options.expectedValue === "function" ? options.expectedValue(field) : options.expectedValue;
|
|
const expectedDateTime = expectedValue === "today" ? dayjs() : expectedValue === "yesterday" ? dayjs().subtract(1, "day") : dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
throw new Error(`Invalid datetime value "${expectedValue}" provided to the beforeOrEqual rule`);
|
|
}
|
|
if (!dateTime.isSameOrBefore(expectedDateTime, compare)) {
|
|
field.report(messages["date.beforeOrEqual"], "date.beforeOrEqual", field, {
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var sameAsRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const dateTime = field.meta.$value;
|
|
const format = options.format || field.meta.$formats;
|
|
const expectedValue = helpers.getNestedValue(options.otherField, field);
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
return;
|
|
}
|
|
if (!dateTime.isSame(expectedDateTime, compare)) {
|
|
field.report(messages["date.sameAs"], "date.sameAs", field, {
|
|
otherField: options.otherField,
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var notSameAsRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const dateTime = field.meta.$value;
|
|
const format = options.format || field.meta.$formats;
|
|
const expectedValue = helpers.getNestedValue(options.otherField, field);
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
return;
|
|
}
|
|
if (dateTime.isSame(expectedDateTime, compare)) {
|
|
field.report(messages["date.notSameAs"], "date.notSameAs", field, {
|
|
otherField: options.otherField,
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var afterFieldRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const dateTime = field.meta.$value;
|
|
const format = options.format || field.meta.$formats;
|
|
const expectedValue = helpers.getNestedValue(options.otherField, field);
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
return;
|
|
}
|
|
if (!dateTime.isAfter(expectedDateTime, compare)) {
|
|
field.report(messages["date.afterField"], "date.afterField", field, {
|
|
otherField: options.otherField,
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var afterOrSameAsRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const dateTime = field.meta.$value;
|
|
const format = options.format || field.meta.$formats;
|
|
const expectedValue = helpers.getNestedValue(options.otherField, field);
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
return;
|
|
}
|
|
if (!dateTime.isSameOrAfter(expectedDateTime, compare)) {
|
|
field.report(messages["date.afterOrSameAs"], "date.afterOrSameAs", field, {
|
|
otherField: options.otherField,
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var beforeFieldRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const dateTime = field.meta.$value;
|
|
const format = options.format || field.meta.$formats;
|
|
const expectedValue = helpers.getNestedValue(options.otherField, field);
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
return;
|
|
}
|
|
if (!dateTime.isBefore(expectedDateTime, compare)) {
|
|
field.report(messages["date.beforeField"], "date.beforeField", field, {
|
|
otherField: options.otherField,
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var beforeOrSameAsRule = createRule((_, options, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const compare = options.compare || "day";
|
|
const dateTime = field.meta.$value;
|
|
const format = options.format || field.meta.$formats;
|
|
const expectedValue = helpers.getNestedValue(options.otherField, field);
|
|
const expectedDateTime = dayjs(expectedValue, format, true);
|
|
if (!expectedDateTime.isValid()) {
|
|
return;
|
|
}
|
|
if (!dateTime.isSameOrBefore(expectedDateTime, compare)) {
|
|
field.report(messages["date.beforeOrSameAs"], "date.beforeOrSameAs", field, {
|
|
otherField: options.otherField,
|
|
expectedValue,
|
|
compare
|
|
});
|
|
}
|
|
});
|
|
var weekendRule = createRule((_, __, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const dateTime = field.meta.$value;
|
|
const day = dateTime.day();
|
|
if (day !== 0 && day !== 6) {
|
|
field.report(messages["date.weekend"], "date.weekend", field);
|
|
}
|
|
});
|
|
var weekdayRule = createRule((_, __, field) => {
|
|
if (!field.meta.$value) {
|
|
return;
|
|
}
|
|
const dateTime = field.meta.$value;
|
|
const day = dateTime.day();
|
|
if (day === 0 || day === 6) {
|
|
field.report(messages["date.weekday"], "date.weekday", field);
|
|
}
|
|
});
|
|
|
|
// src/schema/date/main.ts
|
|
var VineDate = class _VineDate extends BaseLiteralType {
|
|
/**
|
|
* Available VineDate rules
|
|
*/
|
|
static rules = {
|
|
equals: equalsRule,
|
|
after: afterRule,
|
|
afterOrEqual: afterOrEqualRule,
|
|
before: beforeRule,
|
|
beforeOrEqual: beforeOrEqualRule,
|
|
sameAs: sameAsRule,
|
|
notSameAs: notSameAsRule,
|
|
afterField: afterFieldRule,
|
|
afterOrSameAs: afterOrSameAsRule,
|
|
beforeField: beforeFieldRule,
|
|
beforeOrSameAs: beforeOrSameAsRule,
|
|
weekend: weekendRule,
|
|
weekday: weekdayRule
|
|
};
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.date";
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "date";
|
|
/**
|
|
* Checks if the value is of date type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
if (typeof value !== "string") {
|
|
return false;
|
|
}
|
|
return dayjs2(value, this.options.formats || DEFAULT_DATE_FORMATS, true).isValid();
|
|
};
|
|
constructor(options, validations) {
|
|
super(options, validations || [dateRule(options || {})]);
|
|
}
|
|
/**
|
|
* The equals rule compares the input value to be same
|
|
* as the expected value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed.
|
|
*/
|
|
equals(expectedValue, options) {
|
|
return this.use(equalsRule({ expectedValue, ...options }));
|
|
}
|
|
/**
|
|
* The after rule compares the input value to be after
|
|
* the expected value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed.
|
|
*/
|
|
after(expectedValue, options) {
|
|
return this.use(afterRule({ expectedValue, ...options }));
|
|
}
|
|
/**
|
|
* The after or equal rule compares the input value to be
|
|
* after or equal to the expected value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed.
|
|
*/
|
|
afterOrEqual(expectedValue, options) {
|
|
return this.use(afterOrEqualRule({ expectedValue, ...options }));
|
|
}
|
|
/**
|
|
* The before rule compares the input value to be before
|
|
* the expected value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed.
|
|
*/
|
|
before(expectedValue, options) {
|
|
return this.use(beforeRule({ expectedValue, ...options }));
|
|
}
|
|
/**
|
|
* The before rule compares the input value to be before
|
|
* the expected value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed.
|
|
*/
|
|
beforeOrEqual(expectedValue, options) {
|
|
return this.use(beforeOrEqualRule({ expectedValue, ...options }));
|
|
}
|
|
/**
|
|
* The sameAs rule expects the input value to be same
|
|
* as the value of the other field.
|
|
*
|
|
* By default, the comparions of day, month and years are performed
|
|
*/
|
|
sameAs(otherField, options) {
|
|
return this.use(sameAsRule({ otherField, ...options }));
|
|
}
|
|
/**
|
|
* The notSameAs rule expects the input value to be different
|
|
* from the other field's value
|
|
*
|
|
* By default, the comparions of day, month and years are performed
|
|
*/
|
|
notSameAs(otherField, options) {
|
|
return this.use(notSameAsRule({ otherField, ...options }));
|
|
}
|
|
/**
|
|
* The afterField rule expects the input value to be after
|
|
* the other field's value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed
|
|
*/
|
|
afterField(otherField, options) {
|
|
return this.use(afterFieldRule({ otherField, ...options }));
|
|
}
|
|
/**
|
|
* The afterOrSameAs rule expects the input value to be after
|
|
* or equal to the other field's value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed
|
|
*/
|
|
afterOrSameAs(otherField, options) {
|
|
return this.use(afterOrSameAsRule({ otherField, ...options }));
|
|
}
|
|
/**
|
|
* The beforeField rule expects the input value to be before
|
|
* the other field's value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed
|
|
*/
|
|
beforeField(otherField, options) {
|
|
return this.use(beforeFieldRule({ otherField, ...options }));
|
|
}
|
|
/**
|
|
* The beforeOrSameAs rule expects the input value to be before
|
|
* or same as the other field's value.
|
|
*
|
|
* By default, the comparions of day, month and years are performed
|
|
*/
|
|
beforeOrSameAs(otherField, options) {
|
|
return this.use(beforeOrSameAsRule({ otherField, ...options }));
|
|
}
|
|
/**
|
|
* The weekend rule ensures the date falls on a weekend
|
|
*/
|
|
weekend() {
|
|
return this.use(weekendRule());
|
|
}
|
|
/**
|
|
* The weekday rule ensures the date falls on a weekday
|
|
*/
|
|
weekday() {
|
|
return this.use(weekdayRule());
|
|
}
|
|
/**
|
|
* Clones the VineDate schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineDate(this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/union/main.ts
|
|
import camelcase2 from "camelcase";
|
|
var VineUnion = class _VineUnion {
|
|
#conditionals;
|
|
#otherwiseCallback = (_, field) => {
|
|
field.report(messages.union, "union", field);
|
|
};
|
|
constructor(conditionals) {
|
|
this.#conditionals = conditionals;
|
|
}
|
|
/**
|
|
* Define a fallback method to invoke when all of the union conditions
|
|
* fail. You may use this method to report an error.
|
|
*/
|
|
otherwise(callback) {
|
|
this.#otherwiseCallback = callback;
|
|
return this;
|
|
}
|
|
/**
|
|
* Clones the VineUnion schema type.
|
|
*/
|
|
clone() {
|
|
const cloned = new _VineUnion(this.#conditionals);
|
|
cloned.otherwise(this.#otherwiseCallback);
|
|
return cloned;
|
|
}
|
|
/**
|
|
* Compiles to a union
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "union",
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase2(propertyName) : propertyName,
|
|
elseConditionalFnRefId: refs.trackConditional(this.#otherwiseCallback),
|
|
conditions: this.#conditionals.map(
|
|
(conditional) => conditional[PARSE](propertyName, refs, options)
|
|
)
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/union/conditional.ts
|
|
var UnionConditional = class {
|
|
/**
|
|
* Properties to merge when conditonal is true
|
|
*/
|
|
#schema;
|
|
/**
|
|
* Conditional to evaluate
|
|
*/
|
|
#conditional;
|
|
constructor(conditional, schema) {
|
|
this.#schema = schema;
|
|
this.#conditional = conditional;
|
|
}
|
|
/**
|
|
* Compiles to a union conditional
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
conditionalFnRefId: refs.trackConditional(this.#conditional),
|
|
schema: this.#schema[PARSE](propertyName, refs, options)
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/union/builder.ts
|
|
function union(conditionals) {
|
|
return new VineUnion(conditionals);
|
|
}
|
|
union.if = function unionIf(conditon, schema) {
|
|
return new UnionConditional(conditon, schema);
|
|
};
|
|
union.else = function unionElse(schema) {
|
|
return new UnionConditional(() => true, schema);
|
|
};
|
|
|
|
// src/schema/tuple/main.ts
|
|
import camelcase3 from "camelcase";
|
|
|
|
// src/schema/base/main.ts
|
|
import Macroable2 from "@poppinss/macroable";
|
|
var BaseModifiersType2 = class extends Macroable2 {
|
|
/**
|
|
* Mark the field under validation as optional. An optional
|
|
* field allows both null and undefined values.
|
|
*/
|
|
optional() {
|
|
return new OptionalModifier2(this);
|
|
}
|
|
/**
|
|
* Mark the field under validation to be null. The null value will
|
|
* be written to the output as well.
|
|
*
|
|
* If `optional` and `nullable` are used together, then both undefined
|
|
* and null values will be allowed.
|
|
*/
|
|
nullable() {
|
|
return new NullableModifier2(this);
|
|
}
|
|
};
|
|
var NullableModifier2 = class _NullableModifier extends BaseModifiersType2 {
|
|
#parent;
|
|
constructor(parent) {
|
|
super();
|
|
this.#parent = parent;
|
|
}
|
|
/**
|
|
* Creates a fresh instance of the underlying schema type
|
|
* and wraps it inside the nullable modifier
|
|
*/
|
|
clone() {
|
|
return new _NullableModifier(this.#parent.clone());
|
|
}
|
|
/**
|
|
* Compiles to compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
const output = this.#parent[PARSE](propertyName, refs, options);
|
|
if (output.type !== "union") {
|
|
output.allowNull = true;
|
|
}
|
|
return output;
|
|
}
|
|
};
|
|
var OptionalModifier2 = class _OptionalModifier extends BaseModifiersType2 {
|
|
#parent;
|
|
/**
|
|
* Optional modifier validations list
|
|
*/
|
|
validations;
|
|
constructor(parent, validations) {
|
|
super();
|
|
this.#parent = parent;
|
|
this.validations = validations || [];
|
|
}
|
|
/**
|
|
* Shallow clones the validations. Since, there are no API's to mutate
|
|
* the validation options, we can safely copy them by reference.
|
|
*/
|
|
cloneValidations() {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
options: validation.options,
|
|
rule: validation.rule
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Compiles validations
|
|
*/
|
|
compileValidations(refs) {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
ruleFnId: refs.track({
|
|
validator: validation.rule.validator,
|
|
options: validation.options
|
|
}),
|
|
implicit: validation.rule.implicit,
|
|
isAsync: validation.rule.isAsync
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Push a validation to the validations chain.
|
|
*/
|
|
use(validation) {
|
|
this.validations.push(VALIDATION in validation ? validation[VALIDATION]() : validation);
|
|
return this;
|
|
}
|
|
requiredWhen(otherField, operator, expectedValue) {
|
|
if (typeof otherField === "function") {
|
|
return this.use(requiredWhen(otherField));
|
|
}
|
|
let checker;
|
|
switch (operator) {
|
|
case "=":
|
|
checker = (value) => value === expectedValue;
|
|
break;
|
|
case "!=":
|
|
checker = (value) => value !== expectedValue;
|
|
break;
|
|
case "in":
|
|
checker = (value) => expectedValue.includes(value);
|
|
break;
|
|
case "notIn":
|
|
checker = (value) => !expectedValue.includes(value);
|
|
break;
|
|
case ">":
|
|
checker = (value) => value > expectedValue;
|
|
break;
|
|
case "<":
|
|
checker = (value) => value < expectedValue;
|
|
break;
|
|
case ">=":
|
|
checker = (value) => value >= expectedValue;
|
|
break;
|
|
case "<=":
|
|
checker = (value) => value <= expectedValue;
|
|
}
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
const otherFieldValue = helpers.getNestedValue(otherField, field);
|
|
return checker(otherFieldValue);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when all
|
|
* the other fields are present with value other
|
|
* than `undefined` or `null`.
|
|
*/
|
|
requiredIfExists(fields2) {
|
|
const fieldsToExist = Array.isArray(fields2) ? fields2 : [fields2];
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fieldsToExist.every((otherField) => {
|
|
return helpers.exists(helpers.getNestedValue(otherField, field));
|
|
});
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when any
|
|
* one of the other fields are present with non-nullable
|
|
* value.
|
|
*/
|
|
requiredIfAnyExists(fields2) {
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fields2.some(
|
|
(otherField) => helpers.exists(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when all
|
|
* the other fields are missing or their value is
|
|
* `undefined` or `null`.
|
|
*/
|
|
requiredIfMissing(fields2) {
|
|
const fieldsToExist = Array.isArray(fields2) ? fields2 : [fields2];
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fieldsToExist.every(
|
|
(otherField) => helpers.isMissing(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Mark the field under validation as required when any
|
|
* one of the other fields are missing.
|
|
*/
|
|
requiredIfAnyMissing(fields2) {
|
|
return this.use(
|
|
requiredWhen((field) => {
|
|
return fields2.some(
|
|
(otherField) => helpers.isMissing(helpers.getNestedValue(otherField, field))
|
|
);
|
|
})
|
|
);
|
|
}
|
|
/**
|
|
* Creates a fresh instance of the underlying schema type
|
|
* and wraps it inside the optional modifier
|
|
*/
|
|
clone() {
|
|
return new _OptionalModifier(this.#parent.clone(), this.cloneValidations());
|
|
}
|
|
/**
|
|
* Compiles to compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
const output = this.#parent[PARSE](propertyName, refs, options);
|
|
if (output.type !== "union") {
|
|
output.isOptional = true;
|
|
output.validations = output.validations.concat(this.compileValidations(refs));
|
|
}
|
|
return output;
|
|
}
|
|
};
|
|
var BaseType = class extends BaseModifiersType2 {
|
|
/**
|
|
* Field options
|
|
*/
|
|
options;
|
|
/**
|
|
* Set of validations to run
|
|
*/
|
|
validations;
|
|
constructor(options, validations) {
|
|
super();
|
|
this.options = options || {
|
|
bail: true,
|
|
allowNull: false,
|
|
isOptional: false
|
|
};
|
|
this.validations = validations || [];
|
|
}
|
|
/**
|
|
* Shallow clones the validations. Since, there are no API's to mutate
|
|
* the validation options, we can safely copy them by reference.
|
|
*/
|
|
cloneValidations() {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
options: validation.options,
|
|
rule: validation.rule
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Shallow clones the options
|
|
*/
|
|
cloneOptions() {
|
|
return { ...this.options };
|
|
}
|
|
/**
|
|
* Compiles validations
|
|
*/
|
|
compileValidations(refs) {
|
|
return this.validations.map((validation) => {
|
|
return {
|
|
ruleFnId: refs.track({
|
|
validator: validation.rule.validator,
|
|
options: validation.options
|
|
}),
|
|
implicit: validation.rule.implicit,
|
|
isAsync: validation.rule.isAsync
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* Define a method to parse the input value. The method
|
|
* is invoked before any validation and hence you must
|
|
* perform type-checking to know the value you are
|
|
* working it.
|
|
*/
|
|
parse(callback) {
|
|
this.options.parse = callback;
|
|
return this;
|
|
}
|
|
/**
|
|
* Push a validation to the validations chain.
|
|
*/
|
|
use(validation) {
|
|
this.validations.push(VALIDATION in validation ? validation[VALIDATION]() : validation);
|
|
return this;
|
|
}
|
|
/**
|
|
* Enable/disable the bail mode. In bail mode, the field validations
|
|
* are stopped after the first error.
|
|
*/
|
|
bail(state) {
|
|
this.options.bail = state;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
// src/schema/tuple/main.ts
|
|
var VineTuple = class _VineTuple extends BaseType {
|
|
#schemas;
|
|
/**
|
|
* Whether or not to allow unknown properties
|
|
*/
|
|
#allowUnknownProperties = false;
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.array";
|
|
/**
|
|
* Checks if the value is of array type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
return Array.isArray(value);
|
|
};
|
|
constructor(schemas, options, validations) {
|
|
super(options, validations);
|
|
this.#schemas = schemas;
|
|
}
|
|
/**
|
|
* Copy unknown properties to the final output.
|
|
*/
|
|
allowUnknownProperties() {
|
|
this.#allowUnknownProperties = true;
|
|
return this;
|
|
}
|
|
/**
|
|
* Clone object
|
|
*/
|
|
clone() {
|
|
const cloned = new _VineTuple(
|
|
this.#schemas.map((schema) => schema.clone()),
|
|
this.cloneOptions(),
|
|
this.cloneValidations()
|
|
);
|
|
if (this.#allowUnknownProperties) {
|
|
cloned.allowUnknownProperties();
|
|
}
|
|
return cloned;
|
|
}
|
|
/**
|
|
* Compiles to array data type
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "tuple",
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase3(propertyName) : propertyName,
|
|
bail: this.options.bail,
|
|
allowNull: this.options.allowNull,
|
|
isOptional: this.options.isOptional,
|
|
allowUnknownProperties: this.#allowUnknownProperties,
|
|
parseFnId: this.options.parse ? refs.trackParser(this.options.parse) : void 0,
|
|
validations: this.compileValidations(refs),
|
|
properties: this.#schemas.map((schema, index) => schema[PARSE](String(index), refs, options))
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/array/main.ts
|
|
import camelcase4 from "camelcase";
|
|
|
|
// src/schema/array/rules.ts
|
|
var minLengthRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length < options.min) {
|
|
field.report(messages["array.minLength"], "array.minLength", field, options);
|
|
}
|
|
});
|
|
var maxLengthRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length > options.max) {
|
|
field.report(messages["array.maxLength"], "array.maxLength", field, options);
|
|
}
|
|
});
|
|
var fixedLengthRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length !== options.size) {
|
|
field.report(messages["array.fixedLength"], "array.fixedLength", field, options);
|
|
}
|
|
});
|
|
var notEmptyRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length <= 0) {
|
|
field.report(messages.notEmpty, "notEmpty", field);
|
|
}
|
|
});
|
|
var distinctRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isDistinct(value, options.fields)) {
|
|
field.report(messages.distinct, "distinct", field, options);
|
|
}
|
|
});
|
|
var compactRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(
|
|
value.filter((item) => helpers.exists(item) && item !== ""),
|
|
field
|
|
);
|
|
});
|
|
|
|
// src/schema/array/main.ts
|
|
var VineArray = class _VineArray extends BaseType {
|
|
/**
|
|
* Default collection of array rules
|
|
*/
|
|
static rules = {
|
|
compact: compactRule,
|
|
notEmpty: notEmptyRule,
|
|
distinct: distinctRule,
|
|
minLength: minLengthRule,
|
|
maxLength: maxLengthRule,
|
|
fixedLength: fixedLengthRule
|
|
};
|
|
#schema;
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.array";
|
|
/**
|
|
* Checks if the value is of array type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
return Array.isArray(value);
|
|
};
|
|
constructor(schema, options, validations) {
|
|
super(options, validations);
|
|
this.#schema = schema;
|
|
}
|
|
/**
|
|
* Enforce a minimum length on an array field
|
|
*/
|
|
minLength(expectedLength) {
|
|
return this.use(minLengthRule({ min: expectedLength }));
|
|
}
|
|
/**
|
|
* Enforce a maximum length on an array field
|
|
*/
|
|
maxLength(expectedLength) {
|
|
return this.use(maxLengthRule({ max: expectedLength }));
|
|
}
|
|
/**
|
|
* Enforce a fixed length on an array field
|
|
*/
|
|
fixedLength(expectedLength) {
|
|
return this.use(fixedLengthRule({ size: expectedLength }));
|
|
}
|
|
/**
|
|
* Ensure the array is not empty
|
|
*/
|
|
notEmpty() {
|
|
return this.use(notEmptyRule());
|
|
}
|
|
/**
|
|
* Ensure array elements are distinct/unique
|
|
*/
|
|
distinct(fields2) {
|
|
return this.use(distinctRule({ fields: fields2 }));
|
|
}
|
|
/**
|
|
* Removes empty strings, null and undefined values from the array
|
|
*/
|
|
compact() {
|
|
return this.use(compactRule());
|
|
}
|
|
/**
|
|
* Clones the VineArray schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineArray(this.#schema.clone(), this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
/**
|
|
* Compiles to array data type
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "array",
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase4(propertyName) : propertyName,
|
|
bail: this.options.bail,
|
|
allowNull: this.options.allowNull,
|
|
isOptional: this.options.isOptional,
|
|
each: this.#schema[PARSE]("*", refs, options),
|
|
parseFnId: this.options.parse ? refs.trackParser(this.options.parse) : void 0,
|
|
validations: this.compileValidations(refs)
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/object/main.ts
|
|
import camelcase5 from "camelcase";
|
|
var VineCamelCaseObject = class _VineCamelCaseObject extends BaseModifiersType2 {
|
|
#schema;
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "types.object";
|
|
/**
|
|
* Checks if the value is of object type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
};
|
|
constructor(schema) {
|
|
super();
|
|
this.#schema = schema;
|
|
}
|
|
/**
|
|
* Clone object
|
|
*/
|
|
clone() {
|
|
return new _VineCamelCaseObject(this.#schema.clone());
|
|
}
|
|
/**
|
|
* Compiles the schema type to a compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
options.toCamelCase = true;
|
|
return this.#schema[PARSE](propertyName, refs, options);
|
|
}
|
|
};
|
|
var VineObject = class _VineObject extends BaseType {
|
|
/**
|
|
* Object properties
|
|
*/
|
|
#properties;
|
|
/**
|
|
* Object groups to merge based on conditionals
|
|
*/
|
|
#groups = [];
|
|
/**
|
|
* Whether or not to allow unknown properties
|
|
*/
|
|
#allowUnknownProperties = false;
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.object";
|
|
/**
|
|
* Checks if the value is of object type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
};
|
|
constructor(properties, options, validations) {
|
|
if (!properties) {
|
|
throw new Error(
|
|
'Missing properties for "vine.object". Use an empty object if you do not want to validate any specific fields'
|
|
);
|
|
}
|
|
super(options, validations);
|
|
this.#properties = properties;
|
|
}
|
|
/**
|
|
* Returns a clone copy of the object properties. The object groups
|
|
* are not copied to keep the implementations simple and easy to
|
|
* reason about.
|
|
*/
|
|
getProperties() {
|
|
return Object.keys(this.#properties).reduce((result, key) => {
|
|
result[key] = this.#properties[key].clone();
|
|
return result;
|
|
}, {});
|
|
}
|
|
/**
|
|
* Copy unknown properties to the final output.
|
|
*/
|
|
allowUnknownProperties() {
|
|
this.#allowUnknownProperties = true;
|
|
return this;
|
|
}
|
|
/**
|
|
* Merge a union to the object groups. The union can be a "vine.union"
|
|
* with objects, or a "vine.object.union" with properties.
|
|
*/
|
|
merge(group2) {
|
|
this.#groups.push(group2);
|
|
return this;
|
|
}
|
|
/**
|
|
* Clone object
|
|
*/
|
|
clone() {
|
|
const cloned = new _VineObject(
|
|
this.getProperties(),
|
|
this.cloneOptions(),
|
|
this.cloneValidations()
|
|
);
|
|
this.#groups.forEach((group2) => cloned.merge(group2));
|
|
if (this.#allowUnknownProperties) {
|
|
cloned.allowUnknownProperties();
|
|
}
|
|
return cloned;
|
|
}
|
|
/**
|
|
* Applies camelcase transform
|
|
*/
|
|
toCamelCase() {
|
|
return new VineCamelCaseObject(this);
|
|
}
|
|
/**
|
|
* Compiles the schema type to a compiler node
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "object",
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase5(propertyName) : propertyName,
|
|
bail: this.options.bail,
|
|
allowNull: this.options.allowNull,
|
|
isOptional: this.options.isOptional,
|
|
parseFnId: this.options.parse ? refs.trackParser(this.options.parse) : void 0,
|
|
allowUnknownProperties: this.#allowUnknownProperties,
|
|
validations: this.compileValidations(refs),
|
|
properties: Object.keys(this.#properties).map((property) => {
|
|
return this.#properties[property][PARSE](property, refs, options);
|
|
}),
|
|
groups: this.#groups.map((group2) => {
|
|
return group2[PARSE](refs, options);
|
|
})
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/record/main.ts
|
|
import camelcase6 from "camelcase";
|
|
|
|
// src/schema/record/rules.ts
|
|
var minLengthRule2 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (Object.keys(value).length < options.min) {
|
|
field.report(messages["record.minLength"], "record.minLength", field, options);
|
|
}
|
|
});
|
|
var maxLengthRule2 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (Object.keys(value).length > options.max) {
|
|
field.report(messages["record.maxLength"], "record.maxLength", field, options);
|
|
}
|
|
});
|
|
var fixedLengthRule2 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (Object.keys(value).length !== options.size) {
|
|
field.report(messages["record.fixedLength"], "record.fixedLength", field, options);
|
|
}
|
|
});
|
|
var validateKeysRule = createRule(
|
|
(value, callback, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
callback(Object.keys(value), field);
|
|
}
|
|
);
|
|
|
|
// src/schema/record/main.ts
|
|
var VineRecord = class _VineRecord extends BaseType {
|
|
/**
|
|
* Default collection of record rules
|
|
*/
|
|
static rules = {
|
|
maxLength: maxLengthRule2,
|
|
minLength: minLengthRule2,
|
|
fixedLength: fixedLengthRule2,
|
|
validateKeys: validateKeysRule
|
|
};
|
|
#schema;
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.object";
|
|
/**
|
|
* Checks if the value is of object type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
};
|
|
constructor(schema, options, validations) {
|
|
super(options, validations);
|
|
this.#schema = schema;
|
|
}
|
|
/**
|
|
* Enforce a minimum length on an object field
|
|
*/
|
|
minLength(expectedLength) {
|
|
return this.use(minLengthRule2({ min: expectedLength }));
|
|
}
|
|
/**
|
|
* Enforce a maximum length on an object field
|
|
*/
|
|
maxLength(expectedLength) {
|
|
return this.use(maxLengthRule2({ max: expectedLength }));
|
|
}
|
|
/**
|
|
* Enforce a fixed length on an object field
|
|
*/
|
|
fixedLength(expectedLength) {
|
|
return this.use(fixedLengthRule2({ size: expectedLength }));
|
|
}
|
|
/**
|
|
* Register a callback to validate the object keys
|
|
*/
|
|
validateKeys(...args) {
|
|
return this.use(validateKeysRule(...args));
|
|
}
|
|
/**
|
|
* Clones the VineRecord schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineRecord(
|
|
this.#schema.clone(),
|
|
this.cloneOptions(),
|
|
this.cloneValidations()
|
|
);
|
|
}
|
|
/**
|
|
* Compiles to record data type
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "record",
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase6(propertyName) : propertyName,
|
|
bail: this.options.bail,
|
|
allowNull: this.options.allowNull,
|
|
isOptional: this.options.isOptional,
|
|
each: this.#schema[PARSE]("*", refs, options),
|
|
parseFnId: this.options.parse ? refs.trackParser(this.options.parse) : void 0,
|
|
validations: this.compileValidations(refs)
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/string/rules.ts
|
|
import camelcase7 from "camelcase";
|
|
import normalizeUrl from "normalize-url";
|
|
import escape from "validator/lib/escape.js";
|
|
import normalizeEmail from "validator/lib/normalizeEmail.js";
|
|
var stringRule = createRule((value, _, field) => {
|
|
if (typeof value !== "string") {
|
|
field.report(messages.string, "string", field);
|
|
}
|
|
});
|
|
var emailRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isEmail(value, options)) {
|
|
field.report(messages.email, "email", field);
|
|
}
|
|
});
|
|
var mobileRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const normalizedOptions = options && typeof options === "function" ? options(field) : options;
|
|
const locales = normalizedOptions?.locale || "any";
|
|
if (!helpers.isMobilePhone(value, locales, normalizedOptions)) {
|
|
field.report(messages.mobile, "mobile", field);
|
|
}
|
|
});
|
|
var ipAddressRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isIP(value, options?.version)) {
|
|
field.report(messages.ipAddress, "ipAddress", field);
|
|
}
|
|
});
|
|
var regexRule = createRule((value, expression, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!expression.test(value)) {
|
|
field.report(messages.regex, "regex", field);
|
|
}
|
|
});
|
|
var hexCodeRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isHexColor(value)) {
|
|
field.report(messages.hexCode, "hexCode", field);
|
|
}
|
|
});
|
|
var urlRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isURL(value, options)) {
|
|
field.report(messages.url, "url", field);
|
|
}
|
|
});
|
|
var activeUrlRule = createRule(async (value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!await helpers.isActiveURL(value)) {
|
|
field.report(messages.activeUrl, "activeUrl", field);
|
|
}
|
|
});
|
|
var alphaRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
let characterSet = "a-zA-Z";
|
|
if (options) {
|
|
if (options.allowSpaces) {
|
|
characterSet += "\\s";
|
|
}
|
|
if (options.allowDashes) {
|
|
characterSet += "-";
|
|
}
|
|
if (options.allowUnderscores) {
|
|
characterSet += "_";
|
|
}
|
|
}
|
|
const expression = new RegExp(`^[${characterSet}]+$`);
|
|
if (!expression.test(value)) {
|
|
field.report(messages.alpha, "alpha", field);
|
|
}
|
|
});
|
|
var alphaNumericRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
let characterSet = "a-zA-Z0-9";
|
|
if (options) {
|
|
if (options.allowSpaces) {
|
|
characterSet += "\\s";
|
|
}
|
|
if (options.allowDashes) {
|
|
characterSet += "-";
|
|
}
|
|
if (options.allowUnderscores) {
|
|
characterSet += "_";
|
|
}
|
|
}
|
|
const expression = new RegExp(`^[${characterSet}]+$`);
|
|
if (!expression.test(value)) {
|
|
field.report(messages.alphaNumeric, "alphaNumeric", field);
|
|
}
|
|
}
|
|
);
|
|
var minLengthRule3 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length < options.min) {
|
|
field.report(messages.minLength, "minLength", field, options);
|
|
}
|
|
});
|
|
var maxLengthRule3 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length > options.max) {
|
|
field.report(messages.maxLength, "maxLength", field, options);
|
|
}
|
|
});
|
|
var fixedLengthRule3 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value.length !== options.size) {
|
|
field.report(messages.fixedLength, "fixedLength", field, options);
|
|
}
|
|
});
|
|
var endsWithRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!value.endsWith(options.substring)) {
|
|
field.report(messages.endsWith, "endsWith", field, options);
|
|
}
|
|
});
|
|
var startsWithRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!value.startsWith(options.substring)) {
|
|
field.report(messages.startsWith, "startsWith", field, options);
|
|
}
|
|
});
|
|
var sameAsRule2 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const input = helpers.getNestedValue(options.otherField, field);
|
|
if (input !== value) {
|
|
field.report(messages.sameAs, "sameAs", field, options);
|
|
return;
|
|
}
|
|
});
|
|
var notSameAsRule2 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const input = helpers.getNestedValue(options.otherField, field);
|
|
if (input === value) {
|
|
field.report(messages.notSameAs, "notSameAs", field, options);
|
|
return;
|
|
}
|
|
});
|
|
var confirmedRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const otherField = options?.confirmationField || `${field.name}_confirmation`;
|
|
const input = field.parent[otherField];
|
|
if (input !== value) {
|
|
field.report(messages.confirmed, "confirmed", field, { otherField });
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
var trimRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(value.trim(), field);
|
|
});
|
|
var normalizeEmailRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(normalizeEmail.default(value, options), field);
|
|
}
|
|
);
|
|
var toUpperCaseRule = createRule(
|
|
(value, locales, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(value.toLocaleUpperCase(locales), field);
|
|
}
|
|
);
|
|
var toLowerCaseRule = createRule(
|
|
(value, locales, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(value.toLocaleLowerCase(locales), field);
|
|
}
|
|
);
|
|
var toCamelCaseRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(camelcase7(value), field);
|
|
});
|
|
var escapeRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(escape.default(value), field);
|
|
});
|
|
var normalizeUrlRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
field.mutate(normalizeUrl(value, options), field);
|
|
}
|
|
);
|
|
var inRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const choices = typeof options.choices === "function" ? options.choices(field) : options.choices;
|
|
if (!choices.includes(value)) {
|
|
field.report(messages.in, "in", field, options);
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
var notInRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const list = typeof options.list === "function" ? options.list(field) : options.list;
|
|
if (list.includes(value)) {
|
|
field.report(messages.notIn, "notIn", field, options);
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
var creditCardRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const providers = options ? typeof options === "function" ? options(field)?.provider || [] : options.provider : [];
|
|
if (!providers.length) {
|
|
if (!helpers.isCreditCard(value)) {
|
|
field.report(messages.creditCard, "creditCard", field, {
|
|
providersList: "credit"
|
|
});
|
|
}
|
|
} else {
|
|
const matchesAnyProvider = providers.find(
|
|
(provider) => helpers.isCreditCard(value, { provider })
|
|
);
|
|
if (!matchesAnyProvider) {
|
|
field.report(messages.creditCard, "creditCard", field, {
|
|
providers,
|
|
providersList: providers.join("/")
|
|
});
|
|
}
|
|
}
|
|
});
|
|
var passportRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const countryCodes = typeof options === "function" ? options(field).countryCode : options.countryCode;
|
|
const matchesAnyCountryCode = countryCodes.find(
|
|
(countryCode) => helpers.isPassportNumber(value, countryCode)
|
|
);
|
|
if (!matchesAnyCountryCode) {
|
|
field.report(messages.passport, "passport", field, { countryCodes });
|
|
}
|
|
});
|
|
var postalCodeRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
const countryCodes = options ? typeof options === "function" ? options(field)?.countryCode || [] : options.countryCode : [];
|
|
if (!countryCodes.length) {
|
|
if (!helpers.isPostalCode(value, "any")) {
|
|
field.report(messages.postalCode, "postalCode", field);
|
|
}
|
|
} else {
|
|
const matchesAnyCountryCode = countryCodes.find(
|
|
(countryCode) => helpers.isPostalCode(value, countryCode)
|
|
);
|
|
if (!matchesAnyCountryCode) {
|
|
field.report(messages.postalCode, "postalCode", field, { countryCodes });
|
|
}
|
|
}
|
|
});
|
|
var uuidRule = createRule(
|
|
(value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!options || !options.version) {
|
|
if (!helpers.isUUID(value)) {
|
|
field.report(messages.uuid, "uuid", field);
|
|
}
|
|
} else {
|
|
const matchesAnyVersion = options.version.find(
|
|
(version) => helpers.isUUID(value, version)
|
|
);
|
|
if (!matchesAnyVersion) {
|
|
field.report(messages.uuid, "uuid", field, options);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
var ulidRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isULID(value)) {
|
|
field.report(messages.ulid, "ulid", field);
|
|
}
|
|
});
|
|
var asciiRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isAscii(value)) {
|
|
field.report(messages.ascii, "ascii", field);
|
|
}
|
|
});
|
|
var ibanRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isIBAN(value)) {
|
|
field.report(messages.iban, "iban", field);
|
|
}
|
|
});
|
|
var jwtRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isJWT(value)) {
|
|
field.report(messages.jwt, "jwt", field);
|
|
}
|
|
});
|
|
var coordinatesRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isLatLong(value)) {
|
|
field.report(messages.coordinates, "coordinates", field);
|
|
}
|
|
});
|
|
|
|
// src/schema/string/main.ts
|
|
var VineString = class _VineString extends BaseLiteralType {
|
|
static rules = {
|
|
in: inRule,
|
|
jwt: jwtRule,
|
|
url: urlRule,
|
|
iban: ibanRule,
|
|
uuid: uuidRule,
|
|
ulid: ulidRule,
|
|
trim: trimRule,
|
|
email: emailRule,
|
|
alpha: alphaRule,
|
|
ascii: asciiRule,
|
|
notIn: notInRule,
|
|
regex: regexRule,
|
|
escape: escapeRule,
|
|
sameAs: sameAsRule2,
|
|
mobile: mobileRule,
|
|
string: stringRule,
|
|
hexCode: hexCodeRule,
|
|
passport: passportRule,
|
|
endsWith: endsWithRule,
|
|
confirmed: confirmedRule,
|
|
activeUrl: activeUrlRule,
|
|
minLength: minLengthRule3,
|
|
notSameAs: notSameAsRule2,
|
|
maxLength: maxLengthRule3,
|
|
ipAddress: ipAddressRule,
|
|
creditCard: creditCardRule,
|
|
postalCode: postalCodeRule,
|
|
startsWith: startsWithRule,
|
|
toUpperCase: toUpperCaseRule,
|
|
toLowerCase: toLowerCaseRule,
|
|
toCamelCase: toCamelCaseRule,
|
|
fixedLength: fixedLengthRule3,
|
|
coordinates: coordinatesRule,
|
|
normalizeUrl: normalizeUrlRule,
|
|
alphaNumeric: alphaNumericRule,
|
|
normalizeEmail: normalizeEmailRule
|
|
};
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "string";
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.string";
|
|
/**
|
|
* Checks if the value is of string type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
return typeof value === "string";
|
|
};
|
|
constructor(options, validations) {
|
|
super(options, validations || [stringRule()]);
|
|
}
|
|
/**
|
|
* Validates the value to be a valid URL
|
|
*/
|
|
url(...args) {
|
|
return this.use(urlRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be an active URL
|
|
*/
|
|
activeUrl() {
|
|
return this.use(activeUrlRule());
|
|
}
|
|
/**
|
|
* Validates the value to be a valid email address
|
|
*/
|
|
email(...args) {
|
|
return this.use(emailRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid mobile number
|
|
*/
|
|
mobile(...args) {
|
|
return this.use(mobileRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid IP address.
|
|
*/
|
|
ipAddress(version) {
|
|
return this.use(ipAddressRule(version ? { version } : void 0));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid hex color code
|
|
*/
|
|
hexCode() {
|
|
return this.use(hexCodeRule());
|
|
}
|
|
/**
|
|
* Validates the value against a regular expression
|
|
*/
|
|
regex(expression) {
|
|
return this.use(regexRule(expression));
|
|
}
|
|
/**
|
|
* Validates the value to contain only letters
|
|
*/
|
|
alpha(options) {
|
|
return this.use(alphaRule(options));
|
|
}
|
|
/**
|
|
* Validates the value to contain only letters and
|
|
* numbers
|
|
*/
|
|
alphaNumeric(options) {
|
|
return this.use(alphaNumericRule(options));
|
|
}
|
|
/**
|
|
* Enforce a minimum length on a string field
|
|
*/
|
|
minLength(expectedLength) {
|
|
return this.use(minLengthRule3({ min: expectedLength }));
|
|
}
|
|
/**
|
|
* Enforce a maximum length on a string field
|
|
*/
|
|
maxLength(expectedLength) {
|
|
return this.use(maxLengthRule3({ max: expectedLength }));
|
|
}
|
|
/**
|
|
* Enforce a fixed length on a string field
|
|
*/
|
|
fixedLength(expectedLength) {
|
|
return this.use(fixedLengthRule3({ size: expectedLength }));
|
|
}
|
|
/**
|
|
* Ensure the field under validation is confirmed by
|
|
* having another field with the same name.
|
|
*/
|
|
confirmed(options) {
|
|
return this.use(confirmedRule(options));
|
|
}
|
|
/**
|
|
* Trims whitespaces around the string value
|
|
*/
|
|
trim() {
|
|
return this.use(trimRule());
|
|
}
|
|
/**
|
|
* Normalizes the email address
|
|
*/
|
|
normalizeEmail(options) {
|
|
return this.use(normalizeEmailRule(options));
|
|
}
|
|
/**
|
|
* Converts the field value to UPPERCASE.
|
|
*/
|
|
toUpperCase() {
|
|
return this.use(toUpperCaseRule());
|
|
}
|
|
/**
|
|
* Converts the field value to lowercase.
|
|
*/
|
|
toLowerCase() {
|
|
return this.use(toLowerCaseRule());
|
|
}
|
|
/**
|
|
* Converts the field value to camelCase.
|
|
*/
|
|
toCamelCase() {
|
|
return this.use(toCamelCaseRule());
|
|
}
|
|
/**
|
|
* Escape string for HTML entities
|
|
*/
|
|
escape() {
|
|
return this.use(escapeRule());
|
|
}
|
|
/**
|
|
* Normalize a URL
|
|
*/
|
|
normalizeUrl(...args) {
|
|
return this.use(normalizeUrlRule(...args));
|
|
}
|
|
/**
|
|
* Ensure the value starts with the pre-defined substring
|
|
*/
|
|
startsWith(substring) {
|
|
return this.use(startsWithRule({ substring }));
|
|
}
|
|
/**
|
|
* Ensure the value ends with the pre-defined substring
|
|
*/
|
|
endsWith(substring) {
|
|
return this.use(endsWithRule({ substring }));
|
|
}
|
|
/**
|
|
* Ensure the value ends with the pre-defined substring
|
|
*/
|
|
sameAs(otherField) {
|
|
return this.use(sameAsRule2({ otherField }));
|
|
}
|
|
/**
|
|
* Ensure the value ends with the pre-defined substring
|
|
*/
|
|
notSameAs(otherField) {
|
|
return this.use(notSameAsRule2({ otherField }));
|
|
}
|
|
/**
|
|
* Ensure the field's value under validation is a subset of the pre-defined list.
|
|
*/
|
|
in(choices) {
|
|
return this.use(inRule({ choices }));
|
|
}
|
|
/**
|
|
* Ensure the field's value under validation is not inside the pre-defined list.
|
|
*/
|
|
notIn(list) {
|
|
return this.use(notInRule({ list }));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid credit card number
|
|
*/
|
|
creditCard(...args) {
|
|
return this.use(creditCardRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid passport number
|
|
*/
|
|
passport(...args) {
|
|
return this.use(passportRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid postal code
|
|
*/
|
|
postalCode(...args) {
|
|
return this.use(postalCodeRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid UUID
|
|
*/
|
|
uuid(...args) {
|
|
return this.use(uuidRule(...args));
|
|
}
|
|
/**
|
|
* Validates the value to be a valid ULID
|
|
*/
|
|
ulid() {
|
|
return this.use(ulidRule());
|
|
}
|
|
/**
|
|
* Validates the value contains ASCII characters only
|
|
*/
|
|
ascii() {
|
|
return this.use(asciiRule());
|
|
}
|
|
/**
|
|
* Validates the value to be a valid IBAN number
|
|
*/
|
|
iban() {
|
|
return this.use(ibanRule());
|
|
}
|
|
/**
|
|
* Validates the value to be a valid JWT token
|
|
*/
|
|
jwt() {
|
|
return this.use(jwtRule());
|
|
}
|
|
/**
|
|
* Ensure the value is a string with latitude and longitude coordinates
|
|
*/
|
|
coordinates() {
|
|
return this.use(coordinatesRule());
|
|
}
|
|
/**
|
|
* Clones the VineString schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineString(this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/number/rules.ts
|
|
var numberRule = createRule((value, options, field) => {
|
|
const valueAsNumber = options.strict ? value : helpers.asNumber(value);
|
|
if (typeof valueAsNumber !== "number" || Number.isNaN(valueAsNumber) || valueAsNumber === Number.POSITIVE_INFINITY || valueAsNumber === Number.NEGATIVE_INFINITY) {
|
|
field.report(messages.number, "number", field);
|
|
return;
|
|
}
|
|
field.mutate(valueAsNumber, field);
|
|
});
|
|
var minRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value < options.min) {
|
|
field.report(messages.min, "min", field, options);
|
|
}
|
|
});
|
|
var maxRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value > options.max) {
|
|
field.report(messages.max, "max", field, options);
|
|
}
|
|
});
|
|
var rangeRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value < options.min || value > options.max) {
|
|
field.report(messages.range, "range", field, options);
|
|
}
|
|
});
|
|
var positiveRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value < 0) {
|
|
field.report(messages.positive, "positive", field);
|
|
}
|
|
});
|
|
var negativeRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (value >= 0) {
|
|
field.report(messages.negative, "negative", field);
|
|
}
|
|
});
|
|
var decimalRule = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!helpers.isDecimal(String(value), {
|
|
force_decimal: options.range[0] !== 0,
|
|
decimal_digits: options.range.join(",")
|
|
})) {
|
|
field.report(messages.decimal, "decimal", field, { digits: options.range.join("-") });
|
|
}
|
|
});
|
|
var withoutDecimalsRule = createRule((value, _, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!Number.isInteger(value)) {
|
|
field.report(messages.withoutDecimals, "withoutDecimals", field);
|
|
}
|
|
});
|
|
var inRule2 = createRule((value, options, field) => {
|
|
if (!field.isValid) {
|
|
return;
|
|
}
|
|
if (!options.values.includes(value)) {
|
|
field.report(messages["number.in"], "in", field, options);
|
|
}
|
|
});
|
|
|
|
// src/schema/number/main.ts
|
|
var VineNumber = class _VineNumber extends BaseLiteralType {
|
|
/**
|
|
* Default collection of number rules
|
|
*/
|
|
static rules = {
|
|
in: inRule2,
|
|
max: maxRule,
|
|
min: minRule,
|
|
range: rangeRule,
|
|
number: numberRule,
|
|
decimal: decimalRule,
|
|
negative: negativeRule,
|
|
positive: positiveRule,
|
|
withoutDecimals: withoutDecimalsRule
|
|
};
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "number";
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.number";
|
|
/**
|
|
* Checks if the value is of number type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
const valueAsNumber = helpers.asNumber(value);
|
|
return !Number.isNaN(valueAsNumber);
|
|
};
|
|
constructor(options, validations) {
|
|
super(options, validations || [numberRule(options || {})]);
|
|
}
|
|
/**
|
|
* Enforce a minimum value for the number input
|
|
*/
|
|
min(value) {
|
|
return this.use(minRule({ min: value }));
|
|
}
|
|
/**
|
|
* Enforce a maximum value for the number input
|
|
*/
|
|
max(value) {
|
|
return this.use(maxRule({ max: value }));
|
|
}
|
|
/**
|
|
* Enforce value to be within the range of minimum and maximum output.
|
|
*/
|
|
range(value) {
|
|
return this.use(rangeRule({ min: value[0], max: value[1] }));
|
|
}
|
|
/**
|
|
* Enforce the value be a positive number
|
|
*/
|
|
positive() {
|
|
return this.use(positiveRule());
|
|
}
|
|
/**
|
|
* Enforce the value be a negative number
|
|
*/
|
|
negative() {
|
|
return this.use(negativeRule());
|
|
}
|
|
/**
|
|
* Enforce the value to have fixed or range
|
|
* of decimal places
|
|
*/
|
|
decimal(range) {
|
|
return this.use(decimalRule({ range: Array.isArray(range) ? range : [range] }));
|
|
}
|
|
/**
|
|
* Enforce the value to be an integer (aka without decimals)
|
|
*/
|
|
withoutDecimals() {
|
|
return this.use(withoutDecimalsRule());
|
|
}
|
|
/**
|
|
* Clones the VineNumber schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineNumber(this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
/**
|
|
* Enforce the value to be in a list of allowed values
|
|
*/
|
|
in(values) {
|
|
return this.use(inRule2({ values }));
|
|
}
|
|
};
|
|
|
|
// src/schema/boolean/rules.ts
|
|
var booleanRule = createRule((value, options, field) => {
|
|
const valueAsBoolean = options.strict === true ? value : helpers.asBoolean(value);
|
|
if (typeof valueAsBoolean !== "boolean") {
|
|
field.report(messages.boolean, "boolean", field);
|
|
return;
|
|
}
|
|
field.mutate(valueAsBoolean, field);
|
|
});
|
|
|
|
// src/schema/boolean/main.ts
|
|
var VineBoolean = class _VineBoolean extends BaseLiteralType {
|
|
/**
|
|
* Default collection of boolean rules
|
|
*/
|
|
static rules = {
|
|
boolean: booleanRule
|
|
};
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "boolean";
|
|
/**
|
|
* The property must be implemented for "unionOfTypes"
|
|
*/
|
|
[UNIQUE_NAME] = "vine.boolean";
|
|
/**
|
|
* Checks if the value is of boolean type. The method must be
|
|
* implemented for "unionOfTypes"
|
|
*/
|
|
[IS_OF_TYPE] = (value) => {
|
|
const valueAsBoolean = this.options.strict === true ? value : helpers.asBoolean(value);
|
|
return typeof valueAsBoolean === "boolean";
|
|
};
|
|
constructor(options, validations) {
|
|
super(options, validations || [booleanRule(options || {})]);
|
|
}
|
|
/**
|
|
* Clones the VineBoolean schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineBoolean(this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/literal/rules.ts
|
|
var equalsRule2 = createRule((value, options, field) => {
|
|
let input = value;
|
|
if (typeof options.expectedValue === "boolean") {
|
|
input = helpers.asBoolean(value);
|
|
} else if (typeof options.expectedValue === "number") {
|
|
input = helpers.asNumber(value);
|
|
}
|
|
if (input !== options.expectedValue) {
|
|
field.report(messages.literal, "literal", field, options);
|
|
return;
|
|
}
|
|
field.mutate(input, field);
|
|
});
|
|
|
|
// src/schema/literal/main.ts
|
|
var VineLiteral = class _VineLiteral extends BaseLiteralType {
|
|
/**
|
|
* Default collection of literal rules
|
|
*/
|
|
static rules = {
|
|
equals: equalsRule2
|
|
};
|
|
#value;
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "literal";
|
|
constructor(value, options, validations) {
|
|
super(options, validations || [equalsRule2({ expectedValue: value })]);
|
|
this.#value = value;
|
|
}
|
|
/**
|
|
* Clones the VineLiteral schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineLiteral(this.#value, this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/accepted/rules.ts
|
|
var ACCEPTED_VALUES = ["on", "1", "yes", "true", true, 1];
|
|
var acceptedRule = createRule((value, _, field) => {
|
|
if (!ACCEPTED_VALUES.includes(value)) {
|
|
field.report(messages.accepted, "accepted", field);
|
|
}
|
|
});
|
|
|
|
// src/schema/accepted/main.ts
|
|
var VineAccepted = class _VineAccepted extends BaseLiteralType {
|
|
/**
|
|
* Default collection of accepted rules
|
|
*/
|
|
static rules = {
|
|
accepted: acceptedRule
|
|
};
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "checkbox";
|
|
constructor(options, validations) {
|
|
super(options, validations || [acceptedRule()]);
|
|
}
|
|
/**
|
|
* Clones the VineAccepted schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineAccepted(this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/object/group.ts
|
|
var ObjectGroup = class _ObjectGroup {
|
|
#conditionals;
|
|
#otherwiseCallback = (_, field) => {
|
|
field.report(messages.unionGroup, "unionGroup", field);
|
|
};
|
|
constructor(conditionals) {
|
|
this.#conditionals = conditionals;
|
|
}
|
|
/**
|
|
* Clones the ObjectGroup schema type.
|
|
*/
|
|
clone() {
|
|
const cloned = new _ObjectGroup(this.#conditionals);
|
|
cloned.otherwise(this.#otherwiseCallback);
|
|
return cloned;
|
|
}
|
|
/**
|
|
* Define a fallback method to invoke when all of the group conditions
|
|
* fail. You may use this method to report an error.
|
|
*/
|
|
otherwise(callback) {
|
|
this.#otherwiseCallback = callback;
|
|
return this;
|
|
}
|
|
/**
|
|
* Compiles the group
|
|
*/
|
|
[PARSE](refs, options) {
|
|
return {
|
|
type: "group",
|
|
elseConditionalFnRefId: refs.trackConditional(this.#otherwiseCallback),
|
|
conditions: this.#conditionals.map((conditional) => conditional[PARSE](refs, options))
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/object/conditional.ts
|
|
var GroupConditional = class {
|
|
/**
|
|
* Properties to merge when conditonal is true
|
|
*/
|
|
#properties;
|
|
/**
|
|
* Conditional to evaluate
|
|
*/
|
|
#conditional;
|
|
constructor(conditional, properties) {
|
|
this.#properties = properties;
|
|
this.#conditional = conditional;
|
|
}
|
|
/**
|
|
* Compiles to a union conditional
|
|
*/
|
|
[PARSE](refs, options) {
|
|
return {
|
|
schema: {
|
|
type: "sub_object",
|
|
properties: Object.keys(this.#properties).map((property) => {
|
|
return this.#properties[property][PARSE](property, refs, options);
|
|
}),
|
|
groups: []
|
|
// Compiler allows nested groups, but we are not implementing it
|
|
},
|
|
conditionalFnRefId: refs.trackConditional(this.#conditional)
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/object/group_builder.ts
|
|
function group(conditionals) {
|
|
return new ObjectGroup(conditionals);
|
|
}
|
|
group.if = function groupIf(conditon, properties) {
|
|
return new GroupConditional(conditon, properties);
|
|
};
|
|
group.else = function groupElse(properties) {
|
|
return new GroupConditional(() => true, properties);
|
|
};
|
|
|
|
// src/schema/enum/native_enum.ts
|
|
var VineNativeEnum = class _VineNativeEnum extends BaseLiteralType {
|
|
/**
|
|
* Default collection of enum rules
|
|
*/
|
|
static rules = {
|
|
enum: enumRule
|
|
};
|
|
#values;
|
|
/**
|
|
* The subtype of the literal schema field
|
|
*/
|
|
[SUBTYPE] = "enum";
|
|
constructor(values, options, validations) {
|
|
super(options, validations || [enumRule({ choices: Object.values(values) })]);
|
|
this.#values = values;
|
|
}
|
|
/**
|
|
* Clones the VineNativeEnum schema type. The applied options
|
|
* and validations are copied to the new instance
|
|
*/
|
|
clone() {
|
|
return new _VineNativeEnum(this.#values, this.cloneOptions(), this.cloneValidations());
|
|
}
|
|
};
|
|
|
|
// src/schema/union_of_types/main.ts
|
|
import camelcase8 from "camelcase";
|
|
var VineUnionOfTypes = class _VineUnionOfTypes {
|
|
#schemas;
|
|
#otherwiseCallback = (_, field) => {
|
|
field.report(messages.unionOfTypes, "unionOfTypes", field);
|
|
};
|
|
constructor(schemas) {
|
|
this.#schemas = schemas;
|
|
}
|
|
/**
|
|
* Define a fallback method to invoke when all of the union conditions
|
|
* fail. You may use this method to report an error.
|
|
*/
|
|
otherwise(callback) {
|
|
this.#otherwiseCallback = callback;
|
|
return this;
|
|
}
|
|
/**
|
|
* Clones the VineUnionOfTypes schema type.
|
|
*/
|
|
clone() {
|
|
const cloned = new _VineUnionOfTypes(this.#schemas);
|
|
cloned.otherwise(this.#otherwiseCallback);
|
|
return cloned;
|
|
}
|
|
/**
|
|
* Compiles to a union
|
|
*/
|
|
[PARSE](propertyName, refs, options) {
|
|
return {
|
|
type: "union",
|
|
fieldName: propertyName,
|
|
propertyName: options.toCamelCase ? camelcase8(propertyName) : propertyName,
|
|
elseConditionalFnRefId: refs.trackConditional(this.#otherwiseCallback),
|
|
conditions: this.#schemas.map((schema) => {
|
|
return {
|
|
conditionalFnRefId: refs.trackConditional((value, field) => {
|
|
return schema[IS_OF_TYPE](value, field);
|
|
}),
|
|
schema: schema[PARSE](propertyName, refs, options)
|
|
};
|
|
})
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/schema/builder.ts
|
|
var SchemaBuilder = class extends Macroable3 {
|
|
/**
|
|
* Define a sub-object as a union
|
|
*/
|
|
group = group;
|
|
/**
|
|
* Define a union value
|
|
*/
|
|
union = union;
|
|
/**
|
|
* Define a string value
|
|
*/
|
|
string() {
|
|
return new VineString();
|
|
}
|
|
/**
|
|
* Define a boolean value
|
|
*/
|
|
boolean(options) {
|
|
return new VineBoolean(options);
|
|
}
|
|
/**
|
|
* Validate a checkbox to be checked
|
|
*/
|
|
accepted() {
|
|
return new VineAccepted();
|
|
}
|
|
/**
|
|
* Define a number value
|
|
*/
|
|
number(options) {
|
|
return new VineNumber(options);
|
|
}
|
|
/**
|
|
* Define a datetime value
|
|
*/
|
|
date(options) {
|
|
return new VineDate(options);
|
|
}
|
|
/**
|
|
* Define a schema type in which the input value
|
|
* matches the pre-defined value
|
|
*/
|
|
literal(value) {
|
|
return new VineLiteral(value);
|
|
}
|
|
/**
|
|
* Define an object with known properties. You may call "allowUnknownProperties"
|
|
* to merge unknown properties.
|
|
*/
|
|
object(properties) {
|
|
return new VineObject(properties);
|
|
}
|
|
/**
|
|
* Define an array field and validate its children elements.
|
|
*/
|
|
array(schema) {
|
|
return new VineArray(schema);
|
|
}
|
|
/**
|
|
* Define an array field with known length and each children
|
|
* element may have its own schema.
|
|
*/
|
|
tuple(schemas) {
|
|
return new VineTuple(schemas);
|
|
}
|
|
/**
|
|
* Define an object field with key-value pair. The keys in
|
|
* a record are unknown and values can be of a specific
|
|
* schema type.
|
|
*/
|
|
record(schema) {
|
|
return new VineRecord(schema);
|
|
}
|
|
enum(values) {
|
|
if (Array.isArray(values) || typeof values === "function") {
|
|
return new VineEnum(values);
|
|
}
|
|
return new VineNativeEnum(values);
|
|
}
|
|
/**
|
|
* Allow the field value to be anything
|
|
*/
|
|
any() {
|
|
return new VineAny();
|
|
}
|
|
/**
|
|
* Define a union of unique schema types.
|
|
*/
|
|
unionOfTypes(schemas) {
|
|
const schemasInUse = /* @__PURE__ */ new Set();
|
|
schemas.forEach((schema) => {
|
|
if (!schema[IS_OF_TYPE] || !schema[UNIQUE_NAME]) {
|
|
throw new Error(
|
|
`Cannot use "${schema.constructor.name}". The schema type is not compatible for use with "vine.unionOfTypes"`
|
|
);
|
|
}
|
|
if (schemasInUse.has(schema[UNIQUE_NAME])) {
|
|
throw new Error(
|
|
`Cannot use duplicate schema "${schema[UNIQUE_NAME]}". "vine.unionOfTypes" needs distinct schema types only`
|
|
);
|
|
}
|
|
schemasInUse.add(schema[UNIQUE_NAME]);
|
|
});
|
|
schemasInUse.clear();
|
|
return new VineUnionOfTypes(schemas);
|
|
}
|
|
};
|
|
|
|
// src/vine/validator.ts
|
|
import { Compiler, refsBuilder } from "@vinejs/compiler";
|
|
var COMPILER_ERROR_MESSAGES = {
|
|
required: messages.required,
|
|
array: messages.array,
|
|
object: messages.object
|
|
};
|
|
var VineValidator = class {
|
|
/**
|
|
* Reference to the compiled schema
|
|
*/
|
|
#compiled;
|
|
/**
|
|
* Messages provider to use on the validator
|
|
*/
|
|
messagesProvider;
|
|
/**
|
|
* Error reporter to use on the validator
|
|
*/
|
|
errorReporter;
|
|
/**
|
|
* Parses schema to compiler nodes.
|
|
*/
|
|
#parse(schema) {
|
|
const refs = refsBuilder();
|
|
return {
|
|
compilerNode: {
|
|
type: "root",
|
|
schema: schema[PARSE]("", refs, { toCamelCase: false })
|
|
},
|
|
refs: refs.toJSON()
|
|
};
|
|
}
|
|
constructor(schema, options) {
|
|
const { compilerNode, refs } = this.#parse(schema);
|
|
this.#compiled = { schema: compilerNode, refs };
|
|
const metaDataValidator = options.metaDataValidator;
|
|
const validateFn = new Compiler(compilerNode, {
|
|
convertEmptyStringsToNull: options.convertEmptyStringsToNull,
|
|
messages: COMPILER_ERROR_MESSAGES
|
|
}).compile();
|
|
this.errorReporter = options.errorReporter;
|
|
this.messagesProvider = options.messagesProvider;
|
|
if (metaDataValidator) {
|
|
this.validate = (data, validateOptions) => {
|
|
let normalizedOptions = validateOptions ?? {};
|
|
const meta = normalizedOptions.meta ?? {};
|
|
const errorReporter = normalizedOptions.errorReporter ?? this.errorReporter;
|
|
const messagesProvider = normalizedOptions.messagesProvider ?? this.messagesProvider;
|
|
metaDataValidator(meta);
|
|
return validateFn(data, meta, refs, messagesProvider, errorReporter());
|
|
};
|
|
} else {
|
|
this.validate = (data, validateOptions) => {
|
|
let normalizedOptions = validateOptions ?? {};
|
|
const meta = normalizedOptions.meta ?? {};
|
|
const errorReporter = normalizedOptions.errorReporter ?? this.errorReporter;
|
|
const messagesProvider = normalizedOptions.messagesProvider ?? this.messagesProvider;
|
|
return validateFn(data, meta, refs, messagesProvider, errorReporter());
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Performs validation without throwing the validation
|
|
* exception. Instead, the validation errors are
|
|
* returned as the first argument.
|
|
*
|
|
*
|
|
* ```ts
|
|
* await validator.tryValidate(data)
|
|
* await validator.tryValidate(data, { meta: {} })
|
|
*
|
|
* await validator.tryValidate(data, {
|
|
* meta: { userId: auth.user.id },
|
|
* errorReporter,
|
|
* messagesProvider
|
|
* })
|
|
* ```
|
|
*
|
|
*/
|
|
async tryValidate(data, ...[options]) {
|
|
try {
|
|
const result = await this.validate(data, options);
|
|
return [null, result];
|
|
} catch (error) {
|
|
if (error instanceof ValidationError) {
|
|
return [error, null];
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Returns the compiled schema and refs.
|
|
*/
|
|
toJSON() {
|
|
const { schema, refs } = this.#compiled;
|
|
return {
|
|
schema: structuredClone(schema),
|
|
refs
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/vine/main.ts
|
|
var Vine = class extends SchemaBuilder {
|
|
/**
|
|
* Messages provider to use on the validator
|
|
*/
|
|
messagesProvider = new SimpleMessagesProvider(messages, fields);
|
|
/**
|
|
* Error reporter to use on the validator
|
|
*/
|
|
errorReporter = () => new SimpleErrorReporter();
|
|
/**
|
|
* Control whether or not to convert empty strings to null
|
|
*/
|
|
convertEmptyStringsToNull = false;
|
|
/**
|
|
* Helpers to perform type-checking or cast types keeping
|
|
* HTML forms serialization behavior in mind.
|
|
*/
|
|
helpers = helpers;
|
|
/**
|
|
* Convert a validation function to a Vine schema rule
|
|
*/
|
|
createRule = createRule;
|
|
/**
|
|
* Pre-compiles a schema into a validation function.
|
|
*
|
|
* ```ts
|
|
* const validate = vine.compile(schema)
|
|
* await validate({ data })
|
|
* ```
|
|
*/
|
|
compile(schema) {
|
|
return new VineValidator(schema, {
|
|
convertEmptyStringsToNull: this.convertEmptyStringsToNull,
|
|
messagesProvider: this.messagesProvider,
|
|
errorReporter: this.errorReporter
|
|
});
|
|
}
|
|
/**
|
|
* Define a callback to validate the metadata given to the validator
|
|
* at runtime
|
|
*/
|
|
withMetaData(callback) {
|
|
return {
|
|
compile: (schema) => {
|
|
return new VineValidator(schema, {
|
|
convertEmptyStringsToNull: this.convertEmptyStringsToNull,
|
|
messagesProvider: this.messagesProvider,
|
|
errorReporter: this.errorReporter,
|
|
metaDataValidator: callback
|
|
});
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Validate data against a schema. Optionally, you can define
|
|
* error messages, fields, a custom messages provider,
|
|
* or an error reporter.
|
|
*
|
|
* ```ts
|
|
* await vine.validate({ schema, data })
|
|
* await vine.validate({ schema, data, messages, fields })
|
|
*
|
|
* await vine.validate({ schema, data, messages, fields }, {
|
|
* errorReporter
|
|
* })
|
|
* ```
|
|
*/
|
|
validate(options) {
|
|
const validator = this.compile(options.schema);
|
|
return validator.validate(options.data, options);
|
|
}
|
|
/**
|
|
* Validate data against a schema without throwing the
|
|
* "ValidationError" exception. Instead the validation
|
|
* errors are returned within the return value.
|
|
*
|
|
* ```ts
|
|
* await vine.tryValidate({ schema, data })
|
|
* await vine.tryValidate({ schema, data, messages, fields })
|
|
*
|
|
* await vine.tryValidate({ schema, data, messages, fields }, {
|
|
* errorReporter
|
|
* })
|
|
* ```
|
|
*/
|
|
tryValidate(options) {
|
|
const validator = this.compile(options.schema);
|
|
return validator.tryValidate(options.data, options);
|
|
}
|
|
};
|
|
|
|
// index.ts
|
|
var vine = new Vine();
|
|
var index_default = vine;
|
|
|
|
export {
|
|
symbols_exports,
|
|
BaseLiteralType,
|
|
VineAny,
|
|
VineEnum,
|
|
VineDate,
|
|
VineUnion,
|
|
BaseModifiersType2 as BaseModifiersType,
|
|
BaseType,
|
|
VineTuple,
|
|
VineArray,
|
|
VineObject,
|
|
VineRecord,
|
|
VineString,
|
|
VineNumber,
|
|
VineBoolean,
|
|
VineLiteral,
|
|
VineAccepted,
|
|
VineNativeEnum,
|
|
VineValidator,
|
|
Vine,
|
|
index_default
|
|
};
|