@hy_ong/zod-kit 0.1.16 → 0.2.1
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 +184 -0
- package/dist/chunk-36NWHESN.js +124 -0
- package/dist/chunk-4LYZAO3P.js +165 -0
- package/dist/chunk-5GAZQDVS.cjs +206 -0
- package/dist/chunk-5LS4DSRQ.cjs +127 -0
- package/dist/chunk-5OGW2ERW.js +181 -0
- package/dist/chunk-5ZEKWPSE.cjs +69 -0
- package/dist/chunk-6OGDPSWT.js +135 -0
- package/dist/chunk-6X22I6NQ.cjs +136 -0
- package/dist/chunk-77KZUPPN.cjs +177 -0
- package/dist/chunk-AANSHH2O.cjs +165 -0
- package/dist/chunk-AI72FMOF.cjs +130 -0
- package/dist/chunk-AWV2IT66.js +146 -0
- package/dist/chunk-B3U5G3AA.js +160 -0
- package/dist/chunk-CFFCBWYL.cjs +99 -0
- package/dist/chunk-DPXRMSB2.js +130 -0
- package/dist/chunk-DRXPGQM6.cjs +135 -0
- package/dist/chunk-EAU42EVH.js +161 -0
- package/dist/chunk-FC6VDOC7.js +206 -0
- package/dist/chunk-FVO4743A.cjs +134 -0
- package/dist/chunk-G6DV7LX7.cjs +161 -0
- package/dist/chunk-I2RJMDXN.js +90 -0
- package/dist/chunk-IJEEM3DI.js +136 -0
- package/dist/chunk-JBNCMS42.cjs +151 -0
- package/dist/chunk-JZ2SHRGZ.js +87 -0
- package/dist/chunk-KARFFIMP.js +696 -0
- package/dist/chunk-LIQSVJLS.js +177 -0
- package/dist/chunk-LKPXHW5N.cjs +181 -0
- package/dist/chunk-MAQRXYE6.js +118 -0
- package/dist/chunk-MCDESS3T.js +69 -0
- package/dist/chunk-MG25BEV4.cjs +160 -0
- package/dist/chunk-NKCYXBGX.js +99 -0
- package/dist/chunk-OEK7QSQP.js +75 -0
- package/dist/chunk-OMFQ7Z63.cjs +696 -0
- package/dist/chunk-OP4KV3BY.cjs +124 -0
- package/dist/chunk-P2NONIMS.js +257 -0
- package/dist/chunk-P364KRO5.js +61 -0
- package/dist/chunk-PGSDXR2I.js +71 -0
- package/dist/chunk-PL2GERLG.cjs +61 -0
- package/dist/chunk-R5G4V7C6.cjs +75 -0
- package/dist/chunk-RKHX3DGH.js +127 -0
- package/dist/chunk-TSHL7ZO2.js +134 -0
- package/dist/chunk-UFNVCUPQ.cjs +301 -0
- package/dist/chunk-VCRKYMJM.js +301 -0
- package/dist/chunk-VDOAPLA6.cjs +257 -0
- package/dist/chunk-VP5CCP5F.cjs +90 -0
- package/dist/chunk-W2EWMV3A.cjs +87 -0
- package/dist/chunk-WWRFBLCR.cjs +146 -0
- package/dist/chunk-YALLOVNO.cjs +118 -0
- package/dist/chunk-YAU6JCYL.cjs +71 -0
- package/dist/chunk-YWV2BBXN.cjs +2526 -0
- package/dist/chunk-ZBOQCXD4.js +2526 -0
- package/dist/chunk-ZFQQXWNB.js +151 -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 +262 -0
- package/dist/common/id.d.ts +262 -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 -3599
- package/dist/index.d.cts +28 -3150
- package/dist/index.d.ts +28 -3150
- package/dist/index.js +135 -3475
- 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 +128 -2
- package/src/config.ts +2 -2
- package/src/i18n/index.ts +18 -2
- package/src/i18n/locales/en-GB.json +247 -0
- package/src/i18n/locales/{en.json → en-US.json} +43 -0
- package/src/i18n/locales/id-ID.json +247 -0
- package/src/i18n/locales/ja-JP.json +247 -0
- package/src/i18n/locales/ko-KR.json +247 -0
- package/src/i18n/locales/ms-MY.json +247 -0
- package/src/i18n/locales/th-TH.json +247 -0
- package/src/i18n/locales/vi-VN.json +247 -0
- package/src/i18n/locales/zh-CN.json +247 -0
- package/src/i18n/locales/zh-TW.json +43 -0
- package/src/index.ts +10 -2
- package/src/validators/common/boolean.ts +1 -1
- 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/date.ts +1 -1
- package/src/validators/common/datetime.ts +1 -1
- package/src/validators/common/email.ts +1 -1
- package/src/validators/common/file.ts +1 -1
- package/src/validators/common/id.ts +1 -1
- package/src/validators/common/ip.ts +210 -0
- package/src/validators/common/number.ts +1 -1
- package/src/validators/common/password.ts +1 -1
- package/src/validators/common/text.ts +1 -1
- package/src/validators/common/time.ts +1 -1
- package/src/validators/common/url.ts +1 -1
- package/src/validators/taiwan/bank-account.ts +176 -0
- package/src/validators/taiwan/business-id.ts +1 -1
- package/src/validators/taiwan/fax.ts +1 -1
- package/src/validators/taiwan/invoice.ts +84 -0
- package/src/validators/taiwan/license-plate.ts +110 -0
- package/src/validators/taiwan/mobile.ts +1 -1
- package/src/validators/taiwan/national-id.ts +1 -1
- package/src/validators/taiwan/passport.ts +103 -0
- package/src/validators/taiwan/postal-code.ts +1 -1
- package/src/validators/taiwan/tel.ts +1 -1
- package/tests/common/boolean.test.ts +6 -6
- 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/date.test.ts +8 -8
- package/tests/common/datetime.test.ts +7 -7
- package/tests/common/email.test.ts +6 -6
- package/tests/common/file.test.ts +6 -6
- package/tests/common/id.test.ts +5 -5
- package/tests/common/ip.test.ts +419 -0
- package/tests/common/number.test.ts +6 -6
- package/tests/common/password.test.ts +5 -5
- package/tests/common/text.test.ts +7 -7
- package/tests/common/time.test.ts +7 -7
- package/tests/common/url.test.ts +10 -10
- package/tests/taiwan/bank-account.test.ts +286 -0
- package/tests/taiwan/business-id.test.ts +4 -4
- package/tests/taiwan/fax.test.ts +7 -7
- package/tests/taiwan/invoice.test.ts +227 -0
- package/tests/taiwan/license-plate.test.ts +280 -0
- package/tests/taiwan/mobile.test.ts +7 -7
- package/tests/taiwan/national-id.test.ts +4 -4
- package/tests/taiwan/passport.test.ts +277 -0
- package/tests/taiwan/postal-code.test.ts +6 -6
- package/tests/taiwan/tel.test.ts +7 -7
- 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
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# CLAUDE.md — zod-kit
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
- **Version:** 0.2.0
|
|
8
|
+
- **License:** MIT
|
|
9
|
+
- **NPM:** `@hy_ong/zod-kit`
|
|
10
|
+
- **Peer dependency:** Zod ^4.3.6
|
|
11
|
+
- **Production dependency:** dayjs ^1.11.19
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Run all tests (1050 test cases across 25 files)
|
|
17
|
+
npm test
|
|
18
|
+
|
|
19
|
+
# Run tests once (no watch mode)
|
|
20
|
+
npm test -- --run
|
|
21
|
+
|
|
22
|
+
# Run specific test file
|
|
23
|
+
npx vitest run tests/common/email.test.ts
|
|
24
|
+
|
|
25
|
+
# Build (ESM + CJS + .d.ts, 26 entry points with code splitting)
|
|
26
|
+
npm run build
|
|
27
|
+
# Uses tsup.config.ts for multi-entry build
|
|
28
|
+
|
|
29
|
+
# Lint
|
|
30
|
+
npx eslint src/
|
|
31
|
+
|
|
32
|
+
# Type check
|
|
33
|
+
npx tsc --noEmit
|
|
34
|
+
|
|
35
|
+
# Publish (runs tests + build automatically via prepublishOnly)
|
|
36
|
+
npm publish --access public
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Project Structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
src/
|
|
43
|
+
├── index.ts # Re-exports all validators + config
|
|
44
|
+
├── config.ts # Locale state (setLocale, getLocale)
|
|
45
|
+
├── i18n/
|
|
46
|
+
│ ├── index.ts # Translation function t(key, params)
|
|
47
|
+
│ └── locales/
|
|
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
|
|
58
|
+
└── validators/
|
|
59
|
+
├── common/ # 15 universal validators
|
|
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
|
|
64
|
+
│ ├── date.ts
|
|
65
|
+
│ ├── datetime.ts # Uses dayjs for parsing
|
|
66
|
+
│ ├── email.ts
|
|
67
|
+
│ ├── file.ts
|
|
68
|
+
│ ├── id.ts # UUID, nanoid, ObjectId
|
|
69
|
+
│ ├── ip.ts # IPv4, IPv6, CIDR support
|
|
70
|
+
│ ├── number.ts
|
|
71
|
+
│ ├── password.ts
|
|
72
|
+
│ ├── text.ts
|
|
73
|
+
│ ├── time.ts
|
|
74
|
+
│ └── url.ts
|
|
75
|
+
└── taiwan/ # 10 Taiwan-specific validators
|
|
76
|
+
├── bank-account.ts # 銀行帳號 (bank code + account number)
|
|
77
|
+
├── business-id.ts # 統一編號 (checksum)
|
|
78
|
+
├── fax.ts
|
|
79
|
+
├── invoice.ts # 統一發票 (2 letters + 8 digits)
|
|
80
|
+
├── license-plate.ts # 車牌號碼 (new + legacy formats)
|
|
81
|
+
├── mobile.ts # 09X-XXXX-XXXX
|
|
82
|
+
├── national-id.ts # 身分證/居留證 (checksum)
|
|
83
|
+
├── passport.ts # 護照號碼 (9 digits, type prefix)
|
|
84
|
+
├── postal-code.ts # 3/5/6-digit with official data
|
|
85
|
+
└── tel.ts # Landline + toll-free (0800/0809)
|
|
86
|
+
|
|
87
|
+
tests/ # Mirrors src/validators/ structure
|
|
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"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
All 26 sub-modules are configured in `tsup.config.ts` (entry points) and `package.json` (exports map).
|
|
106
|
+
|
|
107
|
+
## Architecture & Patterns
|
|
108
|
+
|
|
109
|
+
### Validator Function Signature
|
|
110
|
+
|
|
111
|
+
Every validator follows this consistent pattern:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
function validatorName<IsRequired extends boolean = false>(
|
|
115
|
+
required?: IsRequired,
|
|
116
|
+
options?: Omit<ValidatorOptions<IsRequired>, "required">
|
|
117
|
+
): ValidatorSchema<IsRequired>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- First param: `required` boolean (defaults to `false`)
|
|
121
|
+
- Second param: options object (varies per validator)
|
|
122
|
+
- Returns: `ZodType` — required returns non-nullable, optional returns nullable
|
|
123
|
+
|
|
124
|
+
### Validation Pipeline
|
|
125
|
+
|
|
126
|
+
All validators use a two-step approach:
|
|
127
|
+
1. `z.preprocess()` — input transformation (trim, coerce, normalize)
|
|
128
|
+
2. `.superRefine()` — complex business logic and custom error messages
|
|
129
|
+
|
|
130
|
+
### i18n System
|
|
131
|
+
|
|
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")`
|
|
134
|
+
- Translation keys are dot-separated: `common.email.invalid`
|
|
135
|
+
- Parameter substitution: `${paramName}` in message templates
|
|
136
|
+
- Each validator supports custom `i18n` overrides in options
|
|
137
|
+
|
|
138
|
+
### Taiwan Validators
|
|
139
|
+
|
|
140
|
+
Taiwan validators implement domain-specific algorithms:
|
|
141
|
+
- **National ID / Business ID**: Checksum verification with weighted sums
|
|
142
|
+
- **Postal Code**: Validates against official Chunghwa Post data (28KB+ of mappings)
|
|
143
|
+
- **Tel/Fax**: Area code + digit length rules per region
|
|
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)
|
|
149
|
+
|
|
150
|
+
## Code Style
|
|
151
|
+
|
|
152
|
+
- **Formatter:** Prettier — no semicolons, double quotes, 200 char line width, 2-space indent
|
|
153
|
+
- **Linter:** ESLint 9 + typescript-eslint (recommended rules, `no-explicit-any` disabled)
|
|
154
|
+
- **TypeScript:** Strict mode, ESNext target, declaration files generated
|
|
155
|
+
- **Module:** ES Module (`"type": "module"` in package.json)
|
|
156
|
+
- **Tests:** Vitest with globals enabled, node environment
|
|
157
|
+
|
|
158
|
+
## Conventions
|
|
159
|
+
|
|
160
|
+
- Commit messages use **Conventional Commits**: `feat(scope):`, `fix(scope):`, `chore:`, `docs:`
|
|
161
|
+
- Every validator exports both a factory function and relevant type definitions
|
|
162
|
+
- Test files use `beforeEach(() => setLocale("en-US"))` for consistent locale
|
|
163
|
+
- Test structure: `describe("validatorName(required) features")` → nested `describe` per feature → `it` cases
|
|
164
|
+
- Source files are organized by domain: `common/` for universal, `taiwan/` for locale-specific
|
|
165
|
+
- CI runs on GitHub Actions (Node 18/20/22): lint, type-check, test, build
|
|
166
|
+
|
|
167
|
+
## Build Output
|
|
168
|
+
|
|
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
|
|
175
|
+
|
|
176
|
+
## Adding a New Validator
|
|
177
|
+
|
|
178
|
+
1. Create `src/validators/{domain}/{name}.ts` following the generic pattern above
|
|
179
|
+
2. Add i18n keys to all 10 locale files in `src/i18n/locales/`
|
|
180
|
+
3. Export from `src/index.ts`
|
|
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,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLocale,
|
|
3
|
+
t
|
|
4
|
+
} from "./chunk-ZBOQCXD4.js";
|
|
5
|
+
|
|
6
|
+
// src/validators/common/date.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import dayjs from "dayjs";
|
|
9
|
+
import customParseFormat from "dayjs/plugin/customParseFormat";
|
|
10
|
+
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
|
11
|
+
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
|
12
|
+
import isToday from "dayjs/plugin/isToday";
|
|
13
|
+
import weekday from "dayjs/plugin/weekday";
|
|
14
|
+
dayjs.extend(isSameOrAfter);
|
|
15
|
+
dayjs.extend(isSameOrBefore);
|
|
16
|
+
dayjs.extend(customParseFormat);
|
|
17
|
+
dayjs.extend(isToday);
|
|
18
|
+
dayjs.extend(weekday);
|
|
19
|
+
function date(required, options) {
|
|
20
|
+
const {
|
|
21
|
+
min,
|
|
22
|
+
max,
|
|
23
|
+
format = "YYYY-MM-DD",
|
|
24
|
+
includes,
|
|
25
|
+
excludes,
|
|
26
|
+
mustBePast,
|
|
27
|
+
mustBeFuture,
|
|
28
|
+
mustBeToday,
|
|
29
|
+
mustNotBeToday,
|
|
30
|
+
weekdaysOnly,
|
|
31
|
+
weekendsOnly,
|
|
32
|
+
transform,
|
|
33
|
+
defaultValue = null,
|
|
34
|
+
i18n
|
|
35
|
+
} = options ?? {};
|
|
36
|
+
const isRequired = required ?? false;
|
|
37
|
+
const actualDefaultValue = defaultValue ?? (isRequired ? "" : null);
|
|
38
|
+
const getMessage = (key, params) => {
|
|
39
|
+
if (i18n) {
|
|
40
|
+
const currentLocale = getLocale();
|
|
41
|
+
const customMessages = i18n[currentLocale];
|
|
42
|
+
if (customMessages && customMessages[key]) {
|
|
43
|
+
const template = customMessages[key];
|
|
44
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return t(`common.date.${key}`, params);
|
|
48
|
+
};
|
|
49
|
+
const preprocessFn = (val) => {
|
|
50
|
+
if (val === "" || val === null || val === void 0) {
|
|
51
|
+
return actualDefaultValue;
|
|
52
|
+
}
|
|
53
|
+
let processed = String(val).trim();
|
|
54
|
+
if (transform) {
|
|
55
|
+
processed = transform(processed);
|
|
56
|
+
}
|
|
57
|
+
return processed;
|
|
58
|
+
};
|
|
59
|
+
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable());
|
|
60
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
61
|
+
if (val === null) return;
|
|
62
|
+
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
63
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (val !== null && !dayjs(val, format, true).isValid()) {
|
|
67
|
+
ctx.addIssue({ code: "custom", message: getMessage("format", { format }) });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const dateObj = dayjs(val, format);
|
|
71
|
+
if (val !== null && min !== void 0 && !dateObj.isSameOrAfter(dayjs(min, format))) {
|
|
72
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min }) });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (val !== null && max !== void 0 && !dateObj.isSameOrBefore(dayjs(max, format))) {
|
|
76
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max }) });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (val !== null && includes !== void 0 && !val.includes(includes)) {
|
|
80
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (val !== null && excludes !== void 0) {
|
|
84
|
+
const excludeList = Array.isArray(excludes) ? excludes : [excludes];
|
|
85
|
+
for (const exclude of excludeList) {
|
|
86
|
+
if (val.includes(exclude)) {
|
|
87
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const today = dayjs().startOf("day");
|
|
93
|
+
const targetDate = dateObj.startOf("day");
|
|
94
|
+
if (val !== null && mustBePast && !targetDate.isBefore(today)) {
|
|
95
|
+
ctx.addIssue({ code: "custom", message: getMessage("past") });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (val !== null && mustBeFuture && !targetDate.isAfter(today)) {
|
|
99
|
+
ctx.addIssue({ code: "custom", message: getMessage("future") });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (val !== null && mustBeToday && !targetDate.isSame(today)) {
|
|
103
|
+
ctx.addIssue({ code: "custom", message: getMessage("today") });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (val !== null && mustNotBeToday && targetDate.isSame(today)) {
|
|
107
|
+
ctx.addIssue({ code: "custom", message: getMessage("notToday") });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (val !== null && weekdaysOnly && (dateObj.day() === 0 || dateObj.day() === 6)) {
|
|
111
|
+
ctx.addIssue({ code: "custom", message: getMessage("weekday") });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (val !== null && weekendsOnly && dateObj.day() !== 0 && dateObj.day() !== 6) {
|
|
115
|
+
ctx.addIssue({ code: "custom", message: getMessage("weekend") });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return schema;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
date
|
|
124
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLocale,
|
|
3
|
+
t
|
|
4
|
+
} from "./chunk-ZBOQCXD4.js";
|
|
5
|
+
|
|
6
|
+
// src/validators/common/number.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
function number(required, options) {
|
|
9
|
+
const { min, max, defaultValue, type = "both", positive, negative, nonNegative, nonPositive, multipleOf, precision, finite = true, transform, parseCommas = false, i18n } = options ?? {};
|
|
10
|
+
const isRequired = required ?? false;
|
|
11
|
+
const getMessage = (key, params) => {
|
|
12
|
+
if (i18n) {
|
|
13
|
+
const currentLocale = getLocale();
|
|
14
|
+
const customMessages = i18n[currentLocale];
|
|
15
|
+
if (customMessages && customMessages[key]) {
|
|
16
|
+
const template = customMessages[key];
|
|
17
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return t(`common.number.${key}`, params);
|
|
21
|
+
};
|
|
22
|
+
const actualDefaultValue = defaultValue ?? null;
|
|
23
|
+
const schema = z.preprocess(
|
|
24
|
+
(val) => {
|
|
25
|
+
if (val === "" || val === void 0 || val === null) {
|
|
26
|
+
return actualDefaultValue;
|
|
27
|
+
}
|
|
28
|
+
if (typeof val === "string") {
|
|
29
|
+
let processedVal = val.trim();
|
|
30
|
+
if (parseCommas) {
|
|
31
|
+
processedVal = processedVal.replace(/,/g, "");
|
|
32
|
+
}
|
|
33
|
+
const parsed = Number(processedVal);
|
|
34
|
+
if (isNaN(parsed)) {
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
if (transform) {
|
|
38
|
+
return transform(parsed);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
if (typeof val === "number") {
|
|
43
|
+
if (transform && Number.isFinite(val)) {
|
|
44
|
+
return transform(val);
|
|
45
|
+
}
|
|
46
|
+
return val;
|
|
47
|
+
}
|
|
48
|
+
return val;
|
|
49
|
+
},
|
|
50
|
+
z.union([z.number(), z.null(), z.nan(), z.custom((val) => val === Infinity || val === -Infinity)])
|
|
51
|
+
).superRefine((val, ctx) => {
|
|
52
|
+
if (isRequired && val === null) {
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: "custom",
|
|
55
|
+
message: getMessage("required")
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (val === null) return;
|
|
60
|
+
if (isNaN(val)) {
|
|
61
|
+
if (type === "integer") {
|
|
62
|
+
ctx.addIssue({
|
|
63
|
+
code: "custom",
|
|
64
|
+
message: getMessage("integer")
|
|
65
|
+
});
|
|
66
|
+
} else if (type === "float") {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: "custom",
|
|
69
|
+
message: getMessage("float")
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
ctx.addIssue({
|
|
73
|
+
code: "custom",
|
|
74
|
+
message: getMessage("invalid")
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (finite && !Number.isFinite(val)) {
|
|
80
|
+
ctx.addIssue({
|
|
81
|
+
code: "custom",
|
|
82
|
+
message: getMessage("finite")
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (type === "integer" && !Number.isInteger(val)) {
|
|
87
|
+
ctx.addIssue({
|
|
88
|
+
code: "custom",
|
|
89
|
+
message: getMessage("integer")
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (type === "float" && Number.isInteger(val)) {
|
|
94
|
+
ctx.addIssue({
|
|
95
|
+
code: "custom",
|
|
96
|
+
message: getMessage("float")
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (positive && val <= 0) {
|
|
101
|
+
ctx.addIssue({
|
|
102
|
+
code: "custom",
|
|
103
|
+
message: getMessage("positive")
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (negative && val >= 0) {
|
|
108
|
+
ctx.addIssue({
|
|
109
|
+
code: "custom",
|
|
110
|
+
message: getMessage("negative")
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (nonNegative && val < 0) {
|
|
115
|
+
ctx.addIssue({
|
|
116
|
+
code: "custom",
|
|
117
|
+
message: getMessage("nonNegative")
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (nonPositive && val > 0) {
|
|
122
|
+
ctx.addIssue({
|
|
123
|
+
code: "custom",
|
|
124
|
+
message: getMessage("nonPositive")
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (min !== void 0 && val < min) {
|
|
129
|
+
ctx.addIssue({
|
|
130
|
+
code: "custom",
|
|
131
|
+
message: getMessage("min", { min })
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (max !== void 0 && val > max) {
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: "custom",
|
|
138
|
+
message: getMessage("max", { max })
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (multipleOf !== void 0 && val % multipleOf !== 0) {
|
|
143
|
+
ctx.addIssue({
|
|
144
|
+
code: "custom",
|
|
145
|
+
message: getMessage("multipleOf", { multipleOf })
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (precision !== void 0) {
|
|
150
|
+
const decimalPlaces = (val.toString().split(".")[1] || "").length;
|
|
151
|
+
if (decimalPlaces > precision) {
|
|
152
|
+
ctx.addIssue({
|
|
153
|
+
code: "custom",
|
|
154
|
+
message: getMessage("precision", { precision })
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return schema;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
number
|
|
165
|
+
};
|