@hy_ong/zod-kit 0.0.5 → 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 +6 -1
- package/README.md +465 -97
- package/dist/index.cjs +1628 -121
- package/dist/index.d.cts +2699 -2
- package/dist/index.d.ts +2699 -2
- package/dist/index.js +1610 -120
- package/package.json +1 -1
- package/src/i18n/locales/en.json +62 -0
- package/src/i18n/locales/zh-TW.json +62 -0
- package/src/index.ts +4 -0
- package/src/validators/common/boolean.ts +94 -0
- package/src/validators/common/date.ts +128 -0
- package/src/validators/common/datetime.ts +673 -0
- package/src/validators/common/email.ts +113 -0
- package/src/validators/common/file.ts +384 -0
- package/src/validators/common/id.ts +224 -12
- package/src/validators/common/number.ts +125 -0
- package/src/validators/common/password.ts +174 -2
- package/src/validators/common/text.ts +120 -0
- package/src/validators/common/time.ts +600 -0
- package/src/validators/common/url.ts +140 -0
- package/src/validators/taiwan/business-id.ts +124 -2
- package/src/validators/taiwan/fax.ts +147 -2
- package/src/validators/taiwan/mobile.ts +134 -2
- package/src/validators/taiwan/national-id.ts +227 -10
- package/src/validators/taiwan/postal-code.ts +1049 -0
- package/src/validators/taiwan/tel.ts +150 -2
- package/tests/common/datetime.test.ts +693 -0
- package/tests/common/file.test.ts +479 -0
- package/tests/common/time.test.ts +528 -0
- package/tests/taiwan/postal-code.test.ts +705 -0
|
@@ -1,7 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ID validator for Zod Kit
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive ID validation with support for multiple ID formats,
|
|
5
|
+
* auto-detection, custom patterns, and flexible validation options.
|
|
6
|
+
*
|
|
7
|
+
* @author Ong Hoe Yuan
|
|
8
|
+
* @version 0.0.5
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
import { z, ZodNullable, ZodString } from "zod"
|
|
2
12
|
import { t } from "../../i18n"
|
|
3
13
|
import { getLocale, type Locale } from "../../config"
|
|
4
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Type definition for ID validation error messages
|
|
17
|
+
*
|
|
18
|
+
* @interface IdMessages
|
|
19
|
+
* @property {string} [required] - Message when field is required but empty
|
|
20
|
+
* @property {string} [invalid] - Message when ID format is invalid
|
|
21
|
+
* @property {string} [minLength] - Message when ID is too short
|
|
22
|
+
* @property {string} [maxLength] - Message when ID is too long
|
|
23
|
+
* @property {string} [numeric] - Message when numeric ID format is invalid
|
|
24
|
+
* @property {string} [uuid] - Message when UUID format is invalid
|
|
25
|
+
* @property {string} [objectId] - Message when MongoDB ObjectId format is invalid
|
|
26
|
+
* @property {string} [nanoid] - Message when Nano ID format is invalid
|
|
27
|
+
* @property {string} [snowflake] - Message when Snowflake ID format is invalid
|
|
28
|
+
* @property {string} [cuid] - Message when CUID format is invalid
|
|
29
|
+
* @property {string} [ulid] - Message when ULID format is invalid
|
|
30
|
+
* @property {string} [shortid] - Message when ShortId format is invalid
|
|
31
|
+
* @property {string} [customFormat] - Message when custom regex format is invalid
|
|
32
|
+
* @property {string} [includes] - Message when ID doesn't contain required string
|
|
33
|
+
* @property {string} [excludes] - Message when ID contains forbidden string
|
|
34
|
+
* @property {string} [startsWith] - Message when ID doesn't start with required string
|
|
35
|
+
* @property {string} [endsWith] - Message when ID doesn't end with required string
|
|
36
|
+
*/
|
|
5
37
|
export type IdMessages = {
|
|
6
38
|
required?: string
|
|
7
39
|
invalid?: string
|
|
@@ -22,17 +54,54 @@ export type IdMessages = {
|
|
|
22
54
|
endsWith?: string
|
|
23
55
|
}
|
|
24
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Supported ID types for validation
|
|
59
|
+
*
|
|
60
|
+
* @typedef {string} IdType
|
|
61
|
+
*
|
|
62
|
+
* Available types:
|
|
63
|
+
* - numeric: Pure numeric IDs (1, 123, 999999)
|
|
64
|
+
* - uuid: UUID v4 format (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
|
65
|
+
* - objectId: MongoDB ObjectId (24-character hexadecimal)
|
|
66
|
+
* - nanoid: Nano ID format (21-character URL-safe)
|
|
67
|
+
* - snowflake: Twitter Snowflake (19-digit number)
|
|
68
|
+
* - cuid: CUID format (25-character starting with 'c')
|
|
69
|
+
* - ulid: ULID format (26-character case-insensitive)
|
|
70
|
+
* - shortid: ShortId format (7-14 character URL-safe)
|
|
71
|
+
* - auto: Auto-detect format from the value
|
|
72
|
+
*/
|
|
25
73
|
export type IdType =
|
|
26
|
-
| "numeric" //
|
|
27
|
-
| "uuid" // UUID v4
|
|
28
|
-
| "objectId" // MongoDB ObjectId (24
|
|
74
|
+
| "numeric" // Pure numeric IDs (1, 123, 999999)
|
|
75
|
+
| "uuid" // UUID v4 format
|
|
76
|
+
| "objectId" // MongoDB ObjectId (24-character hexadecimal)
|
|
29
77
|
| "nanoid" // Nano ID
|
|
30
|
-
| "snowflake" // Twitter Snowflake (19
|
|
31
|
-
| "cuid" // CUID
|
|
32
|
-
| "ulid" // ULID
|
|
33
|
-
| "shortid" // ShortId
|
|
34
|
-
| "auto" //
|
|
78
|
+
| "snowflake" // Twitter Snowflake (19-digit number)
|
|
79
|
+
| "cuid" // CUID format
|
|
80
|
+
| "ulid" // ULID format
|
|
81
|
+
| "shortid" // ShortId format
|
|
82
|
+
| "auto" // Auto-detect format
|
|
35
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Configuration options for ID validation
|
|
86
|
+
*
|
|
87
|
+
* @template IsRequired - Whether the field is required (affects return type)
|
|
88
|
+
*
|
|
89
|
+
* @interface IdOptions
|
|
90
|
+
* @property {IsRequired} [required=true] - Whether the field is required
|
|
91
|
+
* @property {IdType} [type="auto"] - Expected ID type or auto-detection
|
|
92
|
+
* @property {number} [minLength] - Minimum length of ID
|
|
93
|
+
* @property {number} [maxLength] - Maximum length of ID
|
|
94
|
+
* @property {IdType[]} [allowedTypes] - Multiple allowed ID types (overrides type)
|
|
95
|
+
* @property {RegExp} [customRegex] - Custom regex pattern (overrides type validation)
|
|
96
|
+
* @property {string} [includes] - String that must be included in ID
|
|
97
|
+
* @property {string | string[]} [excludes] - String(s) that must not be included
|
|
98
|
+
* @property {string} [startsWith] - String that ID must start with
|
|
99
|
+
* @property {string} [endsWith] - String that ID must end with
|
|
100
|
+
* @property {boolean} [caseSensitive=true] - Whether validation is case-sensitive
|
|
101
|
+
* @property {Function} [transform] - Custom transformation function for ID
|
|
102
|
+
* @property {string | null} [defaultValue] - Default value when input is empty
|
|
103
|
+
* @property {Record<Locale, IdMessages>} [i18n] - Custom error messages for different locales
|
|
104
|
+
*/
|
|
36
105
|
export type IdOptions<IsRequired extends boolean = true> = {
|
|
37
106
|
required?: IsRequired
|
|
38
107
|
type?: IdType
|
|
@@ -50,9 +119,21 @@ export type IdOptions<IsRequired extends boolean = true> = {
|
|
|
50
119
|
i18n?: Record<Locale, IdMessages>
|
|
51
120
|
}
|
|
52
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Type alias for ID validation schema based on required flag
|
|
124
|
+
*
|
|
125
|
+
* @template IsRequired - Whether the field is required
|
|
126
|
+
* @typedef IdSchema
|
|
127
|
+
* @description Returns ZodString if required, ZodNullable<ZodString> if optional
|
|
128
|
+
*/
|
|
53
129
|
export type IdSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
54
130
|
|
|
55
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Regular expression patterns for different ID formats
|
|
133
|
+
*
|
|
134
|
+
* @constant {Record<string, RegExp>} ID_PATTERNS
|
|
135
|
+
* @description Maps each ID type to its corresponding regex pattern
|
|
136
|
+
*/
|
|
56
137
|
const ID_PATTERNS = {
|
|
57
138
|
numeric: /^\d+$/,
|
|
58
139
|
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
@@ -64,7 +145,26 @@ const ID_PATTERNS = {
|
|
|
64
145
|
shortid: /^[A-Za-z0-9_-]{7,14}$/,
|
|
65
146
|
} as const
|
|
66
147
|
|
|
67
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Detects the ID type of a given value using pattern matching
|
|
150
|
+
*
|
|
151
|
+
* @param {string} value - The ID value to analyze
|
|
152
|
+
* @returns {IdType | null} The detected ID type or null if no pattern matches
|
|
153
|
+
*
|
|
154
|
+
* @description
|
|
155
|
+
* Attempts to identify the ID type by testing against known patterns.
|
|
156
|
+
* Patterns are ordered by specificity to avoid false positives.
|
|
157
|
+
* More specific patterns (UUID, ObjectId) are tested before generic ones (numeric, shortid).
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* detectIdType("550e8400-e29b-41d4-a716-446655440000") // "uuid"
|
|
162
|
+
* detectIdType("507f1f77bcf86cd799439011") // "objectId"
|
|
163
|
+
* detectIdType("V1StGXR8_Z5jdHi6B-myT") // "nanoid"
|
|
164
|
+
* detectIdType("123456789") // "numeric"
|
|
165
|
+
* detectIdType("invalid-id") // null
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
68
168
|
const detectIdType = (value: string): IdType | null => {
|
|
69
169
|
// 按優先順序檢查(從最具體到最通用)
|
|
70
170
|
const orderedTypes: Array<[IdType, RegExp]> = [
|
|
@@ -86,7 +186,25 @@ const detectIdType = (value: string): IdType | null => {
|
|
|
86
186
|
return null
|
|
87
187
|
}
|
|
88
188
|
|
|
89
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Validates if a value matches the specified ID type
|
|
191
|
+
*
|
|
192
|
+
* @param {string} value - The ID value to validate
|
|
193
|
+
* @param {IdType} type - The expected ID type
|
|
194
|
+
* @returns {boolean} True if the value matches the specified type
|
|
195
|
+
*
|
|
196
|
+
* @description
|
|
197
|
+
* Validates a specific ID type using regex patterns or auto-detection.
|
|
198
|
+
* For "auto" type, uses detectIdType to check if any known pattern matches.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* validateIdType("123456", "numeric") // true
|
|
203
|
+
* validateIdType("abc123", "numeric") // false
|
|
204
|
+
* validateIdType("550e8400-e29b-41d4-a716-446655440000", "uuid") // true
|
|
205
|
+
* validateIdType("invalid-uuid", "uuid") // false
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
90
208
|
const validateIdType = (value: string, type: IdType): boolean => {
|
|
91
209
|
if (type === "auto") {
|
|
92
210
|
return detectIdType(value) !== null
|
|
@@ -95,6 +213,80 @@ const validateIdType = (value: string, type: IdType): boolean => {
|
|
|
95
213
|
return pattern ? pattern.test(value) : false
|
|
96
214
|
}
|
|
97
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Creates a Zod schema for ID validation with comprehensive format support
|
|
218
|
+
*
|
|
219
|
+
* @template IsRequired - Whether the field is required (affects return type)
|
|
220
|
+
* @param {IdOptions<IsRequired>} [options] - Configuration options for ID validation
|
|
221
|
+
* @returns {IdSchema<IsRequired>} Zod schema for ID validation
|
|
222
|
+
*
|
|
223
|
+
* @description
|
|
224
|
+
* Creates a comprehensive ID validator with support for multiple ID formats,
|
|
225
|
+
* auto-detection, custom patterns, and flexible validation options.
|
|
226
|
+
*
|
|
227
|
+
* Features:
|
|
228
|
+
* - Multiple ID format support (UUID, ObjectId, Snowflake, etc.)
|
|
229
|
+
* - Auto-detection of ID types
|
|
230
|
+
* - Custom regex pattern support
|
|
231
|
+
* - Length validation
|
|
232
|
+
* - Content validation (includes, excludes, startsWith, endsWith)
|
|
233
|
+
* - Case sensitivity control
|
|
234
|
+
* - Multiple allowed types
|
|
235
|
+
* - Custom transformation functions
|
|
236
|
+
* - Comprehensive internationalization
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```typescript
|
|
240
|
+
* // Auto-detect ID format
|
|
241
|
+
* const autoSchema = id()
|
|
242
|
+
* autoSchema.parse("550e8400-e29b-41d4-a716-446655440000") // ✓ Valid (UUID)
|
|
243
|
+
* autoSchema.parse("507f1f77bcf86cd799439011") // ✓ Valid (ObjectId)
|
|
244
|
+
* autoSchema.parse("123456") // ✓ Valid (numeric)
|
|
245
|
+
*
|
|
246
|
+
* // Specific ID type
|
|
247
|
+
* const uuidSchema = id({ type: "uuid" })
|
|
248
|
+
* uuidSchema.parse("550e8400-e29b-41d4-a716-446655440000") // ✓ Valid
|
|
249
|
+
* uuidSchema.parse("invalid-uuid") // ✗ Invalid
|
|
250
|
+
*
|
|
251
|
+
* // Multiple allowed types
|
|
252
|
+
* const multiSchema = id({ allowedTypes: ["uuid", "objectId"] })
|
|
253
|
+
* multiSchema.parse("550e8400-e29b-41d4-a716-446655440000") // ✓ Valid (UUID)
|
|
254
|
+
* multiSchema.parse("507f1f77bcf86cd799439011") // ✓ Valid (ObjectId)
|
|
255
|
+
* multiSchema.parse("123456") // ✗ Invalid (numeric not allowed)
|
|
256
|
+
*
|
|
257
|
+
* // Custom regex pattern
|
|
258
|
+
* const customSchema = id({ customRegex: /^CUST_\d{6}$/ })
|
|
259
|
+
* customSchema.parse("CUST_123456") // ✓ Valid
|
|
260
|
+
* customSchema.parse("invalid") // ✗ Invalid
|
|
261
|
+
*
|
|
262
|
+
* // Content validation
|
|
263
|
+
* const prefixSchema = id({
|
|
264
|
+
* type: "auto",
|
|
265
|
+
* startsWith: "user_",
|
|
266
|
+
* minLength: 10
|
|
267
|
+
* })
|
|
268
|
+
* prefixSchema.parse("user_123456") // ✓ Valid
|
|
269
|
+
*
|
|
270
|
+
* // Case insensitive
|
|
271
|
+
* const caseInsensitiveSchema = id({
|
|
272
|
+
* type: "uuid",
|
|
273
|
+
* caseSensitive: false
|
|
274
|
+
* })
|
|
275
|
+
* caseInsensitiveSchema.parse("550E8400-E29B-41D4-A716-446655440000") // ✓ Valid
|
|
276
|
+
*
|
|
277
|
+
* // Optional with default
|
|
278
|
+
* const optionalSchema = id({
|
|
279
|
+
* required: false,
|
|
280
|
+
* defaultValue: null
|
|
281
|
+
* })
|
|
282
|
+
* ```
|
|
283
|
+
*
|
|
284
|
+
* @throws {z.ZodError} When validation fails with specific error messages
|
|
285
|
+
* @see {@link IdOptions} for all available configuration options
|
|
286
|
+
* @see {@link IdType} for supported ID types
|
|
287
|
+
* @see {@link detectIdType} for auto-detection logic
|
|
288
|
+
* @see {@link validateIdType} for type-specific validation
|
|
289
|
+
*/
|
|
98
290
|
export function id<IsRequired extends boolean = true>(options?: IdOptions<IsRequired>): IdSchema<IsRequired> {
|
|
99
291
|
const {
|
|
100
292
|
required = true,
|
|
@@ -255,5 +447,25 @@ export function id<IsRequired extends boolean = true>(options?: IdOptions<IsRequ
|
|
|
255
447
|
return schema as unknown as IdSchema<IsRequired>
|
|
256
448
|
}
|
|
257
449
|
|
|
258
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Utility functions and constants exported for external use
|
|
452
|
+
*
|
|
453
|
+
* @description
|
|
454
|
+
* These utilities can be used independently for ID validation, type detection,
|
|
455
|
+
* and pattern matching without creating a full Zod schema.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* import { detectIdType, validateIdType, ID_PATTERNS } from './id'
|
|
460
|
+
*
|
|
461
|
+
* // Detect ID type
|
|
462
|
+
* const type = detectIdType("550e8400-e29b-41d4-a716-446655440000") // "uuid"
|
|
463
|
+
*
|
|
464
|
+
* // Validate specific type
|
|
465
|
+
* const isValid = validateIdType("123456", "numeric") // true
|
|
466
|
+
*
|
|
467
|
+
* // Access regex patterns
|
|
468
|
+
* const uuidPattern = ID_PATTERNS.uuid
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
259
471
|
export { detectIdType, validateIdType, ID_PATTERNS }
|
|
@@ -1,7 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Number validator for Zod Kit
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive number validation with type constraints, range validation,
|
|
5
|
+
* precision control, and advanced parsing features including comma-separated numbers.
|
|
6
|
+
*
|
|
7
|
+
* @author Ong Hoe Yuan
|
|
8
|
+
* @version 0.0.5
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
import { z, ZodNullable, ZodNumber } from "zod"
|
|
2
12
|
import { t } from "../../i18n"
|
|
3
13
|
import { getLocale, type Locale } from "../../config"
|
|
4
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Type definition for number validation error messages
|
|
17
|
+
*
|
|
18
|
+
* @interface NumberMessages
|
|
19
|
+
* @property {string} [required] - Message when field is required but empty
|
|
20
|
+
* @property {string} [invalid] - Message when value is not a valid number
|
|
21
|
+
* @property {string} [integer] - Message when integer is required but float provided
|
|
22
|
+
* @property {string} [float] - Message when float is required but integer provided
|
|
23
|
+
* @property {string} [min] - Message when number is below minimum value
|
|
24
|
+
* @property {string} [max] - Message when number exceeds maximum value
|
|
25
|
+
* @property {string} [positive] - Message when positive number is required
|
|
26
|
+
* @property {string} [negative] - Message when negative number is required
|
|
27
|
+
* @property {string} [nonNegative] - Message when non-negative number is required
|
|
28
|
+
* @property {string} [nonPositive] - Message when non-positive number is required
|
|
29
|
+
* @property {string} [multipleOf] - Message when number is not a multiple of specified value
|
|
30
|
+
* @property {string} [finite] - Message when finite number is required
|
|
31
|
+
* @property {string} [precision] - Message when number has too many decimal places
|
|
32
|
+
*/
|
|
5
33
|
export type NumberMessages = {
|
|
6
34
|
required?: string
|
|
7
35
|
invalid?: string
|
|
@@ -18,6 +46,28 @@ export type NumberMessages = {
|
|
|
18
46
|
precision?: string
|
|
19
47
|
}
|
|
20
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Configuration options for number validation
|
|
51
|
+
*
|
|
52
|
+
* @template IsRequired - Whether the field is required (affects return type)
|
|
53
|
+
*
|
|
54
|
+
* @interface NumberOptions
|
|
55
|
+
* @property {IsRequired} [required=true] - Whether the field is required
|
|
56
|
+
* @property {number} [min] - Minimum allowed value
|
|
57
|
+
* @property {number} [max] - Maximum allowed value
|
|
58
|
+
* @property {number | null} [defaultValue] - Default value when input is empty
|
|
59
|
+
* @property {"integer" | "float" | "both"} [type="both"] - Type constraint for the number
|
|
60
|
+
* @property {boolean} [positive] - Whether number must be positive (> 0)
|
|
61
|
+
* @property {boolean} [negative] - Whether number must be negative (< 0)
|
|
62
|
+
* @property {boolean} [nonNegative] - Whether number must be non-negative (>= 0)
|
|
63
|
+
* @property {boolean} [nonPositive] - Whether number must be non-positive (<= 0)
|
|
64
|
+
* @property {number} [multipleOf] - Number must be a multiple of this value
|
|
65
|
+
* @property {number} [precision] - Maximum number of decimal places allowed
|
|
66
|
+
* @property {boolean} [finite=true] - Whether to reject Infinity and -Infinity
|
|
67
|
+
* @property {Function} [transform] - Custom transformation function for number values
|
|
68
|
+
* @property {boolean} [parseCommas=false] - Whether to parse comma-separated numbers (e.g., "1,234")
|
|
69
|
+
* @property {Record<Locale, NumberMessages>} [i18n] - Custom error messages for different locales
|
|
70
|
+
*/
|
|
21
71
|
export type NumberOptions<IsRequired extends boolean = true> = {
|
|
22
72
|
required?: IsRequired
|
|
23
73
|
min?: number
|
|
@@ -36,8 +86,83 @@ export type NumberOptions<IsRequired extends boolean = true> = {
|
|
|
36
86
|
i18n?: Record<Locale, NumberMessages>
|
|
37
87
|
}
|
|
38
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Type alias for number validation schema based on required flag
|
|
91
|
+
*
|
|
92
|
+
* @template IsRequired - Whether the field is required
|
|
93
|
+
* @typedef NumberSchema
|
|
94
|
+
* @description Returns ZodNumber if required, ZodNullable<ZodNumber> if optional
|
|
95
|
+
*/
|
|
39
96
|
export type NumberSchema<IsRequired extends boolean> = IsRequired extends true ? ZodNumber : ZodNullable<ZodNumber>
|
|
40
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Creates a Zod schema for number validation with comprehensive constraints
|
|
100
|
+
*
|
|
101
|
+
* @template IsRequired - Whether the field is required (affects return type)
|
|
102
|
+
* @param {NumberOptions<IsRequired>} [options] - Configuration options for number validation
|
|
103
|
+
* @returns {NumberSchema<IsRequired>} Zod schema for number validation
|
|
104
|
+
*
|
|
105
|
+
* @description
|
|
106
|
+
* Creates a comprehensive number validator with type constraints, range validation,
|
|
107
|
+
* precision control, and advanced parsing features including comma-separated numbers.
|
|
108
|
+
*
|
|
109
|
+
* Features:
|
|
110
|
+
* - Type constraints (integer, float, or both)
|
|
111
|
+
* - Range validation (min/max)
|
|
112
|
+
* - Sign constraints (positive, negative, non-negative, non-positive)
|
|
113
|
+
* - Multiple-of validation
|
|
114
|
+
* - Precision control (decimal places)
|
|
115
|
+
* - Finite number validation
|
|
116
|
+
* - Comma-separated number parsing
|
|
117
|
+
* - Custom transformation functions
|
|
118
|
+
* - Comprehensive internationalization
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* // Basic number validation
|
|
123
|
+
* const basicSchema = number()
|
|
124
|
+
* basicSchema.parse(42) // ✓ Valid
|
|
125
|
+
* basicSchema.parse("42") // ✓ Valid (converted to number)
|
|
126
|
+
*
|
|
127
|
+
* // Integer only
|
|
128
|
+
* const integerSchema = number({ type: "integer" })
|
|
129
|
+
* integerSchema.parse(42) // ✓ Valid
|
|
130
|
+
* integerSchema.parse(42.5) // ✗ Invalid
|
|
131
|
+
*
|
|
132
|
+
* // Range validation
|
|
133
|
+
* const rangeSchema = number({ min: 0, max: 100 })
|
|
134
|
+
* rangeSchema.parse(50) // ✓ Valid
|
|
135
|
+
* rangeSchema.parse(150) // ✗ Invalid
|
|
136
|
+
*
|
|
137
|
+
* // Positive numbers only
|
|
138
|
+
* const positiveSchema = number({ positive: true })
|
|
139
|
+
* positiveSchema.parse(5) // ✓ Valid
|
|
140
|
+
* positiveSchema.parse(-5) // ✗ Invalid
|
|
141
|
+
*
|
|
142
|
+
* // Multiple of constraint
|
|
143
|
+
* const multipleSchema = number({ multipleOf: 5 })
|
|
144
|
+
* multipleSchema.parse(10) // ✓ Valid
|
|
145
|
+
* multipleSchema.parse(7) // ✗ Invalid
|
|
146
|
+
*
|
|
147
|
+
* // Precision control
|
|
148
|
+
* const precisionSchema = number({ precision: 2 })
|
|
149
|
+
* precisionSchema.parse(3.14) // ✓ Valid
|
|
150
|
+
* precisionSchema.parse(3.14159) // ✗ Invalid
|
|
151
|
+
*
|
|
152
|
+
* // Comma-separated parsing
|
|
153
|
+
* const commaSchema = number({ parseCommas: true })
|
|
154
|
+
* commaSchema.parse("1,234.56") // ✓ Valid (parsed as 1234.56)
|
|
155
|
+
*
|
|
156
|
+
* // Optional with default
|
|
157
|
+
* const optionalSchema = number({
|
|
158
|
+
* required: false,
|
|
159
|
+
* defaultValue: 0
|
|
160
|
+
* })
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @throws {z.ZodError} When validation fails with specific error messages
|
|
164
|
+
* @see {@link NumberOptions} for all available configuration options
|
|
165
|
+
*/
|
|
41
166
|
export function number<IsRequired extends boolean = true>(options?: NumberOptions<IsRequired>): NumberSchema<IsRequired> {
|
|
42
167
|
const {
|
|
43
168
|
required = true,
|
|
@@ -1,7 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Password validator for Zod Kit
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive password validation with strength analysis, character requirements,
|
|
5
|
+
* security checks, and protection against common weak passwords.
|
|
6
|
+
*
|
|
7
|
+
* @author Ong Hoe Yuan
|
|
8
|
+
* @version 0.0.5
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
import { z, ZodNullable, ZodString } from "zod"
|
|
2
12
|
import { t } from "../../i18n"
|
|
3
13
|
import { getLocale, type Locale } from "../../config"
|
|
4
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Type definition for password validation error messages
|
|
17
|
+
*
|
|
18
|
+
* @interface PasswordMessages
|
|
19
|
+
* @property {string} [required] - Message when field is required but empty
|
|
20
|
+
* @property {string} [min] - Message when password is too short
|
|
21
|
+
* @property {string} [max] - Message when password is too long
|
|
22
|
+
* @property {string} [uppercase] - Message when uppercase letters are required
|
|
23
|
+
* @property {string} [lowercase] - Message when lowercase letters are required
|
|
24
|
+
* @property {string} [digits] - Message when digits are required
|
|
25
|
+
* @property {string} [special] - Message when special characters are required
|
|
26
|
+
* @property {string} [noRepeating] - Message when repeating characters are forbidden
|
|
27
|
+
* @property {string} [noSequential] - Message when sequential characters are forbidden
|
|
28
|
+
* @property {string} [noCommonWords] - Message when common passwords are forbidden
|
|
29
|
+
* @property {string} [minStrength] - Message when password strength is insufficient
|
|
30
|
+
* @property {string} [excludes] - Message when password contains forbidden strings
|
|
31
|
+
* @property {string} [includes] - Message when password doesn't contain required string
|
|
32
|
+
* @property {string} [invalid] - Message when password doesn't match custom regex
|
|
33
|
+
*/
|
|
5
34
|
export type PasswordMessages = {
|
|
6
35
|
required?: string
|
|
7
36
|
min?: string
|
|
@@ -19,8 +48,42 @@ export type PasswordMessages = {
|
|
|
19
48
|
invalid?: string
|
|
20
49
|
}
|
|
21
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Password strength levels used for validation
|
|
53
|
+
*
|
|
54
|
+
* @typedef {"weak" | "medium" | "strong" | "very-strong"} PasswordStrength
|
|
55
|
+
* @description
|
|
56
|
+
* - weak: Basic passwords with minimal requirements
|
|
57
|
+
* - medium: Passwords with some character variety
|
|
58
|
+
* - strong: Passwords with good character variety and length
|
|
59
|
+
* - very-strong: Passwords with excellent character variety, length, and complexity
|
|
60
|
+
*/
|
|
22
61
|
export type PasswordStrength = "weak" | "medium" | "strong" | "very-strong"
|
|
23
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Configuration options for password validation
|
|
65
|
+
*
|
|
66
|
+
* @template IsRequired - Whether the field is required (affects return type)
|
|
67
|
+
*
|
|
68
|
+
* @interface PasswordOptions
|
|
69
|
+
* @property {IsRequired} [required=true] - Whether the field is required
|
|
70
|
+
* @property {number} [min] - Minimum length of password
|
|
71
|
+
* @property {number} [max] - Maximum length of password
|
|
72
|
+
* @property {boolean} [uppercase] - Whether uppercase letters are required
|
|
73
|
+
* @property {boolean} [lowercase] - Whether lowercase letters are required
|
|
74
|
+
* @property {boolean} [digits] - Whether digits are required
|
|
75
|
+
* @property {boolean} [special] - Whether special characters are required
|
|
76
|
+
* @property {boolean} [noRepeating] - Whether to forbid repeating characters (3+ in a row)
|
|
77
|
+
* @property {boolean} [noSequential] - Whether to forbid sequential characters (abc, 123)
|
|
78
|
+
* @property {boolean} [noCommonWords] - Whether to forbid common weak passwords
|
|
79
|
+
* @property {PasswordStrength} [minStrength] - Minimum required password strength
|
|
80
|
+
* @property {string | string[]} [excludes] - String(s) that must not be included
|
|
81
|
+
* @property {string} [includes] - String that must be included in password
|
|
82
|
+
* @property {RegExp} [regex] - Custom regex pattern for validation
|
|
83
|
+
* @property {Function} [transform] - Custom transformation function for password
|
|
84
|
+
* @property {string | null} [defaultValue] - Default value when input is empty
|
|
85
|
+
* @property {Record<Locale, PasswordMessages>} [i18n] - Custom error messages for different locales
|
|
86
|
+
*/
|
|
24
87
|
export type PasswordOptions<IsRequired extends boolean = true> = {
|
|
25
88
|
required?: IsRequired
|
|
26
89
|
min?: number
|
|
@@ -41,9 +104,21 @@ export type PasswordOptions<IsRequired extends boolean = true> = {
|
|
|
41
104
|
i18n?: Record<Locale, PasswordMessages>
|
|
42
105
|
}
|
|
43
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Type alias for password validation schema based on required flag
|
|
109
|
+
*
|
|
110
|
+
* @template IsRequired - Whether the field is required
|
|
111
|
+
* @typedef PasswordSchema
|
|
112
|
+
* @description Returns ZodString if required, ZodNullable<ZodString> if optional
|
|
113
|
+
*/
|
|
44
114
|
export type PasswordSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
45
115
|
|
|
46
|
-
|
|
116
|
+
/**
|
|
117
|
+
* List of common weak passwords to check against
|
|
118
|
+
*
|
|
119
|
+
* @constant {string[]} COMMON_PASSWORDS
|
|
120
|
+
* @description Contains frequently used weak passwords that should be avoided
|
|
121
|
+
*/
|
|
47
122
|
const COMMON_PASSWORDS = [
|
|
48
123
|
"password",
|
|
49
124
|
"123456",
|
|
@@ -63,7 +138,31 @@ const COMMON_PASSWORDS = [
|
|
|
63
138
|
"princess",
|
|
64
139
|
]
|
|
65
140
|
|
|
66
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Calculates password strength based on various criteria
|
|
143
|
+
*
|
|
144
|
+
* @param {string} password - The password to analyze
|
|
145
|
+
* @returns {PasswordStrength} The calculated strength level
|
|
146
|
+
*
|
|
147
|
+
* @description
|
|
148
|
+
* Analyzes password strength using multiple factors:
|
|
149
|
+
* - Length bonuses (8+, 12+, 16+ characters)
|
|
150
|
+
* - Character variety (lowercase, uppercase, digits, special characters)
|
|
151
|
+
* - Deductions for repeating or sequential patterns
|
|
152
|
+
*
|
|
153
|
+
* Scoring system:
|
|
154
|
+
* - 0-2 points: weak
|
|
155
|
+
* - 3-4 points: medium
|
|
156
|
+
* - 5-6 points: strong
|
|
157
|
+
* - 7+ points: very-strong
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* calculatePasswordStrength("password") // "weak"
|
|
162
|
+
* calculatePasswordStrength("Password123") // "medium"
|
|
163
|
+
* calculatePasswordStrength("MyStr0ng!P@ssw0rd") // "very-strong"
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
67
166
|
const calculatePasswordStrength = (password: string): PasswordStrength => {
|
|
68
167
|
let score = 0
|
|
69
168
|
|
|
@@ -88,6 +187,79 @@ const calculatePasswordStrength = (password: string): PasswordStrength => {
|
|
|
88
187
|
return "very-strong"
|
|
89
188
|
}
|
|
90
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Creates a Zod schema for password validation with comprehensive security checks
|
|
192
|
+
*
|
|
193
|
+
* @template IsRequired - Whether the field is required (affects return type)
|
|
194
|
+
* @param {PasswordOptions<IsRequired>} [options] - Configuration options for password validation
|
|
195
|
+
* @returns {PasswordSchema<IsRequired>} Zod schema for password validation
|
|
196
|
+
*
|
|
197
|
+
* @description
|
|
198
|
+
* Creates a comprehensive password validator with strength analysis, character requirements,
|
|
199
|
+
* security checks, and protection against common weak passwords.
|
|
200
|
+
*
|
|
201
|
+
* Features:
|
|
202
|
+
* - Length validation (min/max)
|
|
203
|
+
* - Character requirements (uppercase, lowercase, digits, special)
|
|
204
|
+
* - Security checks (no repeating, no sequential patterns)
|
|
205
|
+
* - Common password detection
|
|
206
|
+
* - Strength analysis with configurable minimum levels
|
|
207
|
+
* - Content inclusion/exclusion
|
|
208
|
+
* - Custom regex patterns
|
|
209
|
+
* - Custom transformation functions
|
|
210
|
+
* - Comprehensive internationalization
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* // Basic password validation
|
|
215
|
+
* const basicSchema = password()
|
|
216
|
+
* basicSchema.parse("MyPassword123!") // ✓ Valid
|
|
217
|
+
*
|
|
218
|
+
* // Strong password requirements
|
|
219
|
+
* const strongSchema = password({
|
|
220
|
+
* min: 12,
|
|
221
|
+
* uppercase: true,
|
|
222
|
+
* lowercase: true,
|
|
223
|
+
* digits: true,
|
|
224
|
+
* special: true,
|
|
225
|
+
* minStrength: "strong"
|
|
226
|
+
* })
|
|
227
|
+
*
|
|
228
|
+
* // No common passwords
|
|
229
|
+
* const secureSchema = password({
|
|
230
|
+
* noCommonWords: true,
|
|
231
|
+
* noRepeating: true,
|
|
232
|
+
* noSequential: true
|
|
233
|
+
* })
|
|
234
|
+
* secureSchema.parse("password123") // ✗ Invalid (common password)
|
|
235
|
+
* secureSchema.parse("aaa123") // ✗ Invalid (repeating characters)
|
|
236
|
+
* secureSchema.parse("abc123") // ✗ Invalid (sequential characters)
|
|
237
|
+
*
|
|
238
|
+
* // Custom requirements
|
|
239
|
+
* const customSchema = password({
|
|
240
|
+
* min: 8,
|
|
241
|
+
* includes: "@", // Must contain @
|
|
242
|
+
* excludes: ["admin", "user"], // Cannot contain these words
|
|
243
|
+
* regex: /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)/ // Custom pattern
|
|
244
|
+
* })
|
|
245
|
+
*
|
|
246
|
+
* // Minimum strength requirement
|
|
247
|
+
* const strengthSchema = password({ minStrength: "very-strong" })
|
|
248
|
+
* strengthSchema.parse("weak") // ✗ Invalid (insufficient strength)
|
|
249
|
+
* strengthSchema.parse("MyVeryStr0ng!P@ssw0rd2024") // ✓ Valid
|
|
250
|
+
*
|
|
251
|
+
* // Optional with default
|
|
252
|
+
* const optionalSchema = password({
|
|
253
|
+
* required: false,
|
|
254
|
+
* defaultValue: null
|
|
255
|
+
* })
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @throws {z.ZodError} When validation fails with specific error messages
|
|
259
|
+
* @see {@link PasswordOptions} for all available configuration options
|
|
260
|
+
* @see {@link PasswordStrength} for strength level definitions
|
|
261
|
+
* @see {@link calculatePasswordStrength} for strength calculation logic
|
|
262
|
+
*/
|
|
91
263
|
export function password<IsRequired extends boolean = true>(options?: PasswordOptions<IsRequired>): PasswordSchema<IsRequired> {
|
|
92
264
|
const {
|
|
93
265
|
required = true,
|