Full Code of rsuite/schema-typed for AI

master 3776457b1a6c cached
52 files
199.5 KB
52.6k tokens
127 symbols
1 requests
Download .txt
Showing preview only (213K chars total). Download the full file or copy to clipboard to get everything.
Repository: rsuite/schema-typed
Branch: master
Commit: 3776457b1a6c
Files: 52
Total size: 199.5 KB

Directory structure:
gitextract_y06uf7xz/

├── .codesandbox/
│   └── ci.json
├── .eslintrc.js
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1.bug_report.md
│   │   ├── 2.feature_request.md
│   │   └── config.yml
│   └── workflows/
│       ├── nodejs-ci.yml
│       └── nodejs-publish.yml
├── .gitignore
├── .mocharc.js
├── .npmignore
├── .npmrc
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src/
│   ├── ArrayType.ts
│   ├── BooleanType.ts
│   ├── DateType.ts
│   ├── MixedType.ts
│   ├── NumberType.ts
│   ├── ObjectType.ts
│   ├── Schema.ts
│   ├── StringType.ts
│   ├── index.ts
│   ├── locales/
│   │   ├── default.ts
│   │   └── index.ts
│   ├── types.ts
│   └── utils/
│       ├── basicEmptyCheck.ts
│       ├── checkRequired.ts
│       ├── createValidator.ts
│       ├── createValidatorAsync.ts
│       ├── formatErrorMessage.ts
│       ├── index.ts
│       ├── isEmpty.ts
│       ├── pathTransform.ts
│       └── shallowEqual.ts
├── test/
│   ├── ArrayTypeSpec.js
│   ├── BooleanTypeSpec.js
│   ├── DateTypeSpec.js
│   ├── MixedTypeSpec.js
│   ├── NumberTypeSpec.js
│   ├── ObjectTypeSpec.js
│   ├── SchemaSpec.js
│   ├── StringTypeSpec.js
│   └── utilsSpec.js
├── tsconfig-es.json
├── tsconfig.json
└── types/
    ├── index.d.ts
    ├── test.ts
    ├── tsconfig.json
    └── tslint.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .codesandbox/ci.json
================================================
{
  "sandboxes": ["new"]
}


================================================
FILE: .eslintrc.js
================================================
const OFF = 0;
const WARNING = 1;
const ERROR = 2;

module.exports = {
  env: {
    browser: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  extends: [
    'plugin:@typescript-eslint/recommended',
    'prettier/@typescript-eslint',
    'plugin:prettier/recommended'
  ],
  parserOptions: {},
  plugins: ['@typescript-eslint'],
  rules: {
    quotes: [ERROR, 'single'],
    semi: [ERROR, 'always'],
    'space-infix-ops': ERROR,
    'prefer-spread': ERROR,
    'no-multi-spaces': ERROR,
    'class-methods-use-this': WARNING,
    'arrow-parens': [ERROR, 'as-needed'],
    '@typescript-eslint/no-unused-vars': ERROR,
    '@typescript-eslint/no-explicit-any': OFF,
    '@typescript-eslint/explicit-function-return-type': OFF,
    '@typescript-eslint/explicit-member-accessibility': OFF,
    '@typescript-eslint/no-namespace': OFF,
    '@typescript-eslint/explicit-module-boundary-types': OFF
  }
};


================================================
FILE: .github/ISSUE_TEMPLATE/1.bug_report.md
================================================
---
name: 🐛 Bug report
about: Report a reproducible bug or regression
title: ''
labels: ''
assignees: ''
---

<!--
Thanks for taking the time to file a bug report! Please fill out this form as completely as possible.
If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Q&A" section.
-->

### What version of schema-typed are you using?

<!-- For example: 2.0.0 -->

### Describe the Bug

<!-- A clear and concise description of what the bug is.-->

### Expected Behavior

<!-- A clear and concise description of what you expected to happen.-->

### To Reproduce

<!-- Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below. -->


================================================
FILE: .github/ISSUE_TEMPLATE/2.feature_request.md
================================================
---
name: 💄 Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---

<!--
Thanks for taking the time to file a feature request! Please fill out this form as completely as possible.
Feature requests will be converted to the GitHub Discussions "Ideas" section.
-->

### What problem does this feature solve?

<!--
Explain your use case, context, and rationale behind this feature request.
-->

### What does the proposed API look like?

<!--
Describe how you propose to solve the problem and provide code samples of how the API would work once implemented.
-->


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 🤔 Ask a question
    url: https://github.com/rsuite/rsuite/discussions
    about: Ask questions and discuss with other community members


================================================
FILE: .github/workflows/nodejs-ci.yml
================================================
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
        node-version: [16.x, 18.x, 20.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm run build --if-present
      - run: npm test

      - name: Coveralls GitHub Action
        uses: coverallsapp/github-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

    


================================================
FILE: .github/workflows/nodejs-publish.yml
================================================
# see https://help.github.com/cn/actions/language-and-framework-guides/publishing-nodejs-packages

name: Node.js Package

on:
  push:
    tags: ['*']

jobs:
  publish:
    name: 'Publish'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      # Setup .npmrc file to publish to npm
      - uses: actions/setup-node@v3
        with:
          node-version: '12.x'
          registry-url: 'https://registry.npmjs.org'
      - name: Install dependencies
        run: npm install
      - run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

lib
es

.vscode
yarn.lock
.DS_Store

================================================
FILE: .mocharc.js
================================================
'use strict';

// https://github.com/mochajs/mocha-examples/tree/master/packages/typescript-babel
const config = {
  diff: true,
  extension: ['js', 'ts'],
  package: './package.json',
  reporter: 'spec',
  slow: 75,
  timeout: 2000,
  ui: 'bdd',
  require: 'ts-node/register',
  'watch-files': ['src/**/*.ts', 'test/**/*.js']
};

const M = process.env.M;

/**
 * @example:
 * M=ObjectType npm run tdd
 */
if (M) {
  config.spec = 'test/' + M + 'Spec.js';
}

module.exports = config;


================================================
FILE: .npmignore
================================================
src/
types/*.json
types/test.ts



================================================
FILE: .npmrc
================================================
message="build: bump %s"


================================================
FILE: .prettierrc.js
================================================
module.exports = {
  printWidth: 100,
  tabWidth: 2,
  singleQuote: true,
  arrowParens: 'avoid',
  trailingComma: 'none'
};


================================================
FILE: CHANGELOG.md
================================================
## [2.4.2](https://github.com/rsuite/schema-typed/compare/v2.4.1...v2.4.2) (2025-04-11)


### Bug Fixes

* prevent infinite loops in circular proxy validation ([#85](https://github.com/rsuite/schema-typed/issues/85)) ([4d147b9](https://github.com/rsuite/schema-typed/commit/4d147b94f8f5752530984ab1602e53eb2c0c708e))



## [2.4.1](https://github.com/rsuite/schema-typed/compare/v2.4.0...v2.4.1) (2025-03-24)


### Bug Fixes

* when schema is empty getFieldType throw error with nestedObject ([#84](https://github.com/rsuite/schema-typed/issues/84)) ([1c6754e](https://github.com/rsuite/schema-typed/commit/1c6754edfad2ba426e8610b86111c1e9c9809e04))



# [2.4.0](https://github.com/rsuite/schema-typed/compare/v2.3.0...v2.4.0) (2025-03-24)


### Features

* Array support explicit type ([#83](https://github.com/rsuite/schema-typed/issues/83)) ([e716ab4](https://github.com/rsuite/schema-typed/commit/e716ab4b9f20d66da8e3b6b1d2ae46181c59641a))



# [2.3.0](https://github.com/rsuite/schema-typed/compare/v2.2.2...v2.3.0) (2025-02-06)


### Features

* **ArrayType:** enhance nested array validation ([#82](https://github.com/rsuite/schema-typed/issues/82)) ([db389b9](https://github.com/rsuite/schema-typed/commit/db389b90a016627982202214dee23f7ee34d6de4))



## [2.2.2](https://github.com/rsuite/schema-typed/compare/2.2.1...2.2.2) (2024-04-12)



## [2.2.1](https://github.com/rsuite/schema-typed/compare/2.2.0...2.2.1) (2024-04-12)


### Bug Fixes

* **ObjectType:** fix required message for properties not being replaced ([#79](https://github.com/rsuite/schema-typed/issues/79)) ([2aab276](https://github.com/rsuite/schema-typed/commit/2aab2768994b42d3572c2d90a926329912811c80))



# [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)


### Bug Fixes

* wrong error message when parameter is 0 ([#69](https://github.com/rsuite/schema-typed/issues/69)) ([8b399f7](https://github.com/rsuite/schema-typed/commit/8b399f78143dbf36dd2c837c992687c7560027b3))



## [2.1.2](https://github.com/rsuite/schema-typed/compare/2.1.1...2.1.2) (2023-03-10)


### Bug Fixes

* **build:** fix unpublished source code ([#67](https://github.com/rsuite/schema-typed/issues/67)) ([c21ae0a](https://github.com/rsuite/schema-typed/commit/c21ae0a94578907e3fdd0467e5d1a1e3ec7c4d85))



## [2.1.1](https://github.com/rsuite/schema-typed/compare/2.1.0...2.1.1) (2023-03-08)

- chore: change the compilation target of TypeScript from esnext to es2019

# [2.1.0](https://github.com/rsuite/schema-typed/compare/2.0.4...2.1.0) (2023-03-02)

### Features

- addAsyncRule to allow sync and async rules to run ([#63](https://github.com/rsuite/schema-typed/issues/63)) ([574f9ad](https://github.com/rsuite/schema-typed/commit/574f9ad973af97b8c1bae44c3fcfa3dad608c4d6))

## [2.0.4](https://github.com/rsuite/schema-typed/compare/2.0.3...2.0.4) (2023-03-01)

### Bug Fixes

- promises where not allowed by type ([#61](https://github.com/rsuite/schema-typed/issues/61)) ([9cc665c](https://github.com/rsuite/schema-typed/commit/9cc665c4f72b5a22942d351c961263c179888a7a))

## [2.0.3](https://github.com/rsuite/schema-typed/compare/2.0.2...2.0.3) (2022-06-30)

### Bug Fixes

- **ObjectType:** specifies type of property `object` in the `ObjectType` check result ([#46](https://github.com/rsuite/schema-typed/issues/46)) ([0571e09](https://github.com/rsuite/schema-typed/commit/0571e097217b0c999acaf9e5780bdd289aa46a46))

# 2.0.2

- build(deps): add @babel/runtime #37

# 2.0.1

- fix ArrayType.of type error #35

# 2.0.0

- feat(locales): add default error messages for all checks ([#27](https://github.com/rsuite/schema-typed/issues/27)) ([03e21d7](https://github.com/rsuite/schema-typed/commit/03e21d77e9a6e0cd4fddcb1adfe8c485025f246b))
- refactor: refactor the project through typescript.
- feat(MixedType): Added support for `when` method on all types
- feat(MixedType): Replace Type with MixedType.
- feat(ObjectType): Support nested objects in the `shape` method of ObjectType.

# 1.5.1

- Update the typescript definition of `addRule`

# 1.5.0

- Added support for `isRequiredOrEmpty` in StringType and ArrayType

# 1.4.0

- Adding the typescript types declaration in to package

# 1.3.1

- Fixed an issue where `isOneOf` was not valid in `StringType` (#18)

# 1.3.0

- Added support for ESM

# 1.2.2

> Aug 30, 2019

- **Bugfix**: Fix an issue where addRule is not called

# 1.2.0

> Aug 20, 2019

- **Feature**: Support for async check. ([#14])

---

[#14]: https://github.com/rsuite/rsuite/pull/14


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 RSuite Community

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# schema-typed

Schema for data modeling & validation

[![npm][npm-badge]][npm] [![GitHub Actions][actions-svg]][actions-home] [![Coverage Status][soverage-svg]][soverage]

## Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Installation](#installation)
- [Usage](#usage)
  - [Getting Started](#getting-started)
  - [Multiple verification](#multiple-verification)
  - [Custom verification](#custom-verification)
    - [Field dependency validation](#field-dependency-validation)
    - [Asynchronous check](#asynchronous-check)
  - [Validate nested objects](#validate-nested-objects)
  - [Combine](#combine)
- [API](#api)
  - [SchemaModel](#schemamodel)
    - [`static combine(...models)`](#static-combinemodels)
    - [`check(data: object)`](#checkdata-object)
    - [`checkAsync(data: object)`](#checkasyncdata-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)
    - [`isRequired(errorMessage?: string, trim: boolean = true)`](#isrequirederrormessage-string-trim-boolean--true)
    - [`isRequiredOrEmpty(errorMessage?: string, trim: boolean = true)`](#isrequiredoremptyerrormessage-string-trim-boolean--true)
    - [`addRule(onValid: Function, errorMessage?: string, priority: boolean)`](#addruleonvalid-function-errormessage-string-priority-boolean)
    - [`addAsyncRule(onValid: Function, errorMessage?: string, priority: boolean)`](#addasyncruleonvalid-function-errormessage-string-priority-boolean)
    - [`when(condition: (schemaSpec: SchemaDeclaration<DataType, ErrorMsgType>) => Type)`](#whencondition-schemaspec-schemadeclarationdatatype-errormsgtype--type)
    - [`check(value: ValueType, data?: DataType):CheckResult`](#checkvalue-valuetype-data-datatypecheckresult)
    - [`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)
    - [`isEmail(errorMessage?: string)`](#isemailerrormessage-string)
    - [`isURL(errorMessage?: string)`](#isurlerrormessage-string)
    - [`isOneOf(items: string[], errorMessage?: string)`](#isoneofitems-string-errormessage-string)
    - [`containsLetter(errorMessage?: string)`](#containslettererrormessage-string)
    - [`containsUppercaseLetter(errorMessage?: string)`](#containsuppercaselettererrormessage-string)
    - [`containsLowercaseLetter(errorMessage?: string)`](#containslowercaselettererrormessage-string)
    - [`containsLetterOnly(errorMessage?: string)`](#containsletteronlyerrormessage-string)
    - [`containsNumber(errorMessage?: string)`](#containsnumbererrormessage-string)
    - [`pattern(regExp: RegExp, errorMessage?: string)`](#patternregexp-regexp-errormessage-string)
    - [`rangeLength(minLength: number, maxLength: number, errorMessage?: string)`](#rangelengthminlength-number-maxlength-number-errormessage-string)
    - [`minLength(minLength: number, errorMessage?: string)`](#minlengthminlength-number-errormessage-string)
    - [`maxLength(maxLength: number, errorMessage?: string)`](#maxlengthmaxlength-number-errormessage-string)
  - [NumberType(errorMessage?: string)](#numbertypeerrormessage-string)
    - [`isInteger(errorMessage?: string)`](#isintegererrormessage-string)
    - [`isOneOf(items: number[], errorMessage?: string)`](#isoneofitems-number-errormessage-string)
    - [`pattern(regExp: RegExp, errorMessage?: string)`](#patternregexp-regexp-errormessage-string-1)
    - [`range(minLength: number, maxLength: number, errorMessage?: string)`](#rangeminlength-number-maxlength-number-errormessage-string)
    - [`min(min: number, errorMessage?: string)`](#minmin-number-errormessage-string)
    - [`max(max: number, errorMessage?: string)`](#maxmax-number-errormessage-string)
  - [ArrayType(errorMessage?: string)](#arraytypeerrormessage-string)
    - [`isRequiredOrEmpty(errorMessage?: string)`](#isrequiredoremptyerrormessage-string)
    - [`rangeLength(minLength: number, maxLength: number, errorMessage?: string)`](#rangelengthminlength-number-maxlength-number-errormessage-string-1)
    - [`minLength(minLength: number, errorMessage?: string)`](#minlengthminlength-number-errormessage-string-1)
    - [`maxLength(maxLength: number, errorMessage?: string)`](#maxlengthmaxlength-number-errormessage-string-1)
    - [`unrepeatable(errorMessage?: string)`](#unrepeatableerrormessage-string)
    - [`of()`](#of)
  - [DateType(errorMessage?: string)](#datetypeerrormessage-string)
    - [`range(min: Date, max: Date, errorMessage?: string)`](#rangemin-date-max-date-errormessage-string)
    - [`min(min: Date, errorMessage?: string)`](#minmin-date-errormessage-string)
    - [`max(max: Date, errorMessage?: string)`](#maxmax-date-errormessage-string)
  - [ObjectType(errorMessage?: string)](#objecttypeerrormessage-string)
    - [`shape(fields: object)`](#shapefields-object)
  - [BooleanType(errorMessage?: string)](#booleantypeerrormessage-string)
- [⚠️ Notes](#-notes)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Installation

```
npm install schema-typed --save
```

## Usage

### Getting Started

```js
import { SchemaModel, StringType, DateType, NumberType, ObjectType, ArrayType } from 'schema-typed';

const model = SchemaModel({
  username: StringType().isRequired('Username required'),
  email: StringType().isEmail('Email required'),
  age: NumberType('Age should be a number').range(18, 30, 'Over the age limit'),
  tags: ArrayType().of(StringType('The tag should be a string').isRequired()),
  role: ObjectType().shape({
    name: StringType().isRequired('Name required'),
    permissions: ArrayType().isRequired('Permissions required')
  })
});

const checkResult = model.check({
  username: 'foobar',
  email: 'foo@bar.com',
  age: 40,
  tags: ['Sports', 'Games', 10],
  role: { name: 'administrator' }
});

console.log(checkResult);
```

`checkResult` return structure is:

```js
{
  username: { hasError: false },
  email: { hasError: false },
  age: { hasError: true, errorMessage: 'Over the age limit' },
  tags: {
    hasError: true,
    array: [
      { hasError: false },
      { hasError: false },
      { hasError: true, errorMessage: 'The tag should be a string' }
    ]
  },
  role: {
    hasError: true,
    object: {
      name: { hasError: false },
      permissions: { hasError: true, errorMessage: 'Permissions required' }
    }
  }
};
```

### Multiple verification

```js
StringType()
  .minLength(6, "Can't be less than 6 characters")
  .maxLength(30, 'Cannot be greater than 30 characters')
  .isRequired('This field required');
```

### Custom verification

Customize a rule with the `addRule` function.

If you are validating a string type of data, you can set a regular expression for custom validation by the `pattern` method.

```js
const model = SchemaModel({
  field1: StringType().addRule((value, data) => {
    return /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(value);
  }, 'Please enter legal characters'),
  field2: StringType().pattern(/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/, 'Please enter legal characters')
});

model.check({ field1: '', field2: '' });

/**
{
  field1: {
    hasError: true,
    errorMessage: 'Please enter legal characters'
  },
  field2: {
    hasError: true,
    errorMessage: 'Please enter legal characters'
  }
};
**/
```

#### Field dependency validation

1. Use the `equalTo` method to verify that the values of two fields are equal.

```js
const model = SchemaModel({
  password: StringType().isRequired(),
  confirmPassword: StringType().equalTo('password')
});
```

2. Use the `addRule` method to create a custom validation rule.

```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

For example, verify that the mailbox is duplicated

```js
function asyncCheckEmail(email) {
  return new Promise(resolve => {
    setTimeout(() => {
      if (email === 'foo@domain.com') {
        resolve(false);
      } else {
        resolve(true);
      }
    }, 500);
  });
}

const model = SchemaModel({
  email: StringType()
    .isEmail('Please input the correct email address')
    .addAsyncRule((value, data) => {
      return asyncCheckEmail(value);
    }, 'Email address already exists')
    .isRequired('This field cannot be empty')
});

model.checkAsync({ email: 'foo@domain.com' }).then(checkResult => {
  console.log(checkResult);
  /**
  {
    email: {
      hasError: true,
      errorMessage: 'Email address already exists'
    }
  };
  **/
});
```

### Validate nested objects

Validate nested objects, which can be defined using the `ObjectType().shape` method. E.g:

```js
const model = SchemaModel({
  id: NumberType().isRequired('This field required'),
  name: StringType().isRequired('This field required'),
  info: ObjectType().shape({
    email: StringType().isEmail('Should be an email'),
    age: NumberType().min(18, 'Age should be greater than 18 years old')
  })
});

const user = {
  id: 1,
  name: '',
  info: { email: 'schema-type', age: 17 }
};

model.check(data);

/**
 {
  "id": { "hasError": false },
  "name": { "hasError": true, "errorMessage": "This field required" },
  "info": {
    "hasError": true,
    "object": {
      "email": { "hasError": true, "errorMessage": "Should be an email" },
      "age": { "hasError": true, "errorMessage": "Age should be greater than 18 years old" }
    }
  }
}
*/
```

### Combine

`SchemaModel` provides a static method `combine` that can be combined with multiple `SchemaModel` to return a new `SchemaModel`.

```js
const model1 = SchemaModel({
  username: StringType().isRequired('This field required'),
  email: StringType().isEmail('Should be an email')
});

const model2 = SchemaModel({
  username: StringType().minLength(7, "Can't be less than 7 characters"),
  age: NumberType().range(18, 30, 'Age should be greater than 18 years old')
});

const model3 = SchemaModel({
  groupId: NumberType().isRequired('This field required')
});

const model4 = SchemaModel.combine(model1, model2, model3);

model4.check({
  username: 'foobar',
  email: 'foo@bar.com',
  age: 40,
  groupId: 1
});
```

## API

### SchemaModel

SchemaModel is a JavaScript schema builder for data model creation and validation.

#### `static combine(...models)`

A static method for merging multiple models.

```js
const model1 = SchemaModel({
  username: StringType().isRequired('This field required')
});

const model2 = SchemaModel({
  email: StringType().isEmail('Please input the correct email address')
});

const model3 = SchemaModel.combine(model1, model2);
```

#### `check(data: object)`

Check whether the data conforms to the model shape definition. Return a check result.

```js
const model = SchemaModel({
  username: StringType().isRequired('This field required'),
  email: StringType().isEmail('Please input the correct email address')
});

model.check({
  username: 'root',
  email: 'root@email.com'
});
```

#### `checkAsync(data: object)`

Asynchronously check whether the data conforms to the model shape definition. Return a check result.

```js
const model = SchemaModel({
  username: StringType()
    .isRequired('This field required')
    .addRule(value => {
      return new Promise(resolve => {
        // Asynchronous processing logic
      });
    }, 'Username already exists'),
  email: StringType().isEmail('Please input the correct email address')
});

model
  .checkAsync({
    username: 'root',
    email: 'root@email.com'
  })
  .then(result => {
    // Data verification result
  });
```

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

Check whether a field in the data conforms to the model shape definition. Return a check result.

```js
const model = SchemaModel({
  username: StringType().isRequired('This field required'),
  email: StringType().isEmail('Please input the correct email address')
});

const data = {
  username: 'root'
};

model.checkForField('username', data);
```

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

Asynchronously check whether a field in the data conforms to the model shape definition. Return a check result.

```js
const model = SchemaModel({
  username: StringType()
    .isRequired('This field required')
    .addAsyncRule(value => {
      return new Promise(resolve => {
        // Asynchronous processing logic
      });
    }, 'Username already exists'),
  email: StringType().isEmail('Please input the correct email address')
});

const data = {
  username: 'root'
};

model.checkForFieldAsync('username', data).then(result => {
  // Data verification result
});
```

### MixedType()

Creates a type that matches all types. All types inherit from this base type.

#### `isRequired(errorMessage?: string, trim: boolean = true)`

```js
MixedType().isRequired('This field required');
```

#### `isRequiredOrEmpty(errorMessage?: string, trim: boolean = true)`

```js
MixedType().isRequiredOrEmpty('This field required');
```

#### `addRule(onValid: Function, errorMessage?: string, priority: boolean)`

```js
MixedType().addRule((value, data) => {
  return /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(value);
}, 'Please enter a legal character.');
```

#### `addAsyncRule(onValid: Function, errorMessage?: string, priority: boolean)`

```js
MixedType().addAsyncRule((value, data) => {
  return new Promise(resolve => {
    // Asynchronous processing logic
  });
}, 'Please enter a legal character.');
```

#### `when(condition: (schemaSpec: SchemaDeclaration<DataType, ErrorMsgType>) => Type)`

Conditional validation, the return value is a new type.

```ts
const model = SchemaModel({
  option: StringType().isOneOf(['a', 'b', 'other']),
  other: StringType().when(schema => {
    const { value } = schema.option;
    return value === 'other' ? StringType().isRequired('Other required') : StringType();
  })
});

/**
{
  option: { hasError: false },
  other: { hasError: false }
}
*/
model.check({ option: 'a', other: '' });

/*
{
  option: { hasError: false },
  other: { hasError: true, errorMessage: 'Other required' }
}
*/
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`

```js
const type = MixedType().addRule(v => {
  if (typeof v === 'number') {
    return true;
  }
  return false;
}, 'Please enter a valid number');

type.check('1'); //  { hasError: true, errorMessage: 'Please enter a valid number' }
type.check(1); //  { hasError: false }
```

#### `checkAsync(value: ValueType, data?: DataType):Promise<CheckResult>`

```js
const type = MixedType().addRule(v => {
  return new Promise(resolve => {
    setTimeout(() => {
      if (typeof v === 'number') {
        resolve(true);
      } else {
        resolve(false);
      }
    }, 500);
  });
}, 'Please enter a valid number');

type.checkAsync('1').then(checkResult => {
  //  { hasError: true, errorMessage: 'Please enter a valid number' }
});
type.checkAsync(1).then(checkResult => {
  //  { hasError: false }
});
```

#### `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)

Define a string type. Supports all the same methods as [MixedType](#mixedtype).

#### `isEmail(errorMessage?: string)`

```js
StringType().isEmail('Please input the correct email address');
```

#### `isURL(errorMessage?: string)`

```js
StringType().isURL('Please enter the correct URL address');
```

#### `isOneOf(items: string[], errorMessage?: string)`

```js
StringType().isOneOf(['Javascript', 'CSS'], 'Can only type `Javascript` and `CSS`');
```

#### `containsLetter(errorMessage?: string)`

```js
StringType().containsLetter('Must contain English characters');
```

#### `containsUppercaseLetter(errorMessage?: string)`

```js
StringType().containsUppercaseLetter('Must contain uppercase English characters');
```

#### `containsLowercaseLetter(errorMessage?: string)`

```js
StringType().containsLowercaseLetter('Must contain lowercase English characters');
```

#### `containsLetterOnly(errorMessage?: string)`

```js
StringType().containsLetterOnly('English characters that can only be included');
```

#### `containsNumber(errorMessage?: string)`

```js
StringType().containsNumber('Must contain numbers');
```

#### `pattern(regExp: RegExp, errorMessage?: string)`

```js
StringType().pattern(/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/, 'Please enter legal characters');
```

#### `rangeLength(minLength: number, maxLength: number, errorMessage?: string)`

```js
StringType().rangeLength(6, 30, 'The number of characters can only be between 6 and 30');
```

#### `minLength(minLength: number, errorMessage?: string)`

```js
StringType().minLength(6, 'Minimum 6 characters required');
```

#### `maxLength(maxLength: number, errorMessage?: string)`

```js
StringType().maxLength(30, 'The maximum is only 30 characters.');
```

### NumberType(errorMessage?: string)

Define a number type. Supports all the same methods as [MixedType](#mixedtype).

#### `isInteger(errorMessage?: string)`

```js
NumberType().isInteger('It can only be an integer');
```

#### `isOneOf(items: number[], errorMessage?: string)`

```js
NumberType().isOneOf([5, 10, 15], 'Can only be `5`, `10`, `15`');
```

#### `pattern(regExp: RegExp, errorMessage?: string)`

```js
NumberType().pattern(/^[1-9][0-9]{3}$/, 'Please enter a legal character.');
```

#### `range(minLength: number, maxLength: number, errorMessage?: string)`

```js
NumberType().range(18, 40, 'Please enter a number between 18 - 40');
```

#### `min(min: number, errorMessage?: string)`

```js
NumberType().min(18, 'Minimum 18');
```

#### `max(max: number, errorMessage?: string)`

```js
NumberType().max(40, 'Maximum 40');
```

### ArrayType(errorMessage?: string)

Define a array type. Supports all the same methods as [MixedType](#mixedtype).

#### `isRequiredOrEmpty(errorMessage?: string)`

```js
ArrayType().isRequiredOrEmpty('This field required');
```

#### `rangeLength(minLength: number, maxLength: number, errorMessage?: string)`

```js
ArrayType().rangeLength(1, 3, 'Choose at least one, but no more than three');
```

#### `minLength(minLength: number, errorMessage?: string)`

```js
ArrayType().minLength(1, 'Choose at least one');
```

#### `maxLength(maxLength: number, errorMessage?: string)`

```js
ArrayType().maxLength(3, "Can't exceed three");
```

#### `unrepeatable(errorMessage?: string)`

```js
ArrayType().unrepeatable('Duplicate options cannot appear');
```

#### `of()`

```js
// for every element of array
ArrayType().of(StringType('The tag should be a string').isRequired());
// for every element of array
ArrayType().of(
  ObjectType().shape({
    name: StringType().isEmail()
  })
);
// just specify the first and the second element
ArrayType().of(
  StringType().isEmail(),
  ObjectType().shape({
    name: StringType().isEmail()
  })
);
```

### DateType(errorMessage?: string)

Define a date type. Supports all the same methods as [MixedType](#mixedtype).

#### `range(min: Date, max: Date, errorMessage?: string)`

```js
DateType().range(
  new Date('08/01/2017'),
  new Date('08/30/2017'),
  'Date should be between 08/01/2017 - 08/30/2017'
);
```

#### `min(min: Date, errorMessage?: string)`

```js
DateType().min(new Date('08/01/2017'), 'Minimum date 08/01/2017');
```

#### `max(max: Date, errorMessage?: string)`

```js
DateType().max(new Date('08/30/2017'), 'Maximum date 08/30/2017');
```

### ObjectType(errorMessage?: string)

Define a object type. Supports all the same methods as [MixedType](#mixedtype).

#### `shape(fields: object)`

```js
ObjectType().shape({
  email: StringType().isEmail('Should be an email'),
  age: NumberType().min(18, 'Age should be greater than 18 years old')
});
```

### BooleanType(errorMessage?: string)

Define a boolean type. Supports all the same methods as [MixedType](#mixedtype).

## ⚠️ Notes

Default check priority:

- 1.isRequired
- 2.All other checks are executed in sequence

If the third argument to addRule is `true`, the priority of the check is as follows:

- 1.addRule
- 2.isRequired
- 3.Predefined rules (if there is no isRequired, value is empty, the rule is not executed)

[npm-badge]: https://img.shields.io/npm/v/schema-typed.svg
[npm]: https://www.npmjs.com/package/schema-typed
[actions-svg]: https://github.com/rsuite/schema-typed/workflows/Node.js%20CI/badge.svg?branch=master
[actions-home]: https://github.com/rsuite/schema-typed/actions/workflows/nodejs-ci.yml
[soverage-svg]: https://coveralls.io/repos/github/rsuite/schema-typed/badge.svg?branch=master
[soverage]: https://coveralls.io/github/rsuite/schema-typed?branch=master


================================================
FILE: package.json
================================================
{
  "name": "schema-typed",
  "version": "2.4.2",
  "description": "Schema for data modeling & validation",
  "main": "lib/index.js",
  "module": "es/index.js",
  "types": "lib/index.d.ts",
  "scripts": {
    "lint": "eslint src/**/*.ts",
    "build": "tsc --outDir lib && tsc -p tsconfig-es.json --outDir es",
    "prepublishOnly": "npm run test && npm run build",
    "tdd": "mocha --watch",
    "test": "npm run lint && npm run test:once",
    "test:once": "nyc --reporter=lcovonly --reporter=html  mocha",
    "doctoc:": "doctoc README.md",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
    "version": "npm run changelog && git add -A"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/rsuite/schema-typed.git"
  },
  "keywords": [
    "schema",
    "validation"
  ],
  "contributors": [
    "A2ZH",
    "Simon Guo <simonguo.2009@gmail.com>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/rsuite/schema-typed/issues"
  },
  "files": [
    "lib",
    "es",
    "src",
    "types"
  ],
  "homepage": "https://github.com/rsuite/schema-typed#readme",
  "dependencies": {
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "@istanbuljs/nyc-config-typescript": "^1.0.2",
    "@types/node": "^20.12.5",
    "@typescript-eslint/eslint-plugin": "^4.29.3",
    "@typescript-eslint/parser": "^4.29.3",
    "chai": "^3.5.0",
    "conventional-changelog-cli": "^2.1.1",
    "coveralls": "^3.1.0",
    "cross-env": "^6.0.3",
    "del": "^6.0.0",
    "eslint": "^6.7.2",
    "eslint-config-prettier": "^6.11.0",
    "eslint-plugin-import": "^2.19.1",
    "eslint-plugin-prettier": "^3.1.4",
    "istanbul": "^0.4.5",
    "mocha": "^10.2.0",
    "nyc": "^15.1.0",
    "object-flaser": "^0.1.1",
    "prettier": "^2.2.1",
    "ts-node": "^10.9.2",
    "typescript": "^4.2.2"
  }
}


================================================
FILE: src/ArrayType.ts
================================================
import { MixedType, arrayTypeSchemaSpec } from './MixedType';
import { PlainObject, CheckResult, ErrorMessageType } from './types';
import { ArrayTypeLocale } from './locales';

export class ArrayType<DataType = any, E = ErrorMessageType> extends MixedType<
  any[],
  DataType,
  E,
  ArrayTypeLocale
> {
  [arrayTypeSchemaSpec]: MixedType<any, DataType, E> | MixedType<any, DataType, E>[];
  private isArrayTypeNested = false;

  constructor(errorMessage?: E | string) {
    super('array');
    super.pushRule({
      onValid: v => {
        // Skip array type check for nested array elements
        if (this.isArrayTypeNested) {
          return true;
        }
        return Array.isArray(v);
      },
      errorMessage: errorMessage || this.locale.type
    });
  }

  rangeLength(
    minLength: number,
    maxLength: number,
    errorMessage: E | string = this.locale.rangeLength
  ) {
    super.pushRule({
      onValid: (value: string[]) => value.length >= minLength && value.length <= maxLength,
      errorMessage,
      params: { minLength, maxLength }
    });
    return this;
  }

  minLength(minLength: number, errorMessage: E | string = this.locale.minLength) {
    super.pushRule({
      onValid: value => value.length >= minLength,
      errorMessage,
      params: { minLength }
    });

    return this;
  }

  maxLength(maxLength: number, errorMessage: E | string = this.locale.maxLength) {
    super.pushRule({
      onValid: value => value.length <= maxLength,
      errorMessage,
      params: { maxLength }
    });
    return this;
  }

  unrepeatable(errorMessage: E | string = this.locale.unrepeatable) {
    super.pushRule({
      onValid: items => {
        const hash: PlainObject = {};
        for (const i in items) {
          if (hash[items[i]]) {
            return false;
          }
          hash[items[i]] = true;
        }
        return true;
      },
      errorMessage
    });

    return this;
  }

  of(...types: MixedType<any, DataType, E>[]) {
    if (types.length === 1) {
      const type = types[0];
      this[arrayTypeSchemaSpec] = type;

      // Mark inner ArrayType as nested when dealing with nested arrays
      if (type instanceof ArrayType) {
        type.isArrayTypeNested = true;
      }

      super.pushRule({
        onValid: (items, data, fieldName) => {
          // For non-array values in nested arrays, pass directly to inner type validation
          if (!Array.isArray(items) && this.isArrayTypeNested) {
            return type.check(items, data, fieldName);
          }

          // For non-array values in non-nested arrays, return array type error
          if (!Array.isArray(items)) {
            return {
              hasError: true,
              errorMessage: this.locale.type
            };
          }

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

            return type.check(value, data, name as string[]);
          });
          const hasError = !!checkResults.find(item => item?.hasError);

          return {
            hasError,
            array: checkResults
          } as CheckResult<string | E>;
        }
      });
    } else {
      this[arrayTypeSchemaSpec] = types;
      super.pushRule({
        onValid: (items, data, fieldName) => {
          const checkResults = items.map((value, index) => {
            const name = Array.isArray(fieldName)
              ? [...fieldName, `[${index}]`]
              : [fieldName, `[${index}]`];

            return types[index].check(value, data, name as string[]);
          });
          const hasError = !!checkResults.find(item => item?.hasError);

          return {
            hasError,
            array: checkResults
          } as CheckResult<string | E>;
        }
      });
    }

    return this;
  }
}

export default function getArrayType<DataType = any, E = string>(errorMessage?: E) {
  return new ArrayType<DataType, E>(errorMessage);
}


================================================
FILE: src/BooleanType.ts
================================================
import { MixedType } from './MixedType';
import { ErrorMessageType } from './types';
import { BooleanTypeLocale } from './locales';

export class BooleanType<DataType = any, E = ErrorMessageType> extends MixedType<
  boolean,
  DataType,
  E,
  BooleanTypeLocale
> {
  constructor(errorMessage?: E | string) {
    super('boolean');
    super.pushRule({
      onValid: v => typeof v === 'boolean',
      errorMessage: errorMessage || this.locale.type
    });
  }
}

export default function getBooleanType<DataType = any, E = string>(errorMessage?: E) {
  return new BooleanType<DataType, E>(errorMessage);
}


================================================
FILE: src/DateType.ts
================================================
import { MixedType } from './MixedType';
import { ErrorMessageType } from './types';
import { DateTypeLocale } from './locales';

export class DateType<DataType = any, E = ErrorMessageType> extends MixedType<
  string | Date,
  DataType,
  E,
  DateTypeLocale
> {
  constructor(errorMessage?: E | string) {
    super('date');
    super.pushRule({
      onValid: value => !/Invalid|NaN/.test(new Date(value).toString()),
      errorMessage: errorMessage || this.locale.type
    });
  }

  range(min: string | Date, max: string | Date, errorMessage: E | string = this.locale.range) {
    super.pushRule({
      onValid: value => new Date(value) >= new Date(min) && new Date(value) <= new Date(max),
      errorMessage,
      params: { min, max }
    });
    return this;
  }

  min(min: string | Date, errorMessage: E | string = this.locale.min) {
    super.pushRule({
      onValid: value => new Date(value) >= new Date(min),
      errorMessage,
      params: { min }
    });
    return this;
  }

  max(max: string | Date, errorMessage: E | string = this.locale.max) {
    super.pushRule({
      onValid: value => new Date(value) <= new Date(max),
      errorMessage,
      params: { max }
    });
    return this;
  }
}

export default function getDateType<DataType = any, E = string>(errorMessage?: E) {
  return new DateType<DataType, E>(errorMessage);
}


================================================
FILE: src/MixedType.ts
================================================
import {
  SchemaDeclaration,
  CheckResult,
  ValidCallbackType,
  AsyncValidCallbackType,
  RuleType,
  ErrorMessageType,
  TypeName,
  PlainObject
} from './types';
import {
  checkRequired,
  createValidator,
  createValidatorAsync,
  isEmpty,
  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';
export const arrayTypeSchemaSpec = 'arrayTypeSchemaSpec';

/**
 * Get the field type from the schema object
 */
export function getFieldType(schemaSpec: any, fieldName: string, nestedObject?: boolean) {
  if (schemaSpec) {
    if (nestedObject) {
      const namePath = fieldName.split('.');
      const currentField = namePath[0];
      const arrayMatch = currentField.match(/(\w+)\[(\d+)\]/);
      if (arrayMatch) {
        const [, arrayField, arrayIndex] = arrayMatch;
        const type = schemaSpec[arrayField];
        if (type?.[arrayTypeSchemaSpec]) {
          const arrayType = type[arrayTypeSchemaSpec];

          if (namePath.length > 1) {
            if (arrayType[schemaSpecKey]) {
              return getFieldType(arrayType[schemaSpecKey], namePath.slice(1).join('.'), true);
            }
            if (Array.isArray(arrayType) && arrayType[parseInt(arrayIndex)][schemaSpecKey]) {
              return getFieldType(
                arrayType[parseInt(arrayIndex)][schemaSpecKey],
                namePath.slice(1).join('.'),
                true
              );
            }
          }
          if (Array.isArray(arrayType)) {
            return arrayType[parseInt(arrayIndex)];
          }
          // Otherwise return the array element type directly
          return arrayType;
        }
        return type;
      } else {
        const type = schemaSpec[currentField];

        if (namePath.length === 1) {
          return type;
        }

        if (namePath.length > 1 && type && type[schemaSpecKey]) {
          return getFieldType(type[schemaSpecKey], namePath.slice(1).join('.'), true);
        }
      }
    }
    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;
  protected required = false;
  protected requiredMessage: E | string = '';
  protected trim = false;
  protected emptyAllowed = false;
  protected rules: RuleType<ValueType, DataType, E | string>[] = [];
  protected priorityRules: RuleType<ValueType, DataType, E | string>[] = [];
  protected fieldLabel?: string;

  $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.locale = Object.assign(name ? locales[name] : {}, locales.mixed) as L & MixedTypeLocale;
  }

  setSchemaOptions(schemaSpec: SchemaDeclaration<DataType, E>, value: any) {
    this.$schemaSpec = schemaSpec;
    this.value = value;
  }

  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: this.fieldLabel || joinName(fieldName)
        })
      };
    }

    const validator = createValidator<ValueType, DataType, E | string>(
      data,
      fieldName,
      this.fieldLabel
    );

    const checkResult = validator(value, this.priorityRules);

    // If the priority rule fails, return the result directly
    if (checkResult) {
      return checkResult;
    }

    if (!this.required && isEmpty(value)) {
      return { hasError: false };
    }

    return validator(value, this.rules) || { hasError: false };
  }

  checkAsync(
    value: any = this.value,
    data?: DataType,
    fieldName?: string | string[]
  ): Promise<CheckResult<E | string>> {
    if (this.required && !checkRequired(value, this.trim, this.emptyAllowed)) {
      return Promise.resolve({
        hasError: true,
        errorMessage: formatErrorMessage(this.requiredMessage, {
          name: this.fieldLabel || joinName(fieldName)
        })
      });
    }

    const validator = createValidatorAsync<ValueType, DataType, E | string>(
      data,
      fieldName,
      this.fieldLabel
    );

    return new Promise(resolve =>
      validator(value, this.priorityRules)
        .then((checkResult: CheckResult<E | string> | void | null) => {
          // If the priority rule fails, return the result directly
          if (checkResult) {
            resolve(checkResult);
          }
        })
        .then(() => {
          if (!this.required && isEmpty(value)) {
            resolve({ hasError: false });
          }
        })
        .then(() => validator(value, this.rules))
        .then((checkResult: CheckResult<E | string> | void | null) => {
          if (checkResult) {
            resolve(checkResult);
          }
          resolve({ hasError: false });
        })
    );
  }
  protected pushRule(rule: RuleType<ValueType, DataType, E | string>) {
    const { onValid, errorMessage, priority, params } = rule;
    const nextRule = {
      onValid,
      params,
      isAsync: rule.isAsync,
      errorMessage: errorMessage || this.rules?.[0]?.errorMessage
    };

    if (priority) {
      this.priorityRules.push(nextRule);
    } else {
      this.rules.push(nextRule);
    }
  }
  addRule(
    onValid: ValidCallbackType<ValueType, DataType, E | string>,
    errorMessage?: E | string | (() => E | string),
    priority?: boolean
  ) {
    this.pushRule({ onValid, errorMessage, priority });
    return this;
  }
  addAsyncRule(
    onValid: AsyncValidCallbackType<ValueType, DataType, E | string>,
    errorMessage?: E | string,
    priority?: boolean
  ) {
    this.pushRule({ onValid, isAsync: true, errorMessage, priority });
    return this;
  }
  isRequired(errorMessage: E | string = this.locale.isRequired, trim = true) {
    this.required = true;
    this.trim = trim;
    this.requiredMessage = errorMessage;
    return this;
  }
  isRequiredOrEmpty(errorMessage: E | string = this.locale.isRequiredOrEmpty, trim = true) {
    this.required = true;
    this.trim = trim;
    this.emptyAllowed = true;
    this.requiredMessage = errorMessage;
    return this;
  }

  /**
   * Define data verification rules based on conditions.
   * @param condition
   * @example
   *
   * ```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, 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: 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;
  }
}

export default function getMixedType<DataType = any, E = ErrorMessageType>() {
  return new MixedType<DataType, E>();
}


================================================
FILE: src/NumberType.ts
================================================
import { MixedType } from './MixedType';
import { ErrorMessageType } from './types';
import { NumberTypeLocale } from './locales';

function toNumber(value: string | number) {
  return +value;
}

export class NumberType<DataType = any, E = ErrorMessageType> extends MixedType<
  number | string,
  DataType,
  E,
  NumberTypeLocale
> {
  constructor(errorMessage?: E | string) {
    super('number');
    super.pushRule({
      onValid: value => /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value + ''),
      errorMessage: errorMessage || this.locale.type
    });
  }

  isInteger(errorMessage: E | string = this.locale.isInteger) {
    super.pushRule({
      onValid: value => /^-?\d+$/.test(value + ''),
      errorMessage
    });

    return this;
  }

  pattern(regexp: RegExp, errorMessage: E | string = this.locale.pattern) {
    super.pushRule({
      onValid: value => regexp.test(value + ''),
      errorMessage,
      params: { regexp }
    });
    return this;
  }

  isOneOf(values: number[], errorMessage: E | string = this.locale.isOneOf) {
    super.pushRule({
      onValid: value => values.includes(toNumber(value)),
      errorMessage,
      params: { values }
    });
    return this;
  }

  range(min: number, max: number, errorMessage: E | string = this.locale.range) {
    super.pushRule({
      onValid: value => toNumber(value) >= min && toNumber(value) <= max,
      errorMessage,
      params: { min, max }
    });
    return this;
  }

  min(min: number, errorMessage: E | string = this.locale.min) {
    super.pushRule({
      onValid: value => toNumber(value) >= min,
      errorMessage,
      params: { min }
    });
    return this;
  }

  max(max: number, errorMessage: E | string = this.locale.max) {
    super.pushRule({
      onValid: value => toNumber(value) <= max,
      errorMessage,
      params: { max }
    });
    return this;
  }
}

export default function getNumberType<DataType = any, E = string>(errorMessage?: E) {
  return new NumberType<DataType, E>(errorMessage);
}


================================================
FILE: src/ObjectType.ts
================================================
import { MixedType, schemaSpecKey } from './MixedType';
import {
  createValidator,
  createValidatorAsync,
  checkRequired,
  isEmpty,
  formatErrorMessage
} from './utils';
import { PlainObject, SchemaDeclaration, CheckResult, ErrorMessageType } from './types';
import { ObjectTypeLocale } from './locales';

export class ObjectType<DataType = any, E = ErrorMessageType> extends MixedType<
  PlainObject,
  DataType,
  E,
  ObjectTypeLocale
> {
  [schemaSpecKey]: SchemaDeclaration<DataType, E>;
  constructor(errorMessage?: E | string) {
    super('object');
    super.pushRule({
      onValid: v => typeof v === 'object',
      errorMessage: errorMessage || this.locale.type
    });
  }

  check(value: PlainObject = this.value, data?: DataType, fieldName?: string | string[]) {
    const check = (value: any, data: any, type: any, childFieldKey?: string) => {
      if (type.required && !checkRequired(value, type.trim, type.emptyAllowed)) {
        return {
          hasError: true,
          errorMessage: formatErrorMessage<E>(type.requiredMessage || type.locale?.isRequired, {
            name: type.fieldLabel || childFieldKey || fieldName
          })
        };
      }

      if (type[schemaSpecKey] && typeof value === 'object') {
        const checkResultObject: any = {};
        let hasError = false;
        Object.entries(type[schemaSpecKey]).forEach(([k, v]) => {
          const checkResult = check(value[k], value, v, k);
          if (checkResult?.hasError) {
            hasError = true;
          }
          checkResultObject[k] = checkResult;
        });

        return { hasError, object: checkResultObject };
      }

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

      if (checkStatus) {
        return checkStatus;
      }

      if (!type.required && isEmpty(value)) {
        return { hasError: false };
      }

      return validator(value, type.rules) || { hasError: false };
    };

    return check(value, data, this) as CheckResult<E | string, DataType>;
  }

  checkAsync(value: PlainObject = this.value, data?: DataType, fieldName?: string | string[]) {
    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: formatErrorMessage<E>(type.requiredMessage || type.locale?.isRequired, {
            name: type.fieldLabel || childFieldKey || fieldName
          })
        });
      }

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

      return new Promise(resolve => {
        if (type[schemaSpecKey] && typeof value === 'object') {
          const checkResult: any = {};
          const checkAll: Promise<unknown>[] = [];
          const keys: string[] = [];
          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: any, index: number) => {
              if (v?.hasError) {
                hasError = true;
              }
              checkResult[keys[index]] = v;
            });

            resolve({ hasError, object: checkResult });
          });
        }

        return validator(value, type.priorityRules)
          .then((checkStatus: CheckResult<E | string, DataType> | void | null) => {
            if (checkStatus) {
              resolve(checkStatus);
            }
          })
          .then(() => {
            if (!type.required && isEmpty(value)) {
              resolve({ hasError: false });
            }
          })
          .then(() => validator(value, type.rules))
          .then((checkStatus: CheckResult<E | string, DataType> | void | null) => {
            if (checkStatus) {
              resolve(checkStatus);
            }
            resolve({ hasError: false });
          });
      });
    };

    return check(value, data, this) as Promise<CheckResult<E | string, DataType>>;
  }

  /**
   * @example
   * ObjectType().shape({
   *  name: StringType(),
   *  age: NumberType()
   * })
   */
  shape(fields: SchemaDeclaration<DataType, E>) {
    this[schemaSpecKey] = fields;
    return this;
  }
}

export default function getObjectType<DataType = any, E = string>(errorMessage?: E) {
  return new ObjectType<DataType, E>(errorMessage);
}


================================================
FILE: src/Schema.ts
================================================
import { SchemaDeclaration, SchemaCheckResult, CheckResult, PlainObject } from './types';
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>;
  private data: PlainObject;
  private checkedFields: string[] = [];
  private checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};

  constructor(schema: SchemaDeclaration<DataType, ErrorMsgType>) {
    this.$spec = schema;
  }

  private getFieldType<T extends keyof DataType>(
    fieldName: T,
    nestedObject?: boolean
  ): SchemaDeclaration<DataType, ErrorMsgType>[T] {
    return getFieldType(this.$spec, fieldName as string, nestedObject);
  }

  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] = checkResult;
  }

  private setSchemaOptionsForAllType(data: PlainObject) {
    if (data === this.data) {
      return;
    }

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

    this.data = data;
  }

  /**
   * 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);
  }

  /**
   * Get the schema specification
   */
  getSchemaSpec() {
    return this.$spec;
  }
  _checkForField<T extends keyof DataType>(
    fieldName: T,
    data: DataType,
    options: CheckOptions = {}
  ): CheckResult<ErrorMsgType | string> {
    this.setSchemaOptionsForAllType(data);

    const { nestedObject } = options;

    // Add current field to checked list
    this.checkedFields = [...this.checkedFields, fieldName as string];

    const fieldChecker = this.getFieldType(fieldName, nestedObject);

    if (!fieldChecker) {
      return { hasError: false };
    }

    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;

      fieldChecker.otherFields?.forEach((field: string) => {
        if (!this.checkedFields.includes(field)) {
          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;
  }

  checkForField<T extends keyof DataType>(
    fieldName: T,
    data: DataType,
    options: CheckOptions = {}
  ): CheckResult<ErrorMsgType | string> {
    const result = this._checkForField(fieldName, data, options);
    // clean checked fields after check finished
    this.checkedFields = [];
    return result;
  }

  checkForFieldAsync<T extends keyof DataType>(
    fieldName: T,
    data: DataType,
    options: CheckOptions = {}
  ): Promise<CheckResult<ErrorMsgType | string>> {
    this.setSchemaOptionsForAllType(data);

    const { nestedObject } = options;
    const fieldChecker = this.getFieldType(fieldName, nestedObject);

    if (!fieldChecker) {
      // fieldValue can be anything if no schema defined
      return Promise.resolve({ hasError: false });
    }

    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: SchemaCheckResult<DataType, ErrorMsgType> = {};
    Object.keys(this.$spec).forEach(key => {
      if (typeof data === 'object') {
        checkResult[key] = this.checkForField(key as T, data);
      }
    });

    return checkResult;
  }

  checkAsync<T extends keyof DataType>(data: DataType) {
    const checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};
    const promises: Promise<CheckResult<ErrorMsgType | string>>[] = [];
    const keys: string[] = [];

    Object.keys(this.$spec).forEach((key: string) => {
      keys.push(key);
      promises.push(this.checkForFieldAsync(key as T, data));
    });

    return Promise.all(promises).then(values => {
      for (let i = 0; i < values.length; i += 1) {
        checkResult[keys[i]] = values[i];
      }

      return checkResult;
    });
  }
}

export function SchemaModel<DataType = PlainObject, ErrorMsgType = string>(
  o: SchemaDeclaration<DataType, ErrorMsgType>
) {
  return new Schema<DataType, ErrorMsgType>(o);
}

SchemaModel.combine = function combine<DataType = any, ErrorMsgType = string>(
  ...specs: Schema<any, ErrorMsgType>[]
) {
  return new Schema<DataType, ErrorMsgType>(
    specs
      .map(model => model.$spec)
      .reduce((accumulator, currentValue) => Object.assign(accumulator, currentValue), {} as any)
  );
};


================================================
FILE: src/StringType.ts
================================================
import { MixedType } from './MixedType';
import { ErrorMessageType } from './types';
import { StringTypeLocale } from './locales';

export class StringType<DataType = any, E = ErrorMessageType> extends MixedType<
  string,
  DataType,
  E,
  StringTypeLocale
> {
  constructor(errorMessage?: E | string) {
    super('string');
    super.pushRule({
      onValid: v => typeof v === 'string',
      errorMessage: errorMessage || this.locale.type
    });
  }

  containsLetter(errorMessage: E | string = this.locale.containsLetter) {
    super.pushRule({
      onValid: v => /[a-zA-Z]/.test(v),
      errorMessage
    });
    return this;
  }

  containsUppercaseLetter(errorMessage: E | string = this.locale.containsUppercaseLetter) {
    super.pushRule({
      onValid: v => /[A-Z]/.test(v),
      errorMessage
    });
    return this;
  }

  containsLowercaseLetter(errorMessage: E | string = this.locale.containsLowercaseLetter) {
    super.pushRule({
      onValid: v => /[a-z]/.test(v),
      errorMessage
    });
    return this;
  }

  containsLetterOnly(errorMessage: E | string = this.locale.containsLetterOnly) {
    super.pushRule({
      onValid: v => /^[a-zA-Z]+$/.test(v),
      errorMessage
    });
    return this;
  }

  containsNumber(errorMessage: E | string = this.locale.containsNumber) {
    super.pushRule({
      onValid: v => /[0-9]/.test(v),
      errorMessage
    });
    return this;
  }

  isOneOf(values: string[], errorMessage: E | string = this.locale.isOneOf) {
    super.pushRule({
      onValid: v => !!~values.indexOf(v),
      errorMessage,
      params: { values }
    });
    return this;
  }

  isEmail(errorMessage: E | string = this.locale.isEmail) {
    // 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,}))$/;
    super.pushRule({
      onValid: v => regexp.test(v),
      errorMessage
    });
    return this;
  }

  isURL(
    errorMessage: E | string = this.locale.isURL,
    options?: {
      allowMailto?: boolean;
    }
  ) {
    const regexp = new RegExp(
      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'
    );
    super.pushRule({
      onValid: v => regexp.test(v),
      errorMessage
    });
    return this;
  }
  isHex(errorMessage: E | string = this.locale.isHex) {
    const regexp = /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i;
    super.pushRule({
      onValid: v => regexp.test(v),
      errorMessage
    });
    return this;
  }
  pattern(regexp: RegExp, errorMessage: E | string = this.locale.pattern) {
    super.pushRule({
      onValid: v => regexp.test(v),
      errorMessage,
      params: { regexp }
    });
    return this;
  }

  rangeLength(
    minLength: number,
    maxLength: number,
    errorMessage: E | string = this.locale.rangeLength
  ) {
    super.pushRule({
      onValid: value => value.length >= minLength && value.length <= maxLength,
      errorMessage,
      params: { minLength, maxLength }
    });
    return this;
  }

  minLength(minLength: number, errorMessage: E | string = this.locale.minLength) {
    super.pushRule({
      onValid: value => Array.from(value).length >= minLength,
      errorMessage,
      params: { minLength }
    });
    return this;
  }

  maxLength(maxLength: number, errorMessage: E | string = this.locale.maxLength) {
    super.pushRule({
      onValid: value => Array.from(value).length <= maxLength,
      errorMessage,
      params: { maxLength }
    });
    return this;
  }
}

export default function getStringType<DataType = any, E = string>(errorMessage?: E) {
  return new StringType<DataType, E>(errorMessage);
}


================================================
FILE: src/index.ts
================================================
import { SchemaModel, Schema } from './Schema';
import { default as MixedType } from './MixedType';
import { default as StringType } from './StringType';
import { default as NumberType } from './NumberType';
import { default as ArrayType } from './ArrayType';
import { default as DateType } from './DateType';
import { default as ObjectType } from './ObjectType';
import { default as BooleanType } from './BooleanType';

export type {
  CheckResult,
  SchemaCheckResult,
  SchemaDeclaration,
  CheckType,
  RuleType,
  ValidCallbackType
} from './types';

export {
  SchemaModel,
  Schema,
  MixedType,
  StringType,
  NumberType,
  ArrayType,
  DateType,
  ObjectType,
  BooleanType
};


================================================
FILE: src/locales/default.ts
================================================
export default {
  mixed: {
    isRequired: '${name} is a required field',
    isRequiredOrEmpty: '${name} is a required field',
    equalTo: '${name} must be the same as ${toFieldName}'
  },
  array: {
    type: '${name} must be an array',
    rangeLength: '${name} must contain ${minLength} to ${maxLength} items',
    minLength: '${name} field must have at least ${minLength} items',
    maxLength: '${name} field must have less than or equal to ${maxLength} items',
    unrepeatable: '${name} must have non-repeatable items'
  },
  boolean: {
    type: '${name} must be a boolean'
  },
  date: {
    type: '${name} must be a date',
    min: '${name} field must be later than ${min}',
    max: '${name} field must be at earlier than ${max}',
    range: '${name} field must be between ${min} and ${max}'
  },
  number: {
    type: '${name} must be a number',
    isInteger: '${name} must be an integer',
    pattern: '${name} is invalid',
    isOneOf: '${name} must be one of the following values: ${values}',
    range: '${name} field must be between ${min} and ${max}',
    min: '${name} must be greater than or equal to ${min}',
    max: '${name} must be less than or equal to ${max}'
  },
  string: {
    type: '${name} must be a string',
    containsLetter: '${name} field must contain letters',
    containsUppercaseLetter: '${name} must be a upper case string',
    containsLowercaseLetter: '${name} must be a lowercase string',
    containsLetterOnly: '${name} must all be letters',
    containsNumber: '${name} field must contain numbers',
    isOneOf: '${name} must be one of the following values: ${values}',
    isEmail: '${name} must be a valid email',
    isURL: '${name} must be a valid URL',
    isHex: '${name} must be a valid hexadecimal',
    pattern: '${name} is invalid',
    rangeLength: '${name} must contain ${minLength} to ${maxLength} characters',
    minLength: '${name} must be at least ${minLength} characters',
    maxLength: '${name} must be at most ${maxLength} characters'
  },
  object: {
    type: '${name} must be an object'
  }
};


================================================
FILE: src/locales/index.ts
================================================
import defaultLocale from './default';

export type PickKeys<T> = {
  [keys in keyof T]: T[keys];
};

export type Locale = PickKeys<typeof defaultLocale>;
export type MixedTypeLocale = PickKeys<typeof defaultLocale.mixed>;
export type ArrayTypeLocale = PickKeys<typeof defaultLocale.array> & MixedTypeLocale;
export type ObjectTypeLocale = PickKeys<typeof defaultLocale.object> & MixedTypeLocale;
export type BooleanTypeLocale = PickKeys<typeof defaultLocale.boolean> & MixedTypeLocale;
export type StringTypeLocale = PickKeys<typeof defaultLocale.string> & MixedTypeLocale;
export type NumberTypeLocale = PickKeys<typeof defaultLocale.number> & MixedTypeLocale;
export type DateTypeLocale = PickKeys<typeof defaultLocale.date> & MixedTypeLocale;

export default defaultLocale;


================================================
FILE: src/types.ts
================================================
import { ArrayType } from './ArrayType';
import { BooleanType } from './BooleanType';
import { DateType } from './DateType';
import { NumberType } from './NumberType';
import { StringType } from './StringType';
import { ObjectType } from './ObjectType';

export type TypeName = 'array' | 'string' | 'boolean' | 'number' | 'object' | 'date';

export interface CheckResult<E = string, DataType = PlainObject> {
  hasError?: boolean;
  errorMessage?: E | string;
  object?: {
    [P in keyof DataType]: CheckResult<E>;
  };
  array?: CheckResult<E>[];
}
export type ErrorMessageType = string;
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> = {
  [P in keyof T]: T;
};

export interface RuleType<V, D, E> {
  onValid: AsyncValidCallbackType<V, D, E>;
  errorMessage?: any;
  priority?: boolean;
  params?: any;
  isAsync?: boolean;
}

export type CheckType<X, T, E = ErrorMessageType> = X extends string
  ? StringType<T, E> | DateType<T, E> | NumberType<T, E>
  : X extends number
  ? NumberType<T, E>
  : X extends boolean
  ? BooleanType<T, E>
  : X extends Date
  ? DateType<T, E>
  : X extends Array<any>
  ? ArrayType<T, E>
  : X extends Record<string, unknown>
  ? ObjectType<T, E>
  :
      | StringType<T, E>
      | NumberType<T, E>
      | BooleanType<T, E>
      | ArrayType<T, E>
      | DateType<T, E>
      | ObjectType<T, E>;

export type SchemaDeclaration<T, E = string> = {
  [P in keyof T]: CheckType<T[P], T, E>;
};

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


================================================
FILE: src/utils/basicEmptyCheck.ts
================================================
function basicEmptyCheck(value?: any) {
  return typeof value === 'undefined' || value === null;
}

export default basicEmptyCheck;


================================================
FILE: src/utils/checkRequired.ts
================================================
import basicEmptyCheck from './basicEmptyCheck';
import isEmpty from './isEmpty';

function checkRequired(value: any, trim: boolean, emptyAllowed: boolean) {
  // String trim
  if (trim && typeof value === 'string') {
    value = value.replace(/(^\s*)|(\s*$)/g, '');
  }

  if (emptyAllowed) {
    return !basicEmptyCheck(value);
  }

  // Array
  if (Array.isArray(value)) {
    return !!value.length;
  }

  return !isEmpty(value);
}

export default checkRequired;


================================================
FILE: src/utils/createValidator.ts
================================================
import { CheckResult, RuleType } from '../types';
import formatErrorMessage from './formatErrorMessage';
function isObj(o: unknown): o is Record<PropertyKey, unknown> {
  return o != null && (typeof o === 'object' || typeof o == 'function');
}
function isPromiseLike(v: unknown): v is Promise<unknown> {
  return v instanceof Promise || (isObj(v) && typeof v.then === 'function');
}
/**
 * Create a data validator
 * @param data
 */
export function createValidator<V, D, E>(data?: D, name?: string | string[], label?: string) {
  return (value: V, rules: RuleType<V, D, E>[]): CheckResult<E> | null => {
    for (let i = 0; i < rules.length; i += 1) {
      const { onValid, errorMessage, params, isAsync } = rules[i];
      if (isAsync) continue;
      const checkResult = onValid(value, data, name);
      const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;

      if (checkResult === false) {
        return {
          hasError: true,
          errorMessage: formatErrorMessage<E>(errorMsg, {
            ...params,
            name: label || (Array.isArray(name) ? name.join('.') : name)
          })
        };
      } else if (isPromiseLike(checkResult)) {
        throw new Error(
          'synchronous validator had an async result, you should probably call "checkAsync()"'
        );
      } else if (typeof checkResult === 'object' && (checkResult.hasError || checkResult.array)) {
        return checkResult;
      }
    }

    return null;
  };
}

export default createValidator;


================================================
FILE: src/utils/createValidatorAsync.ts
================================================
import { CheckResult, RuleType } from '../types';
import formatErrorMessage, { joinName } from './formatErrorMessage';

/**
 * Create a data asynchronous validator
 * @param data
 */
export function createValidatorAsync<V, D, E>(data?: D, name?: string | string[], label?: string) {
  function check(errorMessage?: E | string) {
    return (checkResult: CheckResult<E> | boolean): CheckResult<E> | null => {
      if (checkResult === false) {
        return { hasError: true, errorMessage };
      } else if (typeof checkResult === 'object' && (checkResult.hasError || checkResult.array)) {
        return checkResult;
      }
      return null;
    };
  }

  return (value: V, rules: RuleType<V, D, E>[]) => {
    const promises = rules.map(rule => {
      const { onValid, errorMessage, params } = rule;
      const errorMsg = typeof errorMessage === 'function' ? errorMessage() : errorMessage;

      return Promise.resolve(onValid(value, data, name)).then(
        check(
          formatErrorMessage<E>(errorMsg, {
            ...params,
            name: label || joinName(name)
          })
        )
      );
    });

    return Promise.all(promises).then(results =>
      results.find((item: CheckResult<E> | null) => item && item?.hasError)
    );
  };
}

export default createValidatorAsync;


================================================
FILE: src/utils/formatErrorMessage.ts
================================================
import isEmpty from './isEmpty';

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

/**
 * formatErrorMessage('${name} is a required field', {name: 'email'});
 * output: 'email is a required field'
 */
export default function formatErrorMessage<E>(errorMessage?: string | E, params?: any) {
  if (typeof errorMessage === 'string') {
    return errorMessage.replace(/\$\{\s*(\w+)\s*\}/g, (_, key) => {
      return isEmpty(params?.[key]) ? `$\{${key}\}` : params?.[key];
    });
  }

  return errorMessage;
}


================================================
FILE: src/utils/index.ts
================================================
export { default as get } from 'lodash/get';
export { default as set } from 'lodash/set';
export { default as basicEmptyCheck } from './basicEmptyCheck';
export { default as checkRequired } from './checkRequired';
export { default as createValidator } from './createValidator';
export { default as createValidatorAsync } from './createValidatorAsync';
export { default as isEmpty } from './isEmpty';
export { default as formatErrorMessage } from './formatErrorMessage';
export { default as shallowEqual } from './shallowEqual';
export { default as pathTransform } from './pathTransform';


================================================
FILE: src/utils/isEmpty.ts
================================================
function isEmpty(value?: any) {
  return typeof value === 'undefined' || value === null || value === '';
}

export default isEmpty;


================================================
FILE: src/utils/pathTransform.ts
================================================
export default function pathTransform(path: string) {
  const arr = path.split('.');

  if (arr.length === 1) {
    return path;
  }

  return path
    .split('.')
    .map((item, index) => {
      if (index === 0) {
        return item;
      }

      // Check if the item is a number, e.g. `list.0`
      return /^\d+$/.test(item) ? `array.${item}` : `object.${item}`;
    })
    .join('.');
}


================================================
FILE: src/utils/shallowEqual.ts
================================================
/**
 * From: https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js
 * @providesModule shallowEqual
 * @typechecks
 * @flow
 */

const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x: any, y: any): boolean {
  // SameValue algorithm
  if (x === y) {
    // Steps 1-5, 7-10
    // Steps 6.b-6.e: +0 != -0
    // Added the nonzero y check to make Flow happy, but it is redundant
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  }
  // Step 6.a: NaN == NaN
  return x !== x && y !== y;
}

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: any, objB: any): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i += 1) {
    if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }

  return true;
}

export default shallowEqual;


================================================
FILE: test/ArrayTypeSpec.js
================================================
import { expect } from 'chai';
import * as schema from '../src';

const { ArrayType, StringType, NumberType, ObjectType, Schema } = schema;

describe('#ArrayType', () => {
  it('Should be a valid array', () => {
    const schemaData = {
      data: ArrayType().minLength(2, 'error1').of(StringType().isEmail('error2')),
      data2: ArrayType().minLength(2).of(StringType().isEmail())
    };

    const schema = new Schema(schemaData);

    const checkResult = schema.checkForField('data', {
      data: ['simon.guo@hypers.com', 'ddddd@d.com', 'ddd@bbb.com']
    });

    expect(checkResult).to.deep.equal({
      hasError: false,
      array: [{ hasError: false }, { hasError: false }, { hasError: false }]
    });

    const checkResult2 = schema.check({
      data: ['simon.guo@hypers.com', 'error_email', 'ddd@bbb.com']
    });

    expect(checkResult2).to.deep.equal({
      data: {
        hasError: true,
        array: [
          { hasError: false },
          { hasError: true, errorMessage: 'error2' },
          { hasError: false }
        ]
      },
      data2: { hasError: false }
    });

    const checkResult3 = schema.check({
      data2: []
    });

    expect(checkResult3).to.deep.equal({
      data: { hasError: false },
      data2: { hasError: true, errorMessage: 'data2 field must have at least 2 items' }
    });

    const checkResult4 = schema.check({
      data2: ['simon.guo@hypers.com', 'error_email', 'ddd@bbb.com']
    });

    expect(checkResult4).to.deep.equal({
      data: { hasError: false },
      data2: {
        hasError: true,
        array: [
          { hasError: false },
          { hasError: true, errorMessage: 'data2.[1] must be a valid email' },
          { hasError: false }
        ]
      }
    });
  });

  it('Should output default error message ', () => {
    const schemaData = { data: ArrayType().of(StringType().isEmail()) };
    const schema = new Schema(schemaData);
    const checkStatus = schema.checkForField('data', {
      data: ['simon.guo@hypers.com', 'error_email', 'ddd@bbb.com']
    });

    checkStatus.array[1].hasError.should.equal(true);
    checkStatus.array[1].errorMessage.should.equal('data.[1] must be a valid email');
  });

  it('Should be unrepeatable ', () => {
    const schemaData = { data: ArrayType().unrepeatable('error1') };
    const schema = new Schema(schemaData);
    const checkStatus = schema.checkForField('data', { data: ['abc', '123', 'abc'] });

    checkStatus.hasError.should.equal(true);
    checkStatus.errorMessage.should.equal('error1');

    const schemaData2 = { data: ArrayType().unrepeatable() };
    const schema2 = new Schema(schemaData2);
    const checkStatus2 = schema2.checkForField('data', { data: ['abc', '123', 'abc'] });
    checkStatus2.errorMessage.should.equal('data must have non-repeatable items');

    schema.checkForField('data', { data: ['1', '2', '3'] }).hasError.should.equal(false);
  });

  it('Should be required ', () => {
    const schemaData = {
      data: ArrayType().isRequired('error1'),
      data2: ArrayType().isRequired()
    };
    const schema = new Schema(schemaData);
    const checkStatus = schema.checkForField('data', { data: null });
    const checkStatus2 = schema.checkForField('data2', { data2: null });

    checkStatus.hasError.should.equal(true);
    checkStatus.errorMessage.should.equal('error1');
    checkStatus2.errorMessage.should.equal('data2 is a required field');

    schema.checkForField('data', { data: [] }).hasError.should.equal(true);
    schema.checkForField('data', { data: undefined }).hasError.should.equal(true);
  });

  it('Should be within the number of items', () => {
    const schemaData = {
      data: ArrayType().rangeLength(2, 4)
    };
    const schema = new Schema(schemaData);
    schema.checkForField('data', { data: [1, 2] }).hasError.should.equal(false);
    schema.checkForField('data', { data: [1] }).hasError.should.equal(true);
    schema.checkForField('data', { data: [1, 2, 3, 4, 5] }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: [1] })
      .errorMessage.should.equal('data must contain 2 to 4 items');
  });

  it('Should not exceed the maximum number of items', () => {
    const schemaData = {
      data: ArrayType().maxLength(2)
    };
    const schema = new Schema(schemaData);
    schema.checkForField('data', { data: [1, 2, 3] }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: [1, 2, 3] })
      .errorMessage.should.equal('data field must have less than or equal to 2 items');
  });

  it('Should not be less than the maximum number of items', () => {
    const schemaData = {
      data: ArrayType().minLength(2)
    };
    const schema = new Schema(schemaData);
    schema.checkForField('data', { data: [1] }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: [1] })
      .errorMessage.should.equal('data field must have at least 2 items');
  });

  describe('Nested Object', () => {
    const options = {
      nestedObject: true
    };

    it('Should support array nested objects', () => {
      const schemaData = {
        users: ArrayType().of(
          ObjectType('error1').shape({
            email: StringType().isEmail('error2'),
            age: NumberType().min(18, 'error3')
          })
        ),
        users2: ArrayType().of(
          ObjectType().shape({
            email: StringType().isEmail(),
            age: NumberType().min(18)
          })
        )
      };
      const schema = new Schema(schemaData);
      const checkResult = schema.check({
        users: [
          'simon.guo@hypers.com',
          { email: 'error_email', age: 19 },
          { email: 'error_email', age: 17 }
        ]
      });

      expect(checkResult).to.deep.equal({
        users: {
          hasError: true,
          array: [
            { hasError: true, errorMessage: 'error1' },
            {
              hasError: true,
              object: {
                email: { hasError: true, errorMessage: 'error2' },
                age: { hasError: false }
              }
            },
            {
              hasError: true,
              object: {
                email: { hasError: true, errorMessage: 'error2' },
                age: { hasError: true, errorMessage: 'error3' }
              }
            }
          ]
        },
        users2: { hasError: false }
      });

      const schema2 = new Schema(schemaData);
      const checkResult2 = schema2.check({
        users2: [
          'simon.guo@hypers.com',
          { email: 'error_email', age: 19 },
          { email: 'error_email', age: 17 }
        ]
      });

      expect(checkResult2).to.deep.equal({
        users: { hasError: false },
        users2: {
          hasError: true,
          array: [
            { hasError: true, errorMessage: 'users2.[0] must be an object' },
            {
              hasError: true,
              object: {
                email: { hasError: true, errorMessage: 'email must be a valid email' },
                age: { hasError: false }
              }
            },
            {
              hasError: true,
              object: {
                email: { hasError: true, errorMessage: 'email must be a valid email' },
                age: { hasError: true, errorMessage: 'age must be greater than or equal to 18' }
              }
            }
          ]
        }
      });
    });

    it('Should validate nested array with required fields', () => {
      const schema = new Schema({
        address: ArrayType().of(
          ObjectType().shape({
            city: StringType().isRequired('City is required'),
            postCode: StringType().isRequired('Post code is required')
          })
        )
      });

      const checkResult = schema.check({
        address: [
          { city: 'Shanghai', postCode: '200000' },
          { city: 'Beijing', postCode: '100000' }
        ]
      });

      expect(checkResult).to.deep.equal({
        address: {
          hasError: false,
          array: [
            {
              hasError: false,
              object: { city: { hasError: false }, postCode: { hasError: false } }
            },
            {
              hasError: false,
              object: { city: { hasError: false }, postCode: { hasError: false } }
            }
          ]
        }
      });

      const checkResult2 = schema.check({
        address: [{ postCode: '200000' }, { city: 'Beijing' }]
      });

      expect(checkResult2).to.deep.equal({
        address: {
          hasError: true,
          array: [
            {
              hasError: true,
              object: {
                city: {
                  hasError: true,
                  errorMessage: 'City is required'
                },
                postCode: {
                  hasError: false
                }
              }
            },
            {
              hasError: true,
              object: {
                city: {
                  hasError: false
                },
                postCode: {
                  hasError: true,
                  errorMessage: 'Post code is required'
                }
              }
            }
          ]
        }
      });
    });

    it('Should check a field in an array', () => {
      const schema = new Schema({
        address: ArrayType().of(
          ObjectType().shape({
            city: StringType().isRequired('City is required'),
            postCode: StringType().isRequired('Post code is required')
          })
        )
      });

      const checkResult = schema.checkForField(
        'address[0].city',
        { address: [{ city: 'Shanghai' }] },
        options
      );

      expect(checkResult).to.deep.equal({
        hasError: false
      });

      const checkResult2 = schema.checkForField(
        'address[1].postCode',
        { address: [{ postCode: '' }] },
        options
      );

      expect(checkResult2).to.deep.equal({
        hasError: true,
        errorMessage: 'Post code is required'
      });
    });

    it('Should check primitive type array items', () => {
      const schema = new Schema({
        emails: ArrayType().of(StringType().isEmail('Invalid email')),
        numbers: ArrayType().of(NumberType().min(0, 'Must be positive'))
      });

      // Test valid email
      expect(
        schema.checkForField('emails[0]', { emails: ['test@example.com'] }, options)
      ).to.deep.equal({
        hasError: false
      });

      // Test invalid email
      expect(
        schema.checkForField('emails[0]', { emails: ['invalid-email'] }, options)
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Invalid email'
      });

      // Test negative number
      expect(schema.checkForField('numbers[0]', { numbers: [-1] }, options)).to.deep.equal({
        hasError: true,
        errorMessage: 'Must be positive'
      });
    });

    it('Should support nested arrays', () => {
      const schema = new Schema({
        matrix: ArrayType().of(ArrayType().of(NumberType().min(0, 'Must be positive')))
      });

      // Test negative number in nested array
      expect(
        schema.checkForField(
          'matrix[0][1]',
          {
            matrix: [[0, -1]]
          },
          options
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Must be positive'
      });
    });

    it('Should support nested arrays in check', () => {
      const schema = new Schema({
        matrix: ArrayType().of(ArrayType().of(NumberType().min(0, 'Must be positive')))
      });

      // Test negative number in nested array
      expect(
        schema.check({
          matrix: [[0, -1]]
        })
      ).to.deep.equal({
        matrix: {
          array: [
            {
              array: [
                {
                  hasError: false
                },
                {
                  errorMessage: 'Must be positive',
                  hasError: true
                }
              ],
              hasError: true
            }
          ],
          hasError: true
        }
      });
    });

    it('Should validate array elements with complex validation rules', () => {
      const schema = new Schema({
        users: ArrayType().of(
          ObjectType().shape({
            name: StringType().isRequired('Name is required'),
            age: NumberType().min(18, 'Must be an adult'),
            email: StringType().isEmail('Invalid email format')
          })
        )
      });

      // Test valid name
      expect(
        schema.checkForField(
          'users[0].name',
          {
            users: [{ name: 'John Doe' }]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: false
      });

      // Test required field in array object
      expect(
        schema.checkForField(
          'users[0].name',
          {
            users: [{ name: '' }]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Name is required'
      });

      // Test minimum value in array object
      expect(
        schema.checkForField(
          'users[0].age',
          {
            users: [{ age: 16 }]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Must be an adult'
      });

      // Test email format in array object
      expect(
        schema.checkForField(
          'users[0].email',
          {
            users: [{ email: 'invalid-email' }]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Invalid email format'
      });
    });

    it('Should validate nested array objects (max 3 levels)', () => {
      const schema = new Schema({
        users: ArrayType().of(
          ObjectType().shape({
            name: StringType().isRequired('Name required'),
            tasks: ArrayType().of(
              ObjectType().shape({
                title: StringType().isRequired('Task title required'),
                assignees: ArrayType().of(
                  ObjectType().shape({
                    email: StringType().isEmail('Invalid email format'),
                    role: StringType()
                      .isOneOf(['owner', 'admin', 'member'], 'Invalid role')
                      .isRequired('Role required'),
                    priority: NumberType()
                      .min(1, 'Priority too low')
                      .max(5, 'Priority too high')
                      .isRequired('Priority required')
                  })
                )
              })
            )
          })
        )
      });

      // Test valid email
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].email',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: false
      });

      // Test invalid email
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].email',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'invalid-email',
                        role: 'owner',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Invalid email format'
      });

      // Test valid role
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].role',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: false
      });

      // Test invalid role
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].role',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'guest',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Invalid role'
      });

      // Test valid priority
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].priority',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: false
      });

      // Test invalid priority (too high)
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].priority',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 6
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Priority too high'
      });

      // Test invalid priority (too low)
      expect(
        schema.checkForField(
          'users[0].tasks[0].assignees[0].priority',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 0
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Priority too low'
      });

      // Test required field present
      expect(
        schema.checkForField(
          'users[0].tasks[0].title',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: 'Frontend Development',
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: false
      });

      // Test required field missing
      expect(
        schema.checkForField(
          'users[0].tasks[0].title',
          {
            users: [
              {
                name: 'John Doe',
                tasks: [
                  {
                    title: null,
                    assignees: [
                      {
                        email: 'test@example.com',
                        role: 'owner',
                        priority: 3
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            nestedObject: true
          }
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'Task title required'
      });
    });

    it('Should validate explicit nested array type', () => {
      const schema = new Schema({
        users: ArrayType().of(
          StringType().isRequired().isEmail(),
          ObjectType().shape({
            name: StringType().isEmail(),
            email: StringType().isEmail()
          })
        )
      });

      expect(
        schema.checkForField(
          'users[0]',
          {
            users: ['xx']
          },
          options
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'users[0] must be a valid email'
      });

      expect(
        schema.checkForField(
          'users[0]',
          {
            users: ['ddd@bbb.com']
          },
          options
        )
      ).to.deep.equal({
        hasError: false
      });

      expect(
        schema.checkForField(
          'users[1].name',
          {
            users: ['ddd@bbb.com', { name: 'xxx' }]
          },
          options
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'users[1].name must be a valid email'
      });

      expect(
        schema.checkForField(
          'users[1].name',
          {
            users: ['ddd@bbb.com', { name: 'ddd@bbb.com' }]
          },
          options
        )
      ).to.deep.equal({
        hasError: false
      });

      expect(
        schema.check({
          users: [
            'xxx',
            {
              name: 'xx',
              email: 'xx'
            }
          ]
        })
      ).to.deep.equal({
        users: {
          hasError: true,
          array: [
            {
              hasError: true,
              errorMessage: 'users.[0] must be a valid email'
            },
            {
              hasError: true,
              object: {
                name: {
                  hasError: true,
                  errorMessage: 'name must be a valid email'
                },
                email: {
                  hasError: true,
                  errorMessage: 'email must be a valid email'
                }
              }
            }
          ]
        }
      });
    });

    it('Should validate nested array within an object', () => {
      const schema = new Schema({
        user: ObjectType().shape({
          emails: ArrayType().of(
            StringType().isEmail(),
            ObjectType().shape({
              name: StringType().isEmail()
            })
          )
        })
      });

      expect(
        schema.checkForField(
          'user.emails[0]',
          {
            user: {
              emails: ['xxx']
            }
          },
          options
        )
      ).to.deep.equal({
        hasError: true,
        errorMessage: 'user.emails[0] must be a valid email'
      });

      expect(
        schema.check({
          user: {
            emails: [
              'xxx',
              {
                name: 'xxx'
              }
            ]
          }
        })
      ).to.deep.equal({
        user: {
          hasError: true,
          object: {
            emails: {
              hasError: true,
              array: [
                {
                  hasError: true,
                  errorMessage: 'emails.[0] must be a valid email'
                },
                {
                  hasError: true,
                  object: {
                    name: {
                      hasError: true,
                      errorMessage: 'name must be a valid email'
                    }
                  }
                }
              ]
            }
          }
        }
      });
    });
  });
});


================================================
FILE: test/BooleanTypeSpec.js
================================================
/* eslint-disable @typescript-eslint/no-var-requires */

require('chai').should();

const schema = require('../src');
const { BooleanType, Schema } = schema;

describe('#BooleanType', () => {
  it('Should be a valid boolean', () => {
    const schemaData = {
      data: BooleanType(),
      data2: BooleanType().isRequired()
    };

    const schema = new Schema(schemaData);

    schema.checkForField('data', { data: true }).hasError.should.equal(false);
    schema.checkForField('data', { data: false }).hasError.should.equal(false);
    schema.checkForField('data', { data: 0 }).hasError.should.equal(true);
    schema.checkForField('data', { data: '' }).hasError.should.equal(false);
    schema.checkForField('data', { data: null }).hasError.should.equal(false);
    schema.checkForField('data', { data: undefined }).hasError.should.equal(false);
    schema.checkForField('data', { data: 0 }).errorMessage.should.equal('data must be a boolean');

    schema.checkForField('data2', { data2: '' }).hasError.should.equal(true);
    schema.checkForField('data2', { data2: null }).hasError.should.equal(true);
    schema.checkForField('data2', { data2: undefined }).hasError.should.equal(true);
    schema
      .checkForField('data2', { data2: '' })
      .errorMessage.should.equal('data2 is a required field');
  });
});


================================================
FILE: test/DateTypeSpec.js
================================================
/* eslint-disable @typescript-eslint/no-var-requires */

require('chai').should();

const schema = require('../src');
const { DateType, Schema } = schema;

describe('#DateType', () => {
  it('Should be a valid date', () => {
    const schemaData = {
      data: DateType(),
      data2: DateType().isRequired()
    };

    const schema = new Schema(schemaData);

    schema.checkForField('data', { data: new Date() }).hasError.should.equal(false);
    schema.checkForField('data', { data: 'date' }).hasError.should.equal(true);

    schema.checkForField('data', { data: '' }).hasError.should.equal(false);
    schema.checkForField('data', { data: null }).hasError.should.equal(false);
    schema.checkForField('data', { data: undefined }).hasError.should.equal(false);
    schema.checkForField('data', { data: 'date' }).errorMessage.should.equal('data must be a date');

    schema.checkForField('data2', { data2: '' }).hasError.should.equal(true);
    schema.checkForField('data2', { data2: null }).hasError.should.equal(true);
    schema.checkForField('data2', { data2: undefined }).hasError.should.equal(true);
    schema
      .checkForField('data2', { data2: '' })
      .errorMessage.should.equal('data2 is a required field');
  });

  it('Should be within the date range', () => {
    const schemaData = {
      data: DateType().range('2020-01-01', '2020-02-01')
    };
    const schema = new Schema(schemaData);
    schema.checkForField('data', { data: '2020-01-02' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '2020-02-02' }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: '2020-02-02' })
      .errorMessage.should.equal('data field must be between 2020-01-01 and 2020-02-01');
  });

  it('Should not be less than the minimum date', () => {
    const schemaData = {
      data: DateType().min('2020-01-01')
    };
    const schema = new Schema(schemaData);
    schema.checkForField('data', { data: '2020-01-02' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '2019-12-30' }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: '2019-12-30' })
      .errorMessage.should.equal('data field must be later than 2020-01-01');
  });

  it('Should not exceed the maximum date', () => {
    const schemaData = {
      data: DateType().max('2020-01-01')
    };
    const schema = new Schema(schemaData);
    schema.checkForField('data', { data: '2019-12-30' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '2020-01-02' }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: '2020-01-02' })
      .errorMessage.should.equal('data field must be at earlier than 2020-01-01');
  });
});


================================================
FILE: test/MixedTypeSpec.js
================================================
import chai, { expect } from 'chai';
import * as schema from '../src';
import { getFieldType, schemaSpecKey, arrayTypeSchemaSpec } from '../src/MixedType';

chai.should();

const { StringType, SchemaModel, NumberType, ArrayType, MixedType, ObjectType } = schema;

describe('#MixedType', () => {
  describe('addRule', () => {
    it('Should check if two fields are the same by addRule', () => {
      const schema = SchemaModel({
        a: StringType().isRequired(),
        b: StringType()
          .addRule((value, data) => value === data.a, 'The two fields are not the same')
          .isRequired()
      });

      expect(schema.check({ a: '123', b: '123' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: false }
      });

      expect(schema.check({ a: '123', b: '456' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'The two fields are not the same' }
      });

      expect(schema.check({ a: '123', b: '' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b is a required field' }
      });
    });

    it('Should check if two fields are the same and the field value is not root', () => {
      const schema = SchemaModel({
        a: StringType().isRequired(),
        b: StringType()
          .addRule(value => value !== 'root', 'The value is root')
          .addRule((value, data) => value === data.a, 'The two fields are not the same')
          .isRequired()
      });

      expect(schema.check({ a: 'root', b: 'root' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'The value is root' }
      });

      expect(schema.check({ a: '123', b: '456' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'The two fields are not the same' }
      });
    });
  });

  describe('priority', () => {
    it('Should have the correct priority', () => {
      const schema = SchemaModel({
        name: StringType()
          .isEmail('error1')
          .addRule(() => false, 'error2')
      });

      schema.check({ name: 'a' }).name.hasError.should.equal(true);
      schema.check({ name: 'a' }).name.errorMessage.should.equal('error1');

      const schema2 = SchemaModel({
        name: StringType()
          .isEmail('error1')
          .addRule(() => false, 'error2', true)
      });

      schema2.check({ name: 'a' }).name.hasError.should.equal(true);
      schema2.check({ name: 'a' }).name.errorMessage.should.equal('error2');

      const schema3 = SchemaModel({
        name: StringType().addRule(() => true, 'error2', true)
      });

      schema3.check({ name: 'a' }).name.hasError.should.equal(false);
    });

    it('Should be isRequired with a higher priority than addRule', () => {
      const schema = SchemaModel({
        str: StringType()
          .isRequired('required')
          .addRule(value => value === '', 'error')
      });

      schema.checkForField('str', { str: '' }).hasError.should.equal(true);
      schema.checkForField('str', { str: '' }).errorMessage.should.equal('required');

      schema.checkForField('str', { str: '12' }).hasError.should.equal(true);
      schema.checkForField('str', { str: '12' }).errorMessage.should.equal('error');

      const schema2 = SchemaModel({
        str: StringType().addRule(value => value === '', 'error')
      });

      schema2.checkForField('str', { str: '12' }).hasError.should.equal(true);
      schema2.checkForField('str', { str: '12' }).errorMessage.should.equal('error');
    });

    describe('priority - async', () => {
      it('Should have the correct priority', async () => {
        const schema = SchemaModel({
          name: StringType()
            .isEmail('error1')
            .addRule(() => false, 'error2')
        });

        const result = await schema.checkAsync({ name: 'a' });

        expect(result).to.deep.equal({
          name: { hasError: true, errorMessage: 'error1' }
        });

        const schema2 = SchemaModel({
          name: StringType()
            .isEmail('error1')
            .addRule(() => false, 'error2', true)
        });

        const result2 = await schema2.checkAsync({ name: 'a' });

        expect(result2).to.deep.equal({
          name: { hasError: true, errorMessage: 'error2' }
        });

        const schema3 = SchemaModel({
          name: StringType().addRule(() => true, 'error2', true)
        });

        const result3 = await schema3.checkAsync({ name: 'a' });

        expect(result3).to.deep.equal({
          name: { hasError: false }
        });
      });

      it('Should be isRequired with a higher priority than addRule', async () => {
        const schema = SchemaModel({
          str: StringType()
            .isRequired('required')
            .addRule(value => value === '', 'error')
        });

        const result = await schema.checkAsync({ str: '' });

        expect(result).to.deep.equal({
          str: { hasError: true, errorMessage: 'required' }
        });

        const result2 = await schema.checkAsync({ str: '12' });

        expect(result2).to.deep.equal({
          str: { hasError: true, errorMessage: 'error' }
        });

        const schema2 = SchemaModel({
          str: StringType().addRule(value => value === '', 'error')
        });

        const result3 = await schema2.checkAsync({ str: '12' });

        expect(result3).to.deep.equal({
          str: { hasError: true, errorMessage: 'error' }
        });
      });
    });
  });

  describe('required', () => {
    it('Should be error for undefined string with isRequired', () => {
      const schema = SchemaModel({
        str: StringType().isRequired('required')
      });

      const result = schema.check({ str: undefined });
      result.str.hasError.should.equal(true);
    });

    it('Should be error for empty string with isRequired', () => {
      const schema = SchemaModel({
        str: StringType().isRequired('required')
      });
      const result = schema.check({ str: '' });
      result.str.hasError.should.equal(true);
    });

    it('Should be error for empty array with isRequired', () => {
      const schema = SchemaModel({
        arr: ArrayType().isRequired('required')
      });
      let obj = {
        arr: []
      };
      let result = schema.check(obj);
      result.arr.hasError.should.equal(true);
    });

    it('Should be without error for empty string with isRequiredOrEmpty', () => {
      const schema = SchemaModel({
        str: StringType().isRequiredOrEmpty('required'),
        str2: StringType().isRequiredOrEmpty()
      });
      let obj = {
        str: '',
        str2: null
      };
      let result = schema.check(obj);

      result.str.hasError.should.equal(false);
      result.str2.hasError.should.equal(true);
      result.str2.errorMessage.should.equal('str2 is a required field');
    });

    it('Should be without error for empty array with isRequiredOrEmpty', () => {
      const schema = SchemaModel({
        arr: ArrayType().isRequiredOrEmpty('required')
      });
      let obj = {
        arr: []
      };
      let result = schema.check(obj);
      result.arr.hasError.should.equal(false);
    });

    it('Should be error for undefined string with isRequiredOrEmpty', () => {
      const schema = SchemaModel({
        str: StringType().isRequiredOrEmpty('required')
      });
      let obj = {
        str: undefined
      };
      let result = schema.check(obj);
      result.str.hasError.should.equal(true);
    });
  });

  describe('async', () => {
    it('Should call async check', done => {
      const schema = SchemaModel({
        email: StringType('error1').isEmail('error2'),
        name: StringType().addRule(() => {
          return new Promise(resolve => {
            setTimeout(() => {
              resolve(false);
            }, 1000);
          });
        }, 'error1')
      });

      schema.checkAsync({ name: 'a', email: 'a' }).then(status => {
        if (
          status.name.hasError &&
          status.name.errorMessage === 'error1' &&
          status.email.hasError &&
          status.email.errorMessage === 'error2'
        ) {
          done();
        }
      });
    });

    it('Should call async check', done => {
      const schema = SchemaModel({
        email: StringType('error1').isEmail('error2')
      });

      schema.checkAsync({ name: 'a', email: 'a' }).then(status => {
        if (status.email.hasError && status.email.errorMessage === 'error2') {
          done();
        }
      });
    });

    it('Should call async checkForFieldAsync and verify pass', done => {
      const schema = SchemaModel({
        name: StringType().addRule(() => {
          return new Promise(resolve => {
            setTimeout(() => {
              resolve(false);
            }, 500);
          });
        }, 'error1')
      });

      schema.checkForFieldAsync('name', { name: 'a' }).then(status => {
        if (status.hasError && status.errorMessage === 'error1') {
          done();
        }
      });
    });

    it('Should call async checkForFieldAsync and the validation fails', done => {
      const schema = SchemaModel({
        email: StringType('error1').isEmail('error2')
      });

      schema.checkForFieldAsync('email', { email: 'a' }).then(status => {
        if (status.hasError && status.errorMessage === 'error2') {
          done();
        }
      });
    });

    it('Should call async checkForFieldAsync and the validation fails', done => {
      const schema = SchemaModel({
        name: StringType().addRule(() => {
          return new Promise(resolve => {
            setTimeout(() => {
              resolve(true);
            }, 200);
          });
        }, 'error1')
      });

      schema.checkForFieldAsync('name', { name: 'a' }).then(status => {
        if (status.hasError === false) {
          done();
        }
      });
    });

    it('Should call async checkForFieldAsync and the validation fails', done => {
      const schema = SchemaModel({
        name: StringType()
          .addRule(() => {
            return new Promise(resolve => {
              setTimeout(() => {
                resolve(false);
              }, 200);
            });
          }, 'error1')
          .addRule(() => {
            return new Promise(resolve => {
              resolve(false);
            });
          }, 'error2')
      });

      schema.checkForFieldAsync('name', { name: 'a' }).then(status => {
        if (status.hasError && status.errorMessage === 'error1') {
          done();
        }
      });
    });
  });

  describe('when', () => {
    it('Should type be changed by condition', () => {
      const model = SchemaModel({
        field1: NumberType().min(10),
        field2: MixedType().when(schema => {
          const checkResult = schema.field1.check();
          return checkResult.hasError
            ? NumberType().min(10, 'error1')
            : NumberType().min(2, 'error2');
        })
      });

      const checkResult1 = model.check({ field1: 20, field2: 2 });

      expect(checkResult1).to.deep.equal({
        field1: { hasError: false },
        field2: { hasError: false }
      });

      const checkResult2 = model.check({ field1: 1, field2: 1 });

      expect(checkResult2).to.deep.equal({
        field1: { hasError: true, errorMessage: 'field1 must be greater than or equal to 10' },
        field2: { hasError: true, errorMessage: 'error1' }
      });

      const checkResult3 = model.check({ field1: 10, field2: 1 });

      expect(checkResult3).to.deep.equal({
        field1: { hasError: false },
        field2: { hasError: true, errorMessage: 'error2' }
      });

      const checkResult4 = model.checkForField('field2', { field1: 20, field2: 1 });
      checkResult4.errorMessage.should.equal('error2');

      expect(checkResult4).to.deep.equal({ hasError: true, errorMessage: 'error2' });

      const checkResult5 = model.checkForField('field2', { field1: 9, field2: 1 });

      expect(checkResult5).to.deep.equal({ hasError: true, errorMessage: 'error1' });
    });

    it('Should change the type by getting the value of other fields in the schema', () => {
      const model = SchemaModel({
        option: StringType().isOneOf(['a', 'b', 'other']),
        other: StringType().when(schema => {
          const { value } = schema.option;
          return value === 'other' ? StringType().isRequired('Other required') : StringType();
        })
      });

      const checkResult = model.check({ option: 'a', other: '' });

      expect(checkResult).to.deep.equal({
        option: { hasError: false },
        other: { hasError: false }
      });

      const checkResult2 = model.check({ option: 'other', other: '' });

      expect(checkResult2).to.deep.equal({
        option: { hasError: false },
        other: { hasError: true, errorMessage: 'Other required' }
      });
    });

    it('Should change the type by verifying the value of other fields in the schema', () => {
      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'
                )
                .isRequired()
                .label('Confirm password');
        })
      });

      const checkResult = model.check({ password: '', confirmPassword: '123' });

      expect(checkResult).to.deep.equal({
        password: { hasError: true, errorMessage: 'Password required' },
        confirmPassword: { hasError: false }
      });

      const checkResult2 = model.check({ password: '123', confirmPassword: '123' });

      expect(checkResult2).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: false }
      });

      const checkResult3 = model.check({ password: '123', confirmPassword: '1234' });

      expect(checkResult3).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: true, errorMessage: 'The passwords are inconsistent twice' }
      });

      const checkResult4 = model.check({ password: '123', confirmPassword: '' });

      expect(checkResult4).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: true, errorMessage: 'Confirm password is a required field' }
      });
    });
  });

  describe('proxy - checkForField', () => {
    it('Should verify the dependent field through proxy', () => {
      const schema = SchemaModel({
        password: StringType().isRequired().proxy(['confirmPassword']),
        confirmPassword: StringType()
          .isRequired()
          .addRule((value, data) => {
            if (value !== data?.password) {
              return false;
            }
            return true;
          }, 'The passwords are inconsistent twice')
      });

      expect(
        schema.checkForField('password', { password: '123', confirmPassword: '13' })
      ).to.deep.equal({ hasError: false });

      expect(schema.getCheckResult()).to.deep.equal({
        password: { hasError: false },
        confirmPassword: {
          hasError: true,
          errorMessage: 'The passwords are inconsistent twice'
        }
      });

      expect(schema.check({ password: '123', confirmPassword: '13' })).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: true, errorMessage: 'The passwords are inconsistent twice' }
      });

      expect(schema.check({ password: '123', confirmPassword: '123' })).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: false }
      });

      expect(schema.getCheckResult()).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: false }
      });
    });

    it('Should not verify the dependent field when field validation fails', () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b']),
        b: StringType().isRequired()
      });

      expect(schema.checkForField('a', { a: '' })).to.deep.equal({
        hasError: true,
        errorMessage: 'a is a required field'
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: true, errorMessage: 'a is a required field' }
      });
    });

    it('Should verify the dependent field through proxy with nestedObject', () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b.c']),
        b: ObjectType().shape({
          c: StringType().isRequired()
        })
      });

      expect(schema.checkForField('a', { a: 'd' }, { nestedObject: true })).to.deep.equal({
        hasError: false
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: { object: { c: { hasError: true, errorMessage: 'b.c is a required field' } } }
      });
    });

    it('Should not verify the dependent field when field validation fails', () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b', 'd']),
        b: StringType().isRequired(),
        c: StringType().isRequired(),
        d: StringType().isRequired()
      });

      expect(schema.checkForField('a', { a: 'a' })).to.deep.equal({
        hasError: false
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b is a required field' },
        d: { hasError: true, errorMessage: 'd is a required field' }
      });
    });

    it('Should verify the dependent field through proxy with checkIfValueExists', () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b'], { checkIfValueExists: true }),
        b: StringType().isRequired()
      });

      expect(schema.checkForField('a', { a: 'a' })).to.deep.equal({
        hasError: false
      });

      expect(schema.getCheckResult()).to.deep.equal({ a: { hasError: false } });

      expect(schema.checkForField('a', { a: 'a', b: 1 })).to.deep.equal({
        hasError: false
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: {
          hasError: true,
          errorMessage: 'b must be a string'
        }
      });
    });
  });

  describe('proxy - checkForFieldAsync', () => {
    it('Should verify the dependent field through proxy', async () => {
      const schema = SchemaModel({
        password: StringType().isRequired().proxy(['confirmPassword']),
        confirmPassword: StringType()
          .isRequired()
          .addRule((value, data) => {
            if (value !== data?.password) {
              return false;
            }
            return true;
          }, 'The passwords are inconsistent twice')
      });

      await schema
        .checkForFieldAsync('password', { password: '123', confirmPassword: '12' })
        .then(result => {
          expect(result).to.deep.equal({ hasError: false });

          return result;
        });

      expect(schema.getCheckResult()).to.deep.equal({
        password: { hasError: false },
        confirmPassword: {
          hasError: true,
          errorMessage: 'The passwords are inconsistent twice'
        }
      });

      await schema.checkAsync({ password: '123', confirmPassword: '13' }).then(result => {
        expect(result).to.deep.equal({
          password: { hasError: false },
          confirmPassword: { hasError: true, errorMessage: 'The passwords are inconsistent twice' }
        });
      });

      await schema.checkAsync({ password: '123', confirmPassword: '123' }).then(result => {
        expect(result).to.deep.equal({
          password: { hasError: false },
          confirmPassword: { hasError: false }
        });
      });

      expect(schema.getCheckResult()).to.deep.equal({
        password: { hasError: false },
        confirmPassword: { hasError: false }
      });
    });

    it('Should not verify the dependent field when field validation fails', async () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b']),
        b: StringType().isRequired()
      });

      await schema.checkForFieldAsync('a', { a: '' }).then(result => {
        expect(result).to.deep.equal({
          hasError: true,
          errorMessage: 'a is a required field'
        });
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: true, errorMessage: 'a is a required field' }
      });
    });

    it('Should verify the dependent field through proxy with nestedObject', async () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b.c']),
        b: ObjectType().shape({
          c: StringType().isRequired()
        })
      });

      await schema.checkForFieldAsync('a', { a: 'd' }, { nestedObject: true }).then(result => {
        expect(result).to.deep.equal({
          hasError: false
        });
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: { object: { c: { hasError: true, errorMessage: 'b.c is a required field' } } }
      });
    });

    it('Should not verify the dependent field when field validation fails', async () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b', 'd']),
        b: StringType().isRequired(),
        c: StringType().isRequired(),
        d: StringType().isRequired()
      });

      await schema.checkForFieldAsync('a', { a: 'a' }).then(result => {
        expect(result).to.deep.equal({ hasError: false });
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b is a required field' },
        d: { hasError: true, errorMessage: 'd is a required field' }
      });
    });

    it('Should verify the dependent field through proxy with checkIfValueExists', async () => {
      const schema = SchemaModel({
        a: StringType().isRequired().proxy(['b'], { checkIfValueExists: true }),
        b: StringType().isRequired()
      });

      await schema.checkForFieldAsync('a', { a: 'a' }).then(result => {
        expect(result).to.deep.equal({ hasError: false });
      });

      expect(schema.getCheckResult()).to.deep.equal({ a: { hasError: false } });

      await schema.checkForFieldAsync('a', { a: 'a', b: 1 }).then(result => {
        expect(result).to.deep.equal({ hasError: false });
      });

      expect(schema.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: {
          hasError: true,
          errorMessage: 'b must be a string'
        }
      });
    });

    it('Should handle circular proxy dependencies', () => {
      const model = SchemaModel({
        username: StringType().minLength(6, 'min 6').proxy(['password']),
        password: StringType().minLength(6, 'min 6').proxy(['username'])
      });

      model.checkForField('username', {
        username: '123456',
        password: '123456'
      });
      const checkResult = model.getCheckResult();
      expect(checkResult.username.hasError).to.equal(false);
      expect(checkResult.password.hasError).to.equal(false);
    });

    it('Should clean checked Field inside', () => {
      const model = SchemaModel({
        username: StringType().minLength(6, 'min 6').proxy(['password']),
        password: StringType().minLength(6, 'min 6').proxy(['username'])
      });
      model.checkForField('username', {
        username: '123456',
        password: '123456'
      });
      expect(model.getCheckResult().password.hasError).to.equal(false);

      model.checkForField('username', {
        username: '123456',
        password: '12'
      });

      expect(model.getCheckResult().password.hasError).to.equal(true);
    });
  });

  it('Should check the wrong verification object', () => {
    const schema = SchemaModel({
      name: StringType()
        .isRequired('This field is required.')
        .addRule(() => ({
          hasError: false,
          errorMessage: 'No Error'
        }))
        .addRule(() => ({
          hasError: true,
          errorMessage: 'Error!!'
        }))
    });

    const checkResult = schema.checkForField('name', { name: 'a' });
    checkResult.hasError.should.equal(true);
    checkResult.errorMessage.should.equal('Error!!');
  });

  it('Should check the wrong verification object by Async', done => {
    const schema = SchemaModel({
      name: StringType()
        .isRequired('This field is required.')
        .addRule(() => ({
          hasError: false,
          errorMessage: 'No Error'
        }))
        .addRule(() => ({
          hasError: true,
          errorMessage: 'Error!!'
        }))
    });

    schema.checkForFieldAsync('name', { name: 'a' }).then(checkResult => {
      if (checkResult.hasError && checkResult.errorMessage === 'Error!!') {
        done();
      }
    });
  });

  it('Should be able to check by `check` ', () => {
    const type = MixedType()
      .addRule(v => {
        if (typeof v === 'number') {
          return true;
        }

        return false;
      }, 'error1')
      .isRequired('error2');

    type.check('').hasError.should.equal(true);
    type.check('').errorMessage.should.equal('error2');
    type.check('1').hasError.should.equal(true);
    type.check('1').errorMessage.should.equal('error1');
    type.check(1).hasError.should.equal(false);
  });

  it('Should be able to check by `checkAsync` ', done => {
    const type = MixedType()
      .addRule(v => {
        return new Promise(resolve => {
          setTimeout(() => {
            if (typeof v === 'number') {
              resolve(true);
            } else {
              resolve(false);
            }
          }, 500);
        });
      }, 'error1')
      .isRequired('error2');

    Promise.all([type.checkAsync(''), type.checkAsync('1'), type.checkAsync(1)]).then(res => {
      if (res[0].hasError && res[1].hasError && !res[2].hasError) {
        done();
      }
    });
  });

  it('should error when an async rule is executed by the sync validator', () => {
    const m = MixedType().addRule(async () => {
      return true;
    }, 'An async error');
    let err;
    try {
      m.check({});
    } catch (e) {
      err = e;
    }
    chai
      .expect(err?.message)
      .to.eql('synchronous validator had an async result, you should probably call "checkAsync()"');
  });
  it('Should be able to check by `checkAsync` with `addAsyncRule`', done => {
    const type = MixedType()
      .addAsyncRule(v => {
        return new Promise(resolve => {
          setTimeout(() => {
            if (typeof v === 'number') {
              resolve(true);
            } else {
              resolve(false);
            }
          }, 500);
        });
      }, 'error1')
      .isRequired('error2');

    Promise.all([type.checkAsync(''), type.checkAsync('1'), type.checkAsync(1)]).then(res => {
      if (res[0].hasError && res[1].hasError && !res[2].hasError) {
        done();
      }
    });
  });
  it('Should be able to check by `check` with `addAsyncRule` and skip the async ', done => {
    let called = false;
    const type = MixedType()
      .addRule(v => {
        return typeof v === 'number';
      }, 'This is not async')
      .addAsyncRule(async () => {
        called = true;
        return false;
      }, 'error1')
      .isRequired('error2');
    setTimeout(() => {
      try {
        expect(called).to.eq(false);
        expect(type.check('').hasError).to.eq(true);
        expect(type.check('1').hasError).to.eq(true);
        expect(type.check(1).hasError).to.eq(false);

        done();
      } catch (e) {
        done(e);
      }
    }, 100);
  });

  describe('equalTo', () => {
    it('Should check if two fields are the same by equalTo', () => {
      const schema = SchemaModel({
        a: StringType().isRequired(),
        b: StringType().equalTo('a').isRequired()
      });

      expect(schema.check({ a: '123', b: '123' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: false }
      });

      expect(schema.check({ a: '123', b: '456' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b must be the same as a' }
      });

      expect(schema.check({ a: '123', b: '' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b is a required field' }
      });
    });

    it('Should check if two fields are the same with custom message', () => {
      const schema = SchemaModel({
        a: StringType().isRequired(),
        b: StringType().equalTo('a', 'The two fields are not the same').isRequired()
      });

      expect(schema.check({ a: '123', b: '456' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'The two fields are not the same' }
      });
    });

    it('Should check if two fields are the same when the field is an object', () => {
      const schema = SchemaModel({
        a: ObjectType(),
        b: ObjectType().equalTo('a'),
        c: ArrayType(),
        d: ArrayType().equalTo('c')
      });

      expect(schema.check({ a: { A: '1' }, b: { A: '2' } })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b must be the same as a' },
        c: { hasError: false },
        d: { hasError: false }
      });

      expect(schema.check({ a: { A: '1' }, b: { A: '1' } })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: false },
        c: { hasError: false },
        d: { hasError: false }
      });

      expect(schema.check({ c: [1, 2, 3], d: [4, 5, 6] })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: false },
        c: { hasError: false },
        d: { hasError: true, errorMessage: 'd must be the same as c' }
      });

      expect(schema.check({ c: [1, 2, 3], d: [1, 2, 3] })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: false },
        c: { hasError: false },
        d: { hasError: false }
      });
    });

    it('Should check if two fields are the same when the field is a nested object', () => {
      const schema = SchemaModel({
        a: ObjectType().shape({
          a1: StringType(),
          a2: StringType().equalTo('a1')
        }),
        c: StringType().equalTo('a.a2').isRequired()
      });

      expect(schema.check({ a: { a1: '1', a2: '1' }, c: '1' })).to.deep.equal({
        a: {
          hasError: false,
          object: { a1: { hasError: false }, a2: { hasError: false } }
        },
        c: { hasError: false }
      });

      expect(schema.check({ a: { a1: '1', a2: '2' }, c: '2' })).to.deep.equal({
        a: {
          hasError: true,
          object: {
            a1: { hasError: false },
            a2: { hasError: true, errorMessage: 'a2 must be the same as a1' }
          }
        },
        c: { hasError: false }
      });

      expect(schema.check({ a: { a1: '1', a2: '1' }, c: '2' })).to.deep.equal({
        a: {
          hasError: false,
          object: { a1: { hasError: false }, a2: { hasError: false } }
        },
        c: { hasError: true, errorMessage: 'c must be the same as a.a2' }
      });
    });
  });

  describe('label', () => {
    it('Should use label to override the field name in the error message', () => {
      const schema = SchemaModel({
        first_name: StringType().label('First Name').isRequired(),
        age: NumberType().label('Age').isRequired().range(18, 60)
      });

      expect(schema.check({})).to.deep.equal({
        first_name: { hasError: true, errorMessage: 'First Name is a required field' },
        age: { hasError: true, errorMessage: 'Age is a required field' }
      });

      expect(schema.checkForField('age', { first_name: 'a', age: 5 })).to.deep.equal({
        hasError: true,
        errorMessage: 'Age field must be between 18 and 60'
      });
    });

    it('Should use label to override the field name in the error message when the field is an object', () => {
      const schema = SchemaModel({
        user: ObjectType().shape({
          first_name: StringType().label('First Name').isRequired(),
          age: NumberType().label('Age').isRequired().isRequired().range(18, 60)
        })
      });

      expect(schema.check({ user: {} })).to.deep.equal({
        user: {
          hasError: true,
          object: {
            first_name: { hasError: true, errorMessage: 'First Name is a required field' },
            age: { hasError: true, errorMessage: 'Age is a required field' }
          }
        }
      });

      expect(schema.checkForField('user', { user: { first_name: 'a', age: 5 } })).to.deep.equal({
        hasError: true,
        object: {
          first_name: { hasError: false },
          age: { hasError: true, errorMessage: 'Age field must be between 18 and 60' }
        }
      });
    });

    it('Should check if two fields are the same by equalTo', () => {
      const schema = SchemaModel({
        a: StringType().isRequired().label('A'),
        b: StringType().equalTo('a').isRequired().label('B')
      });

      expect(schema.check({ a: '123', b: '456' })).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'B must be the same as A' }
      });
    });

    describe('label - async', () => {
      it('Should use label to override the field name in the error message', async () => {
        const schema = SchemaModel({
          first_name: StringType().label('First Name').isRequired(),
          age: NumberType().label('Age').isRequired().range(18, 60)
        });

        await schema.checkAsync({}).then(result => {
          expect(result).to.deep.equal({
            first_name: { hasError: true, errorMessage: 'First Name is a required field' },
            age: { hasError: true, errorMessage: 'Age is a required field' }
          });
        });

        await schema.checkForFieldAsync('age', { first_name: 'a', age: 5 }).then(result => {
          expect(result).to.deep.equal({
            hasError: true,
            errorMessage: 'Age field must be between 18 and 60'
          });
        });
      });

      it('Should use label to override the field name in the error message when the field is an object', async () => {
        const schema = SchemaModel({
          user: ObjectType().shape({
            first_name: StringType().label('First Name').isRequired(),
            age: NumberType().label('Age').isRequired().isRequired().range(18, 60)
          })
        });

        await schema.checkAsync({ user: {} }).then(result => {
          expect(result).to.deep.equal({
            user: {
              hasError: true,
              object: {
                first_name: { hasError: true, errorMessage: 'First Name is a required field' },
                age: { hasError: true, errorMessage: 'Age is a required field' }
              }
            }
          });
        });

        await schema
          .checkForFieldAsync('user', { user: { first_name: 'a', age: 5 } })
          .then(result => {
            expect(result).to.deep.equal({
              hasError: true,
              object: {
                first_name: { hasError: false },
                age: { hasError: true, errorMessage: 'Age field must be between 18 and 60' }
              }
            });
          });
      });

      it('Should check if two fields are the same by equalTo', async () => {
        const schema = SchemaModel({
          a: StringType().isRequired().label('A'),
          b: StringType().equalTo('a').isRequired().label('B')
        });

        await schema.checkAsync({ a: '123', b: '456' }).then(result => {
          expect(result).to.deep.equal({
            a: { hasError: false },
            b: { hasError: true, errorMessage: 'B must be the same as A' }
          });
        });
      });
    });
  });

  describe('getFieldType', () => {
    it('Should return the field type directly', () => {
      const schema = {
        username: StringType().isRequired(),
        email: StringType().isEmail(),
        age: NumberType().range(18, 30)
      };

      expect(getFieldType(schema, 'username')).to.equal(schema.username);
      expect(getFieldType(schema, 'email')).to.equal(schema.email);
      expect(getFieldType(schema, 'age')).to.equal(schema.age);
      expect(getFieldType(schema, 'nonexistent')).to.be.undefined;
    });

    it('Should return the nested field type when nestedObject is true', () => {
      const schema = {
        user: ObjectType().shape({
          name: StringType().isRequired(),
          profile: ObjectType().shape({
            age: NumberType().range(18, 30)
          })
        })
      };

      expect(getFieldType(schema, 'user.name', true)).to.equal(schema.user[schemaSpecKey].name);
      expect(getFieldType(schema, 'user.profile.age', true)).to.equal(
        schema.user[schemaSpecKey].profile[schemaSpecKey].age
      );
      expect(getFieldType(schema, 'user.nonexistent', true)).to.be.undefined;
    });

    it('Should return array element type when dealing with arrays', () => {
      const itemType = StringType().isRequired();
      const schema = {
        tags: ArrayType().of(itemType)
      };

      expect(getFieldType(schema, 'tags[0]', true)).to.equal(itemType);
      expect(getFieldType(schema, 'nonexistent[0]', true)).to.be.undefined;
    });

    it('Should return nested field type within array objects', () => {
      const schema = {
        users: ArrayType().of(
          ObjectType().shape({
            name: StringType().isRequired(),
            age: NumberType().range(18, 30)
          })
        )
      };

      expect(getFieldType(schema, 'users[0].name', true)).to.equal(
        schema.users[arrayTypeSchemaSpec][schemaSpecKey].name
      );
      expect(getFieldType(schema, 'users[0].age', true)).to.equal(
        schema.users[arrayTypeSchemaSpec][schemaSpecKey].age
      );
      expect(getFieldType(schema, 'users[0].nonexistent', true)).to.be.undefined;
    });

    it('Should handle complex nested structures', () => {
      const schema = {
        company: ObjectType().shape({
          departments: ArrayType().of(
            ObjectType().shape({
              name: StringType().isRequired(),
              employees: ArrayType().of(
                ObjectType().shape({
                  name: StringType().isRequired(),
                  email: StringType().isEmail()
                })
              )
            })
          )
        })
      };

      expect(getFieldType(schema, 'company.departments[0].name', true)).to.equal(
        schema.company[schemaSpecKey].departments[arrayTypeSchemaSpec][schemaSpecKey].name
      );

      expect(getFieldType(schema, 'company.departments[0].employees[0].email', true)).to.equal(
        schema.company[schemaSpecKey].departments[arrayTypeSchemaSpec][schemaSpecKey].employees[
          arrayTypeSchemaSpec
        ][schemaSpecKey].email
      );
    });

    it('Should return explicit filed of ArrayType().of', () => {
      const schema = {
        users: ArrayType().of(
          StringType().isRequired(),
          ObjectType().shape({
            name: StringType().isRequired(),
            email: StringType().isEmail()
          })
        )
      };
      expect(getFieldType(schema, 'users[0]', true)).to.equal(schema.users[arrayTypeSchemaSpec][0]);
      expect(getFieldType(schema, 'users[1].name', true)).to.equal(
        schema.users[arrayTypeSchemaSpec][1][schemaSpecKey].name
      );
    });

    it('Should handle edge cases', () => {
      const schema = {
        a: StringType()
      };

      expect(getFieldType(null, 'a')).to.be.undefined;
      expect(getFieldType(undefined, 'a')).to.be.undefined;
      expect(getFieldType({}, 'a')).to.be.undefined;
      expect(getFieldType({}, 'a.a[1]', true)).to.be.undefined;
      expect(getFieldType(schema, '')).to.be.undefined;
      expect(getFieldType(schema, '..')).to.be.undefined;
      expect(getFieldType(schema, 'a.b.c[0].d', true)).to.be.undefined;
    });
  });
});


================================================
FILE: test/NumberTypeSpec.js
================================================
/* eslint-disable @typescript-eslint/no-var-requires */
require('chai').should();
const schema = require('../src');
const { NumberType, Schema } = schema;

describe('#NumberType', () => {
  let schemaData = { data: NumberType() };
  let schema = new Schema(schemaData);

  it('Should be a valid number', () => {
    schema.checkForField('data', { data: '2.22' }).hasError.should.equal(false);
    schema.checkForField('data', { data: 2.22 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 2 }).hasError.should.equal(false);
    schema.checkForField('data', { data: -222 }).hasError.should.equal(false);
  });

  it('Should not be checked', () => {
    schema.checkForField('data', { data: null }).hasError.should.equal(false);
    schema.checkForField('data', { data: undefined }).hasError.should.equal(false);
    schema.checkForField('data', { data: '' }).hasError.should.equal(false);
  });

  it('Should be a invalid number', () => {
    schema.checkForField('data', { data: 'abc' }).hasError.should.equal(true);
    schema.checkForField('data', { data: '1abc' }).hasError.should.equal(true);
    schema.checkForField('data', { data: {} }).hasError.should.equal(true);
    schema.checkForField('data', { data: [] }).hasError.should.equal(true);
    schema.checkForField('data', { data: [] }).errorMessage.should.equal('data must be a number');
  });

  it('True should be a invalid number', () => {
    schema.checkForField('data', { data: true }).hasError.should.equal(true);
  });

  it('Function should be a invalid number', () => {
    schema.checkForField('data', { data: () => 0 }).hasError.should.equal(true);
  });

  it('Null and Undefined should be a invalid number', () => {
    let schemaData = { data: NumberType().isRequired() };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: null }).hasError.should.equal(true);
    schema.checkForField('data', { data: undefined }).hasError.should.equal(true);
  });

  it('Should be an integer', () => {
    let schemaData = { data: NumberType().isInteger() };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: 1 }).hasError.should.equal(false);
    schema.checkForField('data', { data: '1' }).hasError.should.equal(false);
    schema.checkForField('data', { data: -1 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 1.1 }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: 1.1 })
      .errorMessage.should.equal('data must be an integer');
  });

  it('Should not be lower than the minimum', () => {
    let schemaData = { data: NumberType().min(10) };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: 10 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 9 }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: 9 })
      .errorMessage.should.equal('data must be greater than or equal to 10');
  });

  it('Should not exceed the maximum', () => {
    let schemaData = { data: NumberType().max(10) };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: 10 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 11 }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: 11 })
      .errorMessage.should.equal('data must be less than or equal to 10');
  });

  it('Should be within the range of optional values', () => {
    let schemaData = { data: NumberType().range(0, 20) };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: 0 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 20 }).hasError.should.equal(false);
    schema.checkForField('data', { data: -1 }).hasError.should.equal(true);
    schema.checkForField('data', { data: 21 }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: -1 })
      .errorMessage.should.equal('data field must be between 0 and 20');
    schema
      .checkForField('data', { data: 21 })
      .errorMessage.should.equal('data field must be between 0 and 20');
  });

  it('Should be within the following value range: 1,2,3,4', () => {
    let schemaData = { data: NumberType().isOneOf([1, 2, 3, 4]) };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: 1 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 2 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 5 }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: 5 })
      .errorMessage.should.equal('data must be one of the following values: 1,2,3,4');
  });

  it('Should allow custom rules', () => {
    let schemaData = { data: NumberType().pattern(/^-?1\d+$/) };
    let schema = new Schema(schemaData);
    schema.checkForField('data', { data: 11 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 12 }).hasError.should.equal(false);
    schema.checkForField('data', { data: 22 }).hasError.should.equal(true);
    schema.checkForField('data', { data: 22 }).errorMessage.should.equal('data is invalid');
  });
});


================================================
FILE: test/ObjectTypeSpec.js
================================================
import { expect } from 'chai';
import { flaser } from 'object-flaser';
import * as schema from '../src';

const { ObjectType, StringType, NumberType, Schema } = schema;

describe('#ObjectType', () => {
  it('Should be a valid object', () => {
    const schemaData = {
      url: StringType().isURL('Should be a url'),
      user: ObjectType().shape({
        email: StringType().isEmail('Should be an email'),
        age: NumberType().min(18, 'Age should be greater than 18')
      })
    };

    const schema = new Schema(schemaData);

    const checkResult = schema.checkForField('user', {
      user: { email: 'simon.guo@hypers.com', age: 19 }
    });

    expect(checkResult).to.deep.equal({
      hasError: false,
      object: {
        email: { hasError: false },
        age: { hasError: false }
      }
    });

    const checkResult2 = schema.checkForField('user', { user: { email: 'simon.guo', age: 19 } });

    expect(checkResult2).to.deep.equal({
      hasError: true,
      object: {
        email: { hasError: true, errorMessage: 'Should be an email' },
        age: { hasError: false }
      }
    });

    const checkResult3 = schema.checkForField('user', {
      user: {
        email: 'simon.guo@hypers.com',
        age: 17
      }
    });

    expect(checkResult3).to.deep.equal({
      hasError: true,
      object: {
        email: { hasError: false },
        age: { hasError: true, errorMessage: 'Age should be greater than 18' }
      }
    });
  });

  it('Should be checked for object nesting.', () => {
    const schemaData = {
      url: StringType().isURL('Should be a url'),
      user: ObjectType().shape({
        email: StringType().isEmail('Should be an email'),
        age: NumberType().min(18, 'Age should be greater than 18'),
        parent: ObjectType().shape({
          email: StringType().isEmail('Should be an email'),
          age: NumberType().min(50, 'Age should be greater than 50')
        })
      })
    };

    const schema = new Schema(schemaData);

    const checkResult = schema.checkForField('user', {
      user: {
        email: 'simon.guo@hypers.com',
        age: 17,
        parent: { email: 'zicheng', age: 40 }
      }
    });

    expect(checkResult).to.deep.equal({
      hasError: true,
      object: {
        email: { hasError: false },
        age: { hasError: true, errorMessage: 'Age should be greater than 18' },
        parent: {
          hasError: true,
          object: {
            email: { hasError: true, errorMessage: 'Should be an email' },
            age: { hasError: true, errorMessage: 'Age should be greater than 50' }
          }
        }
      }
    });

    const checkResult2 = schema.checkForField('user', {
      user: {
        email: 'simon.guo@hypers.com',
        age: 18,
        parent: { email: 'zicheng@dd.com', age: 50 }
      }
    });

    expect(checkResult2).to.deep.equal({
      hasError: false,
      object: {
        email: { hasError: false },
        age: { hasError: false },
        parent: {
          hasError: false,
          object: {
            email: { hasError: false },
            age: { hasError: false }
          }
        }
      }
    });
  });

  it('Should be a valid object by flaser', () => {
    const schemaData = {
      'data.email': StringType().isEmail('Should be an email'),
      'data.age': NumberType().min(18, 'Should be greater than 18')
    };

    const data = {
      data: { email: 'simon.guo@hypers.com', age: 17 }
    };

    const schema = new Schema(schemaData);
    const checkResult = schema.check(flaser(data));

    expect(checkResult).to.deep.equal({
      'data.email': { hasError: false },
      'data.age': { hasError: true, errorMessage: 'Should be greater than 18' }
    });
  });

  it('Should aync check for object nesting', async () => {
    const schema = new Schema({
      url: StringType().isURL('Should be a url'),
      user: ObjectType().shape({
        email: StringType().addRule(() => {
          return new Promise(resolve => {
            setTimeout(() => resolve(false), 400);
          });
        }, 'Should be an email'),
        age: NumberType().min(18, 'Should be greater than 18')
      })
    });

    const result = await schema.checkAsync({ url: 'url', user: { email: 'a', age: '10' } });

    expect(result).to.deep.equal({
      url: { hasError: true, errorMessage: 'Should be a url' },
      user: {
        hasError: true,
        object: {
          email: { hasError: true, errorMessage: 'Should be an email' },
          age: { hasError: true, errorMessage: 'Should be greater than 18' }
        }
      }
    });
  });

  it('Should be checked for object nesting with nestedObject option.', () => {
    const schema = new Schema({
      url: StringType().isURL('Should be a url'),
      user: ObjectType().shape({
        email: StringType().isEmail('Should be an email'),
        age: NumberType().min(18, 'Age should be greater than 18'),
        parent: ObjectType().shape({
          email: StringType().isEmail('Should be an email').isRequired('Email is required'),
          age: NumberType().min(50, 'Age should be greater than 50')
        })
      })
    });
    const options = { nestedObject: true };

    const checkResult = schema.checkForField(
      'user.parent.age',
      { user: { parent: { age: 40 } } },
      options
    );

    expect(checkResult).to.deep.equal({
      hasError: true,
      errorMessage: 'Age should be greater than 50'
    });

    expect(schema.getCheckResult()).to.deep.equal({
      user: {
        object: {
          parent: {
            object: { age: { hasError: true, errorMessage: 'Age should be greater than 50' } }
          }
        }
      }
    });

    const checkResult2 = schema.checkForField(
      'user.parent.age',
      { user: { parent: { age: 60 } } },
      options
    );

    expect(checkResult2).to.deep.equal({ hasError: false });

    expect(schema.getCheckResult()).to.deep.equal({
      user: { object: { parent: { object: { age: { hasError: false } } } } }
    });

    const checkResult3 = schema.checkForField(
      'user.parent.email',
      { user: { parent: { age: 60 } } },
      options
    );

    expect(checkResult3).to.deep.equal({ hasError: true, errorMessage: 'Email is required' });

    expect(schema.getCheckResult()).to.deep.equal({
      user: {
        object: {
          parent: {
            object: {
              age: { hasError: false },
              email: { hasError: true, errorMessage: 'Email is required' }
            }
          }
        }
      }
    });
  });

  it('Should aync check for object nesting', async () => {
    const schema = new Schema({
      url: StringType().isURL('Should be a url'),
      user: ObjectType().shape({
        email: StringType().isEmail('Should be an email'),
        age: NumberType().min(18, 'Should be greater than 18'),
        parent: ObjectType().shape({
          email: StringType().addRule(value => {
            return new Promise(resolve => {
              setTimeout(() => {
                if (/@/.test(value)) {
                  resolve(true);
                }
                resolve(false);
              }, 400);
            });
          }, 'Should be an email'),
          age: NumberType().min(50, 'Age should be greater than 50')
        })
      })
    });

    const options = { nestedObject: true };

    const result = await schema.checkForFieldAsync(
      'user.parent.email',
      { user: { parent: { email: 'a' } } },
      options
    );

    expect(result).to.deep.equal({ hasError: true, errorMessage: 'Should be an email' });

    const result2 = await schema.checkForFieldAsync(
      'user.parent.email',
      { user: { parent: { email: 'a@a.com' } } },
      options
    );

    expect(result2).to.deep.equal({ hasError: false });
  });

  it('Should not allow empty object', () => {
    const schema = new Schema({
      user: ObjectType().isRequired('User is required')
    });

    const result = schema.check({ user: null });
    expect(result).to.deep.equal({ user: { hasError: true, errorMessage: 'User is required' } });

    const result2 = schema.check({ user: undefined });
    expect(result2).to.deep.equal({ user: { hasError: true, errorMessage: 'User is required' } });

    const result3 = schema.check({ user: false });
    expect(result3).to.deep.equal({
      user: { hasError: true, errorMessage: 'user must be an object' }
    });
  });

  it('Should not allow empty object by async', async () => {
    const schema = new Schema({
      user: ObjectType().isRequired('User is required')
    });

    const result = await schema.checkAsync({ user: null });
    expect(result).to.deep.equal({ user: { hasError: true, errorMessage: 'User is required' } });

    const result2 = await schema.checkAsync({ user: undefined });
    expect(result2).to.deep.equal({ user: { hasError: true, errorMessage: 'User is required' } });

    const result3 = await schema.checkAsync({ user: false });
    expect(result3).to.deep.equal({
      user: { hasError: true, errorMessage: 'user must be an object' }
    });
  });

  it('Should allow empty object', () => {
    const schema = new Schema({
      user: ObjectType()
    });

    const result = schema.check({ user: null });
    expect(result).to.deep.equal({ user: { hasError: false } });
  });

  it('Should allow empty object by async', async () => {
    const schema = new Schema({
      user: ObjectType()
    });

    const result = await schema.checkAsync({ user: null });
    expect(result).to.deep.equal({ user: { hasError: false } });
  });

  it('Should replace default required message', () => {
    const schema = new Schema({
      user: ObjectType().shape({
        email1: StringType().isEmail().isRequired(),
        email2: StringType().isEmail().isRequired('Email is required')
      })
    });

    const result = schema.check({ user: { email1: '', email2: '' } });

    expect(result.user.object.email1.errorMessage).to.equal('email1 is a required field');
    expect(result.user.object.email2.errorMessage).to.equal('Email is required');
  });

  it('Should replace default required message with async', async () => {
    const schema = new Schema({
      user: ObjectType().shape({
        email1: StringType().isEmail().isRequired(),
        email2: StringType().isEmail().isRequired('Email is required')
      })
    });

    const result = await schema.checkAsync({ user: { email1: '', email2: '' } });

    expect(result.user.object.email1.errorMessage).to.equal('email1 is a required field');
    expect(result.user.object.email2.errorMessage).to.equal('Email is required');
  });

  describe('priority', () => {
    it('Should have the correct priority', () => {
      const schema = new Schema({
        user: ObjectType().shape({
          name: StringType()
            .isEmail('error1')
            .addRule(() => false, 'error2')
        })
      });

      const result = schema.check({ user: { name: 'a' } });

      expect(result.user.object).to.deep.equal({
        name: { hasError: true, errorMessage: 'error1' }
      });

      const schema2 = new Schema({
        user: ObjectType().shape({
          name: StringType()
            .isEmail('error1')
            .addRule(() => false, 'error2', true)
        })
      });

      const result2 = schema2.check({ user: { name: 'a' } });

      expect(result2.user.object).to.deep.equal({
        name: { hasError: true, errorMessage: 'error2' }
      });
    });

    it('Should have the correct priority with async', async () => {
      const schema = new Schema({
        user: ObjectType().shape({
          name: StringType()
            .isEmail('error1')
            .addRule(() => false, 'error2')
        })
      });

      const result = await schema.checkAsync({ user: { name: 'a' } });

      expect(result.user.object).to.deep.equal({
        name: { hasError: true, errorMessage: 'error1' }
      });

      const schema2 = new Schema({
        user: ObjectType().shape({
          name: StringType()
            .isEmail('error1')
            .addRule(() => false, 'error2', true)
        })
      });

      const result2 = await schema2.checkAsync({ user: { name: 'a' } });

      expect(result2.user.object).to.deep.equal({
        name: { hasError: true, errorMessage: 'error2' }
      });
    });
  });
});


================================================
FILE: test/SchemaSpec.js
================================================
import chai, { expect } from 'chai';
import * as schema from '../src';

const { StringType, NumberType, ObjectType, ArrayType, Schema, SchemaModel } = schema;

chai.should();

describe('#Schema', () => {
  it('The schema should be saved as proporty', () => {
    const schemaData = { data: StringType() };
    const schema = new Schema(schemaData);

    schema.$spec.should.equal(schemaData);
  });

  it('Should be able to get the field value type for the given field name', () => {
    const schemaData = { data: NumberType() };
    const schema = new Schema(schemaData);
    schema.getFieldType('data').should.equal(schemaData.data);
  });

  it('Should return error information', () => {
    const schemaData = { data: NumberType() };
    const schema = new Schema(schemaData);
    const checkResult = schema.checkForField('data', '2.22');

    checkResult.should.have.property('hasError').be.a('boolean');
  });

  it('Should return error information', () => {
    const model = SchemaModel({
      username: StringType().isRequired(),
      email: StringType().isEmail(),
      age: NumberType().range(18, 30)
    });

    const checkResult = model.check({
      username: 'foobar',
      email: 'foo@bar.com',
      age: 40
    });

    expect(checkResult).to.deep.equal({
      username: { hasError: false },
      email: { hasError: false },
      age: { hasError: true, errorMessage: 'age field must be between 18 and 30' }
    });
  });

  it('Should get the schema spec by calling getSchemaSpec', () => {
    const model = SchemaModel({
      username: StringType().isRequired(),
      email: StringType().isEmail()
    });

    model.getSchemaSpec().should.deep.equal(model.$spec);
  });

  describe('## getKeys', () => {
    it('Should return keys', () => {
      const model = SchemaModel({
        username: StringType(),
        email: StringType(),
        age: NumberType()
      });

      model.getKeys().length.should.equals(3);
      model.getKeys()[0].should.equals('username');
      model.getKeys()[1].should.equals('email');
      model.getKeys()[2].should.equals('age');
    });
  });

  describe('## getErrorMessages', () => {
    it('Should return error messages', () => {
      const model = SchemaModel({
        username: StringType().isRequired(),
        email: StringType().isEmail(),
        age: NumberType().range(18, 30)
      });

      model.check({
        username: 'foobar',
        email: ' ',
        age: 40
      });

      expect(model.getErrorMessages()).to.deep.equal([
        'email must be a valid email',
        'age field must be between 18 and 30'
      ]);

      expect(model.getErrorMessages('age')).to.deep.equal(['age field must be between 18 and 30']);
      expect(model.getErrorMessages('username')).to.deep.equal([]);
    });

    it('Should return error messages for array', () => {
      const model = SchemaModel({
        a: ArrayType().of(StringType().isRequired())
      });

      model.check({
        a: ['', 12]
      });

      expect(model.getErrorMessages('a')).to.deep.equal([
        'a.[0] is a required field',
        'a.[1] must be a string'
      ]);
    });

    it('Should return error messages for nested object', () => {
      const model = SchemaModel({
        a: StringType().isRequired(),
        b: StringType().isEmail(),
        c: NumberType().range(18, 30),
        d: ObjectType().shape({
          e: StringType().isEmail().isRequired(),
          f: NumberType().range(50, 60)
        })
      });

      model.check({
        a: 'foobar',
        b: 'a',
        c: 40,
        d: { e: ' ', f: 40 }
      });

      expect(model.getErrorMessages()).to.deep.equal([
        'b must be a valid email',
        'c field must be between 18 and 30'
      ]);

      expect(model.getErrorMessages('d')).to.deep.equal([
        'e is a required field',
        'f field must be between 50 and 60'
      ]);

      expect(model.getErrorMessages('d.e')).to.deep.equal(['e is a required field']);
    });

    it('Should return error messages for nested array', () => {
      const model = SchemaModel({
        a: StringType().isRequired(),
        b: StringType().isEmail(),
        c: ArrayType()
          .of(
            ObjectType().shape({
              d: StringType().isEmail().isRequired(),
              e: NumberType().range(50, 60)
            })
          )
          .isRequired()
      });

      model.check({
        a: 'foobar',
        b: 'a',
        c: [{}, { d: ' ', e: 40 }]
      });

      expect(model.getErrorMessages()).to.deep.equal(['b must be a valid email']);
      expect(model.getErrorMessages('c.0.d')).to.deep.equal(['d is a required field']);
    });

    it('Should return error messages', () => {
      const model = SchemaModel({
        'a.b': StringType().isRequired()
      });

      model.check({
        'a.b': ''
      });

      expect(model.getErrorMessages()).to.deep.equal(['a.b is a required field']);
      expect(model.getErrorMessages('a.b')).to.deep.equal(['a.b is a required field']);
    });
  });

  describe('## getCheckResult', () => {
    it('Should return check results', () => {
      const model = SchemaModel({
        username: StringType().isRequired(),
        email: StringType().isEmail(),
        age: NumberType().range(18, 30)
      });

      model.check({
        username: 'foobar',
        email: ' ',
        age: 40
      });

      expect(model.getCheckResult()).to.deep.equal({
        username: { hasError: false },
        email: { hasError: true, errorMessage: 'email must be a valid email' },
        age: { hasError: true, errorMessage: 'age field must be between 18 and 30' }
      });

      expect(model.getCheckResult('age')).to.deep.equal({
        hasError: true,
        errorMessage: 'age field must be between 18 and 30'
      });

      expect(model.getCheckResult('username')).to.deep.equal({ hasError: false });
    });

    it('Should return check results for nested object', () => {
      const model = SchemaModel({
        a: StringType().isRequired(),
        b: StringType().isEmail(),
        c: NumberType().range(18, 30),
        d: ObjectType().shape({
          e: StringType().isEmail().isRequired(),
          f: NumberType().range(50, 60)
        })
      });

      model.check({
        a: 'foobar',
        b: 'a',
        c: 40,
        d: { e: ' ', f: 40 }
      });

      expect(model.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b must be a valid email' },
        c: { hasError: true, errorMessage: 'c field must be between 18 and 30' },
        d: {
          hasError: true,
          object: {
            e: { hasError: true, errorMessage: 'e is a required field' },
            f: { hasError: true, errorMessage: 'f field must be between 50 and 60' }
          }
        }
      });

      expect(model.getCheckResult('d')).to.deep.equal({
        hasError: true,
        object: {
          e: { hasError: true, errorMessage: 'e is a required field' },
          f: { hasError: true, errorMessage: 'f field must be between 50 and 60' }
        }
      });

      expect(model.getCheckResult('d.e')).to.deep.equal({
        hasError: true,
        errorMessage: 'e is a required field'
      });
    });

    it('Should return check results for nested array', () => {
      const model = SchemaModel({
        a: StringType().isRequired(),
        b: StringType().isEmail(),
        c: ArrayType()
          .of(
            ObjectType().shape({
              d: StringType().isEmail().isRequired(),
              e: NumberType().range(50, 60)
            })
          )
          .isRequired()
      });

      model.check({
        a: 'foobar',
        b: 'a',
        c: [{}, { d: ' ', e: 40 }]
      });

      expect(model.getCheckResult()).to.deep.equal({
        a: { hasError: false },
        b: { hasError: true, errorMessage: 'b must be a valid email' },
        c: {
          hasError: true,
          array: [
            {
              hasError: true,
              object: {
                d: { hasError: true, errorMessage: 'd is a required field' },
                e: { hasError: false }
              }
            },
            {
              hasError: true,
              object: {
                d: { hasError: true, errorMessage: 'd is a required field' },
                e: { hasError: true, errorMessage: 'e field must be between 50 and 60' }
              }
            }
          ]
        }
      });

      expect(model.getCheckResult('c.0.d')).to.deep.equal({
        hasError: true,
        errorMessage: 'd is a required field'
      });
    });
  });

  describe('## static combine', () => {
    it('Should return a combined model.    ', () => {
      const model1 = SchemaModel({
        username: StringType().isRequired(),
        email: StringType().isEmail()
      });

      const checkResult = model1.check({
        username: 'foobar',
        email: 'foo@bar.com',
        age: 40
      });

      expect(checkResult).to.deep.equal({
        username: { hasError: false },
        email: { hasError: false }
      });

      const model2 = SchemaModel({
        username: StringType().isRequired().minLength(7),
        age: NumberType().range(18, 30)
      });

      const checkResult2 = SchemaModel.combine(model1, model2).check({
        username: 'fooba',
        email: 'foo@bar.com',
        age: 40
      });

      expect(checkResult2).to.deep.equal({
        username: { hasError: true, errorMessage: 'username must be at least 7 characters' },
        email: { hasError: false },
        age: { hasError: true, errorMessage: 'age field must be between 18 and 30' }
      });
    });
  });
});


================================================
FILE: test/StringTypeSpec.js
================================================
/* eslint-disable @typescript-eslint/no-var-requires */
require('chai').should();
const schema = require('../src');
const { StringType, SchemaModel } = schema;

describe('#StringType', () => {
  it('Should check min string length', () => {
    const schema = SchemaModel({
      str: StringType().minLength(5),
      cjkStr: StringType().minLength(5, ''),
      emojiStr: StringType().minLength(5, '')
    });

    schema.checkForField('str', { str: 'abcde' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'abcd' }).hasError.should.equal(true);

    schema.checkForField('cjkStr', { cjkStr: '鲤鱼跃龙门' }).hasError.should.equal(false);
    schema.checkForField('cjkStr', { cjkStr: '岁寒三友' }).hasError.should.equal(true);
    schema.checkForField('emojiStr', { emojiStr: '👌👍🐱🐶🐸' }).hasError.should.equal(false);

    schema.checkForField('emojiStr', { emojiStr: '👌👍🐱🐶' }).hasError.should.equal(true);

    schema
      .checkForField('str', { str: 'a' })
      .errorMessage.should.equal('str must be at least 5 characters');
  });

  it('Should check max string length', () => {
    const schema = SchemaModel({
      str: StringType().maxLength(4),
      cjkStr: StringType().maxLength(4, ''),
      emojiStr: StringType().maxLength(4, '')
    });

    schema.checkForField('str', { str: 'abcde' }).hasError.should.equal(true);
    schema.checkForField('str', { str: 'abcd' }).hasError.should.equal(false);
    schema.checkForField('cjkStr', { cjkStr: '鲤鱼跃龙门' }).hasError.should.equal(true);
    schema.checkForField('cjkStr', { cjkStr: '岁寒三友' }).hasError.should.equal(false);
    schema.checkForField('emojiStr', { emojiStr: '👌👍🐱🐶🐸' }).hasError.should.equal(true);
    schema.checkForField('emojiStr', { emojiStr: '👌👍🐱🐶' }).hasError.should.equal(false);

    schema
      .checkForField('str', { str: 'abcde' })
      .errorMessage.should.equal('str must be at most 4 characters');
  });

  it('Should be required', () => {
    const schema = SchemaModel({
      str: StringType().isRequired('isrequired'),
      str1: StringType().isRequired(),
      str2: StringType().isRequired('isrequired', false)
    });

    schema.checkForField('str', { str: '' }).hasError.should.equal(true);
    schema.checkForField('str', { str: ' abcde ' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '  ' }).hasError.should.equal(true);

    schema
      .checkForField('str1', { str1: '' })
      .errorMessage.should.equal('str1 is a required field');

    schema.checkForField('str2', { str2: '' }).hasError.should.equal(true);
    schema.checkForField('str2', { str2: ' abcde ' }).hasError.should.equal(false);
    schema.checkForField('str2', { str2: '  ' }).hasError.should.equal(false);
  });

  it('Should be able to customize the rules', () => {
    const schema = SchemaModel({
      str: StringType()
        .maxLength(4, 'error1')
        .addRule(value => value !== '123', 'error2')
    });

    schema.checkForField('str', { str: '12' }).hasError.should.equal(false);

    schema.checkForField('str', { str: '123' }).hasError.should.equal(true);
    schema.checkForField('str', { str: '123' }).errorMessage.should.equal('error2');
    schema.checkForField('str', { str: 'abcde' }).hasError.should.equal(true);
    schema.checkForField('str', { str: 'abcde' }).errorMessage.should.equal('error1');
  });

  it('Should be one of value in array', () => {
    const schema = SchemaModel({
      str: StringType().isOneOf(['A', 'B', 'C'], 'error'),
      str1: StringType().isOneOf(['A', 'B', 'C'])
    });
    schema.checkForField('str', { str: 'A' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'D' }).hasError.should.equal(true);
    schema.checkForField('str', { str: 'D' }).errorMessage.should.equal('error');
    schema
      .checkForField('str1', { str1: 'D' })
      .errorMessage.should.equal('str1 must be one of the following values: A,B,C');
  });

  it('Should contain letters', () => {
    const schema = SchemaModel({
      str: StringType().containsLetter()
    });
    schema.checkForField('str', { str: '12A' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'a12' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '12' }).hasError.should.equal(true);
    schema
      .checkForField('str', { str: '-' })
      .errorMessage.should.equal('str field must contain letters');
  });

  it('Should only contain letters', () => {
    const schema = SchemaModel({
      str: StringType().containsLetterOnly()
    });
    schema.checkForField('str', { str: 'aA' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '12A' }).hasError.should.equal(true);
    schema.checkForField('str', { str: 'a12' }).hasError.should.equal(true);
    schema.checkForField('str', { str: '12' }).hasError.should.equal(true);
    schema.checkForField('str', { str: '1a' }).errorMessage.should.equal('str must all be letters');
  });

  it('Should contain uppercase letters', () => {
    const schema = SchemaModel({
      str: StringType().containsUppercaseLetter()
    });
    schema.checkForField('str', { str: '12A' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'a12' }).hasError.should.equal(true);
    schema.checkForField('str', { str: '12' }).hasError.should.equal(true);
    schema
      .checkForField('str', { str: '-' })
      .errorMessage.should.equal('str must be a upper case string');
  });

  it('Should contain lowercase letters', () => {
    const schema = SchemaModel({
      str: StringType().containsLowercaseLetter()
    });
    schema.checkForField('str', { str: '12A' }).hasError.should.equal(true);
    schema.checkForField('str', { str: 'a12' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '12' }).hasError.should.equal(true);
    schema
      .checkForField('str', { str: '-' })
      .errorMessage.should.equal('str must be a lowercase string');
  });

  it('Should contain numbers', () => {
    const schema = SchemaModel({
      str: StringType().containsNumber()
    });
    schema.checkForField('str', { str: '12' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'a12' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '12A' }).hasError.should.equal(false);
    schema
      .checkForField('str', { str: 'a' })
      .errorMessage.should.equal('str field must contain numbers');
  });

  it('Should be a url', () => {
    const schema = SchemaModel({
      str: StringType().isURL(),
      email: StringType().isURL('', { allowMailto: true })
    });
    schema.checkForField('str', { str: 'https://www.abc.com' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'http://www.abc.com' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'ftp://www.abc.com' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'http://127.0.0.1/home' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'www.abc.com' }).hasError.should.equal(true);
    schema.checkForField('str', { str: 'a' }).errorMessage.should.equal('str must be a valid URL');
    schema.checkForField('str', { str: 'mailto:user@example.com' }).hasError.should.be.true;
    schema.checkForField('email', { email: 'mailto:user@example.com' }).hasError.should.be.false;
  });

  it('Should be a hexadecimal character', () => {
    const schema = SchemaModel({
      str: StringType().isHex()
    });
    schema.checkForField('str', { str: '#fff000' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'fff000' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '#fff' }).hasError.should.equal(false);
    schema.checkForField('str', { str: 'fff' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '#000' }).hasError.should.equal(false);
    schema.checkForField('str', { str: '#00' }).hasError.should.equal(true);
    schema
      .checkForField('str', { str: 'a' })
      .errorMessage.should.equal('str must be a valid hexadecimal');
  });

  it('Should allow custom rules', () => {
    let schema = SchemaModel({ data: StringType().pattern(/^-?1\d+$/) });
    schema.checkForField('data', { data: '11' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '12' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '22' }).hasError.should.equal(true);
    schema.checkForField('data', { data: '22' }).errorMessage.should.equal('data is invalid');
  });

  it('Should be within the range of the number of characters', () => {
    let schema = SchemaModel({ data: StringType().rangeLength(5, 10) });
    schema.checkForField('data', { data: '12345' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '1234' }).hasError.should.equal(true);
    schema.checkForField('data', { data: '12345678910' }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: '1234' })
      .errorMessage.should.equal('data must contain 5 to 10 characters');
  });

  it('Should not be less than the minimum number of characters', () => {
    let schema = SchemaModel({ data: StringType().minLength(5) });
    schema.checkForField('data', { data: '12345' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '1234' }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: '1234' })
      .errorMessage.should.equal('data must be at least 5 characters');
  });

  it('Should not exceed the maximum number of characters', () => {
    let schema = SchemaModel({ data: StringType().maxLength(5) });
    schema.checkForField('data', { data: '12345' }).hasError.should.equal(false);
    schema.checkForField('data', { data: '123456' }).hasError.should.equal(true);
    schema
      .checkForField('data', { data: '123456' })
      .errorMessage.should.equal('data must be at most 5 characters');
  });
});


================================================
FILE: test/utilsSpec.js
================================================
import chai from 'chai';
import { formatErrorMessage, checkRequired, shallowEqual, pathTransform } from '../src/utils';

chai.should();

describe('#utils', () => {
  describe('## formatErrorMessage', () => {
    it('Should output the parameter `email`', () => {
      const str = formatErrorMessage('${name} is a required field', { name: 'email' });
      const str2 = formatErrorMessage('${name} is a required field', { name1: 'email' });
      str.should.equal('email is a required field');
      str2.should.equal('${name} is a required field');
    });

    it('Should output multiple parameters', () => {
      const str = formatErrorMessage('${name} must contain ${minLength} to ${maxLength} items', {
        name: 'tag',
        minLength: 3,
        maxLength: 10
      });

      const str2 = formatErrorMessage('${name} must contain ${minLength} to ${maxLength} items', {
        name: 'tag',
        minLength1: 3,
        maxLength: 10
      });
      str.should.equal('tag must contain 3 to 10 items');
      str2.should.equal('tag must contain ${minLength} to 10 items');
    });

    it('Should not replace parameters', () => {
      const str = formatErrorMessage('name is a required field');
      str.should.equal('name is a required field');
    });

    it('Should return unprocessed parameters', () => {
      const str = formatErrorMessage(true);
      str.should.equal(true);
    });
  });

  describe('## checkRequired', () => {
    it('Should check string, null and undefined', () => {
      checkRequired('1').should.equal(true);
      checkRequired(0).should.equal(true);
      checkRequired(' ').should.equal(true);
      checkRequired(' ', true).should.equal(false);

      checkRequired('').should.equal(false);
      checkRequired().should.equal(false);
      checkRequired(null).should.equal(false);

      checkRequired('', false, true).should.equal(true);
      checkRequired(undefined, false, true).should.equal(false);
      checkRequired(null, false, true).should.equal(false);
    });

    it('Should check array', () => {
      checkRequired([]).should.equal(false);
      checkRequired([1]).should.equal(true);
      checkRequired([undefined]).should.equal(true);
      checkRequired(['']).should.equal(true);
    });
  });

  describe('## shallowEqual', () => {
    it('Should compare the object', () => {
      const obj1 = { a: 1, b: 2 };
      const obj2 = { a: 1, b: 2 };
      const obj3 = { a: 1, b: 3 };
      const obj4 = { a: 1, b: 2, c: 3 };

      shallowEqual(obj1, obj2).should.equal(true);
      shallowEqual(obj1, obj3).should.equal(false);
      shallowEqual(obj1, obj4).should.equal(false);
    });

    it('Should compare the array', () => {
      const arr1 = [1, 2];
      const arr2 = [1, 2];
      const arr3 = [1, 3];
      const arr4 = [1, 2, 3];

      shallowEqual(arr1, arr2).should.equal(true);
      shallowEqual(arr1, arr3).should.equal(false);
      shallowEqual(arr1, arr4).should.equal(false);
    });

    it('Should compare the object and array', () => {
      const obj = { a: 1, b: [1, 2] };
      const obj1 = { a: 1, b: [1, 2] };
      const obj2 = { a: 1, b: [1, 3] };
      const obj3 = { a: 1, b: [1, 2, 3] };

      shallowEqual(obj, obj1).should.equal(false);
      shallowEqual(obj, obj2).should.equal(false);
      shallowEqual(obj, obj3).should.equal(false);
    });
  });

  describe('## pathTransform', () => {
    it('Should transform the path', () => {
      pathTransform('a').should.equal('a');
      pathTransform('a.b').should.equal('a.object.b');
      pathTransform('a.0').should.equal('a.array.0');
      pathTransform('a.0.1').should.equal('a.array.0.array.1');
      pathTransform('a.b.c').should.equal('a.object.b.object.c');
      pathTransform('a.0.b').should.equal('a.array.0.object.b');
    });
  });
});


================================================
FILE: tsconfig-es.json
================================================
{
  "compilerOptions": {
    "declaration": true,
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "noImplicitAny": false,
    "noUnusedParameters": true,
    "noUnusedLocals": true,
    "sourceMap": true,
    "moduleResolution": "node",
    "target": "ES2019"
  },
  "include": ["./src/**/*.ts"]
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "declaration": true,
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
  
Download .txt
gitextract_y06uf7xz/

├── .codesandbox/
│   └── ci.json
├── .eslintrc.js
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1.bug_report.md
│   │   ├── 2.feature_request.md
│   │   └── config.yml
│   └── workflows/
│       ├── nodejs-ci.yml
│       └── nodejs-publish.yml
├── .gitignore
├── .mocharc.js
├── .npmignore
├── .npmrc
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src/
│   ├── ArrayType.ts
│   ├── BooleanType.ts
│   ├── DateType.ts
│   ├── MixedType.ts
│   ├── NumberType.ts
│   ├── ObjectType.ts
│   ├── Schema.ts
│   ├── StringType.ts
│   ├── index.ts
│   ├── locales/
│   │   ├── default.ts
│   │   └── index.ts
│   ├── types.ts
│   └── utils/
│       ├── basicEmptyCheck.ts
│       ├── checkRequired.ts
│       ├── createValidator.ts
│       ├── createValidatorAsync.ts
│       ├── formatErrorMessage.ts
│       ├── index.ts
│       ├── isEmpty.ts
│       ├── pathTransform.ts
│       └── shallowEqual.ts
├── test/
│   ├── ArrayTypeSpec.js
│   ├── BooleanTypeSpec.js
│   ├── DateTypeSpec.js
│   ├── MixedTypeSpec.js
│   ├── NumberTypeSpec.js
│   ├── ObjectTypeSpec.js
│   ├── SchemaSpec.js
│   ├── StringTypeSpec.js
│   └── utilsSpec.js
├── tsconfig-es.json
├── tsconfig.json
└── types/
    ├── index.d.ts
    ├── test.ts
    ├── tsconfig.json
    └── tslint.json
Download .txt
SYMBOL INDEX (127 symbols across 20 files)

FILE: .eslintrc.js
  constant OFF (line 1) | const OFF = 0;
  constant WARNING (line 2) | const WARNING = 1;
  constant ERROR (line 3) | const ERROR = 2;

FILE: src/ArrayType.ts
  class ArrayType (line 5) | class ArrayType<DataType = any, E = ErrorMessageType> extends MixedType<
    method constructor (line 14) | constructor(errorMessage?: E | string) {
    method rangeLength (line 28) | rangeLength(
    method minLength (line 41) | minLength(minLength: number, errorMessage: E | string = this.locale.mi...
    method maxLength (line 51) | maxLength(maxLength: number, errorMessage: E | string = this.locale.ma...
    method unrepeatable (line 60) | unrepeatable(errorMessage: E | string = this.locale.unrepeatable) {
    method of (line 78) | of(...types: MixedType<any, DataType, E>[]) {
  function getArrayType (line 143) | function getArrayType<DataType = any, E = string>(errorMessage?: E) {

FILE: src/BooleanType.ts
  class BooleanType (line 5) | class BooleanType<DataType = any, E = ErrorMessageType> extends MixedType<
    method constructor (line 11) | constructor(errorMessage?: E | string) {
  function getBooleanType (line 20) | function getBooleanType<DataType = any, E = string>(errorMessage?: E) {

FILE: src/DateType.ts
  class DateType (line 5) | class DateType<DataType = any, E = ErrorMessageType> extends MixedType<
    method constructor (line 11) | constructor(errorMessage?: E | string) {
    method range (line 19) | range(min: string | Date, max: string | Date, errorMessage: E | string...
    method min (line 28) | min(min: string | Date, errorMessage: E | string = this.locale.min) {
    method max (line 37) | max(max: string | Date, errorMessage: E | string = this.locale.max) {
  function getDateType (line 47) | function getDateType<DataType = any, E = string>(errorMessage?: E) {

FILE: src/MixedType.ts
  type ProxyOptions (line 23) | type ProxyOptions = {
  function getFieldType (line 34) | function getFieldType(schemaSpec: any, fieldName: string, nestedObject?:...
  function getFieldValue (line 84) | function getFieldValue(data: PlainObject, fieldName: string, nestedObjec...
  class MixedType (line 88) | class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L...
    method constructor (line 106) | constructor(name?: TypeName) {
    method setSchemaOptions (line 111) | setSchemaOptions(schemaSpec: SchemaDeclaration<DataType, E>, value: an...
    method check (line 116) | check(value: any = this.value, data?: DataType, fieldName?: string | s...
    method checkAsync (line 146) | checkAsync(
    method pushRule (line 188) | protected pushRule(rule: RuleType<ValueType, DataType, E | string>) {
    method addRule (line 203) | addRule(
    method addAsyncRule (line 211) | addAsyncRule(
    method isRequired (line 219) | isRequired(errorMessage: E | string = this.locale.isRequired, trim = t...
    method isRequiredOrEmpty (line 225) | isRequiredOrEmpty(errorMessage: E | string = this.locale.isRequiredOrE...
    method when (line 248) | when(condition: (schemaSpec: SchemaDeclaration<DataType, E>) => MixedT...
    method equalTo (line 270) | equalTo(fieldName: string, errorMessage: E | string = this.locale.equa...
    method proxy (line 294) | proxy(fieldNames: string[], options?: ProxyOptions) {
    method label (line 311) | label(label: string) {
  function getMixedType (line 317) | function getMixedType<DataType = any, E = ErrorMessageType>() {

FILE: src/NumberType.ts
  function toNumber (line 5) | function toNumber(value: string | number) {
  class NumberType (line 9) | class NumberType<DataType = any, E = ErrorMessageType> extends MixedType<
    method constructor (line 15) | constructor(errorMessage?: E | string) {
    method isInteger (line 23) | isInteger(errorMessage: E | string = this.locale.isInteger) {
    method pattern (line 32) | pattern(regexp: RegExp, errorMessage: E | string = this.locale.pattern) {
    method isOneOf (line 41) | isOneOf(values: number[], errorMessage: E | string = this.locale.isOne...
    method range (line 50) | range(min: number, max: number, errorMessage: E | string = this.locale...
    method min (line 59) | min(min: number, errorMessage: E | string = this.locale.min) {
    method max (line 68) | max(max: number, errorMessage: E | string = this.locale.max) {
  function getNumberType (line 78) | function getNumberType<DataType = any, E = string>(errorMessage?: E) {

FILE: src/ObjectType.ts
  class ObjectType (line 12) | class ObjectType<DataType = any, E = ErrorMessageType> extends MixedType<
    method constructor (line 19) | constructor(errorMessage?: E | string) {
    method check (line 27) | check(value: PlainObject = this.value, data?: DataType, fieldName?: st...
    method checkAsync (line 73) | checkAsync(value: PlainObject = this.value, data?: DataType, fieldName...
    method shape (line 144) | shape(fields: SchemaDeclaration<DataType, E>) {
  function getObjectType (line 150) | function getObjectType<DataType = any, E = string>(errorMessage?: E) {

FILE: src/Schema.ts
  type CheckOptions (line 5) | interface CheckOptions {
  class Schema (line 12) | class Schema<DataType = any, ErrorMsgType = string> {
    method constructor (line 18) | constructor(schema: SchemaDeclaration<DataType, ErrorMsgType>) {
    method getFieldType (line 22) | private getFieldType<T extends keyof DataType>(
    method setFieldCheckResult (line 29) | private setFieldCheckResult(
    method setSchemaOptionsForAllType (line 44) | private setSchemaOptionsForAllType(data: PlainObject) {
    method getCheckResult (line 60) | getCheckResult(path?: string, result = this.checkResult): CheckResult<...
    method getErrorMessages (line 71) | getErrorMessages(path?: string, result = this.checkResult): (string | ...
    method getKeys (line 95) | getKeys() {
    method getSchemaSpec (line 102) | getSchemaSpec() {
    method _checkForField (line 105) | _checkForField<T extends keyof DataType>(
    method checkForField (line 147) | checkForField<T extends keyof DataType>(
    method checkForFieldAsync (line 158) | checkForFieldAsync<T extends keyof DataType>(
    method check (line 202) | check<T extends keyof DataType>(data: DataType) {
    method checkAsync (line 213) | checkAsync<T extends keyof DataType>(data: DataType) {
  function SchemaModel (line 233) | function SchemaModel<DataType = PlainObject, ErrorMsgType = string>(

FILE: src/StringType.ts
  class StringType (line 5) | class StringType<DataType = any, E = ErrorMessageType> extends MixedType<
    method constructor (line 11) | constructor(errorMessage?: E | string) {
    method containsLetter (line 19) | containsLetter(errorMessage: E | string = this.locale.containsLetter) {
    method containsUppercaseLetter (line 27) | containsUppercaseLetter(errorMessage: E | string = this.locale.contain...
    method containsLowercaseLetter (line 35) | containsLowercaseLetter(errorMessage: E | string = this.locale.contain...
    method containsLetterOnly (line 43) | containsLetterOnly(errorMessage: E | string = this.locale.containsLett...
    method containsNumber (line 51) | containsNumber(errorMessage: E | string = this.locale.containsNumber) {
    method isOneOf (line 59) | isOneOf(values: string[], errorMessage: E | string = this.locale.isOne...
    method isEmail (line 68) | isEmail(errorMessage: E | string = this.locale.isEmail) {
    method isURL (line 79) | isURL(
    method isHex (line 97) | isHex(errorMessage: E | string = this.locale.isHex) {
    method pattern (line 105) | pattern(regexp: RegExp, errorMessage: E | string = this.locale.pattern) {
    method rangeLength (line 114) | rangeLength(
    method minLength (line 127) | minLength(minLength: number, errorMessage: E | string = this.locale.mi...
    method maxLength (line 136) | maxLength(maxLength: number, errorMessage: E | string = this.locale.ma...
  function getStringType (line 146) | function getStringType<DataType = any, E = string>(errorMessage?: E) {

FILE: src/locales/index.ts
  type PickKeys (line 3) | type PickKeys<T> = {
  type Locale (line 7) | type Locale = PickKeys<typeof defaultLocale>;
  type MixedTypeLocale (line 8) | type MixedTypeLocale = PickKeys<typeof defaultLocale.mixed>;
  type ArrayTypeLocale (line 9) | type ArrayTypeLocale = PickKeys<typeof defaultLocale.array> & MixedTypeL...
  type ObjectTypeLocale (line 10) | type ObjectTypeLocale = PickKeys<typeof defaultLocale.object> & MixedTyp...
  type BooleanTypeLocale (line 11) | type BooleanTypeLocale = PickKeys<typeof defaultLocale.boolean> & MixedT...
  type StringTypeLocale (line 12) | type StringTypeLocale = PickKeys<typeof defaultLocale.string> & MixedTyp...
  type NumberTypeLocale (line 13) | type NumberTypeLocale = PickKeys<typeof defaultLocale.number> & MixedTyp...
  type DateTypeLocale (line 14) | type DateTypeLocale = PickKeys<typeof defaultLocale.date> & MixedTypeLoc...

FILE: src/types.ts
  type TypeName (line 8) | type TypeName = 'array' | 'string' | 'boolean' | 'number' | 'object' | '...
  type CheckResult (line 10) | interface CheckResult<E = string, DataType = PlainObject> {
  type ErrorMessageType (line 18) | type ErrorMessageType = string;
  type ValidCallbackType (line 19) | type ValidCallbackType<V, D, E> = (
  type AsyncValidCallbackType (line 25) | type AsyncValidCallbackType<V, D, E> = (
  type PlainObject (line 31) | type PlainObject<T extends Record<string, unknown> = any> = {
  type RuleType (line 35) | interface RuleType<V, D, E> {
  type CheckType (line 43) | type CheckType<X, T, E = ErrorMessageType> = X extends string
  type SchemaDeclaration (line 63) | type SchemaDeclaration<T, E = string> = {
  type SchemaCheckResult (line 67) | type SchemaCheckResult<T, E> = {

FILE: src/utils/basicEmptyCheck.ts
  function basicEmptyCheck (line 1) | function basicEmptyCheck(value?: any) {

FILE: src/utils/checkRequired.ts
  function checkRequired (line 4) | function checkRequired(value: any, trim: boolean, emptyAllowed: boolean) {

FILE: src/utils/createValidator.ts
  function isObj (line 3) | function isObj(o: unknown): o is Record<PropertyKey, unknown> {
  function isPromiseLike (line 6) | function isPromiseLike(v: unknown): v is Promise<unknown> {
  function createValidator (line 13) | function createValidator<V, D, E>(data?: D, name?: string | string[], la...

FILE: src/utils/createValidatorAsync.ts
  function createValidatorAsync (line 8) | function createValidatorAsync<V, D, E>(data?: D, name?: string | string[...

FILE: src/utils/formatErrorMessage.ts
  function joinName (line 3) | function joinName(name: string | string[]) {
  function formatErrorMessage (line 11) | function formatErrorMessage<E>(errorMessage?: string | E, params?: any) {

FILE: src/utils/isEmpty.ts
  function isEmpty (line 1) | function isEmpty(value?: any) {

FILE: src/utils/pathTransform.ts
  function pathTransform (line 1) | function pathTransform(path: string) {

FILE: src/utils/shallowEqual.ts
  function is (line 14) | function is(x: any, y: any): boolean {
  function shallowEqual (line 31) | function shallowEqual(objA: any, objB: any): boolean {

FILE: types/test.ts
  type PassObj (line 15) | interface PassObj {
  type Pass (line 18) | interface Pass {
  type F1 (line 93) | interface F1 {
  type F2 (line 106) | interface F2 {
  type F3 (line 119) | interface F3 {
  type F4 (line 133) | interface F4 {
  type F5 (line 147) | interface F5 {
  type F6 (line 167) | interface F6 {
  type F9 (line 225) | interface F9 {
  type F10 (line 241) | interface F10 {
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (214K chars).
[
  {
    "path": ".codesandbox/ci.json",
    "chars": 27,
    "preview": "{\n  \"sandboxes\": [\"new\"]\n}\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 914,
    "preview": "const OFF = 0;\nconst WARNING = 1;\nconst ERROR = 2;\n\nmodule.exports = {\n  env: {\n    browser: true,\n    es6: true\n  },\n  "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1.bug_report.md",
    "chars": 797,
    "preview": "---\nname: 🐛 Bug report\nabout: Report a reproducible bug or regression\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n<!--\nThank"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2.feature_request.md",
    "chars": 598,
    "preview": "---\nname: 💄 Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n<!--\nThanks "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 190,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 🤔 Ask a question\n    url: https://github.com/rsuite/rsuite/discussi"
  },
  {
    "path": ".github/workflows/nodejs-ci.yml",
    "chars": 993,
    "preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
  },
  {
    "path": ".github/workflows/nodejs-publish.yml",
    "chars": 604,
    "preview": "# see https://help.github.com/cn/actions/language-and-framework-guides/publishing-nodejs-packages\n\nname: Node.js Package"
  },
  {
    "path": ".gitignore",
    "chars": 614,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscov"
  },
  {
    "path": ".mocharc.js",
    "chars": 484,
    "preview": "'use strict';\n\n// https://github.com/mochajs/mocha-examples/tree/master/packages/typescript-babel\nconst config = {\n  dif"
  },
  {
    "path": ".npmignore",
    "chars": 33,
    "preview": "src/\ntypes/*.json\ntypes/test.ts\n\n"
  },
  {
    "path": ".npmrc",
    "chars": 25,
    "preview": "message=\"build: bump %s\"\n"
  },
  {
    "path": ".prettierrc.js",
    "chars": 125,
    "preview": "module.exports = {\n  printWidth: 100,\n  tabWidth: 2,\n  singleQuote: true,\n  arrowParens: 'avoid',\n  trailingComma: 'none"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5456,
    "preview": "## [2.4.2](https://github.com/rsuite/schema-typed/compare/v2.4.1...v2.4.2) (2025-04-11)\n\n\n### Bug Fixes\n\n* prevent infin"
  },
  {
    "path": "LICENSE",
    "chars": 1083,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 RSuite Community\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README.md",
    "chars": 23055,
    "preview": "# schema-typed\n\nSchema for data modeling & validation\n\n[![npm][npm-badge]][npm] [![GitHub Actions][actions-svg]][actions"
  },
  {
    "path": "package.json",
    "chars": 1849,
    "preview": "{\n  \"name\": \"schema-typed\",\n  \"version\": \"2.4.2\",\n  \"description\": \"Schema for data modeling & validation\",\n  \"main\": \"l"
  },
  {
    "path": "src/ArrayType.ts",
    "chars": 4055,
    "preview": "import { MixedType, arrayTypeSchemaSpec } from './MixedType';\nimport { PlainObject, CheckResult, ErrorMessageType } from"
  },
  {
    "path": "src/BooleanType.ts",
    "chars": 607,
    "preview": "import { MixedType } from './MixedType';\nimport { ErrorMessageType } from './types';\nimport { BooleanTypeLocale } from '"
  },
  {
    "path": "src/DateType.ts",
    "chars": 1358,
    "preview": "import { MixedType } from './MixedType';\nimport { ErrorMessageType } from './types';\nimport { DateTypeLocale } from './l"
  },
  {
    "path": "src/MixedType.ts",
    "chars": 9160,
    "preview": "import {\n  SchemaDeclaration,\n  CheckResult,\n  ValidCallbackType,\n  AsyncValidCallbackType,\n  RuleType,\n  ErrorMessageTy"
  },
  {
    "path": "src/NumberType.ts",
    "chars": 2020,
    "preview": "import { MixedType } from './MixedType';\nimport { ErrorMessageType } from './types';\nimport { NumberTypeLocale } from '."
  },
  {
    "path": "src/ObjectType.ts",
    "chars": 4737,
    "preview": "import { MixedType, schemaSpecKey } from './MixedType';\nimport {\n  createValidator,\n  createValidatorAsync,\n  checkRequi"
  },
  {
    "path": "src/Schema.ts",
    "chars": 7230,
    "preview": "import { SchemaDeclaration, SchemaCheckResult, CheckResult, PlainObject } from './types';\nimport { MixedType, getFieldTy"
  },
  {
    "path": "src/StringType.ts",
    "chars": 4501,
    "preview": "import { MixedType } from './MixedType';\nimport { ErrorMessageType } from './types';\nimport { StringTypeLocale } from '."
  },
  {
    "path": "src/index.ts",
    "chars": 687,
    "preview": "import { SchemaModel, Schema } from './Schema';\nimport { default as MixedType } from './MixedType';\nimport { default as "
  },
  {
    "path": "src/locales/default.ts",
    "chars": 2070,
    "preview": "export default {\n  mixed: {\n    isRequired: '${name} is a required field',\n    isRequiredOrEmpty: '${name} is a required"
  },
  {
    "path": "src/locales/index.ts",
    "chars": 778,
    "preview": "import defaultLocale from './default';\n\nexport type PickKeys<T> = {\n  [keys in keyof T]: T[keys];\n};\n\nexport type Locale"
  },
  {
    "path": "src/types.ts",
    "chars": 1832,
    "preview": "import { ArrayType } from './ArrayType';\nimport { BooleanType } from './BooleanType';\nimport { DateType } from './DateTy"
  },
  {
    "path": "src/utils/basicEmptyCheck.ts",
    "chars": 132,
    "preview": "function basicEmptyCheck(value?: any) {\n  return typeof value === 'undefined' || value === null;\n}\n\nexport default basic"
  },
  {
    "path": "src/utils/checkRequired.ts",
    "chars": 467,
    "preview": "import basicEmptyCheck from './basicEmptyCheck';\nimport isEmpty from './isEmpty';\n\nfunction checkRequired(value: any, tr"
  },
  {
    "path": "src/utils/createValidator.ts",
    "chars": 1527,
    "preview": "import { CheckResult, RuleType } from '../types';\nimport formatErrorMessage from './formatErrorMessage';\nfunction isObj("
  },
  {
    "path": "src/utils/createValidatorAsync.ts",
    "chars": 1303,
    "preview": "import { CheckResult, RuleType } from '../types';\nimport formatErrorMessage, { joinName } from './formatErrorMessage';\n\n"
  },
  {
    "path": "src/utils/formatErrorMessage.ts",
    "chars": 567,
    "preview": "import isEmpty from './isEmpty';\n\nexport function joinName(name: string | string[]) {\n  return Array.isArray(name) ? nam"
  },
  {
    "path": "src/utils/index.ts",
    "chars": 588,
    "preview": "export { default as get } from 'lodash/get';\nexport { default as set } from 'lodash/set';\nexport { default as basicEmpty"
  },
  {
    "path": "src/utils/isEmpty.ts",
    "chars": 132,
    "preview": "function isEmpty(value?: any) {\n  return typeof value === 'undefined' || value === null || value === '';\n}\n\nexport defau"
  },
  {
    "path": "src/utils/pathTransform.ts",
    "chars": 396,
    "preview": "export default function pathTransform(path: string) {\n  const arr = path.split('.');\n\n  if (arr.length === 1) {\n    retu"
  },
  {
    "path": "src/utils/shallowEqual.ts",
    "chars": 1547,
    "preview": "/**\n * From: https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js\n * @providesModule shal"
  },
  {
    "path": "test/ArrayTypeSpec.js",
    "chars": 25313,
    "preview": "import { expect } from 'chai';\nimport * as schema from '../src';\n\nconst { ArrayType, StringType, NumberType, ObjectType,"
  },
  {
    "path": "test/BooleanTypeSpec.js",
    "chars": 1324,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\n\nrequire('chai').should();\n\nconst schema = require('../src');\nco"
  },
  {
    "path": "test/DateTypeSpec.js",
    "chars": 2746,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\n\nrequire('chai').should();\n\nconst schema = require('../src');\nco"
  },
  {
    "path": "test/MixedTypeSpec.js",
    "chars": 40489,
    "preview": "import chai, { expect } from 'chai';\nimport * as schema from '../src';\nimport { getFieldType, schemaSpecKey, arrayTypeSc"
  },
  {
    "path": "test/NumberTypeSpec.js",
    "chars": 5171,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\nrequire('chai').should();\nconst schema = require('../src');\ncons"
  },
  {
    "path": "test/ObjectTypeSpec.js",
    "chars": 12370,
    "preview": "import { expect } from 'chai';\nimport { flaser } from 'object-flaser';\nimport * as schema from '../src';\n\nconst { Object"
  },
  {
    "path": "test/SchemaSpec.js",
    "chars": 9743,
    "preview": "import chai, { expect } from 'chai';\nimport * as schema from '../src';\n\nconst { StringType, NumberType, ObjectType, Arra"
  },
  {
    "path": "test/StringTypeSpec.js",
    "chars": 9947,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\nrequire('chai').should();\nconst schema = require('../src');\ncons"
  },
  {
    "path": "test/utilsSpec.js",
    "chars": 3812,
    "preview": "import chai from 'chai';\nimport { formatErrorMessage, checkRequired, shallowEqual, pathTransform } from '../src/utils';\n"
  },
  {
    "path": "tsconfig-es.json",
    "chars": 346,
    "preview": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"allowJs\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"es"
  },
  {
    "path": "tsconfig.json",
    "chars": 396,
    "preview": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"allowJs\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"es"
  },
  {
    "path": "types/index.d.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "types/test.ts",
    "chars": 9656,
    "preview": "import {\n  BooleanType,\n  NumberType,\n  StringType,\n  DateType,\n  ArrayType,\n  ObjectType,\n  Schema,\n  SchemaModel\n} fro"
  },
  {
    "path": "types/tsconfig.json",
    "chars": 323,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"lib\": [\"es6\", \"dom\", \"es2017\"],\n    \"noImplicitAny\": true,\n    \""
  },
  {
    "path": "types/tslint.json",
    "chars": 157,
    "preview": "{\n  \"extends\": \"dtslint/dtslint.json\", // Or \"dtslint/dt.json\" if on DefinitelyTyped\n  \"rules\": {\n    \"semicolon\": false"
  }
]

About this extraction

This page contains the full source code of the rsuite/schema-typed GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (199.5 KB), approximately 52.6k tokens, and a symbol index with 127 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!