├── .commitlintrc.cjs
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .husky
├── .gitignore
├── commit-msg
└── pre-commit
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── __tests__
├── luhn.test.ts
├── validator.test.ts
├── verhoeff.test.ts
└── vpa.test.ts
├── commitlint.config.cjs
├── dist
├── luhn.d.ts
├── luhn.js
├── validator.d.ts
├── validator.js
├── verhoeff.d.ts
├── verhoeff.js
├── vpa-handles.json
├── vpa.d.ts
└── vpa.js
├── eslint.config.mjs
├── jest.config.js
├── lint-staged.config.js
├── package-lock.json
├── package.json
├── src
├── luhn.ts
├── validator.ts
├── verhoeff.ts
├── vpa-handles.json
└── vpa.ts
├── tsconfig.json
└── vitest.config.ts
/.commitlintrc.cjs:
--------------------------------------------------------------------------------
1 | const configConventional = require('@commitlint/config-conventional');
2 |
3 | const typeEnum = configConventional.rules['type-enum'];
4 | typeEnum[2].push('init');
5 |
6 | module.exports = {
7 | extends: [
8 | '@commitlint/config-conventional'
9 | ],
10 | rules: {
11 | 'type-enum': typeEnum,
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "06:30"
8 | timezone: Asia/Kolkata
9 | open-pull-requests-limit: 20
10 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: CI
5 |
6 | on: [push]
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: ['22.x']
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | - run: npm ci
24 | - run: npm run lint --if-present
25 | - run: npm run build --if-present
26 | - run: npm test
27 | env:
28 | CI: true
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 |
82 | # Nuxt.js build / generate output
83 | .nuxt
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and not Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # Stores VSCode versions used for testing VSCode extensions
107 | .vscode-test
108 |
109 | # yarn v2
110 |
111 | .yarn/cache
112 | .yarn/unplugged
113 | .yarn/build-state.yml
114 | .pnp.*
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx --no-install commitlint --config commitlint.config.cjs --edit "$1"
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx lint-staged
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | trailingComma: 'all',
3 | singleQuote: true,
4 | printWidth: 120
5 | };
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [0.0.11](https://github.com/mastermunj/format-utils/compare/v0.0.10...v0.0.11) (2025-03-18)
6 |
7 | ### [0.0.10](https://github.com/mastermunj/format-utils/compare/v0.0.9...v0.0.10) (2024-12-17)
8 |
9 | ### [0.0.9](https://github.com/mastermunj/format-utils/compare/v0.0.8...v0.0.9) (2024-03-13)
10 |
11 | ### [0.0.8](https://github.com/mastermunj/format-utils/compare/v0.0.7...v0.0.8) (2022-12-15)
12 |
13 | ### [0.0.7](https://github.com/mastermunj/format-utils/compare/v0.0.6...v0.0.7) (2021-09-04)
14 |
15 | ### [0.0.6](https://github.com/mastermunj/format-utils/compare/v0.0.5...v0.0.6) (2021-03-22)
16 |
17 | ### [0.0.5](https://github.com/mastermunj/format-utils/compare/v0.0.4...v0.0.5) (2020-12-11)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * copy vpa-handles.json file to dist ([ce5cff0](https://github.com/mastermunj/format-utils/commit/ce5cff006278e3cfb277f4b7c48b125bc48de6c4))
23 |
24 | ### [0.0.4](https://github.com/mastermunj/format-utils/compare/v0.0.3...v0.0.4) (2020-09-22)
25 |
26 | ### [0.0.3](https://github.com/mastermunj/format-utils/compare/v0.0.2...v0.0.3) (2020-05-07)
27 |
28 | ### [0.0.2](https://github.com/mastermunj/format-utils/compare/v0.0.1...v0.0.2) (2020-04-28)
29 |
30 |
31 | ### Features
32 |
33 | * added VPA validation ([d843c51](https://github.com/mastermunj/format-utils/commit/d843c51214088efd990c003469786d47ac00821c))
34 |
35 | ### 0.0.1 (2020-04-27)
36 |
37 |
38 | ### Features
39 |
40 | * added AADHAAR & AADHAAR VID validations ([47a3d6d](https://github.com/mastermunj/format-utils/commit/47a3d6da1ec1ccaf19cf7388078826d55283f6bb))
41 | * added GST validation ([3f67b97](https://github.com/mastermunj/format-utils/commit/3f67b97eccb1176ed3b08f72f0fb6b43e19577fd))
42 | * added imei validator ([76f6c16](https://github.com/mastermunj/format-utils/commit/76f6c1691437557fcca955f0f634266351eeb762))
43 | * added validation for vehicle registration number ([c643d5b](https://github.com/mastermunj/format-utils/commit/c643d5b2abd7f51554ef250475dd87e96d6b2d86))
44 | * added validators for mobile & pincode ([da31838](https://github.com/mastermunj/format-utils/commit/da318380b13af975df8da269f29d85ee50899738))
45 | * added validators for PAN, TAN, UAN, IFSC and ESIC ([ac7626e](https://github.com/mastermunj/format-utils/commit/ac7626e599236822fcebc1da3a6e2ca06a9195d4))
46 |
47 |
48 | ### Bug Fixes
49 |
50 | * remove dist from gitignore and added in repo ([bc53c3b](https://github.com/mastermunj/format-utils/commit/bc53c3bffbf40f95411aefee5380e08783ff7626))
51 | * typo in package.json ([47fcf8a](https://github.com/mastermunj/format-utils/commit/47fcf8a89f025eb7a747752b4c7e33441f2223f9))
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Munjal Dhamecha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Format Validation Utilities
2 |
3 | ## Introduction
4 |
5 | Utilities for validating various formats of Indian system codes like Mobile, PAN, AADHAAR, GST and more!
6 |
7 | ## Installation
8 |
9 | ```js
10 | npm install format-utils --save
11 | ```
12 |
13 | ## Usage
14 |
15 | ```js
16 | const { Validator } = require('format-utils');
17 | ```
18 | OR
19 | ```js
20 | import { Validator } from 'format-utils';
21 | ```
22 |
23 | ## Available Validators
24 |
25 | ### Mobile
26 |
27 | A mobile is a 10 digit numerical code starting with either 6, 7, 8, 9.
28 |
29 | ```js
30 | let isValid = Validator.mobile('9876543210');
31 | // isValid = true
32 |
33 | isValid = Validator.mobile('5678943210');
34 | // isValid = false
35 | ```
36 |
37 | ### PIN (Postal Index Number)
38 |
39 | A pincode is a 6 digit numeric code used by Indian Post.
40 |
41 | #### Format
42 | * The first character is a number from `1` to `9`.
43 | * The second to sixth characters are numberical sequence from `00000` to `99999`.
44 |
45 | ```js
46 | let isValid = Validator.pincode('400001');
47 | // isValid = true
48 |
49 | isValid = Validator.pincode('0123456');
50 | // isValid = false
51 | ```
52 |
53 | ### PAN (Permanent Account Number)
54 |
55 | A PAN is a 10 digit alphanumeric code issued by Income Tax Department of India.
56 |
57 | #### Format
58 | * The first three characters are alphabetic series running from `AAA` to `ZZZ`.
59 | * The fourth character represents the status of the PAN holder.
60 | * `P` stands for Individual
61 | * `C` stands for Company
62 | * `H` stands for Hindu Undivided Family (HUF)
63 | * `A` stands for Association of Persons (AOP)
64 | * `B` stands for Body of Individuals (BOI)
65 | * `G` stands for Government Agency
66 | * `J` stands for Artificial Juridical Person
67 | * `L` stands for Local Authority
68 | * `F` stands for Firm/ Limited Liability Partnership
69 | * `T` stands for Trust
70 | * The fifth character represents the first character of the PAN holder's last name/surname in case of an individual. In case of non-individual PAN holders fifth character represents the first character of PAN holder's name.
71 | * The sixth to ninth characters are sequential numbers running from `0001` to `9999`.
72 | * The tenth character is an alphabetic check digit.
73 |
74 | Visit [this](https://www.incometaxindia.gov.in/Forms/tps/1.Permanent%20Account%20Number%20(PAN).pdf) to know more about PAN.
75 |
76 | ```js
77 | let isValid = Validator.pan('ALWPG5809L');
78 | // isValid = true
79 |
80 | isValid = Validator.pan('ABAB12345Y');
81 | // isValid = false
82 | ```
83 |
84 | ### TAN (Tax Deduction and Collection Account Number)
85 |
86 | A TAN is a 10 digit alphanumeric code.
87 |
88 | #### Format
89 | * The first four characters are alphabetic series running from `AAAA` to `ZZZZ`.
90 | * The fifth to ninth characters are sequential numbers running from `00001` to `99999`.
91 | * The tenth character is an alphabetic character.
92 |
93 | ```js
94 | let isValid = Validator.tan('RAJA99999B');
95 | // isValid = true
96 |
97 | isValid = Validator.tan('RAJA999991');
98 | // isValid = false
99 | ```
100 |
101 | ### UAN (Universal Account Number)
102 |
103 | A UAN is a 12 digit numberic code that is issued to member of the Employees’ Provident Fund Organisation (EPFO).
104 |
105 | ```js
106 | let isValid = Validator.uan('987654321098');
107 | // isValid = true
108 |
109 | isValid = Validator.uan('A98765432109');
110 | // isValid = false
111 | ```
112 |
113 | ### IFSC (Indian Financial System Code)
114 |
115 | A IFSC is a 11 digit alphanumberic code that is issued to member of the Employees’ Provident Fund Organisation (EPFO).
116 |
117 | #### Format
118 | * The first four characters are alphabets that denote the bank name.
119 | * The fifth character is numerical zero (`0`).
120 | * The sixth to eleventh characters are numerical code that denote the branch name.
121 |
122 |
123 | ```js
124 | let isValid = Validator.ifsc('SBIN0011569');
125 | // isValid = true
126 |
127 | isValid = Validator.ifsc('BK1D0006046');
128 | // isValid = false
129 | ```
130 |
131 | ### ESIC (Employee State Insurance Corporation) Code
132 |
133 | A ESIC code is a 17 digit numerical code that is issued by Employee State Insurance Corporation.
134 |
135 | ```js
136 | let isValid = Validator.esic('12345678901234567');
137 | // isValid = true
138 |
139 | isValid = Validator.esic('1234567890123456');
140 | // isValid = false
141 | ```
142 |
143 | ### IMEI (International Mobile Equipment Identity)
144 |
145 | A IMEI is a 15 digit numeric code to identify mobile phones, as well as some satellite phones.
146 | The last digit of IMEI is a Luhn check digit.
147 |
148 | ```js
149 | let isValid = Validator.imei('490154203237518');
150 | // isValid = true
151 |
152 | isValid = Validator.imei('490154203237519');
153 | // isValid = false
154 | ```
155 |
156 | ### AADHAAR
157 |
158 | Aadhaar is a 12 digit numberic code that can be obtained by residents or passport holders of India, based on their biometric and demographic data.
159 |
160 | #### Format
161 | * The first character is a number between `2` and `9`.
162 | * The second to eleventh characters are random numbers.
163 | * The twelfth character is a Verhoeff check digit.
164 |
165 | ```js
166 | let isValid = Validator.aadhaar('234567890124');
167 | // isValid = true
168 |
169 | isValid = Validator.aadhaar('187654321096');
170 | // isValid = false
171 | ```
172 |
173 | ### AADHAAR VID (Aadhaar Virtual ID)
174 |
175 | Aadhaar VID is a 16 digit numberic code that can be used instead of Aadhaar number at the time of authentication to avoid sharing of Aadhaar number.
176 | The last digit is a Verhoeff check digit.
177 |
178 | ```js
179 | let isValid = Validator.aadhaarVID('9876543210987659');
180 | // isValid = true
181 |
182 | isValid = Validator.aadhaarVID('6234897234982734');
183 | // isValid = false
184 | ```
185 |
186 | ### GSTIN (Goods & Services Tax Identification Number)
187 |
188 | A GISTIN is a 15 digit alphanumeric code assigned to a business or person registered under the GST Act.
189 |
190 | #### Format
191 | * The first two characters are numerical series from `01` to `37` denoting state code.
192 | * The third to twelfth characters are [PAN](#pan-permanent-account-number) number of the GST registered entity.
193 | * The thirteenth character is a alphabet assigned based on the number of registration within a state.
194 | * The fourteenth character is `Z` by default.
195 | * The fifteenth character is a check codeand can be an alphabet or a number.
196 |
197 | ```js
198 | let isValid = Validator.gst('22ALJPT5243L1ZS');
199 | // isValid = true
200 |
201 | isValid = Validator.gst('22ALJPT5243L1ZB');
202 | // isValid = false
203 | ```
204 |
205 | ### Vehicle Registration (Number Plate)
206 |
207 | A vehicle number plate is an alphanumeric code assigned to a vehicle registered in India.
208 |
209 | The current format of the registration index consists of 4 parts.
210 |
211 | #### Format
212 | * The first two characters are alphanumeric code of the State / Union Territory where the vehicle is registered.
213 | * The third & fourth characters the sequential number of a district.
214 | * The third part consists of one, two or three letters or no letters at all.
215 | * The fourth part is a number from 1 to 9999, unique to each plate.
216 |
217 | ```js
218 | let isValid = Validator.vehicleRegistration('DL4CAF4943');
219 | // isValid = true
220 |
221 | isValid = Validator.vehicleRegistration('DL4CAF494G');
222 | // isValid = false
223 | ```
224 |
225 | ### VPA (Virtual Payment Address)
226 |
227 | A VPA / UPI (Unified Payment Interface) ID is a unique id generated for use of UPI in India.
228 |
229 | #### Format
230 | * VPA consists of alphabets, numbers, hyphen (`-`), underscore (`_`) and dot (`.`) as part of identification.
231 | * The identification part is followed by `@` sign.
232 | * The last part is the handle of the issuer bank or PSP (Payment Service Provider).
233 | * The maximum length of VPA is 50 characters.
234 |
235 | #### Options
236 | | Option | Type | Default | Description |
237 | | ------------- | ------------- | ------------- | ------------- |
238 | | maxLength | number | 50 | Maximum length of the VPA address including `@` sign & the handle. |
239 | | handles | boolean \| string[] | false | Whether to do additional check of verifying the handle.
When it is `true` the handle part is checked against default handles listed in [vpa-handles.json](./src/vpa-handles.json).
When it is `string[]` the handle part is checked against merged list of default handles listed in [vpa-handles.json](./src/vpa-handles.json) and the ones given as input. |
240 |
241 | ```js
242 | let isValid = Validator.vpa('amazing-uid@upi');
243 | // isValid = true
244 |
245 | isValid = Validator.vpa('with@at@upi');
246 | // isValid = false
247 | ```
248 |
--------------------------------------------------------------------------------
/__tests__/luhn.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 | import { Luhn } from '../src/luhn';
3 |
4 | describe('Luhn', () => {
5 | const tests: [string, boolean][] = [
6 | ['378282246310005', true],
7 | ['371449635398431', true],
8 | ['378734493671000', true],
9 | ['5610591081018250', true],
10 | ['30569309025904', true],
11 | ['38520000023237', true],
12 | ['6011111111111117', true],
13 | ['6011000990139424', true],
14 | ['3530111333300000', true],
15 | ['3566002020360505', true],
16 | ['5555555555554444', true],
17 | ['5105105105105100', true],
18 | ['4111111111111111', true],
19 | ['4012888888881881', true],
20 | ['4222222222222', true],
21 | ['5019717010103742', true],
22 | ['6331101999990016', true],
23 |
24 | ['378282246310006', false],
25 | ['371449635398432', false],
26 | ['378734493671001', false],
27 | ['5610591081018251', false],
28 | ['30569309025905', false],
29 | ['38520000023238', false],
30 | ['6011111111111118', false],
31 | ['6011000990139425', false],
32 | ['3530111333300001', false],
33 | ['3566002020360506', false],
34 | ['5555555555554445', false],
35 | ['5105105105105101', false],
36 | ['4111111111111112', false],
37 | ['4012888888881882', false],
38 | ['4222222222223', false],
39 | ['5019717010103743', false],
40 | ['6331101999990017', false],
41 | ];
42 | test.each(tests)(`Check %s => %s`, (value, expected) => {
43 | expect(Luhn.validate(value)).toBe(expected);
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/__tests__/validator.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 | import { Validator } from '../src/validator';
3 |
4 | describe('Mobile', () => {
5 | const tests: [string, boolean][] = [
6 | ['9876543210', true],
7 | ['8765432109', true],
8 | ['7654321098', true],
9 | ['6543210987', true],
10 | ['5432109876', false],
11 | ['4321098765', false],
12 | ['3210987654', false],
13 | ['2109876543', false],
14 | ['1098765432', false],
15 | ['0987654321', false],
16 | ['987654321', false],
17 | ['a987654321', false],
18 | ['987654321a', false],
19 | ['9876 54321', false],
20 | ];
21 | test.each(tests)(`Check %s => %s`, (value, expected) => {
22 | expect(Validator.mobile(value)).toBe(expected);
23 | });
24 | });
25 |
26 | describe('Pincode', () => {
27 | const tests: [string, boolean][] = [
28 | ['403294', true],
29 | ['110014', true],
30 | ['600036', true],
31 | ['a12345', false],
32 | ['6345678', false],
33 | ['012345', false],
34 | ];
35 | test.each(tests)(`Check %s => %s`, (value, expected) => {
36 | expect(Validator.pincode(value)).toBe(expected);
37 | });
38 | });
39 |
40 | describe('PAN', () => {
41 | const tests: [string, boolean][] = [
42 | ['ALWPG5809L', true],
43 | ['alwpg5809l', true],
44 | ['ABAB12345Y', false],
45 | ['abab12345y', false],
46 | ];
47 | test.each(tests)(`Check %s => %s`, (value, expected) => {
48 | expect(Validator.pan(value)).toBe(expected);
49 | });
50 | });
51 |
52 | describe('TAN', () => {
53 | const tests: [string, boolean][] = [
54 | ['RAJA99999B', true],
55 | ['AAAA99999A', true],
56 | ['BLRW39567H', true],
57 | ['blrw39567h', true],
58 |
59 | ['RAJA999991', false],
60 | ['RAJA9999WB', false],
61 | ['R2JA9999WB', false],
62 | ['R2JA9999B', false],
63 | ['R2JA9999WB1', false],
64 | ];
65 | test.each(tests)(`Check %s => %s`, (value, expected) => {
66 | expect(Validator.tan(value)).toBe(expected);
67 | });
68 | });
69 |
70 | describe('UAN', () => {
71 | const tests: [string, boolean][] = [
72 | ['987654321098', true],
73 | ['123456789012', true],
74 |
75 | ['98765432101', false],
76 | ['9876543210987', false],
77 | ['A98765432109', false],
78 | ['98765432109A', false],
79 | ];
80 | test.each(tests)(`Check %s => %s`, (value, expected) => {
81 | expect(Validator.uan(value)).toBe(expected);
82 | });
83 | });
84 |
85 | describe('IFSC', () => {
86 | const tests: [string, boolean][] = [
87 | ['SBIN0011569', true],
88 | ['BKID0006046', true],
89 | ['BKID000604D', true],
90 |
91 | ['BKID000604', false],
92 | ['BKID1006046', false],
93 | ['BK1D0006046', false],
94 | ['BKID00060461', false],
95 | ['BKID0006046D', false],
96 | ];
97 | test.each(tests)(`Check %s => %s`, (value, expected) => {
98 | expect(Validator.ifsc(value)).toBe(expected);
99 | });
100 | });
101 |
102 | describe('ESIC', () => {
103 | const tests: [string, boolean][] = [
104 | ['12345678901234567', true],
105 |
106 | ['123456789012345678', false],
107 | ['1234567890123456', false],
108 | ['A2345678901234567', false],
109 | ];
110 | test.each(tests)(`Check %s => %s`, (value, expected) => {
111 | expect(Validator.esic(value)).toBe(expected);
112 | });
113 | });
114 |
115 | describe('IMEI', () => {
116 | const tests: [string, boolean][] = [
117 | ['49-015420-323751-8', true],
118 | ['490154203237518', true],
119 | ['356938035643809', true],
120 |
121 | ['49-015420-323751-9', false],
122 | ['490154203237519', false],
123 | ['4901542032375191', false],
124 | ['49015420323751', false],
125 | ['a49015420323751', false],
126 | ['49015420323751a', false],
127 | ];
128 | test.each(tests)(`Check %s => %s`, (value, expected) => {
129 | expect(Validator.imei(value)).toBe(expected);
130 | });
131 | });
132 |
133 | describe('Aadhaar', () => {
134 | const tests: [string, boolean][] = [
135 | ['234567890124', true],
136 | ['987654321096', true],
137 |
138 | ['a87654321096', false],
139 | ['98765432109a', false],
140 | ['087654321096', false],
141 | ['187654321096', false],
142 | ['9876543210967', false],
143 | ['98765432109', false],
144 | ];
145 | test.each(tests)(`Check %s => %s`, (value, expected) => {
146 | expect(Validator.aadhaar(value)).toBe(expected);
147 | });
148 | });
149 |
150 | describe('Aadhaar VID', () => {
151 | const tests: [string, boolean][] = [
152 | ['9876543210987659', true],
153 | ['6234897234982733', true],
154 |
155 | ['9876543210987656', false],
156 | ['6234897234982734', false],
157 | ['a876543210987659', false],
158 | ['987654321098765a', false],
159 | ['987654321098765', false],
160 | ['98765432109876591', false],
161 | ];
162 | test.each(tests)(`Check %s => %s`, (value, expected) => {
163 | expect(Validator.aadhaarVID(value)).toBe(expected);
164 | });
165 | });
166 |
167 | describe('GST', () => {
168 | const tests: [string, boolean][] = [
169 | ['22ALJPT5243L1ZS', true],
170 | ['18AABCT3518Q1ZV', true],
171 | ['37AADCB2230M2ZR', true],
172 |
173 | ['22ALJPT5243L1ZB', false],
174 | ['38AABCT3518Q1ZV', false],
175 | ['47AADCB2230M2ZR', false],
176 | ['47AADCB2230M2ZRT', false],
177 | ['47AADCB2230M2Z', false],
178 | ['47AAD CB2230M2Z', false],
179 | ['47AAD-CB2230M2Z', false],
180 | ];
181 | test.each(tests)(`Check %s => %s`, (value, expected) => {
182 | expect(Validator.gst(value)).toBe(expected);
183 | });
184 |
185 | test.each(tests)(`Check GST Checksum %s => %s`, (value, expected) => {
186 | expect(Validator.gstChecksum(value)).toBe(expected);
187 | });
188 | });
189 |
190 | describe('Vehicle Registration Number', () => {
191 | const tests: [string, boolean][] = [
192 | ['DL4CAF4943', true],
193 | ['GJ5CL2213', true],
194 | ['GJ 5 CL 2213', true],
195 | ['KL 01 CK 1', true],
196 | ['TN 58 N 4006', true],
197 |
198 | ['DL4CAF494G', false],
199 | ['DL4CANF4943', false],
200 | ['1DL4CAF4943', false],
201 | ];
202 | test.each(tests)(`Check %s => %s`, (value, expected) => {
203 | expect(Validator.vehicleRegistration(value)).toBe(expected);
204 | });
205 | });
206 |
207 | describe('VPA', () => {
208 | const tests: [string, boolean][] = [
209 | ['amazing-uid@upi', true],
210 | ['random9999@upi', true],
211 |
212 | ['axisbank@bankaxis', false],
213 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
214 | ];
215 | test.each(tests)(`Check %s => %s`, (value, expected) => {
216 | expect(Validator.vpa(value)).toBe(expected);
217 | });
218 | });
219 |
--------------------------------------------------------------------------------
/__tests__/verhoeff.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 | import { Verhoeff } from '../src/verhoeff';
3 |
4 | describe('Verhoeff', () => {
5 | const tests: [string, boolean][] = [
6 | ['98765432101234567897', true],
7 | ['45908745543341', true],
8 |
9 | ['98765432101234567896', false],
10 | ['a98765432101234567897', false],
11 | ['45908745543343', false],
12 | ];
13 | test.each(tests)(`Check %s => %s`, (value, expected) => {
14 | expect(Verhoeff.validate(value)).toBe(expected);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/__tests__/vpa.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 | import { VPA } from '../src/vpa';
3 |
4 | describe('VPA', () => {
5 | const tests: [string, boolean][] = [
6 | ['amazing-uid@upi', true],
7 | ['random9999@upi', true],
8 | ['vpa_with_underscore@upi', true],
9 | ['vpa-with-hyphen@upi', true],
10 | ['vpa.with.dot@upi', true],
11 | ['9876543210@upi', true],
12 |
13 | ['it@upi', false],
14 | ['with#hash@upi', false],
15 | ['with space@upi', false],
16 | ['with@at@upi', false],
17 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
18 | ];
19 | test.each(tests)(`Check %s => %s`, (value, expected) => {
20 | expect(VPA.validate(value)).toBe(expected);
21 | });
22 | });
23 |
24 | describe('VPA with options: { maxLength: 20 }', () => {
25 | const tests: [string, boolean][] = [
26 | ['amazing-uid@upi', true],
27 | ['random9999@upi', true],
28 |
29 | ['vpa_with_underscore@upi', false],
30 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
31 | ];
32 | test.each(tests)(`Check %s => %s`, (value, expected) => {
33 | expect(VPA.validate(value, { maxLength: 20 })).toBe(expected);
34 | });
35 | });
36 |
37 | describe('VPA with options: { handles: true }', () => {
38 | const tests: [string, boolean][] = [
39 | ['amazing-uid@upi', true],
40 | ['random9999@upi', true],
41 | ['yesbank@ybl', true],
42 | ['axisbank@okaxis', true],
43 |
44 | ['axisbank@bankaxis', false],
45 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
46 | ];
47 | test.each(tests)(`Check %s => %s`, (value, expected) => {
48 | expect(VPA.validate(value, { handles: true })).toBe(expected);
49 | });
50 | });
51 |
52 | describe('VPA with options: { handles: ["bankaxis", "superbank"] }', () => {
53 | const tests: [string, boolean][] = [
54 | ['amazing-uid@upi', true],
55 | ['random9999@upi', true],
56 | ['yesbank@ybl', true],
57 | ['axisbank@okaxis', true],
58 | ['axisbank@bankaxis', true],
59 | ['super-vpa@superbank', true],
60 |
61 | ['super-vpa@banksuper', false],
62 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
63 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@superbank', false],
64 | ];
65 | const handles = ['bankaxis', 'superbank'];
66 | test.each(tests)(`Check %s => %s`, (value, expected) => {
67 | expect(VPA.validate(value, { handles })).toBe(expected);
68 | });
69 | });
70 |
71 | describe('VPA with options: { maxLength: 20, handles: ["bankaxis", "superbank"] }', () => {
72 | const tests: [string, boolean][] = [
73 | ['amazing-uid@upi', true],
74 | ['random9999@upi', true],
75 | ['yesbank@ybl', true],
76 | ['axisbank@okaxis', true],
77 | ['axisbank@bankaxis', true],
78 | ['super-vpa@superbank', true],
79 |
80 | ['super-vpa@banksuper', false],
81 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
82 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@superbank', false],
83 | ];
84 | const handles = ['bankaxis', 'superbank'];
85 | test.each(tests)(`Check %s => %s`, (value, expected) => {
86 | expect(VPA.validate(value, { maxLength: 20, handles })).toBe(expected);
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/commitlint.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
--------------------------------------------------------------------------------
/dist/luhn.d.ts:
--------------------------------------------------------------------------------
1 | export declare class Luhn {
2 | static validate(value: string): boolean;
3 | }
4 |
--------------------------------------------------------------------------------
/dist/luhn.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.Luhn = void 0;
4 | class Luhn {
5 | static validate(value) {
6 | const digits = [...value.replace(/\s/g, '')].reverse();
7 | const sum = digits
8 | .map((digit, index) => {
9 | digit = parseInt(digit, 10) * (index % 2 !== 0 ? 2 : 1);
10 | return digit > 9 ? digit - 9 : digit;
11 | })
12 | .reduce((prev, current) => {
13 | return prev + current;
14 | });
15 | return sum > 0 && sum % 10 === 0;
16 | }
17 | }
18 | exports.Luhn = Luhn;
19 |
--------------------------------------------------------------------------------
/dist/validator.d.ts:
--------------------------------------------------------------------------------
1 | import { VpaValidationOptions } from './vpa';
2 | export declare class Validator {
3 | static mobile(value: string): boolean;
4 | static pincode(value: string | number): boolean;
5 | static pan(value: string): boolean;
6 | static tan(value: string): boolean;
7 | static uan(value: string): boolean;
8 | static ifsc(value: string): boolean;
9 | static esic(value: string): boolean;
10 | static imei(value: string): boolean;
11 | static aadhaar(value: string): boolean;
12 | static aadhaarVID(value: string): boolean;
13 | static gst(value: string): boolean;
14 | static gstChecksum(value: string): boolean;
15 | static vehicleRegistration(value: string): boolean;
16 | static vpa(value: string, options?: VpaValidationOptions): boolean;
17 | }
18 |
--------------------------------------------------------------------------------
/dist/validator.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.Validator = void 0;
4 | const luhn_1 = require("./luhn");
5 | const verhoeff_1 = require("./verhoeff");
6 | const vpa_1 = require("./vpa");
7 | class Validator {
8 | static mobile(value) {
9 | return /^[6789]\d{9}$/.test(value);
10 | }
11 | static pincode(value) {
12 | return /^[1-9]\d{5}$/.test(value);
13 | }
14 | static pan(value) {
15 | return /^[A-Z]{3}[PCHABGJLFTE][A-Z]\d{4}[A-Z]$/i.test(value);
16 | }
17 | static tan(value) {
18 | return /^[A-Z]{4}\d{5}[A-Z]$/i.test(value);
19 | }
20 | static uan(value) {
21 | return /^\d{12}$/.test(value);
22 | }
23 | static ifsc(value) {
24 | return /^[A-Z]{4}0[A-Z0-9]{6}$/i.test(value);
25 | }
26 | static esic(value) {
27 | return /^\d{17}$/.test(value);
28 | }
29 | static imei(value) {
30 | value = value.replace(/[\s-]+/g, '');
31 | return /^\d{15}$/.test(value) && luhn_1.Luhn.validate(value);
32 | }
33 | static aadhaar(value) {
34 | value = value.replace(/[\s-]+/g, '');
35 | return /^[2-9]\d{11}$/.test(value) && verhoeff_1.Verhoeff.validate(value);
36 | }
37 | static aadhaarVID(value) {
38 | value = value.replace(/[\s-]+/g, '');
39 | return /^\d{16}$/.test(value) && verhoeff_1.Verhoeff.validate(value);
40 | }
41 | static gst(value) {
42 | const regex = /^([0-2][0-9]|[3][0-7])[A-Z]{3}[ABCFGHLJPTK][A-Z]\d{4}[A-Z][A-Z0-9][Z][A-Z0-9]$/i;
43 | return regex.test(value) && Validator.gstChecksum(value);
44 | }
45 | static gstChecksum(value) {
46 | if (!/^[0-9A-Z]{15}$/i.test(value)) {
47 | return false;
48 | }
49 | const chars = [...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'];
50 | const values = [...value.toUpperCase()];
51 | const lastChar = values.pop();
52 | const sum = values
53 | .map((char, index) => {
54 | const product = chars.indexOf(char) * (index % 2 !== 0 ? 2 : 1);
55 | return Math.floor(product / chars.length) + (product % chars.length);
56 | })
57 | .reduce((prev, current) => {
58 | return prev + current;
59 | });
60 | const checksum = (chars.length - (sum % chars.length)) % chars.length;
61 | return chars[checksum] === lastChar;
62 | }
63 | static vehicleRegistration(value) {
64 | const regex = /^[A-Z]{2}[\s-.]?[0-9]{1,2}[\s-.]?[0-9A-Z]{1,3}[\s-.]?[0-9]{1,4}$/i;
65 | return regex.test(value);
66 | }
67 | static vpa(value, options) {
68 | return vpa_1.VPA.validate(value, options);
69 | }
70 | }
71 | exports.Validator = Validator;
72 |
--------------------------------------------------------------------------------
/dist/verhoeff.d.ts:
--------------------------------------------------------------------------------
1 | export declare class Verhoeff {
2 | static multiplication: number[][];
3 | static permutation: number[][];
4 | static inverse: number[];
5 | static validate(value: string): boolean;
6 | }
7 |
--------------------------------------------------------------------------------
/dist/verhoeff.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.Verhoeff = void 0;
4 | class Verhoeff {
5 | static validate(value) {
6 | const digits = [...value.replace(/\s/g, '')].reverse().map((digit) => parseInt(digit, 10));
7 | const checksum = digits.reduce((prev, current, index) => {
8 | return Verhoeff.multiplication[prev][Verhoeff.permutation[index % 8][current]];
9 | }, 0);
10 | return checksum === 0;
11 | }
12 | }
13 | exports.Verhoeff = Verhoeff;
14 | Verhoeff.multiplication = [
15 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
16 | [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
17 | [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
18 | [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
19 | [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
20 | [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
21 | [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
22 | [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
23 | [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
24 | [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
25 | ];
26 | Verhoeff.permutation = [
27 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
28 | [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
29 | [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
30 | [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
31 | [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
32 | [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
33 | [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
34 | [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
35 | ];
36 | Verhoeff.inverse = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];
37 |
--------------------------------------------------------------------------------
/dist/vpa-handles.json:
--------------------------------------------------------------------------------
1 | [
2 | "abfspay",
3 | "airtel",
4 | "airtelpaymentsbank",
5 | "albk",
6 | "allahabadbank",
7 | "allbank",
8 | "andb",
9 | "apb",
10 | "apl",
11 | "axis",
12 | "axisb",
13 | "axisbank",
14 | "axisgo",
15 | "bandhan",
16 | "barodampay",
17 | "barodapay",
18 | "boi",
19 | "cbin",
20 | "cboi",
21 | "centralbank",
22 | "cmsidfc",
23 | "cnrb",
24 | "csbcash",
25 | "csbpay",
26 | "cub",
27 | "dbs",
28 | "dcb",
29 | "dcbbank",
30 | "denabank",
31 | "dlb",
32 | "eazypay",
33 | "equitas",
34 | "ezeepay",
35 | "fbl",
36 | "federal",
37 | "finobank",
38 | "hdfcbank",
39 | "hdfcbankjd",
40 | "hsbc",
41 | "icici",
42 | "idbi",
43 | "idbibank",
44 | "idfc",
45 | "idfcbank",
46 | "idfcnetc",
47 | "ikwik",
48 | "imobile",
49 | "indbank",
50 | "indianbank",
51 | "indianbk",
52 | "indus",
53 | "indusind",
54 | "iob",
55 | "jkb",
56 | "jsbp",
57 | "karb",
58 | "karurvysyabank",
59 | "kaypay",
60 | "kbl",
61 | "kbl052",
62 | "kmb",
63 | "kmbl",
64 | "kotak",
65 | "kvb",
66 | "kvbank",
67 | "lime",
68 | "lvb",
69 | "lvbank",
70 | "mahb",
71 | "myicici",
72 | "obc",
73 | "okaxis",
74 | "okhdfcbank",
75 | "okicici",
76 | "oksbi",
77 | "paytm",
78 | "payzapp",
79 | "pingpay",
80 | "pnb",
81 | "pockets",
82 | "psb",
83 | "purz",
84 | "rajgovhdfcbank",
85 | "rbl",
86 | "sbi",
87 | "sc",
88 | "scb",
89 | "scbl",
90 | "scmobile",
91 | "sib",
92 | "srcb",
93 | "synd",
94 | "syndbank",
95 | "syndicate",
96 | "tjsb",
97 | "ubi",
98 | "uboi",
99 | "uco",
100 | "unionbank",
101 | "unionbankofindia",
102 | "united",
103 | "utbi",
104 | "vijayabank",
105 | "vijb",
106 | "vjb",
107 | "ybl",
108 | "yesbank",
109 | "yesbankltd",
110 | "freecharge",
111 | "upi"
112 | ]
113 |
--------------------------------------------------------------------------------
/dist/vpa.d.ts:
--------------------------------------------------------------------------------
1 | export type VpaValidationOptions = {
2 | maxLength?: number;
3 | handles?: boolean | string[];
4 | };
5 | export declare class VPA {
6 | static defaultVpaHandles: string[] | undefined;
7 | static validate(value: string, options?: VpaValidationOptions): boolean;
8 | }
9 |
--------------------------------------------------------------------------------
/dist/vpa.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.VPA = void 0;
7 | const ValidationOptionsDefaults = {
8 | maxLength: 50,
9 | handles: true,
10 | };
11 | const vpa_handles_json_1 = __importDefault(require("./vpa-handles.json"));
12 | class VPA {
13 | static validate(value, options) {
14 | options = Object.assign({}, ValidationOptionsDefaults, options);
15 | const regex = /^[a-z0-9_.-]{3,}@[a-z]{3,}$/i;
16 | let isValidFormat = regex.test(value) && value.length <= options.maxLength;
17 | if (!isValidFormat) {
18 | return false;
19 | }
20 | if (options.handles) {
21 | options.handles = (options.handles === true ? vpa_handles_json_1.default : [...options.handles, ...vpa_handles_json_1.default]);
22 | const handle = value.split('@')[1];
23 | isValidFormat = options.handles.indexOf(handle) >= 0;
24 | }
25 | return isValidFormat;
26 | }
27 | }
28 | exports.VPA = VPA;
29 | VPA.defaultVpaHandles = undefined;
30 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import typescriptEslint from '@typescript-eslint/eslint-plugin';
2 | import globals from 'globals';
3 | import tsParser from '@typescript-eslint/parser';
4 | import path from 'node:path';
5 | import { fileURLToPath } from 'node:url';
6 | import js from '@eslint/js';
7 | import { FlatCompat } from '@eslint/eslintrc';
8 |
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = path.dirname(__filename);
11 | const compat = new FlatCompat({
12 | baseDirectory: __dirname,
13 | recommendedConfig: js.configs.recommended,
14 | allConfig: js.configs.all,
15 | });
16 |
17 | export default [
18 | ...compat.extends(
19 | 'eslint:recommended',
20 | 'plugin:@typescript-eslint/eslint-recommended',
21 | 'plugin:@typescript-eslint/recommended',
22 | 'prettier',
23 | 'plugin:prettier/recommended',
24 | ),
25 | {
26 | plugins: {
27 | '@typescript-eslint': typescriptEslint,
28 | },
29 |
30 | languageOptions: {
31 | globals: {
32 | ...globals.node,
33 | ...globals.commonjs,
34 | ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, 'off'])),
35 | },
36 |
37 | parser: tsParser,
38 | ecmaVersion: 2018,
39 | sourceType: 'module',
40 | },
41 | files: ['**/*.ts'],
42 | },
43 | {
44 | ignores: ['node_modules', 'dist', 'coverage', '**/*.d.ts', '**/*.js', '**/*.cjs'],
45 | },
46 | ];
47 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | collectCoverage: true,
5 | coverageDirectory: 'coverage',
6 | coverageProvider: 'v8',
7 | verbose: true,
8 | roots: [''],
9 | testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
10 | transform: {
11 | '^.+\\.(ts|tsx)$': 'ts-jest',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'package.json': 'sort-package-json',
3 | '*.{ts,tsx}': 'eslint --no-warn-ignored --max-warnings=0 . --fix',
4 | '**/*.ts?(x)': () => 'tsc -p tsconfig.json',
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "format-utils",
3 | "version": "0.0.11",
4 | "description": "Utilities for validating various formats of Indian system codes like Mobile, PAN, AADHAAR, GST and more!",
5 | "keywords": [
6 | "format validators",
7 | "mobile",
8 | "pincode",
9 | "PAN",
10 | "TAN",
11 | "UAN",
12 | "IFSC",
13 | "ESIC",
14 | "IMEI",
15 | "AADHAAR",
16 | "AADHAR",
17 | "AADHAAR VID",
18 | "GST",
19 | "GSTIN",
20 | "vehicle registration",
21 | "vpa",
22 | "upi",
23 | "upi id",
24 | "india",
25 | "validators",
26 | "validation"
27 | ],
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/mastermunj/format-utils.git"
31 | },
32 | "license": "MIT",
33 | "author": "Munjal Dhamecha",
34 | "main": "dist/validator.js",
35 | "files": [
36 | "dist/*"
37 | ],
38 | "scripts": {
39 | "build": "npm run clean && tsc --noEmitOnError",
40 | "build:watch": "npm run build -- --watch",
41 | "clean": "rimraf dist coverage",
42 | "commit": "git-cz",
43 | "lint": "eslint --max-warnings=0 .",
44 | "lint:fix": "npm run lint -- --fix",
45 | "prepare": "husky",
46 | "release": "standard-version",
47 | "release:mock": "npm run release -- --dry-run",
48 | "test": "vitest",
49 | "test:watch": "npm run test -- --watch"
50 | },
51 | "config": {
52 | "commitizen": {
53 | "path": "./node_modules/cz-conventional-changelog"
54 | }
55 | },
56 | "devDependencies": {
57 | "@commitlint/cli": "^19.8.0",
58 | "@commitlint/config-conventional": "^19.8.0",
59 | "@types/node": "^22.13.10",
60 | "@typescript-eslint/eslint-plugin": "^8.26.1",
61 | "@typescript-eslint/parser": "^8.26.1",
62 | "@vitest/coverage-v8": "^3.0.9",
63 | "cpy-cli": "^5.0.0",
64 | "cz-conventional-changelog": "^3.3.0",
65 | "eslint": "^9.22.0",
66 | "eslint-config-prettier": "^10.1.1",
67 | "eslint-plugin-prettier": "^5.2.3",
68 | "husky": "^9.1.7",
69 | "lint-staged": "^16.0.0",
70 | "prettier": "^3.5.3",
71 | "rimraf": "^6.0.1",
72 | "sort-package-json": "^3.0.0",
73 | "standard-version": "^9.5.0",
74 | "typescript": "^5.8.2",
75 | "vitest": "^3.0.9"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/luhn.ts:
--------------------------------------------------------------------------------
1 | export class Luhn {
2 | static validate(value: string): boolean {
3 | const digits = [...value.replace(/\s/g, '')].reverse();
4 |
5 | const sum = digits
6 | .map((digit: number | string, index) => {
7 | digit = parseInt(digit as string, 10) * (index % 2 !== 0 ? 2 : 1);
8 | return digit > 9 ? digit - 9 : digit;
9 | })
10 | .reduce((prev, current) => {
11 | return prev + current;
12 | });
13 |
14 | return sum > 0 && sum % 10 === 0;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/validator.ts:
--------------------------------------------------------------------------------
1 | import { Luhn } from './luhn';
2 | import { Verhoeff } from './verhoeff';
3 | import { VPA, VpaValidationOptions } from './vpa';
4 |
5 | export class Validator {
6 | static mobile(value: string): boolean {
7 | return /^[6789]\d{9}$/.test(value);
8 | }
9 |
10 | static pincode(value: string | number): boolean {
11 | return /^[1-9]\d{5}$/.test(value as string);
12 | }
13 |
14 | static pan(value: string): boolean {
15 | return /^[A-Z]{3}[PCHABGJLFTE][A-Z]\d{4}[A-Z]$/i.test(value);
16 | }
17 |
18 | static tan(value: string): boolean {
19 | return /^[A-Z]{4}\d{5}[A-Z]$/i.test(value);
20 | }
21 |
22 | static uan(value: string): boolean {
23 | return /^\d{12}$/.test(value);
24 | }
25 |
26 | static ifsc(value: string): boolean {
27 | return /^[A-Z]{4}0[A-Z0-9]{6}$/i.test(value);
28 | }
29 |
30 | static esic(value: string): boolean {
31 | return /^\d{17}$/.test(value);
32 | }
33 |
34 | static imei(value: string): boolean {
35 | value = value.replace(/[\s-]+/g, '');
36 | return /^\d{15}$/.test(value) && Luhn.validate(value);
37 | }
38 |
39 | static aadhaar(value: string): boolean {
40 | value = value.replace(/[\s-]+/g, '');
41 | return /^[2-9]\d{11}$/.test(value) && Verhoeff.validate(value);
42 | }
43 |
44 | static aadhaarVID(value: string): boolean {
45 | value = value.replace(/[\s-]+/g, '');
46 | return /^\d{16}$/.test(value) && Verhoeff.validate(value);
47 | }
48 |
49 | static gst(value: string): boolean {
50 | const regex = /^([0-2][0-9]|[3][0-7])[A-Z]{3}[ABCFGHLJPTK][A-Z]\d{4}[A-Z][A-Z0-9][Z][A-Z0-9]$/i;
51 | return regex.test(value) && Validator.gstChecksum(value);
52 | }
53 |
54 | static gstChecksum(value: string): boolean {
55 | if (!/^[0-9A-Z]{15}$/i.test(value)) {
56 | return false;
57 | }
58 | const chars = [...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'];
59 | const values = [...value.toUpperCase()];
60 | const lastChar = values.pop();
61 | const sum = values
62 | .map((char, index) => {
63 | const product = chars.indexOf(char) * (index % 2 !== 0 ? 2 : 1);
64 | return Math.floor(product / chars.length) + (product % chars.length);
65 | })
66 | .reduce((prev, current) => {
67 | return prev + current;
68 | });
69 | const checksum = (chars.length - (sum % chars.length)) % chars.length;
70 | return chars[checksum] === lastChar;
71 | }
72 |
73 | static vehicleRegistration(value: string): boolean {
74 | const regex = /^[A-Z]{2}[\s-.]?[0-9]{1,2}[\s-.]?[0-9A-Z]{1,3}[\s-.]?[0-9]{1,4}$/i;
75 | return regex.test(value);
76 | }
77 |
78 | static vpa(value: string, options?: VpaValidationOptions): boolean {
79 | return VPA.validate(value, options);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/verhoeff.ts:
--------------------------------------------------------------------------------
1 | export class Verhoeff {
2 | static multiplication = [
3 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
4 | [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
5 | [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
6 | [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
7 | [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
8 | [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
9 | [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
10 | [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
11 | [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
12 | [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
13 | ];
14 |
15 | static permutation = [
16 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
17 | [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
18 | [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
19 | [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
20 | [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
21 | [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
22 | [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
23 | [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
24 | ];
25 |
26 | static inverse = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];
27 |
28 | static validate(value: string): boolean {
29 | const digits = [...value.replace(/\s/g, '')].reverse().map((digit) => parseInt(digit, 10));
30 |
31 | const checksum = digits.reduce((prev, current, index) => {
32 | return Verhoeff.multiplication[prev][Verhoeff.permutation[index % 8][current]];
33 | }, 0);
34 |
35 | return checksum === 0;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/vpa-handles.json:
--------------------------------------------------------------------------------
1 | [
2 | "abfspay",
3 | "airtel",
4 | "airtelpaymentsbank",
5 | "albk",
6 | "allahabadbank",
7 | "allbank",
8 | "andb",
9 | "apb",
10 | "apl",
11 | "axis",
12 | "axisb",
13 | "axisbank",
14 | "axisgo",
15 | "bandhan",
16 | "barodampay",
17 | "barodapay",
18 | "boi",
19 | "cbin",
20 | "cboi",
21 | "centralbank",
22 | "cmsidfc",
23 | "cnrb",
24 | "csbcash",
25 | "csbpay",
26 | "cub",
27 | "dbs",
28 | "dcb",
29 | "dcbbank",
30 | "denabank",
31 | "dlb",
32 | "eazypay",
33 | "equitas",
34 | "ezeepay",
35 | "fbl",
36 | "federal",
37 | "finobank",
38 | "hdfcbank",
39 | "hdfcbankjd",
40 | "hsbc",
41 | "icici",
42 | "idbi",
43 | "idbibank",
44 | "idfc",
45 | "idfcbank",
46 | "idfcnetc",
47 | "ikwik",
48 | "imobile",
49 | "indbank",
50 | "indianbank",
51 | "indianbk",
52 | "indus",
53 | "indusind",
54 | "iob",
55 | "jkb",
56 | "jsbp",
57 | "karb",
58 | "karurvysyabank",
59 | "kaypay",
60 | "kbl",
61 | "kbl052",
62 | "kmb",
63 | "kmbl",
64 | "kotak",
65 | "kvb",
66 | "kvbank",
67 | "lime",
68 | "lvb",
69 | "lvbank",
70 | "mahb",
71 | "myicici",
72 | "obc",
73 | "okaxis",
74 | "okhdfcbank",
75 | "okicici",
76 | "oksbi",
77 | "paytm",
78 | "payzapp",
79 | "pingpay",
80 | "pnb",
81 | "pockets",
82 | "psb",
83 | "purz",
84 | "rajgovhdfcbank",
85 | "rbl",
86 | "sbi",
87 | "sc",
88 | "scb",
89 | "scbl",
90 | "scmobile",
91 | "sib",
92 | "srcb",
93 | "synd",
94 | "syndbank",
95 | "syndicate",
96 | "tjsb",
97 | "ubi",
98 | "uboi",
99 | "uco",
100 | "unionbank",
101 | "unionbankofindia",
102 | "united",
103 | "utbi",
104 | "vijayabank",
105 | "vijb",
106 | "vjb",
107 | "ybl",
108 | "yesbank",
109 | "yesbankltd",
110 | "freecharge",
111 | "upi"
112 | ]
--------------------------------------------------------------------------------
/src/vpa.ts:
--------------------------------------------------------------------------------
1 | export type VpaValidationOptions = {
2 | maxLength?: number;
3 | handles?: boolean | string[];
4 | };
5 |
6 | const ValidationOptionsDefaults: VpaValidationOptions = {
7 | maxLength: 50,
8 | handles: true,
9 | };
10 |
11 | import defaultVpaHandles from './vpa-handles.json';
12 |
13 | export class VPA {
14 | static defaultVpaHandles: string[] | undefined = undefined;
15 |
16 | static validate(value: string, options?: VpaValidationOptions): boolean {
17 | options = Object.assign({}, ValidationOptionsDefaults, options);
18 |
19 | const regex = /^[a-z0-9_.-]{3,}@[a-z]{3,}$/i;
20 | let isValidFormat = regex.test(value) && value.length <= (options.maxLength as number);
21 | if (!isValidFormat) {
22 | return false;
23 | }
24 |
25 | if (options.handles) {
26 | options.handles = (
27 | options.handles === true ? defaultVpaHandles : [...options.handles, ...defaultVpaHandles]
28 | ) as string[];
29 | const handle = value.split('@')[1];
30 | isValidFormat = options.handles.indexOf(handle) >= 0;
31 | }
32 | return isValidFormat;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "outDir": "./dist",
6 | "rootDir": "./src",
7 | "declaration": true,
8 |
9 | "strict": true,
10 | "noImplicitAny": true,
11 | "strictNullChecks": true,
12 | "strictFunctionTypes": true,
13 | "strictBindCallApply": true,
14 | "strictPropertyInitialization": true,
15 | "noImplicitThis": true,
16 | "alwaysStrict": true,
17 |
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noImplicitReturns": true,
21 | "noFallthroughCasesInSwitch": true,
22 |
23 | "esModuleInterop": true,
24 |
25 | "forceConsistentCasingInFileNames": true,
26 | "resolveJsonModule": true
27 | },
28 | "exclude": [
29 | "node_modules",
30 | "__tests__"
31 | ],
32 | "include": [
33 | "./src/**/*"
34 | ]
35 | }
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | include: ['**/*.test.ts'],
6 | watch: false,
7 | cache: false,
8 | coverage: {
9 | enabled: true,
10 | include: ['src/**'],
11 | },
12 | },
13 | });
14 |
--------------------------------------------------------------------------------