@hy_ong/zod-kit 0.2.0 → 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.
Files changed (189) hide show
  1. package/.github/workflows/ci.yml +24 -0
  2. package/CLAUDE.md +64 -22
  3. package/dist/chunk-36NWHESN.js +124 -0
  4. package/dist/chunk-4LYZAO3P.js +165 -0
  5. package/dist/chunk-5GAZQDVS.cjs +206 -0
  6. package/dist/chunk-5LS4DSRQ.cjs +127 -0
  7. package/dist/chunk-5OGW2ERW.js +181 -0
  8. package/dist/chunk-5ZEKWPSE.cjs +69 -0
  9. package/dist/chunk-6OGDPSWT.js +135 -0
  10. package/dist/chunk-6X22I6NQ.cjs +136 -0
  11. package/dist/chunk-77KZUPPN.cjs +177 -0
  12. package/dist/chunk-AANSHH2O.cjs +165 -0
  13. package/dist/chunk-AI72FMOF.cjs +130 -0
  14. package/dist/chunk-AWV2IT66.js +146 -0
  15. package/dist/chunk-B3U5G3AA.js +160 -0
  16. package/dist/chunk-CFFCBWYL.cjs +99 -0
  17. package/dist/chunk-DPXRMSB2.js +130 -0
  18. package/dist/chunk-DRXPGQM6.cjs +135 -0
  19. package/dist/chunk-EAU42EVH.js +161 -0
  20. package/dist/chunk-FC6VDOC7.js +206 -0
  21. package/dist/chunk-FVO4743A.cjs +134 -0
  22. package/dist/chunk-G6DV7LX7.cjs +161 -0
  23. package/dist/chunk-I2RJMDXN.js +90 -0
  24. package/dist/chunk-IJEEM3DI.js +136 -0
  25. package/dist/chunk-JBNCMS42.cjs +151 -0
  26. package/dist/chunk-JZ2SHRGZ.js +87 -0
  27. package/dist/chunk-KARFFIMP.js +696 -0
  28. package/dist/chunk-LIQSVJLS.js +177 -0
  29. package/dist/chunk-LKPXHW5N.cjs +181 -0
  30. package/dist/chunk-MAQRXYE6.js +118 -0
  31. package/dist/chunk-MCDESS3T.js +69 -0
  32. package/dist/chunk-MG25BEV4.cjs +160 -0
  33. package/dist/chunk-NKCYXBGX.js +99 -0
  34. package/dist/chunk-OEK7QSQP.js +75 -0
  35. package/dist/chunk-OMFQ7Z63.cjs +696 -0
  36. package/dist/chunk-OP4KV3BY.cjs +124 -0
  37. package/dist/chunk-P2NONIMS.js +257 -0
  38. package/dist/chunk-P364KRO5.js +61 -0
  39. package/dist/chunk-PGSDXR2I.js +71 -0
  40. package/dist/chunk-PL2GERLG.cjs +61 -0
  41. package/dist/chunk-R5G4V7C6.cjs +75 -0
  42. package/dist/chunk-RKHX3DGH.js +127 -0
  43. package/dist/chunk-TSHL7ZO2.js +134 -0
  44. package/dist/chunk-UFNVCUPQ.cjs +301 -0
  45. package/dist/chunk-VCRKYMJM.js +301 -0
  46. package/dist/chunk-VDOAPLA6.cjs +257 -0
  47. package/dist/chunk-VP5CCP5F.cjs +90 -0
  48. package/dist/chunk-W2EWMV3A.cjs +87 -0
  49. package/dist/chunk-WWRFBLCR.cjs +146 -0
  50. package/dist/chunk-YALLOVNO.cjs +118 -0
  51. package/dist/chunk-YAU6JCYL.cjs +71 -0
  52. package/dist/chunk-YWV2BBXN.cjs +2526 -0
  53. package/dist/chunk-ZBOQCXD4.js +2526 -0
  54. package/dist/chunk-ZFQQXWNB.js +151 -0
  55. package/dist/common/boolean.cjs +7 -0
  56. package/dist/common/boolean.d.cts +119 -0
  57. package/dist/common/boolean.d.ts +119 -0
  58. package/dist/common/boolean.js +7 -0
  59. package/dist/common/color.cjs +9 -0
  60. package/dist/common/color.d.cts +26 -0
  61. package/dist/common/color.d.ts +26 -0
  62. package/dist/common/color.js +9 -0
  63. package/dist/common/coordinate.cjs +11 -0
  64. package/dist/common/coordinate.d.cts +23 -0
  65. package/dist/common/coordinate.d.ts +23 -0
  66. package/dist/common/coordinate.js +11 -0
  67. package/dist/common/credit-card.cjs +11 -0
  68. package/dist/common/credit-card.d.cts +22 -0
  69. package/dist/common/credit-card.d.ts +22 -0
  70. package/dist/common/credit-card.js +11 -0
  71. package/dist/common/date.cjs +7 -0
  72. package/dist/common/date.d.cts +174 -0
  73. package/dist/common/date.d.ts +174 -0
  74. package/dist/common/date.js +7 -0
  75. package/dist/common/datetime.cjs +15 -0
  76. package/dist/common/datetime.d.cts +301 -0
  77. package/dist/common/datetime.d.ts +301 -0
  78. package/dist/common/datetime.js +15 -0
  79. package/dist/common/email.cjs +7 -0
  80. package/dist/common/email.d.cts +149 -0
  81. package/dist/common/email.d.ts +149 -0
  82. package/dist/common/email.js +7 -0
  83. package/dist/common/file.cjs +7 -0
  84. package/dist/common/file.d.cts +178 -0
  85. package/dist/common/file.d.ts +178 -0
  86. package/dist/common/file.js +7 -0
  87. package/dist/common/id.cjs +13 -0
  88. package/dist/common/id.d.cts +262 -0
  89. package/dist/common/id.d.ts +262 -0
  90. package/dist/common/id.js +13 -0
  91. package/dist/common/ip.cjs +11 -0
  92. package/dist/common/ip.d.cts +25 -0
  93. package/dist/common/ip.d.ts +25 -0
  94. package/dist/common/ip.js +11 -0
  95. package/dist/common/number.cjs +7 -0
  96. package/dist/common/number.d.cts +167 -0
  97. package/dist/common/number.d.ts +167 -0
  98. package/dist/common/number.js +7 -0
  99. package/dist/common/password.cjs +7 -0
  100. package/dist/common/password.d.cts +192 -0
  101. package/dist/common/password.d.ts +192 -0
  102. package/dist/common/password.js +7 -0
  103. package/dist/common/text.cjs +7 -0
  104. package/dist/common/text.d.cts +156 -0
  105. package/dist/common/text.d.ts +156 -0
  106. package/dist/common/text.js +7 -0
  107. package/dist/common/time.cjs +15 -0
  108. package/dist/common/time.d.cts +268 -0
  109. package/dist/common/time.d.ts +268 -0
  110. package/dist/common/time.js +15 -0
  111. package/dist/common/url.cjs +7 -0
  112. package/dist/common/url.d.cts +196 -0
  113. package/dist/common/url.d.ts +196 -0
  114. package/dist/common/url.js +7 -0
  115. package/dist/config-CABSSvAp.d.cts +5 -0
  116. package/dist/config-CABSSvAp.d.ts +5 -0
  117. package/dist/index.cjs +180 -5255
  118. package/dist/index.d.cts +28 -3150
  119. package/dist/index.d.ts +28 -3150
  120. package/dist/index.js +135 -5131
  121. package/dist/taiwan/bank-account.cjs +11 -0
  122. package/dist/taiwan/bank-account.d.cts +22 -0
  123. package/dist/taiwan/bank-account.d.ts +22 -0
  124. package/dist/taiwan/bank-account.js +11 -0
  125. package/dist/taiwan/business-id.cjs +9 -0
  126. package/dist/taiwan/business-id.d.cts +133 -0
  127. package/dist/taiwan/business-id.d.ts +133 -0
  128. package/dist/taiwan/business-id.js +9 -0
  129. package/dist/taiwan/fax.cjs +9 -0
  130. package/dist/taiwan/fax.d.cts +157 -0
  131. package/dist/taiwan/fax.d.ts +157 -0
  132. package/dist/taiwan/fax.js +9 -0
  133. package/dist/taiwan/invoice.cjs +9 -0
  134. package/dist/taiwan/invoice.d.cts +17 -0
  135. package/dist/taiwan/invoice.d.ts +17 -0
  136. package/dist/taiwan/invoice.js +9 -0
  137. package/dist/taiwan/license-plate.cjs +9 -0
  138. package/dist/taiwan/license-plate.d.cts +19 -0
  139. package/dist/taiwan/license-plate.d.ts +19 -0
  140. package/dist/taiwan/license-plate.js +9 -0
  141. package/dist/taiwan/mobile.cjs +9 -0
  142. package/dist/taiwan/mobile.d.cts +146 -0
  143. package/dist/taiwan/mobile.d.ts +146 -0
  144. package/dist/taiwan/mobile.js +9 -0
  145. package/dist/taiwan/national-id.cjs +15 -0
  146. package/dist/taiwan/national-id.d.cts +214 -0
  147. package/dist/taiwan/national-id.d.ts +214 -0
  148. package/dist/taiwan/national-id.js +15 -0
  149. package/dist/taiwan/passport.cjs +9 -0
  150. package/dist/taiwan/passport.d.cts +19 -0
  151. package/dist/taiwan/passport.d.ts +19 -0
  152. package/dist/taiwan/passport.js +9 -0
  153. package/dist/taiwan/postal-code.cjs +17 -0
  154. package/dist/taiwan/postal-code.d.cts +237 -0
  155. package/dist/taiwan/postal-code.d.ts +237 -0
  156. package/dist/taiwan/postal-code.js +17 -0
  157. package/dist/taiwan/tel.cjs +9 -0
  158. package/dist/taiwan/tel.d.cts +162 -0
  159. package/dist/taiwan/tel.d.ts +162 -0
  160. package/dist/taiwan/tel.js +9 -0
  161. package/package.json +128 -2
  162. package/src/i18n/locales/en-GB.json +43 -0
  163. package/src/i18n/locales/en-US.json +43 -0
  164. package/src/i18n/locales/id-ID.json +43 -0
  165. package/src/i18n/locales/ja-JP.json +43 -0
  166. package/src/i18n/locales/ko-KR.json +43 -0
  167. package/src/i18n/locales/ms-MY.json +43 -0
  168. package/src/i18n/locales/th-TH.json +43 -0
  169. package/src/i18n/locales/vi-VN.json +43 -0
  170. package/src/i18n/locales/zh-CN.json +43 -0
  171. package/src/i18n/locales/zh-TW.json +43 -0
  172. package/src/index.ts +10 -2
  173. package/src/validators/common/color.ts +192 -0
  174. package/src/validators/common/coordinate.ts +159 -0
  175. package/src/validators/common/credit-card.ts +134 -0
  176. package/src/validators/common/ip.ts +210 -0
  177. package/src/validators/taiwan/bank-account.ts +176 -0
  178. package/src/validators/taiwan/invoice.ts +84 -0
  179. package/src/validators/taiwan/license-plate.ts +110 -0
  180. package/src/validators/taiwan/passport.ts +103 -0
  181. package/tests/common/color.test.ts +587 -0
  182. package/tests/common/coordinate.test.ts +345 -0
  183. package/tests/common/credit-card.test.ts +378 -0
  184. package/tests/common/ip.test.ts +419 -0
  185. package/tests/taiwan/bank-account.test.ts +286 -0
  186. package/tests/taiwan/invoice.test.ts +227 -0
  187. package/tests/taiwan/license-plate.test.ts +280 -0
  188. package/tests/taiwan/passport.test.ts +277 -0
  189. 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 (English, Traditional Chinese). It includes common validators (email, password, text, number, etc.) and Taiwan-specific validators (National ID, Business ID, mobile, tel, fax, postal code).
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.1.16
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 (743 test cases across 17 files)
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
- # Equivalent to: tsup src/index.ts --format esm,cjs --dts
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 # English messages
46
- └── zh-TW.json # Traditional Chinese messages
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/ # 11 universal validators
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/ # 6 Taiwan-specific validators
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/ # 11 test files
70
- └── taiwan/ # 6 test files
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
- - Locale state is global: `setLocale("en")` / `setLocale("zh-TW")`
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
- - No CI/CD pipelines tests and builds are run manually or via `prepublishOnly` hook
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 four files:
131
- - `index.js` — ESM bundle
132
- - `index.cjs` — CommonJS bundle
133
- - `index.d.ts` — TypeScript declarations (ESM)
134
- - `index.d.cts` — TypeScript declarations (CJS)
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 both `src/i18n/locales/en.json` and `zh-TW.json`
179
+ 2. Add i18n keys to all 10 locale files in `src/i18n/locales/`
140
180
  3. Export from `src/index.ts`
141
- 4. Create `tests/{domain}/{name}.test.ts` with comprehensive coverage
142
- 5. Test with `npx vitest run tests/{domain}/{name}.test.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
+ };
@@ -0,0 +1,206 @@
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 _chunkYWV2BBXNcjs = require('./chunk-YWV2BBXN.cjs');
5
+
6
+ // src/validators/common/id.ts
7
+ var _zod = require('zod');
8
+ var ID_PATTERNS = {
9
+ numeric: /^\d+$/,
10
+ uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
11
+ objectId: /^[0-9a-f]{24}$/i,
12
+ nanoid: /^[A-Za-z0-9_-]{21}$/,
13
+ snowflake: /^\d{19}$/,
14
+ cuid: /^c[a-z0-9]{24}$/,
15
+ ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/,
16
+ shortid: /^[A-Za-z0-9_-]{7,14}$/
17
+ };
18
+ var detectIdType = (value) => {
19
+ const orderedTypes = [
20
+ ["uuid", ID_PATTERNS.uuid],
21
+ ["objectId", ID_PATTERNS.objectId],
22
+ ["snowflake", ID_PATTERNS.snowflake],
23
+ ["cuid", ID_PATTERNS.cuid],
24
+ ["ulid", ID_PATTERNS.ulid],
25
+ ["nanoid", ID_PATTERNS.nanoid],
26
+ ["numeric", ID_PATTERNS.numeric],
27
+ ["shortid", ID_PATTERNS.shortid]
28
+ // 放最後,因為最通用
29
+ ];
30
+ for (const [type, pattern] of orderedTypes) {
31
+ if (pattern.test(value)) {
32
+ return type;
33
+ }
34
+ }
35
+ return null;
36
+ };
37
+ var validateIdType = (value, type) => {
38
+ if (type === "auto") {
39
+ return detectIdType(value) !== null;
40
+ }
41
+ const pattern = ID_PATTERNS[type];
42
+ return pattern ? pattern.test(value) : false;
43
+ };
44
+ function id(required, options) {
45
+ const { type = "auto", minLength, maxLength, allowedTypes, customRegex, includes, excludes, startsWith, endsWith, caseSensitive = true, transform, defaultValue, i18n } = _nullishCoalesce(options, () => ( {}));
46
+ const isRequired = _nullishCoalesce(required, () => ( false));
47
+ const isNumericType = type === "numeric";
48
+ const actualDefaultValue = defaultValue !== void 0 ? defaultValue : isRequired ? isNumericType ? NaN : "" : null;
49
+ const getMessage = (key, params) => {
50
+ if (i18n) {
51
+ const currentLocale = _chunkYWV2BBXNcjs.getLocale.call(void 0, );
52
+ const customMessages = i18n[currentLocale];
53
+ if (customMessages && customMessages[key]) {
54
+ const template = customMessages[key];
55
+ return template.replace(/\$\{(\w+)}/g, (_match, k) => _nullishCoalesce(_optionalChain([params, 'optionalAccess', _ => _[k]]), () => ( "")));
56
+ }
57
+ }
58
+ return _chunkYWV2BBXNcjs.t.call(void 0, `common.id.${key}`, params);
59
+ };
60
+ const preprocessNumericFn = (val) => {
61
+ if (val === "" || val === null || val === void 0) {
62
+ if (isRequired && defaultValue === void 0) {
63
+ return void 0;
64
+ }
65
+ return actualDefaultValue;
66
+ }
67
+ return Number(val);
68
+ };
69
+ const preprocessStringFn = (val) => {
70
+ if (val === "" || val === null || val === void 0) {
71
+ return actualDefaultValue;
72
+ }
73
+ let processed = String(val);
74
+ if (transform) {
75
+ processed = transform(processed);
76
+ }
77
+ return processed;
78
+ };
79
+ if (isNumericType) {
80
+ const numericSchema = _zod.z.preprocess(preprocessNumericFn, _zod.z.any()).superRefine((val, ctx) => {
81
+ if (!isRequired && val === null) return;
82
+ if (val === void 0 || isRequired && val === null) {
83
+ ctx.addIssue({ code: "custom", message: getMessage("required") });
84
+ return;
85
+ }
86
+ if (typeof val !== "number" || isNaN(val)) {
87
+ ctx.addIssue({ code: "custom", message: getMessage("numeric") });
88
+ return;
89
+ }
90
+ const strVal = String(val);
91
+ if (!ID_PATTERNS.numeric.test(strVal)) {
92
+ ctx.addIssue({ code: "custom", message: getMessage("numeric") });
93
+ return;
94
+ }
95
+ if (minLength !== void 0 && strVal.length < minLength) {
96
+ ctx.addIssue({ code: "custom", message: getMessage("minLength", { minLength }) });
97
+ return;
98
+ }
99
+ if (maxLength !== void 0 && strVal.length > maxLength) {
100
+ ctx.addIssue({ code: "custom", message: getMessage("maxLength", { maxLength }) });
101
+ return;
102
+ }
103
+ });
104
+ return numericSchema;
105
+ }
106
+ const baseSchema = isRequired ? _zod.z.preprocess(preprocessStringFn, _zod.z.string()) : _zod.z.preprocess(preprocessStringFn, _zod.z.string().nullable());
107
+ const schema = baseSchema.superRefine((val, ctx) => {
108
+ if (val === null) return;
109
+ if (isRequired && (val === "" || val === "null" || val === "undefined")) {
110
+ ctx.addIssue({ code: "custom", message: getMessage("required") });
111
+ return;
112
+ }
113
+ const comparisonVal = !caseSensitive ? val.toLowerCase() : val;
114
+ if (val !== null && minLength !== void 0 && val.length < minLength) {
115
+ ctx.addIssue({ code: "custom", message: getMessage("minLength", { minLength }) });
116
+ return;
117
+ }
118
+ if (val !== null && maxLength !== void 0 && val.length > maxLength) {
119
+ ctx.addIssue({ code: "custom", message: getMessage("maxLength", { maxLength }) });
120
+ return;
121
+ }
122
+ const hasContentValidations = customRegex !== void 0 || startsWith !== void 0 || endsWith !== void 0 || includes !== void 0 || excludes !== void 0;
123
+ if (val !== null && customRegex !== void 0) {
124
+ if (!customRegex.test(val)) {
125
+ ctx.addIssue({ code: "custom", message: getMessage("customFormat") });
126
+ return;
127
+ }
128
+ } else if (val !== null && !hasContentValidations) {
129
+ let isValidId;
130
+ if (allowedTypes && allowedTypes.length > 0) {
131
+ isValidId = allowedTypes.some((allowedType) => validateIdType(val, allowedType));
132
+ if (!isValidId) {
133
+ const typeNames = allowedTypes.join(", ");
134
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})` });
135
+ return;
136
+ }
137
+ } else if (type && type !== "auto") {
138
+ isValidId = validateIdType(val, type);
139
+ if (!isValidId) {
140
+ ctx.addIssue({ code: "custom", message: getMessage(type) || getMessage("invalid") });
141
+ return;
142
+ }
143
+ } else {
144
+ isValidId = detectIdType(val) !== null;
145
+ if (!isValidId) {
146
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") });
147
+ return;
148
+ }
149
+ }
150
+ } else if (val !== null && hasContentValidations && type && type !== "auto" && !customRegex) {
151
+ if (allowedTypes && allowedTypes.length > 0) {
152
+ const isValidType = allowedTypes.some((allowedType) => validateIdType(val, allowedType));
153
+ if (!isValidType) {
154
+ const typeNames = allowedTypes.join(", ");
155
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})` });
156
+ return;
157
+ }
158
+ } else {
159
+ if (!validateIdType(val, type)) {
160
+ ctx.addIssue({ code: "custom", message: getMessage(type) || getMessage("invalid") });
161
+ return;
162
+ }
163
+ }
164
+ }
165
+ const searchStartsWith = !caseSensitive && startsWith ? startsWith.toLowerCase() : startsWith;
166
+ const searchEndsWith = !caseSensitive && endsWith ? endsWith.toLowerCase() : endsWith;
167
+ const searchIncludes = !caseSensitive && includes ? includes.toLowerCase() : includes;
168
+ if (val !== null && startsWith !== void 0 && !comparisonVal.startsWith(searchStartsWith)) {
169
+ ctx.addIssue({ code: "custom", message: getMessage("startsWith", { startsWith }) });
170
+ return;
171
+ }
172
+ if (val !== null && endsWith !== void 0 && !comparisonVal.endsWith(searchEndsWith)) {
173
+ ctx.addIssue({ code: "custom", message: getMessage("endsWith", { endsWith }) });
174
+ return;
175
+ }
176
+ if (val !== null && includes !== void 0 && !comparisonVal.includes(searchIncludes)) {
177
+ ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) });
178
+ return;
179
+ }
180
+ if (val !== null && excludes !== void 0) {
181
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
182
+ for (const exclude of excludeList) {
183
+ const searchExclude = !caseSensitive ? exclude.toLowerCase() : exclude;
184
+ if (comparisonVal.includes(searchExclude)) {
185
+ ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) });
186
+ return;
187
+ }
188
+ }
189
+ }
190
+ }).transform((val) => {
191
+ if (val === null) return val;
192
+ const shouldPreserveCase = type === "uuid" || type === "objectId";
193
+ if (!caseSensitive && !shouldPreserveCase) {
194
+ return val.toLowerCase();
195
+ }
196
+ return val;
197
+ });
198
+ return schema;
199
+ }
200
+
201
+
202
+
203
+
204
+
205
+
206
+ exports.ID_PATTERNS = ID_PATTERNS; exports.detectIdType = detectIdType; exports.validateIdType = validateIdType; exports.id = id;