├── .eslintrc.cjs
├── .github
├── dependabot.yml
└── workflows
│ ├── auto-tag.yml
│ ├── docs.yml
│ ├── npm-publish.yml
│ └── tests.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE
├── SECURITY.md
├── default.ts
├── demo
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.tsx
│ ├── Checkbox.tsx
│ ├── Constants.ts
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── docs
├── .vitepress
│ ├── cache
│ │ └── deps
│ │ │ ├── @theme_index.js
│ │ │ ├── @theme_index.js.map
│ │ │ ├── _metadata.json
│ │ │ ├── chunk-7DTDMSOX.js
│ │ │ ├── chunk-7DTDMSOX.js.map
│ │ │ ├── chunk-WAIFCIP6.js
│ │ │ ├── chunk-WAIFCIP6.js.map
│ │ │ ├── package.json
│ │ │ ├── vitepress___@vue_devtools-api.js
│ │ │ ├── vitepress___@vue_devtools-api.js.map
│ │ │ ├── vitepress___@vueuse_integrations_useFocusTrap.js
│ │ │ ├── vitepress___@vueuse_integrations_useFocusTrap.js.map
│ │ │ ├── vitepress___mark__js_src_vanilla__js.js
│ │ │ ├── vitepress___mark__js_src_vanilla__js.js.map
│ │ │ ├── vitepress___minisearch.js
│ │ │ ├── vitepress___minisearch.js.map
│ │ │ ├── vue.js
│ │ │ └── vue.js.map
│ └── config.mts
├── api-examples.md
├── customization.md
├── examples.md
├── getting-started.md
├── i18n.md
├── index.md
├── options.md
├── package-lock.json
├── package.json
├── public
│ ├── CNAME
│ └── logo.png
├── rules.md
├── terminology.md
└── why.md
├── index.ts
├── package-lock.json
├── package.json
├── readme.md
├── rollup.config.mjs
├── scripts
└── consumer-test.sh
├── src
├── Constants.ts
├── Factory.ts
├── Interface.ts
├── Locale.ts
├── Options.ts
├── Types.ts
├── converters
│ ├── accepted.ts
│ ├── after.ts
│ ├── afterOrEqual.ts
│ ├── alpha.ts
│ ├── alphaDash.ts
│ ├── alphaNum.ts
│ ├── array.ts
│ ├── before.ts
│ ├── beforeOrEqual.ts
│ ├── between.ts
│ ├── boolean.ts
│ ├── confirmed.ts
│ ├── date.ts
│ ├── digits.ts
│ ├── digitsBetween.ts
│ ├── email.ts
│ ├── hex.ts
│ ├── in.ts
│ ├── index.ts
│ ├── integer.ts
│ ├── max.ts
│ ├── min.ts
│ ├── notIn.ts
│ ├── numeric.ts
│ ├── required.ts
│ ├── size.ts
│ ├── string.ts
│ └── url.ts
├── helpers
│ ├── getValueViaPath.ts
│ └── validate.ts
├── i18n.ts
├── index.ts
├── json.d.ts
├── ruleManager.ts
└── rules
│ ├── index.ts
│ ├── isAccepted.ts
│ ├── isAfter.ts
│ ├── isAfterOrEqual.ts
│ ├── isAlpha.ts
│ ├── isAlphaDash.ts
│ ├── isAlphaNum.ts
│ ├── isArray.ts
│ ├── isBefore.ts
│ ├── isBeforeOrEqual.ts
│ ├── isBetween.ts
│ ├── isBoolean.ts
│ ├── isConfirmed.ts
│ ├── isDate.ts
│ ├── isDigits.ts
│ ├── isDigitsBetween.ts
│ ├── isEmail.ts
│ ├── isHex.ts
│ ├── isIn.ts
│ ├── isInteger.ts
│ ├── isMax.ts
│ ├── isMin.ts
│ ├── isNotIn.ts
│ ├── isNumeric.ts
│ ├── isRequired.ts
│ ├── isSize.ts
│ ├── isString.ts
│ └── isUrl.ts
├── tests
├── Locale.test.ts
├── consumers
│ ├── cjs
│ │ ├── index.js
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ └── test.sh
│ ├── esm
│ │ ├── index.js
│ │ ├── package-lock.json
│ │ └── package.json
│ └── ts-local
│ │ ├── index.ts
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ └── tsconfig.json
├── converters
│ └── index.test.ts
├── helpers.ts
├── helpers
│ └── getValueViaPath.test.ts
├── index.test.ts
├── nested.test.ts
├── ruleManager.test.ts
└── rules
│ ├── accepted.test.ts
│ ├── after.test.ts
│ ├── afterOrEqual.test.ts
│ ├── alpha.test.ts
│ ├── alphaDash.test.ts
│ ├── alphaNum.test.ts
│ ├── array.test.ts
│ ├── before.test.ts
│ ├── beforeOrEqual.test.ts
│ ├── between.test.ts
│ ├── boolean.test.ts
│ ├── confirmed.test.ts
│ ├── date.test.ts
│ ├── digits.test.ts
│ ├── digitsBetween.test.ts
│ ├── email.test.ts
│ ├── hex.test.ts
│ ├── in.test.ts
│ ├── integer.test.ts
│ ├── max.test.ts
│ ├── min.test.ts
│ ├── notIn.test.ts
│ ├── numeric.test.ts
│ ├── required.test.ts
│ ├── size.test.ts
│ ├── string.test.ts
│ └── url.test.ts
├── tsconfig.json
└── vitest.config.ts
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es2021: true,
4 | node: true,
5 | },
6 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
7 | overrides: [
8 | {
9 | env: {
10 | node: true,
11 | },
12 | files: [".eslintrc.{js,cjs}"],
13 | parserOptions: {
14 | sourceType: "script",
15 | },
16 | },
17 | ],
18 | parser: "@typescript-eslint/parser",
19 | parserOptions: {
20 | ecmaVersion: "latest",
21 | sourceType: "module",
22 | },
23 | plugins: ["@typescript-eslint"],
24 | rules: {
25 | "@typescript-eslint/no-explicit-any": "off",
26 | },
27 | ignorePatterns: ["dist", "node_modules", "tests/consumers", "demo"],
28 | };
29 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | target-branch: "develop"
11 | schedule:
12 | interval: "weekly"
13 |
--------------------------------------------------------------------------------
/.github/workflows/auto-tag.yml:
--------------------------------------------------------------------------------
1 | name: Tag and Push
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | tag-and-push:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v2
15 |
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: 20
20 |
21 | - name: Install jq
22 | run: sudo apt-get install jq
23 |
24 | - name: Get version from package.json
25 | id: version
26 | run: echo ::set-output name=version::$(jq -r .version package.json)
27 |
28 | - name: Tag and push to GitHub
29 | continue-on-error: true
30 | run: |
31 | git config --local user.email "i.ozguradem@gmail.com"
32 | git config --local user.name "Özgür Adem Işıklı"
33 | git tag -a "v${{ steps.version.outputs.version }}" -m "Version ${{ steps.version.outputs.version }}"
34 | git push origin "v${{ steps.version.outputs.version }}"
35 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | with:
13 | fetch-depth: 0
14 |
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 20
18 |
19 | - run: cd docs && pwd && npm install --frozen-lockfile
20 |
21 | - name: Build
22 | run: |
23 | cd docs && pwd && npm run docs:build
24 | cd ../demo && pwd && npm install && npm run build
25 | mv dist ../docs/.vitepress/dist/demo
26 |
27 | - name: Deploy
28 | uses: peaceiris/actions-gh-pages@v3
29 | with:
30 | personal_token: ${{ secrets.ACCESS_TOKEN }}
31 | publish_dir: docs/.vitepress/dist
32 | external_repository: axe-api/validator-docs
33 | publish_branch: master
34 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: NPM Publish
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 | - uses: actions/setup-node@v1
13 | with:
14 | node-version: 20
15 | - run: npm install
16 | - run: npm test
17 | - run: npm run build
18 | - uses: JS-DevTools/npm-publish@v1
19 | with:
20 | token: ${{ secrets.NPM_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | jobs:
8 | units:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node-version: [18.x, 20.x, 22.x, 23.x]
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | - run: npm ci
20 | - run: npm run build
21 | - run: npm run test
22 |
23 | consumer-tests:
24 | runs-on: ubuntu-latest
25 | strategy:
26 | matrix:
27 | node-version: [18.x, 20.x, 22.x, 23.x]
28 | steps:
29 | - uses: actions/checkout@v2
30 | - name: Use Node.js ${{ matrix.node-version }}
31 | uses: actions/setup-node@v1
32 | with:
33 | node-version: ${{ matrix.node-version }}
34 | - run: |
35 | npm ci
36 | npm run build
37 | cd tests/consumers/cjs && npm install && npm run test
38 | cd ../esm && npm install && npm run test
39 | cd ../ts-local && npm install && npm run test
40 |
41 | linting:
42 | runs-on: ubuntu-latest
43 | strategy:
44 | matrix:
45 | node-version: [ 22.x ]
46 | steps:
47 | - uses: actions/checkout@v2
48 | - name: Use Node.js ${{ matrix.node-version }}
49 | uses: actions/setup-node@v1
50 | with:
51 | node-version: ${{ matrix.node-version }}
52 | - run: npm install
53 | - run: npm run lint
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | .DS_Store
5 | cache
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | # npm run test
2 | # npm run lint
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github
2 | .husky
3 | coverage
4 | docs
5 | node_modules
6 | src
7 | tests
8 | demo
9 | scripts
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | node-modules
3 | .github
4 | package.json
5 | package-lock.json
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | ## [2.2.1 (2025-02-03)](https://github.com/axe-api/axe-api/compare/2.2.1...2.2.0)
4 |
5 | 🎉 Big thanks to our contributors for this release!
6 | Special shoutout to: @christoph-kluge 🚀👏
7 |
8 | Your help makes this project better—cheers! 🎩✨
9 |
10 | - Fixed: Argument parsing issue on different functions [#70](https://github.com/axe-api/validator/issues/70)
11 | - Fixed: Internal server error: DEFINED_RULES.includes is not a function [#68](https://github.com/axe-api/validator/issues/68)
12 |
13 | ## [2.2.0 (2025-01-13)](https://github.com/axe-api/axe-api/compare/2.2.0...2.2.1)
14 |
15 | - Array and object validation [#54](https://github.com/axe-api/validator/issues/54)
16 |
17 | ## [2.1.1 (2024-10-27)](https://github.com/axe-api/axe-api/compare/2.1.1...2.1.0)
18 |
19 | - TypeError: list.map is not a function [#62](https://github.com/axe-api/validator/issues/62)
20 |
21 | ## [2.1.0 (2024-10-19)](https://github.com/axe-api/axe-api/compare/2.1.0...2.0.1)
22 |
23 | - Added `isRegistered()` function. [#60](https://github.com/axe-api/validator/issues/60)
24 |
25 | ## [2.0.1 (2024-09-28)](https://github.com/axe-api/axe-api/compare/2.0.1...2.0.0)
26 |
27 | - Fixed validation messages in English
28 |
29 | ## [2.0.0 (2024-09-27)](https://github.com/axe-api/axe-api/compare/2.0.0...1.1.1)
30 |
31 | - Fixed bundling issues
32 | - The [dayjs](https://day.js.org) library with [date-fns](https://date-fns.org). You _MUST_ replace the date formats:
33 | - `YYYY-MM-DD` => `yyyy-MM-dd`
34 |
35 | ## [1.1.1 (2024-09-19)](https://github.com/axe-api/axe-api/compare/1.1.1...1.1.0)
36 |
37 | - Security fix
38 |
39 | ## [1.1.0 (2024-02-17)](https://github.com/axe-api/axe-api/compare/1.1.0...1.0.1)
40 |
41 | - Fixed bundling issues
42 | - Reduced library size.
43 | - Fixed custom rule registration issue [#43](https://github.com/axe-api/validator/issues/43)
44 |
45 | ### Breaking changes
46 |
47 | Before:
48 |
49 | ```ts
50 | import { validate, setLocales } from "robust-validator";
51 | import en from "robust-validator/dist/i18n/en.json";
52 | ```
53 |
54 | After:
55 |
56 | ```ts
57 | import { validate, setLocales, en } from "robust-validator";
58 | ```
59 |
60 | ## [1.0.1 (2024-02-16)](https://github.com/axe-api/axe-api/compare/1.0.1...1.0.0)
61 |
62 | ### Fixed
63 |
64 | - Fixed module target
65 |
66 | ## [1.0.0 (2024-02-11)](https://github.com/axe-api/axe-api/compare/1.0.0...1.0.0)
67 |
68 | - Initial version
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2012-2020 Mike Erickson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you discover a security vulnerability within Laravel, please send an email to Ozgur Isikli at i.ozguradem@gmail.com. All security vulnerabilities will be promptly addressed.
6 |
7 |
--------------------------------------------------------------------------------
/default.ts:
--------------------------------------------------------------------------------
1 | import * as all from "./index";
2 |
3 | export default all;
4 |
--------------------------------------------------------------------------------
/demo/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:@typescript-eslint/recommended",
7 | "plugin:react-hooks/recommended",
8 | ],
9 | ignorePatterns: ["dist", ".eslintrc.cjs"],
10 | parser: "@typescript-eslint/parser",
11 | plugins: ["react-refresh"],
12 | rules: {
13 | "react-refresh/only-export-components": [
14 | "warn",
15 | { allowConstantExport: true },
16 | ],
17 | "@typescript-eslint/no-explicit-any": "off",
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | robust-validator Playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "examples",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@babel/parser": "^7.23.6",
14 | "date-fns": "^4.1.0",
15 | "prettier": "^3.1.1",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0",
18 | "react-syntax-highlighter": "^15.5.0",
19 | "robust-validator": "^0.1.1",
20 | "styled-components": "^6.1.3"
21 | },
22 | "devDependencies": {
23 | "@types/react": "^18.2.43",
24 | "@types/react-dom": "^18.2.17",
25 | "@types/react-syntax-highlighter": "^15.5.11",
26 | "@typescript-eslint/eslint-plugin": "^6.14.0",
27 | "@typescript-eslint/parser": "^6.14.0",
28 | "@vitejs/plugin-react": "^4.2.1",
29 | "eslint": "^8.55.0",
30 | "eslint-plugin-react-hooks": "^4.6.0",
31 | "eslint-plugin-react-refresh": "^0.4.5",
32 | "typescript": "^5.2.2",
33 | "vite": "^5.4.14"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/demo/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import * as prettier from "prettier/standalone";
3 | import parserBabel from "prettier/plugins/babel";
4 | import * as prettierPluginEstree from "prettier/plugins/estree";
5 | import SyntaxHighlighter from "react-syntax-highlighter";
6 | import { stackoverflowDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
7 | import { setLocales, validate } from "robust-validator";
8 | import en from "robust-validator/dist/i18n/en.json";
9 | import { RULE_PARAMETER_MAP } from "./Constants";
10 | import Checkbox from "./Checkbox";
11 | import { useEffect, useState } from "react";
12 |
13 | setLocales(en);
14 |
15 | const Container = styled.div`
16 | max-width: 1200px;
17 | margin: auto;
18 | `;
19 |
20 | const Title = styled.h1``;
21 |
22 | const SubTitle = styled.h2`
23 | margin-top: 0px;
24 | margin-bottom: 0px;
25 | padding-bottom: 0px;
26 | `;
27 |
28 | const Frame = styled.div``;
29 |
30 | const Highlight = styled(SyntaxHighlighter)`
31 | margin-top: 0px;
32 | font-size: 16px;
33 | background-color: #161618 !important;
34 | `;
35 |
36 | const Column = styled.div`
37 | display: flex;
38 | gap: 20px;
39 | `;
40 |
41 | const Options = styled.div`
42 | width: 35%;
43 | `;
44 |
45 | const Playground = styled.div`
46 | width: 65%;
47 | flex-grow: 1;
48 | display: flex;
49 | flex-direction: column;
50 | gap: 20px;
51 | `;
52 |
53 | const Input = styled.input`
54 | padding: 12px 8px;
55 | font-size: 16px;
56 | `;
57 |
58 | const RuleList = styled.div`
59 | display: flex;
60 | flex-direction: column;
61 | gap: 6px;
62 | `;
63 |
64 | interface IRule {
65 | key: string;
66 | params: any[];
67 | title: string;
68 | }
69 |
70 | function App() {
71 | const [input, setInput] = useState("");
72 | const [code, setCode] = useState("");
73 | const [result, setResult] = useState(null);
74 | const [selectedRules, setSelectedRule] = useState>({
75 | required: true,
76 | email: true,
77 | min: true,
78 | max: true,
79 | });
80 |
81 | const rules: IRule[] = [
82 | ...Object.keys(RULE_PARAMETER_MAP).map((key) => {
83 | const params = RULE_PARAMETER_MAP[key];
84 | const suffix = params.length === 0 ? "" : `:${params.join(",")}`;
85 | return {
86 | key,
87 | params,
88 | title: `${key}${suffix}`,
89 | };
90 | }),
91 | ];
92 | rules.sort((a, b) => a.title.localeCompare(b.title));
93 |
94 | const definition = {
95 | myInput: "",
96 | };
97 |
98 | const createCodes = async () => {
99 | const dataAsString = JSON.stringify({
100 | myInput: input,
101 | });
102 | const rulesAsString = JSON.stringify(definition);
103 | const definitionCode = `
104 | import { validate } from "robust-validator";
105 |
106 | const result = await validate(
107 | ${dataAsString}, // data
108 | ${rulesAsString} // validation rules
109 | )`;
110 | setCode(
111 | await prettier.format(definitionCode, {
112 | parser: "babel",
113 | plugins: [parserBabel, prettierPluginEstree],
114 | }),
115 | );
116 | };
117 |
118 | const validateData = async () => {
119 | const result = await validate(
120 | {
121 | myInput: input,
122 | },
123 | definition,
124 | );
125 | setResult(result);
126 | };
127 |
128 | useEffect(() => {
129 | createCodes();
130 | validateData();
131 | // eslint-disable-next-line
132 | }, [selectedRules, input]);
133 |
134 | const items: string[] = [];
135 | Object.keys(selectedRules).forEach((key) => {
136 | if (selectedRules[key]) {
137 | const ruleObject = rules.find((item) => item.key === key);
138 | if (ruleObject) {
139 | items.push(ruleObject.title);
140 | }
141 | }
142 | });
143 |
144 | definition.myInput = items.join("|");
145 |
146 | const handleRuleSelection = (rule: IRule) => {
147 | const newState = {
148 | ...selectedRules,
149 | [rule.key]: !selectedRules[rule.key],
150 | };
151 | setSelectedRule(newState);
152 | };
153 |
154 | return (
155 |
156 | robust-validator
157 |
158 |
159 |
160 | Rules
161 |
162 | {rules.map((rule, index) => (
163 | handleRuleSelection(rule)}
168 | />
169 | ))}
170 |
171 |
172 |
173 | Code
174 |
175 |
176 | {code}
177 |
178 |
179 |
180 | myInput
181 | setInput(event.target.value)}
186 | />
187 |
188 | result
189 |
190 |
191 | {JSON.stringify(result, undefined, 2)}
192 |
193 |
194 |
195 |
196 |
197 | );
198 | }
199 |
200 | export default App;
201 |
--------------------------------------------------------------------------------
/demo/src/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | interface CheckboxProps {
4 | labelText: string;
5 | checked: boolean;
6 | onChange: (checked: boolean) => void;
7 | }
8 |
9 | const StyledCheckboxContainer = styled.div`
10 | display: flex;
11 | align-items: center;
12 | `;
13 |
14 | const StyledLabel = styled.label`
15 | display: flex;
16 | align-items: center;
17 | cursor: pointer;
18 | user-select: none;
19 | gap: 10px;
20 | `;
21 |
22 | const StyledCheckboxInput = styled.input`
23 | margin-right: 8px; /* Adjust spacing between checkbox and label */
24 | cursor: pointer;
25 | display: none;
26 | `;
27 |
28 | const StyledCustomCheckbox = styled.div<{ checked: boolean }>`
29 | width: 20px; /* Set the width of the checkbox area */
30 | height: 20px; /* Set the height of the checkbox area */
31 | border: 2px solid #000;
32 | background-color: ${(props) =>
33 | props.checked
34 | ? "#007bff"
35 | : "#fff"}; /* Change background color when checked */
36 | color: white;
37 | transition: background-color 0.3s;
38 | display: flex;
39 | justify-content: center;
40 | align-items: center;
41 | border-radius: 2px;
42 | `;
43 |
44 | const Checkbox: React.FC = ({
45 | labelText,
46 | checked,
47 | onChange,
48 | }) => {
49 | const handleCheckboxChange = (event: React.ChangeEvent) => {
50 | onChange(event.target.checked);
51 | };
52 |
53 | return (
54 |
55 |
56 |
61 | ✓
62 | {labelText}
63 |
64 |
65 | );
66 | };
67 |
68 | export default Checkbox;
69 |
--------------------------------------------------------------------------------
/demo/src/Constants.ts:
--------------------------------------------------------------------------------
1 | import { format } from "date-fns";
2 |
3 | export const RULE_PARAMETER_MAP: Record = {
4 | string: [],
5 | boolean: [],
6 | accepted: [],
7 | after: [format(new Date(), "yyyy-MM-dd")],
8 | after_or_equal: [format(new Date(), "yyyy-MM-dd")],
9 | alpha: [],
10 | alpha_dash: [],
11 | alpha_num: [],
12 | array: [],
13 | before: [format(new Date(), "yyyy-MM-dd")],
14 | before_or_equal: [format(new Date(), "yyyy-MM-dd")],
15 | between: [5, 10],
16 | confirmed: [],
17 | date: [],
18 | digits: [4],
19 | digits_between: [4],
20 | email: [],
21 | hex: [],
22 | in: ["apple", "orange"],
23 | integer: [],
24 | max: [15],
25 | min: [8],
26 | not_in: ["apple", "orange"],
27 | numeric: [],
28 | required: [],
29 | size: [10],
30 | url: [],
31 | };
32 |
--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | font-synthesis: none;
7 | text-rendering: optimizeLegibility;
8 | -webkit-font-smoothing: antialiased;
9 | -moz-osx-font-smoothing: grayscale;
10 | }
11 |
12 | a {
13 | font-weight: 500;
14 | color: #646cff;
15 | text-decoration: inherit;
16 | }
17 | a:hover {
18 | color: #535bf2;
19 | }
20 |
21 | body {
22 | margin: 0;
23 | background-color: #1b1b1f;
24 | color: white;
25 | }
26 |
--------------------------------------------------------------------------------
/demo/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/demo/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/demo/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | base: "/demo",
7 | plugins: [react()],
8 | });
9 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/@theme_index.js:
--------------------------------------------------------------------------------
1 | import {
2 | useMediaQuery
3 | } from "./chunk-WAIFCIP6.js";
4 | import {
5 | computed,
6 | ref,
7 | watch
8 | } from "./chunk-7DTDMSOX.js";
9 |
10 | // node_modules/vitepress/dist/client/theme-default/index.js
11 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
12 |
13 | // node_modules/vitepress/dist/client/theme-default/without-fonts.js
14 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
15 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
16 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
17 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
18 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
19 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
20 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
21 | import "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
22 | import VPBadge from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
23 | import Layout from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
24 | import { default as default2 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
25 | import { default as default3 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
26 | import { default as default4 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
27 | import { default as default5 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
28 | import { default as default6 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
29 | import { default as default7 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
30 | import { default as default8 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
31 | import { default as default9 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
32 | import { default as default10 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
33 | import { default as default11 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
34 | import { default as default12 } from "/Users/personal/Desktop/github/axe/validator/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
35 |
36 | // node_modules/vitepress/dist/client/shared.js
37 | var inBrowser = typeof document !== "undefined";
38 |
39 | // node_modules/vitepress/dist/client/theme-default/support/utils.js
40 | import { withBase } from "vitepress";
41 |
42 | // node_modules/vitepress/dist/client/theme-default/composables/data.js
43 | import { useData as useData$ } from "vitepress";
44 | var useData = useData$;
45 |
46 | // node_modules/vitepress/dist/client/theme-default/support/utils.js
47 | function ensureStartingSlash(path) {
48 | return /^\//.test(path) ? path : `/${path}`;
49 | }
50 |
51 | // node_modules/vitepress/dist/client/theme-default/support/sidebar.js
52 | function getSidebar(_sidebar, path) {
53 | if (Array.isArray(_sidebar))
54 | return addBase(_sidebar);
55 | if (_sidebar == null)
56 | return [];
57 | path = ensureStartingSlash(path);
58 | const dir = Object.keys(_sidebar).sort((a, b) => {
59 | return b.split("/").length - a.split("/").length;
60 | }).find((dir2) => {
61 | return path.startsWith(ensureStartingSlash(dir2));
62 | });
63 | const sidebar = dir ? _sidebar[dir] : [];
64 | return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base);
65 | }
66 | function getSidebarGroups(sidebar) {
67 | const groups = [];
68 | let lastGroupIndex = 0;
69 | for (const index in sidebar) {
70 | const item = sidebar[index];
71 | if (item.items) {
72 | lastGroupIndex = groups.push(item);
73 | continue;
74 | }
75 | if (!groups[lastGroupIndex]) {
76 | groups.push({ items: [] });
77 | }
78 | groups[lastGroupIndex].items.push(item);
79 | }
80 | return groups;
81 | }
82 | function addBase(items, _base) {
83 | return [...items].map((_item) => {
84 | const item = { ..._item };
85 | const base = item.base || _base;
86 | if (base && item.link)
87 | item.link = base + item.link;
88 | if (item.items)
89 | item.items = addBase(item.items, base);
90 | return item;
91 | });
92 | }
93 |
94 | // node_modules/vitepress/dist/client/theme-default/composables/sidebar.js
95 | function useSidebar() {
96 | const { frontmatter, page, theme: theme2 } = useData();
97 | const is960 = useMediaQuery("(min-width: 960px)");
98 | const isOpen = ref(false);
99 | const _sidebar = computed(() => {
100 | const sidebarConfig = theme2.value.sidebar;
101 | const relativePath = page.value.relativePath;
102 | return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
103 | });
104 | const sidebar = ref(_sidebar.value);
105 | watch(_sidebar, (next, prev) => {
106 | if (JSON.stringify(next) !== JSON.stringify(prev))
107 | sidebar.value = _sidebar.value;
108 | });
109 | const hasSidebar = computed(() => {
110 | return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home";
111 | });
112 | const leftAside = computed(() => {
113 | if (hasAside)
114 | return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left";
115 | return false;
116 | });
117 | const hasAside = computed(() => {
118 | if (frontmatter.value.layout === "home")
119 | return false;
120 | if (frontmatter.value.aside != null)
121 | return !!frontmatter.value.aside;
122 | return theme2.value.aside !== false;
123 | });
124 | const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
125 | const sidebarGroups = computed(() => {
126 | return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
127 | });
128 | function open() {
129 | isOpen.value = true;
130 | }
131 | function close() {
132 | isOpen.value = false;
133 | }
134 | function toggle() {
135 | isOpen.value ? close() : open();
136 | }
137 | return {
138 | isOpen,
139 | sidebar,
140 | sidebarGroups,
141 | hasSidebar,
142 | hasAside,
143 | leftAside,
144 | isSidebarEnabled,
145 | open,
146 | close,
147 | toggle
148 | };
149 | }
150 | var hashRef = ref(inBrowser ? location.hash : "");
151 | if (inBrowser) {
152 | window.addEventListener("hashchange", () => {
153 | hashRef.value = location.hash;
154 | });
155 | }
156 |
157 | // node_modules/vitepress/dist/client/theme-default/without-fonts.js
158 | var theme = {
159 | Layout,
160 | enhanceApp: ({ app }) => {
161 | app.component("Badge", VPBadge);
162 | }
163 | };
164 | var without_fonts_default = theme;
165 | export {
166 | default3 as VPButton,
167 | default7 as VPDocAsideSponsors,
168 | default5 as VPHomeFeatures,
169 | default4 as VPHomeHero,
170 | default6 as VPHomeSponsors,
171 | default2 as VPImage,
172 | default8 as VPSponsors,
173 | default12 as VPTeamMembers,
174 | default9 as VPTeamPage,
175 | default11 as VPTeamPageSection,
176 | default10 as VPTeamPageTitle,
177 | without_fonts_default as default,
178 | useSidebar
179 | };
180 | //# sourceMappingURL=@theme_index.js.map
181 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/_metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "hash": "ebdf5647",
3 | "configHash": "2404d6d8",
4 | "lockfileHash": "f2e780c8",
5 | "browserHash": "21073ad7",
6 | "optimized": {
7 | "vue": {
8 | "src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
9 | "file": "vue.js",
10 | "fileHash": "92dd9a8d",
11 | "needsInterop": false
12 | },
13 | "vitepress > @vue/devtools-api": {
14 | "src": "../../../node_modules/@vue/devtools-api/lib/esm/index.js",
15 | "file": "vitepress___@vue_devtools-api.js",
16 | "fileHash": "042431ed",
17 | "needsInterop": false
18 | },
19 | "vitepress > @vueuse/integrations/useFocusTrap": {
20 | "src": "../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
21 | "file": "vitepress___@vueuse_integrations_useFocusTrap.js",
22 | "fileHash": "d94d8cc9",
23 | "needsInterop": false
24 | },
25 | "vitepress > mark.js/src/vanilla.js": {
26 | "src": "../../../node_modules/mark.js/src/vanilla.js",
27 | "file": "vitepress___mark__js_src_vanilla__js.js",
28 | "fileHash": "0e823711",
29 | "needsInterop": false
30 | },
31 | "vitepress > minisearch": {
32 | "src": "../../../node_modules/minisearch/dist/es/index.js",
33 | "file": "vitepress___minisearch.js",
34 | "fileHash": "a74bad0b",
35 | "needsInterop": false
36 | },
37 | "@theme/index": {
38 | "src": "../../../node_modules/vitepress/dist/client/theme-default/index.js",
39 | "file": "@theme_index.js",
40 | "fileHash": "28bdc7cb",
41 | "needsInterop": false
42 | }
43 | },
44 | "chunks": {
45 | "chunk-WAIFCIP6": {
46 | "file": "chunk-WAIFCIP6.js"
47 | },
48 | "chunk-7DTDMSOX": {
49 | "file": "chunk-7DTDMSOX.js"
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/chunk-WAIFCIP6.js:
--------------------------------------------------------------------------------
1 | import {
2 | computed,
3 | getCurrentInstance,
4 | getCurrentScope,
5 | onMounted,
6 | onScopeDispose,
7 | ref,
8 | unref,
9 | watchEffect
10 | } from "./chunk-7DTDMSOX.js";
11 |
12 | // node_modules/@vueuse/shared/index.mjs
13 | function tryOnScopeDispose(fn) {
14 | if (getCurrentScope()) {
15 | onScopeDispose(fn);
16 | return true;
17 | }
18 | return false;
19 | }
20 | function toValue(r) {
21 | return typeof r === "function" ? r() : unref(r);
22 | }
23 | var isClient = typeof window !== "undefined" && typeof document !== "undefined";
24 | var isWorker = typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
25 | var isIOS = getIsIOS();
26 | function getIsIOS() {
27 | var _a, _b;
28 | return isClient && ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) && (/iP(ad|hone|od)/.test(window.navigator.userAgent) || ((_b = window == null ? void 0 : window.navigator) == null ? void 0 : _b.maxTouchPoints) > 2 && /iPad|Macintosh/.test(window == null ? void 0 : window.navigator.userAgent));
29 | }
30 | function cacheStringFunction(fn) {
31 | const cache = /* @__PURE__ */ Object.create(null);
32 | return (str) => {
33 | const hit = cache[str];
34 | return hit || (cache[str] = fn(str));
35 | };
36 | }
37 | var hyphenateRE = /\B([A-Z])/g;
38 | var hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, "-$1").toLowerCase());
39 | var camelizeRE = /-(\w)/g;
40 | var camelize = cacheStringFunction((str) => {
41 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : "");
42 | });
43 | function identity(arg) {
44 | return arg;
45 | }
46 |
47 | // node_modules/@vueuse/core/index.mjs
48 | function unrefElement(elRef) {
49 | var _a;
50 | const plain = toValue(elRef);
51 | return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;
52 | }
53 | var defaultWindow = isClient ? window : void 0;
54 | var defaultDocument = isClient ? window.document : void 0;
55 | var defaultNavigator = isClient ? window.navigator : void 0;
56 | var defaultLocation = isClient ? window.location : void 0;
57 | function useMounted() {
58 | const isMounted = ref(false);
59 | if (getCurrentInstance()) {
60 | onMounted(() => {
61 | isMounted.value = true;
62 | });
63 | }
64 | return isMounted;
65 | }
66 | function useSupported(callback) {
67 | const isMounted = useMounted();
68 | return computed(() => {
69 | isMounted.value;
70 | return Boolean(callback());
71 | });
72 | }
73 | function useMediaQuery(query, options = {}) {
74 | const { window: window2 = defaultWindow } = options;
75 | const isSupported = useSupported(() => window2 && "matchMedia" in window2 && typeof window2.matchMedia === "function");
76 | let mediaQuery;
77 | const matches = ref(false);
78 | const handler = (event) => {
79 | matches.value = event.matches;
80 | };
81 | const cleanup = () => {
82 | if (!mediaQuery)
83 | return;
84 | if ("removeEventListener" in mediaQuery)
85 | mediaQuery.removeEventListener("change", handler);
86 | else
87 | mediaQuery.removeListener(handler);
88 | };
89 | const stopWatch = watchEffect(() => {
90 | if (!isSupported.value)
91 | return;
92 | cleanup();
93 | mediaQuery = window2.matchMedia(toValue(query));
94 | if ("addEventListener" in mediaQuery)
95 | mediaQuery.addEventListener("change", handler);
96 | else
97 | mediaQuery.addListener(handler);
98 | matches.value = mediaQuery.matches;
99 | });
100 | tryOnScopeDispose(() => {
101 | stopWatch();
102 | cleanup();
103 | mediaQuery = void 0;
104 | });
105 | return matches;
106 | }
107 | var _global = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
108 | var globalKey = "__vueuse_ssr_handlers__";
109 | var handlers = getHandlers();
110 | function getHandlers() {
111 | if (!(globalKey in _global))
112 | _global[globalKey] = _global[globalKey] || {};
113 | return _global[globalKey];
114 | }
115 | var defaultState = {
116 | x: 0,
117 | y: 0,
118 | pointerId: 0,
119 | pressure: 0,
120 | tiltX: 0,
121 | tiltY: 0,
122 | width: 0,
123 | height: 0,
124 | twist: 0,
125 | pointerType: null
126 | };
127 | var keys = Object.keys(defaultState);
128 | var DEFAULT_UNITS = [
129 | { max: 6e4, value: 1e3, name: "second" },
130 | { max: 276e4, value: 6e4, name: "minute" },
131 | { max: 72e6, value: 36e5, name: "hour" },
132 | { max: 5184e5, value: 864e5, name: "day" },
133 | { max: 24192e5, value: 6048e5, name: "week" },
134 | { max: 28512e6, value: 2592e6, name: "month" },
135 | { max: Number.POSITIVE_INFINITY, value: 31536e6, name: "year" }
136 | ];
137 | var _TransitionPresets = {
138 | easeInSine: [0.12, 0, 0.39, 0],
139 | easeOutSine: [0.61, 1, 0.88, 1],
140 | easeInOutSine: [0.37, 0, 0.63, 1],
141 | easeInQuad: [0.11, 0, 0.5, 0],
142 | easeOutQuad: [0.5, 1, 0.89, 1],
143 | easeInOutQuad: [0.45, 0, 0.55, 1],
144 | easeInCubic: [0.32, 0, 0.67, 0],
145 | easeOutCubic: [0.33, 1, 0.68, 1],
146 | easeInOutCubic: [0.65, 0, 0.35, 1],
147 | easeInQuart: [0.5, 0, 0.75, 0],
148 | easeOutQuart: [0.25, 1, 0.5, 1],
149 | easeInOutQuart: [0.76, 0, 0.24, 1],
150 | easeInQuint: [0.64, 0, 0.78, 0],
151 | easeOutQuint: [0.22, 1, 0.36, 1],
152 | easeInOutQuint: [0.83, 0, 0.17, 1],
153 | easeInExpo: [0.7, 0, 0.84, 0],
154 | easeOutExpo: [0.16, 1, 0.3, 1],
155 | easeInOutExpo: [0.87, 0, 0.13, 1],
156 | easeInCirc: [0.55, 0, 1, 0.45],
157 | easeOutCirc: [0, 0.55, 0.45, 1],
158 | easeInOutCirc: [0.85, 0, 0.15, 1],
159 | easeInBack: [0.36, 0, 0.66, -0.56],
160 | easeOutBack: [0.34, 1.56, 0.64, 1],
161 | easeInOutBack: [0.68, -0.6, 0.32, 1.6]
162 | };
163 | var TransitionPresets = Object.assign({}, { linear: identity }, _TransitionPresets);
164 |
165 | export {
166 | tryOnScopeDispose,
167 | unrefElement,
168 | useMediaQuery
169 | };
170 | //# sourceMappingURL=chunk-WAIFCIP6.js.map
171 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module"
3 | }
4 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js:
--------------------------------------------------------------------------------
1 | // node_modules/@vue/devtools-api/lib/esm/env.js
2 | function getDevtoolsGlobalHook() {
3 | return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;
4 | }
5 | function getTarget() {
6 | return typeof navigator !== "undefined" && typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {};
7 | }
8 | var isProxyAvailable = typeof Proxy === "function";
9 |
10 | // node_modules/@vue/devtools-api/lib/esm/const.js
11 | var HOOK_SETUP = "devtools-plugin:setup";
12 | var HOOK_PLUGIN_SETTINGS_SET = "plugin:settings:set";
13 |
14 | // node_modules/@vue/devtools-api/lib/esm/time.js
15 | var supported;
16 | var perf;
17 | function isPerformanceSupported() {
18 | var _a;
19 | if (supported !== void 0) {
20 | return supported;
21 | }
22 | if (typeof window !== "undefined" && window.performance) {
23 | supported = true;
24 | perf = window.performance;
25 | } else if (typeof global !== "undefined" && ((_a = global.perf_hooks) === null || _a === void 0 ? void 0 : _a.performance)) {
26 | supported = true;
27 | perf = global.perf_hooks.performance;
28 | } else {
29 | supported = false;
30 | }
31 | return supported;
32 | }
33 | function now() {
34 | return isPerformanceSupported() ? perf.now() : Date.now();
35 | }
36 |
37 | // node_modules/@vue/devtools-api/lib/esm/proxy.js
38 | var ApiProxy = class {
39 | constructor(plugin, hook) {
40 | this.target = null;
41 | this.targetQueue = [];
42 | this.onQueue = [];
43 | this.plugin = plugin;
44 | this.hook = hook;
45 | const defaultSettings = {};
46 | if (plugin.settings) {
47 | for (const id in plugin.settings) {
48 | const item = plugin.settings[id];
49 | defaultSettings[id] = item.defaultValue;
50 | }
51 | }
52 | const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;
53 | let currentSettings = Object.assign({}, defaultSettings);
54 | try {
55 | const raw = localStorage.getItem(localSettingsSaveId);
56 | const data = JSON.parse(raw);
57 | Object.assign(currentSettings, data);
58 | } catch (e) {
59 | }
60 | this.fallbacks = {
61 | getSettings() {
62 | return currentSettings;
63 | },
64 | setSettings(value) {
65 | try {
66 | localStorage.setItem(localSettingsSaveId, JSON.stringify(value));
67 | } catch (e) {
68 | }
69 | currentSettings = value;
70 | },
71 | now() {
72 | return now();
73 | }
74 | };
75 | if (hook) {
76 | hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
77 | if (pluginId === this.plugin.id) {
78 | this.fallbacks.setSettings(value);
79 | }
80 | });
81 | }
82 | this.proxiedOn = new Proxy({}, {
83 | get: (_target, prop) => {
84 | if (this.target) {
85 | return this.target.on[prop];
86 | } else {
87 | return (...args) => {
88 | this.onQueue.push({
89 | method: prop,
90 | args
91 | });
92 | };
93 | }
94 | }
95 | });
96 | this.proxiedTarget = new Proxy({}, {
97 | get: (_target, prop) => {
98 | if (this.target) {
99 | return this.target[prop];
100 | } else if (prop === "on") {
101 | return this.proxiedOn;
102 | } else if (Object.keys(this.fallbacks).includes(prop)) {
103 | return (...args) => {
104 | this.targetQueue.push({
105 | method: prop,
106 | args,
107 | resolve: () => {
108 | }
109 | });
110 | return this.fallbacks[prop](...args);
111 | };
112 | } else {
113 | return (...args) => {
114 | return new Promise((resolve) => {
115 | this.targetQueue.push({
116 | method: prop,
117 | args,
118 | resolve
119 | });
120 | });
121 | };
122 | }
123 | }
124 | });
125 | }
126 | async setRealTarget(target) {
127 | this.target = target;
128 | for (const item of this.onQueue) {
129 | this.target.on[item.method](...item.args);
130 | }
131 | for (const item of this.targetQueue) {
132 | item.resolve(await this.target[item.method](...item.args));
133 | }
134 | }
135 | };
136 |
137 | // node_modules/@vue/devtools-api/lib/esm/index.js
138 | function setupDevtoolsPlugin(pluginDescriptor, setupFn) {
139 | const descriptor = pluginDescriptor;
140 | const target = getTarget();
141 | const hook = getDevtoolsGlobalHook();
142 | const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;
143 | if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
144 | hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);
145 | } else {
146 | const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;
147 | const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];
148 | list.push({
149 | pluginDescriptor: descriptor,
150 | setupFn,
151 | proxy
152 | });
153 | if (proxy)
154 | setupFn(proxy.proxiedTarget);
155 | }
156 | }
157 | export {
158 | isPerformanceSupported,
159 | now,
160 | setupDevtoolsPlugin
161 | };
162 | //# sourceMappingURL=vitepress___@vue_devtools-api.js.map
163 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": ["../../../node_modules/@vue/devtools-api/lib/esm/env.js", "../../../node_modules/@vue/devtools-api/lib/esm/const.js", "../../../node_modules/@vue/devtools-api/lib/esm/time.js", "../../../node_modules/@vue/devtools-api/lib/esm/proxy.js", "../../../node_modules/@vue/devtools-api/lib/esm/index.js"],
4 | "sourcesContent": ["export function getDevtoolsGlobalHook() {\n return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;\n}\nexport function getTarget() {\n // @ts-ignore\n return (typeof navigator !== 'undefined' && typeof window !== 'undefined')\n ? window\n : typeof global !== 'undefined'\n ? global\n : {};\n}\nexport const isProxyAvailable = typeof Proxy === 'function';\n", "export const HOOK_SETUP = 'devtools-plugin:setup';\nexport const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set';\n", "let supported;\nlet perf;\nexport function isPerformanceSupported() {\n var _a;\n if (supported !== undefined) {\n return supported;\n }\n if (typeof window !== 'undefined' && window.performance) {\n supported = true;\n perf = window.performance;\n }\n else if (typeof global !== 'undefined' && ((_a = global.perf_hooks) === null || _a === void 0 ? void 0 : _a.performance)) {\n supported = true;\n perf = global.perf_hooks.performance;\n }\n else {\n supported = false;\n }\n return supported;\n}\nexport function now() {\n return isPerformanceSupported() ? perf.now() : Date.now();\n}\n", "import { HOOK_PLUGIN_SETTINGS_SET } from './const.js';\nimport { now } from './time.js';\nexport class ApiProxy {\n constructor(plugin, hook) {\n this.target = null;\n this.targetQueue = [];\n this.onQueue = [];\n this.plugin = plugin;\n this.hook = hook;\n const defaultSettings = {};\n if (plugin.settings) {\n for (const id in plugin.settings) {\n const item = plugin.settings[id];\n defaultSettings[id] = item.defaultValue;\n }\n }\n const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;\n let currentSettings = Object.assign({}, defaultSettings);\n try {\n const raw = localStorage.getItem(localSettingsSaveId);\n const data = JSON.parse(raw);\n Object.assign(currentSettings, data);\n }\n catch (e) {\n // noop\n }\n this.fallbacks = {\n getSettings() {\n return currentSettings;\n },\n setSettings(value) {\n try {\n localStorage.setItem(localSettingsSaveId, JSON.stringify(value));\n }\n catch (e) {\n // noop\n }\n currentSettings = value;\n },\n now() {\n return now();\n },\n };\n if (hook) {\n hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {\n if (pluginId === this.plugin.id) {\n this.fallbacks.setSettings(value);\n }\n });\n }\n this.proxiedOn = new Proxy({}, {\n get: (_target, prop) => {\n if (this.target) {\n return this.target.on[prop];\n }\n else {\n return (...args) => {\n this.onQueue.push({\n method: prop,\n args,\n });\n };\n }\n },\n });\n this.proxiedTarget = new Proxy({}, {\n get: (_target, prop) => {\n if (this.target) {\n return this.target[prop];\n }\n else if (prop === 'on') {\n return this.proxiedOn;\n }\n else if (Object.keys(this.fallbacks).includes(prop)) {\n return (...args) => {\n this.targetQueue.push({\n method: prop,\n args,\n resolve: () => { },\n });\n return this.fallbacks[prop](...args);\n };\n }\n else {\n return (...args) => {\n return new Promise(resolve => {\n this.targetQueue.push({\n method: prop,\n args,\n resolve,\n });\n });\n };\n }\n },\n });\n }\n async setRealTarget(target) {\n this.target = target;\n for (const item of this.onQueue) {\n this.target.on[item.method](...item.args);\n }\n for (const item of this.targetQueue) {\n item.resolve(await this.target[item.method](...item.args));\n }\n }\n}\n", "import { getTarget, getDevtoolsGlobalHook, isProxyAvailable } from './env.js';\nimport { HOOK_SETUP } from './const.js';\nimport { ApiProxy } from './proxy.js';\nexport * from './api/index.js';\nexport * from './plugin.js';\nexport * from './time.js';\nexport function setupDevtoolsPlugin(pluginDescriptor, setupFn) {\n const descriptor = pluginDescriptor;\n const target = getTarget();\n const hook = getDevtoolsGlobalHook();\n const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;\n if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {\n hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);\n }\n else {\n const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;\n const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];\n list.push({\n pluginDescriptor: descriptor,\n setupFn,\n proxy,\n });\n if (proxy)\n setupFn(proxy.proxiedTarget);\n }\n}\n"],
5 | "mappings": ";AAAO,SAAS,wBAAwB;AACpC,SAAO,UAAU,EAAE;AACvB;AACO,SAAS,YAAY;AAExB,SAAQ,OAAO,cAAc,eAAe,OAAO,WAAW,cACxD,SACA,OAAO,WAAW,cACd,SACA,CAAC;AACf;AACO,IAAM,mBAAmB,OAAO,UAAU;;;ACX1C,IAAM,aAAa;AACnB,IAAM,2BAA2B;;;ACDxC,IAAI;AACJ,IAAI;AACG,SAAS,yBAAyB;AACrC,MAAI;AACJ,MAAI,cAAc,QAAW;AACzB,WAAO;AAAA,EACX;AACA,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa;AACrD,gBAAY;AACZ,WAAO,OAAO;AAAA,EAClB,WACS,OAAO,WAAW,iBAAiB,KAAK,OAAO,gBAAgB,QAAQ,OAAO,SAAS,SAAS,GAAG,cAAc;AACtH,gBAAY;AACZ,WAAO,OAAO,WAAW;AAAA,EAC7B,OACK;AACD,gBAAY;AAAA,EAChB;AACA,SAAO;AACX;AACO,SAAS,MAAM;AAClB,SAAO,uBAAuB,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI;AAC5D;;;ACpBO,IAAM,WAAN,MAAe;AAAA,EAClB,YAAY,QAAQ,MAAM;AACtB,SAAK,SAAS;AACd,SAAK,cAAc,CAAC;AACpB,SAAK,UAAU,CAAC;AAChB,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,UAAM,kBAAkB,CAAC;AACzB,QAAI,OAAO,UAAU;AACjB,iBAAW,MAAM,OAAO,UAAU;AAC9B,cAAM,OAAO,OAAO,SAAS,EAAE;AAC/B,wBAAgB,EAAE,IAAI,KAAK;AAAA,MAC/B;AAAA,IACJ;AACA,UAAM,sBAAsB,mCAAmC,OAAO,EAAE;AACxE,QAAI,kBAAkB,OAAO,OAAO,CAAC,GAAG,eAAe;AACvD,QAAI;AACA,YAAM,MAAM,aAAa,QAAQ,mBAAmB;AACpD,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,aAAO,OAAO,iBAAiB,IAAI;AAAA,IACvC,SACO,GAAG;AAAA,IAEV;AACA,SAAK,YAAY;AAAA,MACb,cAAc;AACV,eAAO;AAAA,MACX;AAAA,MACA,YAAY,OAAO;AACf,YAAI;AACA,uBAAa,QAAQ,qBAAqB,KAAK,UAAU,KAAK,CAAC;AAAA,QACnE,SACO,GAAG;AAAA,QAEV;AACA,0BAAkB;AAAA,MACtB;AAAA,MACA,MAAM;AACF,eAAO,IAAI;AAAA,MACf;AAAA,IACJ;AACA,QAAI,MAAM;AACN,WAAK,GAAG,0BAA0B,CAAC,UAAU,UAAU;AACnD,YAAI,aAAa,KAAK,OAAO,IAAI;AAC7B,eAAK,UAAU,YAAY,KAAK;AAAA,QACpC;AAAA,MACJ,CAAC;AAAA,IACL;AACA,SAAK,YAAY,IAAI,MAAM,CAAC,GAAG;AAAA,MAC3B,KAAK,CAAC,SAAS,SAAS;AACpB,YAAI,KAAK,QAAQ;AACb,iBAAO,KAAK,OAAO,GAAG,IAAI;AAAA,QAC9B,OACK;AACD,iBAAO,IAAI,SAAS;AAChB,iBAAK,QAAQ,KAAK;AAAA,cACd,QAAQ;AAAA,cACR;AAAA,YACJ,CAAC;AAAA,UACL;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,CAAC;AACD,SAAK,gBAAgB,IAAI,MAAM,CAAC,GAAG;AAAA,MAC/B,KAAK,CAAC,SAAS,SAAS;AACpB,YAAI,KAAK,QAAQ;AACb,iBAAO,KAAK,OAAO,IAAI;AAAA,QAC3B,WACS,SAAS,MAAM;AACpB,iBAAO,KAAK;AAAA,QAChB,WACS,OAAO,KAAK,KAAK,SAAS,EAAE,SAAS,IAAI,GAAG;AACjD,iBAAO,IAAI,SAAS;AAChB,iBAAK,YAAY,KAAK;AAAA,cAClB,QAAQ;AAAA,cACR;AAAA,cACA,SAAS,MAAM;AAAA,cAAE;AAAA,YACrB,CAAC;AACD,mBAAO,KAAK,UAAU,IAAI,EAAE,GAAG,IAAI;AAAA,UACvC;AAAA,QACJ,OACK;AACD,iBAAO,IAAI,SAAS;AAChB,mBAAO,IAAI,QAAQ,aAAW;AAC1B,mBAAK,YAAY,KAAK;AAAA,gBAClB,QAAQ;AAAA,gBACR;AAAA,gBACA;AAAA,cACJ,CAAC;AAAA,YACL,CAAC;AAAA,UACL;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,MAAM,cAAc,QAAQ;AACxB,SAAK,SAAS;AACd,eAAW,QAAQ,KAAK,SAAS;AAC7B,WAAK,OAAO,GAAG,KAAK,MAAM,EAAE,GAAG,KAAK,IAAI;AAAA,IAC5C;AACA,eAAW,QAAQ,KAAK,aAAa;AACjC,WAAK,QAAQ,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;AAAA,IAC7D;AAAA,EACJ;AACJ;;;ACpGO,SAAS,oBAAoB,kBAAkB,SAAS;AAC3D,QAAM,aAAa;AACnB,QAAM,SAAS,UAAU;AACzB,QAAM,OAAO,sBAAsB;AACnC,QAAM,cAAc,oBAAoB,WAAW;AACnD,MAAI,SAAS,OAAO,yCAAyC,CAAC,cAAc;AACxE,SAAK,KAAK,YAAY,kBAAkB,OAAO;AAAA,EACnD,OACK;AACD,UAAM,QAAQ,cAAc,IAAI,SAAS,YAAY,IAAI,IAAI;AAC7D,UAAM,OAAO,OAAO,2BAA2B,OAAO,4BAA4B,CAAC;AACnF,SAAK,KAAK;AAAA,MACN,kBAAkB;AAAA,MAClB;AAAA,MACA;AAAA,IACJ,CAAC;AACD,QAAI;AACA,cAAQ,MAAM,aAAa;AAAA,EACnC;AACJ;",
6 | "names": []
7 | }
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vue.js:
--------------------------------------------------------------------------------
1 | import {
2 | BaseTransition,
3 | BaseTransitionPropsValidators,
4 | Comment,
5 | EffectScope,
6 | Fragment,
7 | KeepAlive,
8 | ReactiveEffect,
9 | Static,
10 | Suspense,
11 | Teleport,
12 | Text,
13 | Transition,
14 | TransitionGroup,
15 | VueElement,
16 | assertNumber,
17 | callWithAsyncErrorHandling,
18 | callWithErrorHandling,
19 | camelize,
20 | capitalize,
21 | cloneVNode,
22 | compatUtils,
23 | compile,
24 | computed,
25 | createApp,
26 | createBaseVNode,
27 | createBlock,
28 | createCommentVNode,
29 | createElementBlock,
30 | createHydrationRenderer,
31 | createPropsRestProxy,
32 | createRenderer,
33 | createSSRApp,
34 | createSlots,
35 | createStaticVNode,
36 | createTextVNode,
37 | createVNode,
38 | customRef,
39 | defineAsyncComponent,
40 | defineComponent,
41 | defineCustomElement,
42 | defineEmits,
43 | defineExpose,
44 | defineModel,
45 | defineOptions,
46 | defineProps,
47 | defineSSRCustomElement,
48 | defineSlots,
49 | devtools,
50 | effect,
51 | effectScope,
52 | getCurrentInstance,
53 | getCurrentScope,
54 | getTransitionRawChildren,
55 | guardReactiveProps,
56 | h,
57 | handleError,
58 | hasInjectionContext,
59 | hydrate,
60 | initCustomFormatter,
61 | initDirectivesForSSR,
62 | inject,
63 | isMemoSame,
64 | isProxy,
65 | isReactive,
66 | isReadonly,
67 | isRef,
68 | isRuntimeOnly,
69 | isShallow,
70 | isVNode,
71 | markRaw,
72 | mergeDefaults,
73 | mergeModels,
74 | mergeProps,
75 | nextTick,
76 | normalizeClass,
77 | normalizeProps,
78 | normalizeStyle,
79 | onActivated,
80 | onBeforeMount,
81 | onBeforeUnmount,
82 | onBeforeUpdate,
83 | onDeactivated,
84 | onErrorCaptured,
85 | onMounted,
86 | onRenderTracked,
87 | onRenderTriggered,
88 | onScopeDispose,
89 | onServerPrefetch,
90 | onUnmounted,
91 | onUpdated,
92 | openBlock,
93 | popScopeId,
94 | provide,
95 | proxyRefs,
96 | pushScopeId,
97 | queuePostFlushCb,
98 | reactive,
99 | readonly,
100 | ref,
101 | registerRuntimeCompiler,
102 | render,
103 | renderList,
104 | renderSlot,
105 | resolveComponent,
106 | resolveDirective,
107 | resolveDynamicComponent,
108 | resolveFilter,
109 | resolveTransitionHooks,
110 | setBlockTracking,
111 | setDevtoolsHook,
112 | setTransitionHooks,
113 | shallowReactive,
114 | shallowReadonly,
115 | shallowRef,
116 | ssrContextKey,
117 | ssrUtils,
118 | stop,
119 | toDisplayString,
120 | toHandlerKey,
121 | toHandlers,
122 | toRaw,
123 | toRef,
124 | toRefs,
125 | toValue,
126 | transformVNodeArgs,
127 | triggerRef,
128 | unref,
129 | useAttrs,
130 | useCssModule,
131 | useCssVars,
132 | useModel,
133 | useSSRContext,
134 | useSlots,
135 | useTransitionState,
136 | vModelCheckbox,
137 | vModelDynamic,
138 | vModelRadio,
139 | vModelSelect,
140 | vModelText,
141 | vShow,
142 | version,
143 | warn,
144 | watch,
145 | watchEffect,
146 | watchPostEffect,
147 | watchSyncEffect,
148 | withAsyncContext,
149 | withCtx,
150 | withDefaults,
151 | withDirectives,
152 | withKeys,
153 | withMemo,
154 | withModifiers,
155 | withScopeId
156 | } from "./chunk-7DTDMSOX.js";
157 | export {
158 | BaseTransition,
159 | BaseTransitionPropsValidators,
160 | Comment,
161 | EffectScope,
162 | Fragment,
163 | KeepAlive,
164 | ReactiveEffect,
165 | Static,
166 | Suspense,
167 | Teleport,
168 | Text,
169 | Transition,
170 | TransitionGroup,
171 | VueElement,
172 | assertNumber,
173 | callWithAsyncErrorHandling,
174 | callWithErrorHandling,
175 | camelize,
176 | capitalize,
177 | cloneVNode,
178 | compatUtils,
179 | compile,
180 | computed,
181 | createApp,
182 | createBlock,
183 | createCommentVNode,
184 | createElementBlock,
185 | createBaseVNode as createElementVNode,
186 | createHydrationRenderer,
187 | createPropsRestProxy,
188 | createRenderer,
189 | createSSRApp,
190 | createSlots,
191 | createStaticVNode,
192 | createTextVNode,
193 | createVNode,
194 | customRef,
195 | defineAsyncComponent,
196 | defineComponent,
197 | defineCustomElement,
198 | defineEmits,
199 | defineExpose,
200 | defineModel,
201 | defineOptions,
202 | defineProps,
203 | defineSSRCustomElement,
204 | defineSlots,
205 | devtools,
206 | effect,
207 | effectScope,
208 | getCurrentInstance,
209 | getCurrentScope,
210 | getTransitionRawChildren,
211 | guardReactiveProps,
212 | h,
213 | handleError,
214 | hasInjectionContext,
215 | hydrate,
216 | initCustomFormatter,
217 | initDirectivesForSSR,
218 | inject,
219 | isMemoSame,
220 | isProxy,
221 | isReactive,
222 | isReadonly,
223 | isRef,
224 | isRuntimeOnly,
225 | isShallow,
226 | isVNode,
227 | markRaw,
228 | mergeDefaults,
229 | mergeModels,
230 | mergeProps,
231 | nextTick,
232 | normalizeClass,
233 | normalizeProps,
234 | normalizeStyle,
235 | onActivated,
236 | onBeforeMount,
237 | onBeforeUnmount,
238 | onBeforeUpdate,
239 | onDeactivated,
240 | onErrorCaptured,
241 | onMounted,
242 | onRenderTracked,
243 | onRenderTriggered,
244 | onScopeDispose,
245 | onServerPrefetch,
246 | onUnmounted,
247 | onUpdated,
248 | openBlock,
249 | popScopeId,
250 | provide,
251 | proxyRefs,
252 | pushScopeId,
253 | queuePostFlushCb,
254 | reactive,
255 | readonly,
256 | ref,
257 | registerRuntimeCompiler,
258 | render,
259 | renderList,
260 | renderSlot,
261 | resolveComponent,
262 | resolveDirective,
263 | resolveDynamicComponent,
264 | resolveFilter,
265 | resolveTransitionHooks,
266 | setBlockTracking,
267 | setDevtoolsHook,
268 | setTransitionHooks,
269 | shallowReactive,
270 | shallowReadonly,
271 | shallowRef,
272 | ssrContextKey,
273 | ssrUtils,
274 | stop,
275 | toDisplayString,
276 | toHandlerKey,
277 | toHandlers,
278 | toRaw,
279 | toRef,
280 | toRefs,
281 | toValue,
282 | transformVNodeArgs,
283 | triggerRef,
284 | unref,
285 | useAttrs,
286 | useCssModule,
287 | useCssVars,
288 | useModel,
289 | useSSRContext,
290 | useSlots,
291 | useTransitionState,
292 | vModelCheckbox,
293 | vModelDynamic,
294 | vModelRadio,
295 | vModelSelect,
296 | vModelText,
297 | vShow,
298 | version,
299 | warn,
300 | watch,
301 | watchEffect,
302 | watchPostEffect,
303 | watchSyncEffect,
304 | withAsyncContext,
305 | withCtx,
306 | withDefaults,
307 | withDirectives,
308 | withKeys,
309 | withMemo,
310 | withModifiers,
311 | withScopeId
312 | };
313 | //# sourceMappingURL=vue.js.map
314 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vue.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": [],
4 | "sourcesContent": [],
5 | "mappings": "",
6 | "names": []
7 | }
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitepress";
2 | import { SitemapStream } from "sitemap";
3 | import { resolve } from "node:path";
4 | import { createWriteStream } from "node:fs";
5 | import fs from "fs";
6 | import path from "path";
7 |
8 | const links: any = [];
9 |
10 | // https://vitepress.dev/reference/site-config
11 | export default defineConfig({
12 | title: "Robust Validator",
13 | description: "Another data validation library",
14 | lang: "en-US",
15 | lastUpdated: false,
16 | themeConfig: {
17 | nav: [{ text: "Demo", link: "https://validator.axe-api.com/demo" }],
18 |
19 | editLink: {
20 | pattern: "https://github.com/axe-api/validator/edit/main/docs/:path",
21 | text: "Edit this page on GitHub",
22 | },
23 |
24 | sidebar: [
25 | {
26 | text: "Introduction",
27 | items: [
28 | { text: "Why?", link: "/why" },
29 | { text: "Getting started", link: "/getting-started" },
30 | { text: "Examples", link: "/examples" },
31 | { text: "Terminology", link: "/terminology" },
32 | ],
33 | },
34 | {
35 | text: "Reference",
36 | items: [
37 | { text: "Rules", link: "/rules" },
38 | { text: "i18n", link: "/i18n" },
39 | { text: "Customization", link: "/customization" },
40 | { text: "Options", link: "/options" },
41 | ],
42 | },
43 | ],
44 | socialLinks: [
45 | { icon: "github", link: "https://github.com/axe-api/validator" },
46 | ],
47 |
48 | footer: {
49 | message: "Released under the MIT License.",
50 | copyright: "Copyright © 2020-present",
51 | },
52 |
53 | search: {
54 | provider: "local",
55 | },
56 | },
57 |
58 | transformHtml: (_, id, { pageData }) => {
59 | const file = fs.statSync(path.join(__dirname, "..", pageData.relativePath));
60 | if (!/[\\/]404\.html$/.test(id)) {
61 | const url = pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2");
62 | // you might need to change this if not using clean urls mode
63 | links.push({
64 | url: url.length > 0 ? `${url}.html` : url,
65 | lastmod: file.mtime,
66 | });
67 | }
68 | },
69 |
70 | buildEnd: ({ outDir }) => {
71 | // console.log(links)
72 | const sitemap = new SitemapStream({
73 | hostname: "https://validator.axe-api.com/",
74 | });
75 | const writeStream = createWriteStream(resolve(outDir, "sitemap.xml"));
76 | sitemap.pipe(writeStream);
77 | links.forEach((link) => sitemap.write(link));
78 | sitemap.end();
79 | },
80 | });
81 |
--------------------------------------------------------------------------------
/docs/api-examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Runtime API Examples
6 |
7 | This page demonstrates usage of some of the runtime APIs provided by VitePress.
8 |
9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
10 |
11 | ```md
12 |
17 |
18 | ## Results
19 |
20 | ### Theme Data
21 |
22 | {{ theme }}
23 |
24 | ### Page Data
25 |
26 | {{ page }}
27 |
28 | ### Page Frontmatter
29 |
30 | {{ frontmatter }}
31 | ```
32 |
33 |
38 |
39 | ## Results
40 |
41 | ### Theme Data
42 |
43 | {{ theme }}
44 |
45 | ### Page Data
46 |
47 | {{ page }}
48 |
49 | ### Page Frontmatter
50 |
51 | {{ frontmatter }}
52 |
53 | ## More
54 |
55 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
56 |
--------------------------------------------------------------------------------
/docs/customization.md:
--------------------------------------------------------------------------------
1 | # Customization
2 |
3 | You can register your custom rules easily.
4 |
5 | ## `register()`
6 |
7 | ```ts
8 | import { validate, setLocales, register, en } from "robust-validator";
9 |
10 | // Setting the locales firsrts
11 | setLocales(en);
12 |
13 | const ruleFunction = (value: any) => {
14 | // TODO: add your custom rule logic here
15 | return false;
16 | };
17 |
18 | register(
19 | "exists", // The rule name
20 | ruleFunction, // The rule functions
21 | // Translations
22 | {
23 | en: "The record doesn't exists on database: {0}",
24 | },
25 | );
26 |
27 | const validateData = async () => {
28 | const result = await validate(
29 | { email: "user@example.com" },
30 | {
31 | email: "required|exists:users",
32 | },
33 | );
34 | };
35 | ```
36 |
37 | ## `isRegister()`
38 |
39 | You can check if the rule is already register.
40 |
41 | ```ts
42 | import { isRegistered, register } from "robust-validator";
43 |
44 | if (!isRegistered("myCustomRule")) {
45 | // register your rule
46 | }
47 | ```
48 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | # Demo
2 |
3 | In this page, you can find many example codes.
4 |
5 | ## Vue.js
6 |
7 | You can find a Vue.js example here.
8 |
9 | ::: code-group
10 |
11 | ```vue [App.vue]
12 |
27 |
28 |
29 |
35 |
36 | ```
37 |
38 | :::
39 |
40 | ## Node.js (CJS)
41 |
42 | You can find a Node.js example here.
43 |
44 | ::: code-group
45 |
46 | ```js [index.js]
47 | const { validate, setLocales, en } = require("robust-validator");
48 |
49 | setLocales(en);
50 |
51 | const validate = async (data) => {
52 | const result = await validate(data, {
53 | email: "required|email|min:1|max:50",
54 | });
55 |
56 | console.log(result);
57 | };
58 |
59 | validate({
60 | email: null,
61 | });
62 | ```
63 |
64 | :::
65 |
66 | ## Node.js (ESM)
67 |
68 | You can find a Node.js (ESM) example here.
69 |
70 | ::: code-group
71 |
72 | ```js [index.ts]
73 | import { validate, setLocales, en } from "robust-validator";
74 |
75 | setLocales(en);
76 |
77 | const validate = async (data) => {
78 | const result = await validate(data, {
79 | email: "required|email|min:1|max:50",
80 | });
81 |
82 | console.log(result);
83 | };
84 |
85 | validate({
86 | email: null,
87 | });
88 | ```
89 |
90 | :::
91 |
92 | ## TypeScript
93 |
94 | You can find a TypeScript example here.
95 |
96 | ::: code-group
97 |
98 | ```js [index.js]
99 | import { validate, setLocales, en } from "robust-validator";
100 |
101 | setLocales(en);
102 |
103 | const validate = async (data) => {
104 | const result = await validate(data, {
105 | email: "required|email|min:1|max:50",
106 | });
107 |
108 | console.log(result);
109 | };
110 |
111 | validate({
112 | email: null,
113 | });
114 | ```
115 |
116 | :::
117 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | ## Installation
4 |
5 | The library can be installed into an existing project:
6 |
7 | ```bash
8 | $ npm install --save robust-validator
9 | ```
10 |
11 | ## Usage
12 |
13 | Using `robust-validator` is very simple.
14 |
15 | You should just call the `validate()` function with data and the definition.
16 |
17 | ::: code-group
18 |
19 | ```js [Declarative]
20 | import { validate, setLocales, en } from "robust-validator";
21 |
22 | setLocales(en);
23 |
24 | const data = {
25 | email: "not-a-valid-email",
26 | name: "John",
27 | surname: "Doe",
28 | };
29 |
30 | const definition = {
31 | email: "required|email",
32 | name: "required|min:1|max:50",
33 | surname: "required|min:1|max:50",
34 | };
35 |
36 | const result = await validate(data, definition);
37 | console.log(result);
38 | ```
39 |
40 | ```js [Function-based]
41 | import {
42 | validate,
43 | setLocales,
44 | required,
45 | email,
46 | min,
47 | max,
48 | en,
49 | } from "robust-validator";
50 |
51 | setLocales(en);
52 |
53 | const data = {
54 | email: "not-a-valid-email",
55 | name: "John",
56 | surname: "Doe",
57 | };
58 |
59 | const definition = {
60 | email: [required(), email()],
61 | name: [required(), min(1), max(50)],
62 | surname: [required(), min(1), max(50)],
63 | };
64 |
65 | const result = await validate(data, definition);
66 | console.log(result);
67 | ```
68 |
69 | :::
70 |
71 | By the example, you would get the following response:
72 |
73 | ```json
74 | {
75 | "isValid": false,
76 | "isInvalid": true,
77 | "fields": {
78 | "email": false,
79 | "name": true,
80 | "surname": true
81 | },
82 | "errors": {
83 | "email": [
84 | {
85 | "rule": "required",
86 | "message": "The field is required."
87 | }
88 | ]
89 | }
90 | }
91 | ```
92 |
93 | ## Nested data validation
94 |
95 | This feature allows dynamic traversal of nested data structures, supporting complex validation rules for paths like `users.*.addresses.*.city`.
96 |
97 | It is inspired by Laravel's validation system and works seamlessly with arrays and objects, including deeply nested data.
98 |
99 | ```ts
100 | import { validate, setLocales, en } from "robust-validator";
101 |
102 | setLocales(en);
103 |
104 | const data = {
105 | secret: "some secret",
106 | users: [
107 | {
108 | addresses: [
109 | {
110 | city: "New York",
111 | },
112 | {
113 | city: "Istanbul",
114 | },
115 | ],
116 | },
117 | {
118 | addresses: [
119 | {
120 | city: "New York",
121 | },
122 | {
123 | street: "Wall Street",
124 | },
125 | ],
126 | },
127 | ],
128 | permissons: {
129 | read: true,
130 | write: true,
131 | },
132 | };
133 |
134 | const definition = {
135 | secret: "required|min:100",
136 | "users.*.addresses.*.city": "required",
137 | "permissons.read": "required|boolean",
138 | "permissons.delete": "required|boolean",
139 | };
140 |
141 | const result = await validate(data, definition);
142 | console.log(result);
143 | ```
144 |
145 | And this is the content of the `result` variable:
146 |
147 | ```json
148 | {
149 | "isValid": false,
150 | "isInvalid": true,
151 | "fields": {
152 | "secret": false,
153 | "users.*.addresses.*.city": false,
154 | "permissons.read": true,
155 | "permissons.delete": false
156 | },
157 | "errors": {
158 | "secret": [
159 | {
160 | "rule": "min",
161 | "message": "The field must be at least 100."
162 | }
163 | ],
164 | "users.1.addresses.1.city": [
165 | {
166 | "rule": "required",
167 | "message": "The field is required."
168 | }
169 | ],
170 | "permissons.delete": [
171 | {
172 | "rule": "required",
173 | "message": "The field is required."
174 | }
175 | ]
176 | }
177 | }
178 | ```
179 |
--------------------------------------------------------------------------------
/docs/i18n.md:
--------------------------------------------------------------------------------
1 | # Internationalization (i18n)
2 |
3 | `robust-validator` supports 66 languages in error messages.
4 |
5 | ## Setting locales
6 |
7 | You should import languages that you plan to support like the following example.
8 |
9 | ```ts
10 | import { validate, setLocales, en, fr, de } from "robust-validator";
11 |
12 | setLocales([en, fr, de]);
13 | ```
14 |
15 | ## Default language
16 |
17 | You can decide default language selection.
18 |
19 | ```ts
20 | import { validate, setLocales, setOptions, en } from "robust-validator";
21 |
22 | setLocales(en);
23 |
24 | // Setting the default language
25 | setOptions({
26 | language: "en",
27 | });
28 | ```
29 |
30 | ## Active language
31 |
32 | You can override the active language by users' selection for every data validation.
33 |
34 | ```ts
35 | await validate(data, { email: "required" }, { language: "de" });
36 | ```
37 |
38 | ## Supported Languages
39 |
40 | | Code (ISO 639-1) | Language Name |
41 | | ---------------- | -------------------------------------------------------------------------------- |
42 | | ar | [Arabic](https://github.com/axe-api/validator/blob/main/src/i18n/ar.json) |
43 | | az | [Azerbaijani](https://github.com/axe-api/validator/blob/main/src/i18n/az.json) |
44 | | be | [Belarusian](https://github.com/axe-api/validator/blob/main/src/i18n/be.json) |
45 | | bg | [Bulgarian](https://github.com/axe-api/validator/blob/main/src/i18n/bg.json) |
46 | | bs | [Bosnian](https://github.com/axe-api/validator/blob/main/src/i18n/bs.json) |
47 | | ca | [Catalan](https://github.com/axe-api/validator/blob/main/src/i18n/ca.json) |
48 | | cs | [Czech](https://github.com/axe-api/validator/blob/main/src/i18n/cs.json) |
49 | | cy | [Welsh](https://github.com/axe-api/validator/blob/main/src/i18n/cy.json) |
50 | | da | [Danish](https://github.com/axe-api/validator/blob/main/src/i18n/da.json) |
51 | | de | [German](https://github.com/axe-api/validator/blob/main/src/i18n/de.json) |
52 | | el | [Greek](https://github.com/axe-api/validator/blob/main/src/i18n/el.json) |
53 | | en | [English](https://github.com/axe-api/validator/blob/main/src/i18n/en.json) |
54 | | es | [Spanish](https://github.com/axe-api/validator/blob/main/src/i18n/es.json) |
55 | | et | [Estonian](https://github.com/axe-api/validator/blob/main/src/i18n/et.json) |
56 | | eu | [Basque](https://github.com/axe-api/validator/blob/main/src/i18n/eu.json) |
57 | | fa | [Persian](https://github.com/axe-api/validator/blob/main/src/i18n/fa.json) |
58 | | fi | [Finnish](https://github.com/axe-api/validator/blob/main/src/i18n/fi.json) |
59 | | fr | [French](https://github.com/axe-api/validator/blob/main/src/i18n/fr.json) |
60 | | hr | [Croatian](https://github.com/axe-api/validator/blob/main/src/i18n/hr.json) |
61 | | hu | [Hungarian](https://github.com/axe-api/validator/blob/main/src/i18n/hu.json) |
62 | | id | [Indonesian](https://github.com/axe-api/validator/blob/main/src/i18n/id.json) |
63 | | it | [Italian](https://github.com/axe-api/validator/blob/main/src/i18n/it.json) |
64 | | ja | [Japanese](https://github.com/axe-api/validator/blob/main/src/i18n/ja.json) |
65 | | ka | [Georgian](https://github.com/axe-api/validator/blob/main/src/i18n/ka.json) |
66 | | ko | [Korean](https://github.com/axe-api/validator/blob/main/src/i18n/ko.json) |
67 | | li | [Limburgish](https://github.com/axe-api/validator/blob/main/src/i18n/li.json) |
68 | | lt | [Lithuanian](https://github.com/axe-api/validator/blob/main/src/i18n/lt.json) |
69 | | lv | [Latvian](https://github.com/axe-api/validator/blob/main/src/i18n/lv.json) |
70 | | mk | [Macedonian](https://github.com/axe-api/validator/blob/main/src/i18n/mk.json) |
71 | | mn | [Mongolian](https://github.com/axe-api/validator/blob/main/src/i18n/mn.json) |
72 | | ms | [Malay](https://github.com/axe-api/validator/blob/main/src/i18n/ms.json) |
73 | | no | [Norwegian](https://github.com/axe-api/validator/blob/main/src/i18n/no.json) |
74 | | nl | [Dutch](https://github.com/axe-api/validator/blob/main/src/i18n/nl.json) |
75 | | pl | [Polish](https://github.com/axe-api/validator/blob/main/src/i18n/pl.json) |
76 | | pt | [Portuguese](https://github.com/axe-api/validator/blob/main/src/i18n/pt.json) |
77 | | ro | [Romanian](https://github.com/axe-api/validator/blob/main/src/i18n/ro.json) |
78 | | ru | [Russian](https://github.com/axe-api/validator/blob/main/src/i18n/ru.json) |
79 | | se | [Northern Sami](https://github.com/axe-api/validator/blob/main/src/i18n/se.json) |
80 | | sl | [Slovenian](https://github.com/axe-api/validator/blob/main/src/i18n/sl.json) |
81 | | sq | [Albanian](https://github.com/axe-api/validator/blob/main/src/i18n/sq.json) |
82 | | sr | [Serbian](https://github.com/axe-api/validator/blob/main/src/i18n/sr.json) |
83 | | sv | [Swedish](https://github.com/axe-api/validator/blob/main/src/i18n/sv.json) |
84 | | tr | [Turkish](https://github.com/axe-api/validator/blob/main/src/i18n/tr.json) |
85 | | uk | [Ukrainian](https://github.com/axe-api/validator/blob/main/src/i18n/uk.json) |
86 | | vi | [Vietnamese](https://github.com/axe-api/validator/blob/main/src/i18n/vi.json) |
87 | | zh | [Chinese](https://github.com/axe-api/validator/blob/main/src/i18n/zh.json) |
88 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Robust Validator"
7 | text: "Rule-based data validation in JS"
8 | tagline: Extendable, function-oriented, i18n-supported
9 | image:
10 | src: /logo.png
11 | alt: Robust Validator
12 | actions:
13 | - theme: brand
14 | text: Getting started
15 | link: /getting-started
16 |
17 | features:
18 | - title: Declarative ✍🏽
19 | details: Declarative rule definition allows you to save your rules in different places such as configuration files, databases, etc.
20 | - title: Simple 🐤
21 | details: Starting to validate data is very fast. Instead of creating complicated validation rules, you just need seconds.
22 | - title: Proof of work 💪
23 | details: Laravel-ish data validation rules are well-tested as a concept. This library is just another implementation for JavaScript.
24 | - title: i18n 🇺🇳
25 | details: Multi-language error messages are supported internally, unlike other libraries.
26 | ---
27 |
--------------------------------------------------------------------------------
/docs/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | ## Default options
4 |
5 | You can set options for the validator.
6 |
7 | ```ts
8 | import { validate, setLocales, setOptions, en } from "robust-validator";
9 |
10 | setLocales(en);
11 |
12 | // Setting the default options
13 | setOptions({
14 | stopOnFail: true,
15 | language: "en",
16 | dateFormat: "yyyy-MM-dd",
17 | });
18 | ```
19 |
20 | ## Active options
21 |
22 | You can override the default options for a validate action like the following example:
23 |
24 | ```ts
25 | import { validate, setLocales, setOptions, en } from "robust-validator";
26 |
27 | setLocales(en);
28 |
29 | // Setting the default options
30 | setOptions({
31 | stopOnFail: true,
32 | language: "en",
33 | dateFormat: "yyyy-MM-dd",
34 | });
35 |
36 | await validate(
37 | data,
38 | { email: "required" },
39 | // You can override any of the following property
40 | {
41 | stopOnFail: false,
42 | language: "de",
43 | dateFormat: "yyyy-MM-dd",
44 | },
45 | );
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "robust-validator-docs",
3 | "scripts": {
4 | "docs:dev": "vitepress dev",
5 | "docs:build": "vitepress build",
6 | "docs:preview": "vitepress preview"
7 | },
8 | "dependencies": {
9 | "sitemap-ts": "^1.6.1"
10 | },
11 | "devDependencies": {
12 | "sass": "^1.69.5",
13 | "vite-ssg": "^0.23.5",
14 | "vite-ssg-sitemap": "^0.6.1",
15 | "vitepress": "^1.0.0-rc.31"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/docs/public/CNAME:
--------------------------------------------------------------------------------
1 | validator.axe-api.com
2 |
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axe-api/validator/5a039cf767052c45a731435553056dfe6000438a/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/terminology.md:
--------------------------------------------------------------------------------
1 | # Terminology
2 |
3 | In this section, we are going to explain the basic terminology.
4 |
5 | ## Rule
6 |
7 | A rule is a function that takes at least one argument (value) and validates the data. It should return a `true` or `false` value as a return.
8 |
9 | The following type definition is the definition of a rule function.
10 |
11 | ```ts
12 | type RuleFunction = (...args: any[]) => Promise | boolean;
13 | ```
14 |
15 | The first parameter of a rule function should be the value that will be validated always.
16 |
17 | ```ts
18 | export default (value: any): boolean => {
19 | // Return `true` if the `value` is valid.
20 | return true;
21 | };
22 | ```
23 |
24 | A rule function always might have more parameters if it needs them. For example, if you want to check the minimum string size, the rule function should have to parameter like the following example.
25 |
26 | ```ts
27 | export default (value: any, size: any): boolean => {
28 | // Return `true` if the `value` is valid.
29 | return true;
30 | };
31 | ```
32 |
33 | A rule should be able to execute directly.
34 |
35 | ```ts
36 | import { isRequired } from "robust-validator";
37 |
38 | const result = isRequired("data");
39 | ```
40 |
41 | :::warning
42 | Each rule function should validate only one thing. For example, the `email` validation **should NOT** check if the data is provided. Otherwise, a rule function can not check the optional data.
43 |
44 | That's why `null` and `undefined` values are acceptable for all rules except the `required`.
45 |
46 | If you want to check if the data is provided and is a valid email, you should use two rules (`required`, `email`) at the same time.
47 |
48 | :::
49 |
50 | ## Definition
51 |
52 | The definition means which rule sets will be executed for data.
53 |
54 | It should be an object like the following example:
55 |
56 | ```js
57 | const definition = {
58 | email: "required|email",
59 | name: "required|min:1|max:50",
60 | surname: "required|min:1|max:50",
61 | };
62 | ```
63 |
64 | For each data property, the rule names should be defined.
65 |
66 | The `|` should be used to be able to use multiple rule names at the same time:
67 |
68 | `required|email|alpha`
69 |
70 | All possible rule parameters should be defined after the `:` operator. If there is more than one parameter, they must be separated using commas.
71 |
72 | `required|min:1|max:50|between:1,50`
73 |
--------------------------------------------------------------------------------
/docs/why.md:
--------------------------------------------------------------------------------
1 | # Why?
2 |
3 | Discovering a data validation library that seamlessly combines ease of use, the ability to store validation rules for future use, and robust internationalization (i18n) support is a formidable challenge. While numerous data validation libraries exist, finding one that fulfills all these criteria is often elusive. Some libraries that do meet these requirements are unfortunately no longer actively maintained.
4 |
5 | Axe Validator was born out of the need for a versatile data validation solution that not only simplifies the validation process but also empowers developers with the flexibility to preserve and reuse validation rules. This library aims to bridge the gap by offering a user-friendly experience, ensuring your validation needs are met comprehensively.
6 |
7 | Why choose Axe Validator? It's more than just a data validation tool; it's a commitment to providing a reliable, well-maintained, and feature-rich solution for developers who value simplicity and effectiveness in their projects.
8 |
9 | ## Principles
10 |
11 | I decided on some fundamental rules while building this library:
12 |
13 | - Every validation rule should be an independent function.
14 | - Every validation rule should be able to be used separately
15 | - All validation definition should be able to be stored anywhere (database, memory, configuration files, 3rd party API, etc) to be used later.
16 | - All validation rules should be able to be used in different languages.
17 | - Contribution to the rule set should be easy.
18 | - Should be well-documented.
19 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./src/Options";
2 | export * from "./src/ruleManager";
3 | export * from "./src/helpers/validate";
4 | export * from "./src/Locale";
5 | export * from "./src/rules";
6 | export * from "./src/converters";
7 | export * from "./src/Constants";
8 | export * from "./src/Interface";
9 | export * from "./src/Types";
10 | export * from "./src/i18n";
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "robust-validator",
3 | "version": "2.2.1",
4 | "description": "Rule-based data validation library",
5 | "type": "module",
6 | "main": "dist/index.cjs",
7 | "module": "dist/index.mjs",
8 | "types": "dist/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "import": "./dist/index.mjs",
12 | "require": "./dist/index.cjs",
13 | "types": "./dist/index.d.ts"
14 | }
15 | },
16 | "scripts": {
17 | "test": "vitest",
18 | "test:run": "vitest run",
19 | "test:coverage": "vitest run --coverage",
20 | "test:consumer": "sh scripts/consumer-test.sh",
21 | "lint": "eslint .",
22 | "prepare": "husky",
23 | "build": "rm -rf ./dist && rollup -c"
24 | },
25 | "files": [
26 | "/dist"
27 | ],
28 | "engines": {
29 | "node": ">=18.0.0"
30 | },
31 | "keywords": [
32 | "robust-validator",
33 | "validatorjs",
34 | "validator.js",
35 | "data validation",
36 | "validator",
37 | "validate",
38 | "validation",
39 | "data",
40 | "laravel",
41 | "laravel-validator-for-js"
42 | ],
43 | "author": "Özgür Adem Işıklı ",
44 | "license": "MIT",
45 | "repository": {
46 | "type": "git",
47 | "url": "https://github.com/axe-api/validator"
48 | },
49 | "bugs": {
50 | "url": "https://github.com/axe-api/validator/issues"
51 | },
52 | "homepage": "https://github.com/axe-api/validator#readme",
53 | "devDependencies": {
54 | "@babel/preset-env": "^7.23.6",
55 | "@babel/preset-typescript": "^7.23.3",
56 | "@jest/globals": "^29.7.0",
57 | "@rollup/plugin-typescript": "^12.1.0",
58 | "@types/jest": "^29.5.11",
59 | "@typescript-eslint/eslint-plugin": "^6.15.0",
60 | "@typescript-eslint/parser": "^6.15.0",
61 | "@vitest/coverage-v8": "^1.1.0",
62 | "eslint": "^8.56.0",
63 | "eslint-config-standard-with-typescript": "^43.0.0",
64 | "eslint-plugin-import": "^2.29.1",
65 | "eslint-plugin-n": "^16.4.0",
66 | "eslint-plugin-promise": "^6.1.1",
67 | "husky": "^9.0.10",
68 | "jest": "^29.7.0",
69 | "rollup": "^4.9.1",
70 | "rollup-plugin-analyzer": "^4.0.0",
71 | "rollup-plugin-auto-external": "^2.0.0",
72 | "rollup-plugin-copy": "^3.5.0",
73 | "rollup-plugin-filesize": "^10.0.0",
74 | "rollup-plugin-peer-deps-external": "^2.2.4",
75 | "rollup-plugin-typescript2": "^0.36.0",
76 | "terser": "^5.27.1",
77 | "ts-jest": "^29.1.1",
78 | "ts-node": "^10.9.2",
79 | "ts-node-dev": "^2.0.0",
80 | "typescript": "^5.3.3",
81 | "vitest": "^1.1.0"
82 | },
83 | "dependencies": {
84 | "date-fns": "^4.1.0"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 |
24 |
25 | Rule-based data validation in JS. Extendable, function-oriented, i18n-supported
26 |
27 | [Documentation](https://validator.axe-api.com/)
28 |
29 | ## 💡 Why?
30 |
31 | Discovering a data validation library that seamlessly combines ease of use, the ability to store validation rules for future use, and robust internationalization (i18n) support is a formidable challenge. While numerous data validation libraries exist, finding one that fulfills all these criteria is often elusive. Some libraries that do meet these requirements are unfortunately no longer actively maintained.
32 |
33 | Robust Validator was born out of the need for a versatile data validation solution that not only simplifies the validation process but also empowers developers with the flexibility to preserve and reuse validation rules. This library aims to bridge the gap by offering a user-friendly experience, ensuring your validation needs are met comprehensively.
34 |
35 | Why choose Robust Validator? It's more than just a data validation tool; it's a commitment to providing a reliable, well-maintained, and feature-rich solution for developers who value simplicity and effectiveness in their projects.
36 |
37 | ## 🤞 Principles
38 |
39 | I decided on some fundamental rules while building this library:
40 |
41 | - ✅︎ Every validation rule should be an independent function.
42 | - ✅︎ Every validation rule should be able to be used separately
43 | - ✅︎ All validation definition should be able to be stored anywhere (database, memory, configuration files, 3rd party API, etc) to be used later.
44 | - ✅︎ All validation rules should be able to be used in different languages.
45 | - ✅︎ Contribution to the rule set should be easy.
46 | - ✅︎ Should be well-documented.
47 |
48 | ## 🏃 Installation
49 |
50 | The library can be installed into an existing project:
51 |
52 | ```bash
53 | $ npm install --save robust-validator
54 | ```
55 |
56 | ## 💪 Usage
57 |
58 | Using robust-validator is very simple.
59 |
60 | You should just call the `validate()` function with data and the definition.
61 |
62 | ```ts
63 | import { validate, setLocales, en } from "robust-validator";
64 |
65 | setLocales(en);
66 |
67 | const data = {
68 | email: "not-a-valid-email",
69 | name: "John",
70 | surname: "Doe",
71 | };
72 |
73 | const definition = {
74 | email: "required|email",
75 | name: "required|min:1|max:50",
76 | surname: "required|min:1|max:50",
77 | };
78 |
79 | const result = await validate(data, definition);
80 | console.log(result);
81 | ```
82 |
83 | ## 🤝 Contributors
84 |
85 |
86 |
87 |
88 |
89 | Made with [contrib.rocks](https://contrib.rocks).
90 |
91 | ## ⚖️ License
92 |
93 | [MIT License](LICENSE)
94 |
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 |
3 | export default {
4 | input: "src/index.ts",
5 | output: [
6 | {
7 | file: "dist/index.cjs",
8 | format: "cjs",
9 | },
10 | {
11 | file: "dist/index.mjs",
12 | format: "esm",
13 | },
14 | ],
15 | plugins: [typescript()],
16 | };
17 |
--------------------------------------------------------------------------------
/scripts/consumer-test.sh:
--------------------------------------------------------------------------------
1 | echo "TESTING!!!"
2 |
3 | npm install
4 | npm run build
5 |
6 | cd tests/consumers/cjs && npm run test
7 | cd ../esm && npm run test
8 | cd ../ts-local && npm run test
--------------------------------------------------------------------------------
/src/Constants.ts:
--------------------------------------------------------------------------------
1 | import { RuleType, RuleFunction } from "./Types";
2 | import * as rules from "./rules";
3 |
4 | export const RULE_FUNCTION_MAPS: Record = {
5 | string: rules.isString,
6 | boolean: rules.isBoolean,
7 | accepted: rules.isAccepted,
8 | after: rules.isAfter,
9 | after_or_equal: rules.isAfterOrEqual,
10 | alpha: rules.isAlpha,
11 | alpha_dash: rules.isAlphaDash,
12 | alpha_num: rules.isAlphaNum,
13 | array: rules.isArray,
14 | before: rules.isBefore,
15 | before_or_equal: rules.isBeforeOrEqual,
16 | between: rules.isBetween,
17 | confirmed: rules.isConfirmed,
18 | date: rules.isDate,
19 | digits: rules.isDigits,
20 | digits_between: rules.isDigitsBetween,
21 | email: rules.isEmail,
22 | hex: rules.isHex,
23 | in: rules.isIn,
24 | integer: rules.isInteger,
25 | max: rules.isMax,
26 | min: rules.isMin,
27 | not_in: rules.isNotIn,
28 | numeric: rules.isNumeric,
29 | required: rules.isRequired,
30 | size: rules.isSize,
31 | url: rules.isUrl,
32 | };
33 |
--------------------------------------------------------------------------------
/src/Factory.ts:
--------------------------------------------------------------------------------
1 | import { IRuleDefinition } from "./Interface";
2 | import { RuleType } from "./Types";
3 | import { DEFINED_RULES } from "./ruleManager";
4 |
5 | export const toRuleDefinition = (rule: string): IRuleDefinition => {
6 | const [name, paramsAsString] = rule.split(":");
7 | const ruleType = toRuleType(name);
8 |
9 | const params = paramsAsString ? paramsAsString.split(",") : [];
10 |
11 | const callback = DEFINED_RULES[ruleType];
12 |
13 | if (callback === undefined) {
14 | throw new Error(`Undefined validation rule: ${ruleType}`);
15 | }
16 |
17 | return {
18 | name: ruleType,
19 | callback,
20 | params,
21 | };
22 | };
23 |
24 | export const toRuleType = (name: string): RuleType => {
25 | try {
26 | return name as RuleType;
27 | } catch (error) {
28 | throw new Error(`Undefined rule: ${name}`);
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/Interface.ts:
--------------------------------------------------------------------------------
1 | import {
2 | RuleType,
3 | LanguageType,
4 | RuleFunction,
5 | ValidationResult,
6 | Translation,
7 | } from "./Types";
8 |
9 | export interface IRuleDefinition {
10 | name: RuleType;
11 | callback: RuleFunction;
12 | params: any[];
13 | }
14 |
15 | export interface IRuleResult {
16 | rule: RuleType;
17 | message: string;
18 | }
19 |
20 | export interface IValidationResult {
21 | isValid: boolean;
22 | isInvalid: boolean;
23 | fields: Record;
24 | errors: ValidationResult;
25 | }
26 |
27 | export interface IContext {
28 | data: any;
29 | field: string;
30 | definition: string;
31 | }
32 |
33 | export interface IOptions {
34 | stopOnFail: boolean;
35 | language: LanguageType;
36 | dateFormat: string;
37 | }
38 |
39 | export interface IValidationOptions extends IOptions {
40 | translations?: Record;
41 | }
42 |
43 | export interface ILocale {
44 | key: LanguageType;
45 | values: Translation;
46 | }
47 |
48 | export interface ITraversePair {
49 | path: string;
50 | value: any;
51 | }
52 |
53 | export interface ITraverseItem {
54 | path: string;
55 | rules: string | string[];
56 | resolved: Array;
57 | }
58 |
--------------------------------------------------------------------------------
/src/Locale.ts:
--------------------------------------------------------------------------------
1 | import { ILocale } from "./Interface";
2 | import { RuleType, LanguageType } from "./Types";
3 |
4 | const TRANSLATIONS: Partial>> = {};
5 |
6 | export const setLocales = (values: ILocale[] | ILocale) => {
7 | if (Array.isArray(values)) {
8 | const locales = values as ILocale[];
9 | for (const item of locales) {
10 | mergeTranslations(item);
11 | }
12 | } else {
13 | const locale = values as ILocale;
14 | mergeTranslations(locale);
15 | }
16 | };
17 |
18 | const mergeTranslations = (locale: ILocale) => {
19 | if (TRANSLATIONS[locale.key]) {
20 | TRANSLATIONS[locale.key] = {
21 | ...TRANSLATIONS[locale.key],
22 | ...locale.values,
23 | };
24 | } else {
25 | TRANSLATIONS[locale.key] = locale.values;
26 | }
27 | };
28 |
29 | export const getLoadedLocales = () => {
30 | return Object.keys(TRANSLATIONS);
31 | };
32 |
33 | export const addCustomLocale = (
34 | locale: LanguageType,
35 | ruleName: string,
36 | translation: string
37 | ) => {
38 | if (!TRANSLATIONS[locale]) {
39 | TRANSLATIONS[locale] = {};
40 | }
41 |
42 | const root = TRANSLATIONS[locale];
43 |
44 | if (root) {
45 | root[ruleName] = translation;
46 | } else {
47 | throw new Error(`The translation path couldn't find: ${locale}`);
48 | }
49 | };
50 |
51 | export const getMessage = (
52 | rule: RuleType,
53 | params: any[],
54 | language: LanguageType,
55 | customTranslations: Record = {}
56 | ) => {
57 | const defaultTranslations = TRANSLATIONS[language];
58 | if (defaultTranslations === undefined) {
59 | throw new Error(`You should set locale: setLocales(["${language}"])`);
60 | }
61 |
62 | const translations = { ...defaultTranslations, ...customTranslations };
63 | let message = translations[rule];
64 |
65 | if (message === undefined) {
66 | throw new Error(`Undefined error message: ${rule} (${language})`);
67 | }
68 |
69 | for (const index in params) {
70 | message = message.replace(`{${index}}`, params[index]);
71 | }
72 |
73 | return message;
74 | };
75 |
--------------------------------------------------------------------------------
/src/Options.ts:
--------------------------------------------------------------------------------
1 | import { IOptions } from "./Interface";
2 |
3 | const DEFAULT_OPTIONS: IOptions = {
4 | stopOnFail: false,
5 | language: "en",
6 | dateFormat: "yyyy-MM-dd",
7 | };
8 |
9 | let OPTIONS: IOptions = {
10 | ...DEFAULT_OPTIONS,
11 | };
12 |
13 | export const getOptions = () => {
14 | return OPTIONS;
15 | };
16 |
17 | export const setOptions = async (value: Partial) => {
18 | OPTIONS = {
19 | ...OPTIONS,
20 | ...value,
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/src/Types.ts:
--------------------------------------------------------------------------------
1 | import { IRuleResult } from "./Interface";
2 |
3 | export type RuleType =
4 | | "accepted"
5 | | "after"
6 | | "after_or_equal"
7 | | "alpha"
8 | | "alpha_dash"
9 | | "alpha_num"
10 | | "array"
11 | | "before"
12 | | "before_or_equal"
13 | | "between"
14 | | "boolean"
15 | | "confirmed"
16 | | "date"
17 | | "digits"
18 | | "digits_between"
19 | | "email"
20 | | "hex"
21 | | "in"
22 | | "integer"
23 | | "max"
24 | | "min"
25 | | "not_in"
26 | | "numeric"
27 | | "required"
28 | | "size"
29 | | "string"
30 | | "url";
31 |
32 | export type RuleFunction = (...args: any[]) => Promise | boolean;
33 |
34 | export type ValidationResult = Record;
35 |
36 | export type LanguageType =
37 | | "ar"
38 | | "az"
39 | | "be"
40 | | "bg"
41 | | "bs"
42 | | "ca"
43 | | "cs"
44 | | "cy"
45 | | "da"
46 | | "de"
47 | | "el"
48 | | "en"
49 | | "es"
50 | | "et"
51 | | "eu"
52 | | "fa"
53 | | "fi"
54 | | "fr"
55 | | "hr"
56 | | "hu"
57 | | "id"
58 | | "it"
59 | | "ja"
60 | | "ka"
61 | | "ko"
62 | | "li"
63 | | "lt"
64 | | "lv"
65 | | "mk"
66 | | "mn"
67 | | "ms"
68 | | "no"
69 | | "nl"
70 | | "pl"
71 | | "pt"
72 | | "ro"
73 | | "ru"
74 | | "se"
75 | | "sl"
76 | | "sq"
77 | | "sr"
78 | | "sv"
79 | | "tr"
80 | | "uk"
81 | | "vi"
82 | | "zh";
83 |
84 | export type Translation = Partial>;
85 |
86 | export type Definition = Record;
87 |
88 | export type InputResultType = "valid" | "invalid" | "fail";
89 |
--------------------------------------------------------------------------------
/src/converters/accepted.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be yes, on, 1 or true. This is useful for
3 | * validating "Terms of Service" acceptance.
4 | *
5 | * @example
6 | * import { accepted } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [accepted()]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#accepted
13 | */
14 | export default (): string => {
15 | return "accepted";
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/after.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be after the given date.
3 | *
4 | * @example
5 | * import { after } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [after("2023-01-01")]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#after-date
12 | */
13 | export default (date: string): string => {
14 | return `after:${date}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/afterOrEqual.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field unter validation must be after or equal to the given field
3 | *
4 | * @example
5 | * import { afterOrEqual } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [afterOrEqual("2023-01-01")]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#after-or-equal-date
12 | */
13 | export default (date: string): string => {
14 | return `after_or_equal:${date}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/alpha.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be entirely alphabetic characters.
3 | *
4 | * @example
5 | * import { alpha } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [alpha()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#alpha
12 | */
13 | export default (): string => {
14 | return "alpha";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/alphaDash.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation may have alpha-numeric characters, as well as
3 | * dashes and underscores.
4 | *
5 | * @example
6 | * import { alphaDash } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [alphaDash()]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#alpha-dash
13 | */
14 | export default (): string => {
15 | return "alpha_dash";
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/alphaNum.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be entirely alpha-numeric characters.
3 | *
4 | * @example
5 | * import { alphaNum } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [alphaNum()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#alpha-num
12 | */
13 | export default (): string => {
14 | return "alpha_num";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/array.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be an array.
3 | *
4 | * @example
5 | * import { array } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [array()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#array
12 | */
13 | export default (): string => {
14 | return "array";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/before.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be before the given date.
3 | *
4 | * @example
5 | * import { before } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [before("2023-01-01")]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#before-date
12 | */
13 | export default (date: string): string => {
14 | return `before:${date}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/beforeOrEqual.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be before or equal to the given date.
3 | *
4 | * @example
5 | * import { beforeOrEqual } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [beforeOrEqual("2023-01-01")]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#before-or-equal-date
12 | */
13 | export default (date: string): string => {
14 | return `before_or_equal:${date}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/between.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must have a size between the given min and max.
3 | * Strings, and numerics are evaluated in the same fashion as the size rule.
4 | *
5 | * @example
6 | * import { between } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [between(5, 10)]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#between-min-max
13 | */
14 | export default (min: number, max: number): string => {
15 | return `between:${min},${max}`;
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/boolean.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be a boolean value of the form true, false,
3 | * 0, 1, 'true', 'false', '0', '1',
4 | *
5 | * @example
6 | * import { boolean } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [boolean()]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#boolean
13 | */
14 | export default (): string => {
15 | return "boolean";
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/confirmed.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must have a matching field of foo_confirmed.
3 | * For example, if the field under validation is password, a matching
4 | * password_confirmed field must be present in the input.
5 | *
6 | * @example
7 | * import { confirmed } from "robust-validator"
8 | *
9 | * const definition = {
10 | * value: [confirmed()]
11 | * };
12 | * @type {string}
13 | * @tutorial https://validator.axe-api.com/rules.html#confirmed
14 | */
15 | export default (): string => {
16 | return "confirmed";
17 | };
18 |
--------------------------------------------------------------------------------
/src/converters/date.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be a valid date format.
3 | *
4 | * @example
5 | * import { date } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [date("yyyy-MM-dd")]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#date-format
12 | */
13 | export default (dateFormat?: string): string => {
14 | if (dateFormat) {
15 | return `date|${dateFormat}`;
16 | }
17 | return "date";
18 | };
19 |
--------------------------------------------------------------------------------
/src/converters/digits.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be numeric and must have an exact length of
3 | * value.
4 | *
5 | * @example
6 | * import { digits } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [digits(4)]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#digits-value
13 | */
14 | export default (length: number): string => {
15 | return `digits:${length}`;
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/digitsBetween.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be numeric and must have length between
3 | * given min and max.
4 | *
5 | * @example
6 | * import { digitsBetween } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [digitsBetween(4, 6)]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#digits-between-min-max
13 | */
14 | export default (min: number, max: number): string => {
15 | return `digits_between:${min},${max}`;
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/email.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be formatted as an e-mail address.
3 | *
4 | * @example
5 | * import { email } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [email()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#email
12 | */
13 | export default (): string => {
14 | return "email";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/hex.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation should be a hexadecimal format.
3 | *
4 | * @example
5 | * import { hex } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [hex()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#hex
12 | */
13 | export default (): string => {
14 | return "hex";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/in.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be included in the given list of values.
3 | * The field can be an array or string.
4 | *
5 | * @example
6 | * import { in } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [in(["apple", "orange"])]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#in-foo-bar
13 | */
14 | export default (items: string[]): string => {
15 | return `in:${items.join(",")}`;
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/index.ts:
--------------------------------------------------------------------------------
1 | import accepted from "./accepted";
2 | import after from "./after";
3 | import afterOrEqual from "./afterOrEqual";
4 | import alpha from "./alpha";
5 | import alphaDash from "./alphaDash";
6 | import alphaNum from "./alphaNum";
7 | import array from "./array";
8 | import before from "./before";
9 | import beforeOrEqual from "./beforeOrEqual";
10 | import between from "./between";
11 | import boolean from "./boolean";
12 | import confirmed from "./confirmed";
13 | import date from "./date";
14 | import digits from "./digits";
15 | import digitsBetween from "./digitsBetween";
16 | import email from "./email";
17 | import hex from "./hex";
18 | import inConverter from "./in";
19 | import integer from "./integer";
20 | import max from "./max";
21 | import min from "./min";
22 | import notIn from "./notIn";
23 | import numeric from "./numeric";
24 | import required from "./required";
25 | import size from "./size";
26 | import string from "./string";
27 | import url from "./url";
28 |
29 | export {
30 | accepted,
31 | after,
32 | afterOrEqual,
33 | alpha,
34 | alphaDash,
35 | alphaNum,
36 | array,
37 | before,
38 | beforeOrEqual,
39 | between,
40 | boolean,
41 | confirmed,
42 | date,
43 | digits,
44 | digitsBetween,
45 | email,
46 | hex,
47 | inConverter,
48 | integer,
49 | max,
50 | min,
51 | notIn,
52 | numeric,
53 | required,
54 | size,
55 | string,
56 | url,
57 | };
58 |
--------------------------------------------------------------------------------
/src/converters/integer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must have an integer value.
3 | *
4 | * @example
5 | * import { integer } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [integer()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#integer
12 | */
13 | export default (): string => {
14 | return "integer";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/max.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Validate that an attribute is no greater than a given size
3 | *
4 | * @example
5 | * import { max } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [max(20)]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#max-value
12 | */
13 | export default (value: number): string => {
14 | return `max:${value}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/min.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Validate that an attribute is at least a given size.
3 | *
4 | * @example
5 | * import { min } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [min(20)]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#min-value
12 | */
13 | export default (value: number): string => {
14 | return `min:${value}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/notIn.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must not be included in the given list of values.
3 | *
4 | * @example
5 | * import { notIn } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [notIn(["apple", "orange"])]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#not-in-foo-bar
12 | */
13 | export default (items: string[]): string => {
14 | return `not_in:${items.join(",")}`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/numeric.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Validate that an attribute is numeric. The string representation of a number
3 | * will pass.
4 | *
5 | * @example
6 | * import { numeric } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [numeric()]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#numeric
13 | */
14 | export default (): string => {
15 | return "numeric";
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/required.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The value should be defined. null, undefined or empty strings are not
3 | * acceptable.
4 | *
5 | * @example
6 | * import { required } from "robust-validator"
7 | *
8 | * const definition = {
9 | * value: [required()]
10 | * };
11 | * @type {string}
12 | * @tutorial https://validator.axe-api.com/rules.html#required
13 | */
14 | export default (): string => {
15 | return "required";
16 | };
17 |
--------------------------------------------------------------------------------
/src/converters/size.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must have a size matching the given value.
3 | * For string data, value corresponds to the number of characters. For numeric
4 | * data, value corresponds to a given integer value.
5 | *
6 | * @example
7 | * import { size } from "robust-validator"
8 | *
9 | * const definition = {
10 | * value: [size(10)]
11 | * };
12 | * @type {string}
13 | * @tutorial https://validator.axe-api.com/rules.html#size-value
14 | */
15 | export default (value: number): string => {
16 | return `size:${value}`;
17 | };
18 |
--------------------------------------------------------------------------------
/src/converters/string.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The field under validation must be a string.
3 | *
4 | * @example
5 | * import { string } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [string()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#string
12 | */
13 | export default (): string => {
14 | return "string";
15 | };
16 |
--------------------------------------------------------------------------------
/src/converters/url.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Validate that an attribute has a valid URL format
3 | *
4 | * @example
5 | * import { url } from "robust-validator"
6 | *
7 | * const definition = {
8 | * value: [url()]
9 | * };
10 | * @type {string}
11 | * @tutorial https://validator.axe-api.com/rules.html#url
12 | */
13 | export default (): string => {
14 | return "url";
15 | };
16 |
--------------------------------------------------------------------------------
/src/helpers/getValueViaPath.ts:
--------------------------------------------------------------------------------
1 | export const getValueViaPath = (data: any, path: string): any => {
2 | const keys = path.split(".");
3 |
4 | let value = keys.reduce((acc, key) => {
5 | if (acc && typeof acc === "object" && key in acc) {
6 | return acc[key];
7 | }
8 | return undefined;
9 | }, data);
10 |
11 | if (typeof value === "string") {
12 | value = value.trim();
13 | }
14 |
15 | if (value === "") {
16 | return null;
17 | }
18 |
19 | return value;
20 | };
21 |
--------------------------------------------------------------------------------
/src/helpers/validate.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IContext,
3 | ITraverseItem,
4 | IValidationOptions,
5 | IValidationResult,
6 | } from "../Interface";
7 | import { getMessage } from "../Locale";
8 | import { Definition, ValidationResult } from "../Types";
9 | import { toRuleDefinition } from "../Factory";
10 | import { getOptions } from "../Options";
11 |
12 | export const validate = async (
13 | data: any,
14 | definition: Definition,
15 | options?: Partial
16 | ): Promise => {
17 | const currentOptions: IValidationOptions = {
18 | ...getOptions(),
19 | ...options,
20 | };
21 |
22 | const { isValid, fields, results } = await getResults(
23 | data,
24 | definition,
25 | currentOptions
26 | );
27 |
28 | return {
29 | isValid,
30 | isInvalid: !isValid,
31 | fields,
32 | errors: results,
33 | };
34 | };
35 |
36 | const toTraverseArray = (data: any, definition: Definition) => {
37 | function resolvePath(data: any, path: string) {
38 | const parts = path.split(".");
39 | const result: Array<{ path: string; value: any }> = [];
40 |
41 | function traverse(
42 | current: any,
43 | index = 0,
44 | resolvedPath: Array = []
45 | ) {
46 | if (index >= parts.length) {
47 | result.push({ path: resolvedPath.join("."), value: current });
48 | return;
49 | }
50 |
51 | const part = parts[index];
52 |
53 | if (part === "*") {
54 | if (Array.isArray(current)) {
55 | current.forEach((item, i) => {
56 | traverse(item, index + 1, [...resolvedPath, i]);
57 | });
58 | } else if (current && typeof current === "object") {
59 | Object.keys(current).forEach((key) => {
60 | traverse(current[key], index + 1, [...resolvedPath, key]);
61 | });
62 | } else {
63 | result.push({
64 | path: [...resolvedPath, "*"].join("."),
65 | value: current,
66 | });
67 | }
68 | } else {
69 | if (current && typeof current === "object" && part in current) {
70 | traverse(current[part], index + 1, [...resolvedPath, part]);
71 | } else {
72 | result.push({
73 | path: [...resolvedPath, part].join("."),
74 | value: undefined,
75 | });
76 | }
77 | }
78 | }
79 |
80 | traverse(data);
81 | return result;
82 | }
83 |
84 | const checks: ITraverseItem[] = [];
85 |
86 | // Example usage
87 | Object.entries(definition).forEach(([path, rules]) => {
88 | const resolved = resolvePath(data, path);
89 | checks.push({ path, rules, resolved });
90 | });
91 |
92 | return checks;
93 | };
94 |
95 | const getResults = async (
96 | data: any,
97 | definition: Definition,
98 | options: IValidationOptions
99 | ) => {
100 | let isValid = true;
101 | const fields: Record = {};
102 | const results: ValidationResult = {};
103 |
104 | const traverse = toTraverseArray(data, definition);
105 |
106 | for (const item of traverse) {
107 | const { path, rules, resolved } = item;
108 | fields[path] = true;
109 |
110 | const rulesAsString = Array.isArray(rules) ? rules.join("|") : rules;
111 |
112 | const ruleDefinitions =
113 | toRuleNameArray(rulesAsString).map(toRuleDefinition);
114 |
115 | const context: IContext = {
116 | data,
117 | field: path,
118 | definition: rulesAsString,
119 | };
120 |
121 | for (const check of resolved) {
122 | // Checking all rules one by one
123 | for (const rule of ruleDefinitions) {
124 | // If the value is empty but the rule is not required, we don't execute
125 | // the rules
126 | if (
127 | rule.name !== "required" &&
128 | (check.value === null || check.value === undefined)
129 | ) {
130 | continue;
131 | }
132 | // Calling the rule function with the validation parameters
133 | const isRuleValid = await rule.callback(
134 | check.value,
135 | ...[...rule.params, context]
136 | );
137 | // Is the value valid?
138 | if (isRuleValid === false) {
139 | if (!results[check.path]) {
140 | results[check.path] = [];
141 | }
142 | isValid = false;
143 | fields[path] = false;
144 | // Setting the rule and the error message
145 | results[check.path].push({
146 | rule: rule.name,
147 | message: getMessage(
148 | rule.name,
149 | rule.params,
150 | options.language,
151 | options.translations || {}
152 | ),
153 | });
154 | if (options.stopOnFail) {
155 | return {
156 | isValid: false,
157 | fields,
158 | results,
159 | };
160 | }
161 | }
162 | }
163 | }
164 | }
165 |
166 | return {
167 | isValid,
168 | fields,
169 | results,
170 | };
171 | };
172 |
173 | const toRuleNameArray = (rules: string): string[] => {
174 | if (Array.isArray(rules)) {
175 | return rules;
176 | }
177 |
178 | return rules.split("|");
179 | };
180 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Options";
2 | export * from "./ruleManager";
3 | export * from "./helpers/validate";
4 | export * from "./Locale";
5 | export * from "./rules";
6 | export * from "./converters";
7 | export * from "./Constants";
8 | export * from "./Interface";
9 | export * from "./Types";
10 | export * from "./i18n";
11 |
--------------------------------------------------------------------------------
/src/json.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.json";
2 |
--------------------------------------------------------------------------------
/src/ruleManager.ts:
--------------------------------------------------------------------------------
1 | import { RULE_FUNCTION_MAPS } from "./Constants";
2 | import { addCustomLocale } from "./Locale";
3 | import { LanguageType, RuleFunction } from "./Types";
4 |
5 | export const DEFINED_RULES: Record = {
6 | ...RULE_FUNCTION_MAPS,
7 | };
8 |
9 | export const isRegistered = (name: string) => {
10 | return Object.keys(DEFINED_RULES).includes(name);
11 | };
12 |
13 | export const register = (
14 | name: string,
15 | ruleFunction: RuleFunction,
16 | translations: Partial>
17 | ) => {
18 | if (DEFINED_RULES[name]) {
19 | throw new Error(`The rule name is already defined: ${name}`);
20 | }
21 |
22 | for (const locale of Object.keys(translations)) {
23 | const message = translations[locale as LanguageType];
24 | if (message === undefined) {
25 | throw new Error(
26 | `The custom rule translation should be provided: ${locale}`
27 | );
28 | }
29 |
30 | addCustomLocale(locale as LanguageType, name, message);
31 | }
32 |
33 | DEFINED_RULES[name] = ruleFunction;
34 | };
35 |
--------------------------------------------------------------------------------
/src/rules/index.ts:
--------------------------------------------------------------------------------
1 | import isAccepted from "./isAccepted";
2 | import isAfter from "./isAfter";
3 | import isAfterOrEqual from "./isAfterOrEqual";
4 | import isAlpha from "./isAlpha";
5 | import isAlphaDash from "./isAlphaDash";
6 | import isAlphaNum from "./isAlphaNum";
7 | import isArray from "./isArray";
8 | import isBefore from "./isBefore";
9 | import isBeforeOrEqual from "./isBeforeOrEqual";
10 | import isBetween from "./isBetween";
11 | import isBoolean from "./isBoolean";
12 | import isConfirmed from "./isConfirmed";
13 | import isDate from "./isDate";
14 | import isDigits from "./isDigits";
15 | import isDigitsBetween from "./isDigitsBetween";
16 | import isEmail from "./isEmail";
17 | import isHex from "./isHex";
18 | import isIn from "./isIn";
19 | import isInteger from "./isInteger";
20 | import isMax from "./isMax";
21 | import isMin from "./isMin";
22 | import isNotIn from "./isNotIn";
23 | import isNumeric from "./isNumeric";
24 | import isRequired from "./isRequired";
25 | import isSize from "./isSize";
26 | import isString from "./isString";
27 | import isUrl from "./isUrl";
28 |
29 | export {
30 | isAccepted,
31 | isAfter,
32 | isAfterOrEqual,
33 | isAlpha,
34 | isAlphaDash,
35 | isAlphaNum,
36 | isArray,
37 | isBefore,
38 | isBeforeOrEqual,
39 | isBetween,
40 | isBoolean,
41 | isConfirmed,
42 | isDate,
43 | isDigits,
44 | isDigitsBetween,
45 | isEmail,
46 | isHex,
47 | isIn,
48 | isInteger,
49 | isMax,
50 | isMin,
51 | isNotIn,
52 | isNumeric,
53 | isRequired,
54 | isSize,
55 | isString,
56 | isUrl,
57 | };
58 |
--------------------------------------------------------------------------------
/src/rules/isAccepted.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | const acceptedValues = ["yes", "on", 1, "1", true];
3 | return acceptedValues.includes(value);
4 | };
5 |
--------------------------------------------------------------------------------
/src/rules/isAfter.ts:
--------------------------------------------------------------------------------
1 | import { IContext } from "../Interface";
2 |
3 | export default (value: any, date: any, extra?: IContext): boolean => {
4 | const inputValue = new Date(value);
5 | const comparisonDate = extra?.data[date]
6 | ? new Date(extra?.data[date])
7 | : new Date(date);
8 |
9 | if (isNaN(inputValue.getTime()) || isNaN(comparisonDate.getTime())) {
10 | return false;
11 | }
12 |
13 | return inputValue > comparisonDate;
14 | };
15 |
--------------------------------------------------------------------------------
/src/rules/isAfterOrEqual.ts:
--------------------------------------------------------------------------------
1 | import { IContext } from "../Interface";
2 |
3 | export default (value: any, date: any, extra?: IContext): boolean => {
4 | const inputValue = new Date(value);
5 | const comparisonDate = extra?.data[date]
6 | ? new Date(extra?.data[date])
7 | : new Date(date);
8 |
9 | if (isNaN(inputValue.getTime()) || isNaN(comparisonDate.getTime())) {
10 | return false;
11 | }
12 |
13 | return inputValue >= comparisonDate;
14 | };
15 |
--------------------------------------------------------------------------------
/src/rules/isAlpha.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | return /^[a-zA-Z]+$/.test(value);
3 | };
4 |
--------------------------------------------------------------------------------
/src/rules/isAlphaDash.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | // Check if the value contains only alphanumeric characters, dashes, and underscores
3 | const regex = /^[a-zA-Z0-9-_]+$/;
4 | return regex.test(value);
5 | };
6 |
--------------------------------------------------------------------------------
/src/rules/isAlphaNum.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | if (typeof value === "boolean") {
3 | return false;
4 | }
5 |
6 | // Check if the value consists entirely of alpha-numeric characters
7 | return /^[a-zA-Z0-9]+$/.test(value);
8 | };
9 |
--------------------------------------------------------------------------------
/src/rules/isArray.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | return Array.isArray(value);
3 | };
4 |
--------------------------------------------------------------------------------
/src/rules/isBefore.ts:
--------------------------------------------------------------------------------
1 | import { IContext } from "../Interface";
2 |
3 | export default (value: any, date: any, extra?: IContext): boolean => {
4 | // Parse the date strings or use Date objects based on your needs
5 | const inputValue = new Date(value);
6 | const comparisonDate = extra?.data[date]
7 | ? new Date(extra?.data[date])
8 | : new Date(date);
9 |
10 | if (isNaN(inputValue.getTime()) || isNaN(comparisonDate.getTime())) {
11 | return false;
12 | }
13 |
14 | // Compare the dates
15 | return inputValue < comparisonDate;
16 | };
17 |
--------------------------------------------------------------------------------
/src/rules/isBeforeOrEqual.ts:
--------------------------------------------------------------------------------
1 | import { IContext } from "../Interface";
2 |
3 | export default (value: any, date: any, extra?: IContext): boolean => {
4 | // Parse the date strings or use Date objects based on your needs
5 | const inputValue = new Date(value);
6 | const comparisonDate = extra?.data[date]
7 | ? new Date(extra?.data[date])
8 | : new Date(date);
9 |
10 | if (isNaN(inputValue.getTime()) || isNaN(comparisonDate.getTime())) {
11 | return false;
12 | }
13 |
14 | // Compare the dates
15 | return inputValue <= comparisonDate;
16 | };
17 |
--------------------------------------------------------------------------------
/src/rules/isBetween.ts:
--------------------------------------------------------------------------------
1 | import { IContext } from "../Interface";
2 |
3 | export default (value: any, min: any, max: any, extra?: IContext): boolean => {
4 | // Convert min and max to numbers if they are not already
5 | const minValue = typeof min === "number" ? min : parseFloat(min);
6 | const maxValue = typeof max === "number" ? max : parseFloat(max);
7 |
8 | // If there is a numeric definition on the field, we should test the numeric
9 | // values of the data.
10 | const shouldBeNumeric =
11 | extra?.definition.split("|").includes("numeric") || false;
12 | if (shouldBeNumeric) {
13 | value = parseFloat(value);
14 | }
15 |
16 | // Check the size based on the type of value
17 | if (Array.isArray(value)) {
18 | return value.length >= minValue && value.length <= maxValue;
19 | } else if (typeof value === "string" && shouldBeNumeric === false) {
20 | return value.length >= minValue && value.length <= maxValue;
21 | } else if (typeof value === "number") {
22 | return value >= minValue && value <= maxValue;
23 | }
24 |
25 | // For other types, return false
26 | return false;
27 | };
28 |
--------------------------------------------------------------------------------
/src/rules/isBoolean.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | // Check if the value is a boolean or a valid string representation of boolean
3 | if (typeof value === "boolean") {
4 | return true;
5 | }
6 |
7 | const lowerCaseValue = String(value).toLowerCase();
8 |
9 | // Check for valid boolean string representations
10 | return ["true", "false", "0", "1"].includes(lowerCaseValue);
11 | };
12 |
--------------------------------------------------------------------------------
/src/rules/isConfirmed.ts:
--------------------------------------------------------------------------------
1 | import { getValueViaPath } from "../helpers/getValueViaPath";
2 | import { IContext } from "../Interface";
3 |
4 | export default (value: any, context: IContext): boolean => {
5 | const confirmedKey = `${context.field}_confirmed`;
6 | const confirmedValue = getValueViaPath(context.data, confirmedKey);
7 | return value === confirmedValue;
8 | };
9 |
--------------------------------------------------------------------------------
/src/rules/isDate.ts:
--------------------------------------------------------------------------------
1 | import { parse } from "date-fns";
2 | import { getOptions } from "../Options";
3 |
4 | export default (value: any, dateFormat?: string): boolean => {
5 | const options = getOptions();
6 | const format = dateFormat ?? options.dateFormat;
7 | const date: any = parse(value, format, new Date());
8 | return !isNaN(date);
9 | };
10 |
--------------------------------------------------------------------------------
/src/rules/isDigits.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, length: any): boolean => {
2 | if (typeof value === "boolean") {
3 | return false;
4 | }
5 |
6 | // Check if the value is numeric
7 | if (typeof value !== "number" && isNaN(Number(value))) {
8 | return false;
9 | }
10 |
11 | // Check if the length is numeric
12 | if (typeof length !== "number" && isNaN(Number(length))) {
13 | throw new Error(`Incorrect validation rule: digits:number`);
14 | }
15 |
16 | // Shouldn't be a float value
17 | if (Number(value) % 1 !== 0) {
18 | return false;
19 | }
20 |
21 | // Check if the length is exact
22 | return String(value).trim().length === Number(length);
23 | };
24 |
--------------------------------------------------------------------------------
/src/rules/isDigitsBetween.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, min: any, max: any): boolean => {
2 | if (value === null || value === undefined || typeof value === "boolean") {
3 | return false;
4 | }
5 |
6 | // Check if the value is numeric
7 | if (typeof value !== "number" && isNaN(Number(value))) {
8 | return false;
9 | }
10 |
11 | if (min === null || min === undefined || max === null || max === undefined) {
12 | throw new Error(`Incorrect validation rule: digits:min,max`);
13 | }
14 |
15 | if (typeof min !== "number" && isNaN(Number(min))) {
16 | // Check if the min is numeric
17 | throw new Error(`Incorrect validation rule: digits:min,max`);
18 | }
19 |
20 | // Check if the max is numeric
21 | if (typeof max !== "number" && isNaN(Number(max))) {
22 | throw new Error(`Incorrect validation rule: digits:min,max`);
23 | }
24 |
25 | // Shouldn't be a float value
26 | if (Number(value) % 1 !== 0) {
27 | return false;
28 | }
29 |
30 | // Check if the length is exact
31 | const length = String(value).trim().length;
32 | return length >= Number(min) && length <= Number(max);
33 | };
34 |
--------------------------------------------------------------------------------
/src/rules/isEmail.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | return !!String(value)
3 | .toLowerCase()
4 | .match(
5 | /^(([^<>()[\]\\.,;:\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,}))$/
6 | );
7 | };
8 |
--------------------------------------------------------------------------------
/src/rules/isHex.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | return /^[0-9a-f]+$/i.test(String(value).trim());
3 | };
4 |
--------------------------------------------------------------------------------
/src/rules/isIn.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, ...args: any[]): boolean => {
2 | // Let's check the last item is IContext
3 | const lastItem: any = args.at(-1);
4 | if (lastItem.definition && lastItem.field) {
5 | args.pop();
6 | }
7 |
8 | const [options] = args;
9 |
10 | const list: any[] = Array.isArray(options)
11 | ? options
12 | : (options as string).split(",");
13 | const listAsString = list.map((item) => String(item).trim());
14 |
15 | if (Array.isArray(value)) {
16 | // Check if all elements in the array are in the list
17 | return value.every((item: any) =>
18 | listAsString.includes(String(item).trim())
19 | );
20 | }
21 |
22 | // Check if the string value is in the list
23 | return listAsString.includes(String(value).trim());
24 | };
25 |
--------------------------------------------------------------------------------
/src/rules/isInteger.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | if (isNaN(value)) {
3 | return false;
4 | }
5 | return String(parseInt(value, 10)) === String(value);
6 | };
7 |
--------------------------------------------------------------------------------
/src/rules/isMax.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, size: any): boolean => {
2 | if (value === null || value === undefined || typeof value === "boolean") {
3 | return false;
4 | }
5 |
6 | const check = Number(size);
7 |
8 | if (size === null || size === undefined || isNaN(check)) {
9 | throw new Error(`Incorrect validation rule: max:number`);
10 | }
11 |
12 | if (typeof value === "string") {
13 | return value.trim().length <= check;
14 | }
15 |
16 | const input = Number(value);
17 |
18 | if (isNaN(input)) {
19 | return false;
20 | }
21 |
22 | return input <= check;
23 | };
24 |
--------------------------------------------------------------------------------
/src/rules/isMin.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, size: any): boolean => {
2 | if (value === null || value === undefined || typeof value === "boolean") {
3 | return false;
4 | }
5 |
6 | const check = Number(size);
7 |
8 | if (size === null || size === undefined || isNaN(check)) {
9 | throw new Error(`Incorrect validation rule: max:number`);
10 | }
11 |
12 | if (typeof value === "string") {
13 | return value.trim().length >= check;
14 | }
15 |
16 | const input = Number(value);
17 | if (isNaN(input)) {
18 | return false;
19 | }
20 |
21 | return input >= size;
22 | };
23 |
--------------------------------------------------------------------------------
/src/rules/isNotIn.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, options: any[] | string): boolean => {
2 | if (value === null || value === undefined) {
3 | return false;
4 | }
5 |
6 | const list: any[] = Array.isArray(options) ? options : options.split(",");
7 | const listAsString = list.map((item) => String(item).trim());
8 |
9 | if (Array.isArray(value)) {
10 | // Check if all elements in the array are in the list
11 | return value.every(
12 | (item: any) => !listAsString.includes(String(item).trim())
13 | );
14 | }
15 |
16 | // Check if the string value is in the list
17 | return !listAsString.includes(String(value).trim());
18 | };
19 |
--------------------------------------------------------------------------------
/src/rules/isNumeric.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | if (typeof value === "object") {
3 | return false;
4 | }
5 |
6 | if (String(value).trim().length === 0) {
7 | return true;
8 | }
9 |
10 | // Check if the string representation is numeric
11 | const stringValue = String(value).trim();
12 | return stringValue !== "" && !isNaN(Number(stringValue));
13 | };
14 |
--------------------------------------------------------------------------------
/src/rules/isRequired.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | if (value === null || value === undefined) {
3 | return false;
4 | }
5 |
6 | if (Array.isArray(value)) {
7 | return true;
8 | }
9 |
10 | const content = String(value).trim();
11 | return content.length > 0;
12 | };
13 |
--------------------------------------------------------------------------------
/src/rules/isSize.ts:
--------------------------------------------------------------------------------
1 | export default (value: any, size: any): boolean => {
2 | if (value === null || value === undefined) {
3 | return false;
4 | }
5 |
6 | if (isNaN(Number(size))) {
7 | throw new Error(`Invalid validation rule: size:${size} (size:number)`);
8 | }
9 |
10 | if (typeof value === "string") {
11 | return String(value).trim().length === Number(size);
12 | }
13 |
14 | return value === Number(size);
15 | };
16 |
--------------------------------------------------------------------------------
/src/rules/isString.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | return typeof value === "string";
3 | };
4 |
--------------------------------------------------------------------------------
/src/rules/isUrl.ts:
--------------------------------------------------------------------------------
1 | export default (value: any): boolean => {
2 | const urlRegex = /^(ftp|http|https):\/\/[^ "]+$/;
3 | return urlRegex.test(value);
4 | };
5 |
--------------------------------------------------------------------------------
/tests/Locale.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, test, expect } from "vitest";
2 | import { LanguageType, setLocales, ILocale } from "../index";
3 |
4 | const LANGUAGES: Record = {
5 | ar: "ar",
6 | az: "az",
7 | be: "be",
8 | bg: "bg",
9 | bs: "bs",
10 | ca: "ca",
11 | cs: "cs",
12 | cy: "cy",
13 | da: "da",
14 | de: "de",
15 | el: "el",
16 | en: "en",
17 | es: "es",
18 | et: "et",
19 | eu: "eu",
20 | fa: "fa",
21 | fi: "fi",
22 | fr: "fr",
23 | hr: "hr",
24 | hu: "hu",
25 | id: "id",
26 | it: "it",
27 | ja: "ja",
28 | ka: "ka",
29 | ko: "ko",
30 | li: "li",
31 | lt: "lt",
32 | lv: "lv",
33 | mk: "mk",
34 | mn: "mn",
35 | ms: "ms",
36 | no: "no",
37 | nl: "nl",
38 | pl: "pl",
39 | pt: "pt",
40 | ro: "ro",
41 | ru: "ru",
42 | se: "se",
43 | sl: "sl",
44 | sq: "sq",
45 | sr: "sr",
46 | sv: "sv",
47 | tr: "tr",
48 | uk: "uk",
49 | vi: "vi",
50 | zh: "zh",
51 | };
52 |
53 | describe("Locale helpers", () => {
54 | test("setLocales() should be able to load all supported languages", async () => {
55 | expect(true).toBe(true);
56 | for (const language of Object.keys(LANGUAGES)) {
57 | const content = await import(`../src/i18n`);
58 | const locale = content[language] as ILocale;
59 | expect(locale.key).toBe(language);
60 | await setLocales(locale);
61 | }
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/tests/consumers/cjs/index.js:
--------------------------------------------------------------------------------
1 | const {
2 | validate,
3 | setLocales,
4 | isEmail,
5 | setOptions,
6 | az,
7 | } = require("robust-validator");
8 |
9 | const data = {
10 | email: null,
11 | };
12 |
13 | const rules = {
14 | email: "required|email|min:1|max:50",
15 | };
16 |
17 | const main = async () => {
18 | setLocales(az);
19 | setOptions({ language: "az" });
20 | const result = await validate(data, rules);
21 | if (result.isValid) {
22 | throw new Error("The email should be invalid!");
23 | }
24 |
25 | const { message } = result.errors.email[0];
26 | if (message !== "Sahə tələb olunur.") {
27 | throw new Error(`Unaccepted message: ${message}`);
28 | }
29 |
30 | console.log("CJS module tests are succeed!");
31 | console.log("isEmail", isEmail);
32 | };
33 |
34 | main();
35 |
--------------------------------------------------------------------------------
/tests/consumers/cjs/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tester",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "tester",
9 | "version": "1.0.0",
10 | "dependencies": {
11 | "robust-validator": "file:../../../"
12 | }
13 | },
14 | "../../..": {
15 | "name": "robust-validator",
16 | "version": "2.0.0-rc-2",
17 | "license": "MIT",
18 | "dependencies": {
19 | "date-fns": "^4.1.0"
20 | },
21 | "devDependencies": {
22 | "@babel/preset-env": "^7.23.6",
23 | "@babel/preset-typescript": "^7.23.3",
24 | "@jest/globals": "^29.7.0",
25 | "@rollup/plugin-typescript": "^12.1.0",
26 | "@types/jest": "^29.5.11",
27 | "@typescript-eslint/eslint-plugin": "^6.15.0",
28 | "@typescript-eslint/parser": "^6.15.0",
29 | "@vitest/coverage-v8": "^1.1.0",
30 | "eslint": "^8.56.0",
31 | "eslint-config-standard-with-typescript": "^43.0.0",
32 | "eslint-plugin-import": "^2.29.1",
33 | "eslint-plugin-n": "^16.4.0",
34 | "eslint-plugin-promise": "^6.1.1",
35 | "husky": "^9.0.10",
36 | "jest": "^29.7.0",
37 | "rollup": "^4.9.1",
38 | "rollup-plugin-analyzer": "^4.0.0",
39 | "rollup-plugin-auto-external": "^2.0.0",
40 | "rollup-plugin-copy": "^3.5.0",
41 | "rollup-plugin-filesize": "^10.0.0",
42 | "rollup-plugin-peer-deps-external": "^2.2.4",
43 | "rollup-plugin-typescript2": "^0.36.0",
44 | "terser": "^5.27.1",
45 | "ts-jest": "^29.1.1",
46 | "ts-node": "^10.9.2",
47 | "ts-node-dev": "^2.0.0",
48 | "typescript": "^5.3.3",
49 | "vitest": "^1.1.0"
50 | },
51 | "engines": {
52 | "node": ">=18.0.0"
53 | }
54 | },
55 | "node_modules/robust-validator": {
56 | "resolved": "../../..",
57 | "link": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/consumers/cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tester",
3 | "version": "1.0.0",
4 | "main": "common.js",
5 | "type": "commonjs",
6 | "scripts": {
7 | "test": "rm -rf node_modules && rm -rf package-lock.json && npm install && node index.js"
8 | },
9 | "dependencies": {
10 | "robust-validator": "file:../../../"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/consumers/cjs/test.sh:
--------------------------------------------------------------------------------
1 | rm -rf node_modules
2 | rm -rf package-lock.json
3 |
4 | npm unlink robust-validator
5 | npm link robust-validator
6 |
7 | # npm install
8 | pwd
9 | node index.js
10 |
11 | npm unlink robust-validator
--------------------------------------------------------------------------------
/tests/consumers/esm/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | validate,
3 | setLocales,
4 | setOptions,
5 | isEmail,
6 | az,
7 | } from "robust-validator";
8 |
9 | const data = {
10 | email: null,
11 | };
12 |
13 | const rules = {
14 | email: "required|email|min:1|max:50",
15 | };
16 |
17 | const main = async () => {
18 | setLocales(az);
19 | setOptions({ language: "az" });
20 | const result = await validate(data, rules);
21 | if (result.isValid) {
22 | throw new Error("The email should be invalid!");
23 | }
24 |
25 | const { message } = result.errors.email[0];
26 | if (message !== "Sahə tələb olunur.") {
27 | throw new Error(`Unaccepted message: ${message}`);
28 | }
29 |
30 | console.log("ESM module tests are succeed!");
31 | console.log("isEmail", isEmail);
32 | };
33 |
34 | main();
35 |
--------------------------------------------------------------------------------
/tests/consumers/esm/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tester",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "tester",
9 | "version": "1.0.0",
10 | "dependencies": {
11 | "robust-validator": "file:../../../"
12 | }
13 | },
14 | "../../..": {
15 | "name": "robust-validator",
16 | "version": "2.0.0-rc-2",
17 | "license": "MIT",
18 | "dependencies": {
19 | "date-fns": "^4.1.0"
20 | },
21 | "devDependencies": {
22 | "@babel/preset-env": "^7.23.6",
23 | "@babel/preset-typescript": "^7.23.3",
24 | "@jest/globals": "^29.7.0",
25 | "@rollup/plugin-typescript": "^12.1.0",
26 | "@types/jest": "^29.5.11",
27 | "@typescript-eslint/eslint-plugin": "^6.15.0",
28 | "@typescript-eslint/parser": "^6.15.0",
29 | "@vitest/coverage-v8": "^1.1.0",
30 | "eslint": "^8.56.0",
31 | "eslint-config-standard-with-typescript": "^43.0.0",
32 | "eslint-plugin-import": "^2.29.1",
33 | "eslint-plugin-n": "^16.4.0",
34 | "eslint-plugin-promise": "^6.1.1",
35 | "husky": "^9.0.10",
36 | "jest": "^29.7.0",
37 | "rollup": "^4.9.1",
38 | "rollup-plugin-analyzer": "^4.0.0",
39 | "rollup-plugin-auto-external": "^2.0.0",
40 | "rollup-plugin-copy": "^3.5.0",
41 | "rollup-plugin-filesize": "^10.0.0",
42 | "rollup-plugin-peer-deps-external": "^2.2.4",
43 | "rollup-plugin-typescript2": "^0.36.0",
44 | "terser": "^5.27.1",
45 | "ts-jest": "^29.1.1",
46 | "ts-node": "^10.9.2",
47 | "ts-node-dev": "^2.0.0",
48 | "typescript": "^5.3.3",
49 | "vitest": "^1.1.0"
50 | },
51 | "engines": {
52 | "node": ">=18.0.0"
53 | }
54 | },
55 | "node_modules/robust-validator": {
56 | "resolved": "../../..",
57 | "link": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/consumers/esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tester",
3 | "version": "1.0.0",
4 | "main": "common.js",
5 | "type": "module",
6 | "scripts": {
7 | "test": "rm -rf node_modules && rm -rf package-lock.json && npm install && node index.js"
8 | },
9 | "dependencies": {
10 | "robust-validator": "file:../../../"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/consumers/ts-local/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | validate,
3 | setLocales,
4 | setOptions,
5 | isEmail,
6 | az,
7 | } from "robust-validator";
8 |
9 | const data = {
10 | email: null,
11 | };
12 |
13 | const rules = {
14 | email: "required|email|min:1|max:50",
15 | };
16 |
17 | const main = async () => {
18 | setLocales(az);
19 | setOptions({
20 | language: "az",
21 | });
22 | const result = await validate(data, rules);
23 | if (result.isValid) {
24 | throw new Error("The email should be invalid!");
25 | }
26 |
27 | const { message } = result.errors.email[0];
28 | if (message !== "Sahə tələb olunur.") {
29 | throw new Error(`Unaccepted message: ${message}`);
30 | }
31 |
32 | console.log("ESM module tests are succeed!");
33 | console.log("isEmail", isEmail);
34 | };
35 |
36 | main();
37 |
--------------------------------------------------------------------------------
/tests/consumers/ts-local/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tester",
3 | "version": "1.0.0",
4 | "main": "index.ts",
5 | "scripts": {
6 | "test": "rm -rf node_modules && rm -rf package-lock.json && npm install && ts-node index.ts"
7 | },
8 | "dependencies": {
9 | "robust-validator": "file:../../../"
10 | },
11 | "devDependencies": {
12 | "ts-node": "^10.9.2",
13 | "typescript": "^5.3.3"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/consumers/ts-local/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "CommonJS",
5 | "esModuleInterop": true,
6 | "skipLibCheck": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "outDir": "./dist",
9 | "allowSyntheticDefaultImports": true,
10 | "declaration": true,
11 | "resolveJsonModule": true
12 | },
13 | "include": ["index.ts"],
14 | "exclude": ["node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/tests/converters/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, afterAll } from "vitest";
2 | import {
3 | RULE_FUNCTION_MAPS,
4 | accepted,
5 | after,
6 | afterOrEqual,
7 | alpha,
8 | alphaDash,
9 | alphaNum,
10 | array,
11 | before,
12 | beforeOrEqual,
13 | between,
14 | boolean,
15 | confirmed,
16 | date,
17 | digits,
18 | digitsBetween,
19 | email,
20 | hex,
21 | inConverter,
22 | integer,
23 | max,
24 | min,
25 | notIn,
26 | numeric,
27 | required,
28 | size,
29 | string,
30 | url,
31 | } from "../../index";
32 |
33 | const RESULT_LIST: string[] = [];
34 |
35 | describe("converters functions", () => {
36 | afterAll(() => {
37 | const ruleNames = Object.keys(RULE_FUNCTION_MAPS);
38 | const results = RESULT_LIST.map((item) => {
39 | const [name] = item.split(":");
40 | return name;
41 | });
42 |
43 | for (const rule of ruleNames) {
44 | expect(results.includes(rule), rule).toBe(true);
45 | }
46 | });
47 |
48 | it("accepted() should be able to convert to the correct pattern.", () => {
49 | const result = accepted();
50 | RESULT_LIST.push(result);
51 | expect(result).toBe("accepted");
52 | });
53 |
54 | it("after() should be able to convert to the correct pattern.", () => {
55 | const result = after("2023-10-21");
56 | RESULT_LIST.push(result);
57 | expect(result).toBe("after:2023-10-21");
58 | });
59 |
60 | it("afterOrEqual() should be able to convert to the correct pattern.", () => {
61 | const result = afterOrEqual("2023-10-21");
62 | RESULT_LIST.push(result);
63 | expect(result).toBe("after_or_equal:2023-10-21");
64 | });
65 |
66 | it("alpha() should be able to convert to the correct pattern.", () => {
67 | const result = alpha();
68 | RESULT_LIST.push(result);
69 | expect(result).toBe("alpha");
70 | });
71 |
72 | it("alphaDash() should be able to convert to the correct pattern.", () => {
73 | const result = alphaDash();
74 | RESULT_LIST.push(result);
75 | expect(result).toBe("alpha_dash");
76 | });
77 |
78 | it("alphaNum() should be able to convert to the correct pattern.", () => {
79 | const result = alphaNum();
80 | RESULT_LIST.push(result);
81 | expect(result).toBe("alpha_num");
82 | });
83 |
84 | it("array() should be able to convert to the correct pattern.", () => {
85 | const result = array();
86 | RESULT_LIST.push(result);
87 | expect(result).toBe("array");
88 | });
89 |
90 | it("before() should be able to convert to the correct pattern.", () => {
91 | const result = before("2023-10-21");
92 | RESULT_LIST.push(result);
93 | expect(result).toBe("before:2023-10-21");
94 | });
95 |
96 | it("beforeOrEqual() should be able to convert to the correct pattern.", () => {
97 | const result = beforeOrEqual("2023-10-21");
98 | RESULT_LIST.push(result);
99 | expect(result).toBe("before_or_equal:2023-10-21");
100 | });
101 |
102 | it("between() should be able to convert to the correct pattern.", () => {
103 | const result = between(5, 10);
104 | RESULT_LIST.push(result);
105 | expect(result).toBe("between:5,10");
106 | });
107 |
108 | it("boolean() should be able to convert to the correct pattern.", () => {
109 | const result = boolean();
110 | RESULT_LIST.push(result);
111 | expect(result).toBe("boolean");
112 | });
113 |
114 | it("confirmed() should be able to convert to the correct pattern.", () => {
115 | const result = confirmed();
116 | RESULT_LIST.push(result);
117 | expect(result).toBe("confirmed");
118 | });
119 |
120 | it("date() should be able to convert to the correct pattern.", () => {
121 | const result = date();
122 | RESULT_LIST.push(result);
123 | expect(result).toBe("date");
124 | });
125 |
126 | it("digits() should be able to convert to the correct pattern.", () => {
127 | const result = digits(4);
128 | RESULT_LIST.push(result);
129 | expect(result).toBe("digits:4");
130 | });
131 |
132 | it("digitsBetween() should be able to convert to the correct pattern.", () => {
133 | const result = digitsBetween(4, 6);
134 | RESULT_LIST.push(result);
135 | expect(result).toBe("digits_between:4,6");
136 | });
137 |
138 | it("email() should be able to convert to the correct pattern.", () => {
139 | const result = email();
140 | RESULT_LIST.push(result);
141 | expect(result).toBe("email");
142 | });
143 |
144 | it("hex() should be able to convert to the correct pattern.", () => {
145 | const result = hex();
146 | RESULT_LIST.push(result);
147 | expect(result).toBe("hex");
148 | });
149 |
150 | it("inConverter() should be able to convert to the correct pattern.", () => {
151 | const result = inConverter(["apple", "orange"]);
152 | RESULT_LIST.push(result);
153 | expect(result).toBe("in:apple,orange");
154 | });
155 |
156 | it("integer() should be able to convert to the correct pattern.", () => {
157 | const result = integer();
158 | RESULT_LIST.push(result);
159 | expect(result).toBe("integer");
160 | });
161 |
162 | it("max() should be able to convert to the correct pattern.", () => {
163 | const result = max(5);
164 | RESULT_LIST.push(result);
165 | expect(result).toBe("max:5");
166 | });
167 |
168 | it("min() should be able to convert to the correct pattern.", () => {
169 | const result = min(5);
170 | RESULT_LIST.push(result);
171 | expect(result).toBe("min:5");
172 | });
173 |
174 | it("notIn() should be able to convert to the correct pattern.", () => {
175 | const result = notIn(["apple", "orange"]);
176 | RESULT_LIST.push(result);
177 | expect(result).toBe("not_in:apple,orange");
178 | });
179 |
180 | it("numeric() should be able to convert to the correct pattern.", () => {
181 | const result = numeric();
182 | RESULT_LIST.push(result);
183 | expect(result).toBe("numeric");
184 | });
185 |
186 | it("required() should be able to convert to the correct pattern.", () => {
187 | const result = required();
188 | RESULT_LIST.push(result);
189 | expect(result).toBe("required");
190 | });
191 |
192 | it("size() should be able to convert to the correct pattern.", () => {
193 | const result = size(5);
194 | RESULT_LIST.push(result);
195 | expect(result).toBe("size:5");
196 | });
197 |
198 | it("string() should be able to convert to the correct pattern.", () => {
199 | const result = string();
200 | RESULT_LIST.push(result);
201 | expect(result).toBe("string");
202 | });
203 |
204 | it("url() should be able to convert to the correct pattern.", () => {
205 | const result = url();
206 | RESULT_LIST.push(result);
207 | expect(result).toBe("url");
208 | });
209 | });
210 |
--------------------------------------------------------------------------------
/tests/helpers.ts:
--------------------------------------------------------------------------------
1 | import { IContext } from "../src/Interface";
2 |
3 | export const context: IContext = {
4 | data: {},
5 | key: "",
6 | };
7 |
--------------------------------------------------------------------------------
/tests/helpers/getValueViaPath.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { getValueViaPath } from "../../src/helpers/getValueViaPath";
3 |
4 | describe("getValueViaPath() function", () => {
5 | const data = {
6 | user: {
7 | role: {
8 | title: "Title",
9 | },
10 | },
11 | numbers: [1, 2, 3],
12 | emptyObject: {},
13 | nullValue: null,
14 | };
15 |
16 | it("should return the correct value for a valid path", () => {
17 | expect(getValueViaPath(data, "user.role.title")).toBe("Title");
18 | expect(getValueViaPath(data, "numbers.1")).toBe(2);
19 | });
20 |
21 | it("should return undefined for an invalid path", () => {
22 | expect(getValueViaPath(data, "user.role.invalidKey")).toBeUndefined();
23 | expect(getValueViaPath(data, "nonexistent.path")).toBeUndefined();
24 | });
25 |
26 | it("should return undefined for invalid data or null value", () => {
27 | expect(getValueViaPath(undefined, "user.role.title")).toBeUndefined();
28 | expect(getValueViaPath(null, "user.role.title")).toBeUndefined();
29 | });
30 |
31 | it("should return undefined for null or undefined in nested data", () => {
32 | expect(getValueViaPath(data, "nullValue")).toBeNull();
33 | expect(getValueViaPath(data, "emptyObject.someKey")).toBeUndefined();
34 | });
35 |
36 | it("should handle arrays in the path correctly", () => {
37 | expect(getValueViaPath(data, "numbers.0")).toBe(1);
38 | expect(getValueViaPath(data, "numbers.3")).toBeUndefined();
39 | });
40 |
41 | it("should return undefined for non-object values in the path", () => {
42 | expect(getValueViaPath(data, "user.role.title.someKey")).toBeUndefined();
43 | expect(getValueViaPath(data, "numbers.0.someKey")).toBeUndefined();
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/tests/index.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, test } from "vitest";
2 | import {
3 | setOptions,
4 | validate,
5 | setLocales,
6 | ILocale,
7 | required,
8 | email,
9 | register,
10 | en,
11 | tr,
12 | az,
13 | } from "../index";
14 |
15 | const EXISTS_RULE_TRANSLATIONS = {
16 | en: "The record doesn't exists on database: {0}",
17 | tr: "Kayıt veritabanında bulunamadı: {0}",
18 | };
19 |
20 | describe("validate() function ", () => {
21 | beforeAll(async () => {
22 | setLocales(en as ILocale);
23 | });
24 |
25 | test("should be able to validate the general structure", async () => {
26 | const data = {
27 | email: "foo@bar.com",
28 | };
29 | const rules = {
30 | email: "required|email|min:20",
31 | };
32 |
33 | const result = await validate(data, rules);
34 | expect(result.isValid).toBe(false);
35 | expect(result.isInvalid).toBe(true);
36 | expect(result.errors.email.length).toBe(1);
37 | expect(result.errors.email[0].rule).toBe("min");
38 | expect(result.errors.email[0].message).toBe(
39 | "The field must be at least 20."
40 | );
41 | });
42 |
43 | test("should be able to validate the nested values", async () => {
44 | const data = {
45 | user: {
46 | email: "foo@bar.com",
47 | },
48 | };
49 | const rules = {
50 | "user.email": "required",
51 | };
52 |
53 | const result = await validate(data, rules);
54 | expect(result.isValid).toBe(true);
55 | });
56 |
57 | test("should be able parse the parameters correctly", async () => {
58 | const data = {
59 | price: 999,
60 | };
61 | const rules = {
62 | price: "required|between:1000,2000",
63 | };
64 |
65 | const result = await validate(data, rules);
66 | expect(result.isValid).toBe(false);
67 | expect(result.errors.price[0].message).toBe(
68 | "The field must be between 1000 and 2000."
69 | );
70 | });
71 |
72 | test("should stop on fail by options", async () => {
73 | const rules = {
74 | email: "min:100|email",
75 | };
76 |
77 | const result = await validate({ email: "xxx" }, rules, {
78 | stopOnFail: true,
79 | });
80 | expect(result.errors.email.length).toBe(1);
81 | });
82 |
83 | test("should not stop on fail by default", async () => {
84 | const rules = {
85 | email: "min:100|email",
86 | };
87 |
88 | const result = await validate({ email: "xxx" }, rules);
89 | expect(result.errors.email.length).toBe(2);
90 | });
91 |
92 | test("should be able to use custom translations", async () => {
93 | const rules = {
94 | email: "required",
95 | };
96 |
97 | const result = await validate({ email: "" }, rules, {
98 | translations: {
99 | required: "The field is required. (custom translation message)",
100 | },
101 | });
102 | expect(result.errors.email[0].message).toBe(
103 | "The field is required. (custom translation message)"
104 | );
105 | });
106 |
107 | test("should be able set the default options", async () => {
108 | // Setting the general default options
109 | await setOptions({
110 | stopOnFail: true,
111 | });
112 | const rules = {
113 | email: "min:100|email",
114 | };
115 |
116 | const result1 = await validate({ email: "xxx" }, rules);
117 | expect(result1.errors.email.length).toBe(1);
118 |
119 | // Reverting the changes
120 | await setOptions({
121 | stopOnFail: false,
122 | });
123 | const result2 = await validate({ email: "xxx" }, rules);
124 | expect(result2.errors.email.length).toBe(2);
125 |
126 | // Overriding my general options for a specific validate call
127 | await setOptions({
128 | stopOnFail: true,
129 | });
130 | const result3 = await validate({ email: "xxx" }, rules, {
131 | stopOnFail: false,
132 | });
133 | expect(result3.errors.email.length).toBe(2);
134 | });
135 |
136 | test("should not be able show a valid field in the error list", async () => {
137 | const rules = {
138 | email: "required",
139 | };
140 |
141 | const result = await validate({ email: "foo@bar.com" }, rules);
142 | expect(result.errors.email).toBeUndefined();
143 | });
144 |
145 | test("should be able to see if a field is valid or not", async () => {
146 | const rules = {
147 | email: "required",
148 | name: "required",
149 | };
150 |
151 | const result = await validate({ email: "foo@bar.com" }, rules);
152 | expect(result.fields.email).toBe(true);
153 | expect(result.fields.name).toBe(false);
154 | });
155 |
156 | test("should be able to load languages dynamically", async () => {
157 | await setLocales([en, tr]);
158 |
159 | const rules = {
160 | email: "required|email",
161 | };
162 |
163 | const result = await validate({ email: "invalid-email" }, rules, {
164 | language: "tr",
165 | });
166 | expect(result.isValid).toBe(false);
167 | expect(result.errors.email[0].message).toBe("Alan bir e-posta olmalıdır.");
168 | });
169 |
170 | test("should be able to use the converters", async () => {
171 | const data = {
172 | email: "sample",
173 | };
174 | const rules = {
175 | email: [required(), email()],
176 | };
177 |
178 | const result = await validate(data, rules);
179 | expect(result.isValid).toBe(false);
180 | });
181 |
182 | test("should not try to validate for empty records", async () => {
183 | const data = {
184 | email: null,
185 | phone: undefined,
186 | };
187 | const rules = {
188 | email: "email",
189 | phone: "min:10",
190 | };
191 |
192 | const result = await validate(data, rules);
193 | expect(result.isValid).toBe(true);
194 | });
195 |
196 | test("should throw an error for undefined validation rules", async () => {
197 | const data = {
198 | email: "user@example.com",
199 | };
200 | const rules = {
201 | email: "exists:users",
202 | };
203 |
204 | await expect(validate(data, rules)).rejects.toThrow(
205 | "Undefined validation rule: exists"
206 | );
207 | });
208 |
209 | test("should be able to register a custom rule", async () => {
210 | // Register a custom rule
211 | const testRule = () => {
212 | return true;
213 | };
214 | register("test", testRule, EXISTS_RULE_TRANSLATIONS);
215 |
216 | const data = {
217 | email: "user@example.com",
218 | };
219 | const rules = {
220 | email: "test:users",
221 | };
222 |
223 | const results = await validate(data, rules);
224 | expect(results.isValid).toBe(true);
225 | });
226 |
227 | test("should be able to register a custom rule with translations", async () => {
228 | // Register a custom rule
229 | const existsRule = () => {
230 | return false;
231 | };
232 | register("exists", existsRule, EXISTS_RULE_TRANSLATIONS);
233 |
234 | const data = {
235 | email: "user@example.com",
236 | };
237 | const rules = {
238 | email: "exists:users",
239 | };
240 |
241 | const results = await validate(data, rules);
242 | expect(results.isValid).toBe(false);
243 | expect(results.errors.email[0].rule).toBe("exists");
244 | expect(results.errors.email[0].message).toBe(
245 | "The record doesn't exists on database: users"
246 | );
247 | });
248 |
249 | test("should be able to register a rule before setLocales", async () => {
250 | const isNullable = (value: any) => {
251 | if (value === null || value === "null") {
252 | return true;
253 | }
254 | return false;
255 | };
256 |
257 | register("nullable", isNullable, {
258 | en: "The {0} field must be null or empty.",
259 | tr: "The {0} field must be null or empty.",
260 | az: "AZ: The {0} field must be null or empty!",
261 | });
262 |
263 | setLocales(az);
264 |
265 | const data = {
266 | email: "user@example.com",
267 | };
268 | const rules = {
269 | email: "nullable",
270 | };
271 |
272 | const results = await validate(data, rules, { language: "az" });
273 | expect(results.isValid).toBe(false);
274 | expect(results.errors.email[0].message).toBe(
275 | "AZ: The {0} field must be null or empty!"
276 | );
277 | });
278 |
279 | test("should not be able to register the same rule twice", async () => {
280 | expect(() =>
281 | register("exists", () => false, EXISTS_RULE_TRANSLATIONS)
282 | ).toThrow("The rule name is already defined: exists");
283 | });
284 | });
285 |
--------------------------------------------------------------------------------
/tests/nested.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, test, expect } from "vitest";
2 | import { validate, setLocales, en, ILocale } from "../index";
3 |
4 | describe("validate()", () => {
5 | beforeAll(async () => {
6 | setLocales(en as ILocale);
7 | });
8 |
9 | test("should validate nested objects", async () => {
10 | const result = await validate(
11 | {
12 | id: 1,
13 | token: "123",
14 | user: {
15 | id: 1,
16 | email: "email",
17 | },
18 | },
19 | {
20 | id: "required|numeric",
21 | token: "required|min:20",
22 | "user.email": "required|email",
23 | }
24 | );
25 | expect(result.isValid).toBe(false);
26 | expect(result.fields.id).toBe(true);
27 | expect(result.fields.token).toBe(false);
28 | expect(result.fields["user.email"]).toBe(false);
29 |
30 | expect(result.errors["user.email"][0].message).toBe(
31 | "The field must be an email."
32 | );
33 | });
34 |
35 | test("should validate arrays", async () => {
36 | const result = await validate(
37 | {
38 | users: [
39 | {
40 | email: "correct@mail.com",
41 | },
42 | { email: "email" },
43 | ],
44 | },
45 | {
46 | "users.*.email": "required|email",
47 | }
48 | );
49 | expect(result.isValid).toBe(false);
50 | expect(result.fields["users.*.email"]).toBe(false);
51 | expect(result.errors["users.1.email"][0].message).toBe(
52 | "The field must be an email."
53 | );
54 | });
55 |
56 | test("should validate nested arrays", async () => {
57 | const result = await validate(
58 | {
59 | users: [
60 | {
61 | addresses: [
62 | {
63 | city: "New York",
64 | },
65 | {
66 | street: "Wall Street",
67 | },
68 | ],
69 | },
70 | {
71 | addresses: [
72 | {
73 | city: "New York",
74 | },
75 | {
76 | city: "Los Angeles",
77 | },
78 | ],
79 | },
80 | ],
81 | },
82 | {
83 | "users.*.addresses.*.city": "required",
84 | }
85 | );
86 | expect(result.isValid).toBe(false);
87 | expect(result.fields["users.*.addresses.*.city"]).toBe(false);
88 | expect(result.errors["users.0.addresses.1.city"][0].message).toBe(
89 | "The field is required."
90 | );
91 | });
92 |
93 | test("should validate everything", async () => {
94 | const result = await validate(
95 | {
96 | secret: "some secret",
97 | users: [
98 | {
99 | addresses: [
100 | {
101 | city: "New York",
102 | },
103 | {
104 | city: "Istanbul",
105 | },
106 | ],
107 | },
108 | {
109 | addresses: [
110 | {
111 | city: "New York",
112 | },
113 | {
114 | street: "Wall Street",
115 | },
116 | ],
117 | },
118 | ],
119 | permissons: {
120 | read: true,
121 | write: true,
122 | },
123 | },
124 | {
125 | secret: "required|min:100",
126 | "users.*.addresses.*.city": "required",
127 | "permissons.read": "required|boolean",
128 | "permissons.delete": "required|boolean",
129 | }
130 | );
131 | expect(result.isValid).toBe(false);
132 | expect(result.fields.secret).toBe(false);
133 | expect(result.fields["users.*.addresses.*.city"]).toBe(false);
134 | expect(result.fields["permissons.read"]).toBe(true);
135 | expect(result.fields["permissons.delete"]).toBe(false);
136 | });
137 | });
138 |
--------------------------------------------------------------------------------
/tests/ruleManager.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, test, expect } from "vitest";
2 | import {
3 | setLocales,
4 | en,
5 | ILocale,
6 | isRegistered,
7 | register,
8 | validate,
9 | } from "../index";
10 |
11 | describe("isRegistered()", () => {
12 | beforeAll(async () => {
13 | setLocales(en as ILocale);
14 | });
15 |
16 | test("should check the unregistered rules", async () => {
17 | const result = isRegistered("myRule");
18 | expect(result).toBe(false);
19 | });
20 |
21 | test("should check the registered rules", async () => {
22 | register(
23 | "myRule",
24 | () => {
25 | return false;
26 | },
27 | {
28 | en: "My rule throws an error!",
29 | }
30 | );
31 | const result = isRegistered("myRule");
32 | expect(result).toBe(true);
33 | });
34 |
35 | test("should be able use custom rule", async () => {
36 | const data = {
37 | email: "foo@bar.com",
38 | };
39 | const rules = {
40 | email: "myRule",
41 | };
42 |
43 | const result = await validate(data, rules);
44 | expect(result.isValid).toBe(false);
45 | expect(result.errors.email[0].rule).toBe("myRule");
46 | expect(result.errors.email[0].message).toBe("My rule throws an error!");
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tests/rules/accepted.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isAccepted } from "../../index";
3 |
4 | const positiveList = ["yes", "on", 1, "1", true];
5 |
6 | const negativeList = ["10", "", "no", 0, false, {}, [], null, undefined];
7 |
8 | describe("isAccepted() ", () => {
9 | test.each(positiveList)("should return TRUE for the %s value", (value) => {
10 | expect(isAccepted(value)).toBe(true);
11 | });
12 |
13 | test.each(negativeList)("should return FALSE for the %s value", (value) => {
14 | expect(isAccepted(value)).toBe(false);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/rules/after.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isAfter, setLocales, validate, en } from "../../index";
3 |
4 | describe("isAfter()", () => {
5 | it("should return true if value is after the date", () => {
6 | expect(isAfter("2023-01-01", "2022-01-01")).toBe(true);
7 | expect(isAfter(new Date("2023-01-01"), "2022-01-01")).toBe(true);
8 | expect(
9 | isAfter(
10 | "Wed Jun 22 2022 15:42:50 GMT-0700 (Pacific Daylight Time)",
11 | "2022-01-01"
12 | )
13 | ).toBe(true);
14 | });
15 |
16 | it("should return false if value is on or before the date", () => {
17 | expect(isAfter("2022-01-01", "2022-01-01")).toBe(false);
18 | expect(isAfter(new Date("2022-01-01"), "2022-01-01")).toBe(false);
19 | expect(isAfter("2022-01-01", "2023-01-01")).toBe(false);
20 | expect(isAfter(new Date("2022-01-01"), "2023-01-01")).toBe(false);
21 | expect(
22 | isAfter(
23 | "Wed Jun 22 2022 15:42:50 GMT-0700 (Pacific Daylight Time)",
24 | "2023-01-01"
25 | )
26 | ).toBe(false);
27 | });
28 |
29 | it("should handle valid date strings and Date objects", () => {
30 | expect(isAfter("2023-01-01", new Date("2022-01-01"))).toBe(true);
31 | expect(isAfter(new Date("2023-01-01"), new Date("2022-01-01"))).toBe(true);
32 | });
33 |
34 | it("should return false for invalid date formats", () => {
35 | expect(isAfter("invalid-date", "2022-01-01")).toBe(false);
36 | expect(isAfter("2023-01-01", "invalid-date")).toBe(false);
37 | });
38 |
39 | it("should handle weird values gracefully", () => {
40 | expect(isAfter("", "2022-01-01")).toBe(false);
41 | expect(isAfter(42, "2022-01-01")).toBe(false);
42 | expect(isAfter([], "2022-01-01")).toBe(false);
43 | expect(isAfter({}, "2022-01-01")).toBe(false);
44 | expect(isAfter("2022-01-01", "")).toBe(false);
45 | expect(isAfter("2022-01-01", [])).toBe(false);
46 | expect(isAfter("2022-01-01", {})).toBe(false);
47 | });
48 |
49 | it("should be able to use another field as the comparison date", async () => {
50 | await setLocales(en);
51 |
52 | const data = {
53 | startAt: "2023-10-21",
54 | finishAt: "2023-10-01",
55 | };
56 | const rules = {
57 | finishAt: "after:startAt",
58 | };
59 |
60 | const result = await validate(data, rules);
61 | expect(result.isValid).toBe(false);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/tests/rules/afterOrEqual.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isAfterOrEqual, setLocales, validate, en } from "../../index";
3 |
4 | describe("isAfterOrEqual() ", () => {
5 | it("returns true if the value is after or equal to the date (string input)", () => {
6 | expect(isAfterOrEqual("2023-01-01", "2022-01-01")).toBe(true);
7 | expect(
8 | isAfterOrEqual(
9 | "Wed Jun 22 2022 15:42:50 GMT-0700 (Pacific Daylight Time)",
10 | "2022-01-01"
11 | )
12 | ).toBe(true);
13 | expect(isAfterOrEqual("2022-01-01", "2022-01-01")).toBe(true);
14 | });
15 |
16 | it("returns true if the value is after or equal to the date (Date object input)", () => {
17 | expect(isAfterOrEqual(new Date("2023-01-01"), new Date("2022-01-01"))).toBe(
18 | true
19 | );
20 | expect(
21 | isAfterOrEqual(
22 | new Date("Wed Jun 22 2022 15:42:50 GMT-0700 (Pacific Daylight Time)"),
23 | "2022-01-01"
24 | )
25 | ).toBe(true);
26 | expect(isAfterOrEqual(new Date("2022-01-01"), new Date("2022-01-01"))).toBe(
27 | true
28 | );
29 | });
30 |
31 | it("returns false if the value is before the date (string input)", () => {
32 | expect(isAfterOrEqual("2022-01-01", "2023-01-01")).toBe(false);
33 | expect(
34 | isAfterOrEqual(
35 | "Wed Jun 22 2022 15:42:50 GMT-0700 (Pacific Daylight Time)",
36 | "2023-01-01"
37 | )
38 | ).toBe(false);
39 | });
40 |
41 | it("returns false if the value is before the date (Date object input)", () => {
42 | expect(isAfterOrEqual(new Date("2022-01-01"), new Date("2023-01-01"))).toBe(
43 | false
44 | );
45 | expect(
46 | isAfterOrEqual(
47 | new Date("Wed Jun 22 2022 15:42:50 GMT-0700 (Pacific Daylight Time)"),
48 | "2023-01-01"
49 | )
50 | ).toBe(false);
51 | });
52 |
53 | it("returns false if the value is not strictly equal to the date (string input)", () => {
54 | expect(isAfterOrEqual("2022-01-01", "2022-01-02")).toBe(false);
55 | });
56 |
57 | it("returns false if the value is not strictly equal to the date (Date object input)", () => {
58 | expect(isAfterOrEqual(new Date("2022-01-01"), new Date("2022-01-02"))).toBe(
59 | false
60 | );
61 | });
62 |
63 | it("throws an error for invalid date formats (string input)", () => {
64 | expect(isAfterOrEqual("invalid-date", "2022-01-01")).toBe(false);
65 | expect(isAfterOrEqual("2022-01-01", "invalid-date")).toBe(false);
66 | expect(isAfterOrEqual("invalid-date", "invalid-date")).toBe(false);
67 | });
68 |
69 | it("throws an error for invalid date formats (Date object input)", () => {
70 | expect(isAfterOrEqual("invalid-date", new Date("2022-01-01"))).toBe(false);
71 | expect(isAfterOrEqual(new Date("2022-01-01"), "invalid-date")).toBe(false);
72 | expect(isAfterOrEqual("invalid-date", "invalid-date")).toBe(false);
73 | });
74 |
75 | it("should be able to use another field as the comparison date", async () => {
76 | await setLocales(en);
77 |
78 | const data = {
79 | startAt: "2023-10-21",
80 | finishAt: "2023-10-21",
81 | };
82 | const rules = {
83 | finishAt: "after_or_equal:startAt",
84 | };
85 |
86 | const result = await validate(data, rules);
87 | expect(result.isValid).toBe(true);
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/tests/rules/alpha.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isAlpha } from "../../index";
3 |
4 | describe("isAlpha() ", () => {
5 | it("returns true for valid alphabetic strings", () => {
6 | expect(isAlpha("abc")).toBe(true);
7 | expect(isAlpha("XYZ")).toBe(true);
8 | expect(isAlpha("AbCdEf")).toBe(true);
9 | });
10 |
11 | it("returns false for non-alphabetic strings", () => {
12 | expect(isAlpha("123")).toBe(false);
13 | expect(isAlpha("abc123")).toBe(false);
14 | expect(isAlpha("Special!")).toBe(false);
15 | expect(isAlpha(123)).toBe(false); // Non-string input
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/tests/rules/alphaDash.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isAlphaDash } from "../../index";
3 |
4 | const positiveList = ["John_-", "abc123", "alpha-numeric_123", 123];
5 |
6 | const negativeList = ["special$char", { key: "value" }, "'", "a b"];
7 |
8 | describe("isAlphaDash() ", () => {
9 | test.each(positiveList)("should return TRUE for the `%s` value", (value) => {
10 | expect(isAlphaDash(value)).toBe(true);
11 | });
12 |
13 | test.each(negativeList)("should return FALSE for the `%s` value", (value) => {
14 | expect(isAlphaDash(value)).toBe(false);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/rules/alphaNum.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isAlphaNum } from "../../index";
3 |
4 | const positiveList = ["abc123", "AbC456", "123XYZ", 123];
5 |
6 | const negativeList = [
7 | "abc!123",
8 | "123@xyz",
9 | "abc_123",
10 | true,
11 | false,
12 | { key: "value" },
13 | "word word",
14 | ];
15 |
16 | describe("isAlphaNum() ", () => {
17 | test.each(positiveList)("should return TRUE for the `%s` value", (value) => {
18 | expect(isAlphaNum(value)).toBe(true);
19 | });
20 |
21 | test.each(negativeList)("should return FALSE for the `%s` value", (value) => {
22 | expect(isAlphaNum(value)).toBe(false);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/tests/rules/array.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isArray } from "../../index";
3 |
4 | describe("isArray() ", () => {
5 | it("should return true for an empty array", () => {
6 | expect(isArray([])).toBe(true);
7 | expect(isArray([1, 2, 3])).toBe(true);
8 | });
9 |
10 | it("should return false for non-arrays", () => {
11 | expect(isArray("not an array")).toBe(false);
12 | expect(isArray(42)).toBe(false);
13 | expect(isArray({ key: "value" })).toBe(false);
14 | expect(isArray(null)).toBe(false);
15 | expect(isArray(undefined)).toBe(false);
16 | expect(isArray(true)).toBe(false);
17 | expect(isArray(false)).toBe(false);
18 | expect(isArray(() => {})).toBe(false);
19 | expect(isArray(new Map())).toBe(false);
20 | expect(isArray(new Set())).toBe(false);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/rules/before.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isBefore, setLocales, validate, en } from "../../index";
3 |
4 | describe("isBefore() ", () => {
5 | it("should return true if the value is before the given date", () => {
6 | expect(isBefore("2023-01-01", "2023-01-02")).toBe(true);
7 | expect(isBefore(100, "2023-01-01")).toBe(true);
8 | expect(isBefore(100, "2022-01-01")).toBe(true);
9 | });
10 |
11 | it("should return false if the value is on or after the given date", () => {
12 | expect(isBefore("2024-01-01", "2023-01-01")).toBe(false);
13 | expect(isBefore("2023-01-01", "2023-01-01")).toBe(false);
14 | });
15 |
16 | it("should handle date objects as well", () => {
17 | const currentDate = new Date();
18 | const futureDate = new Date(currentDate);
19 | futureDate.setDate(currentDate.getDate() + 1);
20 |
21 | expect(isBefore(currentDate, futureDate)).toBe(true);
22 | expect(isBefore(futureDate, currentDate)).toBe(false);
23 | });
24 |
25 | it("should be able to use another field as the comparison date", async () => {
26 | await setLocales(en);
27 |
28 | const data = {
29 | startAt: "2023-01-01",
30 | finishAt: "2023-01-31",
31 | };
32 | const rules = {
33 | startAt: "before:finishAt",
34 | };
35 |
36 | const result = await validate(data, rules);
37 | expect(result.isValid).toBe(true);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/tests/rules/beforeOrEqual.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isBeforeOrEqual, setLocales, validate, en } from "../../index";
3 |
4 | describe("isBeforeOrEqual() ", () => {
5 | it("should return true if the value is before or equal to the date", () => {
6 | const date = new Date("2023-01-01");
7 |
8 | expect(isBeforeOrEqual(new Date("2022-01-01"), date)).toBe(true);
9 | expect(isBeforeOrEqual(new Date("2023-01-01"), date)).toBe(true);
10 | expect(isBeforeOrEqual("2023-01-01", "2023-01-01")).toBe(true);
11 | expect(isBeforeOrEqual("2020-01-01", "2023-01-01")).toBe(true);
12 | });
13 |
14 | it("should return false if the value is after the date", () => {
15 | const date = new Date("2023-01-01");
16 |
17 | expect(isBeforeOrEqual(new Date("2024-01-01"), date)).toBe(false);
18 | expect(isBeforeOrEqual("2023-01-02", date)).toBe(false);
19 | });
20 |
21 | it("should return false for invalid value or date types", () => {
22 | expect(isBeforeOrEqual("invalidValue", new Date())).toBe(false);
23 | expect(isBeforeOrEqual(new Date(), "invalidDate")).toBe(false);
24 | });
25 |
26 | it("should be able to use another field as the comparison date", async () => {
27 | await setLocales(en);
28 |
29 | const data = {
30 | startAt: "2023-01-01",
31 | finishAt: "2023-01-01",
32 | };
33 | const rules = {
34 | startAt: "before_or_equal:finishAt",
35 | };
36 |
37 | const result = await validate(data, rules);
38 | expect(result.isValid).toBe(true);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/tests/rules/between.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isBetween, setLocales, validate, en } from "../../index";
3 |
4 | const positiveList = [
5 | [5, 1, 10],
6 | [5.5, 1, 10],
7 | ["abc", 1, 3],
8 | [-2, -5, -1],
9 | ];
10 |
11 | const negativeList = [{}, new Error(), true, false, 100, "abc"];
12 |
13 | describe("isBetween() ", () => {
14 | test.each(positiveList)(
15 | "should return TRUE for the `%s` value (%s:%s)",
16 | (value, min, max) => {
17 | expect(isBetween(value, min, max)).toBe(true);
18 | }
19 | );
20 |
21 | test.each(negativeList)(
22 | "should return FALSE for the `%s` value (5:10)",
23 | (value) => {
24 | expect(isBetween(value, 5, 10)).toBe(false);
25 | }
26 | );
27 |
28 | test("should threat as string if the definition has `numeric` rule", async () => {
29 | await setLocales(en);
30 |
31 | const data = { input: "8" };
32 | const rules = { input: "numeric|between:5,10" };
33 |
34 | const result = await validate(data, rules);
35 | expect(result.isValid).toBe(true);
36 | });
37 |
38 | test("should support array", function () {
39 | expect(isBetween(["a", "b", "c"], 1, 5)).toBe(true);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/tests/rules/boolean.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isBoolean } from "../../index";
3 |
4 | describe("isBoolean() ", () => {
5 | it("should return true for valid boolean values", () => {
6 | expect(isBoolean(true)).toBe(true);
7 | expect(isBoolean(false)).toBe(true);
8 | expect(isBoolean(0)).toBe(true);
9 | expect(isBoolean(1)).toBe(true);
10 | expect(isBoolean("true")).toBe(true);
11 | expect(isBoolean("false")).toBe(true);
12 | expect(isBoolean("0")).toBe(true);
13 | expect(isBoolean("1")).toBe(true);
14 | });
15 |
16 | it("should return false for invalid boolean values", () => {
17 | expect(isBoolean("abc")).toBe(false);
18 | expect(isBoolean({})).toBe(false);
19 | expect(isBoolean([])).toBe(false);
20 | expect(isBoolean(42)).toBe(false);
21 | expect(isBoolean("")).toBe(false);
22 | expect(isBoolean(" ")).toBe(false);
23 | expect(isBoolean(new Error())).toBe(false);
24 | expect(isBoolean(null)).toBe(false);
25 | expect(isBoolean(undefined)).toBe(false);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/tests/rules/confirmed.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { IContext, isConfirmed } from "../../index";
3 |
4 | const toContext = (value: any): IContext => {
5 | return {
6 | data: {
7 | password: "123456",
8 | password_confirmed: value,
9 | },
10 | field: "password",
11 | definition: "confirmed",
12 | };
13 | };
14 |
15 | describe("confirmed() ", () => {
16 | it("should return true for valid confirmed values", () => {
17 | expect(isConfirmed("123456", toContext("123456"))).toBe(true);
18 | });
19 |
20 | it("should return false for invalid confirmed values", () => {
21 | expect(isConfirmed("123456", toContext("12345678"))).toBe(false);
22 | expect(isConfirmed("123456", toContext(null))).toBe(false);
23 | expect(isConfirmed("123456", toContext(false))).toBe(false);
24 | expect(isConfirmed("123456", toContext({}))).toBe(false);
25 | expect(isConfirmed("123456", toContext(""))).toBe(false);
26 | expect(isConfirmed("123456", toContext(123456))).toBe(false);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/rules/date.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isDate } from "../../index";
3 |
4 | describe("isDate() ", () => {
5 | test("should return TRUE for valid dates", () => {
6 | expect(isDate("2023-01-01")).toBe(true);
7 | expect(isDate("2023-01-15")).toBe(true);
8 | expect(isDate("2023-01-31")).toBe(true);
9 | expect(isDate("2023-31-01", "yyyy-dd-MM")).toBe(true);
10 | expect(isDate("2023.31.01", "yyyy.dd.MM")).toBe(true);
11 | expect(isDate("2012-05-28 10:21:15", "yyyy-MM-dd HH:mm:ss")).toBe(true);
12 | expect(isDate("2023 January 15", "yyyy MMMM dd")).toBe(true);
13 | expect(isDate("2023 Jan 15", "yyyy MMM dd")).toBe(true);
14 | });
15 |
16 | test("should return FALSE for invalid dates", () => {
17 | expect(isDate("2023-01-34")).toBe(false);
18 | expect(isDate("2023-02-31")).toBe(false);
19 | expect(isDate("02-31-2023")).toBe(false);
20 | expect(isDate("2023-31-01", "yyyy-MM-dd")).toBe(false);
21 | expect(isDate("2023-31-01", "yyyy.dd.MM")).toBe(false);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/tests/rules/digits.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, test } from "vitest";
2 | import { isDigits } from "../../index";
3 |
4 | const negativeList = [
5 | "9098a",
6 | "abcde",
7 | null,
8 | undefined,
9 | true,
10 | false,
11 | {},
12 | NaN,
13 | Infinity,
14 | -Infinity,
15 | ];
16 |
17 | describe("isDigits() ", () => {
18 | it("returns true for numeric values with exact length", () => {
19 | expect(isDigits(12345, 5)).toBe(true);
20 | expect(isDigits(12345, "5")).toBe(true);
21 | expect(isDigits(0, 1)).toBe(true);
22 | });
23 |
24 | it("returns false for numeric values with incorrect length", () => {
25 | expect(isDigits(123, 5)).toBe(false);
26 | expect(isDigits(123456, 3)).toBe(false);
27 | expect(isDigits(1.6, 3)).toBe(false);
28 | expect(isDigits("1234 ", 5)).toBe(false);
29 | expect(isDigits(" 1234", 5)).toBe(false);
30 | expect(isDigits(" ", 1)).toBe(false);
31 | });
32 |
33 | it("throwns error for invalid length value", () => {
34 | expect(() => isDigits(123, "abc")).toThrow();
35 | expect(() => isDigits(123, "123")).not.toThrow();
36 | });
37 |
38 | test.each(negativeList)("should return FALSE for the %s value", (value) => {
39 | expect(isDigits(value, 5)).toBe(false);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/tests/rules/digitsBetween.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isDigitsBetween } from "../../index";
3 |
4 | const negativeList = [
5 | "9098a",
6 | "abcde",
7 | null,
8 | undefined,
9 | true,
10 | false,
11 | {},
12 | NaN,
13 | Infinity,
14 | -Infinity,
15 | ];
16 |
17 | describe("digitsBetween() ", () => {
18 | test("Valid numeric value within range", () => {
19 | expect(isDigitsBetween("123", 2, 4)).toBe(true);
20 | expect(isDigitsBetween("123", "2", "4")).toBe(true);
21 | expect(isDigitsBetween(123, "2", "4")).toBe(true);
22 | });
23 |
24 | test("Valid numeric value at the lower edge of the range", () => {
25 | expect(isDigitsBetween("12", 2, 4)).toBe(true);
26 | });
27 |
28 | test("Valid numeric value at the upper edge of the range", () => {
29 | expect(isDigitsBetween("1234", 2, 4)).toBe(true);
30 | });
31 |
32 | test("Valid numeric value with minimum length", () => {
33 | expect(isDigitsBetween("1", 1, 5)).toBe(true);
34 | });
35 |
36 | test("Valid numeric value with maximum length", () => {
37 | expect(isDigitsBetween("12345", 1, 5)).toBe(true);
38 | });
39 |
40 | test("Invalid numeric value (not a number)", () => {
41 | expect(isDigitsBetween("abc", 2, 4)).toBe(false);
42 | expect(isDigitsBetween(1.2, 2, 4)).toBe(false);
43 | });
44 |
45 | test("Invalid numeric value (below the minimum length)", () => {
46 | expect(isDigitsBetween("12", 3, 4)).toBe(false);
47 | });
48 |
49 | test("Invalid numeric value (above the maximum length)", () => {
50 | expect(isDigitsBetween("12345", 1, 4)).toBe(false);
51 | });
52 |
53 | test("Incorrect min or max value should thrown error ", () => {
54 | expect(() => isDigitsBetween(12, "a", 4)).toThrow();
55 | expect(() => isDigitsBetween(12, 1, "c")).toThrow();
56 | expect(() => isDigitsBetween(12, 1, undefined)).toThrow();
57 | expect(() => isDigitsBetween(12, null, 4)).toThrow();
58 | });
59 |
60 | test.each(negativeList)("should return FALSE for the %s value", (value) => {
61 | expect(isDigitsBetween(value, 4, 6)).toBe(false);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/tests/rules/email.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isEmail } from "../../index";
3 |
4 | const positiveList = [
5 | "test@example.com",
6 | "user@mail.co",
7 | "johndoe@gmail.com.uk",
8 | "johndoe@gmail.com.au",
9 | "mike.erickson@ru.codedungeon.ru",
10 | ];
11 |
12 | const negativeList = [
13 | null,
14 | undefined,
15 | "",
16 | " ",
17 | "invalid-email",
18 | "user@",
19 | ".wooly@example.com",
20 | "özgür@ışıklı.com",
21 | 123,
22 | { key: "value" },
23 | "johndoe.gmail.com",
24 | ];
25 |
26 | describe("isEmail() ", () => {
27 | test.each(positiveList)("should return TRUE for the %s value", (value) => {
28 | expect(isEmail(value)).toBe(true);
29 | });
30 |
31 | test.each(negativeList)("should return FALSE for the %s value", (value) => {
32 | expect(isEmail(value)).toBe(false);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/rules/hex.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isHex } from "../../index";
3 |
4 | const positiveList = [
5 | "1aF",
6 | "1234567890ABCDEF",
7 | 123,
8 | "54759eb3c090d83494e2d804",
9 | "0",
10 | 0,
11 | "a",
12 | ];
13 |
14 | const negativeList = [
15 | "4d4b8z",
16 | "123xyz",
17 | "0xg",
18 | "invalid string",
19 | true,
20 | false,
21 | null,
22 | undefined,
23 | "",
24 | ];
25 |
26 | describe("isHex() ", () => {
27 | test.each(positiveList)("should return TRUE for the '%s' value", (value) => {
28 | expect(isHex(value)).toBe(true);
29 | });
30 |
31 | test.each(negativeList)("should return FALSE for the '%s' value", (value) => {
32 | expect(isHex(value)).toBe(false);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/rules/in.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isIn } from "../../index";
3 |
4 | const LIST = ["a", "b", "c"];
5 |
6 | describe("isIn() ", () => {
7 | it("should return FALSE for null or undefined values", () => {
8 | expect(isIn(null, LIST)).toBe(false);
9 | expect(isIn(undefined, LIST)).toBe(false);
10 | });
11 |
12 | it("should return true if value is in the list", () => {
13 | expect(isIn("a", LIST)).toBe(true);
14 | expect(isIn(["a", "b"], LIST)).toBe(true);
15 | });
16 |
17 | it("should return false if value is not in the list", () => {
18 | expect(isIn("d", LIST)).toBe(false);
19 | expect(isIn(["a", "d"], LIST)).toBe(false);
20 | });
21 |
22 | it("should handle numeric values correctly", () => {
23 | expect(isIn(42, ["42", "55", "10"])).toBe(true);
24 | expect(isIn([42, 55], ["42", "55", "10"])).toBe(true);
25 | expect(isIn(7, ["42", "55", "10"])).toBe(false);
26 | });
27 |
28 | it("should handle mixed types correctly", () => {
29 | expect(isIn("1", ["1", 2, "3"])).toBe(true);
30 | expect(isIn([1, "2"], ["1", 2, "3"])).toBe(true);
31 | expect(isIn("4", [1, "2", 3])).toBe(false);
32 | });
33 |
34 | it("should return false for empty list", () => {
35 | expect(isIn("a", [])).toBe(false);
36 | expect(isIn(["a", "b"], [])).toBe(false);
37 | });
38 |
39 | it("should handle case sensitivity correctly", () => {
40 | expect(isIn("A", ["a", "b", "c"])).toBe(false);
41 | expect(isIn("A", ["A", "B", "C"])).toBe(true);
42 | });
43 |
44 | it("should be able to parse string values", async () => {
45 | expect(isIn("A", "A,B,B")).toBe(true);
46 | expect(isIn("B", "A,B,C")).toBe(true);
47 | expect(isIn("C", "A,B,C")).toBe(true);
48 | expect(isIn("D", "A,B,C")).toBe(false);
49 |
50 | expect(isIn("a", ["a", "b", "c"])).toBe(true);
51 | expect(isIn("b", ["a", "b", "c"])).toBe(true);
52 | expect(isIn("c", ["a", "b", "c"])).toBe(true);
53 | expect(isIn("d", ["a", "b", "c"])).toBe(false);
54 | });
55 |
56 | it("should be able to work with spread-string options", async () => {
57 | expect(isIn("A", ...["A,B,B"])).toBe(true);
58 | expect(isIn("A", ...["A", "B", "C"])).toBe(true);
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/tests/rules/integer.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isInteger } from "../../index";
3 |
4 | describe("isInteger() ", () => {
5 | it("should return FALSE for null or undefined", () => {
6 | expect(isInteger(null)).toBe(false);
7 | expect(isInteger(undefined)).toBe(false);
8 | });
9 |
10 | it("should return true for integer values", () => {
11 | expect(isInteger(0)).toBe(true);
12 | expect(isInteger(42)).toBe(true);
13 | expect(isInteger(-15)).toBe(true);
14 | expect(isInteger("10")).toBe(true);
15 | });
16 |
17 | it("should return false for non-integer numbers", () => {
18 | expect(isInteger(3.14)).toBe(false);
19 | expect(isInteger(-0.5)).toBe(false);
20 | expect(isInteger("3.14")).toBe(false);
21 | expect(isInteger("10cd")).toBe(false);
22 | });
23 |
24 | it("should return false for non-numeric values", () => {
25 | expect(isInteger("string")).toBe(false);
26 | expect(isInteger(true)).toBe(false);
27 | expect(isInteger(false)).toBe(false);
28 | expect(isInteger([])).toBe(false);
29 | expect(isInteger({})).toBe(false);
30 | expect(isInteger(new Error())).toBe(false);
31 | expect(isInteger(NaN)).toBe(false);
32 | expect(isInteger(Infinity)).toBe(false);
33 | expect(isInteger(-Infinity)).toBe(false);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/tests/rules/max.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isMax } from "../../index";
3 |
4 | describe("isMax() ", () => {
5 | test("returns true when value is less than or equal to size", () => {
6 | expect(isMax(5, 10)).toBe(true);
7 | expect(isMax(10, 10)).toBe(true);
8 | expect(isMax("5", 10)).toBe(true);
9 | expect(isMax(0, 0)).toBe(true);
10 | expect(isMax(-3, 5)).toBe(true);
11 | expect(isMax(5, 5.1)).toBe(true);
12 | expect(isMax("john", 10)).toBe(true);
13 | });
14 |
15 | test("returns false when value is greater than size", () => {
16 | expect(isMax(15, 10)).toBe(false);
17 | expect(isMax(100, 50)).toBe(false);
18 | expect(isMax("ozzy", 3)).toBe(false);
19 | });
20 |
21 | test("returns FALSE when value is null or undefined", () => {
22 | expect(isMax(null, 5)).toBe(false);
23 | expect(isMax(undefined, 10)).toBe(false);
24 | expect(isMax(true, 10)).toBe(false);
25 | });
26 |
27 | test("returns false for strings, arrays, and other types", () => {
28 | expect(isMax([1, 2, 3], 5)).toBe(false);
29 | expect(isMax({ key: "value" }, 100)).toBe(false);
30 | });
31 |
32 | test("handles edge cases and special values", () => {
33 | expect(isMax(0, 0)).toBe(true);
34 | expect(isMax(-1, 0)).toBe(true);
35 | expect(isMax(0, -1)).toBe(false);
36 | expect(() => isMax(0, null)).toThrow();
37 | expect(() => isMax(0, undefined)).toThrow();
38 | expect(() => isMax(0, "string-value")).toThrow();
39 | expect(() => isMax(0, new Error())).toThrow();
40 | expect(() => isMax(0, {})).toThrow();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/tests/rules/min.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isMin } from "../../index";
3 |
4 | describe("isMin() ", () => {
5 | test("returns false when value is less than or equal to size", () => {
6 | expect(isMin(5, 10)).toBe(false);
7 | expect(isMin(-3, 5)).toBe(false);
8 | expect(isMin(5, 5.1)).toBe(false);
9 | });
10 |
11 | test("returns true when value is greater than size", () => {
12 | expect(isMin(15, 10)).toBe(true);
13 | expect(isMin(100, 50)).toBe(true);
14 | expect(isMin(0, 0)).toBe(true);
15 | });
16 |
17 | test("returns false when value is null or undefined", () => {
18 | expect(isMin(null, 5)).toBe(false);
19 | expect(isMin(undefined, 10)).toBe(false);
20 | });
21 |
22 | test("returns false for strings, arrays, and other types", () => {
23 | expect(isMin("hello", 10)).toBe(false);
24 | expect(isMin([1, 2, 3], 5)).toBe(false);
25 | expect(isMin({ key: "value" }, 100)).toBe(false);
26 | });
27 |
28 | test("handles edge cases and special values", () => {
29 | expect(isMin(0, 0)).toBe(true);
30 | expect(isMin(-1, 0)).toBe(false);
31 | expect(isMin(0, -1)).toBe(true);
32 | expect(() => isMin(0, null)).toThrow();
33 | expect(() => isMin(0, undefined)).toThrow();
34 | expect(() => isMin(0, "string-value")).toThrow();
35 | expect(() => isMin(0, new Error())).toThrow();
36 | expect(() => isMin(0, {})).toThrow();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/tests/rules/notIn.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isNotIn } from "../../index";
3 |
4 | describe("isNotIn() ", () => {
5 | it("should return false for null or undefined values", () => {
6 | expect(isNotIn(null, ["a", "b", "c"])).toBe(false);
7 | expect(isNotIn(undefined, ["a", "b", "c"])).toBe(false);
8 | });
9 |
10 | it("should return false if value is in the list", () => {
11 | expect(isNotIn("a", ["a", "b", "c"])).toBe(false);
12 | expect(isNotIn(["a", "b"], ["a", "b", "c"])).toBe(false);
13 | expect(isNotIn(["a", "d"], ["a", "b", "c"])).toBe(false);
14 | });
15 |
16 | it("should return true if value is not in the list", () => {
17 | expect(isNotIn("d", ["a", "b", "c"])).toBe(true);
18 | });
19 |
20 | it("should handle numeric values correctly", () => {
21 | expect(isNotIn(42, ["42", "55", "10"])).toBe(false);
22 | expect(isNotIn([42, 55], ["42", "55", "10"])).toBe(false);
23 | expect(isNotIn(7, ["42", "55", "10"])).toBe(true);
24 | });
25 |
26 | it("should handle mixed types correctly", () => {
27 | expect(isNotIn("1", ["1", 2, "3"])).toBe(false);
28 | expect(isNotIn([1, "2"], ["1", 2, "3"])).toBe(false);
29 | expect(isNotIn("4", [1, "2", 3])).toBe(true);
30 | });
31 |
32 | it("should return false for empty list", () => {
33 | expect(isNotIn("a", [])).toBe(true);
34 | expect(isNotIn(["a", "b"], [])).toBe(true);
35 | });
36 |
37 | it("should handle case sensitivity correctly", () => {
38 | expect(isNotIn("A", ["a", "b", "c"])).toBe(true);
39 | expect(isNotIn("A", ["A", "B", "C"])).toBe(false);
40 | });
41 |
42 | it("should be able to parse string values", async () => {
43 | expect(isNotIn("A", "a,b,c")).toBe(true);
44 | expect(isNotIn("A", "A,B,C")).toBe(false);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/tests/rules/numeric.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { isNumeric } from "../../index";
3 |
4 | describe("isNumeric() ", () => {
5 | test("should return true for numeric values", () => {
6 | expect(isNumeric(42)).toBe(true);
7 | expect(isNumeric(0.5432)).toBe(true);
8 | expect(isNumeric(-3.14)).toBe(true);
9 | expect(isNumeric("123")).toBe(true);
10 | expect(isNumeric("-456")).toBe(true);
11 | expect(isNumeric(" 789 ")).toBe(true);
12 | expect(isNumeric("")).toBe(true);
13 | });
14 |
15 | test("should return false for non-numeric values", () => {
16 | expect(isNumeric("abc")).toBe(false);
17 | expect(isNumeric("123abc")).toBe(false);
18 | expect(isNumeric(true)).toBe(false);
19 | expect(isNumeric(false)).toBe(false);
20 | expect(isNumeric(null)).toBe(false);
21 | expect(isNumeric(undefined)).toBe(false);
22 | expect(isNumeric({})).toBe(false);
23 | expect(isNumeric([])).toBe(false);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tests/rules/required.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isRequired } from "../../index";
3 |
4 | describe("isRequired() ", () => {
5 | it("should return true for non-null, non-undefined, and non-empty string values", () => {
6 | expect(isRequired(42)).toBe(true);
7 | expect(isRequired("hello")).toBe(true);
8 | expect(isRequired(true)).toBe(true);
9 | expect(isRequired(false)).toBe(true);
10 | expect(isRequired([])).toBe(true);
11 | expect(isRequired({ key: "value" })).toBe(true);
12 | expect(isRequired(0)).toBe(true);
13 | expect(isRequired(0)).toBe(true);
14 | expect(isRequired("0")).toBe(true);
15 | expect(isRequired(true)).toBe(true);
16 | });
17 |
18 | it("should return false for null, undefined, and empty string values", () => {
19 | expect(isRequired(null)).toBe(false);
20 | expect(isRequired(undefined)).toBe(false);
21 | expect(isRequired("")).toBe(false);
22 | expect(isRequired(" ")).toBe(false);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/tests/rules/size.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isSize } from "../../index";
3 |
4 | describe("isSize() ", () => {
5 | it("should return false for null or undefined values", () => {
6 | expect(isSize(null, 5)).toBe(false);
7 | expect(isSize(undefined, 10)).toBe(false);
8 | });
9 |
10 | it("should return true for strings with the correct length", () => {
11 | expect(isSize("abcde", 5)).toBe(true);
12 | expect(isSize("test", 4)).toBe(true);
13 | });
14 |
15 | it("should return false for strings with incorrect length", () => {
16 | expect(isSize("abcd", 5)).toBe(false);
17 | expect(isSize("testing", 6)).toBe(false);
18 | });
19 |
20 | it("should return true for integers with the correct value", () => {
21 | expect(isSize(10, 10)).toBe(true);
22 | expect(isSize(5, 5)).toBe(true);
23 | });
24 |
25 | it("should return false for non-integer numbers", () => {
26 | expect(isSize(5.5, 5)).toBe(false);
27 | });
28 |
29 | it("should return false for non-string and non-numeric values", () => {
30 | expect(isSize(true, 3)).toBe(false);
31 | expect(isSize([1, 2, 3], 3)).toBe(false);
32 | expect(isSize({ key: "value" }, 2)).toBe(false);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/rules/string.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isString } from "../../index";
3 |
4 | describe("isString() ", () => {
5 | it("should return the correct value for the data type", () => {
6 | expect(isString("Hello")).toBe(true);
7 | expect(isString("")).toBe(true);
8 | expect(isString(null)).toBe(false);
9 | expect(isString(undefined)).toBe(false);
10 | expect(isString(123)).toBe(false);
11 | expect(isString(123.12)).toBe(false);
12 | expect(isString(true)).toBe(false);
13 | expect(isString({ key: "value" })).toBe(false);
14 | expect(isString(new Error())).toBe(false);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/rules/url.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { isUrl } from "../../index";
3 |
4 | describe("isUrl() ", () => {
5 | it("should return false for null and undefined", () => {
6 | expect(isUrl(null)).toBe(false);
7 | expect(isUrl(undefined)).toBe(false);
8 | });
9 |
10 | it("should return true for valid URLs", () => {
11 | expect(isUrl("https://example.com")).toBe(true);
12 | expect(isUrl("http://example.com/path")).toBe(true);
13 | expect(isUrl("ftp://example.com")).toBe(true);
14 | });
15 |
16 | it("should return false for invalid URLs", () => {
17 | expect(isUrl("invalid-url")).toBe(false);
18 | });
19 |
20 | it("should handle various data types", () => {
21 | expect(isUrl(123)).toBe(false);
22 | expect(isUrl({ key: "value" })).toBe(false);
23 | });
24 |
25 | it("should return false for empty strings", () => {
26 | expect(isUrl("")).toBe(false);
27 | });
28 |
29 | it("should handle special characters in URLs", () => {
30 | expect(isUrl("https://example.com/#section")).toBe(true);
31 | expect(isUrl("https://example.com/?param=value")).toBe(true);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "./dist",
11 | "allowSyntheticDefaultImports": true,
12 | "declaration": true,
13 | "typeRoots": ["node_modules/@types", "src/json.d.ts"]
14 | },
15 | "include": ["src/**/*.ts"],
16 | "exclude": ["node_modules", "dist", "docs"],
17 | "watchOptions": {
18 | "watchFile": "useFsEvents",
19 | "watchDirectory": "useFsEvents",
20 | "fallbackPolling": "dynamicPriority",
21 | "synchronousWatchDirectory": true,
22 | "excludeDirectories": ["**/node_modules", "dist"]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { configDefaults, defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | exclude: [...configDefaults.exclude, "./docs/**", "./tests/consumers"],
6 | },
7 | });
8 |
--------------------------------------------------------------------------------