@warlock.js/seal 3.0.18 → 3.0.20
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 +373 -373
- package/cjs/factory/validators.d.ts +2 -1
- package/cjs/factory/validators.d.ts.map +1 -1
- package/cjs/factory/validators.js +4 -2
- package/cjs/factory/validators.js.map +1 -1
- package/cjs/helpers/date-helpers.d.ts.map +1 -1
- package/cjs/helpers/date-helpers.js +2 -7
- package/cjs/helpers/date-helpers.js.map +1 -1
- package/cjs/index.js +1 -1
- package/cjs/mutators/index.d.ts +1 -0
- package/cjs/mutators/index.d.ts.map +1 -1
- package/cjs/mutators/scalar-mutators.d.ts +3 -0
- package/cjs/mutators/scalar-mutators.d.ts.map +1 -0
- package/cjs/mutators/scalar-mutators.js +6 -0
- package/cjs/mutators/scalar-mutators.js.map +1 -0
- package/cjs/rules/core/index.d.ts +1 -0
- package/cjs/rules/core/index.d.ts.map +1 -1
- package/cjs/rules/core/union.d.ts +9 -0
- package/cjs/rules/core/union.d.ts.map +1 -0
- package/cjs/rules/core/union.js +40 -0
- package/cjs/rules/core/union.js.map +1 -0
- package/cjs/validators/array-validator.d.ts +8 -0
- package/cjs/validators/array-validator.d.ts.map +1 -1
- package/cjs/validators/array-validator.js +20 -6
- package/cjs/validators/array-validator.js.map +1 -1
- package/cjs/validators/base-validator.d.ts +47 -3
- package/cjs/validators/base-validator.d.ts.map +1 -1
- package/cjs/validators/base-validator.js +64 -13
- package/cjs/validators/base-validator.js.map +1 -1
- package/cjs/validators/boolean-validator.d.ts +4 -0
- package/cjs/validators/boolean-validator.d.ts.map +1 -1
- package/cjs/validators/boolean-validator.js +6 -0
- package/cjs/validators/boolean-validator.js.map +1 -1
- package/cjs/validators/date-validator.d.ts +4 -0
- package/cjs/validators/date-validator.d.ts.map +1 -1
- package/cjs/validators/date-validator.js +7 -1
- package/cjs/validators/date-validator.js.map +1 -1
- package/cjs/validators/index.d.ts +1 -0
- package/cjs/validators/index.d.ts.map +1 -1
- package/cjs/validators/number-validator.d.ts +4 -0
- package/cjs/validators/number-validator.d.ts.map +1 -1
- package/cjs/validators/number-validator.js +6 -0
- package/cjs/validators/number-validator.js.map +1 -1
- package/cjs/validators/object-validator.d.ts +155 -1
- package/cjs/validators/object-validator.d.ts.map +1 -1
- package/cjs/validators/object-validator.js +235 -3
- package/cjs/validators/object-validator.js.map +1 -1
- package/cjs/validators/scalar-validator.d.ts +4 -0
- package/cjs/validators/scalar-validator.d.ts.map +1 -1
- package/cjs/validators/scalar-validator.js +9 -2
- package/cjs/validators/scalar-validator.js.map +1 -1
- package/cjs/validators/string-validator.d.ts +4 -0
- package/cjs/validators/string-validator.d.ts.map +1 -1
- package/cjs/validators/string-validator.js +6 -0
- package/cjs/validators/string-validator.js.map +1 -1
- package/cjs/validators/union-validator.d.ts +42 -0
- package/cjs/validators/union-validator.d.ts.map +1 -0
- package/cjs/validators/union-validator.js +44 -0
- package/cjs/validators/union-validator.js.map +1 -0
- package/esm/factory/validators.d.ts +2 -1
- package/esm/factory/validators.d.ts.map +1 -1
- package/esm/factory/validators.js +4 -2
- package/esm/factory/validators.js.map +1 -1
- package/esm/helpers/date-helpers.d.ts.map +1 -1
- package/esm/helpers/date-helpers.js +2 -7
- package/esm/helpers/date-helpers.js.map +1 -1
- package/esm/index.js +1 -1
- package/esm/mutators/index.d.ts +1 -0
- package/esm/mutators/index.d.ts.map +1 -1
- package/esm/mutators/scalar-mutators.d.ts +3 -0
- package/esm/mutators/scalar-mutators.d.ts.map +1 -0
- package/esm/mutators/scalar-mutators.js +6 -0
- package/esm/mutators/scalar-mutators.js.map +1 -0
- package/esm/rules/core/index.d.ts +1 -0
- package/esm/rules/core/index.d.ts.map +1 -1
- package/esm/rules/core/union.d.ts +9 -0
- package/esm/rules/core/union.d.ts.map +1 -0
- package/esm/rules/core/union.js +40 -0
- package/esm/rules/core/union.js.map +1 -0
- package/esm/validators/array-validator.d.ts +8 -0
- package/esm/validators/array-validator.d.ts.map +1 -1
- package/esm/validators/array-validator.js +20 -6
- package/esm/validators/array-validator.js.map +1 -1
- package/esm/validators/base-validator.d.ts +47 -3
- package/esm/validators/base-validator.d.ts.map +1 -1
- package/esm/validators/base-validator.js +64 -13
- package/esm/validators/base-validator.js.map +1 -1
- package/esm/validators/boolean-validator.d.ts +4 -0
- package/esm/validators/boolean-validator.d.ts.map +1 -1
- package/esm/validators/boolean-validator.js +6 -0
- package/esm/validators/boolean-validator.js.map +1 -1
- package/esm/validators/date-validator.d.ts +4 -0
- package/esm/validators/date-validator.d.ts.map +1 -1
- package/esm/validators/date-validator.js +7 -1
- package/esm/validators/date-validator.js.map +1 -1
- package/esm/validators/index.d.ts +1 -0
- package/esm/validators/index.d.ts.map +1 -1
- package/esm/validators/number-validator.d.ts +4 -0
- package/esm/validators/number-validator.d.ts.map +1 -1
- package/esm/validators/number-validator.js +6 -0
- package/esm/validators/number-validator.js.map +1 -1
- package/esm/validators/object-validator.d.ts +155 -1
- package/esm/validators/object-validator.d.ts.map +1 -1
- package/esm/validators/object-validator.js +235 -3
- package/esm/validators/object-validator.js.map +1 -1
- package/esm/validators/scalar-validator.d.ts +4 -0
- package/esm/validators/scalar-validator.d.ts.map +1 -1
- package/esm/validators/scalar-validator.js +9 -2
- package/esm/validators/scalar-validator.js.map +1 -1
- package/esm/validators/string-validator.d.ts +4 -0
- package/esm/validators/string-validator.d.ts.map +1 -1
- package/esm/validators/string-validator.js +6 -0
- package/esm/validators/string-validator.js.map +1 -1
- package/esm/validators/union-validator.d.ts +42 -0
- package/esm/validators/union-validator.d.ts.map +1 -0
- package/esm/validators/union-validator.js +44 -0
- package/esm/validators/union-validator.js.map +1 -0
- package/package.json +23 -2
package/README.md
CHANGED
|
@@ -1,373 +1,373 @@
|
|
|
1
|
-
# 🔮 Warlock Seal
|
|
2
|
-
|
|
3
|
-
> Cast validation seals on your schemas to protect your data
|
|
4
|
-
|
|
5
|
-
A powerful, type-safe validation library for TypeScript with an intuitive API and framework-agnostic design.
|
|
6
|
-
|
|
7
|
-
## 📦 Installation
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @warlock.js/seal
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
yarn add @warlock.js/seal
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
pnpm add @warlock.js/seal
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## 🚀 Quick Start
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { v, type Infer } from "@warlock.js/seal";
|
|
25
|
-
|
|
26
|
-
// Define your validation schema (cast a seal)
|
|
27
|
-
const userSeal = v.object({
|
|
28
|
-
name: v.string().required().min(3),
|
|
29
|
-
email: v.string().email().required(),
|
|
30
|
-
age: v.int().min(18),
|
|
31
|
-
status: v.string().in(["active", "inactive"]),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Extract TypeScript type automatically
|
|
35
|
-
type User = Infer<typeof userSeal>;
|
|
36
|
-
// Result: { name: string; email: string; age: number; status: string }
|
|
37
|
-
|
|
38
|
-
// Validate data
|
|
39
|
-
const result = await v.validate(userSeal, userData);
|
|
40
|
-
|
|
41
|
-
if (result.isValid) {
|
|
42
|
-
console.log("Data is sealed! ✅", result.data);
|
|
43
|
-
} else {
|
|
44
|
-
console.log("Seal broken! ❌", result.errors);
|
|
45
|
-
// [
|
|
46
|
-
// { error: "The name must be at least 3 characters", path: "name" },
|
|
47
|
-
// { error: "The email must be a valid email address", path: "email" }
|
|
48
|
-
// ]
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 🎯 Core Concepts
|
|
53
|
-
|
|
54
|
-
### Three-Layer Validation Pipeline
|
|
55
|
-
|
|
56
|
-
Seal uses a unique **three-layer architecture** that separates concerns:
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
Input Data
|
|
60
|
-
↓
|
|
61
|
-
🔧 MUTATORS (Prep for Validation)
|
|
62
|
-
- Normalize data before validation
|
|
63
|
-
- Examples: trim(), lowercase(), toStartOfDay()
|
|
64
|
-
- Run BEFORE validation rules
|
|
65
|
-
↓
|
|
66
|
-
✅ VALIDATORS (Check Constraints)
|
|
67
|
-
- Validate against rules
|
|
68
|
-
- Examples: email(), min(), after()
|
|
69
|
-
- Return errors if validation fails
|
|
70
|
-
↓
|
|
71
|
-
🎨 TRANSFORMERS (Format Output)
|
|
72
|
-
- Format validated data for output
|
|
73
|
-
- Examples: toISOString(), toFormat()
|
|
74
|
-
- Run AFTER validation passes
|
|
75
|
-
↓
|
|
76
|
-
Output Data
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Example: Date Validation
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
const schema = v
|
|
83
|
-
.date()
|
|
84
|
-
.toStartOfDay() // 🔧 Mutator: normalize to 00:00:00
|
|
85
|
-
.after("2024-01-01") // ✅ Validator: check Date object
|
|
86
|
-
.toISOString(); // 🎨 Transformer: output as ISO string
|
|
87
|
-
|
|
88
|
-
const result = await v.validate(schema, "2024-06-15 14:30:00");
|
|
89
|
-
// result.data = "2024-06-15T00:00:00.000Z"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
**Why this matters:**
|
|
93
|
-
|
|
94
|
-
- Mutators prepare data for validation (no more string comparison issues!)
|
|
95
|
-
- Validators check constraints on normalized data
|
|
96
|
-
- Transformers format output without affecting validation
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## 🌟 Key Features
|
|
101
|
-
|
|
102
|
-
### ✅ Type Inference
|
|
103
|
-
|
|
104
|
-
Automatically extract TypeScript types from your schemas:
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
const schema = v.object({
|
|
108
|
-
name: v.string().required(),
|
|
109
|
-
age: v.int(),
|
|
110
|
-
tags: v.array(v.string()),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
type User = Infer<typeof schema>;
|
|
114
|
-
// { name: string; age: number; tags: string[] }
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### ✅ Intuitive API
|
|
118
|
-
|
|
119
|
-
Readable, chainable methods:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
v.string().required().email().min(5).max(100);
|
|
123
|
-
|
|
124
|
-
v.int().min(0).max(100).positive();
|
|
125
|
-
|
|
126
|
-
v.array(v.string()).minLength(1).unique();
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### ✅ Conditional Validation
|
|
130
|
-
|
|
131
|
-
Apply different validators based on other field values:
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
const schema = v.object({
|
|
135
|
-
type: v.string().required().in(["admin", "user"]),
|
|
136
|
-
role: v.string().when("type", {
|
|
137
|
-
is: {
|
|
138
|
-
admin: v.string().required().in(["super", "moderator"]),
|
|
139
|
-
user: v.string().required().in(["member", "guest"]),
|
|
140
|
-
},
|
|
141
|
-
}),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// If type is "admin", role must be "super" or "moderator"
|
|
145
|
-
// If type is "user", role must be "member" or "guest"
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### ✅ Field Comparison
|
|
149
|
-
|
|
150
|
-
Compare fields against each other (global or sibling scope):
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
const schema = v.object({
|
|
154
|
-
password: v.string().required().min(8),
|
|
155
|
-
confirmPassword: v.string().required().sameAs("password"), // Compare with password field
|
|
156
|
-
|
|
157
|
-
startDate: v.date().required(),
|
|
158
|
-
endDate: v.date().required().after("startDate"), // Compare with startDate field
|
|
159
|
-
});
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### ✅ Custom Validation
|
|
163
|
-
|
|
164
|
-
Add your own validation logic:
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
v.string().refine(async value => {
|
|
168
|
-
const exists = await checkUsername(value);
|
|
169
|
-
if (exists) return "Username already taken";
|
|
170
|
-
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### ✅ Mutators & Transformers
|
|
174
|
-
|
|
175
|
-
Normalize input and format output:
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// String mutators
|
|
179
|
-
v.string()
|
|
180
|
-
.trim() // Remove whitespace
|
|
181
|
-
.lowercase() // Convert to lowercase
|
|
182
|
-
.email() // Validate email
|
|
183
|
-
.toJSON(); // Transform to JSON string
|
|
184
|
-
|
|
185
|
-
// Date mutators & transformers
|
|
186
|
-
v.date()
|
|
187
|
-
.toStartOfDay() // Normalize to midnight
|
|
188
|
-
.after("2024-01-01")
|
|
189
|
-
.toISOString(); // Output as ISO string
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## 📚 Documentation
|
|
195
|
-
|
|
196
|
-
For complete documentation, visit: **[https://warlock.js.org/seal](https://warlock.js.org/seal)**
|
|
197
|
-
|
|
198
|
-
### Available Validators
|
|
199
|
-
|
|
200
|
-
| Validator | Purpose | Example |
|
|
201
|
-
| ------------- | ------------------ | -------------------------------- |
|
|
202
|
-
| `v.string()` | String validation | `v.string().email().min(3)` |
|
|
203
|
-
| `v.int()` | Integer validation | `v.int().min(0).max(100)` |
|
|
204
|
-
| `v.float()` | Float validation | `v.float().positive()` |
|
|
205
|
-
| `v.number()` | Number validation | `v.number().min(0)` |
|
|
206
|
-
| `v.boolean()` | Boolean validation | `v.boolean().accepted()` |
|
|
207
|
-
| `v.date()` | Date validation | `v.date().after(new Date())` |
|
|
208
|
-
| `v.array()` | Array validation | `v.array(v.string()).min(1)` |
|
|
209
|
-
| `v.object()` | Object validation | `v.object({ name: v.string() })` |
|
|
210
|
-
| `v.scalar()` | Scalar values | `v.scalar().in([1, "2", true])` |
|
|
211
|
-
|
|
212
|
-
### Common Methods
|
|
213
|
-
|
|
214
|
-
Available on all validators:
|
|
215
|
-
|
|
216
|
-
| Method | Purpose |
|
|
217
|
-
| -------------------------- | -------------------------------- |
|
|
218
|
-
| `.required()` | Value must be present |
|
|
219
|
-
| `.optional()` | Value is optional |
|
|
220
|
-
| `.forbidden()` | Value must not be present |
|
|
221
|
-
| `.equals(value)` | Must equal specific value |
|
|
222
|
-
| `.default(value)` | Set default value |
|
|
223
|
-
| `.allowsEmpty()` | Skip validation if empty |
|
|
224
|
-
| `.when(field, conditions)` | Conditional validation |
|
|
225
|
-
| `.omit()` | Validate but exclude from output |
|
|
226
|
-
| `.refine(callback)` | Custom validation logic |
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## 💡 Examples
|
|
231
|
-
|
|
232
|
-
### User Registration
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
const registerSchema = v.object({
|
|
236
|
-
email: v.string().required().email(),
|
|
237
|
-
password: v.string().required().min(8).strongPassword(),
|
|
238
|
-
confirmPassword: v.string().required().sameAs("password"),
|
|
239
|
-
age: v.int().required().min(18).max(120),
|
|
240
|
-
terms: v.boolean().required().accepted(),
|
|
241
|
-
});
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Form with Conditional Fields
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
const formSchema = v.object({
|
|
248
|
-
accountType: v.string().required().in(["personal", "business"]),
|
|
249
|
-
|
|
250
|
-
// Required only if accountType is "business"
|
|
251
|
-
companyName: v.string().requiredIf("accountType", { is: "business" }),
|
|
252
|
-
|
|
253
|
-
// Conditional validation
|
|
254
|
-
taxId: v.string().when("accountType", {
|
|
255
|
-
is: {
|
|
256
|
-
business: v
|
|
257
|
-
.string()
|
|
258
|
-
.required()
|
|
259
|
-
.pattern(/^[0-9]{9}$/),
|
|
260
|
-
personal: v.string().forbidden(),
|
|
261
|
-
},
|
|
262
|
-
}),
|
|
263
|
-
});
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Date Range Validation
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
const bookingSchema = v.object({
|
|
270
|
-
checkIn: v.date().required().afterToday(),
|
|
271
|
-
|
|
272
|
-
checkOut: v
|
|
273
|
-
.date()
|
|
274
|
-
.required()
|
|
275
|
-
.after("checkIn") // Compare with checkIn field
|
|
276
|
-
.withinDays(30), // Max 30 days from checkIn
|
|
277
|
-
});
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### Array Validation
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
const tagsSchema = v
|
|
284
|
-
.array(v.string())
|
|
285
|
-
.required()
|
|
286
|
-
.minLength(1)
|
|
287
|
-
.maxLength(10)
|
|
288
|
-
.unique();
|
|
289
|
-
|
|
290
|
-
const usersSchema = v
|
|
291
|
-
.array(
|
|
292
|
-
v.object({
|
|
293
|
-
name: v.string().required(),
|
|
294
|
-
email: v.string().email(),
|
|
295
|
-
})
|
|
296
|
-
)
|
|
297
|
-
.minLength(1);
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
## 🔧 Framework Extensions
|
|
303
|
-
|
|
304
|
-
For Warlock.js projects, framework-specific validators are available:
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
import { v } from "@warlock.js/core/v";
|
|
308
|
-
|
|
309
|
-
const schema = v.object({
|
|
310
|
-
email: v.string().email().unique(User), // Database validation
|
|
311
|
-
avatar: v.file().image().maxSize(5000000), // File upload validation
|
|
312
|
-
uploadId: v.string().uploadable(), // Upload hash validation
|
|
313
|
-
});
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
---
|
|
317
|
-
|
|
318
|
-
## 🎨 Why "Seal"?
|
|
319
|
-
|
|
320
|
-
🔮 **Magical Context**
|
|
321
|
-
Warlocks use seals to protect and verify. Your validation schemas are seals that protect your data integrity.
|
|
322
|
-
|
|
323
|
-
💻 **Programming Context**
|
|
324
|
-
A "seal of approval" - data that passes validation is sealed and verified.
|
|
325
|
-
|
|
326
|
-
✨ **Developer Experience**
|
|
327
|
-
Clean, intuitive API that feels natural:
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
const seal = v.object({ ... }); // Cast a seal
|
|
331
|
-
await v.validate(seal, data); // Verify with the seal
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
## 🤝 Philosophy
|
|
337
|
-
|
|
338
|
-
**Seal** is designed around three principles:
|
|
339
|
-
|
|
340
|
-
1. **Type Safety First** - TypeScript support is not an afterthought
|
|
341
|
-
2. **Framework Agnostic** - Works anywhere JavaScript runs
|
|
342
|
-
3. **Intuitive API** - If it feels right, it probably works
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
|
-
## 📖 Full Documentation
|
|
347
|
-
|
|
348
|
-
For complete documentation, visit: **[https://warlock.js.org/seal](https://warlock.js.org/seal)**
|
|
349
|
-
|
|
350
|
-
The documentation includes:
|
|
351
|
-
|
|
352
|
-
- 📘 [Getting Started Guide](https://warlock.js.org/seal/getting-started/introduction)
|
|
353
|
-
- 🎯 [Core Concepts](https://warlock.js.org/seal/concepts/three-layer-architecture)
|
|
354
|
-
- 📝 [Validator Reference](https://warlock.js.org/seal/base-validator)
|
|
355
|
-
- 🔍 [All Validation Rules](https://warlock.js.org/seal/string-validator)
|
|
356
|
-
- 🔌 [Plugin System](https://warlock.js.org/seal/advanced/plugins)
|
|
357
|
-
- 🎨 [Custom Rules](https://warlock.js.org/seal/advanced/custom-rules)
|
|
358
|
-
|
|
359
|
-
---
|
|
360
|
-
|
|
361
|
-
## 📄 License
|
|
362
|
-
|
|
363
|
-
MIT
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
## 🤝 Contributing
|
|
368
|
-
|
|
369
|
-
See main Warlock.js repository
|
|
370
|
-
|
|
371
|
-
---
|
|
372
|
-
|
|
373
|
-
**Cast your seals and protect your data! 🔮✨**
|
|
1
|
+
# 🔮 Warlock Seal
|
|
2
|
+
|
|
3
|
+
> Cast validation seals on your schemas to protect your data
|
|
4
|
+
|
|
5
|
+
A powerful, type-safe validation library for TypeScript with an intuitive API and framework-agnostic design.
|
|
6
|
+
|
|
7
|
+
## 📦 Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @warlock.js/seal
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
yarn add @warlock.js/seal
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @warlock.js/seal
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { v, type Infer } from "@warlock.js/seal";
|
|
25
|
+
|
|
26
|
+
// Define your validation schema (cast a seal)
|
|
27
|
+
const userSeal = v.object({
|
|
28
|
+
name: v.string().required().min(3),
|
|
29
|
+
email: v.string().email().required(),
|
|
30
|
+
age: v.int().min(18),
|
|
31
|
+
status: v.string().in(["active", "inactive"]),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Extract TypeScript type automatically
|
|
35
|
+
type User = Infer<typeof userSeal>;
|
|
36
|
+
// Result: { name: string; email: string; age: number; status: string }
|
|
37
|
+
|
|
38
|
+
// Validate data
|
|
39
|
+
const result = await v.validate(userSeal, userData);
|
|
40
|
+
|
|
41
|
+
if (result.isValid) {
|
|
42
|
+
console.log("Data is sealed! ✅", result.data);
|
|
43
|
+
} else {
|
|
44
|
+
console.log("Seal broken! ❌", result.errors);
|
|
45
|
+
// [
|
|
46
|
+
// { error: "The name must be at least 3 characters", path: "name" },
|
|
47
|
+
// { error: "The email must be a valid email address", path: "email" }
|
|
48
|
+
// ]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🎯 Core Concepts
|
|
53
|
+
|
|
54
|
+
### Three-Layer Validation Pipeline
|
|
55
|
+
|
|
56
|
+
Seal uses a unique **three-layer architecture** that separates concerns:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Input Data
|
|
60
|
+
↓
|
|
61
|
+
🔧 MUTATORS (Prep for Validation)
|
|
62
|
+
- Normalize data before validation
|
|
63
|
+
- Examples: trim(), lowercase(), toStartOfDay()
|
|
64
|
+
- Run BEFORE validation rules
|
|
65
|
+
↓
|
|
66
|
+
✅ VALIDATORS (Check Constraints)
|
|
67
|
+
- Validate against rules
|
|
68
|
+
- Examples: email(), min(), after()
|
|
69
|
+
- Return errors if validation fails
|
|
70
|
+
↓
|
|
71
|
+
🎨 TRANSFORMERS (Format Output)
|
|
72
|
+
- Format validated data for output
|
|
73
|
+
- Examples: toISOString(), toFormat()
|
|
74
|
+
- Run AFTER validation passes
|
|
75
|
+
↓
|
|
76
|
+
Output Data
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Example: Date Validation
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const schema = v
|
|
83
|
+
.date()
|
|
84
|
+
.toStartOfDay() // 🔧 Mutator: normalize to 00:00:00
|
|
85
|
+
.after("2024-01-01") // ✅ Validator: check Date object
|
|
86
|
+
.toISOString(); // 🎨 Transformer: output as ISO string
|
|
87
|
+
|
|
88
|
+
const result = await v.validate(schema, "2024-06-15 14:30:00");
|
|
89
|
+
// result.data = "2024-06-15T00:00:00.000Z"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Why this matters:**
|
|
93
|
+
|
|
94
|
+
- Mutators prepare data for validation (no more string comparison issues!)
|
|
95
|
+
- Validators check constraints on normalized data
|
|
96
|
+
- Transformers format output without affecting validation
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 🌟 Key Features
|
|
101
|
+
|
|
102
|
+
### ✅ Type Inference
|
|
103
|
+
|
|
104
|
+
Automatically extract TypeScript types from your schemas:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const schema = v.object({
|
|
108
|
+
name: v.string().required(),
|
|
109
|
+
age: v.int(),
|
|
110
|
+
tags: v.array(v.string()),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
type User = Infer<typeof schema>;
|
|
114
|
+
// { name: string; age: number; tags: string[] }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### ✅ Intuitive API
|
|
118
|
+
|
|
119
|
+
Readable, chainable methods:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
v.string().required().email().min(5).max(100);
|
|
123
|
+
|
|
124
|
+
v.int().min(0).max(100).positive();
|
|
125
|
+
|
|
126
|
+
v.array(v.string()).minLength(1).unique();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ✅ Conditional Validation
|
|
130
|
+
|
|
131
|
+
Apply different validators based on other field values:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const schema = v.object({
|
|
135
|
+
type: v.string().required().in(["admin", "user"]),
|
|
136
|
+
role: v.string().when("type", {
|
|
137
|
+
is: {
|
|
138
|
+
admin: v.string().required().in(["super", "moderator"]),
|
|
139
|
+
user: v.string().required().in(["member", "guest"]),
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// If type is "admin", role must be "super" or "moderator"
|
|
145
|
+
// If type is "user", role must be "member" or "guest"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### ✅ Field Comparison
|
|
149
|
+
|
|
150
|
+
Compare fields against each other (global or sibling scope):
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const schema = v.object({
|
|
154
|
+
password: v.string().required().min(8),
|
|
155
|
+
confirmPassword: v.string().required().sameAs("password"), // Compare with password field
|
|
156
|
+
|
|
157
|
+
startDate: v.date().required(),
|
|
158
|
+
endDate: v.date().required().after("startDate"), // Compare with startDate field
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### ✅ Custom Validation
|
|
163
|
+
|
|
164
|
+
Add your own validation logic:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
v.string().refine(async value => {
|
|
168
|
+
const exists = await checkUsername(value);
|
|
169
|
+
if (exists) return "Username already taken";
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### ✅ Mutators & Transformers
|
|
174
|
+
|
|
175
|
+
Normalize input and format output:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// String mutators
|
|
179
|
+
v.string()
|
|
180
|
+
.trim() // Remove whitespace
|
|
181
|
+
.lowercase() // Convert to lowercase
|
|
182
|
+
.email() // Validate email
|
|
183
|
+
.toJSON(); // Transform to JSON string
|
|
184
|
+
|
|
185
|
+
// Date mutators & transformers
|
|
186
|
+
v.date()
|
|
187
|
+
.toStartOfDay() // Normalize to midnight
|
|
188
|
+
.after("2024-01-01")
|
|
189
|
+
.toISOString(); // Output as ISO string
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 📚 Documentation
|
|
195
|
+
|
|
196
|
+
For complete documentation, visit: **[https://warlock.js.org/seal](https://warlock.js.org/seal)**
|
|
197
|
+
|
|
198
|
+
### Available Validators
|
|
199
|
+
|
|
200
|
+
| Validator | Purpose | Example |
|
|
201
|
+
| ------------- | ------------------ | -------------------------------- |
|
|
202
|
+
| `v.string()` | String validation | `v.string().email().min(3)` |
|
|
203
|
+
| `v.int()` | Integer validation | `v.int().min(0).max(100)` |
|
|
204
|
+
| `v.float()` | Float validation | `v.float().positive()` |
|
|
205
|
+
| `v.number()` | Number validation | `v.number().min(0)` |
|
|
206
|
+
| `v.boolean()` | Boolean validation | `v.boolean().accepted()` |
|
|
207
|
+
| `v.date()` | Date validation | `v.date().after(new Date())` |
|
|
208
|
+
| `v.array()` | Array validation | `v.array(v.string()).min(1)` |
|
|
209
|
+
| `v.object()` | Object validation | `v.object({ name: v.string() })` |
|
|
210
|
+
| `v.scalar()` | Scalar values | `v.scalar().in([1, "2", true])` |
|
|
211
|
+
|
|
212
|
+
### Common Methods
|
|
213
|
+
|
|
214
|
+
Available on all validators:
|
|
215
|
+
|
|
216
|
+
| Method | Purpose |
|
|
217
|
+
| -------------------------- | -------------------------------- |
|
|
218
|
+
| `.required()` | Value must be present |
|
|
219
|
+
| `.optional()` | Value is optional |
|
|
220
|
+
| `.forbidden()` | Value must not be present |
|
|
221
|
+
| `.equals(value)` | Must equal specific value |
|
|
222
|
+
| `.default(value)` | Set default value |
|
|
223
|
+
| `.allowsEmpty()` | Skip validation if empty |
|
|
224
|
+
| `.when(field, conditions)` | Conditional validation |
|
|
225
|
+
| `.omit()` | Validate but exclude from output |
|
|
226
|
+
| `.refine(callback)` | Custom validation logic |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 💡 Examples
|
|
231
|
+
|
|
232
|
+
### User Registration
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const registerSchema = v.object({
|
|
236
|
+
email: v.string().required().email(),
|
|
237
|
+
password: v.string().required().min(8).strongPassword(),
|
|
238
|
+
confirmPassword: v.string().required().sameAs("password"),
|
|
239
|
+
age: v.int().required().min(18).max(120),
|
|
240
|
+
terms: v.boolean().required().accepted(),
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Form with Conditional Fields
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const formSchema = v.object({
|
|
248
|
+
accountType: v.string().required().in(["personal", "business"]),
|
|
249
|
+
|
|
250
|
+
// Required only if accountType is "business"
|
|
251
|
+
companyName: v.string().requiredIf("accountType", { is: "business" }),
|
|
252
|
+
|
|
253
|
+
// Conditional validation
|
|
254
|
+
taxId: v.string().when("accountType", {
|
|
255
|
+
is: {
|
|
256
|
+
business: v
|
|
257
|
+
.string()
|
|
258
|
+
.required()
|
|
259
|
+
.pattern(/^[0-9]{9}$/),
|
|
260
|
+
personal: v.string().forbidden(),
|
|
261
|
+
},
|
|
262
|
+
}),
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Date Range Validation
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const bookingSchema = v.object({
|
|
270
|
+
checkIn: v.date().required().afterToday(),
|
|
271
|
+
|
|
272
|
+
checkOut: v
|
|
273
|
+
.date()
|
|
274
|
+
.required()
|
|
275
|
+
.after("checkIn") // Compare with checkIn field
|
|
276
|
+
.withinDays(30), // Max 30 days from checkIn
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Array Validation
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const tagsSchema = v
|
|
284
|
+
.array(v.string())
|
|
285
|
+
.required()
|
|
286
|
+
.minLength(1)
|
|
287
|
+
.maxLength(10)
|
|
288
|
+
.unique();
|
|
289
|
+
|
|
290
|
+
const usersSchema = v
|
|
291
|
+
.array(
|
|
292
|
+
v.object({
|
|
293
|
+
name: v.string().required(),
|
|
294
|
+
email: v.string().email(),
|
|
295
|
+
})
|
|
296
|
+
)
|
|
297
|
+
.minLength(1);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 🔧 Framework Extensions
|
|
303
|
+
|
|
304
|
+
For Warlock.js projects, framework-specific validators are available:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { v } from "@warlock.js/core/v";
|
|
308
|
+
|
|
309
|
+
const schema = v.object({
|
|
310
|
+
email: v.string().email().unique(User), // Database validation
|
|
311
|
+
avatar: v.file().image().maxSize(5000000), // File upload validation
|
|
312
|
+
uploadId: v.string().uploadable(), // Upload hash validation
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 🎨 Why "Seal"?
|
|
319
|
+
|
|
320
|
+
🔮 **Magical Context**
|
|
321
|
+
Warlocks use seals to protect and verify. Your validation schemas are seals that protect your data integrity.
|
|
322
|
+
|
|
323
|
+
💻 **Programming Context**
|
|
324
|
+
A "seal of approval" - data that passes validation is sealed and verified.
|
|
325
|
+
|
|
326
|
+
✨ **Developer Experience**
|
|
327
|
+
Clean, intuitive API that feels natural:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const seal = v.object({ ... }); // Cast a seal
|
|
331
|
+
await v.validate(seal, data); // Verify with the seal
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 🤝 Philosophy
|
|
337
|
+
|
|
338
|
+
**Seal** is designed around three principles:
|
|
339
|
+
|
|
340
|
+
1. **Type Safety First** - TypeScript support is not an afterthought
|
|
341
|
+
2. **Framework Agnostic** - Works anywhere JavaScript runs
|
|
342
|
+
3. **Intuitive API** - If it feels right, it probably works
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 📖 Full Documentation
|
|
347
|
+
|
|
348
|
+
For complete documentation, visit: **[https://warlock.js.org/seal](https://warlock.js.org/seal)**
|
|
349
|
+
|
|
350
|
+
The documentation includes:
|
|
351
|
+
|
|
352
|
+
- 📘 [Getting Started Guide](https://warlock.js.org/seal/getting-started/introduction)
|
|
353
|
+
- 🎯 [Core Concepts](https://warlock.js.org/seal/concepts/three-layer-architecture)
|
|
354
|
+
- 📝 [Validator Reference](https://warlock.js.org/seal/base-validator)
|
|
355
|
+
- 🔍 [All Validation Rules](https://warlock.js.org/seal/string-validator)
|
|
356
|
+
- 🔌 [Plugin System](https://warlock.js.org/seal/advanced/plugins)
|
|
357
|
+
- 🎨 [Custom Rules](https://warlock.js.org/seal/advanced/custom-rules)
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 📄 License
|
|
362
|
+
|
|
363
|
+
MIT
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 🤝 Contributing
|
|
368
|
+
|
|
369
|
+
See main Warlock.js repository
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
**Cast your seals and protect your data! 🔮✨**
|