@hy_ong/zod-kit 0.0.4 → 0.0.6
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/.claude/settings.local.json +28 -0
- package/LICENSE +21 -0
- package/README.md +465 -97
- package/debug.js +21 -0
- package/debug.ts +16 -0
- package/dist/index.cjs +3127 -146
- package/dist/index.d.cts +3021 -25
- package/dist/index.d.ts +3021 -25
- package/dist/index.js +3081 -144
- package/eslint.config.mts +8 -0
- package/package.json +10 -9
- package/src/config.ts +1 -1
- package/src/i18n/locales/en.json +161 -25
- package/src/i18n/locales/zh-TW.json +165 -26
- package/src/index.ts +17 -7
- package/src/validators/common/boolean.ts +191 -0
- package/src/validators/common/date.ts +299 -0
- package/src/validators/common/datetime.ts +673 -0
- package/src/validators/common/email.ts +313 -0
- package/src/validators/common/file.ts +384 -0
- package/src/validators/common/id.ts +471 -0
- package/src/validators/common/number.ts +319 -0
- package/src/validators/common/password.ts +386 -0
- package/src/validators/common/text.ts +271 -0
- package/src/validators/common/time.ts +600 -0
- package/src/validators/common/url.ts +347 -0
- package/src/validators/taiwan/business-id.ts +262 -0
- package/src/validators/taiwan/fax.ts +327 -0
- package/src/validators/taiwan/mobile.ts +242 -0
- package/src/validators/taiwan/national-id.ts +425 -0
- package/src/validators/taiwan/postal-code.ts +1049 -0
- package/src/validators/taiwan/tel.ts +330 -0
- package/tests/common/boolean.test.ts +340 -92
- package/tests/common/date.test.ts +458 -0
- package/tests/common/datetime.test.ts +693 -0
- package/tests/common/email.test.ts +232 -60
- package/tests/common/file.test.ts +479 -0
- package/tests/common/id.test.ts +535 -0
- package/tests/common/number.test.ts +230 -60
- package/tests/common/password.test.ts +271 -44
- package/tests/common/text.test.ts +210 -13
- package/tests/common/time.test.ts +528 -0
- package/tests/common/url.test.ts +492 -67
- package/tests/taiwan/business-id.test.ts +240 -0
- package/tests/taiwan/fax.test.ts +463 -0
- package/tests/taiwan/mobile.test.ts +373 -0
- package/tests/taiwan/national-id.test.ts +435 -0
- package/tests/taiwan/postal-code.test.ts +705 -0
- package/tests/taiwan/tel.test.ts +467 -0
- package/eslint.config.mjs +0 -10
- package/src/common/boolean.ts +0 -36
- package/src/common/date.ts +0 -43
- package/src/common/email.ts +0 -44
- package/src/common/integer.ts +0 -46
- package/src/common/number.ts +0 -37
- package/src/common/password.ts +0 -33
- package/src/common/text.ts +0 -34
- package/src/common/url.ts +0 -37
- package/tests/common/integer.test.ts +0 -90
|
@@ -1,77 +1,249 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from "vitest"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
messages: {
|
|
19
|
-
required: "必填",
|
|
20
|
-
min: "長度至少 20 字元",
|
|
21
|
-
max: "長度最多 10 字元",
|
|
22
|
-
includes: "必須包含「company」",
|
|
23
|
-
invalid: "格式錯誤",
|
|
24
|
-
domain: "必須為 @example.com 網域",
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
] as const
|
|
28
|
-
|
|
29
|
-
describe.each(locales)("email() locale: $locale", ({ locale, messages }) => {
|
|
30
|
-
beforeEach(() => setLocale(locale as Locale))
|
|
31
|
-
|
|
32
|
-
it("should pass for valid email", () => {
|
|
33
|
-
const schema = email()
|
|
34
|
-
expect(schema.parse("test@example.com")).toBe("test@example.com")
|
|
2
|
+
import { email, setLocale } from "../../src"
|
|
3
|
+
|
|
4
|
+
describe("email() features", () => {
|
|
5
|
+
beforeEach(() => setLocale("en"))
|
|
6
|
+
|
|
7
|
+
describe("multiple domain support", () => {
|
|
8
|
+
it("should accept multiple allowed domains", () => {
|
|
9
|
+
const schema = email({ domain: ["example.com", "company.org"] })
|
|
10
|
+
expect(schema.parse("test@example.com")).toBe("test@example.com")
|
|
11
|
+
expect(schema.parse("user@company.org")).toBe("user@company.org")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("should reject domains not in whitelist", () => {
|
|
15
|
+
const schema = email({ domain: ["example.com", "company.org"] })
|
|
16
|
+
expect(() => schema.parse("test@notallowed.com")).toThrow("Must be from domain: example.com, company.org")
|
|
17
|
+
})
|
|
35
18
|
})
|
|
36
19
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
describe("subdomain support", () => {
|
|
21
|
+
it("should allow subdomains by default", () => {
|
|
22
|
+
const schema = email({ domain: "example.com" })
|
|
23
|
+
expect(schema.parse("test@mail.example.com")).toBe("test@mail.example.com")
|
|
24
|
+
expect(schema.parse("user@api.example.com")).toBe("user@api.example.com")
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("should reject subdomains when allowSubdomains=false", () => {
|
|
28
|
+
const schema = email({ domain: "example.com", allowSubdomains: false })
|
|
29
|
+
expect(schema.parse("test@example.com")).toBe("test@example.com")
|
|
30
|
+
expect(() => schema.parse("test@mail.example.com")).toThrow("Must be from domain: example.com")
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe("domain blacklist", () => {
|
|
35
|
+
it("should reject blacklisted domains", () => {
|
|
36
|
+
const schema = email({ domainBlacklist: ["spam.com", "fake.org"] })
|
|
37
|
+
expect(schema.parse("test@example.com")).toBe("test@example.com")
|
|
38
|
+
expect(() => schema.parse("test@spam.com")).toThrow("Domain spam.com is not allowed")
|
|
39
|
+
expect(() => schema.parse("user@fake.org")).toThrow("Domain fake.org is not allowed")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("should reject blacklisted subdomains when allowSubdomains=true", () => {
|
|
43
|
+
const schema = email({ domainBlacklist: ["spam.com"], allowSubdomains: true })
|
|
44
|
+
expect(() => schema.parse("test@mail.spam.com")).toThrow("Domain mail.spam.com is not allowed")
|
|
45
|
+
})
|
|
40
46
|
})
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
describe("business email validation", () => {
|
|
49
|
+
it("should reject free email providers when businessOnly=true", () => {
|
|
50
|
+
const schema = email({ businessOnly: true })
|
|
51
|
+
expect(schema.parse("john@company.com")).toBe("john@company.com")
|
|
52
|
+
expect(() => schema.parse("john@gmail.com")).toThrow("Only business email addresses are allowed")
|
|
53
|
+
expect(() => schema.parse("jane@yahoo.com")).toThrow("Only business email addresses are allowed")
|
|
54
|
+
expect(() => schema.parse("test@hotmail.com")).toThrow("Only business email addresses are allowed")
|
|
55
|
+
})
|
|
47
56
|
})
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
describe("disposable email detection", () => {
|
|
59
|
+
it("should reject disposable email addresses when noDisposable=true", () => {
|
|
60
|
+
const schema = email({ noDisposable: true })
|
|
61
|
+
expect(schema.parse("john@company.com")).toBe("john@company.com")
|
|
62
|
+
expect(() => schema.parse("test@10minutemail.com")).toThrow("Disposable email addresses are not allowed")
|
|
63
|
+
expect(() => schema.parse("user@tempmail.org")).toThrow("Disposable email addresses are not allowed")
|
|
64
|
+
expect(() => schema.parse("temp@mailinator.com")).toThrow("Disposable email addresses are not allowed")
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe("case handling", () => {
|
|
69
|
+
it("should convert to lowercase by default", () => {
|
|
70
|
+
const schema = email()
|
|
71
|
+
expect(schema.parse("Test@EXAMPLE.COM")).toBe("test@example.com")
|
|
72
|
+
expect(schema.parse("USER@Company.Org")).toBe("user@company.org")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("should preserve case when lowercase=false", () => {
|
|
76
|
+
const schema = email({ lowercase: false })
|
|
77
|
+
expect(schema.parse("Test@EXAMPLE.COM")).toBe("Test@EXAMPLE.COM")
|
|
78
|
+
expect(schema.parse("USER@Company.Org")).toBe("USER@Company.Org")
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe("excludes functionality", () => {
|
|
83
|
+
it("should reject emails containing excluded strings", () => {
|
|
84
|
+
const schema = email({ excludes: ["test", "demo"] })
|
|
85
|
+
expect(schema.parse("john@example.com")).toBe("john@example.com")
|
|
86
|
+
expect(() => schema.parse("test@example.com")).toThrow("Must include test")
|
|
87
|
+
expect(() => schema.parse("demo.user@example.com")).toThrow("Must include demo")
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it("should handle excludes as array", () => {
|
|
91
|
+
const schema = email({ excludes: ["spam", "fake", "test"] })
|
|
92
|
+
expect(() => schema.parse("spam@example.com")).toThrow("Must include spam")
|
|
93
|
+
expect(() => schema.parse("fake.user@example.com")).toThrow("Must include fake")
|
|
94
|
+
expect(() => schema.parse("user@test.com")).toThrow("Must include test")
|
|
95
|
+
})
|
|
54
96
|
})
|
|
55
97
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
describe("transform functionality", () => {
|
|
99
|
+
it("should apply transform function", () => {
|
|
100
|
+
const schema = email({
|
|
101
|
+
transform: (val) => val.replace(/test/g, "user"), // Replace test with user
|
|
102
|
+
})
|
|
103
|
+
expect(schema.parse("test@example.com")).toBe("user@example.com")
|
|
104
|
+
expect(schema.parse("test.email@company.org")).toBe("user.email@company.org")
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("should apply transform after lowercase", () => {
|
|
108
|
+
const schema = email({
|
|
109
|
+
lowercase: true,
|
|
110
|
+
transform: (val) => val.replace(/\+[^@]*/, ""), // Remove + aliases
|
|
111
|
+
})
|
|
112
|
+
expect(schema.parse("USER+tag@EXAMPLE.COM")).toBe("user@example.com")
|
|
113
|
+
})
|
|
59
114
|
})
|
|
60
115
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
116
|
+
describe("default value functionality", () => {
|
|
117
|
+
it("should use defaultValue for empty input when required", () => {
|
|
118
|
+
const schema = email({ defaultValue: "default@example.com" })
|
|
119
|
+
expect(schema.parse("")).toBe("default@example.com")
|
|
120
|
+
expect(schema.parse(null)).toBe("default@example.com")
|
|
121
|
+
expect(schema.parse(undefined)).toBe("default@example.com")
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("should use defaultValue for empty input when not required", () => {
|
|
125
|
+
const schema = email({ required: false, defaultValue: "default@example.com" })
|
|
126
|
+
expect(schema.parse("")).toBe("default@example.com")
|
|
127
|
+
expect(schema.parse(null)).toBe("default@example.com")
|
|
128
|
+
expect(schema.parse(undefined)).toBe("default@example.com")
|
|
129
|
+
})
|
|
64
130
|
})
|
|
65
131
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
132
|
+
describe("custom i18n messages", () => {
|
|
133
|
+
it("should use custom messages when provided", () => {
|
|
134
|
+
const schema = email({
|
|
135
|
+
domain: "company.com",
|
|
136
|
+
businessOnly: true,
|
|
137
|
+
i18n: {
|
|
138
|
+
en: {
|
|
139
|
+
required: "Custom required message",
|
|
140
|
+
domain: "Custom domain message: ${domain}",
|
|
141
|
+
businessOnly: "Custom business only message",
|
|
142
|
+
},
|
|
143
|
+
"zh-TW": {
|
|
144
|
+
required: "客製化必填訊息",
|
|
145
|
+
domain: "客製化網域訊息: ${domain}",
|
|
146
|
+
businessOnly: "客製化僅限企業郵箱訊息",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
expect(() => schema.parse("")).toThrow("Custom required message")
|
|
152
|
+
expect(() => schema.parse("test@example.com")).toThrow("Custom domain message: company.com")
|
|
153
|
+
expect(() => schema.parse("test@gmail.com")).toThrow("Custom business only message")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it("should fallback to default messages when custom not provided", () => {
|
|
157
|
+
const schema = email({
|
|
158
|
+
noDisposable: true,
|
|
159
|
+
i18n: {
|
|
160
|
+
en: {
|
|
161
|
+
required: "Custom required message",
|
|
162
|
+
},
|
|
163
|
+
"zh-TW": {
|
|
164
|
+
required: "客製化必填訊息",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
expect(() => schema.parse("")).toThrow("Custom required message")
|
|
170
|
+
expect(() => schema.parse("test@tempmail.org")).toThrow("Disposable email addresses are not allowed")
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("should use correct locale for custom messages", () => {
|
|
174
|
+
setLocale("en")
|
|
175
|
+
const schemaEn = email({
|
|
176
|
+
businessOnly: true,
|
|
177
|
+
i18n: {
|
|
178
|
+
en: {
|
|
179
|
+
businessOnly: "English business message",
|
|
180
|
+
},
|
|
181
|
+
"zh-TW": {
|
|
182
|
+
businessOnly: "繁體中文企業訊息",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
expect(() => schemaEn.parse("test@gmail.com")).toThrow("English business message")
|
|
187
|
+
|
|
188
|
+
setLocale("zh-TW")
|
|
189
|
+
const schemaZh = email({
|
|
190
|
+
businessOnly: true,
|
|
191
|
+
i18n: {
|
|
192
|
+
en: {
|
|
193
|
+
businessOnly: "English business message",
|
|
194
|
+
},
|
|
195
|
+
"zh-TW": {
|
|
196
|
+
businessOnly: "繁體中文企業訊息",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
expect(() => schemaZh.parse("test@gmail.com")).toThrow("繁體中文企業訊息")
|
|
201
|
+
})
|
|
70
202
|
})
|
|
71
203
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
204
|
+
describe("complex scenarios", () => {
|
|
205
|
+
it("should work with multiple validations", () => {
|
|
206
|
+
const schema = email({
|
|
207
|
+
domain: ["company.com", "business.org"],
|
|
208
|
+
minLength: 10,
|
|
209
|
+
maxLength: 30,
|
|
210
|
+
businessOnly: true,
|
|
211
|
+
noDisposable: true,
|
|
212
|
+
allowSubdomains: false,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
expect(schema.parse("john@company.com")).toBe("john@company.com")
|
|
216
|
+
expect(schema.parse("jane@business.org")).toBe("jane@business.org")
|
|
217
|
+
|
|
218
|
+
expect(() => schema.parse("a@co.me")).toThrow("Must be at least 10 characters")
|
|
219
|
+
expect(() => schema.parse("verylongusernamethatexceedslimit@company.com")).toThrow("Must be at most 30 characters")
|
|
220
|
+
expect(() => schema.parse("test@mail.company.com")).toThrow("Must be from domain: company.com, business.org")
|
|
221
|
+
expect(() => schema.parse("user@gmail.com")).toThrow("Only business email addresses are allowed")
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it("should handle null values correctly when not required", () => {
|
|
225
|
+
const schema = email({
|
|
226
|
+
required: false,
|
|
227
|
+
domain: "example.com",
|
|
228
|
+
minLength: 10,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
expect(schema.parse(null)).toBe(null)
|
|
232
|
+
expect(schema.parse("")).toBe(null)
|
|
233
|
+
expect(schema.parse(undefined)).toBe(null)
|
|
234
|
+
expect(schema.parse("longuser@example.com")).toBe("longuser@example.com")
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it("should work with transform, lowercase, and domain validation together", () => {
|
|
238
|
+
const schema = email({
|
|
239
|
+
domain: "company.com",
|
|
240
|
+
lowercase: true,
|
|
241
|
+
transform: (val) => val.replace(/\+[^@]*/, ""), // Remove + aliases
|
|
242
|
+
allowSubdomains: true,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
expect(schema.parse("USER+tag@MAIL.COMPANY.COM")).toBe("user@mail.company.com")
|
|
246
|
+
expect(schema.parse("TEST+alias@COMPANY.COM")).toBe("test@company.com")
|
|
247
|
+
})
|
|
76
248
|
})
|
|
77
249
|
})
|