@hy_ong/zod-kit 0.0.5 → 0.1.0

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 (46) hide show
  1. package/.claude/settings.local.json +9 -1
  2. package/README.md +465 -97
  3. package/dist/index.cjs +1690 -179
  4. package/dist/index.d.cts +2791 -28
  5. package/dist/index.d.ts +2791 -28
  6. package/dist/index.js +1672 -178
  7. package/package.json +2 -1
  8. package/src/i18n/locales/en.json +62 -0
  9. package/src/i18n/locales/zh-TW.json +62 -0
  10. package/src/index.ts +4 -0
  11. package/src/validators/common/boolean.ts +101 -4
  12. package/src/validators/common/date.ts +141 -6
  13. package/src/validators/common/datetime.ts +680 -0
  14. package/src/validators/common/email.ts +120 -4
  15. package/src/validators/common/file.ts +391 -0
  16. package/src/validators/common/id.ts +230 -18
  17. package/src/validators/common/number.ts +132 -4
  18. package/src/validators/common/password.ts +187 -8
  19. package/src/validators/common/text.ts +130 -6
  20. package/src/validators/common/time.ts +607 -0
  21. package/src/validators/common/url.ts +153 -6
  22. package/src/validators/taiwan/business-id.ts +138 -9
  23. package/src/validators/taiwan/fax.ts +164 -10
  24. package/src/validators/taiwan/mobile.ts +151 -10
  25. package/src/validators/taiwan/national-id.ts +233 -17
  26. package/src/validators/taiwan/postal-code.ts +1048 -0
  27. package/src/validators/taiwan/tel.ts +167 -10
  28. package/tests/common/boolean.test.ts +38 -38
  29. package/tests/common/date.test.ts +65 -65
  30. package/tests/common/datetime.test.ts +675 -0
  31. package/tests/common/email.test.ts +24 -28
  32. package/tests/common/file.test.ts +475 -0
  33. package/tests/common/id.test.ts +80 -113
  34. package/tests/common/number.test.ts +24 -25
  35. package/tests/common/password.test.ts +28 -35
  36. package/tests/common/text.test.ts +36 -37
  37. package/tests/common/time.test.ts +510 -0
  38. package/tests/common/url.test.ts +67 -67
  39. package/tests/taiwan/business-id.test.ts +22 -22
  40. package/tests/taiwan/fax.test.ts +33 -42
  41. package/tests/taiwan/mobile.test.ts +32 -41
  42. package/tests/taiwan/national-id.test.ts +31 -31
  43. package/tests/taiwan/postal-code.test.ts +751 -0
  44. package/tests/taiwan/tel.test.ts +33 -42
  45. package/debug.js +0 -21
  46. package/debug.ts +0 -16
@@ -42,31 +42,31 @@ const locales = [
42
42
  },
43
43
  ] as const
44
44
 
45
- describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
45
+ describe.each(locales)("password(true) locale: $locale", ({ locale, messages }) => {
46
46
  beforeEach(() => setLocale(locale as Locale))
47
47
 
48
48
  describe("basic functionality", () => {
49
49
  it("should pass with valid string", () => {
50
- const schema = password()
50
+ const schema = password(true)
51
51
  expect(schema.parse("hello")).toBe("hello")
52
52
  })
53
53
 
54
54
  it("should fail with empty string when required", () => {
55
- const schema = password()
55
+ const schema = password(true)
56
56
  expect(() => schema.parse("")).toThrow(messages.required)
57
57
  expect(() => schema.parse(null)).toThrow(messages.required)
58
58
  expect(() => schema.parse(undefined)).toThrow(messages.required)
59
59
  })
60
60
 
61
61
  it("should pass with null when not required", () => {
62
- const schema = password({ required: false })
62
+ const schema = password(false)
63
63
  expect(schema.parse("")).toBe(null)
64
64
  expect(schema.parse(null)).toBe(null)
65
65
  expect(schema.parse(undefined)).toBe(null)
66
66
  })
67
67
 
68
68
  it("should handle default values", () => {
69
- const schema = password({ required: false, defaultValue: "default123" })
69
+ const schema = password(false, { defaultValue: "default123" })
70
70
  expect(schema.parse("")).toBe("default123")
71
71
  expect(schema.parse(null)).toBe("default123")
72
72
  expect(schema.parse(undefined)).toBe("default123")
@@ -74,55 +74,55 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
74
74
  })
75
75
 
76
76
  it("should apply transform function", () => {
77
- const schema = password({ transform: (val) => val.toLowerCase() })
77
+ const schema = password(true, { transform: (val) => val.toLowerCase() })
78
78
  expect(schema.parse("HELLO")).toBe("hello")
79
79
  })
80
80
  })
81
81
 
82
82
  describe("length validation", () => {
83
83
  it("should fail with string shorter than min", () => {
84
- const schema = password({ min: 8 })
84
+ const schema = password(true, { min: 8 })
85
85
  expect(() => schema.parse("short")).toThrow(messages.min)
86
86
  })
87
87
 
88
88
  it("should fail with string longer than max", () => {
89
- const schema = password({ max: 20 })
89
+ const schema = password(true, { max: 20 })
90
90
  expect(() => schema.parse("this_is_a_very_long_password_that_exceeds_limit")).toThrow(messages.max)
91
91
  })
92
92
 
93
93
  it("should pass with valid length", () => {
94
- const schema = password({ min: 5, max: 15 })
94
+ const schema = password(true, { min: 5, max: 15 })
95
95
  expect(schema.parse("validpassword")).toBe("validpassword")
96
96
  })
97
97
  })
98
98
 
99
99
  describe("character requirements", () => {
100
100
  it("should enforce uppercase requirement", () => {
101
- const schema = password({ uppercase: true })
101
+ const schema = password(true, { uppercase: true })
102
102
  expect(() => schema.parse("lowercase1!")).toThrow(messages.uppercase)
103
103
  expect(schema.parse("Hello1!")).toBe("Hello1!")
104
104
  })
105
105
 
106
106
  it("should enforce lowercase requirement", () => {
107
- const schema = password({ lowercase: true })
107
+ const schema = password(true, { lowercase: true })
108
108
  expect(() => schema.parse("UPPERCASE1!")).toThrow(messages.lowercase)
109
109
  expect(schema.parse("Test1!")).toBe("Test1!")
110
110
  })
111
111
 
112
112
  it("should enforce digit requirement", () => {
113
- const schema = password({ digits: true })
113
+ const schema = password(true, { digits: true })
114
114
  expect(() => schema.parse("NoDigits!")).toThrow(messages.digits)
115
115
  expect(schema.parse("With1!")).toBe("With1!")
116
116
  })
117
117
 
118
118
  it("should enforce special character requirement", () => {
119
- const schema = password({ special: true })
119
+ const schema = password(true, { special: true })
120
120
  expect(() => schema.parse("NoSpecial1")).toThrow(messages.special)
121
121
  expect(schema.parse("Valid1!")).toBe("Valid1!")
122
122
  })
123
123
 
124
124
  it("should enforce multiple requirements", () => {
125
- const schema = password({ uppercase: true, lowercase: true, digits: true, special: true })
125
+ const schema = password(true, { uppercase: true, lowercase: true, digits: true, special: true })
126
126
  expect(() => schema.parse("nouppercase1!")).toThrow(messages.uppercase)
127
127
  expect(() => schema.parse("NOLOWERCASE1!")).toThrow(messages.lowercase)
128
128
  expect(() => schema.parse("NoDigits!")).toThrow(messages.digits)
@@ -133,14 +133,14 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
133
133
 
134
134
  describe("advanced security features", () => {
135
135
  it("should enforce no repeating characters", () => {
136
- const schema = password({ noRepeating: true })
136
+ const schema = password(true, { noRepeating: true })
137
137
  expect(() => schema.parse("password111")).toThrow(messages.noRepeating)
138
138
  expect(() => schema.parse("helllo")).toThrow(messages.noRepeating)
139
139
  expect(schema.parse("password")).toBe("password")
140
140
  })
141
141
 
142
142
  it("should enforce no sequential characters", () => {
143
- const schema = password({ noSequential: true })
143
+ const schema = password(true, { noSequential: true })
144
144
  expect(() => schema.parse("abc123")).toThrow(messages.noSequential)
145
145
  expect(() => schema.parse("xyz789")).toThrow(messages.noSequential)
146
146
  expect(() => schema.parse("password456")).toThrow(messages.noSequential)
@@ -148,7 +148,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
148
148
  })
149
149
 
150
150
  it("should enforce no common words", () => {
151
- const schema = password({ noCommonWords: true })
151
+ const schema = password(true, { noCommonWords: true })
152
152
  expect(() => schema.parse("password123")).toThrow(messages.noCommonWords)
153
153
  expect(() => schema.parse("admin")).toThrow(messages.noCommonWords)
154
154
  expect(() => schema.parse("qwerty")).toThrow(messages.noCommonWords)
@@ -156,7 +156,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
156
156
  })
157
157
 
158
158
  it("should enforce minimum strength", () => {
159
- const schema = password({ minStrength: "strong" })
159
+ const schema = password(true, { minStrength: "strong" })
160
160
  expect(() => schema.parse("weak")).toThrow(messages.minStrength)
161
161
  expect(() => schema.parse("123456")).toThrow(messages.minStrength)
162
162
  expect(schema.parse("StrongP@ssw0rd")).toBe("StrongP@ssw0rd")
@@ -165,26 +165,26 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
165
165
 
166
166
  describe("content validation", () => {
167
167
  it("should enforce includes requirement", () => {
168
- const schema = password({ includes: "@" })
168
+ const schema = password(true, { includes: "@" })
169
169
  expect(() => schema.parse("password")).toThrow(messages.includes)
170
170
  expect(schema.parse("user@pass")).toBe("user@pass")
171
171
  })
172
172
 
173
173
  it("should enforce excludes requirement with string", () => {
174
- const schema = password({ excludes: "test" })
174
+ const schema = password(true, { excludes: "test" })
175
175
  expect(() => schema.parse("testpassword")).toThrow(messages.excludes)
176
176
  expect(schema.parse("password")).toBe("password")
177
177
  })
178
178
 
179
179
  it("should enforce excludes requirement with array", () => {
180
- const schema = password({ excludes: ["test", "admin"] })
180
+ const schema = password(true, { excludes: ["test", "admin"] })
181
181
  expect(() => schema.parse("testpassword")).toThrow()
182
182
  expect(() => schema.parse("adminpass")).toThrow()
183
183
  expect(schema.parse("password")).toBe("password")
184
184
  })
185
185
 
186
186
  it("should enforce regex requirement", () => {
187
- const schema = password({ regex: /^[a-zA-Z0-9!@#$%^&*()]+$/ })
187
+ const schema = password(true, { regex: /^[a-zA-Z0-9!@#$%^&*()]+$/ })
188
188
  expect(() => schema.parse("invalid-password")).toThrow(messages.invalid)
189
189
  expect(schema.parse("Valid123!")).toBe("Valid123!")
190
190
  })
@@ -205,7 +205,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
205
205
  },
206
206
  }
207
207
 
208
- const schema = password({
208
+ const schema = password(true, {
209
209
  min: 8,
210
210
  uppercase: true,
211
211
  i18n: customMessages,
@@ -228,7 +228,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
228
228
  "zh-TW": { required: "自訂必填訊息" },
229
229
  }
230
230
 
231
- const schema = password({
231
+ const schema = password(true, {
232
232
  uppercase: true,
233
233
  i18n: customMessages,
234
234
  })
@@ -245,7 +245,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
245
245
 
246
246
  describe("complex scenarios", () => {
247
247
  it("should handle all features combined", () => {
248
- const schema = password({
248
+ const schema = password(true, {
249
249
  min: 12,
250
250
  max: 50,
251
251
  uppercase: true,
@@ -277,14 +277,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
277
277
  })
278
278
 
279
279
  it("should work with optional password and all features", () => {
280
- const schema = password({
281
- required: false,
282
- min: 8,
283
- uppercase: true,
284
- lowercase: true,
285
- digits: true,
286
- special: true,
287
- })
280
+ const schema = password(false, { min: 8, uppercase: true, lowercase: true, digits: true, special: true })
288
281
 
289
282
  expect(schema.parse(null)).toBe(null)
290
283
  expect(schema.parse("")).toBe(null)
@@ -303,7 +296,7 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
303
296
 
304
297
  testStrengthScenarios.forEach(({ password: testPassword, expectedToPass, minStrength }) => {
305
298
  it(`should ${expectedToPass ? "pass" : "fail"} for password "${testPassword}" with minStrength "${minStrength}"`, () => {
306
- const schema = password({ minStrength })
299
+ const schema = password(true, { minStrength })
307
300
 
308
301
  if (expectedToPass) {
309
302
  expect(schema.parse(testPassword)).toBe(testPassword)
@@ -313,4 +306,4 @@ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
313
306
  })
314
307
  })
315
308
  })
316
- })
309
+ })
@@ -32,30 +32,30 @@ const locales = [
32
32
  },
33
33
  ] as const
34
34
 
35
- describe.each(locales)("text() locale: $locale", ({ locale, messages }) => {
35
+ describe.each(locales)("text(true) locale: $locale", ({ locale, messages }) => {
36
36
  beforeEach(() => setLocale(locale as Locale))
37
37
 
38
38
  it("should pass with valid string", () => {
39
- const schema = text()
39
+ const schema = text(true)
40
40
  expect(schema.parse("hello")).toBe("hello")
41
41
  })
42
42
 
43
43
  it("should fail with empty string when required", () => {
44
- const schema = text()
44
+ const schema = text(true)
45
45
  expect(() => schema.parse("")).toThrow(messages.required)
46
46
  expect(() => schema.parse(null)).toThrow(messages.required)
47
47
  expect(() => schema.parse(undefined)).toThrow(messages.required)
48
48
  })
49
49
 
50
50
  it("should pass with null when not required", () => {
51
- const schema = text({ required: false })
51
+ const schema = text(false)
52
52
  expect(schema.parse("")).toBe(null)
53
53
  expect(schema.parse(null)).toBe(null)
54
54
  expect(schema.parse(undefined)).toBe(null)
55
55
  })
56
56
 
57
57
  it("should fail with string shorter than minLength", () => {
58
- const schema = text({ minLength: 5 })
58
+ const schema = text(true, { minLength: 5 })
59
59
  expect(() => schema.parse("hi")).toThrow(messages.minLength)
60
60
  expect(() => schema.parse("")).toThrow(messages.required) // Empty string triggers required first
61
61
  expect(() => schema.parse(null)).toThrow(messages.required) // Null triggers required first
@@ -63,42 +63,42 @@ describe.each(locales)("text() locale: $locale", ({ locale, messages }) => {
63
63
  })
64
64
 
65
65
  it("should fail with string longer than maxLength", () => {
66
- const schema = text({ maxLength: 5 })
66
+ const schema = text(true, { maxLength: 5 })
67
67
  expect(() => schema.parse("hello world")).toThrow(messages.maxLength)
68
68
  })
69
69
 
70
70
  it("should fail if does not start with specified string", () => {
71
- const schema = text({ startsWith: "pre" })
71
+ const schema = text(true, { startsWith: "pre" })
72
72
  expect(schema.parse("prepaid")).toBe("prepaid")
73
73
  expect(() => schema.parse("hello world")).toThrow(messages.startsWith)
74
74
  })
75
75
 
76
76
  it("should fail if does not end with specified string", () => {
77
- const schema = text({ endsWith: "xyz" })
77
+ const schema = text(true, { endsWith: "xyz" })
78
78
  expect(schema.parse("hello xyz")).toBe("hello xyz")
79
79
  expect(() => schema.parse("hello world")).toThrow(messages.endsWith)
80
80
  })
81
81
 
82
82
  it("should fail if does not include specified substring", () => {
83
- const schema = text({ includes: "foo" })
83
+ const schema = text(true, { includes: "foo" })
84
84
  expect(schema.parse("hello foo")).toBe("hello foo")
85
85
  expect(() => schema.parse("hello world")).toThrow(messages.includes)
86
86
  })
87
87
 
88
88
  it("should fail if does not match regex", () => {
89
- const schema = text({ regex: /^[A-Z]+$/ })
89
+ const schema = text(true, { regex: /^[A-Z]+$/ })
90
90
  expect(schema.parse("HELLO")).toBe("HELLO")
91
91
  expect(() => schema.parse("hello")).toThrow(messages.invalid)
92
92
  })
93
93
 
94
94
  it("should fail if contains excluded substring", () => {
95
- const schema = text({ excludes: "admin" })
95
+ const schema = text(true, { excludes: "admin" })
96
96
  expect(schema.parse("hello")).toBe("hello")
97
97
  expect(() => schema.parse("admin user")).toThrow(messages.excludes)
98
98
  })
99
99
 
100
100
  it("should fail if contains any excluded substring from array", () => {
101
- const schema = text({ excludes: ["admin", "root", "test"] })
101
+ const schema = text(true, { excludes: ["admin", "root", "test"] })
102
102
  expect(schema.parse("hello")).toBe("hello")
103
103
  expect(() => schema.parse("admin user")).toThrow(/Must not contain|不得包含/)
104
104
  expect(() => schema.parse("root access")).toThrow(/Must not contain|不得包含/)
@@ -106,68 +106,68 @@ describe.each(locales)("text() locale: $locale", ({ locale, messages }) => {
106
106
  })
107
107
 
108
108
  it("should fail with notEmpty when only whitespace", () => {
109
- const schema = text({ notEmpty: true, trimMode: "none" })
109
+ const schema = text(true, { notEmpty: true, trimMode: "none" })
110
110
  expect(schema.parse("hello")).toBe("hello")
111
111
  expect(() => schema.parse(" ")).toThrow(messages.notEmpty)
112
112
  expect(() => schema.parse("\t\n")).toThrow(messages.notEmpty)
113
113
  })
114
114
  })
115
115
 
116
- describe("text() trimMode functionality", () => {
116
+ describe("text(true) trimMode functionality", () => {
117
117
  it("should trim by default", () => {
118
- const schema = text()
118
+ const schema = text(true)
119
119
  expect(schema.parse(" hello ")).toBe("hello")
120
120
  })
121
121
 
122
122
  it("should trim start only", () => {
123
- const schema = text({ trimMode: "trimStart" })
123
+ const schema = text(true, { trimMode: "trimStart" })
124
124
  expect(schema.parse(" hello ")).toBe("hello ")
125
125
  })
126
126
 
127
127
  it("should trim end only", () => {
128
- const schema = text({ trimMode: "trimEnd" })
128
+ const schema = text(true, { trimMode: "trimEnd" })
129
129
  expect(schema.parse(" hello ")).toBe(" hello")
130
130
  })
131
131
 
132
132
  it("should not trim when mode is none", () => {
133
- const schema = text({ trimMode: "none" })
133
+ const schema = text(true, { trimMode: "none" })
134
134
  expect(schema.parse(" hello ")).toBe(" hello ")
135
135
  })
136
136
  })
137
137
 
138
- describe("text() casing functionality", () => {
138
+ describe("text(true) casing functionality", () => {
139
139
  it("should convert to uppercase", () => {
140
- const schema = text({ casing: "upper" })
140
+ const schema = text(true, { casing: "upper" })
141
141
  expect(schema.parse("hello world")).toBe("HELLO WORLD")
142
142
  })
143
143
 
144
144
  it("should convert to lowercase", () => {
145
- const schema = text({ casing: "lower" })
145
+ const schema = text(true, { casing: "lower" })
146
146
  expect(schema.parse("HELLO WORLD")).toBe("hello world")
147
147
  })
148
148
 
149
149
  it("should convert to title case", () => {
150
- const schema = text({ casing: "title" })
150
+ const schema = text(true, { casing: "title" })
151
151
  expect(schema.parse("hello world")).toBe("Hello World")
152
152
  expect(schema.parse("HELLO WORLD")).toBe("Hello World")
153
153
  })
154
154
 
155
155
  it("should not change casing when none", () => {
156
- const schema = text({ casing: "none" })
156
+ const schema = text(true, { casing: "none" })
157
157
  expect(schema.parse("Hello World")).toBe("Hello World")
158
158
  })
159
159
  })
160
160
 
161
- describe("text() transform functionality", () => {
161
+ describe("text(true) transform functionality", () => {
162
162
  it("should apply custom transform function", () => {
163
- const schema = text({
163
+ const schema = text(true, {
164
164
  transform: (val) => val.replace(/\s+/g, "-"),
165
165
  })
166
166
  expect(schema.parse("hello world test")).toBe("hello-world-test")
167
167
  })
168
168
 
169
169
  it("should apply transform after trimming and casing", () => {
170
- const schema = text({
170
+ const schema = text(true, {
171
171
  trimMode: "trim",
172
172
  casing: "lower",
173
173
  transform: (val) => val.replace(/\s+/g, "_"),
@@ -176,27 +176,27 @@ describe("text() transform functionality", () => {
176
176
  })
177
177
  })
178
178
 
179
- describe("text() defaultValue functionality", () => {
179
+ describe("text(true) defaultValue functionality", () => {
180
180
  it("should use defaultValue for empty input when required", () => {
181
- const schema = text({ defaultValue: "default" })
181
+ const schema = text(true, { defaultValue: "default" })
182
182
  expect(schema.parse("")).toBe("default")
183
183
  expect(schema.parse(null)).toBe("default")
184
184
  expect(schema.parse(undefined)).toBe("default")
185
185
  })
186
186
 
187
187
  it("should use defaultValue for empty input when not required", () => {
188
- const schema = text({ required: false, defaultValue: "default" })
188
+ const schema = text(false, { defaultValue: "default" })
189
189
  expect(schema.parse("")).toBe("default")
190
190
  expect(schema.parse(null)).toBe("default")
191
191
  expect(schema.parse(undefined)).toBe("default")
192
192
  })
193
193
  })
194
194
 
195
- describe("text() custom i18n messages", () => {
195
+ describe("text(true) custom i18n messages", () => {
196
196
  beforeEach(() => setLocale("en"))
197
197
 
198
198
  it("should use custom messages when provided", () => {
199
- const schema = text({
199
+ const schema = text(true, {
200
200
  minLength: 5,
201
201
  i18n: {
202
202
  en: {
@@ -215,7 +215,7 @@ describe("text() custom i18n messages", () => {
215
215
  })
216
216
 
217
217
  it("should fallback to default messages when custom not provided", () => {
218
- const schema = text({
218
+ const schema = text(true, {
219
219
  maxLength: 3,
220
220
  i18n: {
221
221
  en: {
@@ -232,7 +232,7 @@ describe("text() custom i18n messages", () => {
232
232
  })
233
233
 
234
234
  it("should use correct locale for custom messages", () => {
235
- const schema = text({
235
+ const schema = text(true, {
236
236
  i18n: {
237
237
  en: {
238
238
  required: "English required",
@@ -251,11 +251,11 @@ describe("text() custom i18n messages", () => {
251
251
  })
252
252
  })
253
253
 
254
- describe("text() complex scenarios", () => {
254
+ describe("text(true) complex scenarios", () => {
255
255
  beforeEach(() => setLocale("en")) // Ensure a consistent locale for this test
256
256
 
257
257
  it("should work with multiple validations", () => {
258
- const schema = text({
258
+ const schema = text(true, {
259
259
  minLength: 5,
260
260
  maxLength: 20,
261
261
  startsWith: "user_",
@@ -272,8 +272,7 @@ describe("text() complex scenarios", () => {
272
272
  })
273
273
 
274
274
  it("should handle null values correctly when not required", () => {
275
- const schema = text({
276
- required: false,
275
+ const schema = text(false, {
277
276
  minLength: 5,
278
277
  includes: "test",
279
278
  })