@hy_ong/zod-kit 0.0.4 → 0.0.5
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 +23 -0
- package/LICENSE +21 -0
- package/README.md +5 -5
- package/debug.js +21 -0
- package/debug.ts +16 -0
- package/dist/index.cjs +1619 -145
- package/dist/index.d.cts +324 -25
- package/dist/index.d.ts +324 -25
- package/dist/index.js +1590 -143
- package/eslint.config.mts +8 -0
- package/package.json +10 -9
- package/src/config.ts +1 -1
- package/src/i18n/locales/en.json +99 -25
- package/src/i18n/locales/zh-TW.json +103 -26
- package/src/index.ts +13 -7
- package/src/validators/common/boolean.ts +97 -0
- package/src/validators/common/date.ts +171 -0
- package/src/validators/common/email.ts +200 -0
- package/src/validators/common/id.ts +259 -0
- package/src/validators/common/number.ts +194 -0
- package/src/validators/common/password.ts +214 -0
- package/src/validators/common/text.ts +151 -0
- package/src/validators/common/url.ts +207 -0
- package/src/validators/taiwan/business-id.ts +140 -0
- package/src/validators/taiwan/fax.ts +182 -0
- package/src/validators/taiwan/mobile.ts +110 -0
- package/src/validators/taiwan/national-id.ts +208 -0
- package/src/validators/taiwan/tel.ts +182 -0
- package/tests/common/boolean.test.ts +340 -92
- package/tests/common/date.test.ts +458 -0
- package/tests/common/email.test.ts +232 -60
- 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/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/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
|
@@ -6,11 +6,13 @@ const locales = [
|
|
|
6
6
|
locale: "en",
|
|
7
7
|
messages: {
|
|
8
8
|
required: "Required",
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
notEmpty: "Cannot be empty or whitespace only",
|
|
10
|
+
minLength: "Must be at least 5 characters",
|
|
11
|
+
maxLength: "Must be at most 5 characters",
|
|
11
12
|
startsWith: "Must start with pre",
|
|
12
13
|
endsWith: "Must end with xyz",
|
|
13
14
|
includes: "Must include foo",
|
|
15
|
+
excludes: "Must not contain admin",
|
|
14
16
|
invalid: "Invalid format",
|
|
15
17
|
},
|
|
16
18
|
},
|
|
@@ -18,11 +20,13 @@ const locales = [
|
|
|
18
20
|
locale: "zh-TW",
|
|
19
21
|
messages: {
|
|
20
22
|
required: "必填",
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
notEmpty: "不可為空白或僅含空格",
|
|
24
|
+
minLength: "長度至少 5 字元",
|
|
25
|
+
maxLength: "長度最多 5 字元",
|
|
23
26
|
startsWith: "必須以「pre」開頭",
|
|
24
27
|
endsWith: "必須以「xyz」結尾",
|
|
25
28
|
includes: "必須包含「foo」",
|
|
29
|
+
excludes: "不得包含「admin」",
|
|
26
30
|
invalid: "格式錯誤",
|
|
27
31
|
},
|
|
28
32
|
},
|
|
@@ -50,17 +54,17 @@ describe.each(locales)("text() locale: $locale", ({ locale, messages }) => {
|
|
|
50
54
|
expect(schema.parse(undefined)).toBe(null)
|
|
51
55
|
})
|
|
52
56
|
|
|
53
|
-
it("should fail with string shorter than
|
|
54
|
-
const schema = text({
|
|
55
|
-
expect(() => schema.parse("hi")).toThrow(messages.
|
|
56
|
-
expect(() => schema.parse("")).toThrow(messages.
|
|
57
|
-
expect(() => schema.parse(null)).toThrow(messages.
|
|
58
|
-
expect(() => schema.parse(undefined)).toThrow(messages.
|
|
57
|
+
it("should fail with string shorter than minLength", () => {
|
|
58
|
+
const schema = text({ minLength: 5 })
|
|
59
|
+
expect(() => schema.parse("hi")).toThrow(messages.minLength)
|
|
60
|
+
expect(() => schema.parse("")).toThrow(messages.required) // Empty string triggers required first
|
|
61
|
+
expect(() => schema.parse(null)).toThrow(messages.required) // Null triggers required first
|
|
62
|
+
expect(() => schema.parse(undefined)).toThrow(messages.required) // Undefined triggers required first
|
|
59
63
|
})
|
|
60
64
|
|
|
61
|
-
it("should fail with string longer than
|
|
62
|
-
const schema = text({
|
|
63
|
-
expect(() => schema.parse("hello world")).toThrow(messages.
|
|
65
|
+
it("should fail with string longer than maxLength", () => {
|
|
66
|
+
const schema = text({ maxLength: 5 })
|
|
67
|
+
expect(() => schema.parse("hello world")).toThrow(messages.maxLength)
|
|
64
68
|
})
|
|
65
69
|
|
|
66
70
|
it("should fail if does not start with specified string", () => {
|
|
@@ -86,4 +90,197 @@ describe.each(locales)("text() locale: $locale", ({ locale, messages }) => {
|
|
|
86
90
|
expect(schema.parse("HELLO")).toBe("HELLO")
|
|
87
91
|
expect(() => schema.parse("hello")).toThrow(messages.invalid)
|
|
88
92
|
})
|
|
93
|
+
|
|
94
|
+
it("should fail if contains excluded substring", () => {
|
|
95
|
+
const schema = text({ excludes: "admin" })
|
|
96
|
+
expect(schema.parse("hello")).toBe("hello")
|
|
97
|
+
expect(() => schema.parse("admin user")).toThrow(messages.excludes)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it("should fail if contains any excluded substring from array", () => {
|
|
101
|
+
const schema = text({ excludes: ["admin", "root", "test"] })
|
|
102
|
+
expect(schema.parse("hello")).toBe("hello")
|
|
103
|
+
expect(() => schema.parse("admin user")).toThrow(/Must not contain|不得包含/)
|
|
104
|
+
expect(() => schema.parse("root access")).toThrow(/Must not contain|不得包含/)
|
|
105
|
+
expect(() => schema.parse("test mode")).toThrow(/Must not contain|不得包含/)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("should fail with notEmpty when only whitespace", () => {
|
|
109
|
+
const schema = text({ notEmpty: true, trimMode: "none" })
|
|
110
|
+
expect(schema.parse("hello")).toBe("hello")
|
|
111
|
+
expect(() => schema.parse(" ")).toThrow(messages.notEmpty)
|
|
112
|
+
expect(() => schema.parse("\t\n")).toThrow(messages.notEmpty)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe("text() trimMode functionality", () => {
|
|
117
|
+
it("should trim by default", () => {
|
|
118
|
+
const schema = text()
|
|
119
|
+
expect(schema.parse(" hello ")).toBe("hello")
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it("should trim start only", () => {
|
|
123
|
+
const schema = text({ trimMode: "trimStart" })
|
|
124
|
+
expect(schema.parse(" hello ")).toBe("hello ")
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("should trim end only", () => {
|
|
128
|
+
const schema = text({ trimMode: "trimEnd" })
|
|
129
|
+
expect(schema.parse(" hello ")).toBe(" hello")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("should not trim when mode is none", () => {
|
|
133
|
+
const schema = text({ trimMode: "none" })
|
|
134
|
+
expect(schema.parse(" hello ")).toBe(" hello ")
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe("text() casing functionality", () => {
|
|
139
|
+
it("should convert to uppercase", () => {
|
|
140
|
+
const schema = text({ casing: "upper" })
|
|
141
|
+
expect(schema.parse("hello world")).toBe("HELLO WORLD")
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("should convert to lowercase", () => {
|
|
145
|
+
const schema = text({ casing: "lower" })
|
|
146
|
+
expect(schema.parse("HELLO WORLD")).toBe("hello world")
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it("should convert to title case", () => {
|
|
150
|
+
const schema = text({ casing: "title" })
|
|
151
|
+
expect(schema.parse("hello world")).toBe("Hello World")
|
|
152
|
+
expect(schema.parse("HELLO WORLD")).toBe("Hello World")
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it("should not change casing when none", () => {
|
|
156
|
+
const schema = text({ casing: "none" })
|
|
157
|
+
expect(schema.parse("Hello World")).toBe("Hello World")
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe("text() transform functionality", () => {
|
|
162
|
+
it("should apply custom transform function", () => {
|
|
163
|
+
const schema = text({
|
|
164
|
+
transform: (val) => val.replace(/\s+/g, "-"),
|
|
165
|
+
})
|
|
166
|
+
expect(schema.parse("hello world test")).toBe("hello-world-test")
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it("should apply transform after trimming and casing", () => {
|
|
170
|
+
const schema = text({
|
|
171
|
+
trimMode: "trim",
|
|
172
|
+
casing: "lower",
|
|
173
|
+
transform: (val) => val.replace(/\s+/g, "_"),
|
|
174
|
+
})
|
|
175
|
+
expect(schema.parse(" HELLO WORLD ")).toBe("hello_world")
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe("text() defaultValue functionality", () => {
|
|
180
|
+
it("should use defaultValue for empty input when required", () => {
|
|
181
|
+
const schema = text({ defaultValue: "default" })
|
|
182
|
+
expect(schema.parse("")).toBe("default")
|
|
183
|
+
expect(schema.parse(null)).toBe("default")
|
|
184
|
+
expect(schema.parse(undefined)).toBe("default")
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it("should use defaultValue for empty input when not required", () => {
|
|
188
|
+
const schema = text({ required: false, defaultValue: "default" })
|
|
189
|
+
expect(schema.parse("")).toBe("default")
|
|
190
|
+
expect(schema.parse(null)).toBe("default")
|
|
191
|
+
expect(schema.parse(undefined)).toBe("default")
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe("text() custom i18n messages", () => {
|
|
196
|
+
beforeEach(() => setLocale("en"))
|
|
197
|
+
|
|
198
|
+
it("should use custom messages when provided", () => {
|
|
199
|
+
const schema = text({
|
|
200
|
+
minLength: 5,
|
|
201
|
+
i18n: {
|
|
202
|
+
en: {
|
|
203
|
+
required: "Custom required message",
|
|
204
|
+
minLength: "Custom min message: at least ${minLength} chars",
|
|
205
|
+
},
|
|
206
|
+
"zh-TW": {
|
|
207
|
+
required: "客製化必填訊息",
|
|
208
|
+
minLength: "客製化最小長度: 至少 ${minLength} 字元",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
expect(() => schema.parse("")).toThrow("Custom required message")
|
|
214
|
+
expect(() => schema.parse("hi")).toThrow("Custom min message: at least 5 chars")
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it("should fallback to default messages when custom not provided", () => {
|
|
218
|
+
const schema = text({
|
|
219
|
+
maxLength: 3,
|
|
220
|
+
i18n: {
|
|
221
|
+
en: {
|
|
222
|
+
required: "Custom required message",
|
|
223
|
+
},
|
|
224
|
+
"zh-TW": {
|
|
225
|
+
required: "客製化必填訊息",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
expect(() => schema.parse("")).toThrow("Custom required message")
|
|
231
|
+
expect(() => schema.parse("hello")).toThrow("Must be at most 3 characters")
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it("should use correct locale for custom messages", () => {
|
|
235
|
+
const schema = text({
|
|
236
|
+
i18n: {
|
|
237
|
+
en: {
|
|
238
|
+
required: "English required",
|
|
239
|
+
},
|
|
240
|
+
"zh-TW": {
|
|
241
|
+
required: "繁體中文必填",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
setLocale("en")
|
|
247
|
+
expect(() => schema.parse("")).toThrow("English required")
|
|
248
|
+
|
|
249
|
+
setLocale("zh-TW")
|
|
250
|
+
expect(() => schema.parse("")).toThrow("繁體中文必填")
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe("text() complex scenarios", () => {
|
|
255
|
+
beforeEach(() => setLocale("en")) // Ensure a consistent locale for this test
|
|
256
|
+
|
|
257
|
+
it("should work with multiple validations", () => {
|
|
258
|
+
const schema = text({
|
|
259
|
+
minLength: 5,
|
|
260
|
+
maxLength: 20,
|
|
261
|
+
startsWith: "user_",
|
|
262
|
+
includes: "test",
|
|
263
|
+
excludes: ["admin", "root"],
|
|
264
|
+
casing: "lower",
|
|
265
|
+
trimMode: "trim",
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
expect(schema.parse(" USER_TEST123 ")).toBe("user_test123")
|
|
269
|
+
expect(() => schema.parse("user")).toThrow("Must be at least 5 characters")
|
|
270
|
+
expect(() => schema.parse("user_admin_test")).toThrow("Must not contain admin")
|
|
271
|
+
expect(() => schema.parse("test123")).toThrow("Must start with user_")
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it("should handle null values correctly when not required", () => {
|
|
275
|
+
const schema = text({
|
|
276
|
+
required: false,
|
|
277
|
+
minLength: 5,
|
|
278
|
+
includes: "test",
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
expect(schema.parse(null)).toBe(null)
|
|
282
|
+
expect(schema.parse("")).toBe(null)
|
|
283
|
+
expect(schema.parse(undefined)).toBe(null)
|
|
284
|
+
expect(schema.parse("test12345")).toBe("test12345")
|
|
285
|
+
})
|
|
89
286
|
})
|