zod-validate 1.0.0
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/LICENSE +21 -0
- package/README.md +833 -0
- package/dist/createValidators.d.ts +7 -0
- package/dist/createValidators.d.ts.map +1 -0
- package/dist/createValidators.js +16 -0
- package/dist/createValidators.js.map +1 -0
- package/dist/engine.d.ts +5 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +39 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +3 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +77 -0
- package/dist/messages.js.map +1 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bibek Bhattarai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
# zod-validate
|
|
2
|
+
|
|
3
|
+
Reusable validation utilities built on top of [Zod](https://zod.dev/) for frontend and backend applications.
|
|
4
|
+
|
|
5
|
+
`zod-validate` provides a small layer around Zod's `safeParse()` API so you can create a reusable validator from a schema and use the same validator for:
|
|
6
|
+
|
|
7
|
+
- Whole-form validation
|
|
8
|
+
- Individual field validation
|
|
9
|
+
- Nested object validation
|
|
10
|
+
- Nested array validation
|
|
11
|
+
- Validation error codes
|
|
12
|
+
- Human-readable message resolution
|
|
13
|
+
- Localized validation messages
|
|
14
|
+
|
|
15
|
+
The package does **not** replace Zod. Zod remains the validation engine.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install zod-validate zod
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Why zod-validate?
|
|
28
|
+
|
|
29
|
+
Zod already provides excellent schema validation.
|
|
30
|
+
|
|
31
|
+
The problem is usually what happens **around** the schema.
|
|
32
|
+
|
|
33
|
+
For example, an application may need to:
|
|
34
|
+
|
|
35
|
+
- Validate an entire form.
|
|
36
|
+
- Validate one field while the user is typing.
|
|
37
|
+
- Keep validation errors associated with their original fields.
|
|
38
|
+
- Handle deeply nested objects and arrays.
|
|
39
|
+
- Return stable validation codes instead of UI-specific messages.
|
|
40
|
+
- Resolve those codes into different languages on the frontend.
|
|
41
|
+
|
|
42
|
+
`zod-validate` provides a small, consistent API for those tasks.
|
|
43
|
+
|
|
44
|
+
Instead of repeatedly working directly with Zod's `safeParse()` result, create a validator once:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const signup = createValidator(signupSchema);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then use it wherever needed:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
signup.validate(data);
|
|
54
|
+
signup.validateField("mobile_no", value);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
# Basic Usage
|
|
60
|
+
|
|
61
|
+
## 1. Define a Zod schema
|
|
62
|
+
|
|
63
|
+
You continue to define your validation rules using Zod.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { z } from "zod";
|
|
67
|
+
|
|
68
|
+
const signupSchema = z.object({
|
|
69
|
+
mobile_no: z
|
|
70
|
+
.string("mobile_no")
|
|
71
|
+
.trim()
|
|
72
|
+
.min(1, "mobile_no_required")
|
|
73
|
+
.regex(/^[0-9]+$/, "mobile_no_digits")
|
|
74
|
+
.regex(/^(98|97)/, "mobile_no_prefix")
|
|
75
|
+
.length(10, "mobile_no_length"),
|
|
76
|
+
|
|
77
|
+
password: z.string("password").min(8, "password_min_length"),
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The validation codes are supplied as Zod messages:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
"mobile_no_required";
|
|
85
|
+
"mobile_no_digits";
|
|
86
|
+
"mobile_no_prefix";
|
|
87
|
+
"mobile_no_length";
|
|
88
|
+
"password_min_length";
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
These codes can later be converted into human-readable messages.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
# 2. Create a Validator
|
|
96
|
+
|
|
97
|
+
Import `createValidator`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createValidator } from "zod-validate";
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Create a validator from your schema:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const signup = createValidator(signupSchema);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The validator is now tied to that schema.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
# 3. Validate an Entire Object
|
|
114
|
+
|
|
115
|
+
Pass your data to `validate()`:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const result = signup.validate({
|
|
119
|
+
mobile_no: "9812345678",
|
|
120
|
+
password: "password123",
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If the data is valid:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
{
|
|
128
|
+
valid: true,
|
|
129
|
+
data: {
|
|
130
|
+
mobile_no: "9812345678",
|
|
131
|
+
password: "password123"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The returned `data` is the data parsed by Zod.
|
|
137
|
+
|
|
138
|
+
This means Zod transformations are also preserved.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Invalid data
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const result = signup.validate({
|
|
146
|
+
mobile_no: "",
|
|
147
|
+
password: "123",
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The result is:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
{
|
|
155
|
+
valid: false,
|
|
156
|
+
errors: {
|
|
157
|
+
mobile_no: "mobile_no_required",
|
|
158
|
+
password: "password_min_length"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The validator returns the validation codes rather than forcing a human-readable message into your validation layer.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
# 4. Type-Safe Result Handling
|
|
168
|
+
|
|
169
|
+
The result is a discriminated union.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const result = signup.validate(data);
|
|
173
|
+
|
|
174
|
+
if (result.valid) {
|
|
175
|
+
console.log(result.data);
|
|
176
|
+
} else {
|
|
177
|
+
console.log(result.errors);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
TypeScript understands that:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
result.valid === true;
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
means `result.data` is available.
|
|
188
|
+
|
|
189
|
+
And:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
result.valid === false;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
means `result.errors` is available.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
# 5. Validate a Single Field
|
|
200
|
+
|
|
201
|
+
For frontend forms, you often don't want to validate the entire form.
|
|
202
|
+
|
|
203
|
+
For example, while the user is entering their mobile number:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const result = signup.validateField("mobile_no", "9812345678");
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
If valid:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
{
|
|
213
|
+
valid: true,
|
|
214
|
+
data: "9812345678"
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
If invalid:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const result = signup.validateField("mobile_no", "abc");
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
You get:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
{
|
|
228
|
+
valid: false,
|
|
229
|
+
errors: {
|
|
230
|
+
mobile_no: "mobile_no_digits"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
This makes the same schema useful for both:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
Whole form validation
|
|
239
|
+
+
|
|
240
|
+
Individual field validation
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
# 6. Why Field Validation Is Useful
|
|
246
|
+
|
|
247
|
+
A frontend form might use the validator like this:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const result = signup.validateField("mobile_no", mobileNo);
|
|
251
|
+
|
|
252
|
+
if (!result.valid) {
|
|
253
|
+
setMobileError(result.errors.mobile_no);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Then when the entire form is submitted:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
const result = signup.validate(formData);
|
|
261
|
+
|
|
262
|
+
if (!result.valid) {
|
|
263
|
+
setErrors(result.errors);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
You don't need a separate schema for field validation.
|
|
268
|
+
|
|
269
|
+
The same Zod schema is used for both operations.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
# 7. Nested Objects
|
|
274
|
+
|
|
275
|
+
`zod-validate` preserves the structure of nested validation errors.
|
|
276
|
+
|
|
277
|
+
Consider:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
const userSchema = z.object({
|
|
281
|
+
name: z.string("name_required"),
|
|
282
|
+
|
|
283
|
+
address: z.object({
|
|
284
|
+
city: z.string("city_required"),
|
|
285
|
+
street: z.string("street_required"),
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Create the validator:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
const userValidator = createValidator(userSchema);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Validate:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
const result = userValidator.validate({
|
|
300
|
+
name: "Bibek",
|
|
301
|
+
address: {
|
|
302
|
+
city: "",
|
|
303
|
+
street: "",
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The errors mirror the input structure:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
{
|
|
312
|
+
valid: false,
|
|
313
|
+
errors: {
|
|
314
|
+
address: {
|
|
315
|
+
city: "city_required",
|
|
316
|
+
street: "street_required"
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
This is useful because the error structure follows the same hierarchy as the data.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
# 8. Nested Arrays
|
|
327
|
+
|
|
328
|
+
Arrays are also preserved.
|
|
329
|
+
|
|
330
|
+
For example:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
const buildingSchema = z.object({
|
|
334
|
+
name: z.string("building_name_required"),
|
|
335
|
+
|
|
336
|
+
floors: z.array(
|
|
337
|
+
z.object({
|
|
338
|
+
floor_number: z.number("floor_number_required"),
|
|
339
|
+
|
|
340
|
+
name: z.string("floor_name_required"),
|
|
341
|
+
|
|
342
|
+
units: z.array(
|
|
343
|
+
z.object({
|
|
344
|
+
unit_number: z.string("unit_number_required"),
|
|
345
|
+
|
|
346
|
+
rent: z.number("rent_required"),
|
|
347
|
+
}),
|
|
348
|
+
),
|
|
349
|
+
}),
|
|
350
|
+
),
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Create the validator:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
const buildingValidator = createValidator(buildingSchema);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Suppose the second floor contains invalid data:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
const result = buildingValidator.validate({
|
|
364
|
+
name: "Sunrise Apartments",
|
|
365
|
+
|
|
366
|
+
floors: [
|
|
367
|
+
{
|
|
368
|
+
floor_number: 1,
|
|
369
|
+
name: "Ground Floor",
|
|
370
|
+
units: [
|
|
371
|
+
{
|
|
372
|
+
unit_number: "101",
|
|
373
|
+
rent: 15000,
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
{
|
|
379
|
+
floor_number: 2,
|
|
380
|
+
name: "",
|
|
381
|
+
units: [
|
|
382
|
+
{
|
|
383
|
+
unit_number: "",
|
|
384
|
+
rent: -5000,
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
The resulting errors follow the same structure:
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
{
|
|
396
|
+
valid: false,
|
|
397
|
+
errors: {
|
|
398
|
+
floors: [
|
|
399
|
+
<empty>,
|
|
400
|
+
{
|
|
401
|
+
name: "floor_name_required",
|
|
402
|
+
|
|
403
|
+
units: [
|
|
404
|
+
{
|
|
405
|
+
unit_number: "unit_number_required",
|
|
406
|
+
rent: "rent_required"
|
|
407
|
+
}
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The error at:
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
errors.floors[1].name;
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
belongs to:
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
floors[1].name;
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
And:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
errors.floors[1].units[0].rent;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
belongs to:
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
floors[1].units[0].rent;
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The structure is intentionally aligned with the original data.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
# 9. Using Nested Errors in React
|
|
444
|
+
|
|
445
|
+
This structure works naturally with component hierarchies.
|
|
446
|
+
|
|
447
|
+
For example:
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
<Building errors={errors} />
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
The building component can pass the appropriate floor errors:
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
<Floors errors={errors.floors?.[floorIndex]} />
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The floor component can pass errors to a unit:
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
<Unit errors={errors?.units?.[unitIndex]} />
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
And the unit component can access:
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
errors?.rent;
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
For example:
|
|
472
|
+
|
|
473
|
+
```tsx
|
|
474
|
+
<input />;
|
|
475
|
+
|
|
476
|
+
{
|
|
477
|
+
errors?.rent && <p>{errors.rent}</p>;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
This works because the validation error structure follows the data structure.
|
|
482
|
+
|
|
483
|
+
You don't need to convert:
|
|
484
|
+
|
|
485
|
+
```text
|
|
486
|
+
floors[1].units[0].rent
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
into a flat string just to find the error.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
# 10. Sparse Array Errors
|
|
494
|
+
|
|
495
|
+
When only some array items contain errors, the resulting error array can contain empty slots.
|
|
496
|
+
|
|
497
|
+
For example:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
[
|
|
501
|
+
<empty>,
|
|
502
|
+
{
|
|
503
|
+
name: "floor_name_required"
|
|
504
|
+
}
|
|
505
|
+
]
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
The empty slot represents an array item that has no validation error.
|
|
509
|
+
|
|
510
|
+
Therefore:
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
errors.floors?.[0];
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
returns:
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
undefined;
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
while:
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
errors.floors?.[1];
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
contains the second floor's errors.
|
|
529
|
+
|
|
530
|
+
This allows the error array to preserve the original data indexes.
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
# 11. Validation Codes
|
|
535
|
+
|
|
536
|
+
A major part of the design is that the validation layer can return stable codes.
|
|
537
|
+
|
|
538
|
+
For example:
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
const result = signup.validate(data);
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
may produce:
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
{
|
|
548
|
+
valid: false,
|
|
549
|
+
errors: {
|
|
550
|
+
mobile_no: "mobile_no_required",
|
|
551
|
+
password: "password_min_length"
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
The validation layer doesn't need to know how these errors should be displayed.
|
|
557
|
+
|
|
558
|
+
This allows the application to decide what each code means.
|
|
559
|
+
|
|
560
|
+
For example:
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
const messages = {
|
|
564
|
+
mobile_no_required: "Mobile number is required.",
|
|
565
|
+
password_min_length: "Password must be at least 8 characters.",
|
|
566
|
+
};
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
# 12. Resolve Validation Messages
|
|
572
|
+
|
|
573
|
+
Import:
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
import { resolveValidationMessages } from "zod-validate";
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Then:
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
const resolvedErrors = resolveValidationMessages(result.errors, messages);
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Given:
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
const errors = {
|
|
589
|
+
mobile_no: "mobile_no_required",
|
|
590
|
+
password: "password_min_length",
|
|
591
|
+
};
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
and:
|
|
595
|
+
|
|
596
|
+
```ts
|
|
597
|
+
const messages = {
|
|
598
|
+
mobile_no_required: "Mobile number is required.",
|
|
599
|
+
password_min_length: "Password must be at least 8 characters.",
|
|
600
|
+
};
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
the result is:
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
{
|
|
607
|
+
mobile_no: "Mobile number is required.",
|
|
608
|
+
password: "Password must be at least 8 characters."
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
# 13. Missing Message Codes
|
|
615
|
+
|
|
616
|
+
A message does not have to exist for every validation code.
|
|
617
|
+
|
|
618
|
+
For example:
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
const errors = {
|
|
622
|
+
mobile_no: "mobile_no_required",
|
|
623
|
+
password: "password_min_length",
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const messages = {
|
|
627
|
+
mobile_no_required: "Mobile number is required.",
|
|
628
|
+
};
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
The known message is resolved:
|
|
632
|
+
|
|
633
|
+
```ts
|
|
634
|
+
{
|
|
635
|
+
mobile_no: "Mobile number is required.",
|
|
636
|
+
password: "password_min_length"
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
An unknown code falls back to the original code.
|
|
641
|
+
|
|
642
|
+
This means missing translations do not silently disappear.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
# 14. Localized Messages
|
|
647
|
+
|
|
648
|
+
Messages can also be provided by locale.
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
const messages = {
|
|
652
|
+
en: {
|
|
653
|
+
mobile_no_required: "Mobile number is required.",
|
|
654
|
+
password_min_length: "Password must be at least 8 characters.",
|
|
655
|
+
},
|
|
656
|
+
|
|
657
|
+
np: {
|
|
658
|
+
mobile_no_required: "मोबाइल नम्बर आवश्यक छ।",
|
|
659
|
+
password_min_length: "पासवर्ड कम्तीमा ८ अक्षरको हुनुपर्छ।",
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Resolve English messages:
|
|
665
|
+
|
|
666
|
+
```ts
|
|
667
|
+
const errors = resolveValidationMessages(
|
|
668
|
+
validationResult.errors,
|
|
669
|
+
messages,
|
|
670
|
+
"en",
|
|
671
|
+
);
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Result:
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
{
|
|
678
|
+
mobile_no: "Mobile number is required.",
|
|
679
|
+
password: "Password must be at least 8 characters."
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
Resolve Nepali messages:
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
const errors = resolveValidationMessages(
|
|
687
|
+
validationResult.errors,
|
|
688
|
+
messages,
|
|
689
|
+
"np",
|
|
690
|
+
);
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Result:
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
{
|
|
697
|
+
mobile_no: "मोबाइल नम्बर आवश्यक छ।",
|
|
698
|
+
password: "पासवर्ड कम्तीमा ८ अक्षरको हुनुपर्छ।"
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
# 15. Default Locale
|
|
705
|
+
|
|
706
|
+
If no locale is supplied and the message configuration is localized, `en` is preferred when available.
|
|
707
|
+
|
|
708
|
+
For example:
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
const messages = {
|
|
712
|
+
en: {
|
|
713
|
+
required: "This field is required.",
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
np: {
|
|
717
|
+
required: "यो field आवश्यक छ।",
|
|
718
|
+
},
|
|
719
|
+
};
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Then:
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
resolveValidationMessages(errors, messages);
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
uses the English messages.
|
|
729
|
+
|
|
730
|
+
If `en` does not exist, the first available locale is used.
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
# 16. Plain Messages
|
|
735
|
+
|
|
736
|
+
Localization is optional.
|
|
737
|
+
|
|
738
|
+
You can simply use:
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
const messages = {
|
|
742
|
+
required: "This field is required.",
|
|
743
|
+
invalid_email: "Invalid email address.",
|
|
744
|
+
};
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
Then:
|
|
748
|
+
|
|
749
|
+
```ts
|
|
750
|
+
resolveValidationMessages(errors, messages);
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
No locale is required.
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
# 17. Locale Configuration Validation
|
|
758
|
+
|
|
759
|
+
The resolver validates the message configuration.
|
|
760
|
+
|
|
761
|
+
A plain message map must contain string messages:
|
|
762
|
+
|
|
763
|
+
```ts
|
|
764
|
+
const messages = {
|
|
765
|
+
required: "This field is required.",
|
|
766
|
+
invalid: "Invalid value.",
|
|
767
|
+
};
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
A localized message map must contain message maps:
|
|
771
|
+
|
|
772
|
+
```ts
|
|
773
|
+
const messages = {
|
|
774
|
+
en: {
|
|
775
|
+
required: "This field is required.",
|
|
776
|
+
},
|
|
777
|
+
|
|
778
|
+
np: {
|
|
779
|
+
required: "यो field आवश्यक छ।",
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
Invalid message values are rejected instead of being silently accepted.
|
|
785
|
+
|
|
786
|
+
For example:
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
const messages = {
|
|
790
|
+
en: {
|
|
791
|
+
required: 123,
|
|
792
|
+
},
|
|
793
|
+
};
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
is invalid because validation messages must be strings.
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
# 18. Frontend Use Case
|
|
801
|
+
|
|
802
|
+
A React form can use the library in two stages.
|
|
803
|
+
|
|
804
|
+
### During field interaction
|
|
805
|
+
|
|
806
|
+
```ts
|
|
807
|
+
const result = signup.validateField("mobile_no", mobileNo);
|
|
808
|
+
|
|
809
|
+
if (!result.valid) {
|
|
810
|
+
setErrors(result.errors);
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### During submission
|
|
815
|
+
|
|
816
|
+
```ts
|
|
817
|
+
const result = signup.validate(formData);
|
|
818
|
+
|
|
819
|
+
if (!result.valid) {
|
|
820
|
+
const errors = resolveValidationMessages(result.errors, messages);
|
|
821
|
+
|
|
822
|
+
setErrors(errors);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
submitForm(result.data);
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
This gives the frontend:
|
|
830
|
+
|
|
831
|
+
```text
|
|
832
|
+
Zod schema
|
|
833
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ValidationResult } from "./types.js";
|
|
3
|
+
export declare const createValidator: <S extends z.ZodType>(schema: S) => {
|
|
4
|
+
validate(data: unknown): ValidationResult<z.infer<S>>;
|
|
5
|
+
validateField<K extends keyof z.infer<S>>(field: K, value: unknown): ValidationResult<z.infer<S>[K]>;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=createValidators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createValidators.d.ts","sourceRoot":"","sources":["../src/createValidators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnD,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,UAAU,CAAC;IAK1D,QAAQ,OAAO,OAAO,GAAG,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAIrD,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAC/B,CAAC,SACD,OAAO,GACb,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CASrC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { validateFieldEngine, validateObjectEngine } from "./engine.js";
|
|
3
|
+
export const createValidator = (schema) => {
|
|
4
|
+
const validateField = schema instanceof z.ZodObject ? validateFieldEngine(schema) : undefined;
|
|
5
|
+
return {
|
|
6
|
+
validate(data) {
|
|
7
|
+
return validateObjectEngine(schema, data);
|
|
8
|
+
},
|
|
9
|
+
validateField(field, value) {
|
|
10
|
+
if (!validateField)
|
|
11
|
+
throw new Error("validateField() can only be used with a Zod object schema.");
|
|
12
|
+
return validateField(field, value);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=createValidators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createValidators.js","sourceRoot":"","sources":["../src/createValidators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAsB,MAAS,EAAE,EAAE;IAChE,MAAM,aAAa,GACjB,MAAM,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE1E,OAAO;QACL,QAAQ,CAAC,IAAa;YACpB,OAAO,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QAED,aAAa,CACX,KAAQ,EACR,KAAc;YAEd,IAAI,CAAC,aAAa;gBAChB,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;YAEJ,OAAO,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ValidationResult } from "./types.js";
|
|
3
|
+
export declare const validateObjectEngine: <S extends z.ZodType>(schema: S, data: unknown) => ValidationResult<z.infer<S>>;
|
|
4
|
+
export declare const validateFieldEngine: <T extends z.ZodObject<any>>(schema: T) => <K extends keyof z.infer<T>>(field: K, value: unknown) => ValidationResult<z.infer<T>[K]>;
|
|
5
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,UAC9C,CAAC,QACH,OAAO,KACZ,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CA0B7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,MAG/D,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SACzB,CAAC,SACD,OAAO,KACb,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAYlC,CAAC"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const validateObjectEngine = (schema, data) => {
|
|
3
|
+
const result = schema.safeParse(data);
|
|
4
|
+
if (result.success)
|
|
5
|
+
return { valid: true, data: result.data };
|
|
6
|
+
const errors = {};
|
|
7
|
+
for (const issue of result.error.issues) {
|
|
8
|
+
let current = errors;
|
|
9
|
+
const path = issue.path;
|
|
10
|
+
for (let i = 0; i < path.length; i++) {
|
|
11
|
+
const key = path[i];
|
|
12
|
+
const isLast = i === path.length - 1;
|
|
13
|
+
if (isLast) {
|
|
14
|
+
if (current[key] === undefined)
|
|
15
|
+
current[key] = issue.message;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (!current[key] || typeof current[key] !== "object")
|
|
19
|
+
current[key] = typeof path[i + 1] === "number" ? [] : {};
|
|
20
|
+
current = current[key];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { valid: false, errors };
|
|
25
|
+
};
|
|
26
|
+
export const validateFieldEngine = (schema) => {
|
|
27
|
+
const shape = schema.shape;
|
|
28
|
+
return (field, value) => {
|
|
29
|
+
const result = shape[field].safeParse(value);
|
|
30
|
+
if (result.success)
|
|
31
|
+
return { valid: true, data: result.data };
|
|
32
|
+
const error = result.error.issues[0];
|
|
33
|
+
return {
|
|
34
|
+
valid: false,
|
|
35
|
+
errors: { [String(field)]: error?.message ?? "validation_error" },
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAAS,EACT,IAAa,EACiB,EAAE;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAE9D,MAAM,MAAM,GAAQ,EAAE,CAAC;IAEvB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACxC,IAAI,OAAO,GAAG,MAAM,CAAC;QACrB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QAExB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YACrB,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YAErC,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS;oBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ;oBACnD,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAE3D,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAA6B,MAAS,EAAE,EAAE;IAC3E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAE3B,OAAO,CACL,KAAQ,EACR,KAAc,EACmB,EAAE;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAE7C,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;QAE9D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAErC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,IAAI,kBAAkB,EAAE;SAClE,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAqHpB,eAAO,MAAM,yBAAyB,WAC5B,gBAAgB,YACd,kBAAkB,WACnB,MAAM,KACd,gBAGF,CAAC"}
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const getMessageMap = (messages, locale) => {
|
|
2
|
+
const isPlain = isPlainMessageMap(messages);
|
|
3
|
+
const isLocalized = isLocaleMessageMap(messages);
|
|
4
|
+
if (!isPlain && !isLocalized) {
|
|
5
|
+
const localeError = getLocaleMessageMapError(messages);
|
|
6
|
+
if (localeError)
|
|
7
|
+
throw new Error(localeError);
|
|
8
|
+
throw new Error("Invalid validation messages. Messages must be either a plain message map or a locale-based message map.");
|
|
9
|
+
}
|
|
10
|
+
if (isPlain) {
|
|
11
|
+
if (locale)
|
|
12
|
+
throw new Error(`Locale "${locale}" was provided, but the validation messages are not localized.`);
|
|
13
|
+
return messages;
|
|
14
|
+
}
|
|
15
|
+
const locales = Object.keys(messages);
|
|
16
|
+
if (locales.length === 0)
|
|
17
|
+
throw new Error("No validation message locales have been defined.");
|
|
18
|
+
const selectedLocale = locale ?? (locales.includes("en") ? "en" : locales[0]);
|
|
19
|
+
if (!selectedLocale)
|
|
20
|
+
throw new Error("No validation message locale has been defined.");
|
|
21
|
+
const messageMap = messages[selectedLocale];
|
|
22
|
+
if (!messageMap)
|
|
23
|
+
throw new Error(`Validation messages for locale "${selectedLocale}" are not defined.`);
|
|
24
|
+
return messageMap;
|
|
25
|
+
};
|
|
26
|
+
const isPlainMessageMap = (value) => {
|
|
27
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const values = Object.values(value);
|
|
31
|
+
return (values.length > 0 && values.every((message) => typeof message === "string"));
|
|
32
|
+
};
|
|
33
|
+
const isLocaleMessageMap = (value) => {
|
|
34
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const localeMaps = Object.values(value);
|
|
38
|
+
return (localeMaps.length > 0 &&
|
|
39
|
+
localeMaps.every((messageMap) => isPlainMessageMap(messageMap)));
|
|
40
|
+
};
|
|
41
|
+
const getLocaleMessageMapError = (value) => {
|
|
42
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
43
|
+
return undefined;
|
|
44
|
+
for (const [locale, messageMap] of Object.entries(value)) {
|
|
45
|
+
if (typeof messageMap !== "object" ||
|
|
46
|
+
messageMap === null ||
|
|
47
|
+
Array.isArray(messageMap))
|
|
48
|
+
return `Validation messages for locale "${locale}" must be a message map.`;
|
|
49
|
+
for (const [code, message] of Object.entries(messageMap)) {
|
|
50
|
+
if (typeof message !== "string")
|
|
51
|
+
return `Validation message for locale "${locale}" and code "${code}" must be a string. Received ${typeof message}.`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
};
|
|
56
|
+
const resolveErrors = (errors, messageMap) => {
|
|
57
|
+
const resolved = {};
|
|
58
|
+
for (const key of Object.keys(errors)) {
|
|
59
|
+
const value = errors[key];
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
resolved[key] = messageMap[value] ?? value;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
resolved[key] = value.map((item) => resolveErrors(item, messageMap));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (value !== undefined)
|
|
69
|
+
resolved[key] = resolveErrors(value, messageMap);
|
|
70
|
+
}
|
|
71
|
+
return resolved;
|
|
72
|
+
};
|
|
73
|
+
export const resolveValidationMessages = (errors, messages, locale) => {
|
|
74
|
+
const messageMap = getMessageMap(messages, locale);
|
|
75
|
+
return resolveErrors(errors, messageMap);
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zod-validate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable validation utilities built on top of Zod for frontend and backend applications.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"dev:test": "tsx test.ts"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"zod",
|
|
18
|
+
"validation",
|
|
19
|
+
"typescript",
|
|
20
|
+
"frontend",
|
|
21
|
+
"backend"
|
|
22
|
+
],
|
|
23
|
+
"author": "Bibek Bhattarai",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"zod": "^4.6.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"zod": "^4.6.2",
|
|
30
|
+
"tsx": "^4.23.13",
|
|
31
|
+
"typescript": "^7.0.2",
|
|
32
|
+
"vitest": "^5.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|