@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.
Files changed (59) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +465 -97
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +3127 -146
  7. package/dist/index.d.cts +3021 -25
  8. package/dist/index.d.ts +3021 -25
  9. package/dist/index.js +3081 -144
  10. package/eslint.config.mts +8 -0
  11. package/package.json +10 -9
  12. package/src/config.ts +1 -1
  13. package/src/i18n/locales/en.json +161 -25
  14. package/src/i18n/locales/zh-TW.json +165 -26
  15. package/src/index.ts +17 -7
  16. package/src/validators/common/boolean.ts +191 -0
  17. package/src/validators/common/date.ts +299 -0
  18. package/src/validators/common/datetime.ts +673 -0
  19. package/src/validators/common/email.ts +313 -0
  20. package/src/validators/common/file.ts +384 -0
  21. package/src/validators/common/id.ts +471 -0
  22. package/src/validators/common/number.ts +319 -0
  23. package/src/validators/common/password.ts +386 -0
  24. package/src/validators/common/text.ts +271 -0
  25. package/src/validators/common/time.ts +600 -0
  26. package/src/validators/common/url.ts +347 -0
  27. package/src/validators/taiwan/business-id.ts +262 -0
  28. package/src/validators/taiwan/fax.ts +327 -0
  29. package/src/validators/taiwan/mobile.ts +242 -0
  30. package/src/validators/taiwan/national-id.ts +425 -0
  31. package/src/validators/taiwan/postal-code.ts +1049 -0
  32. package/src/validators/taiwan/tel.ts +330 -0
  33. package/tests/common/boolean.test.ts +340 -92
  34. package/tests/common/date.test.ts +458 -0
  35. package/tests/common/datetime.test.ts +693 -0
  36. package/tests/common/email.test.ts +232 -60
  37. package/tests/common/file.test.ts +479 -0
  38. package/tests/common/id.test.ts +535 -0
  39. package/tests/common/number.test.ts +230 -60
  40. package/tests/common/password.test.ts +271 -44
  41. package/tests/common/text.test.ts +210 -13
  42. package/tests/common/time.test.ts +528 -0
  43. package/tests/common/url.test.ts +492 -67
  44. package/tests/taiwan/business-id.test.ts +240 -0
  45. package/tests/taiwan/fax.test.ts +463 -0
  46. package/tests/taiwan/mobile.test.ts +373 -0
  47. package/tests/taiwan/national-id.test.ts +435 -0
  48. package/tests/taiwan/postal-code.test.ts +705 -0
  49. package/tests/taiwan/tel.test.ts +467 -0
  50. package/eslint.config.mjs +0 -10
  51. package/src/common/boolean.ts +0 -36
  52. package/src/common/date.ts +0 -43
  53. package/src/common/email.ts +0 -44
  54. package/src/common/integer.ts +0 -46
  55. package/src/common/number.ts +0 -37
  56. package/src/common/password.ts +0 -33
  57. package/src/common/text.ts +0 -34
  58. package/src/common/url.ts +0 -37
  59. package/tests/common/integer.test.ts +0 -90
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @fileoverview Boolean validator for Zod Kit
3
+ *
4
+ * Provides flexible boolean validation with support for various truthy/falsy values,
5
+ * strict mode validation, and comprehensive transformation options.
6
+ *
7
+ * @author Ong Hoe Yuan
8
+ * @version 0.0.5
9
+ */
10
+
11
+ import { z, ZodBoolean, ZodNullable, ZodType } from "zod"
12
+ import { t } from "../../i18n"
13
+ import { getLocale, type Locale } from "../../config"
14
+
15
+ /**
16
+ * Type definition for boolean validation error messages
17
+ *
18
+ * @interface BooleanMessages
19
+ * @property {string} [required] - Message when field is required but empty
20
+ * @property {string} [shouldBeTrue] - Message when value should be true but isn't
21
+ * @property {string} [shouldBeFalse] - Message when value should be false but isn't
22
+ * @property {string} [invalid] - Message when value is not a valid boolean
23
+ */
24
+ export type BooleanMessages = {
25
+ required?: string
26
+ shouldBeTrue?: string
27
+ shouldBeFalse?: string
28
+ invalid?: string
29
+ }
30
+
31
+ /**
32
+ * Configuration options for boolean validation
33
+ *
34
+ * @template IsRequired - Whether the field is required (affects return type)
35
+ *
36
+ * @interface BooleanOptions
37
+ * @property {IsRequired} [required=true] - Whether the field is required
38
+ * @property {boolean | null} [defaultValue] - Default value when input is empty
39
+ * @property {boolean} [shouldBe] - Specific boolean value that must be matched
40
+ * @property {unknown[]} [truthyValues] - Array of values that should be treated as true
41
+ * @property {unknown[]} [falsyValues] - Array of values that should be treated as false
42
+ * @property {boolean} [strict=false] - If true, only accepts actual boolean values
43
+ * @property {Function} [transform] - Custom transformation function for boolean values
44
+ * @property {Record<Locale, BooleanMessages>} [i18n] - Custom error messages for different locales
45
+ */
46
+ export type BooleanOptions<IsRequired extends boolean = true> = {
47
+ required?: IsRequired
48
+ defaultValue?: IsRequired extends true ? boolean : boolean | null
49
+ shouldBe?: boolean
50
+ truthyValues?: unknown[]
51
+ falsyValues?: unknown[]
52
+ strict?: boolean
53
+ transform?: (value: boolean) => boolean
54
+ i18n?: Record<Locale, BooleanMessages>
55
+ }
56
+
57
+ /**
58
+ * Type alias for boolean validation schema based on required flag
59
+ *
60
+ * @template IsRequired - Whether the field is required
61
+ * @typedef BooleanSchema
62
+ * @description Returns ZodBoolean if required, ZodNullable<ZodBoolean> if optional
63
+ */
64
+ export type BooleanSchema<IsRequired extends boolean> = IsRequired extends true ? ZodBoolean : ZodNullable<ZodBoolean>
65
+
66
+ /**
67
+ * Creates a Zod schema for boolean validation with flexible value interpretation
68
+ *
69
+ * @template IsRequired - Whether the field is required (affects return type)
70
+ * @param {BooleanOptions<IsRequired>} [options] - Configuration options for boolean validation
71
+ * @returns {BooleanSchema<IsRequired>} Zod schema for boolean validation
72
+ *
73
+ * @description
74
+ * Creates a flexible boolean validator that can interpret various values as true/false,
75
+ * supports strict mode for type safety, and provides comprehensive transformation options.
76
+ *
77
+ * Features:
78
+ * - Flexible truthy/falsy value interpretation
79
+ * - Strict mode for type safety
80
+ * - Custom transformation functions
81
+ * - Specific boolean value requirements
82
+ * - Comprehensive internationalization
83
+ * - Default value support
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // Basic boolean validation
88
+ * const basicSchema = boolean()
89
+ * basicSchema.parse(true) // ✓ Valid
90
+ * basicSchema.parse("true") // ✓ Valid (converted to true)
91
+ *
92
+ * // Strict mode (only actual booleans)
93
+ * const strictSchema = boolean({ strict: true })
94
+ * strictSchema.parse(true) // ✓ Valid
95
+ * strictSchema.parse("true") // ✗ Invalid
96
+ *
97
+ * // Must be true
98
+ * const mustBeTrueSchema = boolean({ shouldBe: true })
99
+ * mustBeTrueSchema.parse(true) // ✓ Valid
100
+ * mustBeTrueSchema.parse(false) // ✗ Invalid
101
+ *
102
+ * // Custom truthy/falsy values
103
+ * const customSchema = boolean({
104
+ * truthyValues: ["yes", "on", 1],
105
+ * falsyValues: ["no", "off", 0]
106
+ * })
107
+ * customSchema.parse("yes") // ✓ Valid (converted to true)
108
+ *
109
+ * // Optional with default
110
+ * const optionalSchema = boolean({
111
+ * required: false,
112
+ * defaultValue: false
113
+ * })
114
+ * ```
115
+ *
116
+ * @throws {z.ZodError} When validation fails with specific error messages
117
+ * @see {@link BooleanOptions} for all available configuration options
118
+ */
119
+ export function boolean<IsRequired extends boolean = true>(options?: BooleanOptions<IsRequired>): BooleanSchema<IsRequired> {
120
+ const {
121
+ required = true,
122
+ defaultValue = null,
123
+ shouldBe,
124
+ truthyValues = [true, "true", 1, "1", "yes", "on"],
125
+ falsyValues = [false, "false", 0, "0", "no", "off"],
126
+ strict = false,
127
+ transform,
128
+ i18n,
129
+ } = options ?? {}
130
+
131
+ // Helper function to get custom message or fallback to default i18n
132
+ const getMessage = (key: keyof BooleanMessages, params?: Record<string, any>) => {
133
+ if (i18n) {
134
+ const currentLocale = getLocale()
135
+ const customMessages = i18n[currentLocale]
136
+ if (customMessages && customMessages[key]) {
137
+ const template = customMessages[key]!
138
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
139
+ }
140
+ }
141
+ return t(`common.boolean.${key}`, params)
142
+ }
143
+
144
+ let result: ZodType = z.preprocess(
145
+ (val) => {
146
+ if (val === "" || val === undefined || val === null) return defaultValue
147
+
148
+ if (strict && typeof val !== "boolean" && val !== null) {
149
+ return val // Let it fail in validation
150
+ }
151
+
152
+ // Check truthy values
153
+ if (truthyValues.includes(val)) {
154
+ let processed = true
155
+ if (transform) processed = transform(processed)
156
+ return processed
157
+ }
158
+
159
+ // Check falsy values
160
+ if (falsyValues.includes(val)) {
161
+ let processed = false
162
+ if (transform) processed = transform(processed)
163
+ return processed
164
+ }
165
+
166
+ return val
167
+ },
168
+ z.union([z.literal(true), z.literal(false), z.literal(null)])
169
+ )
170
+
171
+ if (required && defaultValue === null) {
172
+ result = result.refine((val) => val !== null, { message: getMessage("required") })
173
+ }
174
+
175
+ if (shouldBe === true) {
176
+ result = result.refine((val) => val === true, { message: getMessage("shouldBeTrue") })
177
+ } else if (shouldBe === false) {
178
+ result = result.refine((val) => val === false, { message: getMessage("shouldBeFalse") })
179
+ }
180
+
181
+ if (strict) {
182
+ result = result.refine(
183
+ (val) => {
184
+ return val === null || typeof val === "boolean"
185
+ },
186
+ { message: getMessage("invalid") }
187
+ )
188
+ }
189
+
190
+ return result as IsRequired extends true ? ZodBoolean : ZodNullable<ZodBoolean>
191
+ }
@@ -0,0 +1,299 @@
1
+ /**
2
+ * @fileoverview Date validator for Zod Kit
3
+ *
4
+ * Provides comprehensive date validation with format support, range validation,
5
+ * temporal constraints, and weekday/weekend filtering using dayjs library.
6
+ *
7
+ * @author Ong Hoe Yuan
8
+ * @version 0.0.5
9
+ */
10
+
11
+ import { z, ZodNullable, ZodString } from "zod"
12
+ import { t } from "../../i18n"
13
+ import { getLocale, type Locale } from "../../config"
14
+ import dayjs from "dayjs"
15
+ import customParseFormat from "dayjs/plugin/customParseFormat"
16
+ import isSameOrAfter from "dayjs/plugin/isSameOrAfter"
17
+ import isSameOrBefore from "dayjs/plugin/isSameOrBefore"
18
+ import isToday from "dayjs/plugin/isToday"
19
+ import weekday from "dayjs/plugin/weekday"
20
+
21
+ // Initialize dayjs plugins for extended date functionality
22
+ dayjs.extend(isSameOrAfter)
23
+ dayjs.extend(isSameOrBefore)
24
+ dayjs.extend(customParseFormat)
25
+ dayjs.extend(isToday)
26
+ dayjs.extend(weekday)
27
+
28
+ /**
29
+ * Type definition for date validation error messages
30
+ *
31
+ * @interface DateMessages
32
+ * @property {string} [required] - Message when field is required but empty
33
+ * @property {string} [invalid] - Message when date is invalid
34
+ * @property {string} [format] - Message when date doesn't match expected format
35
+ * @property {string} [min] - Message when date is before minimum allowed
36
+ * @property {string} [max] - Message when date is after maximum allowed
37
+ * @property {string} [includes] - Message when date string doesn't contain required text
38
+ * @property {string} [excludes] - Message when date string contains forbidden text
39
+ * @property {string} [past] - Message when date must be in the past
40
+ * @property {string} [future] - Message when date must be in the future
41
+ * @property {string} [today] - Message when date must be today
42
+ * @property {string} [notToday] - Message when date must not be today
43
+ * @property {string} [weekday] - Message when date must be a weekday
44
+ * @property {string} [notWeekday] - Message when date must not be a weekday
45
+ * @property {string} [weekend] - Message when date must be a weekend
46
+ * @property {string} [notWeekend] - Message when date must not be a weekend
47
+ */
48
+ export type DateMessages = {
49
+ required?: string
50
+ invalid?: string
51
+ format?: string
52
+ min?: string
53
+ max?: string
54
+ includes?: string
55
+ excludes?: string
56
+ past?: string
57
+ future?: string
58
+ today?: string
59
+ notToday?: string
60
+ weekday?: string
61
+ notWeekday?: string
62
+ weekend?: string
63
+ notWeekend?: string
64
+ }
65
+
66
+ /**
67
+ * Configuration options for date validation
68
+ *
69
+ * @template IsRequired - Whether the field is required (affects return type)
70
+ *
71
+ * @interface DateOptions
72
+ * @property {IsRequired} [required=true] - Whether the field is required
73
+ * @property {string} [min] - Minimum allowed date (in same format as specified)
74
+ * @property {string} [max] - Maximum allowed date (in same format as specified)
75
+ * @property {string} [format="YYYY-MM-DD"] - Date format for parsing and validation
76
+ * @property {string} [includes] - String that must be included in the date
77
+ * @property {string | string[]} [excludes] - String(s) that must not be included
78
+ * @property {boolean} [mustBePast] - Whether date must be in the past
79
+ * @property {boolean} [mustBeFuture] - Whether date must be in the future
80
+ * @property {boolean} [mustBeToday] - Whether date must be today
81
+ * @property {boolean} [mustNotBeToday] - Whether date must not be today
82
+ * @property {boolean} [weekdaysOnly] - Whether date must be a weekday (Monday-Friday)
83
+ * @property {boolean} [weekendsOnly] - Whether date must be a weekend (Saturday-Sunday)
84
+ * @property {Function} [transform] - Custom transformation function for date strings
85
+ * @property {string | null} [defaultValue] - Default value when input is empty
86
+ * @property {Record<Locale, DateMessages>} [i18n] - Custom error messages for different locales
87
+ */
88
+ export type DateOptions<IsRequired extends boolean = true> = {
89
+ required?: IsRequired
90
+ min?: string
91
+ max?: string
92
+ format?: string
93
+ includes?: string
94
+ excludes?: string | string[]
95
+ mustBePast?: boolean
96
+ mustBeFuture?: boolean
97
+ mustBeToday?: boolean
98
+ mustNotBeToday?: boolean
99
+ weekdaysOnly?: boolean
100
+ weekendsOnly?: boolean
101
+ transform?: (value: string) => string
102
+ defaultValue?: IsRequired extends true ? string : string | null
103
+ i18n?: Record<Locale, DateMessages>
104
+ }
105
+
106
+ /**
107
+ * Type alias for date validation schema based on required flag
108
+ *
109
+ * @template IsRequired - Whether the field is required
110
+ * @typedef DateSchema
111
+ * @description Returns ZodString if required, ZodNullable<ZodString> if optional
112
+ */
113
+ export type DateSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
114
+
115
+ /**
116
+ * Creates a Zod schema for date validation with temporal constraints
117
+ *
118
+ * @template IsRequired - Whether the field is required (affects return type)
119
+ * @param {DateOptions<IsRequired>} [options] - Configuration options for date validation
120
+ * @returns {DateSchema<IsRequired>} Zod schema for date validation
121
+ *
122
+ * @description
123
+ * Creates a comprehensive date validator with format support, range validation,
124
+ * temporal constraints, and weekday/weekend filtering using dayjs library.
125
+ *
126
+ * Features:
127
+ * - Flexible date format parsing (default: YYYY-MM-DD)
128
+ * - Range validation (min/max dates)
129
+ * - Temporal validation (past/future/today)
130
+ * - Weekday/weekend filtering
131
+ * - Content inclusion/exclusion
132
+ * - Custom transformation functions
133
+ * - Comprehensive internationalization
134
+ * - Strict date parsing with format validation
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * // Basic date validation (YYYY-MM-DD)
139
+ * const basicSchema = date()
140
+ * basicSchema.parse("2024-03-15") // ✓ Valid
141
+ * basicSchema.parse("2024-13-01") // ✗ Invalid (month 13)
142
+ *
143
+ * // Custom format
144
+ * const customFormatSchema = date({ format: "DD/MM/YYYY" })
145
+ * customFormatSchema.parse("15/03/2024") // ✓ Valid
146
+ * customFormatSchema.parse("2024-03-15") // ✗ Invalid (wrong format)
147
+ *
148
+ * // Date range validation
149
+ * const rangeSchema = date({
150
+ * min: "2024-01-01",
151
+ * max: "2024-12-31"
152
+ * })
153
+ * rangeSchema.parse("2024-06-15") // ✓ Valid
154
+ * rangeSchema.parse("2023-12-31") // ✗ Invalid (before min)
155
+ *
156
+ * // Future dates only
157
+ * const futureSchema = date({ mustBeFuture: true })
158
+ * futureSchema.parse("2030-01-01") // ✓ Valid (assuming current date < 2030)
159
+ * futureSchema.parse("2020-01-01") // ✗ Invalid (past date)
160
+ *
161
+ * // Weekdays only (Monday-Friday)
162
+ * const weekdaySchema = date({ weekdaysOnly: true })
163
+ * weekdaySchema.parse("2024-03-15") // ✓ Valid (if Friday)
164
+ * weekdaySchema.parse("2024-03-16") // ✗ Invalid (if Saturday)
165
+ *
166
+ * // Business date validation
167
+ * const businessSchema = date({
168
+ * format: "YYYY-MM-DD",
169
+ * mustBeFuture: true,
170
+ * weekdaysOnly: true
171
+ * })
172
+ *
173
+ * // Optional with default
174
+ * const optionalSchema = date({
175
+ * required: false,
176
+ * defaultValue: "2024-01-01"
177
+ * })
178
+ * ```
179
+ *
180
+ * @throws {z.ZodError} When validation fails with specific error messages
181
+ * @see {@link DateOptions} for all available configuration options
182
+ */
183
+ export function date<IsRequired extends boolean = true>(options?: DateOptions<IsRequired>): DateSchema<IsRequired> {
184
+ const {
185
+ required = true,
186
+ min,
187
+ max,
188
+ format = "YYYY-MM-DD",
189
+ includes,
190
+ excludes,
191
+ mustBePast,
192
+ mustBeFuture,
193
+ mustBeToday,
194
+ mustNotBeToday,
195
+ weekdaysOnly,
196
+ weekendsOnly,
197
+ transform,
198
+ defaultValue = null,
199
+ i18n,
200
+ } = options ?? {}
201
+
202
+ const actualDefaultValue = defaultValue ?? (required ? "" : null)
203
+
204
+ // Helper function to get custom message or fallback to default i18n
205
+ const getMessage = (key: keyof DateMessages, params?: Record<string, any>) => {
206
+ if (i18n) {
207
+ const currentLocale = getLocale()
208
+ const customMessages = i18n[currentLocale]
209
+ if (customMessages && customMessages[key]) {
210
+ const template = customMessages[key]!
211
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
212
+ }
213
+ }
214
+ return t(`common.date.${key}`, params)
215
+ }
216
+
217
+ // Preprocessing function with transformations
218
+ const preprocessFn = (val: unknown) => {
219
+ if (val === "" || val === null || val === undefined) {
220
+ return actualDefaultValue
221
+ }
222
+
223
+ let processed = String(val).trim()
224
+
225
+ if (transform) {
226
+ processed = transform(processed)
227
+ }
228
+
229
+ return processed
230
+ }
231
+
232
+ const baseSchema = required ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
233
+
234
+ const schema = baseSchema.refine((val) => {
235
+ if (val === null) return true
236
+
237
+ // Required check
238
+ if (required && (val === "" || val === "null" || val === "undefined")) {
239
+ throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
240
+ }
241
+
242
+ // Format validation
243
+ if (val !== null && !dayjs(val, format, true).isValid()) {
244
+ throw new z.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }])
245
+ }
246
+
247
+ const dateObj = dayjs(val, format)
248
+
249
+ // Range checks
250
+ if (val !== null && min !== undefined && !dateObj.isSameOrAfter(dayjs(min, format))) {
251
+ throw new z.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }])
252
+ }
253
+ if (val !== null && max !== undefined && !dateObj.isSameOrBefore(dayjs(max, format))) {
254
+ throw new z.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }])
255
+ }
256
+
257
+ // String content checks
258
+ if (val !== null && includes !== undefined && !val.includes(includes)) {
259
+ throw new z.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }])
260
+ }
261
+ if (val !== null && excludes !== undefined) {
262
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes]
263
+ for (const exclude of excludeList) {
264
+ if (val.includes(exclude)) {
265
+ throw new z.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }])
266
+ }
267
+ }
268
+ }
269
+
270
+ // Time-based validations
271
+ const today = dayjs().startOf('day')
272
+ const targetDate = dateObj.startOf('day')
273
+
274
+ if (val !== null && mustBePast && !targetDate.isBefore(today)) {
275
+ throw new z.ZodError([{ code: "custom", message: getMessage("past"), path: [] }])
276
+ }
277
+ if (val !== null && mustBeFuture && !targetDate.isAfter(today)) {
278
+ throw new z.ZodError([{ code: "custom", message: getMessage("future"), path: [] }])
279
+ }
280
+ if (val !== null && mustBeToday && !targetDate.isSame(today)) {
281
+ throw new z.ZodError([{ code: "custom", message: getMessage("today"), path: [] }])
282
+ }
283
+ if (val !== null && mustNotBeToday && targetDate.isSame(today)) {
284
+ throw new z.ZodError([{ code: "custom", message: getMessage("notToday"), path: [] }])
285
+ }
286
+
287
+ // Weekday/weekend validations
288
+ if (val !== null && weekdaysOnly && (dateObj.day() === 0 || dateObj.day() === 6)) {
289
+ throw new z.ZodError([{ code: "custom", message: getMessage("weekday"), path: [] }])
290
+ }
291
+ if (val !== null && weekendsOnly && dateObj.day() !== 0 && dateObj.day() !== 6) {
292
+ throw new z.ZodError([{ code: "custom", message: getMessage("weekend"), path: [] }])
293
+ }
294
+
295
+ return true
296
+ })
297
+
298
+ return schema as unknown as DateSchema<IsRequired>
299
+ }