@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
|
@@ -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
|
+
}
|