snap-validate 0.4.2 โ 0.4.3
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/README.md +76 -51
- package/package.json +12 -6
- package/src/core/BaseValidator.js +594 -0
- package/src/core/ValidationResult.js +19 -0
- package/src/index.js +10 -950
- package/src/schema/runner.js +32 -0
- package/src/schema/validate.js +78 -0
- package/src/utils/safeRegex.js +72 -0
- package/src/validators/alphanumeric.js +9 -0
- package/src/validators/creditCard.js +49 -0
- package/src/validators/email.js +11 -0
- package/src/validators/index.js +19 -0
- package/src/validators/numeric.js +9 -0
- package/src/validators/password.js +41 -0
- package/src/validators/phone.js +24 -0
- package/src/validators/url.js +10 -0
- package/src/validators/zipCode.js +15 -0
- package/types/index.d.ts +85 -7
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ A lightning-fast, lightweight validation library for common patterns without hea
|
|
|
21
21
|
- ๐ฏ **Conditional**: Advanced conditional validation with `when()` and `optional()`
|
|
22
22
|
- ๐ ๏ธ **Custom Validators**: Add your own sync and async validation logic
|
|
23
23
|
- ๐ **Security First**: Built-in protection against ReDoS attacks and unsafe regex patterns
|
|
24
|
-
- ๐ก๏ธ **
|
|
24
|
+
- ๐ก๏ธ **Input Guards**: Input-length limits and static detection of dangerous regex shapes
|
|
25
25
|
- ๐งช **Well Tested**: Comprehensive test suite with high coverage
|
|
26
26
|
- ๐ฆ **Easy Integration**: Works in Node.js and browsers
|
|
27
27
|
- ๐ **Chainable API**: Intuitive fluent interface
|
|
@@ -33,6 +33,8 @@ A lightning-fast, lightweight validation library for common patterns without hea
|
|
|
33
33
|
npm install snap-validate
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Runtime: Node.js **>= 18** (the published library has zero dependencies and also runs in browsers via a bundler โ see [Browser Usage](#browser-usage)).
|
|
37
|
+
|
|
36
38
|
### TypeScript
|
|
37
39
|
|
|
38
40
|
For TypeScript projects, types are included automatically:
|
|
@@ -120,22 +122,26 @@ Features:
|
|
|
120
122
|
|
|
121
123
|
Snap Validate includes built-in protection against Regular Expression Denial of Service (ReDoS) attacks:
|
|
122
124
|
|
|
123
|
-
- **Regex Safety Detection**:
|
|
125
|
+
- **Regex Safety Detection**: Best-effort static heuristic (`isRegexSafe`) that flags a few common catastrophic-backtracking shapes before a pattern is used
|
|
124
126
|
- **Input Length Limits**: Protects against extremely long input strings (10,000 character limit)
|
|
125
|
-
- **Timeout Protection**: Configurable timeout for regex operations (default: 1 second)
|
|
126
127
|
- **Safe Defaults**: All predefined validators use safe, optimized regex patterns
|
|
127
128
|
|
|
129
|
+
> **A note on timeouts:** earlier versions advertised a configurable regex "timeout." JavaScript runs regexes synchronously on a single thread, so a timer cannot interrupt a regex that is mid-backtrack โ the event loop is blocked until it finishes. The timeout therefore never provided real protection and has been deprecated (see the CHANGELOG). The effective guards are the input-length cap and the `isRegexSafe` static check. For guaranteed linear-time matching, use a non-backtracking engine such as the native `re2` module.
|
|
130
|
+
|
|
128
131
|
```javascript
|
|
129
|
-
|
|
130
|
-
const validator = new BaseValidator(value)
|
|
131
|
-
.setRegexTimeout(2000) // 2 second timeout
|
|
132
|
-
.pattern(/your-pattern/, 'Error message');
|
|
132
|
+
const { BaseValidator, isRegexSafe } = require('snap-validate');
|
|
133
133
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
// Check a custom pattern before using it
|
|
135
|
+
if (isRegexSafe(/^[a-zA-Z0-9]+$/)) {
|
|
136
|
+
const validator = new BaseValidator(value)
|
|
137
|
+
.pattern(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed');
|
|
138
|
+
}
|
|
137
139
|
|
|
138
|
-
|
|
140
|
+
// Async pattern validation (applies the same length + safety guards)
|
|
141
|
+
const asyncValidator = new BaseValidator(value)
|
|
142
|
+
.patternAsync(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed');
|
|
143
|
+
|
|
144
|
+
const result = await asyncValidator.validateAsync();
|
|
139
145
|
```
|
|
140
146
|
|
|
141
147
|
## Available Validators
|
|
@@ -229,7 +235,7 @@ const validator = new BaseValidator(value)
|
|
|
229
235
|
// Optional validation - skip if empty/null/undefined
|
|
230
236
|
const optionalValidator = new BaseValidator(value)
|
|
231
237
|
.optional()
|
|
232
|
-
.
|
|
238
|
+
.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Must be a valid email if provided');
|
|
233
239
|
|
|
234
240
|
// Function-based conditions
|
|
235
241
|
const conditionalValidator = new BaseValidator(value)
|
|
@@ -240,7 +246,7 @@ const conditionalValidator = new BaseValidator(value)
|
|
|
240
246
|
### Custom Validators
|
|
241
247
|
|
|
242
248
|
```javascript
|
|
243
|
-
const { BaseValidator } = require('snap-validate');
|
|
249
|
+
const { BaseValidator, validators } = require('snap-validate');
|
|
244
250
|
|
|
245
251
|
// Synchronous custom validation
|
|
246
252
|
const customValidator = new BaseValidator(value)
|
|
@@ -252,9 +258,8 @@ const customValidator = new BaseValidator(value)
|
|
|
252
258
|
return true;
|
|
253
259
|
});
|
|
254
260
|
|
|
255
|
-
// Asynchronous custom validation
|
|
256
|
-
const asyncValidator =
|
|
257
|
-
.email()
|
|
261
|
+
// Asynchronous custom validation (validators.email returns a BaseValidator you can chain)
|
|
262
|
+
const asyncValidator = validators.email(email)
|
|
258
263
|
.customAsync(async (email) => {
|
|
259
264
|
const exists = await checkEmailExists(email);
|
|
260
265
|
return !exists || 'Email already exists';
|
|
@@ -267,6 +272,8 @@ const result = await asyncValidator.validateAsync();
|
|
|
267
272
|
### Async Validation
|
|
268
273
|
|
|
269
274
|
```javascript
|
|
275
|
+
const { BaseValidator, validators, validateAsync } = require('snap-validate');
|
|
276
|
+
|
|
270
277
|
// Async validation for single field
|
|
271
278
|
const validator = new BaseValidator(username)
|
|
272
279
|
.required()
|
|
@@ -294,7 +301,7 @@ const asyncSchema = {
|
|
|
294
301
|
})
|
|
295
302
|
};
|
|
296
303
|
|
|
297
|
-
const asyncResult = await
|
|
304
|
+
const asyncResult = await validateAsync(asyncSchema, userData);
|
|
298
305
|
```
|
|
299
306
|
|
|
300
307
|
## Security and Pattern Validation
|
|
@@ -308,26 +315,27 @@ const { BaseValidator } = require('snap-validate');
|
|
|
308
315
|
const validator = new BaseValidator(value)
|
|
309
316
|
.pattern(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed');
|
|
310
317
|
|
|
311
|
-
// Asynchronous pattern validation
|
|
318
|
+
// Asynchronous pattern validation (same length + static-safety guards)
|
|
312
319
|
const asyncValidator = new BaseValidator(value)
|
|
313
|
-
.patternAsync(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed')
|
|
314
|
-
.setRegexTimeout(5000); // 5 second timeout
|
|
320
|
+
.patternAsync(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed');
|
|
315
321
|
|
|
316
322
|
const result = await asyncValidator.validateAsync();
|
|
317
323
|
```
|
|
318
324
|
|
|
319
|
-
###
|
|
325
|
+
### How the safety guards work
|
|
320
326
|
|
|
321
327
|
```javascript
|
|
322
328
|
const validator = new BaseValidator(value)
|
|
323
|
-
.setRegexTimeout(3000) // Set custom timeout (3 seconds)
|
|
324
329
|
.pattern(/your-pattern/, 'Error message');
|
|
325
330
|
|
|
326
|
-
//
|
|
327
|
-
// -
|
|
328
|
-
//
|
|
329
|
-
// -
|
|
331
|
+
// For every pattern, the library automatically:
|
|
332
|
+
// - Runs a best-effort static check (isRegexSafe) that rejects a few common
|
|
333
|
+
// catastrophic-backtracking shapes (throws on a flagged pattern)
|
|
334
|
+
// - Limits input length to 10,000 characters to bound matching work
|
|
330
335
|
// - Provides clear error messages for security violations
|
|
336
|
+
//
|
|
337
|
+
// Note: there is no runtime timeout - a timer cannot interrupt a synchronous
|
|
338
|
+
// regex on a single thread. See the Security Features section above.
|
|
331
339
|
```
|
|
332
340
|
|
|
333
341
|
## Custom Validation
|
|
@@ -411,16 +419,17 @@ if (!unsafeResult.isValid) {
|
|
|
411
419
|
|
|
412
420
|
## Browser Usage
|
|
413
421
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
</script>
|
|
422
|
+
Snap Validate ships as CommonJS modules (`require`/`module.exports`). In the browser, use it through a bundler that understands CommonJS โ such as [Vite](https://vitejs.dev/), [webpack](https://webpack.js.org/), [esbuild](https://esbuild.github.io/), or [Rollup](https://rollupjs.org/):
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
import { validators } from 'snap-validate';
|
|
426
|
+
|
|
427
|
+
const result = validators.email('user@example.com').validate();
|
|
428
|
+
console.log(result.isValid);
|
|
422
429
|
```
|
|
423
430
|
|
|
431
|
+
> A raw `<script src=".../src/index.js">` tag will not work directly, because the source uses `require()` to load its internal modules. Bundle it (or wrap it in your own UMD/ESM build) for direct browser use.
|
|
432
|
+
|
|
424
433
|
## API Reference
|
|
425
434
|
|
|
426
435
|
### ValidationResult
|
|
@@ -431,17 +440,27 @@ if (!unsafeResult.isValid) {
|
|
|
431
440
|
### BaseValidator Methods
|
|
432
441
|
|
|
433
442
|
- `required(message?)` - Field is required
|
|
434
|
-
- `
|
|
435
|
-
- `
|
|
443
|
+
- `optional()` - Skip validation if empty/null/undefined
|
|
444
|
+
- `setFieldName(name)` - Set the field name used to prefix error messages
|
|
445
|
+
- `transform(fn, errorMessage?)` - Transform/sanitize the value before later rules run
|
|
446
|
+
- `equals(compareValue, message?)` - Require strict equality with a value
|
|
447
|
+
- `oneOf(allowedValues, message?)` - Require the value to be one of a set
|
|
448
|
+
- `between(min, max, message?)` - Require a number within an inclusive range
|
|
449
|
+
- `min(length, message?)` - Minimum length (string/array) or value (number)
|
|
450
|
+
- `max(length, message?)` - Maximum length (string/array) or value (number)
|
|
451
|
+
- `array(message?)` - Require the value to be an array
|
|
452
|
+
- `arrayOf(validator, message?)` - Validate each item of an array
|
|
453
|
+
- `arrayOfAsync(validator, message?)` - Validate each item of an array (async)
|
|
454
|
+
- `object(schema, message?)` - Validate a nested object against a schema
|
|
455
|
+
- `objectAsync(schema, message?)` - Validate a nested object against a schema (async)
|
|
436
456
|
- `pattern(regex, message?)` - Pattern matching validation with safety checks
|
|
437
|
-
- `patternAsync(regex, message?)` - Async pattern validation
|
|
438
|
-
- `setRegexTimeout(timeoutMs)` - Set custom timeout for regex operations
|
|
457
|
+
- `patternAsync(regex, message?)` - Async pattern validation (length + static-safety guards)
|
|
439
458
|
- `when(condition, validator)` - Conditional validation
|
|
440
|
-
- `optional()` - Skip validation if empty/null/undefined
|
|
441
459
|
- `custom(fn, message?)` - Custom synchronous validation
|
|
442
460
|
- `customAsync(fn, message?)` - Custom asynchronous validation
|
|
443
461
|
- `validate()` - Execute synchronous validation
|
|
444
462
|
- `validateAsync()` - Execute asynchronous validation
|
|
463
|
+
- `setRegexTimeout(timeoutMs)` - **Deprecated (no-op).** Kept for compatibility; a timer cannot interrupt a synchronous regex, so the value is ignored
|
|
445
464
|
|
|
446
465
|
### Available Validators
|
|
447
466
|
|
|
@@ -456,29 +475,33 @@ if (!unsafeResult.isValid) {
|
|
|
456
475
|
|
|
457
476
|
### TypeScript Types
|
|
458
477
|
|
|
459
|
-
- `ValidationResult` -
|
|
460
|
-
- `
|
|
461
|
-
- `
|
|
462
|
-
- `
|
|
463
|
-
- `
|
|
478
|
+
- `ValidationResult` - Class for validation results (`isValid`, `errors`)
|
|
479
|
+
- `SchemaValidationResult` - Result of `validate` / `validateAsync` (`isValid`, `errors`, `getErrors()`)
|
|
480
|
+
- `ValidationFunction` - Type for validator functions used in schemas
|
|
481
|
+
- `Schema` - Type for validation schema objects
|
|
482
|
+
- `PasswordOptions` - Interface for password validation configuration
|
|
483
|
+
- `PhoneFormat` / `CountryCode` - String-literal unions for `phone` / `zipCode` options
|
|
484
|
+
- `CustomValidatorFunction` / `AsyncValidatorFunction` / `TransformFunction` - Callback types
|
|
485
|
+
- `BaseValidator` - The chainable validator class
|
|
464
486
|
|
|
465
487
|
### Validation Functions
|
|
466
488
|
|
|
467
489
|
- `validate(schema, data)` - Synchronous schema validation
|
|
468
|
-
- `
|
|
490
|
+
- `validateAsync(schema, data)` - Asynchronous schema validation
|
|
469
491
|
|
|
470
492
|
### Security Functions
|
|
471
493
|
|
|
472
|
-
- `isRegexSafe(regex)` -
|
|
473
|
-
- `
|
|
494
|
+
- `isRegexSafe(regex)` - Best-effort static check for a few dangerous regex shapes (returns `boolean`)
|
|
495
|
+
- `safeRegexTest(regex, str)` - Async regex test with input-length + safety guards (returns `Promise<boolean>`). A legacy `timeoutMs` third argument is accepted but ignored (deprecated)
|
|
496
|
+
- `safeRegexTestSync(regex, str, maxLength?)` - Synchronous regex test with input-length protection (returns `boolean`)
|
|
474
497
|
|
|
475
498
|
## Security Best Practices
|
|
476
499
|
|
|
477
500
|
1. **Use Built-in Validators**: The predefined validators are optimized for security and performance
|
|
478
501
|
2. **Validate Input Length**: Large inputs are automatically limited to prevent ReDoS attacks
|
|
479
|
-
3. **
|
|
480
|
-
4. **
|
|
481
|
-
5. **
|
|
502
|
+
3. **Test Custom Patterns**: Use `isRegexSafe()` to check custom regex patterns before deployment
|
|
503
|
+
4. **Handle Async Errors**: Always use try-catch blocks with async validation
|
|
504
|
+
5. **Consider a hardened engine**: For untrusted patterns needing guaranteed linear-time matching, pair the library with a non-backtracking engine such as the native `re2` module
|
|
482
505
|
|
|
483
506
|
## Contributing
|
|
484
507
|
|
|
@@ -492,6 +515,8 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
|
|
|
492
515
|
|
|
493
516
|
## Development
|
|
494
517
|
|
|
518
|
+
> Development tooling (ESLint 10) requires Node.js **>= 20.19**. This is a contributor requirement only โ the published library still supports Node >= 18 for consumers.
|
|
519
|
+
|
|
495
520
|
```bash
|
|
496
521
|
# Install dependencies
|
|
497
522
|
npm install
|
|
@@ -531,4 +556,4 @@ See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.
|
|
|
531
556
|
|
|
532
557
|
---
|
|
533
558
|
|
|
534
|
-
Made with โก by [Ramachandra Anirudh Vemulapalli](https://github.com/aniru-dh21)
|
|
559
|
+
Made with โก by [Ramachandra Anirudh Vemulapalli](https://github.com/aniru-dh21)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snap-validate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Lightweight validation library for common patterns without heavy dependencies",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"lint": "eslint src tests",
|
|
20
20
|
"lint:fix": "eslint src tests --fix",
|
|
21
21
|
"format": "prettier --write \"src/**/*.js\" \"tests/**/*.js\"",
|
|
22
|
-
"prepare": "husky
|
|
22
|
+
"prepare": "husky",
|
|
23
23
|
"prepublishOnly": "npm test && npm run lint",
|
|
24
24
|
"build": "echo 'No build step required for this library'",
|
|
25
25
|
"example": "node examples/basic-usage.js",
|
|
@@ -56,13 +56,19 @@
|
|
|
56
56
|
"url": "https://github.com/aniru-dh21/snap-validate/issues"
|
|
57
57
|
},
|
|
58
58
|
"homepage": "https://github.com/aniru-dh21/snap-validate#readme",
|
|
59
|
+
"overrides": {
|
|
60
|
+
"js-yaml": "4.2.0"
|
|
61
|
+
},
|
|
59
62
|
"devDependencies": {
|
|
60
63
|
"@babel/core": "^7.24.0",
|
|
61
64
|
"@babel/preset-env": "^7.24.0",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
65
|
+
"@eslint/js": "^10.0.1",
|
|
66
|
+
"@types/jest": "^30.0.0",
|
|
67
|
+
"babel-jest": "^30.4.1",
|
|
68
|
+
"eslint": "^10.6.0",
|
|
69
|
+
"globals": "^17.7.0",
|
|
64
70
|
"husky": "^9.1.0",
|
|
65
|
-
"jest": "^
|
|
71
|
+
"jest": "^30.4.2",
|
|
66
72
|
"lint-staged": "^15.2.0",
|
|
67
73
|
"prettier": "^3.2.0"
|
|
68
74
|
},
|
|
@@ -73,7 +79,7 @@
|
|
|
73
79
|
"LICENSE"
|
|
74
80
|
],
|
|
75
81
|
"engines": {
|
|
76
|
-
"node": ">=
|
|
82
|
+
"node": ">=18.0.0"
|
|
77
83
|
},
|
|
78
84
|
"jest": {
|
|
79
85
|
"testEnvironment": "node",
|