@@ -19,6 +19,7 @@ A lightweight, type-safe validation library for TypeScript with blazing-fast per
19
19
- 🧩 ** Extendable** - Easy to add custom validators
20
20
- 💾 ** Tiny footprint** - Lightweight with no dependencies
21
21
- 🔍 ** Detailed errors** - Comprehensive error reporting
22
+ - 🛡️ ** Robust validation** - Handles null, undefined, and edge cases properly
22
23
23
24
## Installation
24
25
@@ -45,7 +46,7 @@ import { v } from '@stacksjs/ts-validation'
45
46
const userValidator = v .object ({
46
47
name: v .string ().min (2 ).max (50 ).required (),
47
48
email: v .string ().email ().required (),
48
- age: v .number ().min (18 ).integer ( ).required (),
49
+ age: v .integer ().min (18 ).max ( 120 ).required (),
49
50
website: v .string ().url ().optional (),
50
51
tags: v .array ().each (v .string ()).optional (),
51
52
})
@@ -69,6 +70,46 @@ else {
69
70
}
70
71
```
71
72
73
+ ## Basic Usage
74
+
75
+ ### Simple Validation
76
+
77
+ ``` typescript
78
+ import { v } from ' @stacksjs/ts-validation'
79
+
80
+ // Validate a single value
81
+ const emailValidator = v .string ().email ().required ()
82
+ const result
= emailValidator .
validate (
' [email protected] ' )
83
+
84
+ if (result .valid ) {
85
+ console .log (' Email is valid!' )
86
+ }
87
+ else {
88
+ console .log (' Validation errors:' , result .errors )
89
+ }
90
+
91
+ // Quick test method
92
+ const isValid
= emailValidator .
test (
' [email protected] ' )
// returns boolean
93
+ ```
94
+
95
+ ### Error Handling
96
+
97
+ ``` typescript
98
+ const result = userValidator .validate (invalidUser )
99
+
100
+ if (! result .valid ) {
101
+ // For single field validation
102
+ result .errors .forEach ((error ) => {
103
+ console .log (error .message )
104
+ })
105
+
106
+ // For object validation
107
+ Object .entries (result .errors ).forEach (([field , errors ]) => {
108
+ console .log (` ${field }: ` , errors .map (e => e .message ))
109
+ })
110
+ }
111
+ ```
112
+
72
113
## Validation Types
73
114
74
115
### String Validation
@@ -88,6 +129,9 @@ const zipCodeValidator = v.string().matches(/^\d{5}$/).required()
88
129
89
130
// Alphanumeric, alpha, or numeric characters
90
131
const usernameValidator = v .string ().alphanumeric ().required ()
132
+
133
+ // Text validation (specialized for text content)
134
+ const bioValidator = v .text ().max (500 ).optional ()
91
135
```
92
136
93
137
### Number Validation
@@ -96,8 +140,17 @@ const usernameValidator = v.string().alphanumeric().required()
96
140
// Basic number validation
97
141
const ageValidator = v .number ().min (18 ).max (120 ).required ()
98
142
99
- // Integer validation
100
- const quantityValidator = v .number ().integer ().positive ().required ()
143
+ // Integer validation (whole numbers only)
144
+ const quantityValidator = v .integer ().positive ().required ()
145
+
146
+ // Float validation (decimal numbers)
147
+ const priceValidator = v .float ().min (0.01 ).required ()
148
+
149
+ // Small integer validation (-32,768 to 32,767)
150
+ const smallNumberValidator = v .smallint ().required ()
151
+
152
+ // Decimal validation (with precision control)
153
+ const decimalValidator = v .decimal ().min (0 ).max (999.99 ).required ()
101
154
102
155
// Negative numbers
103
156
const temperatureValidator = v .number ().negative ().required ()
@@ -135,8 +188,7 @@ const addressValidator = v.object({
135
188
})
136
189
137
190
// Nested object validation
138
- // .shape() is an alias for .object()
139
- const userValidator = v .object ().shape ({
191
+ const userValidator = v .object ({
140
192
name: v .string ().required (),
141
193
address: addressValidator ,
142
194
})
@@ -148,6 +200,71 @@ const strictValidator = v.object().strict().shape({
148
200
})
149
201
```
150
202
203
+ ### Date and Time Validation
204
+
205
+ ``` typescript
206
+ // Basic date validation
207
+ const dateValidator = v .date ()
208
+ expect (dateValidator .test (new Date ())).toBe (true )
209
+ expect (dateValidator .test (new Date (' invalid' ))).toBe (false )
210
+
211
+ // Datetime validation (MySQL DATETIME compatible)
212
+ const datetimeValidator = v .datetime ()
213
+ expect (datetimeValidator .test (new Date (' 2023-01-01' ))).toBe (true )
214
+
215
+ // Time validation (24-hour format)
216
+ const timeValidator = v .time ()
217
+ expect (timeValidator .test (' 14:30' )).toBe (true )
218
+ expect (timeValidator .test (' 25:00' )).toBe (false ) // Invalid hour
219
+
220
+ // Unix timestamp validation
221
+ const unixValidator = v .unix ()
222
+ expect (unixValidator .test (1683912345 )).toBe (true ) // Seconds
223
+ expect (unixValidator .test (1683912345000 )).toBe (true ) // Milliseconds
224
+
225
+ // Regular timestamp validation (MySQL TIMESTAMP compatible)
226
+ const timestampValidator = v .timestamp ()
227
+ expect (timestampValidator .test (0 )).toBe (true ) // 1970-01-01 00:00:00 UTC
228
+
229
+ // Timestamp with timezone
230
+ const timestampTzValidator = v .timestampTz ()
231
+ ```
232
+
233
+ ### JSON Validation
234
+
235
+ ``` typescript
236
+ // JSON string validation
237
+ const jsonValidator = v .json ()
238
+ expect (jsonValidator .test (' {"name": "John"}' )).toBe (true )
239
+ expect (jsonValidator .test (' {"a": 1, "b": 2}' )).toBe (true )
240
+ expect (jsonValidator .test (' 123' )).toBe (false ) // Primitive values are invalid
241
+ expect (jsonValidator .test (' not json' )).toBe (false )
242
+ ```
243
+
244
+ ### Binary and Blob Validation
245
+
246
+ ``` typescript
247
+ // Binary data validation
248
+ const binaryValidator = v .binary ()
249
+
250
+ // Blob validation
251
+ const blobValidator = v .blob ()
252
+ ```
253
+
254
+ ### BigInt Validation
255
+
256
+ ``` typescript
257
+ // BigInt validation
258
+ const bigIntValidator = v .bigint ().min (0n ).max (1000000n ).required ()
259
+ ```
260
+
261
+ ### Enum Validation
262
+
263
+ ``` typescript
264
+ // Enum validation
265
+ const statusValidator = v .enum ([' active' , ' inactive' , ' pending' ]).required ()
266
+ ```
267
+
151
268
### Custom Validation
152
269
153
270
``` typescript
@@ -168,12 +285,6 @@ const passwordValidator = v.string()
168
285
169
286
The password validator provides comprehensive password validation with multiple security rules:
170
287
171
- - Minimum and maximum length
172
- - Must contain both letters and numbers (alphanumeric)
173
- - Must have uppercase and lowercase letters
174
- - Must contain special characters
175
- - Can validate password matches (for confirmation)
176
-
177
288
``` typescript
178
289
// Basic password validation
179
290
const passwordValidator = v .password ()
@@ -199,38 +310,114 @@ else {
199
310
const confirmPasswordValidator = v .password ().matches (' MySecureP@ss123' )
200
311
```
201
312
202
- ### Date and Time Validation
313
+ ## Advanced Usage
203
314
204
- The library provides several date and time validators to handle different formats:
315
+ ### Conditional Validation
316
+
317
+ ``` typescript
318
+ const userValidator = v .object ({
319
+ name: v .string ().required (),
320
+ email: v .string ().email ().required (),
321
+ age: v .integer ().min (18 ).required (),
322
+ // Conditional validation based on age
323
+ guardianInfo: v .object ({
324
+ name: v .string ().required (),
325
+ phone: v .string ().required (),
326
+ }).custom ((value , data ) => {
327
+ return data .age < 18 ? value !== null : true
328
+ }, ' Guardian information required for users under 18' ),
329
+ })
330
+ ```
205
331
206
- - Basic JavaScript Date objects
207
- - MySQL DATETIME format (1000-01-01 to 9999-12-31)
208
- - Unix timestamps (both seconds and milliseconds)
209
- - MySQL TIMESTAMP format (1970-01-01 00:00:00 UTC to 2038-01-19 03:14:07 UTC)
332
+ ### Reusable Validators
210
333
211
334
``` typescript
212
- // Basic date validation
213
- const dateValidator = v .date ()
214
- expect (dateValidator .test (new Date ())).toBe (true )
215
- expect (dateValidator .test (new Date (' invalid' ))).toBe (false )
335
+ // Create reusable validators
336
+ const emailValidator = v .string ().email ().required ()
337
+ const phoneValidator = v .string ().matches (/ ^ \+ ? [\d \s -( )] + $ / ).required ()
216
338
217
- // Datetime validation (MySQL DATETIME compatible)
218
- const datetimeValidator = v .datetime ()
219
- expect ( datetimeValidator . test ( new Date ( ' 2023-01-01 ' ))). toBe ( true )
220
- expect ( datetimeValidator . test ( new Date ( ' 0999-12-31 ' ))). toBe ( false ) // Before 1000-01-01
221
- expect ( datetimeValidator . test ( new Date ( ' 10000-01-01 ' ))). toBe ( false ) // After 9999-12-31
339
+ // Use them in multiple places
340
+ const contactFormValidator = v .object ({
341
+ email: emailValidator ,
342
+ phone: phoneValidator ,
343
+ })
222
344
223
- // Unix timestamp validation
224
- const unixValidator = v .unix ()
225
- expect (unixValidator .test (1683912345 )).toBe (true ) // Seconds
226
- expect (unixValidator .test (1683912345000 )).toBe (true ) // Milliseconds
227
- expect (unixValidator .test (- 1 )).toBe (false ) // Invalid negative timestamp
345
+ const userProfileValidator = v .object ({
346
+ primaryEmail: emailValidator ,
347
+ secondaryEmail: emailValidator .optional (),
348
+ phone: phoneValidator .optional (),
349
+ })
350
+ ```
228
351
229
- // Regular timestamp validation (MySQL TIMESTAMP compatible)
230
- const timestampValidator = v .timestamp ()
231
- expect (timestampValidator .test (0 )).toBe (true ) // 1970-01-01 00:00:00 UTC
232
- expect (timestampValidator .test (2147483647 )).toBe (true ) // 2038-01-19 03:14:07 UTC
233
- expect (timestampValidator .test (- 1 )).toBe (false ) // Invalid negative timestamp
352
+ ### Validation with Custom Error Messages
353
+
354
+ ``` typescript
355
+ const userValidator = v .object ({
356
+ name: v .string ()
357
+ .min (2 , ' Name must be at least 2 characters' )
358
+ .max (50 , ' Name cannot exceed 50 characters' )
359
+ .required (' Name is required' ),
360
+ email: v .string ()
361
+ .email (' Please provide a valid email address' )
362
+ .required (' Email is required' ),
363
+ })
364
+ ```
365
+
366
+ ## Error Handling Examples
367
+
368
+ ``` typescript
369
+ // Single field validation
370
+ const emailResult = emailValidator .validate (' invalid-email' )
371
+ if (! emailResult .valid ) {
372
+ emailResult .errors .forEach ((error ) => {
373
+ console .log (` Email error: ${error .message } ` )
374
+ })
375
+ }
376
+
377
+ // Object validation
378
+ const userResult = userValidator .validate (invalidUser )
379
+ if (! userResult .valid ) {
380
+ // userResult.errors is an object with field names as keys
381
+ Object .entries (userResult .errors ).forEach (([field , errors ]) => {
382
+ console .log (` ${field }: ` )
383
+ errors .forEach ((error ) => {
384
+ console .log (` - ${error .message } ` )
385
+ })
386
+ })
387
+ }
388
+ ```
389
+
390
+ ## Performance Tips
391
+
392
+ 1 . ** Reuse validators** : Create validators once and reuse them instead of creating new ones for each validation
393
+ 2 . ** Use specific validators** : Use ` v.integer() ` instead of ` v.number().integer() ` for better performance
394
+ 3 . ** Chain efficiently** : Order validation rules from most likely to fail first
395
+ 4 . ** Avoid unnecessary validations** : Use ` .optional() ` for fields that can be undefined
396
+
397
+ ## TypeScript Integration
398
+
399
+ ``` typescript
400
+ import { v } from ' @stacksjs/ts-validation'
401
+
402
+ // Type-safe validation
403
+ interface User {
404
+ name: string
405
+ email: string
406
+ age: number
407
+ }
408
+
409
+ const userValidator = v .object ({
410
+ name: v .string ().required (),
411
+ email: v .string ().email ().required (),
412
+ age: v .integer ().min (18 ).required (),
413
+ })
414
+
415
+ // The result is fully typed
416
+ const result = userValidator .validate (userData )
417
+ if (result .valid ) {
418
+ // TypeScript knows userData is valid here
419
+ const validUser: User = userData
420
+ }
234
421
```
235
422
236
423
## Configuration
@@ -256,12 +443,6 @@ const config: ValidationOptions = {
256
443
export default config
257
444
```
258
445
259
- ## Performance Tips
260
-
261
- 1 . ** Use caching** : Enable ` cacheResults ` in the config for repeated validations
262
- 2 . ** Early returns** : Set ` strictMode: true ` to stop on first error when validating complex objects
263
- 3 . ** Reuse validators** : Create validators once and reuse them instead of creating new ones for each validation
264
-
265
446
## Testing
266
447
267
448
``` bash
0 commit comments