Socket
Socket
Sign inDemoInstall

schema-typed

Package Overview
Dependencies
2
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.1.3 to 2.2.0

es/utils/pathTransform.d.ts

12

CHANGELOG.md

@@ -0,1 +1,13 @@

# [2.2.0](https://github.com/rsuite/schema-typed/compare/2.1.3...2.2.0) (2024-04-11)
### Features
* add support for `equalTo` and `proxy` ([#78](https://github.com/rsuite/schema-typed/issues/78)) ([d9f0e55](https://github.com/rsuite/schema-typed/commit/d9f0e555cf532731839584b0c036648001fe0503))
* add support for `label` method ([#77](https://github.com/rsuite/schema-typed/issues/77)) ([9ff16c3](https://github.com/rsuite/schema-typed/commit/9ff16c346d6f13caabd4910a7d920c1c11eced18))
* **Schema:** support nested object check with `checkForField` and `checkForFieldAsync` ([#76](https://github.com/rsuite/schema-typed/issues/76)) ([e315aec](https://github.com/rsuite/schema-typed/commit/e315aec657ee230f2cf235861e05b37a7eedd274))
* **StringType:** add alllowMailto option to isURL rule ([#72](https://github.com/rsuite/schema-typed/issues/72)) ([349dc42](https://github.com/rsuite/schema-typed/commit/349dc429b51db89e7b261ed24aa006435c501685))
## [2.1.3](https://github.com/rsuite/schema-typed/compare/2.1.2...2.1.3) (2023-05-06)

@@ -2,0 +14,0 @@

8

es/ArrayType.js

@@ -52,7 +52,7 @@ import { MixedType } from './MixedType';

super.pushRule({
onValid: (items, data, filedName) => {
onValid: (items, data, fieldName) => {
const checkResults = items.map((value, index) => {
const name = Array.isArray(filedName)
? [...filedName, `[${index}]`]
: [filedName, `[${index}]`];
const name = Array.isArray(fieldName)
? [...fieldName, `[${index}]`]
: [fieldName, `[${index}]`];
return type.check(value, data, name);

@@ -59,0 +59,0 @@ });

@@ -5,2 +5,3 @@ declare const _default: {

isRequiredOrEmpty: string;
equalTo: string;
};

@@ -7,0 +8,0 @@ array: {

export default {
mixed: {
isRequired: '${name} is a required field',
isRequiredOrEmpty: '${name} is a required field'
isRequiredOrEmpty: '${name} is a required field',
equalTo: '${name} must be the same as ${toFieldName}'
},

@@ -6,0 +7,0 @@ array: {

@@ -1,5 +0,17 @@

import { SchemaDeclaration, CheckResult, ValidCallbackType, AsyncValidCallbackType, RuleType, ErrorMessageType, TypeName } from './types';
import { SchemaDeclaration, CheckResult, ValidCallbackType, AsyncValidCallbackType, RuleType, ErrorMessageType, TypeName, PlainObject } from './types';
import { MixedTypeLocale } from './locales';
type ProxyOptions = {
checkIfValueExists?: boolean;
};
export declare const schemaSpecKey = "objectTypeSchemaSpec";
/**
* Get the field type from the schema object
*/
export declare function getFieldType(schemaSpec: any, fieldName: string, nestedObject?: boolean): any;
/**
* Get the field value from the data object
*/
export declare function getFieldValue(data: PlainObject, fieldName: string, nestedObject?: boolean): any;
export declare class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L = any> {
readonly typeName?: string;
readonly $typeName?: string;
protected required: boolean;

@@ -11,11 +23,14 @@ protected requiredMessage: E | string;

protected priorityRules: RuleType<ValueType, DataType, E | string>[];
schemaSpec: SchemaDeclaration<DataType, E>;
protected fieldLabel?: string;
$schemaSpec: SchemaDeclaration<DataType, E>;
value: any;
locale: L & MixedTypeLocale;
otherFields: string[];
proxyOptions: ProxyOptions;
constructor(name?: TypeName);
setSchemaOptions(schemaSpec: SchemaDeclaration<DataType, E>, value: any): void;
check(value?: ValueType, data?: DataType, fieldName?: string | string[]): CheckResult<string | E, import("./types").PlainObject<any>>;
checkAsync(value?: ValueType, data?: DataType, fieldName?: string | string[]): Promise<CheckResult<E | string>>;
check(value?: any, data?: DataType, fieldName?: string | string[]): CheckResult<string | E, PlainObject<any>>;
checkAsync(value?: any, data?: DataType, fieldName?: string | string[]): Promise<CheckResult<E | string>>;
protected pushRule(rule: RuleType<ValueType, DataType, E | string>): void;
addRule(onValid: ValidCallbackType<ValueType, DataType, E | string>, errorMessage?: E | string, priority?: boolean): this;
addRule(onValid: ValidCallbackType<ValueType, DataType, E | string>, errorMessage?: E | string | (() => E | string), priority?: boolean): this;
addAsyncRule(onValid: AsyncValidCallbackType<ValueType, DataType, E | string>, errorMessage?: E | string, priority?: boolean): this;

@@ -26,10 +41,55 @@ isRequired(errorMessage?: E | string, trim?: boolean): this;

* Define data verification rules based on conditions.
* @param validator
* @param condition
* @example
* MixedType().when(schema => {
* return schema.filed1.check() ? NumberType().min(5) : NumberType().min(0);
*
* ```js
* SchemaModel({
* option: StringType().isOneOf(['a', 'b', 'other']),
* other: StringType().when(schema => {
* const { value } = schema.option;
* return value === 'other' ? StringType().isRequired('Other required') : StringType();
* })
* });
* ```
*/
when(condition: (schemaSpec: SchemaDeclaration<DataType, E>) => MixedType): this;
/**
* Check if the value is equal to the value of another field.
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired(),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
equalTo(fieldName: string, errorMessage?: E | string): this;
/**
* After the field verification passes, proxy verification of other fields.
* @param options.checkIfValueExists When the value of other fields exists, the verification is performed (default: false)
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired().proxy(['confirmPassword']),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
proxy(fieldNames: string[], options?: ProxyOptions): this;
/**
* Overrides the key name in error messages.
*
* @example
* ```js
* SchemaModel({
* first_name: StringType().label('First name'),
* age: NumberType().label('Age')
* });
* ```
*/
label(label: string): this;
}
export default function getMixedType<DataType = any, E = ErrorMessageType>(): MixedType<DataType, E, string, any>;
export {};

@@ -1,3 +0,21 @@

import { checkRequired, createValidator, createValidatorAsync, isEmpty, formatErrorMessage } from './utils';
import { checkRequired, createValidator, createValidatorAsync, isEmpty, shallowEqual, formatErrorMessage, get } from './utils';
import { joinName } from './utils/formatErrorMessage';
import locales from './locales';
export const schemaSpecKey = 'objectTypeSchemaSpec';
/**
* Get the field type from the schema object
*/
export function getFieldType(schemaSpec, fieldName, nestedObject) {
if (nestedObject) {
const namePath = fieldName.split('.').join(`.${schemaSpecKey}.`);
return get(schemaSpec, namePath);
}
return schemaSpec === null || schemaSpec === void 0 ? void 0 : schemaSpec[fieldName];
}
/**
* Get the field value from the data object
*/
export function getFieldValue(data, fieldName, nestedObject) {
return nestedObject ? get(data, fieldName) : data === null || data === void 0 ? void 0 : data[fieldName];
}
export class MixedType {

@@ -11,7 +29,10 @@ constructor(name) {

this.priorityRules = [];
this.typeName = name;
// The field name that depends on the verification of other fields
this.otherFields = [];
this.proxyOptions = {};
this.$typeName = name;
this.locale = Object.assign(name ? locales[name] : {}, locales.mixed);
}
setSchemaOptions(schemaSpec, value) {
this.schemaSpec = schemaSpec;
this.$schemaSpec = schemaSpec;
this.value = value;

@@ -23,6 +44,8 @@ }

hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || joinName(fieldName)
})
};
}
const validator = createValidator(data, fieldName);
const validator = createValidator(data, fieldName, this.fieldLabel);
const checkStatus = validator(value, this.priorityRules);

@@ -41,6 +64,8 @@ if (checkStatus) {

hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || joinName(fieldName)
})
});
}
const validator = createValidatorAsync(data, fieldName);
const validator = createValidatorAsync(data, fieldName, this.fieldLabel);
return new Promise(resolve => validator(value, this.priorityRules)

@@ -104,14 +129,74 @@ .then((checkStatus) => {

* Define data verification rules based on conditions.
* @param validator
* @param condition
* @example
* MixedType().when(schema => {
* return schema.filed1.check() ? NumberType().min(5) : NumberType().min(0);
*
* ```js
* SchemaModel({
* option: StringType().isOneOf(['a', 'b', 'other']),
* other: StringType().when(schema => {
* const { value } = schema.option;
* return value === 'other' ? StringType().isRequired('Other required') : StringType();
* })
* });
* ```
*/
when(condition) {
this.addRule((value, data, filedName) => {
return condition(this.schemaSpec).check(value, data, filedName);
this.addRule((value, data, fieldName) => {
return condition(this.$schemaSpec).check(value, data, fieldName);
}, undefined, true);
return this;
}
/**
* Check if the value is equal to the value of another field.
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired(),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
equalTo(fieldName, errorMessage = this.locale.equalTo) {
const errorMessageFunc = () => {
const type = getFieldType(this.$schemaSpec, fieldName, true);
return formatErrorMessage(errorMessage, { toFieldName: (type === null || type === void 0 ? void 0 : type.fieldLabel) || fieldName });
};
this.addRule((value, data) => {
return shallowEqual(value, get(data, fieldName));
}, errorMessageFunc);
return this;
}
/**
* After the field verification passes, proxy verification of other fields.
* @param options.checkIfValueExists When the value of other fields exists, the verification is performed (default: false)
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired().proxy(['confirmPassword']),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
proxy(fieldNames, options) {
this.otherFields = fieldNames;
this.proxyOptions = options || {};
return this;
}
/**
* Overrides the key name in error messages.
*
* @example
* ```js
* SchemaModel({
* first_name: StringType().label('First name'),
* age: NumberType().label('Age')
* });
* ```
*/
label(label) {
this.fieldLabel = label;
return this;
}
}

@@ -118,0 +203,0 @@ export default function getMixedType() {

@@ -1,6 +0,6 @@

import { MixedType } from './MixedType';
import { MixedType, schemaSpecKey } from './MixedType';
import { PlainObject, SchemaDeclaration, CheckResult, ErrorMessageType } from './types';
import { ObjectTypeLocale } from './locales';
export declare class ObjectType<DataType = any, E = ErrorMessageType> extends MixedType<PlainObject, DataType, E, ObjectTypeLocale> {
objectTypeSchemaSpec: SchemaDeclaration<DataType, E>;
[schemaSpecKey]: SchemaDeclaration<DataType, E>;
constructor(errorMessage?: E | string);

@@ -7,0 +7,0 @@ check(value?: PlainObject, data?: DataType, fieldName?: string | string[]): CheckResult<string | E, DataType>;

@@ -1,3 +0,3 @@

import { MixedType } from './MixedType';
import { createValidator, createValidatorAsync, checkRequired, isEmpty } from './utils';
import { MixedType, schemaSpecKey } from './MixedType';
import { createValidator, createValidatorAsync, checkRequired, isEmpty, formatErrorMessage } from './utils';
export class ObjectType extends MixedType {

@@ -12,11 +12,16 @@ constructor(errorMessage) {

check(value = this.value, data, fieldName) {
const check = (value, data, type) => {
const check = (value, data, type, childFieldKey) => {
if (type.required && !checkRequired(value, type.trim, type.emptyAllowed)) {
return { hasError: true, errorMessage: type.requiredMessage };
return {
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage || this.locale.isRequired, {
name: type.fieldLabel || childFieldKey || fieldName
})
};
}
if (type.objectTypeSchemaSpec && typeof value === 'object') {
if (type[schemaSpecKey] && typeof value === 'object') {
const checkResultObject = {};
let hasError = false;
Object.entries(type.objectTypeSchemaSpec).forEach(([k, v]) => {
const checkResult = check(value[k], value, v);
Object.entries(type[schemaSpecKey]).forEach(([k, v]) => {
const checkResult = check(value[k], value, v, k);
if (checkResult === null || checkResult === void 0 ? void 0 : checkResult.hasError) {

@@ -29,3 +34,3 @@ hasError = true;

}
const validator = createValidator(data, fieldName);
const validator = createValidator(data, childFieldKey || fieldName, type.fieldLabel);
const checkStatus = validator(value, type.priorityRules);

@@ -43,21 +48,30 @@ if (checkStatus) {

checkAsync(value = this.value, data, fieldName) {
const check = (value, data, type) => {
const check = (value, data, type, childFieldKey) => {
if (type.required && !checkRequired(value, type.trim, type.emptyAllowed)) {
return Promise.resolve({ hasError: true, errorMessage: this.requiredMessage });
return Promise.resolve({
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage || this.locale.isRequired, {
name: type.fieldLabel || childFieldKey || fieldName
})
});
}
const validator = createValidatorAsync(data, fieldName);
const validator = createValidatorAsync(data, childFieldKey || fieldName, type.fieldLabel);
return new Promise(resolve => {
if (type.objectTypeSchemaSpec && typeof value === 'object') {
if (type[schemaSpecKey] && typeof value === 'object') {
const checkResult = {};
const checkAll = [];
const keys = [];
Object.entries(type.objectTypeSchemaSpec).forEach(([k, v]) => {
checkAll.push(check(value[k], value, v));
Object.entries(type[schemaSpecKey]).forEach(([k, v]) => {
checkAll.push(check(value[k], value, v, k));
keys.push(k);
});
return Promise.all(checkAll).then(values => {
let hasError = false;
values.forEach((v, index) => {
if (v === null || v === void 0 ? void 0 : v.hasError) {
hasError = true;
}
checkResult[keys[index]] = v;
});
resolve({ object: checkResult });
resolve({ hasError, object: checkResult });
});

@@ -95,3 +109,3 @@ }

shape(fields) {
this.objectTypeSchemaSpec = fields;
this[schemaSpecKey] = fields;
return this;

@@ -98,0 +112,0 @@ }

import { SchemaDeclaration, SchemaCheckResult, CheckResult, PlainObject } from './types';
interface CheckOptions {
/**
* Check for nested object
*/
nestedObject?: boolean;
}
export declare class Schema<DataType = any, ErrorMsgType = string> {
readonly spec: SchemaDeclaration<DataType, ErrorMsgType>;
readonly $spec: SchemaDeclaration<DataType, ErrorMsgType>;
private data;
private checkResult;
constructor(schema: SchemaDeclaration<DataType, ErrorMsgType>);
getFieldType<T extends keyof DataType>(fieldName: T): SchemaDeclaration<DataType, ErrorMsgType>[T];
private getFieldType;
private setFieldCheckResult;
private setSchemaOptionsForAllType;
/**
* Get the check result of the schema
* @returns CheckResult<ErrorMsgType | string>
*/
getCheckResult(path?: string, result?: SchemaCheckResult<DataType, ErrorMsgType>): CheckResult<ErrorMsgType | string>;
/**
* Get the error messages of the schema
*/
getErrorMessages(path?: string, result?: SchemaCheckResult<DataType, ErrorMsgType>): (string | ErrorMsgType)[];
/**
* Get all the keys of the schema
*/
getKeys(): string[];
setSchemaOptionsForAllType(data: PlainObject): void;
checkForField<T extends keyof DataType>(fieldName: T, data: DataType): CheckResult<string | ErrorMsgType, PlainObject<any>>;
checkForFieldAsync<T extends keyof DataType>(fieldName: T, data: DataType): Promise<CheckResult<ErrorMsgType | string>>;
checkForField<T extends keyof DataType>(fieldName: T, data: DataType, options?: CheckOptions): CheckResult<ErrorMsgType | string>;
checkForFieldAsync<T extends keyof DataType>(fieldName: T, data: DataType, options?: CheckOptions): Promise<CheckResult<ErrorMsgType | string>>;
check<T extends keyof DataType>(data: DataType): SchemaCheckResult<DataType, ErrorMsgType>;

@@ -18,1 +38,2 @@ checkAsync<T extends keyof DataType>(data: DataType): Promise<SchemaCheckResult<DataType, ErrorMsgType>>;

}
export {};

@@ -0,11 +1,18 @@

import { getFieldType, getFieldValue } from './MixedType';
import { set, get, isEmpty, pathTransform } from './utils';
export class Schema {
constructor(schema) {
this.spec = schema;
this.checkResult = {};
this.$spec = schema;
}
getFieldType(fieldName) {
var _a;
return (_a = this.spec) === null || _a === void 0 ? void 0 : _a[fieldName];
getFieldType(fieldName, nestedObject) {
return getFieldType(this.$spec, fieldName, nestedObject);
}
getKeys() {
return Object.keys(this.spec);
setFieldCheckResult(fieldName, checkResult, nestedObject) {
if (nestedObject) {
const namePath = fieldName.split('.').join('.object.');
set(this.checkResult, namePath, checkResult);
return;
}
this.checkResult[fieldName] = checkResult;
}

@@ -16,10 +23,50 @@ setSchemaOptionsForAllType(data) {

}
Object.entries(this.spec).forEach(([key, type]) => {
type.setSchemaOptions(this.spec, data === null || data === void 0 ? void 0 : data[key]);
Object.entries(this.$spec).forEach(([key, type]) => {
type.setSchemaOptions(this.$spec, data === null || data === void 0 ? void 0 : data[key]);
});
this.data = data;
}
checkForField(fieldName, data) {
/**
* Get the check result of the schema
* @returns CheckResult<ErrorMsgType | string>
*/
getCheckResult(path, result = this.checkResult) {
if (path) {
return (result === null || result === void 0 ? void 0 : result[path]) || get(result, pathTransform(path)) || { hasError: false };
}
return result;
}
/**
* Get the error messages of the schema
*/
getErrorMessages(path, result = this.checkResult) {
let messages = [];
if (path) {
const { errorMessage, object, array } = (result === null || result === void 0 ? void 0 : result[path]) || get(result, pathTransform(path)) || {};
if (errorMessage) {
messages = [errorMessage];
}
else if (object) {
messages = Object.keys(object).map(key => { var _a; return (_a = object[key]) === null || _a === void 0 ? void 0 : _a.errorMessage; });
}
else if (array) {
messages = array.map(item => item === null || item === void 0 ? void 0 : item.errorMessage);
}
}
else {
messages = Object.keys(result).map(key => { var _a; return (_a = result[key]) === null || _a === void 0 ? void 0 : _a.errorMessage; });
}
return messages.filter(Boolean);
}
/**
* Get all the keys of the schema
*/
getKeys() {
return Object.keys(this.$spec);
}
checkForField(fieldName, data, options = {}) {
var _a;
this.setSchemaOptionsForAllType(data);
const fieldChecker = this.spec[fieldName];
const { nestedObject } = options;
const fieldChecker = this.getFieldType(fieldName, nestedObject);
if (!fieldChecker) {

@@ -29,7 +76,24 @@ // fieldValue can be anything if no schema defined

}
return fieldChecker.check(data[fieldName], data, fieldName);
const fieldValue = getFieldValue(data, fieldName, nestedObject);
const checkResult = fieldChecker.check(fieldValue, data, fieldName);
this.setFieldCheckResult(fieldName, checkResult, nestedObject);
if (!checkResult.hasError) {
const { checkIfValueExists } = fieldChecker.proxyOptions;
// Check other fields if the field depends on them for validation
(_a = fieldChecker.otherFields) === null || _a === void 0 ? void 0 : _a.forEach((field) => {
if (checkIfValueExists) {
if (!isEmpty(getFieldValue(data, field, nestedObject))) {
this.checkForField(field, data, options);
}
return;
}
this.checkForField(field, data, options);
});
}
return checkResult;
}
checkForFieldAsync(fieldName, data) {
checkForFieldAsync(fieldName, data, options = {}) {
this.setSchemaOptionsForAllType(data);
const fieldChecker = this.spec[fieldName];
const { nestedObject } = options;
const fieldChecker = this.getFieldType(fieldName, nestedObject);
if (!fieldChecker) {

@@ -39,7 +103,28 @@ // fieldValue can be anything if no schema defined

}
return fieldChecker.checkAsync(data[fieldName], data, fieldName);
const fieldValue = getFieldValue(data, fieldName, nestedObject);
const checkResult = fieldChecker.checkAsync(fieldValue, data, fieldName);
return checkResult.then(async (result) => {
var _a;
this.setFieldCheckResult(fieldName, result, nestedObject);
if (!result.hasError) {
const { checkIfValueExists } = fieldChecker.proxyOptions;
const checkAll = [];
// Check other fields if the field depends on them for validation
(_a = fieldChecker.otherFields) === null || _a === void 0 ? void 0 : _a.forEach((field) => {
if (checkIfValueExists) {
if (!isEmpty(getFieldValue(data, field, nestedObject))) {
checkAll.push(this.checkForFieldAsync(field, data, options));
}
return;
}
checkAll.push(this.checkForFieldAsync(field, data, options));
});
await Promise.all(checkAll);
}
return result;
});
}
check(data) {
const checkResult = {};
Object.keys(this.spec).forEach(key => {
Object.keys(this.$spec).forEach(key => {
if (typeof data === 'object') {

@@ -55,3 +140,3 @@ checkResult[key] = this.checkForField(key, data);

const keys = [];
Object.keys(this.spec).forEach((key) => {
Object.keys(this.$spec).forEach((key) => {
keys.push(key);

@@ -73,5 +158,5 @@ promises.push(this.checkForFieldAsync(key, data));

return new Schema(specs
.map(model => model.spec)
.map(model => model.$spec)
.reduce((accumulator, currentValue) => Object.assign(accumulator, currentValue), {}));
};
//# sourceMappingURL=Schema.js.map

@@ -13,3 +13,5 @@ import { MixedType } from './MixedType';

isEmail(errorMessage?: E | string): this;
isURL(errorMessage?: E | string): this;
isURL(errorMessage?: E | string, options?: {
allowMailto?: boolean;
}): this;
isHex(errorMessage?: E | string): this;

@@ -16,0 +18,0 @@ pattern(regexp: RegExp, errorMessage?: E | string): this;

@@ -62,4 +62,7 @@ import { MixedType } from './MixedType';

}
isURL(errorMessage = this.locale.isURL) {
const regexp = new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
isURL(errorMessage = this.locale.isURL, options) {
var _a;
const regexp = new RegExp(((_a = options === null || options === void 0 ? void 0 : options.allowMailto) !== null && _a !== void 0 ? _a : false)
? '^(?:mailto:|(?:(?:http|https|ftp)://|//))(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'
: '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
super.pushRule({

@@ -66,0 +69,0 @@ onValid: v => regexp.test(v),

@@ -17,4 +17,4 @@ import { ArrayType } from './ArrayType';

export type ErrorMessageType = string;
export type ValidCallbackType<V, D, E> = (value: V, data?: D, filedName?: string | string[]) => CheckResult<E> | boolean;
export type AsyncValidCallbackType<V, D, E> = (value: V, data?: D, filedName?: string | string[]) => CheckResult<E> | boolean | Promise<boolean | CheckResult<E>>;
export type ValidCallbackType<V, D, E> = (value: V, data?: D, fieldName?: string | string[]) => CheckResult<E> | boolean;
export type AsyncValidCallbackType<V, D, E> = (value: V, data?: D, fieldName?: string | string[]) => CheckResult<E> | boolean | Promise<boolean | CheckResult<E>>;
export type PlainObject<T extends Record<string, unknown> = any> = {

@@ -25,3 +25,3 @@ [P in keyof T]: T;

onValid: AsyncValidCallbackType<V, D, E>;
errorMessage?: E;
errorMessage?: any;
priority?: boolean;

@@ -36,3 +36,3 @@ params?: any;

export type SchemaCheckResult<T, E> = {
[P in keyof T]: CheckResult<E>;
[P in keyof T]?: CheckResult<E>;
};

@@ -6,3 +6,3 @@ import { CheckResult, RuleType } from '../types';

*/
export declare function createValidator<V, D, E>(data?: D, name?: string | string[]): (value: V, rules: RuleType<V, D, E>[]) => CheckResult<E> | null;
export declare function createValidator<V, D, E>(data?: D, name?: string | string[], label?: string): (value: V, rules: RuleType<V, D, E>[]) => CheckResult<E> | null;
export default createValidator;

@@ -12,3 +12,3 @@ import formatErrorMessage from './formatErrorMessage';

*/
export function createValidator(data, name) {
export function createValidator(data, name, label) {
return (value, rules) => {

@@ -20,8 +20,9 @@ for (let i = 0; i < rules.length; i += 1) {

const checkResult = onValid(value, data, name);
const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;
if (checkResult === false) {
return {
hasError: true,
errorMessage: formatErrorMessage(errorMessage, {
errorMessage: formatErrorMessage(errorMsg, {
...params,
name: Array.isArray(name) ? name.join('.') : name
name: label || (Array.isArray(name) ? name.join('.') : name)
})

@@ -28,0 +29,0 @@ };

@@ -6,3 +6,3 @@ import { CheckResult, RuleType } from '../types';

*/
export declare function createValidatorAsync<V, D, E>(data?: D, name?: string | string[]): (value: V, rules: RuleType<V, D, E>[]) => Promise<CheckResult<E, import("../types").PlainObject<any>>>;
export declare function createValidatorAsync<V, D, E>(data?: D, name?: string | string[], label?: string): (value: V, rules: RuleType<V, D, E>[]) => Promise<CheckResult<E, import("../types").PlainObject<any>>>;
export default createValidatorAsync;

@@ -1,2 +0,2 @@

import formatErrorMessage from './formatErrorMessage';
import formatErrorMessage, { joinName } from './formatErrorMessage';
/**

@@ -6,3 +6,3 @@ * Create a data asynchronous validator

*/
export function createValidatorAsync(data, name) {
export function createValidatorAsync(data, name, label) {
function check(errorMessage) {

@@ -22,5 +22,6 @@ return (checkResult) => {

const { onValid, errorMessage, params } = rule;
return Promise.resolve(onValid(value, data, name)).then(check(formatErrorMessage(errorMessage, {
const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;
return Promise.resolve(onValid(value, data, name)).then(check(formatErrorMessage(errorMsg, {
...params,
name: Array.isArray(name) ? name.join('.') : name
name: label || joinName(name)
})));

@@ -27,0 +28,0 @@ });

@@ -0,1 +1,2 @@

export declare function joinName(name: string | string[]): string;
/**

@@ -2,0 +3,0 @@ * formatErrorMessage('${name} is a required field', {name: 'email'});

import isEmpty from './isEmpty';
export function joinName(name) {
return Array.isArray(name) ? name.join('.') : name;
}
/**

@@ -9,3 +12,3 @@ * formatErrorMessage('${name} is a required field', {name: 'email'});

return errorMessage.replace(/\$\{\s*(\w+)\s*\}/g, (_, key) => {
return isEmpty(params === null || params === void 0 ? void 0 : params[key]) ? `[${key}]` : params === null || params === void 0 ? void 0 : params[key];
return isEmpty(params === null || params === void 0 ? void 0 : params[key]) ? `$\{${key}\}` : params === null || params === void 0 ? void 0 : params[key];
});

@@ -12,0 +15,0 @@ }

@@ -0,1 +1,3 @@

export { default as get } from 'lodash.get';
export { default as set } from 'lodash.set';
export { default as basicEmptyCheck } from './basicEmptyCheck';

@@ -7,1 +9,3 @@ export { default as checkRequired } from './checkRequired';

export { default as formatErrorMessage } from './formatErrorMessage';
export { default as shallowEqual } from './shallowEqual';
export { default as pathTransform } from './pathTransform';

@@ -0,1 +1,3 @@

export { default as get } from 'lodash.get';
export { default as set } from 'lodash.set';
export { default as basicEmptyCheck } from './basicEmptyCheck';

@@ -7,2 +9,4 @@ export { default as checkRequired } from './checkRequired';

export { default as formatErrorMessage } from './formatErrorMessage';
export { default as shallowEqual } from './shallowEqual';
export { default as pathTransform } from './pathTransform';
//# sourceMappingURL=index.js.map

@@ -55,7 +55,7 @@ "use strict";

super.pushRule({
onValid: (items, data, filedName) => {
onValid: (items, data, fieldName) => {
const checkResults = items.map((value, index) => {
const name = Array.isArray(filedName)
? [...filedName, `[${index}]`]
: [filedName, `[${index}]`];
const name = Array.isArray(fieldName)
? [...fieldName, `[${index}]`]
: [fieldName, `[${index}]`];
return type.check(value, data, name);

@@ -62,0 +62,0 @@ });

@@ -5,2 +5,3 @@ declare const _default: {

isRequiredOrEmpty: string;
equalTo: string;
};

@@ -7,0 +8,0 @@ array: {

@@ -6,3 +6,4 @@ "use strict";

isRequired: '${name} is a required field',
isRequiredOrEmpty: '${name} is a required field'
isRequiredOrEmpty: '${name} is a required field',
equalTo: '${name} must be the same as ${toFieldName}'
},

@@ -9,0 +10,0 @@ array: {

@@ -1,5 +0,17 @@

import { SchemaDeclaration, CheckResult, ValidCallbackType, AsyncValidCallbackType, RuleType, ErrorMessageType, TypeName } from './types';
import { SchemaDeclaration, CheckResult, ValidCallbackType, AsyncValidCallbackType, RuleType, ErrorMessageType, TypeName, PlainObject } from './types';
import { MixedTypeLocale } from './locales';
type ProxyOptions = {
checkIfValueExists?: boolean;
};
export declare const schemaSpecKey = "objectTypeSchemaSpec";
/**
* Get the field type from the schema object
*/
export declare function getFieldType(schemaSpec: any, fieldName: string, nestedObject?: boolean): any;
/**
* Get the field value from the data object
*/
export declare function getFieldValue(data: PlainObject, fieldName: string, nestedObject?: boolean): any;
export declare class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L = any> {
readonly typeName?: string;
readonly $typeName?: string;
protected required: boolean;

@@ -11,11 +23,14 @@ protected requiredMessage: E | string;

protected priorityRules: RuleType<ValueType, DataType, E | string>[];
schemaSpec: SchemaDeclaration<DataType, E>;
protected fieldLabel?: string;
$schemaSpec: SchemaDeclaration<DataType, E>;
value: any;
locale: L & MixedTypeLocale;
otherFields: string[];
proxyOptions: ProxyOptions;
constructor(name?: TypeName);
setSchemaOptions(schemaSpec: SchemaDeclaration<DataType, E>, value: any): void;
check(value?: ValueType, data?: DataType, fieldName?: string | string[]): CheckResult<string | E, import("./types").PlainObject<any>>;
checkAsync(value?: ValueType, data?: DataType, fieldName?: string | string[]): Promise<CheckResult<E | string>>;
check(value?: any, data?: DataType, fieldName?: string | string[]): CheckResult<string | E, PlainObject<any>>;
checkAsync(value?: any, data?: DataType, fieldName?: string | string[]): Promise<CheckResult<E | string>>;
protected pushRule(rule: RuleType<ValueType, DataType, E | string>): void;
addRule(onValid: ValidCallbackType<ValueType, DataType, E | string>, errorMessage?: E | string, priority?: boolean): this;
addRule(onValid: ValidCallbackType<ValueType, DataType, E | string>, errorMessage?: E | string | (() => E | string), priority?: boolean): this;
addAsyncRule(onValid: AsyncValidCallbackType<ValueType, DataType, E | string>, errorMessage?: E | string, priority?: boolean): this;

@@ -26,10 +41,55 @@ isRequired(errorMessage?: E | string, trim?: boolean): this;

* Define data verification rules based on conditions.
* @param validator
* @param condition
* @example
* MixedType().when(schema => {
* return schema.filed1.check() ? NumberType().min(5) : NumberType().min(0);
*
* ```js
* SchemaModel({
* option: StringType().isOneOf(['a', 'b', 'other']),
* other: StringType().when(schema => {
* const { value } = schema.option;
* return value === 'other' ? StringType().isRequired('Other required') : StringType();
* })
* });
* ```
*/
when(condition: (schemaSpec: SchemaDeclaration<DataType, E>) => MixedType): this;
/**
* Check if the value is equal to the value of another field.
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired(),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
equalTo(fieldName: string, errorMessage?: E | string): this;
/**
* After the field verification passes, proxy verification of other fields.
* @param options.checkIfValueExists When the value of other fields exists, the verification is performed (default: false)
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired().proxy(['confirmPassword']),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
proxy(fieldNames: string[], options?: ProxyOptions): this;
/**
* Overrides the key name in error messages.
*
* @example
* ```js
* SchemaModel({
* first_name: StringType().label('First name'),
* age: NumberType().label('Age')
* });
* ```
*/
label(label: string): this;
}
export default function getMixedType<DataType = any, E = ErrorMessageType>(): MixedType<DataType, E, string, any>;
export {};

@@ -6,5 +6,25 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.MixedType = void 0;
exports.MixedType = exports.getFieldValue = exports.getFieldType = exports.schemaSpecKey = void 0;
const utils_1 = require("./utils");
const formatErrorMessage_1 = require("./utils/formatErrorMessage");
const locales_1 = __importDefault(require("./locales"));
exports.schemaSpecKey = 'objectTypeSchemaSpec';
/**
* Get the field type from the schema object
*/
function getFieldType(schemaSpec, fieldName, nestedObject) {
if (nestedObject) {
const namePath = fieldName.split('.').join(`.${exports.schemaSpecKey}.`);
return (0, utils_1.get)(schemaSpec, namePath);
}
return schemaSpec === null || schemaSpec === void 0 ? void 0 : schemaSpec[fieldName];
}
exports.getFieldType = getFieldType;
/**
* Get the field value from the data object
*/
function getFieldValue(data, fieldName, nestedObject) {
return nestedObject ? (0, utils_1.get)(data, fieldName) : data === null || data === void 0 ? void 0 : data[fieldName];
}
exports.getFieldValue = getFieldValue;
class MixedType {

@@ -18,7 +38,10 @@ constructor(name) {

this.priorityRules = [];
this.typeName = name;
// The field name that depends on the verification of other fields
this.otherFields = [];
this.proxyOptions = {};
this.$typeName = name;
this.locale = Object.assign(name ? locales_1.default[name] : {}, locales_1.default.mixed);
}
setSchemaOptions(schemaSpec, value) {
this.schemaSpec = schemaSpec;
this.$schemaSpec = schemaSpec;
this.value = value;

@@ -30,6 +53,8 @@ }

hasError: true,
errorMessage: (0, utils_1.formatErrorMessage)(this.requiredMessage, { name: fieldName })
errorMessage: (0, utils_1.formatErrorMessage)(this.requiredMessage, {
name: this.fieldLabel || (0, formatErrorMessage_1.joinName)(fieldName)
})
};
}
const validator = (0, utils_1.createValidator)(data, fieldName);
const validator = (0, utils_1.createValidator)(data, fieldName, this.fieldLabel);
const checkStatus = validator(value, this.priorityRules);

@@ -48,6 +73,8 @@ if (checkStatus) {

hasError: true,
errorMessage: (0, utils_1.formatErrorMessage)(this.requiredMessage, { name: fieldName })
errorMessage: (0, utils_1.formatErrorMessage)(this.requiredMessage, {
name: this.fieldLabel || (0, formatErrorMessage_1.joinName)(fieldName)
})
});
}
const validator = (0, utils_1.createValidatorAsync)(data, fieldName);
const validator = (0, utils_1.createValidatorAsync)(data, fieldName, this.fieldLabel);
return new Promise(resolve => validator(value, this.priorityRules)

@@ -111,14 +138,74 @@ .then((checkStatus) => {

* Define data verification rules based on conditions.
* @param validator
* @param condition
* @example
* MixedType().when(schema => {
* return schema.filed1.check() ? NumberType().min(5) : NumberType().min(0);
*
* ```js
* SchemaModel({
* option: StringType().isOneOf(['a', 'b', 'other']),
* other: StringType().when(schema => {
* const { value } = schema.option;
* return value === 'other' ? StringType().isRequired('Other required') : StringType();
* })
* });
* ```
*/
when(condition) {
this.addRule((value, data, filedName) => {
return condition(this.schemaSpec).check(value, data, filedName);
this.addRule((value, data, fieldName) => {
return condition(this.$schemaSpec).check(value, data, fieldName);
}, undefined, true);
return this;
}
/**
* Check if the value is equal to the value of another field.
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired(),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
equalTo(fieldName, errorMessage = this.locale.equalTo) {
const errorMessageFunc = () => {
const type = getFieldType(this.$schemaSpec, fieldName, true);
return (0, utils_1.formatErrorMessage)(errorMessage, { toFieldName: (type === null || type === void 0 ? void 0 : type.fieldLabel) || fieldName });
};
this.addRule((value, data) => {
return (0, utils_1.shallowEqual)(value, (0, utils_1.get)(data, fieldName));
}, errorMessageFunc);
return this;
}
/**
* After the field verification passes, proxy verification of other fields.
* @param options.checkIfValueExists When the value of other fields exists, the verification is performed (default: false)
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired().proxy(['confirmPassword']),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
proxy(fieldNames, options) {
this.otherFields = fieldNames;
this.proxyOptions = options || {};
return this;
}
/**
* Overrides the key name in error messages.
*
* @example
* ```js
* SchemaModel({
* first_name: StringType().label('First name'),
* age: NumberType().label('Age')
* });
* ```
*/
label(label) {
this.fieldLabel = label;
return this;
}
}

@@ -125,0 +212,0 @@ exports.MixedType = MixedType;

@@ -1,6 +0,6 @@

import { MixedType } from './MixedType';
import { MixedType, schemaSpecKey } from './MixedType';
import { PlainObject, SchemaDeclaration, CheckResult, ErrorMessageType } from './types';
import { ObjectTypeLocale } from './locales';
export declare class ObjectType<DataType = any, E = ErrorMessageType> extends MixedType<PlainObject, DataType, E, ObjectTypeLocale> {
objectTypeSchemaSpec: SchemaDeclaration<DataType, E>;
[schemaSpecKey]: SchemaDeclaration<DataType, E>;
constructor(errorMessage?: E | string);

@@ -7,0 +7,0 @@ check(value?: PlainObject, data?: DataType, fieldName?: string | string[]): CheckResult<string | E, DataType>;

@@ -15,11 +15,16 @@ "use strict";

check(value = this.value, data, fieldName) {
const check = (value, data, type) => {
const check = (value, data, type, childFieldKey) => {
if (type.required && !(0, utils_1.checkRequired)(value, type.trim, type.emptyAllowed)) {
return { hasError: true, errorMessage: type.requiredMessage };
return {
hasError: true,
errorMessage: (0, utils_1.formatErrorMessage)(this.requiredMessage || this.locale.isRequired, {
name: type.fieldLabel || childFieldKey || fieldName
})
};
}
if (type.objectTypeSchemaSpec && typeof value === 'object') {
if (type[MixedType_1.schemaSpecKey] && typeof value === 'object') {
const checkResultObject = {};
let hasError = false;
Object.entries(type.objectTypeSchemaSpec).forEach(([k, v]) => {
const checkResult = check(value[k], value, v);
Object.entries(type[MixedType_1.schemaSpecKey]).forEach(([k, v]) => {
const checkResult = check(value[k], value, v, k);
if (checkResult === null || checkResult === void 0 ? void 0 : checkResult.hasError) {

@@ -32,3 +37,3 @@ hasError = true;

}
const validator = (0, utils_1.createValidator)(data, fieldName);
const validator = (0, utils_1.createValidator)(data, childFieldKey || fieldName, type.fieldLabel);
const checkStatus = validator(value, type.priorityRules);

@@ -46,21 +51,30 @@ if (checkStatus) {

checkAsync(value = this.value, data, fieldName) {
const check = (value, data, type) => {
const check = (value, data, type, childFieldKey) => {
if (type.required && !(0, utils_1.checkRequired)(value, type.trim, type.emptyAllowed)) {
return Promise.resolve({ hasError: true, errorMessage: this.requiredMessage });
return Promise.resolve({
hasError: true,
errorMessage: (0, utils_1.formatErrorMessage)(this.requiredMessage || this.locale.isRequired, {
name: type.fieldLabel || childFieldKey || fieldName
})
});
}
const validator = (0, utils_1.createValidatorAsync)(data, fieldName);
const validator = (0, utils_1.createValidatorAsync)(data, childFieldKey || fieldName, type.fieldLabel);
return new Promise(resolve => {
if (type.objectTypeSchemaSpec && typeof value === 'object') {
if (type[MixedType_1.schemaSpecKey] && typeof value === 'object') {
const checkResult = {};
const checkAll = [];
const keys = [];
Object.entries(type.objectTypeSchemaSpec).forEach(([k, v]) => {
checkAll.push(check(value[k], value, v));
Object.entries(type[MixedType_1.schemaSpecKey]).forEach(([k, v]) => {
checkAll.push(check(value[k], value, v, k));
keys.push(k);
});
return Promise.all(checkAll).then(values => {
let hasError = false;
values.forEach((v, index) => {
if (v === null || v === void 0 ? void 0 : v.hasError) {
hasError = true;
}
checkResult[keys[index]] = v;
});
resolve({ object: checkResult });
resolve({ hasError, object: checkResult });
});

@@ -98,3 +112,3 @@ }

shape(fields) {
this.objectTypeSchemaSpec = fields;
this[MixedType_1.schemaSpecKey] = fields;
return this;

@@ -101,0 +115,0 @@ }

import { SchemaDeclaration, SchemaCheckResult, CheckResult, PlainObject } from './types';
interface CheckOptions {
/**
* Check for nested object
*/
nestedObject?: boolean;
}
export declare class Schema<DataType = any, ErrorMsgType = string> {
readonly spec: SchemaDeclaration<DataType, ErrorMsgType>;
readonly $spec: SchemaDeclaration<DataType, ErrorMsgType>;
private data;
private checkResult;
constructor(schema: SchemaDeclaration<DataType, ErrorMsgType>);
getFieldType<T extends keyof DataType>(fieldName: T): SchemaDeclaration<DataType, ErrorMsgType>[T];
private getFieldType;
private setFieldCheckResult;
private setSchemaOptionsForAllType;
/**
* Get the check result of the schema
* @returns CheckResult<ErrorMsgType | string>
*/
getCheckResult(path?: string, result?: SchemaCheckResult<DataType, ErrorMsgType>): CheckResult<ErrorMsgType | string>;
/**
* Get the error messages of the schema
*/
getErrorMessages(path?: string, result?: SchemaCheckResult<DataType, ErrorMsgType>): (string | ErrorMsgType)[];
/**
* Get all the keys of the schema
*/
getKeys(): string[];
setSchemaOptionsForAllType(data: PlainObject): void;
checkForField<T extends keyof DataType>(fieldName: T, data: DataType): CheckResult<string | ErrorMsgType, PlainObject<any>>;
checkForFieldAsync<T extends keyof DataType>(fieldName: T, data: DataType): Promise<CheckResult<ErrorMsgType | string>>;
checkForField<T extends keyof DataType>(fieldName: T, data: DataType, options?: CheckOptions): CheckResult<ErrorMsgType | string>;
checkForFieldAsync<T extends keyof DataType>(fieldName: T, data: DataType, options?: CheckOptions): Promise<CheckResult<ErrorMsgType | string>>;
check<T extends keyof DataType>(data: DataType): SchemaCheckResult<DataType, ErrorMsgType>;

@@ -18,1 +38,2 @@ checkAsync<T extends keyof DataType>(data: DataType): Promise<SchemaCheckResult<DataType, ErrorMsgType>>;

}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaModel = exports.Schema = void 0;
const MixedType_1 = require("./MixedType");
const utils_1 = require("./utils");
class Schema {
constructor(schema) {
this.spec = schema;
this.checkResult = {};
this.$spec = schema;
}
getFieldType(fieldName) {
var _a;
return (_a = this.spec) === null || _a === void 0 ? void 0 : _a[fieldName];
getFieldType(fieldName, nestedObject) {
return (0, MixedType_1.getFieldType)(this.$spec, fieldName, nestedObject);
}
getKeys() {
return Object.keys(this.spec);
setFieldCheckResult(fieldName, checkResult, nestedObject) {
if (nestedObject) {
const namePath = fieldName.split('.').join('.object.');
(0, utils_1.set)(this.checkResult, namePath, checkResult);
return;
}
this.checkResult[fieldName] = checkResult;
}

@@ -19,10 +26,50 @@ setSchemaOptionsForAllType(data) {

}
Object.entries(this.spec).forEach(([key, type]) => {
type.setSchemaOptions(this.spec, data === null || data === void 0 ? void 0 : data[key]);
Object.entries(this.$spec).forEach(([key, type]) => {
type.setSchemaOptions(this.$spec, data === null || data === void 0 ? void 0 : data[key]);
});
this.data = data;
}
checkForField(fieldName, data) {
/**
* Get the check result of the schema
* @returns CheckResult<ErrorMsgType | string>
*/
getCheckResult(path, result = this.checkResult) {
if (path) {
return (result === null || result === void 0 ? void 0 : result[path]) || (0, utils_1.get)(result, (0, utils_1.pathTransform)(path)) || { hasError: false };
}
return result;
}
/**
* Get the error messages of the schema
*/
getErrorMessages(path, result = this.checkResult) {
let messages = [];
if (path) {
const { errorMessage, object, array } = (result === null || result === void 0 ? void 0 : result[path]) || (0, utils_1.get)(result, (0, utils_1.pathTransform)(path)) || {};
if (errorMessage) {
messages = [errorMessage];
}
else if (object) {
messages = Object.keys(object).map(key => { var _a; return (_a = object[key]) === null || _a === void 0 ? void 0 : _a.errorMessage; });
}
else if (array) {
messages = array.map(item => item === null || item === void 0 ? void 0 : item.errorMessage);
}
}
else {
messages = Object.keys(result).map(key => { var _a; return (_a = result[key]) === null || _a === void 0 ? void 0 : _a.errorMessage; });
}
return messages.filter(Boolean);
}
/**
* Get all the keys of the schema
*/
getKeys() {
return Object.keys(this.$spec);
}
checkForField(fieldName, data, options = {}) {
var _a;
this.setSchemaOptionsForAllType(data);
const fieldChecker = this.spec[fieldName];
const { nestedObject } = options;
const fieldChecker = this.getFieldType(fieldName, nestedObject);
if (!fieldChecker) {

@@ -32,7 +79,24 @@ // fieldValue can be anything if no schema defined

}
return fieldChecker.check(data[fieldName], data, fieldName);
const fieldValue = (0, MixedType_1.getFieldValue)(data, fieldName, nestedObject);
const checkResult = fieldChecker.check(fieldValue, data, fieldName);
this.setFieldCheckResult(fieldName, checkResult, nestedObject);
if (!checkResult.hasError) {
const { checkIfValueExists } = fieldChecker.proxyOptions;
// Check other fields if the field depends on them for validation
(_a = fieldChecker.otherFields) === null || _a === void 0 ? void 0 : _a.forEach((field) => {
if (checkIfValueExists) {
if (!(0, utils_1.isEmpty)((0, MixedType_1.getFieldValue)(data, field, nestedObject))) {
this.checkForField(field, data, options);
}
return;
}
this.checkForField(field, data, options);
});
}
return checkResult;
}
checkForFieldAsync(fieldName, data) {
checkForFieldAsync(fieldName, data, options = {}) {
this.setSchemaOptionsForAllType(data);
const fieldChecker = this.spec[fieldName];
const { nestedObject } = options;
const fieldChecker = this.getFieldType(fieldName, nestedObject);
if (!fieldChecker) {

@@ -42,7 +106,28 @@ // fieldValue can be anything if no schema defined

}
return fieldChecker.checkAsync(data[fieldName], data, fieldName);
const fieldValue = (0, MixedType_1.getFieldValue)(data, fieldName, nestedObject);
const checkResult = fieldChecker.checkAsync(fieldValue, data, fieldName);
return checkResult.then(async (result) => {
var _a;
this.setFieldCheckResult(fieldName, result, nestedObject);
if (!result.hasError) {
const { checkIfValueExists } = fieldChecker.proxyOptions;
const checkAll = [];
// Check other fields if the field depends on them for validation
(_a = fieldChecker.otherFields) === null || _a === void 0 ? void 0 : _a.forEach((field) => {
if (checkIfValueExists) {
if (!(0, utils_1.isEmpty)((0, MixedType_1.getFieldValue)(data, field, nestedObject))) {
checkAll.push(this.checkForFieldAsync(field, data, options));
}
return;
}
checkAll.push(this.checkForFieldAsync(field, data, options));
});
await Promise.all(checkAll);
}
return result;
});
}
check(data) {
const checkResult = {};
Object.keys(this.spec).forEach(key => {
Object.keys(this.$spec).forEach(key => {
if (typeof data === 'object') {

@@ -58,3 +143,3 @@ checkResult[key] = this.checkForField(key, data);

const keys = [];
Object.keys(this.spec).forEach((key) => {
Object.keys(this.$spec).forEach((key) => {
keys.push(key);

@@ -78,5 +163,5 @@ promises.push(this.checkForFieldAsync(key, data));

return new Schema(specs
.map(model => model.spec)
.map(model => model.$spec)
.reduce((accumulator, currentValue) => Object.assign(accumulator, currentValue), {}));
};
//# sourceMappingURL=Schema.js.map

@@ -13,3 +13,5 @@ import { MixedType } from './MixedType';

isEmail(errorMessage?: E | string): this;
isURL(errorMessage?: E | string): this;
isURL(errorMessage?: E | string, options?: {
allowMailto?: boolean;
}): this;
isHex(errorMessage?: E | string): this;

@@ -16,0 +18,0 @@ pattern(regexp: RegExp, errorMessage?: E | string): this;

@@ -65,4 +65,7 @@ "use strict";

}
isURL(errorMessage = this.locale.isURL) {
const regexp = new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
isURL(errorMessage = this.locale.isURL, options) {
var _a;
const regexp = new RegExp(((_a = options === null || options === void 0 ? void 0 : options.allowMailto) !== null && _a !== void 0 ? _a : false)
? '^(?:mailto:|(?:(?:http|https|ftp)://|//))(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'
: '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
super.pushRule({

@@ -69,0 +72,0 @@ onValid: v => regexp.test(v),

@@ -17,4 +17,4 @@ import { ArrayType } from './ArrayType';

export type ErrorMessageType = string;
export type ValidCallbackType<V, D, E> = (value: V, data?: D, filedName?: string | string[]) => CheckResult<E> | boolean;
export type AsyncValidCallbackType<V, D, E> = (value: V, data?: D, filedName?: string | string[]) => CheckResult<E> | boolean | Promise<boolean | CheckResult<E>>;
export type ValidCallbackType<V, D, E> = (value: V, data?: D, fieldName?: string | string[]) => CheckResult<E> | boolean;
export type AsyncValidCallbackType<V, D, E> = (value: V, data?: D, fieldName?: string | string[]) => CheckResult<E> | boolean | Promise<boolean | CheckResult<E>>;
export type PlainObject<T extends Record<string, unknown> = any> = {

@@ -25,3 +25,3 @@ [P in keyof T]: T;

onValid: AsyncValidCallbackType<V, D, E>;
errorMessage?: E;
errorMessage?: any;
priority?: boolean;

@@ -36,3 +36,3 @@ params?: any;

export type SchemaCheckResult<T, E> = {
[P in keyof T]: CheckResult<E>;
[P in keyof T]?: CheckResult<E>;
};

@@ -6,3 +6,3 @@ import { CheckResult, RuleType } from '../types';

*/
export declare function createValidator<V, D, E>(data?: D, name?: string | string[]): (value: V, rules: RuleType<V, D, E>[]) => CheckResult<E> | null;
export declare function createValidator<V, D, E>(data?: D, name?: string | string[], label?: string): (value: V, rules: RuleType<V, D, E>[]) => CheckResult<E> | null;
export default createValidator;

@@ -18,3 +18,3 @@ "use strict";

*/
function createValidator(data, name) {
function createValidator(data, name, label) {
return (value, rules) => {

@@ -26,8 +26,9 @@ for (let i = 0; i < rules.length; i += 1) {

const checkResult = onValid(value, data, name);
const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;
if (checkResult === false) {
return {
hasError: true,
errorMessage: (0, formatErrorMessage_1.default)(errorMessage, {
errorMessage: (0, formatErrorMessage_1.default)(errorMsg, {
...params,
name: Array.isArray(name) ? name.join('.') : name
name: label || (Array.isArray(name) ? name.join('.') : name)
})

@@ -34,0 +35,0 @@ };

@@ -6,3 +6,3 @@ import { CheckResult, RuleType } from '../types';

*/
export declare function createValidatorAsync<V, D, E>(data?: D, name?: string | string[]): (value: V, rules: RuleType<V, D, E>[]) => Promise<CheckResult<E, import("../types").PlainObject<any>>>;
export declare function createValidatorAsync<V, D, E>(data?: D, name?: string | string[], label?: string): (value: V, rules: RuleType<V, D, E>[]) => Promise<CheckResult<E, import("../types").PlainObject<any>>>;
export default createValidatorAsync;
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createValidatorAsync = void 0;
const formatErrorMessage_1 = __importDefault(require("./formatErrorMessage"));
const formatErrorMessage_1 = __importStar(require("./formatErrorMessage"));
/**

@@ -12,3 +32,3 @@ * Create a data asynchronous validator

*/
function createValidatorAsync(data, name) {
function createValidatorAsync(data, name, label) {
function check(errorMessage) {

@@ -28,5 +48,6 @@ return (checkResult) => {

const { onValid, errorMessage, params } = rule;
return Promise.resolve(onValid(value, data, name)).then(check((0, formatErrorMessage_1.default)(errorMessage, {
const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;
return Promise.resolve(onValid(value, data, name)).then(check((0, formatErrorMessage_1.default)(errorMsg, {
...params,
name: Array.isArray(name) ? name.join('.') : name
name: label || (0, formatErrorMessage_1.joinName)(name)
})));

@@ -33,0 +54,0 @@ });

@@ -0,1 +1,2 @@

export declare function joinName(name: string | string[]): string;
/**

@@ -2,0 +3,0 @@ * formatErrorMessage('${name} is a required field', {name: 'email'});

@@ -6,3 +6,8 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.joinName = void 0;
const isEmpty_1 = __importDefault(require("./isEmpty"));
function joinName(name) {
return Array.isArray(name) ? name.join('.') : name;
}
exports.joinName = joinName;
/**

@@ -15,3 +20,3 @@ * formatErrorMessage('${name} is a required field', {name: 'email'});

return errorMessage.replace(/\$\{\s*(\w+)\s*\}/g, (_, key) => {
return (0, isEmpty_1.default)(params === null || params === void 0 ? void 0 : params[key]) ? `[${key}]` : params === null || params === void 0 ? void 0 : params[key];
return (0, isEmpty_1.default)(params === null || params === void 0 ? void 0 : params[key]) ? `$\{${key}\}` : params === null || params === void 0 ? void 0 : params[key];
});

@@ -18,0 +23,0 @@ }

@@ -0,1 +1,3 @@

export { default as get } from 'lodash.get';
export { default as set } from 'lodash.set';
export { default as basicEmptyCheck } from './basicEmptyCheck';

@@ -7,1 +9,3 @@ export { default as checkRequired } from './checkRequired';

export { default as formatErrorMessage } from './formatErrorMessage';
export { default as shallowEqual } from './shallowEqual';
export { default as pathTransform } from './pathTransform';

@@ -6,3 +6,7 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.formatErrorMessage = exports.isEmpty = exports.createValidatorAsync = exports.createValidator = exports.checkRequired = exports.basicEmptyCheck = void 0;
exports.pathTransform = exports.shallowEqual = exports.formatErrorMessage = exports.isEmpty = exports.createValidatorAsync = exports.createValidator = exports.checkRequired = exports.basicEmptyCheck = exports.set = exports.get = void 0;
var lodash_get_1 = require("lodash.get");
Object.defineProperty(exports, "get", { enumerable: true, get: function () { return __importDefault(lodash_get_1).default; } });
var lodash_set_1 = require("lodash.set");
Object.defineProperty(exports, "set", { enumerable: true, get: function () { return __importDefault(lodash_set_1).default; } });
var basicEmptyCheck_1 = require("./basicEmptyCheck");

@@ -20,2 +24,6 @@ Object.defineProperty(exports, "basicEmptyCheck", { enumerable: true, get: function () { return __importDefault(basicEmptyCheck_1).default; } });

Object.defineProperty(exports, "formatErrorMessage", { enumerable: true, get: function () { return __importDefault(formatErrorMessage_1).default; } });
var shallowEqual_1 = require("./shallowEqual");
Object.defineProperty(exports, "shallowEqual", { enumerable: true, get: function () { return __importDefault(shallowEqual_1).default; } });
var pathTransform_1 = require("./pathTransform");
Object.defineProperty(exports, "pathTransform", { enumerable: true, get: function () { return __importDefault(pathTransform_1).default; } });
//# sourceMappingURL=index.js.map
{
"name": "schema-typed",
"version": "2.1.3",
"version": "2.2.0",
"description": "Schema for data modeling & validation",

@@ -41,4 +41,9 @@ "main": "lib/index.js",

"homepage": "https://github.com/rsuite/schema-typed#readme",
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/node": "^20.12.5",
"@typescript-eslint/eslint-plugin": "^4.29.3",

@@ -60,5 +65,5 @@ "@typescript-eslint/parser": "^4.29.3",

"prettier": "^2.2.1",
"ts-node": "^9.1.1",
"ts-node": "^10.9.2",
"typescript": "^4.2.2"
}
}

@@ -17,3 +17,3 @@ # schema-typed

- [Custom verification](#custom-verification)
- [Multi-field cross validation](#multi-field-cross-validation)
- [Field dependency validation](#field-dependency-validation)
- [Asynchronous check](#asynchronous-check)

@@ -27,4 +27,4 @@ - [Validate nested objects](#validate-nested-objects)

- [`checkAsync(data: object)`](#checkasyncdata-object)
- [`checkForField(fieldName: string, data: object)`](#checkforfieldfieldname-string-data-object)
- [`checkForFieldAsync(fieldName: string, data: object)`](#checkforfieldasyncfieldname-string-data-object)
- [`checkForField(fieldName: string, data: object, options?: { nestedObject?: boolean })`](#checkforfieldfieldname-string-data-object-options--nestedobject-boolean-)
- [`checkForFieldAsync(fieldName: string, data: object, options?: { nestedObject?: boolean })`](#checkforfieldasyncfieldname-string-data-object-options--nestedobject-boolean-)
- [MixedType()](#mixedtype)

@@ -38,2 +38,5 @@ - [`isRequired(errorMessage?: string, trim: boolean = true)`](#isrequirederrormessage-string-trim-boolean--true)

- [`checkAsync(value: ValueType, data?: DataType):Promise<CheckResult>`](#checkasyncvalue-valuetype-data-datatypepromisecheckresult)
- [`label(label: string)`](#labellabel-string)
- [`equalTo(fieldName: string, errorMessage?: string)`](#equaltofieldname-string-errormessage-string)
- [`proxy(fieldNames: string[], options?: { checkIfValueExists?: boolean })`](#proxyfieldnames-string-options--checkifvalueexists-boolean-)
- [StringType(errorMessage?: string)](#stringtypeerrormessage-string)

@@ -176,30 +179,34 @@ - [`isEmail(errorMessage?: string)`](#isemailerrormessage-string)

#### Multi-field cross validation
#### Field dependency validation
E.g: verify that the two passwords are the same.
1. Use the `equalTo` method to verify that the values of two fields are equal.
```js
const model = SchemaModel({
password1: StringType().isRequired('This field required'),
password2: StringType().addRule((value, data) => {
if (value !== data.password1) {
return false;
}
return true;
}, 'The passwords are inconsistent twice')
password: StringType().isRequired(),
confirmPassword: StringType().equalTo('password')
});
```
model.check({ password1: '123456', password2: 'root' });
2. Use the `addRule` method to create a custom validation rule.
/**
{
password1: { hasError: false },
password2: {
hasError: true,
errorMessage: 'The passwords are inconsistent twice'
}
}
**/
```js
const model = SchemaModel({
password: StringType().isRequired(),
confirmPassword: StringType().addRule(
(value, data) => value === data.password,
'Confirm password must be the same as password'
)
});
```
3. Use the `proxy` method to verify that a field passes, and then proxy verification of other fields.
```js
const model = SchemaModel({
password: StringType().isRequired().proxy(['confirmPassword']),
confirmPassword: StringType().equalTo('password')
});
```
#### Asynchronous check

@@ -374,3 +381,3 @@

#### `checkForField(fieldName: string, data: object)`
#### `checkForField(fieldName: string, data: object, options?: { nestedObject?: boolean })`

@@ -392,3 +399,3 @@ Check whether a field in the data conforms to the model shape definition. Return a check result.

#### `checkForFieldAsync(fieldName: string, data: object)`
#### `checkForFieldAsync(fieldName: string, data: object, options?: { nestedObject?: boolean })`

@@ -454,12 +461,10 @@ Asynchronously check whether a field in the data conforms to the model shape definition. Return a check result.

Define data verification rules based on conditions.
Conditional validation, the return value is a new type.
```js
```ts
const model = SchemaModel({
age: NumberType().min(18, 'error'),
contact: MixedType().when(schema => {
const checkResult = schema.age.check();
return checkResult.hasError
? StringType().isRequired('Please provide contact information')
: StringType();
option: StringType().isOneOf(['a', 'b', 'other']),
other: StringType().when(schema => {
const { value } = schema.option;
return value === 'other' ? StringType().isRequired('Other required') : StringType();
})

@@ -469,21 +474,35 @@ });

/**
{
age: { hasError: false },
contact: { hasError: false }
{
option: { hasError: false },
other: { hasError: false }
}
*/
model.check({ age: 18, contact: '' });
model.check({ option: 'a', other: '' });
/*
{
age: { hasError: true, errorMessage: 'error' },
contact: {
hasError: true,
errorMessage: 'Please provide contact information'
}
option: { hasError: false },
other: { hasError: true, errorMessage: 'Other required' }
}
*/
model.check({ age: 17, contact: '' });
model.check({ option: 'other', other: '' });
```
Check whether a field passes the validation to determine the validation rules of another field.
```js
const model = SchemaModel({
password: StringType().isRequired('Password required'),
confirmPassword: StringType().when(schema => {
const { hasError } = schema.password.check();
return hasError
? StringType()
: StringType().addRule(
value => value === schema.password.value,
'The passwords are inconsistent twice'
);
})
});
```
#### `check(value: ValueType, data?: DataType):CheckResult`

@@ -526,2 +545,44 @@

#### `label(label: string)`
Overrides the key name in error messages.
```js
MixedType().label('Username');
```
Eg:
```js
SchemaModel({
first_name: StringType().label('First name'),
age: NumberType().label('Age')
});
```
#### `equalTo(fieldName: string, errorMessage?: string)`
Check if the value is equal to the value of another field.
```js
SchemaModel({
password: StringType().isRequired(),
confirmPassword: StringType().equalTo('password')
});
```
#### `proxy(fieldNames: string[], options?: { checkIfValueExists?: boolean })`
After the field verification passes, proxy verification of other fields.
- `fieldNames`: The field name to be proxied.
- `options.checkIfValueExists`: When the value of other fields exists, the verification is performed (default: false)
```js
SchemaModel({
password: StringType().isRequired().proxy(['confirmPassword']),
confirmPassword: StringType().equalTo('password')
});
```
### StringType(errorMessage?: string)

@@ -528,0 +589,0 @@

@@ -71,7 +71,7 @@ import { MixedType } from './MixedType';

super.pushRule({
onValid: (items, data, filedName) => {
onValid: (items, data, fieldName) => {
const checkResults = items.map((value, index) => {
const name = Array.isArray(filedName)
? [...filedName, `[${index}]`]
: [filedName, `[${index}]`];
const name = Array.isArray(fieldName)
? [...fieldName, `[${index}]`]
: [fieldName, `[${index}]`];

@@ -78,0 +78,0 @@ return type.check(value, data, name as string[]);

export default {
mixed: {
isRequired: '${name} is a required field',
isRequiredOrEmpty: '${name} is a required field'
isRequiredOrEmpty: '${name} is a required field',
equalTo: '${name} must be the same as ${toFieldName}'
},

@@ -6,0 +7,0 @@ array: {

@@ -8,3 +8,4 @@ import {

ErrorMessageType,
TypeName
TypeName,
PlainObject
} from './types';

@@ -16,8 +17,36 @@ import {

isEmpty,
formatErrorMessage
shallowEqual,
formatErrorMessage,
get
} from './utils';
import { joinName } from './utils/formatErrorMessage';
import locales, { MixedTypeLocale } from './locales';
type ProxyOptions = {
// Check if the value exists
checkIfValueExists?: boolean;
};
export const schemaSpecKey = 'objectTypeSchemaSpec';
/**
* Get the field type from the schema object
*/
export function getFieldType(schemaSpec: any, fieldName: string, nestedObject?: boolean) {
if (nestedObject) {
const namePath = fieldName.split('.').join(`.${schemaSpecKey}.`);
return get(schemaSpec, namePath);
}
return schemaSpec?.[fieldName];
}
/**
* Get the field value from the data object
*/
export function getFieldValue(data: PlainObject, fieldName: string, nestedObject?: boolean) {
return nestedObject ? get(data, fieldName) : data?.[fieldName];
}
export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L = any> {
readonly typeName?: string;
readonly $typeName?: string;
protected required = false;

@@ -29,9 +58,14 @@ protected requiredMessage: E | string = '';

protected priorityRules: RuleType<ValueType, DataType, E | string>[] = [];
protected fieldLabel?: string;
schemaSpec: SchemaDeclaration<DataType, E>;
$schemaSpec: SchemaDeclaration<DataType, E>;
value: any;
locale: L & MixedTypeLocale;
// The field name that depends on the verification of other fields
otherFields: string[] = [];
proxyOptions: ProxyOptions = {};
constructor(name?: TypeName) {
this.typeName = name;
this.$typeName = name;
this.locale = Object.assign(name ? locales[name] : {}, locales.mixed) as L & MixedTypeLocale;

@@ -41,15 +75,21 @@ }

setSchemaOptions(schemaSpec: SchemaDeclaration<DataType, E>, value: any) {
this.schemaSpec = schemaSpec;
this.$schemaSpec = schemaSpec;
this.value = value;
}
check(value: ValueType = this.value, data?: DataType, fieldName?: string | string[]) {
check(value: any = this.value, data?: DataType, fieldName?: string | string[]) {
if (this.required && !checkRequired(value, this.trim, this.emptyAllowed)) {
return {
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || joinName(fieldName)
})
};
}
const validator = createValidator<ValueType, DataType, E | string>(data, fieldName);
const validator = createValidator<ValueType, DataType, E | string>(
data,
fieldName,
this.fieldLabel
);

@@ -70,3 +110,3 @@ const checkStatus = validator(value, this.priorityRules);

checkAsync(
value: ValueType = this.value,
value: any = this.value,
data?: DataType,

@@ -78,7 +118,13 @@ fieldName?: string | string[]

hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || joinName(fieldName)
})
});
}
const validator = createValidatorAsync<ValueType, DataType, E | string>(data, fieldName);
const validator = createValidatorAsync<ValueType, DataType, E | string>(
data,
fieldName,
this.fieldLabel
);

@@ -123,3 +169,3 @@ return new Promise(resolve =>

onValid: ValidCallbackType<ValueType, DataType, E | string>,
errorMessage?: E | string,
errorMessage?: E | string | (() => E | string),
priority?: boolean

@@ -154,12 +200,19 @@ ) {

* Define data verification rules based on conditions.
* @param validator
* @param condition
* @example
* MixedType().when(schema => {
* return schema.filed1.check() ? NumberType().min(5) : NumberType().min(0);
*
* ```js
* SchemaModel({
* option: StringType().isOneOf(['a', 'b', 'other']),
* other: StringType().when(schema => {
* const { value } = schema.option;
* return value === 'other' ? StringType().isRequired('Other required') : StringType();
* })
* });
* ```
*/
when(condition: (schemaSpec: SchemaDeclaration<DataType, E>) => MixedType) {
this.addRule(
(value, data, filedName) => {
return condition(this.schemaSpec).check(value, data, filedName);
(value, data, fieldName) => {
return condition(this.$schemaSpec).check(value, data, fieldName);
},

@@ -171,2 +224,59 @@ undefined,

}
/**
* Check if the value is equal to the value of another field.
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired(),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
equalTo(fieldName: string, errorMessage: E | string = this.locale.equalTo) {
const errorMessageFunc = () => {
const type = getFieldType(this.$schemaSpec, fieldName, true);
return formatErrorMessage(errorMessage, { toFieldName: type?.fieldLabel || fieldName });
};
this.addRule((value, data) => {
return shallowEqual(value, get(data, fieldName));
}, errorMessageFunc);
return this;
}
/**
* After the field verification passes, proxy verification of other fields.
* @param options.checkIfValueExists When the value of other fields exists, the verification is performed (default: false)
* @example
*
* ```js
* SchemaModel({
* password: StringType().isRequired().proxy(['confirmPassword']),
* confirmPassword: StringType().equalTo('password').isRequired()
* });
* ```
*/
proxy(fieldNames: string[], options?: ProxyOptions) {
this.otherFields = fieldNames;
this.proxyOptions = options || {};
return this;
}
/**
* Overrides the key name in error messages.
*
* @example
* ```js
* SchemaModel({
* first_name: StringType().label('First name'),
* age: NumberType().label('Age')
* });
* ```
*/
label(label: string) {
this.fieldLabel = label;
return this;
}
}

@@ -173,0 +283,0 @@

@@ -1,3 +0,9 @@

import { MixedType } from './MixedType';
import { createValidator, createValidatorAsync, checkRequired, isEmpty } from './utils';
import { MixedType, schemaSpecKey } from './MixedType';
import {
createValidator,
createValidatorAsync,
checkRequired,
isEmpty,
formatErrorMessage
} from './utils';
import { PlainObject, SchemaDeclaration, CheckResult, ErrorMessageType } from './types';

@@ -12,3 +18,3 @@ import { ObjectTypeLocale } from './locales';

> {
objectTypeSchemaSpec: SchemaDeclaration<DataType, E>;
[schemaSpecKey]: SchemaDeclaration<DataType, E>;
constructor(errorMessage?: E | string) {

@@ -23,12 +29,17 @@ super('object');

check(value: PlainObject = this.value, data?: DataType, fieldName?: string | string[]) {
const check = (value: any, data: any, type: any) => {
const check = (value: any, data: any, type: any, childFieldKey?: string) => {
if (type.required && !checkRequired(value, type.trim, type.emptyAllowed)) {
return { hasError: true, errorMessage: type.requiredMessage };
return {
hasError: true,
errorMessage: formatErrorMessage<E>(this.requiredMessage || this.locale.isRequired, {
name: type.fieldLabel || childFieldKey || fieldName
})
};
}
if (type.objectTypeSchemaSpec && typeof value === 'object') {
if (type[schemaSpecKey] && typeof value === 'object') {
const checkResultObject: any = {};
let hasError = false;
Object.entries(type.objectTypeSchemaSpec).forEach(([k, v]) => {
const checkResult = check(value[k], value, v);
Object.entries(type[schemaSpecKey]).forEach(([k, v]) => {
const checkResult = check(value[k], value, v, k);
if (checkResult?.hasError) {

@@ -43,3 +54,7 @@ hasError = true;

const validator = createValidator<PlainObject, DataType, E | string>(data, fieldName);
const validator = createValidator<PlainObject, DataType, E | string>(
data,
childFieldKey || fieldName,
type.fieldLabel
);
const checkStatus = validator(value, type.priorityRules);

@@ -62,16 +77,25 @@

checkAsync(value: PlainObject = this.value, data?: DataType, fieldName?: string | string[]) {
const check = (value: any, data: any, type: any) => {
const check = (value: any, data: any, type: any, childFieldKey?: string) => {
if (type.required && !checkRequired(value, type.trim, type.emptyAllowed)) {
return Promise.resolve({ hasError: true, errorMessage: this.requiredMessage });
return Promise.resolve({
hasError: true,
errorMessage: formatErrorMessage<E>(this.requiredMessage || this.locale.isRequired, {
name: type.fieldLabel || childFieldKey || fieldName
})
});
}
const validator = createValidatorAsync<PlainObject, DataType, E | string>(data, fieldName);
const validator = createValidatorAsync<PlainObject, DataType, E | string>(
data,
childFieldKey || fieldName,
type.fieldLabel
);
return new Promise(resolve => {
if (type.objectTypeSchemaSpec && typeof value === 'object') {
if (type[schemaSpecKey] && typeof value === 'object') {
const checkResult: any = {};
const checkAll: Promise<unknown>[] = [];
const keys: string[] = [];
Object.entries(type.objectTypeSchemaSpec).forEach(([k, v]) => {
checkAll.push(check(value[k], value, v));
Object.entries(type[schemaSpecKey]).forEach(([k, v]) => {
checkAll.push(check(value[k], value, v, k));
keys.push(k);

@@ -81,7 +105,11 @@ });

return Promise.all(checkAll).then(values => {
values.forEach((v, index) => {
let hasError = false;
values.forEach((v: any, index: number) => {
if (v?.hasError) {
hasError = true;
}
checkResult[keys[index]] = v;
});
resolve({ object: checkResult });
resolve({ hasError, object: checkResult });
});

@@ -122,3 +150,3 @@ }

shape(fields: SchemaDeclaration<DataType, E>) {
this.objectTypeSchemaSpec = fields;
this[schemaSpecKey] = fields;
return this;

@@ -125,0 +153,0 @@ }

import { SchemaDeclaration, SchemaCheckResult, CheckResult, PlainObject } from './types';
import { MixedType } from './MixedType';
import { MixedType, getFieldType, getFieldValue } from './MixedType';
import { set, get, isEmpty, pathTransform } from './utils';
interface CheckOptions {
/**
* Check for nested object
*/
nestedObject?: boolean;
}
export class Schema<DataType = any, ErrorMsgType = string> {
readonly spec: SchemaDeclaration<DataType, ErrorMsgType>;
readonly $spec: SchemaDeclaration<DataType, ErrorMsgType>;
private data: PlainObject;
private checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};
constructor(schema: SchemaDeclaration<DataType, ErrorMsgType>) {
this.spec = schema;
this.$spec = schema;
}
getFieldType<T extends keyof DataType>(fieldName: T) {
return this.spec?.[fieldName];
private getFieldType<T extends keyof DataType>(
fieldName: T,
nestedObject?: boolean
): SchemaDeclaration<DataType, ErrorMsgType>[T] {
return getFieldType(this.$spec, fieldName as string, nestedObject);
}
getKeys() {
return Object.keys(this.spec);
private setFieldCheckResult(
fieldName: string,
checkResult: CheckResult<ErrorMsgType | string>,
nestedObject?: boolean
) {
if (nestedObject) {
const namePath = fieldName.split('.').join('.object.');
set(this.checkResult, namePath, checkResult);
return;
}
this.checkResult[fieldName as string] = checkResult;
}
setSchemaOptionsForAllType(data: PlainObject) {
private setSchemaOptionsForAllType(data: PlainObject) {
if (data === this.data) {

@@ -25,4 +48,4 @@ return;

Object.entries(this.spec).forEach(([key, type]) => {
(type as MixedType).setSchemaOptions(this.spec as any, data?.[key]);
Object.entries(this.$spec).forEach(([key, type]) => {
(type as MixedType).setSchemaOptions(this.$spec as any, data?.[key]);
});

@@ -33,6 +56,55 @@

checkForField<T extends keyof DataType>(fieldName: T, data: DataType) {
/**
* Get the check result of the schema
* @returns CheckResult<ErrorMsgType | string>
*/
getCheckResult(path?: string, result = this.checkResult): CheckResult<ErrorMsgType | string> {
if (path) {
return result?.[path] || get(result, pathTransform(path)) || { hasError: false };
}
return result;
}
/**
* Get the error messages of the schema
*/
getErrorMessages(path?: string, result = this.checkResult): (string | ErrorMsgType)[] {
let messages: (string | ErrorMsgType)[] = [];
if (path) {
const { errorMessage, object, array } =
result?.[path] || get(result, pathTransform(path)) || {};
if (errorMessage) {
messages = [errorMessage];
} else if (object) {
messages = Object.keys(object).map(key => object[key]?.errorMessage);
} else if (array) {
messages = array.map(item => item?.errorMessage);
}
} else {
messages = Object.keys(result).map(key => result[key]?.errorMessage);
}
return messages.filter(Boolean);
}
/**
* Get all the keys of the schema
*/
getKeys() {
return Object.keys(this.$spec);
}
checkForField<T extends keyof DataType>(
fieldName: T,
data: DataType,
options: CheckOptions = {}
): CheckResult<ErrorMsgType | string> {
this.setSchemaOptionsForAllType(data);
const fieldChecker = this.spec[fieldName];
const { nestedObject } = options;
const fieldChecker = this.getFieldType(fieldName, nestedObject);
if (!fieldChecker) {

@@ -43,3 +115,23 @@ // fieldValue can be anything if no schema defined

return fieldChecker.check((data[fieldName] as unknown) as never, data, fieldName as string);
const fieldValue = getFieldValue(data, fieldName as string, nestedObject);
const checkResult = fieldChecker.check(fieldValue, data, fieldName as string);
this.setFieldCheckResult(fieldName as string, checkResult, nestedObject);
if (!checkResult.hasError) {
const { checkIfValueExists } = fieldChecker.proxyOptions;
// Check other fields if the field depends on them for validation
fieldChecker.otherFields?.forEach((field: string) => {
if (checkIfValueExists) {
if (!isEmpty(getFieldValue(data, field, nestedObject))) {
this.checkForField(field as T, data, options);
}
return;
}
this.checkForField(field as T, data, options);
});
}
return checkResult;
}

@@ -49,7 +141,10 @@

fieldName: T,
data: DataType
data: DataType,
options: CheckOptions = {}
): Promise<CheckResult<ErrorMsgType | string>> {
this.setSchemaOptionsForAllType(data);
const fieldChecker = this.spec[fieldName];
const { nestedObject } = options;
const fieldChecker = this.getFieldType(fieldName, nestedObject);
if (!fieldChecker) {

@@ -59,12 +154,35 @@ // fieldValue can be anything if no schema defined

}
return fieldChecker.checkAsync(
(data[fieldName] as unknown) as never,
data,
fieldName as string
);
const fieldValue = getFieldValue(data, fieldName as string, nestedObject);
const checkResult = fieldChecker.checkAsync(fieldValue, data, fieldName as string);
return checkResult.then(async result => {
this.setFieldCheckResult(fieldName as string, result, nestedObject);
if (!result.hasError) {
const { checkIfValueExists } = fieldChecker.proxyOptions;
const checkAll: Promise<CheckResult<ErrorMsgType | string>>[] = [];
// Check other fields if the field depends on them for validation
fieldChecker.otherFields?.forEach((field: string) => {
if (checkIfValueExists) {
if (!isEmpty(getFieldValue(data, field, nestedObject))) {
checkAll.push(this.checkForFieldAsync(field as T, data, options));
}
return;
}
checkAll.push(this.checkForFieldAsync(field as T, data, options));
});
await Promise.all(checkAll);
}
return result;
});
}
check<T extends keyof DataType>(data: DataType) {
const checkResult: PlainObject = {};
Object.keys(this.spec).forEach(key => {
const checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};
Object.keys(this.$spec).forEach(key => {
if (typeof data === 'object') {

@@ -75,11 +193,11 @@ checkResult[key] = this.checkForField(key as T, data);

return checkResult as SchemaCheckResult<DataType, ErrorMsgType>;
return checkResult;
}
checkAsync<T extends keyof DataType>(data: DataType) {
const checkResult: PlainObject = {};
const checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};
const promises: Promise<CheckResult<ErrorMsgType | string>>[] = [];
const keys: string[] = [];
Object.keys(this.spec).forEach((key: string) => {
Object.keys(this.$spec).forEach((key: string) => {
keys.push(key);

@@ -93,3 +211,4 @@ promises.push(this.checkForFieldAsync(key as T, data));

}
return checkResult as SchemaCheckResult<DataType, ErrorMsgType>;
return checkResult;
});

@@ -110,5 +229,5 @@ }

specs
.map(model => model.spec)
.map(model => model.$spec)
.reduce((accumulator, currentValue) => Object.assign(accumulator, currentValue), {} as any)
);
};

@@ -70,3 +70,4 @@ import { MixedType } from './MixedType';

// http://emailregex.com/
const regexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const regexp =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
super.pushRule({

@@ -79,5 +80,12 @@ onValid: v => regexp.test(v),

isURL(errorMessage: E | string = this.locale.isURL) {
isURL(
errorMessage: E | string = this.locale.isURL,
options?: {
allowMailto?: boolean;
}
) {
const regexp = new RegExp(
'^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$',
options?.allowMailto ?? false
? '^(?:mailto:|(?:(?:http|https|ftp)://|//))(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'
: '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$',
'i'

@@ -84,0 +92,0 @@ );

@@ -22,3 +22,3 @@ import { ArrayType } from './ArrayType';

data?: D,
filedName?: string | string[]
fieldName?: string | string[]
) => CheckResult<E> | boolean;

@@ -29,3 +29,3 @@

data?: D,
filedName?: string | string[]
fieldName?: string | string[]
) => CheckResult<E> | boolean | Promise<boolean | CheckResult<E>>;

@@ -39,3 +39,3 @@

onValid: AsyncValidCallbackType<V, D, E>;
errorMessage?: E;
errorMessage?: any;
priority?: boolean;

@@ -71,3 +71,3 @@ params?: any;

export type SchemaCheckResult<T, E> = {
[P in keyof T]: CheckResult<E>;
[P in keyof T]?: CheckResult<E>;
};

@@ -13,3 +13,3 @@ import { CheckResult, RuleType } from '../types';

*/
export function createValidator<V, D, E>(data?: D, name?: string | string[]) {
export function createValidator<V, D, E>(data?: D, name?: string | string[], label?: string) {
return (value: V, rules: RuleType<V, D, E>[]): CheckResult<E> | null => {

@@ -20,2 +20,3 @@ for (let i = 0; i < rules.length; i += 1) {

const checkResult = onValid(value, data, name);
const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;

@@ -25,5 +26,5 @@ if (checkResult === false) {

hasError: true,
errorMessage: formatErrorMessage<E>(errorMessage, {
errorMessage: formatErrorMessage<E>(errorMsg, {
...params,
name: Array.isArray(name) ? name.join('.') : name
name: label || (Array.isArray(name) ? name.join('.') : name)
})

@@ -30,0 +31,0 @@ };

import { CheckResult, RuleType } from '../types';
import formatErrorMessage from './formatErrorMessage';
import formatErrorMessage, { joinName } from './formatErrorMessage';

@@ -8,3 +8,3 @@ /**

*/
export function createValidatorAsync<V, D, E>(data?: D, name?: string | string[]) {
export function createValidatorAsync<V, D, E>(data?: D, name?: string | string[], label?: string) {
function check(errorMessage?: E | string) {

@@ -24,7 +24,9 @@ return (checkResult: CheckResult<E> | boolean): CheckResult<E> | null => {

const { onValid, errorMessage, params } = rule;
const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;
return Promise.resolve(onValid(value, data, name)).then(
check(
formatErrorMessage<E>(errorMessage, {
formatErrorMessage<E>(errorMsg, {
...params,
name: Array.isArray(name) ? name.join('.') : name
name: label || joinName(name)
})

@@ -31,0 +33,0 @@ )

import isEmpty from './isEmpty';
export function joinName(name: string | string[]) {
return Array.isArray(name) ? name.join('.') : name;
}
/**

@@ -10,3 +14,3 @@ * formatErrorMessage('${name} is a required field', {name: 'email'});

return errorMessage.replace(/\$\{\s*(\w+)\s*\}/g, (_, key) => {
return isEmpty(params?.[key]) ? `[${key}]` : params?.[key];
return isEmpty(params?.[key]) ? `$\{${key}\}` : params?.[key];
});

@@ -13,0 +17,0 @@ }

@@ -0,1 +1,3 @@

export { default as get } from 'lodash.get';
export { default as set } from 'lodash.set';
export { default as basicEmptyCheck } from './basicEmptyCheck';

@@ -7,1 +9,3 @@ export { default as checkRequired } from './checkRequired';

export { default as formatErrorMessage } from './formatErrorMessage';
export { default as shallowEqual } from './shallowEqual';
export { default as pathTransform } from './pathTransform';

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc