snap-validate 0.4.1 โ†’ 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 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
- - ๐Ÿ›ก๏ธ **Timeout Protection**: Configurable timeout for regex operations to prevent DoS attacks
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**: Automatically detects and prevents potentially dangerous regex patterns
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
- // Set custom timeout for regex operations
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
- // Use async pattern validation for complex patterns with timeout protection
135
- const validator = new BaseValidator(value)
136
- .patternAsync(/complex-pattern/, 'Error message');
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
- const result = await validator.validateAsync();
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
- .email('Must be a valid email if provided');
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 = new BaseValidator(email)
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 validate.async(asyncSchema, userData);
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 with timeout protection
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
- ### Configurable Security Settings
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
- // The library automatically:
327
- // - Detects unsafe regex patterns
328
- // - Limits input length to prevent ReDoS
329
- // - Applies timeout protection for complex patterns
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
- ```html
415
- <script src="https://unpkg.com/snap-validate/src/index.js"></script>
416
- <script>
417
- const { validators } = SnapValidate;
418
-
419
- const result = validators.email('user@example.com').validate();
420
- console.log(result.isValid);
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
- - `min(length, message?)` - Minimum length validation
435
- - `max(length, message?)` - Maximum length validation
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 with timeout protection
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` - Interface for validation results
460
- - `ValidatorFunction` - Type for validator functions used in schemas
461
- - `ValidationSchema` - Type for validation schema objects
462
- - `PasswordOptions` - Interface for password validation configuration
463
- - `BaseValidator<T>` - Generic base validator class
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
- - `validate.async(schema, data)` - Asynchronous schema validation
490
+ - `validateAsync(schema, data)` - Asynchronous schema validation
469
491
 
470
492
  ### Security Functions
471
493
 
472
- - `isRegexSafe(regex)` - Check if a regex pattern is safe to use
473
- - `safeRegexText(regex, str, timeoutMs)` - Execute regex with timeout protection
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. **Set Appropriate Timeouts**: Configure regex timeouts based on your application's needs
480
- 4. **Test Custom Patterns**: Use `isRegexSafe()` to check custom regex patterns before deployment
481
- 5. **Handle Async Errors**: Always use try-catch blocks with async validation
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.1",
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 install",
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
- "babel-jest": "^29.7.0",
63
- "eslint": "^8.57.1",
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": "^29.7.0",
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": ">=12.0.0"
82
+ "node": ">=18.0.0"
77
83
  },
78
84
  "jest": {
79
85
  "testEnvironment": "node",