@hy_ong/zod-kit 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +24 -0
- package/CLAUDE.md +64 -22
- package/dist/chunk-2SWEVDFZ.js +134 -0
- package/dist/chunk-32JI34CV.cjs +146 -0
- package/dist/chunk-42C5OHRK.js +71 -0
- package/dist/chunk-46VAH2BJ.js +160 -0
- package/dist/chunk-5JGTDL3Y.js +87 -0
- package/dist/chunk-5LEXCVLX.js +257 -0
- package/dist/chunk-6AAP4LPF.js +2606 -0
- package/dist/chunk-B4EZYZOK.cjs +215 -0
- package/dist/chunk-COYKBWTI.js +161 -0
- package/dist/chunk-DFJZ3NS2.cjs +151 -0
- package/dist/chunk-EDHT4LPO.js +118 -0
- package/dist/chunk-EGHL277K.cjs +165 -0
- package/dist/chunk-ERH4NIMU.cjs +69 -0
- package/dist/chunk-FM3EZ72O.js +165 -0
- package/dist/chunk-GJIRDBZJ.cjs +90 -0
- package/dist/chunk-H2XTEM4M.js +696 -0
- package/dist/chunk-HMSM6FFA.cjs +181 -0
- package/dist/chunk-HTEHINI7.cjs +177 -0
- package/dist/chunk-JOLSGZGN.cjs +696 -0
- package/dist/chunk-JXY7APBU.js +69 -0
- package/dist/chunk-K2UOY6TB.js +136 -0
- package/dist/chunk-KFOHKTFD.js +61 -0
- package/dist/chunk-L4HSIKTU.cjs +135 -0
- package/dist/chunk-LH7ZB4BK.js +124 -0
- package/dist/chunk-LL4ZWLGO.js +90 -0
- package/dist/chunk-M6MTP3NY.cjs +99 -0
- package/dist/chunk-MHJFYYGV.js +215 -0
- package/dist/chunk-MINMXGW3.js +135 -0
- package/dist/chunk-MM7IL2RG.js +181 -0
- package/dist/chunk-OPQJWHXN.cjs +301 -0
- package/dist/chunk-ORFHDJII.cjs +136 -0
- package/dist/chunk-ORVV4MCF.cjs +87 -0
- package/dist/chunk-QICQ6YEY.js +75 -0
- package/dist/chunk-RKUQREMW.js +127 -0
- package/dist/chunk-RO47DKQG.js +146 -0
- package/dist/chunk-RRPXIRTQ.cjs +257 -0
- package/dist/chunk-RYFG2GKM.cjs +118 -0
- package/dist/chunk-STNHTRG7.cjs +124 -0
- package/dist/chunk-TFGS34VD.cjs +71 -0
- package/dist/chunk-TQXDUMML.cjs +61 -0
- package/dist/chunk-UBK3VCVH.cjs +134 -0
- package/dist/chunk-UCOXAZJF.cjs +2606 -0
- package/dist/chunk-UQZKFAFX.js +130 -0
- package/dist/chunk-VB2KV2ZM.cjs +130 -0
- package/dist/chunk-WABKPFPK.js +151 -0
- package/dist/chunk-WDI4QJMQ.js +177 -0
- package/dist/chunk-YDH3L27K.cjs +127 -0
- package/dist/chunk-YIM3D2AD.js +99 -0
- package/dist/chunk-YPSEIDUR.cjs +160 -0
- package/dist/chunk-ZNJLWJX3.cjs +75 -0
- package/dist/chunk-ZTFCJCPO.cjs +161 -0
- package/dist/chunk-ZXUMK2RR.js +301 -0
- package/dist/common/boolean.cjs +7 -0
- package/dist/common/boolean.d.cts +119 -0
- package/dist/common/boolean.d.ts +119 -0
- package/dist/common/boolean.js +7 -0
- package/dist/common/color.cjs +9 -0
- package/dist/common/color.d.cts +26 -0
- package/dist/common/color.d.ts +26 -0
- package/dist/common/color.js +9 -0
- package/dist/common/coordinate.cjs +11 -0
- package/dist/common/coordinate.d.cts +23 -0
- package/dist/common/coordinate.d.ts +23 -0
- package/dist/common/coordinate.js +11 -0
- package/dist/common/credit-card.cjs +11 -0
- package/dist/common/credit-card.d.cts +22 -0
- package/dist/common/credit-card.d.ts +22 -0
- package/dist/common/credit-card.js +11 -0
- package/dist/common/date.cjs +7 -0
- package/dist/common/date.d.cts +174 -0
- package/dist/common/date.d.ts +174 -0
- package/dist/common/date.js +7 -0
- package/dist/common/datetime.cjs +15 -0
- package/dist/common/datetime.d.cts +301 -0
- package/dist/common/datetime.d.ts +301 -0
- package/dist/common/datetime.js +15 -0
- package/dist/common/email.cjs +7 -0
- package/dist/common/email.d.cts +149 -0
- package/dist/common/email.d.ts +149 -0
- package/dist/common/email.js +7 -0
- package/dist/common/file.cjs +7 -0
- package/dist/common/file.d.cts +178 -0
- package/dist/common/file.d.ts +178 -0
- package/dist/common/file.js +7 -0
- package/dist/common/id.cjs +13 -0
- package/dist/common/id.d.cts +288 -0
- package/dist/common/id.d.ts +288 -0
- package/dist/common/id.js +13 -0
- package/dist/common/ip.cjs +11 -0
- package/dist/common/ip.d.cts +25 -0
- package/dist/common/ip.d.ts +25 -0
- package/dist/common/ip.js +11 -0
- package/dist/common/number.cjs +7 -0
- package/dist/common/number.d.cts +167 -0
- package/dist/common/number.d.ts +167 -0
- package/dist/common/number.js +7 -0
- package/dist/common/password.cjs +7 -0
- package/dist/common/password.d.cts +192 -0
- package/dist/common/password.d.ts +192 -0
- package/dist/common/password.js +7 -0
- package/dist/common/text.cjs +7 -0
- package/dist/common/text.d.cts +156 -0
- package/dist/common/text.d.ts +156 -0
- package/dist/common/text.js +7 -0
- package/dist/common/time.cjs +15 -0
- package/dist/common/time.d.cts +268 -0
- package/dist/common/time.d.ts +268 -0
- package/dist/common/time.js +15 -0
- package/dist/common/url.cjs +7 -0
- package/dist/common/url.d.cts +196 -0
- package/dist/common/url.d.ts +196 -0
- package/dist/common/url.js +7 -0
- package/dist/config-CABSSvAp.d.cts +5 -0
- package/dist/config-CABSSvAp.d.ts +5 -0
- package/dist/index.cjs +180 -5255
- package/dist/index.d.cts +28 -3150
- package/dist/index.d.ts +28 -3150
- package/dist/index.js +135 -5131
- package/dist/taiwan/bank-account.cjs +11 -0
- package/dist/taiwan/bank-account.d.cts +22 -0
- package/dist/taiwan/bank-account.d.ts +22 -0
- package/dist/taiwan/bank-account.js +11 -0
- package/dist/taiwan/business-id.cjs +9 -0
- package/dist/taiwan/business-id.d.cts +133 -0
- package/dist/taiwan/business-id.d.ts +133 -0
- package/dist/taiwan/business-id.js +9 -0
- package/dist/taiwan/fax.cjs +9 -0
- package/dist/taiwan/fax.d.cts +157 -0
- package/dist/taiwan/fax.d.ts +157 -0
- package/dist/taiwan/fax.js +9 -0
- package/dist/taiwan/invoice.cjs +9 -0
- package/dist/taiwan/invoice.d.cts +17 -0
- package/dist/taiwan/invoice.d.ts +17 -0
- package/dist/taiwan/invoice.js +9 -0
- package/dist/taiwan/license-plate.cjs +9 -0
- package/dist/taiwan/license-plate.d.cts +19 -0
- package/dist/taiwan/license-plate.d.ts +19 -0
- package/dist/taiwan/license-plate.js +9 -0
- package/dist/taiwan/mobile.cjs +9 -0
- package/dist/taiwan/mobile.d.cts +146 -0
- package/dist/taiwan/mobile.d.ts +146 -0
- package/dist/taiwan/mobile.js +9 -0
- package/dist/taiwan/national-id.cjs +15 -0
- package/dist/taiwan/national-id.d.cts +214 -0
- package/dist/taiwan/national-id.d.ts +214 -0
- package/dist/taiwan/national-id.js +15 -0
- package/dist/taiwan/passport.cjs +9 -0
- package/dist/taiwan/passport.d.cts +19 -0
- package/dist/taiwan/passport.d.ts +19 -0
- package/dist/taiwan/passport.js +9 -0
- package/dist/taiwan/postal-code.cjs +17 -0
- package/dist/taiwan/postal-code.d.cts +237 -0
- package/dist/taiwan/postal-code.d.ts +237 -0
- package/dist/taiwan/postal-code.js +17 -0
- package/dist/taiwan/tel.cjs +9 -0
- package/dist/taiwan/tel.d.cts +162 -0
- package/dist/taiwan/tel.d.ts +162 -0
- package/dist/taiwan/tel.js +9 -0
- package/package.json +132 -6
- package/src/i18n/locales/en-GB.json +51 -0
- package/src/i18n/locales/en-US.json +52 -1
- package/src/i18n/locales/id-ID.json +51 -0
- package/src/i18n/locales/ja-JP.json +51 -0
- package/src/i18n/locales/ko-KR.json +51 -0
- package/src/i18n/locales/ms-MY.json +51 -0
- package/src/i18n/locales/th-TH.json +51 -0
- package/src/i18n/locales/vi-VN.json +51 -0
- package/src/i18n/locales/zh-CN.json +51 -0
- package/src/i18n/locales/zh-TW.json +51 -0
- package/src/index.ts +10 -2
- package/src/validators/common/color.ts +192 -0
- package/src/validators/common/coordinate.ts +159 -0
- package/src/validators/common/credit-card.ts +134 -0
- package/src/validators/common/id.ts +45 -3
- package/src/validators/common/ip.ts +210 -0
- package/src/validators/taiwan/bank-account.ts +176 -0
- package/src/validators/taiwan/invoice.ts +84 -0
- package/src/validators/taiwan/license-plate.ts +110 -0
- package/src/validators/taiwan/passport.ts +103 -0
- package/tests/common/color.test.ts +587 -0
- package/tests/common/coordinate.test.ts +345 -0
- package/tests/common/credit-card.test.ts +378 -0
- package/tests/common/id.test.ts +68 -3
- package/tests/common/ip.test.ts +419 -0
- package/tests/taiwan/bank-account.test.ts +286 -0
- package/tests/taiwan/invoice.test.ts +227 -0
- package/tests/taiwan/license-plate.test.ts +280 -0
- package/tests/taiwan/passport.test.ts +277 -0
- package/tsup.config.ts +36 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
node-version: [18, 20, 22]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: ${{ matrix.node-version }}
|
|
19
|
+
cache: npm
|
|
20
|
+
- run: npm ci
|
|
21
|
+
- run: npx eslint src/
|
|
22
|
+
- run: npx tsc --noEmit
|
|
23
|
+
- run: npm test -- --run
|
|
24
|
+
- run: npm run build
|
package/CLAUDE.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Project Overview
|
|
4
4
|
|
|
5
|
-
`@hy_ong/zod-kit` is a TypeScript library providing pre-built Zod validation schemas with full i18n support (
|
|
5
|
+
`@hy_ong/zod-kit` is a TypeScript library providing pre-built Zod validation schemas with full i18n support (10 locales). It includes common validators (email, password, text, number, credit card, IP, color, coordinate, etc.) and Taiwan-specific validators (National ID, Business ID, mobile, tel, fax, postal code, invoice, license plate, bank account, passport).
|
|
6
6
|
|
|
7
|
-
- **Version:** 0.
|
|
7
|
+
- **Version:** 0.2.0
|
|
8
8
|
- **License:** MIT
|
|
9
9
|
- **NPM:** `@hy_ong/zod-kit`
|
|
10
10
|
- **Peer dependency:** Zod ^4.3.6
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
## Commands
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
# Run all tests (
|
|
16
|
+
# Run all tests (1050 test cases across 25 files)
|
|
17
17
|
npm test
|
|
18
18
|
|
|
19
19
|
# Run tests once (no watch mode)
|
|
@@ -22,13 +22,16 @@ npm test -- --run
|
|
|
22
22
|
# Run specific test file
|
|
23
23
|
npx vitest run tests/common/email.test.ts
|
|
24
24
|
|
|
25
|
-
# Build (ESM + CJS + .d.ts)
|
|
25
|
+
# Build (ESM + CJS + .d.ts, 26 entry points with code splitting)
|
|
26
26
|
npm run build
|
|
27
|
-
#
|
|
27
|
+
# Uses tsup.config.ts for multi-entry build
|
|
28
28
|
|
|
29
29
|
# Lint
|
|
30
30
|
npx eslint src/
|
|
31
31
|
|
|
32
|
+
# Type check
|
|
33
|
+
npx tsc --noEmit
|
|
34
|
+
|
|
32
35
|
# Publish (runs tests + build automatically via prepublishOnly)
|
|
33
36
|
npm publish --access public
|
|
34
37
|
```
|
|
@@ -42,34 +45,65 @@ src/
|
|
|
42
45
|
├── i18n/
|
|
43
46
|
│ ├── index.ts # Translation function t(key, params)
|
|
44
47
|
│ └── locales/
|
|
45
|
-
│ ├── en.json
|
|
46
|
-
│
|
|
48
|
+
│ ├── en-US.json # English (US)
|
|
49
|
+
│ ├── en-GB.json # English (UK)
|
|
50
|
+
│ ├── zh-TW.json # Traditional Chinese
|
|
51
|
+
│ ├── zh-CN.json # Simplified Chinese
|
|
52
|
+
│ ├── ja-JP.json # Japanese
|
|
53
|
+
│ ├── ko-KR.json # Korean
|
|
54
|
+
│ ├── ms-MY.json # Malay
|
|
55
|
+
│ ├── id-ID.json # Indonesian
|
|
56
|
+
│ ├── th-TH.json # Thai
|
|
57
|
+
│ └── vi-VN.json # Vietnamese
|
|
47
58
|
└── validators/
|
|
48
|
-
├── common/ #
|
|
59
|
+
├── common/ # 15 universal validators
|
|
49
60
|
│ ├── boolean.ts
|
|
61
|
+
│ ├── color.ts # Hex, RGB, HSL with alpha support
|
|
62
|
+
│ ├── coordinate.ts # Latitude, longitude, coordinate pairs
|
|
63
|
+
│ ├── credit-card.ts # Luhn algorithm, card type detection
|
|
50
64
|
│ ├── date.ts
|
|
51
65
|
│ ├── datetime.ts # Uses dayjs for parsing
|
|
52
66
|
│ ├── email.ts
|
|
53
67
|
│ ├── file.ts
|
|
54
68
|
│ ├── id.ts # UUID, nanoid, ObjectId
|
|
69
|
+
│ ├── ip.ts # IPv4, IPv6, CIDR support
|
|
55
70
|
│ ├── number.ts
|
|
56
71
|
│ ├── password.ts
|
|
57
72
|
│ ├── text.ts
|
|
58
73
|
│ ├── time.ts
|
|
59
74
|
│ └── url.ts
|
|
60
|
-
└── taiwan/ #
|
|
75
|
+
└── taiwan/ # 10 Taiwan-specific validators
|
|
76
|
+
├── bank-account.ts # 銀行帳號 (bank code + account number)
|
|
61
77
|
├── business-id.ts # 統一編號 (checksum)
|
|
62
78
|
├── fax.ts
|
|
79
|
+
├── invoice.ts # 統一發票 (2 letters + 8 digits)
|
|
80
|
+
├── license-plate.ts # 車牌號碼 (new + legacy formats)
|
|
63
81
|
├── mobile.ts # 09X-XXXX-XXXX
|
|
64
82
|
├── national-id.ts # 身分證/居留證 (checksum)
|
|
83
|
+
├── passport.ts # 護照號碼 (9 digits, type prefix)
|
|
65
84
|
├── postal-code.ts # 3/5/6-digit with official data
|
|
66
85
|
└── tel.ts # Landline + toll-free (0800/0809)
|
|
67
86
|
|
|
68
87
|
tests/ # Mirrors src/validators/ structure
|
|
69
|
-
├── common/ #
|
|
70
|
-
└── taiwan/ #
|
|
88
|
+
├── common/ # 15 test files
|
|
89
|
+
└── taiwan/ # 10 test files
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Per-Module Imports
|
|
93
|
+
|
|
94
|
+
Users can import individual validators for tree-shaking:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Full import
|
|
98
|
+
import { email, twNationalId } from "@hy_ong/zod-kit"
|
|
99
|
+
|
|
100
|
+
// Per-module import (tree-shakeable)
|
|
101
|
+
import { email } from "@hy_ong/zod-kit/common/email"
|
|
102
|
+
import { twNationalId } from "@hy_ong/zod-kit/taiwan/national-id"
|
|
71
103
|
```
|
|
72
104
|
|
|
105
|
+
All 26 sub-modules are configured in `tsup.config.ts` (entry points) and `package.json` (exports map).
|
|
106
|
+
|
|
73
107
|
## Architecture & Patterns
|
|
74
108
|
|
|
75
109
|
### Validator Function Signature
|
|
@@ -95,7 +129,8 @@ All validators use a two-step approach:
|
|
|
95
129
|
|
|
96
130
|
### i18n System
|
|
97
131
|
|
|
98
|
-
-
|
|
132
|
+
- 10 locales: en-US, en-GB, zh-TW, zh-CN, ja-JP, ko-KR, ms-MY, id-ID, th-TH, vi-VN
|
|
133
|
+
- Locale state is global: `setLocale("en-US")` / `setLocale("zh-TW")`
|
|
99
134
|
- Translation keys are dot-separated: `common.email.invalid`
|
|
100
135
|
- Parameter substitution: `${paramName}` in message templates
|
|
101
136
|
- Each validator supports custom `i18n` overrides in options
|
|
@@ -107,6 +142,10 @@ Taiwan validators implement domain-specific algorithms:
|
|
|
107
142
|
- **Postal Code**: Validates against official Chunghwa Post data (28KB+ of mappings)
|
|
108
143
|
- **Tel/Fax**: Area code + digit length rules per region
|
|
109
144
|
- **Mobile**: Operator prefix validation (090-099)
|
|
145
|
+
- **Invoice**: 2-letter prefix + 8-digit format, auto-strip hyphens, auto-uppercase
|
|
146
|
+
- **License Plate**: New (2012+) and legacy formats for car/motorcycle
|
|
147
|
+
- **Bank Account**: Bank code validation against official list + account number length
|
|
148
|
+
- **Passport**: 9-digit format with type prefix (0=diplomatic, 1=official, 2=ordinary, 3=travel)
|
|
110
149
|
|
|
111
150
|
## Code Style
|
|
112
151
|
|
|
@@ -120,23 +159,26 @@ Taiwan validators implement domain-specific algorithms:
|
|
|
120
159
|
|
|
121
160
|
- Commit messages use **Conventional Commits**: `feat(scope):`, `fix(scope):`, `chore:`, `docs:`
|
|
122
161
|
- Every validator exports both a factory function and relevant type definitions
|
|
123
|
-
- Test files use `beforeEach(() => setLocale("en"))` for consistent locale
|
|
162
|
+
- Test files use `beforeEach(() => setLocale("en-US"))` for consistent locale
|
|
124
163
|
- Test structure: `describe("validatorName(required) features")` → nested `describe` per feature → `it` cases
|
|
125
164
|
- Source files are organized by domain: `common/` for universal, `taiwan/` for locale-specific
|
|
126
|
-
-
|
|
165
|
+
- CI runs on GitHub Actions (Node 18/20/22): lint, type-check, test, build
|
|
127
166
|
|
|
128
167
|
## Build Output
|
|
129
168
|
|
|
130
|
-
`dist/` contains
|
|
131
|
-
- `
|
|
132
|
-
- `
|
|
133
|
-
- `
|
|
134
|
-
- `
|
|
169
|
+
`dist/` contains per-module outputs with code splitting:
|
|
170
|
+
- `{module}.js` — ESM bundle
|
|
171
|
+
- `{module}.cjs` — CommonJS bundle
|
|
172
|
+
- `{module}.d.ts` — TypeScript declarations (ESM)
|
|
173
|
+
- `{module}.d.cts` — TypeScript declarations (CJS)
|
|
174
|
+
- `chunk-*.js` / `chunk-*.cjs` — Shared code chunks
|
|
135
175
|
|
|
136
176
|
## Adding a New Validator
|
|
137
177
|
|
|
138
178
|
1. Create `src/validators/{domain}/{name}.ts` following the generic pattern above
|
|
139
|
-
2. Add i18n keys to
|
|
179
|
+
2. Add i18n keys to all 10 locale files in `src/i18n/locales/`
|
|
140
180
|
3. Export from `src/index.ts`
|
|
141
|
-
4.
|
|
142
|
-
5.
|
|
181
|
+
4. Add entry point to `tsup.config.ts`
|
|
182
|
+
5. Add exports map entry to `package.json`
|
|
183
|
+
6. Create `tests/{domain}/{name}.test.ts` with comprehensive coverage
|
|
184
|
+
7. Test with `npx vitest run tests/{domain}/{name}.test.ts`
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLocale,
|
|
3
|
+
t
|
|
4
|
+
} from "./chunk-6AAP4LPF.js";
|
|
5
|
+
|
|
6
|
+
// src/validators/common/color.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var HEX_RGB = /^#[0-9a-f]{3}$/i;
|
|
9
|
+
var HEX_RRGGBB = /^#[0-9a-f]{6}$/i;
|
|
10
|
+
var HEX_RRGGBBAA = /^#[0-9a-f]{8}$/i;
|
|
11
|
+
function isValidByte(n) {
|
|
12
|
+
return Number.isInteger(n) && n >= 0 && n <= 255;
|
|
13
|
+
}
|
|
14
|
+
function isValidAlpha(n) {
|
|
15
|
+
return n >= 0 && n <= 1;
|
|
16
|
+
}
|
|
17
|
+
function isValidHue(n) {
|
|
18
|
+
return n >= 0 && n <= 360;
|
|
19
|
+
}
|
|
20
|
+
function isValidPercentage(n) {
|
|
21
|
+
return n >= 0 && n <= 100;
|
|
22
|
+
}
|
|
23
|
+
function validateHex(value, allowAlpha) {
|
|
24
|
+
if (HEX_RGB.test(value) || HEX_RRGGBB.test(value)) return true;
|
|
25
|
+
if (allowAlpha && HEX_RRGGBBAA.test(value)) return true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function validateRgb(value, allowAlpha) {
|
|
29
|
+
const rgbaMatch = value.match(/^rgba?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*(?:,\s*(-?\d+(?:\.\d+)?))?\s*\)$/);
|
|
30
|
+
if (!rgbaMatch) return false;
|
|
31
|
+
const r = Number(rgbaMatch[1]);
|
|
32
|
+
const g = Number(rgbaMatch[2]);
|
|
33
|
+
const b = Number(rgbaMatch[3]);
|
|
34
|
+
const a = rgbaMatch[4] !== void 0 ? Number(rgbaMatch[4]) : void 0;
|
|
35
|
+
if (!isValidByte(r) || !isValidByte(g) || !isValidByte(b)) return false;
|
|
36
|
+
if (a !== void 0) {
|
|
37
|
+
if (!allowAlpha) return false;
|
|
38
|
+
if (!isValidAlpha(a)) return false;
|
|
39
|
+
}
|
|
40
|
+
if (value.startsWith("rgba(") && a === void 0) return false;
|
|
41
|
+
if (value.startsWith("rgb(") && a !== void 0) return false;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function validateHsl(value, allowAlpha) {
|
|
45
|
+
const hslaMatch = value.match(/^hsla?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*(?:,\s*(-?\d+(?:\.\d+)?))?\s*\)$/);
|
|
46
|
+
if (!hslaMatch) return false;
|
|
47
|
+
const h = Number(hslaMatch[1]);
|
|
48
|
+
const s = Number(hslaMatch[2]);
|
|
49
|
+
const l = Number(hslaMatch[3]);
|
|
50
|
+
const a = hslaMatch[4] !== void 0 ? Number(hslaMatch[4]) : void 0;
|
|
51
|
+
if (!isValidHue(h) || !isValidPercentage(s) || !isValidPercentage(l)) return false;
|
|
52
|
+
if (a !== void 0) {
|
|
53
|
+
if (!allowAlpha) return false;
|
|
54
|
+
if (!isValidAlpha(a)) return false;
|
|
55
|
+
}
|
|
56
|
+
if (value.startsWith("hsla(") && a === void 0) return false;
|
|
57
|
+
if (value.startsWith("hsl(") && a !== void 0) return false;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function validateColor(value, formats, allowAlpha) {
|
|
61
|
+
const hasAny = formats.includes("any");
|
|
62
|
+
if (hasAny) {
|
|
63
|
+
if (validateHex(value, allowAlpha)) return { valid: true };
|
|
64
|
+
if (validateRgb(value, allowAlpha)) return { valid: true };
|
|
65
|
+
if (validateHsl(value, allowAlpha)) return { valid: true };
|
|
66
|
+
return { valid: false };
|
|
67
|
+
}
|
|
68
|
+
for (const format of formats) {
|
|
69
|
+
if (format === "hex" && validateHex(value, allowAlpha)) return { valid: true };
|
|
70
|
+
if (format === "rgb" && validateRgb(value, allowAlpha)) return { valid: true };
|
|
71
|
+
if (format === "hsl" && validateHsl(value, allowAlpha)) return { valid: true };
|
|
72
|
+
}
|
|
73
|
+
if (formats.length === 1) {
|
|
74
|
+
return { valid: false, failedFormat: formats[0] };
|
|
75
|
+
}
|
|
76
|
+
return { valid: false };
|
|
77
|
+
}
|
|
78
|
+
function color(required, options) {
|
|
79
|
+
const { format = "any", allowAlpha = true, transform, defaultValue, i18n } = options ?? {};
|
|
80
|
+
const isRequired = required ?? false;
|
|
81
|
+
const actualDefaultValue = defaultValue ?? (isRequired ? "" : null);
|
|
82
|
+
const formats = Array.isArray(format) ? format : [format];
|
|
83
|
+
const getMessage = (key, params) => {
|
|
84
|
+
if (i18n) {
|
|
85
|
+
const currentLocale = getLocale();
|
|
86
|
+
const customMessages = i18n[currentLocale];
|
|
87
|
+
if (customMessages && customMessages[key]) {
|
|
88
|
+
const template = customMessages[key];
|
|
89
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return t(`common.color.${key}`, params);
|
|
93
|
+
};
|
|
94
|
+
const preprocessFn = (val) => {
|
|
95
|
+
if (val === "" || val === null || val === void 0) {
|
|
96
|
+
return actualDefaultValue;
|
|
97
|
+
}
|
|
98
|
+
let processed = String(val).trim();
|
|
99
|
+
if (processed === "" && !required) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (transform) {
|
|
103
|
+
processed = transform(processed);
|
|
104
|
+
}
|
|
105
|
+
return processed;
|
|
106
|
+
};
|
|
107
|
+
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable());
|
|
108
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
109
|
+
if (val === null) return;
|
|
110
|
+
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
111
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!isRequired && val === "") return;
|
|
115
|
+
const result = validateColor(val, formats, allowAlpha);
|
|
116
|
+
if (!result.valid) {
|
|
117
|
+
const formatMessageMap = {
|
|
118
|
+
hex: "notHex",
|
|
119
|
+
rgb: "notRgb",
|
|
120
|
+
hsl: "notHsl",
|
|
121
|
+
any: "invalid"
|
|
122
|
+
};
|
|
123
|
+
const messageKey = result.failedFormat ? formatMessageMap[result.failedFormat] : "invalid";
|
|
124
|
+
ctx.addIssue({ code: "custom", message: getMessage(messageKey) });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return schema;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
validateColor,
|
|
133
|
+
color
|
|
134
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkUCOXAZJFcjs = require('./chunk-UCOXAZJF.cjs');
|
|
5
|
+
|
|
6
|
+
// src/validators/common/ip.ts
|
|
7
|
+
var _zod = require('zod');
|
|
8
|
+
function validateIPv4(value) {
|
|
9
|
+
const parts = value.split(".");
|
|
10
|
+
if (parts.length !== 4) return false;
|
|
11
|
+
for (const part of parts) {
|
|
12
|
+
if (part === "") return false;
|
|
13
|
+
if (part.length > 1 && part.startsWith("0")) return false;
|
|
14
|
+
const num = Number(part);
|
|
15
|
+
if (!Number.isInteger(num) || num < 0 || num > 255) return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
function validateIPv6(value) {
|
|
20
|
+
const lastColon = value.lastIndexOf(":");
|
|
21
|
+
if (lastColon !== -1) {
|
|
22
|
+
const afterLastColon = value.substring(lastColon + 1);
|
|
23
|
+
if (afterLastColon.includes(".")) {
|
|
24
|
+
if (!validateIPv4(afterLastColon)) return false;
|
|
25
|
+
const ipv6Prefix = value.substring(0, lastColon);
|
|
26
|
+
return validateIPv6Groups(ipv6Prefix, 6);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return validateIPv6Groups(value, 8);
|
|
30
|
+
}
|
|
31
|
+
function validateIPv6Groups(value, maxGroups) {
|
|
32
|
+
if (value.includes("::")) {
|
|
33
|
+
const doubleColonCount = value.split("::").length - 1;
|
|
34
|
+
if (doubleColonCount > 1) return false;
|
|
35
|
+
const [left, right] = value.split("::");
|
|
36
|
+
const leftGroups = left === "" ? [] : left.split(":");
|
|
37
|
+
const rightGroups = right === "" ? [] : right.split(":");
|
|
38
|
+
if (leftGroups.length + rightGroups.length >= maxGroups) return false;
|
|
39
|
+
for (const group of [...leftGroups, ...rightGroups]) {
|
|
40
|
+
if (!isValidHexGroup(group)) return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const groups = value.split(":");
|
|
45
|
+
if (groups.length !== maxGroups) return false;
|
|
46
|
+
for (const group of groups) {
|
|
47
|
+
if (!isValidHexGroup(group)) return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
function isValidHexGroup(group) {
|
|
52
|
+
if (group.length === 0 || group.length > 4) return false;
|
|
53
|
+
return /^[0-9a-fA-F]{1,4}$/.test(group);
|
|
54
|
+
}
|
|
55
|
+
function ip(required, options) {
|
|
56
|
+
const { version = "any", allowCIDR = false, whitelist, transform, defaultValue, i18n } = _nullishCoalesce(options, () => ( {}));
|
|
57
|
+
const isRequired = _nullishCoalesce(required, () => ( false));
|
|
58
|
+
const actualDefaultValue = _nullishCoalesce(defaultValue, () => ( (isRequired ? "" : null)));
|
|
59
|
+
const getMessage = (key, params) => {
|
|
60
|
+
if (i18n) {
|
|
61
|
+
const currentLocale = _chunkUCOXAZJFcjs.getLocale.call(void 0, );
|
|
62
|
+
const customMessages = i18n[currentLocale];
|
|
63
|
+
if (customMessages && customMessages[key]) {
|
|
64
|
+
const template = customMessages[key];
|
|
65
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => _nullishCoalesce(_optionalChain([params, 'optionalAccess', _2 => _2[k]]), () => ( "")));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return _chunkUCOXAZJFcjs.t.call(void 0, `common.ip.${key}`, params);
|
|
69
|
+
};
|
|
70
|
+
const preprocessFn = (val) => {
|
|
71
|
+
if (val === "" || val === null || val === void 0) {
|
|
72
|
+
return actualDefaultValue;
|
|
73
|
+
}
|
|
74
|
+
let processed = String(val).trim();
|
|
75
|
+
if (processed === "" && !required) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (transform) {
|
|
79
|
+
processed = transform(processed);
|
|
80
|
+
}
|
|
81
|
+
return processed;
|
|
82
|
+
};
|
|
83
|
+
const baseSchema = isRequired ? _zod.z.preprocess(preprocessFn, _zod.z.string()) : _zod.z.preprocess(preprocessFn, _zod.z.string().nullable());
|
|
84
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
85
|
+
if (val === null) return;
|
|
86
|
+
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
87
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!isRequired && val === "") return;
|
|
91
|
+
let ipPart = val;
|
|
92
|
+
let cidrPrefix = null;
|
|
93
|
+
const slashIndex = val.indexOf("/");
|
|
94
|
+
if (slashIndex !== -1) {
|
|
95
|
+
if (!allowCIDR) {
|
|
96
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
ipPart = val.substring(0, slashIndex);
|
|
100
|
+
cidrPrefix = val.substring(slashIndex + 1);
|
|
101
|
+
}
|
|
102
|
+
const isV4 = validateIPv4(ipPart);
|
|
103
|
+
const isV6 = validateIPv6(ipPart);
|
|
104
|
+
if (version === "v4") {
|
|
105
|
+
if (!isV4) {
|
|
106
|
+
ctx.addIssue({ code: "custom", message: getMessage("notIPv4") });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
} else if (version === "v6") {
|
|
110
|
+
if (!isV6) {
|
|
111
|
+
ctx.addIssue({ code: "custom", message: getMessage("notIPv6") });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
if (!isV4 && !isV6) {
|
|
116
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (cidrPrefix !== null) {
|
|
121
|
+
if (!/^\d+$/.test(cidrPrefix)) {
|
|
122
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const prefixNum = Number(cidrPrefix);
|
|
126
|
+
const maxPrefix = isV4 ? 32 : 128;
|
|
127
|
+
if (prefixNum < 0 || prefixNum > maxPrefix) {
|
|
128
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (whitelist && whitelist.length > 0) {
|
|
133
|
+
if (!whitelist.includes(val)) {
|
|
134
|
+
ctx.addIssue({ code: "custom", message: getMessage("notInWhitelist") });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return schema;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
exports.validateIPv4 = validateIPv4; exports.validateIPv6 = validateIPv6; exports.ip = ip;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLocale,
|
|
3
|
+
t
|
|
4
|
+
} from "./chunk-6AAP4LPF.js";
|
|
5
|
+
|
|
6
|
+
// src/validators/common/boolean.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
function boolean(required, options) {
|
|
9
|
+
const {
|
|
10
|
+
defaultValue = null,
|
|
11
|
+
shouldBe,
|
|
12
|
+
truthyValues = [true, "true", 1, "1", "yes", "on"],
|
|
13
|
+
falsyValues = [false, "false", 0, "0", "no", "off"],
|
|
14
|
+
strict = false,
|
|
15
|
+
transform,
|
|
16
|
+
i18n
|
|
17
|
+
} = options ?? {};
|
|
18
|
+
const isRequired = required ?? false;
|
|
19
|
+
const getMessage = (key, params) => {
|
|
20
|
+
if (i18n) {
|
|
21
|
+
const currentLocale = getLocale();
|
|
22
|
+
const customMessages = i18n[currentLocale];
|
|
23
|
+
if (customMessages && customMessages[key]) {
|
|
24
|
+
const template = customMessages[key];
|
|
25
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return t(`common.boolean.${key}`, params);
|
|
29
|
+
};
|
|
30
|
+
let result = z.preprocess(
|
|
31
|
+
(val) => {
|
|
32
|
+
if (val === "" || val === void 0 || val === null) return defaultValue;
|
|
33
|
+
if (strict && typeof val !== "boolean" && val !== null) {
|
|
34
|
+
return val;
|
|
35
|
+
}
|
|
36
|
+
if (truthyValues.includes(val)) {
|
|
37
|
+
let processed = true;
|
|
38
|
+
if (transform) processed = transform(processed);
|
|
39
|
+
return processed;
|
|
40
|
+
}
|
|
41
|
+
if (falsyValues.includes(val)) {
|
|
42
|
+
let processed = false;
|
|
43
|
+
if (transform) processed = transform(processed);
|
|
44
|
+
return processed;
|
|
45
|
+
}
|
|
46
|
+
return val;
|
|
47
|
+
},
|
|
48
|
+
z.union([z.literal(true), z.literal(false), z.literal(null)])
|
|
49
|
+
);
|
|
50
|
+
if (isRequired && defaultValue === null) {
|
|
51
|
+
result = result.refine((val) => val !== null, { message: getMessage("required") });
|
|
52
|
+
}
|
|
53
|
+
if (shouldBe === true) {
|
|
54
|
+
result = result.refine((val) => val === true, { message: getMessage("shouldBeTrue") });
|
|
55
|
+
} else if (shouldBe === false) {
|
|
56
|
+
result = result.refine((val) => val === false, { message: getMessage("shouldBeFalse") });
|
|
57
|
+
}
|
|
58
|
+
if (strict) {
|
|
59
|
+
result = result.refine(
|
|
60
|
+
(val) => {
|
|
61
|
+
return val === null || typeof val === "boolean";
|
|
62
|
+
},
|
|
63
|
+
{ message: getMessage("invalid") }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
boolean
|
|
71
|
+
};
|