@lara-node/validator 0.1.1 → 0.1.2
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 +218 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# @lara-node/validator
|
|
2
|
+
|
|
3
|
+
Laravel-inspired validation engine for Lara-Node applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @lara-node/validator
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { validate, ValidationError } from '@lara-node/validator';
|
|
15
|
+
|
|
16
|
+
const data = await validate(req.body, {
|
|
17
|
+
name: 'required|string|min:2|max:100',
|
|
18
|
+
email: 'required|email|unique:users,email',
|
|
19
|
+
password: 'required|string|min:8|confirmed',
|
|
20
|
+
age: 'required|integer|min:18|max:120',
|
|
21
|
+
role: 'nullable|in:admin,user,moderator',
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If validation fails, a `ValidationError` is thrown with an `errors` map and `messages` array.
|
|
26
|
+
|
|
27
|
+
## Built-in Rules
|
|
28
|
+
|
|
29
|
+
### Presence
|
|
30
|
+
| Rule | Description |
|
|
31
|
+
|------|-------------|
|
|
32
|
+
| `required` | Field must be present and non-empty |
|
|
33
|
+
| `nullable` | Field may be null/undefined (stops further rules) |
|
|
34
|
+
| `sometimes` | Only validate if the field is present |
|
|
35
|
+
|
|
36
|
+
### Type
|
|
37
|
+
| Rule | Description |
|
|
38
|
+
|------|-------------|
|
|
39
|
+
| `string` | Must be a string |
|
|
40
|
+
| `integer` | Must be an integer |
|
|
41
|
+
| `numeric` | Must be a number |
|
|
42
|
+
| `boolean` | Must be true/false/1/0/'true'/'false' |
|
|
43
|
+
| `array` | Must be an array |
|
|
44
|
+
| `json` | Must be valid JSON |
|
|
45
|
+
|
|
46
|
+
### String
|
|
47
|
+
| Rule | Description |
|
|
48
|
+
|------|-------------|
|
|
49
|
+
| `min:n` | Minimum length (string) or value (number) |
|
|
50
|
+
| `max:n` | Maximum length (string) or value (number) |
|
|
51
|
+
| `between:min,max` | Length or value between min and max |
|
|
52
|
+
| `size:n` | Exact length |
|
|
53
|
+
| `email` | Valid email address |
|
|
54
|
+
| `url` | Valid URL |
|
|
55
|
+
| `uuid` | Valid UUID |
|
|
56
|
+
| `regex:/pattern/` | Must match regex |
|
|
57
|
+
| `starts_with:prefix` | Must start with prefix |
|
|
58
|
+
| `ends_with:suffix` | Must end with suffix |
|
|
59
|
+
| `contains:str` | Must contain str |
|
|
60
|
+
| `phone` | Valid phone number (E.164 or common formats) |
|
|
61
|
+
|
|
62
|
+
### Comparison
|
|
63
|
+
| Rule | Description |
|
|
64
|
+
|------|-------------|
|
|
65
|
+
| `in:a,b,c` | Value must be one of the listed values |
|
|
66
|
+
| `not_in:a,b,c` | Value must not be one of the listed values |
|
|
67
|
+
| `gt:n` | Greater than n |
|
|
68
|
+
| `gte:n` | Greater than or equal to n |
|
|
69
|
+
| `lt:n` | Less than n |
|
|
70
|
+
| `lte:n` | Less than or equal to n |
|
|
71
|
+
| `same:field` | Must equal another field |
|
|
72
|
+
| `different:field` | Must not equal another field |
|
|
73
|
+
| `confirmed` | Must equal `field_confirmation` |
|
|
74
|
+
| `accepted` | Must be truthy (true/1/'yes'/'on') |
|
|
75
|
+
| `declined` | Must be falsy (false/0/'no'/'off') |
|
|
76
|
+
|
|
77
|
+
### Date
|
|
78
|
+
| Rule | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `date` | Valid date string |
|
|
81
|
+
| `date_format:format` | Must match specific format |
|
|
82
|
+
| `before:date` | Must be before a given date |
|
|
83
|
+
| `after:date` | Must be after a given date |
|
|
84
|
+
| `time` | Valid time string (HH:MM or HH:MM:SS) |
|
|
85
|
+
| `datetime` | Valid datetime string |
|
|
86
|
+
| `timezone` | Valid timezone identifier |
|
|
87
|
+
|
|
88
|
+
### Database
|
|
89
|
+
| Rule | Description |
|
|
90
|
+
|------|-------------|
|
|
91
|
+
| `exists:table,column` | Record must exist in DB |
|
|
92
|
+
| `unique:table,column` | Value must not exist in DB |
|
|
93
|
+
|
|
94
|
+
## Rule Spec Objects
|
|
95
|
+
|
|
96
|
+
For more control, use a `RuleSpec` object instead of a pipe-delimited string:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const data = await validate(body, {
|
|
100
|
+
email: {
|
|
101
|
+
rule: 'required|email|unique:users,email',
|
|
102
|
+
message: 'Please provide a valid, unique email address.',
|
|
103
|
+
},
|
|
104
|
+
age: {
|
|
105
|
+
rule: 'required|integer|between:18,120',
|
|
106
|
+
messages: {
|
|
107
|
+
required: 'Age is required.',
|
|
108
|
+
between: 'You must be between 18 and 120 years old.',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Custom Rule Functions
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { validate, RuleFn } from '@lara-node/validator';
|
|
118
|
+
|
|
119
|
+
const isEvenNumber: RuleFn = (value, field) => {
|
|
120
|
+
if (value % 2 !== 0) return `${field} must be an even number`;
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
await validate(body, { quantity: isEvenNumber });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Conditional Rules
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { requiredIf, requiredUnless } from '@lara-node/validator';
|
|
131
|
+
|
|
132
|
+
await validate(body, {
|
|
133
|
+
// required only when type === 'business'
|
|
134
|
+
company_name: requiredIf('type', 'business'),
|
|
135
|
+
|
|
136
|
+
// required unless type is 'guest'
|
|
137
|
+
email: requiredUnless('type', 'guest'),
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## File Validation
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { fileRule, mimes, maxFileSize } from '@lara-node/validator';
|
|
145
|
+
|
|
146
|
+
await validate({ file: req.file }, {
|
|
147
|
+
file: [
|
|
148
|
+
fileRule(), // must be a multer file object
|
|
149
|
+
mimes(['image/jpeg', 'image/png', 'image/webp']),
|
|
150
|
+
maxFileSize(5 * 1024 * 1024), // 5 MB
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Nested Objects
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { nestedRule, arrayOfObjectsRule } from '@lara-node/validator';
|
|
159
|
+
|
|
160
|
+
await validate(body, {
|
|
161
|
+
address: nestedRule({
|
|
162
|
+
street: 'required|string',
|
|
163
|
+
city: 'required|string',
|
|
164
|
+
zip: 'required|string|min:4',
|
|
165
|
+
}),
|
|
166
|
+
items: arrayOfObjectsRule({
|
|
167
|
+
product_id: 'required|integer|exists:products,id',
|
|
168
|
+
quantity: 'required|integer|min:1',
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Express Integration
|
|
174
|
+
|
|
175
|
+
The `@lara-node/middlewares` package attaches `req.validate()` via `ValidatorMiddleware`:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { ValidatorMiddleware } from '@lara-node/middlewares';
|
|
179
|
+
|
|
180
|
+
app.use((req, res, next) => new ValidatorMiddleware().handle(req as any, res, next));
|
|
181
|
+
|
|
182
|
+
// In controllers:
|
|
183
|
+
const data = await req.validate({
|
|
184
|
+
email: 'required|email',
|
|
185
|
+
password: 'required|string|min:8',
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Error Handling
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { ValidationError } from '@lara-node/validator';
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const data = await validate(body, rules);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (err instanceof ValidationError) {
|
|
198
|
+
console.log(err.errors); // { email: ['The email field is required.'] }
|
|
199
|
+
console.log(err.messages); // ['The email field is required.']
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The `ErrorHandlerMiddleware` from `@lara-node/middlewares` automatically catches `ValidationError` and returns HTTP 422.
|
|
205
|
+
|
|
206
|
+
## Custom Messages
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
await validate(body, rules, {
|
|
210
|
+
'name.required': 'Please enter your full name.',
|
|
211
|
+
'email.email': 'That doesn\'t look like a valid email.',
|
|
212
|
+
'age.min': 'You must be at least 18 years old.',
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|