@esmj/schema 0.1.1 → 0.2.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/README.md CHANGED
@@ -25,10 +25,47 @@ When choosing a schema validation library, bundle size can be an important facto
25
25
  | Library | Bundle Size (minified + gzipped) |
26
26
  |------------------|---------------------------------|
27
27
  | `@esmj/schema` | ~1 KB |
28
- | Superstruct | ~3,2 KB |
29
- | Yup | ~12,2 KB |
30
- | Zod | ~14 KB |
31
- | Joi | ~40 KB |
28
+ | Superstruct | ~3.2 KB |
29
+ | Yup | ~12.2 KB |
30
+ | Zod@3 | ~13 KB |
31
+ | @zod/mini | ~20,5 KB |
32
+ | Joi | ~40,4 KB |
33
+ | Zod@4 | ~40,8 KB |
34
+
35
+ ### Performance Comparison
36
+
37
+ #### Schema Creation Performance
38
+
39
+ | Library | 1 Iteration | 1,000 Iterations | 1,000,000 Iterations |
40
+ |-----------------|-------------|------------------|----------------------|
41
+ | `@esmj/schema` | 0.02 ms | 4.93 ms | 399.62 ms |
42
+ | zod@3 | 0.08 ms | 9.68 ms | 8.53 s |
43
+ | @zod/mini | 0.22 ms | 39.77 ms | 34.51 s |
44
+ | Yup | 0.54 ms | 14.03 ms | 12.34 s |
45
+ | Superstruct | 0.13 ms | 1.04 ms | 487.94 ms |
46
+ | Joi | 0.62 ms | 31.60 ms | 23.06 s |
47
+
48
+ #### Parsing Performance
49
+
50
+ | Library | 1 Iteration | 1,000 Iterations | 1,000,000 Iterations |
51
+ |-----------------|-------------|------------------|----------------------|
52
+ | `@esmj/schema` | 0.05 ms | 0.46 ms | 267.93 ms |
53
+ | zod@3 | 0.14 ms | 1.44 ms | 897.89 ms |
54
+ | @zod/mini | 0.23 ms | 0.42 ms | 199.08 ms |
55
+ | Yup | 0.30 ms | 9.49 ms | 8.69 s |
56
+ | Superstruct | 0.08 ms | 4.18 ms | 3.71 s |
57
+ | Joi | 0.33 ms | 3.35 ms | 2.69 s |
58
+
59
+ #### Error Handling Performance
60
+
61
+ | Library | 1 Iteration | 1,000 Iterations | 1,000,000 Iterations |
62
+ |-----------------|-------------|------------------|----------------------|
63
+ | `@esmj/schema` | 0.03 ms | 0.59 ms | 365.32 ms |
64
+ | zod3 | 0.05 ms | 2.09 ms | 1.26 s |
65
+ | @zod/mini | 0.07 ms | 0.99 ms | 545.12 ms |
66
+ | Yup | 0.27 ms | 19.28 ms | 18.87 s |
67
+ | Superstruct | 0.04 ms | 8.62 ms | 6.24 s |
68
+ | Joi | 0.15 ms | 4.13 ms | 2.57 s |
32
69
 
33
70
  ## Usage
34
71
 
@@ -180,9 +217,26 @@ const result = stringSchema.parse('hello');
180
217
  Safely parses the given value according to the schema, returning a success or error result.
181
218
 
182
219
  ```typescript
183
- const result = stringSchema.safeParse('hello'); // { success: true, data: 'hello' }
220
+ const result = stringSchema.safeParse('hello');
221
+ // { success: true, data: 'hello' }
222
+
223
+ const errorResult = stringSchema.safeParse(123);
224
+ // { success: false, error: { message: 'The value "123" must be type of string but is type of "number".' } }
184
225
  ```
185
226
 
227
+ **Note:** The `error` returned by `safeParse` is not a native `Error` instance. Instead, it is a plain object with the following structure:
228
+
229
+ ```typescript
230
+ type ErrorStructure = {
231
+ message: string;
232
+ cause?: {
233
+ key?: string;
234
+ };
235
+ };
236
+ ```
237
+
238
+ This allows for easier serialization and debugging but may require additional handling if you expect a native `Error` instance.
239
+
186
240
  #### `optional()`
187
241
 
188
242
  Makes the schema optional.
@@ -0,0 +1,585 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { z as zodMini } from '@zod/mini'; // Import Zod Mini
3
+ import Joi from 'joi'; // Import Joi
4
+ import superStruct from 'superstruct'; // Import Superstruct
5
+ import * as yup from 'yup'; // Import Yup
6
+ import { z } from 'zod'; // Import Zod
7
+ import { s } from '../src/index.ts'; // Import @esmj/schema
8
+
9
+ function formatTime(time: number) {
10
+ // Convert time to milliseconds
11
+ if (time < 1000) {
12
+ return `${time.toFixed(2)} ms`;
13
+ }
14
+ // Convert to seconds
15
+ const seconds = time / 1000;
16
+ return `${seconds.toFixed(2)} s`;
17
+ }
18
+
19
+ function benchmark(label: string, fn: () => void) {
20
+ const REPEAT = 10;
21
+ let sum = 0;
22
+ let min = Number.POSITIVE_INFINITY;
23
+ let max = Number.NEGATIVE_INFINITY;
24
+ for (let i = 0; i < REPEAT; i++) {
25
+ const start = performance.now();
26
+ fn();
27
+ const end = performance.now();
28
+
29
+ const time = end - start;
30
+
31
+ min = Math.min(min, time);
32
+ max = Math.max(max, time);
33
+ sum += time;
34
+ }
35
+
36
+ const average = sum / REPEAT;
37
+
38
+ console.log(
39
+ `${label} took an average of ${formatTime(average)} over ${REPEAT} iterations. Min: ${formatTime(min)}, Max: ${formatTime(max)}`,
40
+ );
41
+
42
+ return formatTime(average);
43
+ }
44
+
45
+ function scenarioParseSchema(testData) {
46
+ // Define a schema using @esmj/schema
47
+ const esmjSchema = s.object({
48
+ username: s.string(),
49
+ password: s.string(),
50
+ age: s.number().optional(),
51
+ address: s.object({
52
+ street: s.string().nullish(),
53
+ city: s.string().nullable(),
54
+ date: s.date().optional(), // Optional date field
55
+ }),
56
+ tags: s.array(s.string()),
57
+ });
58
+
59
+ // Define a schema using Zod
60
+ const zodSchema = z.object({
61
+ username: z.string(),
62
+ password: z.string(),
63
+ age: z.number().optional(),
64
+ address: z.object({
65
+ street: z.string().nullish(),
66
+ city: z.string().nullable(),
67
+ date: z.date().optional(), // Optional date field
68
+ }),
69
+ tags: z.array(z.string()),
70
+ });
71
+
72
+ // Define a schema using Zod Mini
73
+ const zodMiniSchema = zodMini.object({
74
+ username: zodMini.string(),
75
+ password: zodMini.string(),
76
+ age: zodMini.optional(zodMini.number()),
77
+ address: zodMini.object({
78
+ street: zodMini.nullish(zodMini.string()),
79
+ city: zodMini.nullable(zodMini.string()),
80
+ date: zodMini.optional(zodMini.date()), // Optional date field
81
+ }),
82
+ tags: zodMini.array(zodMini.string()),
83
+ });
84
+
85
+ // Define a schema using Yup
86
+ const yupSchema = yup.object({
87
+ username: yup.string().required(),
88
+ password: yup.string().required(),
89
+ age: yup.number().optional(),
90
+ address: yup.object({
91
+ street: yup.string().nullable(),
92
+ city: yup.string().nullable(),
93
+ date: yup.date().optional(), // Optional date field
94
+ }),
95
+ tags: yup.array().of(yup.string().required()).required(),
96
+ });
97
+
98
+ // Define a schema using Superstruct
99
+ const superStructSchema = superStruct.object({
100
+ username: superStruct.string(),
101
+ password: superStruct.string(),
102
+ age: superStruct.number(),
103
+ address: superStruct.object({
104
+ street: superStruct.optional(superStruct.string()),
105
+ city: superStruct.optional(superStruct.string()),
106
+ date: superStruct.optional(superStruct.date()), // Optional date field
107
+ }),
108
+ tags: superStruct.array(superStruct.string()),
109
+ });
110
+
111
+ // Define a schema using Joi
112
+ const joiSchema = Joi.object({
113
+ username: Joi.string().required(),
114
+ password: Joi.string().required(),
115
+ age: Joi.number().required(),
116
+ address: Joi.object({
117
+ street: Joi.string().optional(), // Allow null for street
118
+ city: Joi.string().optional(),
119
+ date: Joi.date().optional(), // Optional date field
120
+ }),
121
+ tags: Joi.array().items(Joi.string().required()).required(),
122
+ });
123
+
124
+ const result = {} as Record<string, object>;
125
+
126
+ const esmjSchemaResult = {};
127
+ esmjSchemaResult['1'] = benchmark('@esmj/schema 1', () => {
128
+ for (let i = 0; i < 1; i++) {
129
+ esmjSchema.safeParse(testData);
130
+ }
131
+ });
132
+
133
+ esmjSchemaResult['1_000'] = benchmark('@esmj/schema 1_000', () => {
134
+ for (let i = 0; i < 1_000; i++) {
135
+ esmjSchema.safeParse(testData);
136
+ }
137
+ });
138
+
139
+ esmjSchemaResult['1_000_000'] = benchmark('@esmj/schema 1_000_000', () => {
140
+ for (let i = 0; i < 1_000_000; i++) {
141
+ esmjSchema.safeParse(testData);
142
+ }
143
+ });
144
+ result.esmjSchema = esmjSchemaResult;
145
+
146
+ const zod = {};
147
+ zod['1'] = benchmark('zod 1', () => {
148
+ for (let i = 0; i < 1; i++) {
149
+ zodSchema.safeParse(testData);
150
+ }
151
+ });
152
+
153
+ zod['1_000'] = benchmark('zod 1_000', () => {
154
+ for (let i = 0; i < 1_000; i++) {
155
+ zodSchema.safeParse(testData);
156
+ }
157
+ });
158
+
159
+ zod['1_000_000'] = benchmark('zod 1_000_000', () => {
160
+ for (let i = 0; i < 1_000_000; i++) {
161
+ zodSchema.safeParse(testData);
162
+ }
163
+ });
164
+ result.zod = zod;
165
+
166
+ const zodMiniResult = {};
167
+ zodMiniResult['1'] = benchmark('@zod/mini 1', () => {
168
+ for (let i = 0; i < 1; i++) {
169
+ zodMiniSchema.safeParse(testData);
170
+ }
171
+ });
172
+
173
+ zodMiniResult['1_000'] = benchmark('@zod/mini 1_000', () => {
174
+ for (let i = 0; i < 1_000; i++) {
175
+ zodMiniSchema.safeParse(testData);
176
+ }
177
+ });
178
+
179
+ zodMiniResult['1_000_000'] = benchmark('@zod/mini 1_000_000', () => {
180
+ for (let i = 0; i < 1_000_000; i++) {
181
+ zodMiniSchema.safeParse(testData);
182
+ }
183
+ });
184
+ result.zodMini = zodMiniResult;
185
+
186
+ const yupResult = {};
187
+ yupResult['1'] = benchmark('yup 1', () => {
188
+ for (let i = 0; i < 1; i++) {
189
+ yupSchema.isValidSync(testData);
190
+ }
191
+ });
192
+
193
+ yupResult['1_000'] = benchmark('yup 1_000', () => {
194
+ for (let i = 0; i < 1_000; i++) {
195
+ yupSchema.isValidSync(testData);
196
+ }
197
+ });
198
+
199
+ yupResult['1_000_000'] = benchmark('yup 1_000_000', () => {
200
+ for (let i = 0; i < 1_000_000; i++) {
201
+ yupSchema.isValidSync(testData);
202
+ }
203
+ });
204
+ result.yup = yupResult;
205
+
206
+ const superStructResult = {};
207
+ superStructResult['1'] = benchmark('superStruct 1', () => {
208
+ for (let i = 0; i < 1; i++) {
209
+ superStruct.is(testData, superStructSchema);
210
+ }
211
+ });
212
+
213
+ superStructResult['1_000'] = benchmark('superStruct 1_000', () => {
214
+ for (let i = 0; i < 1_000; i++) {
215
+ superStruct.is(testData, superStructSchema);
216
+ }
217
+ });
218
+
219
+ superStructResult['1_000_000'] = benchmark('superStruct 1_000_000', () => {
220
+ for (let i = 0; i < 1_000_000; i++) {
221
+ superStruct.is(testData, superStructSchema);
222
+ }
223
+ });
224
+ result.superStruct = superStructResult;
225
+
226
+ const joiResult = {};
227
+ joiResult['1'] = benchmark('joi 1', () => {
228
+ for (let i = 0; i < 1; i++) {
229
+ try {
230
+ joiSchema.validate(testData);
231
+ } catch (e) {}
232
+ }
233
+ });
234
+
235
+ joiResult['1_000'] = benchmark('joi 1_000', () => {
236
+ for (let i = 0; i < 1_000; i++) {
237
+ try {
238
+ joiSchema.validate(testData);
239
+ } catch (e) {}
240
+ }
241
+ });
242
+
243
+ joiResult['1_000_000'] = benchmark('joi 1_000_000', () => {
244
+ for (let i = 0; i < 1_000_000; i++) {
245
+ try {
246
+ joiSchema.validate(testData);
247
+ } catch (e) {}
248
+ }
249
+ });
250
+ result.joi = joiResult;
251
+
252
+ return result;
253
+ }
254
+
255
+ function scenarioCreatingSchema() {
256
+ const result = {} as Record<string, object>;
257
+ const esmjSchemaResult = {};
258
+ esmjSchemaResult['1'] = benchmark('@esmj/schema 1', () => {
259
+ for (let i = 0; i < 1; i++) {
260
+ const esmjSchema = s.object({
261
+ username: s.string(),
262
+ password: s.string(),
263
+ age: s.number().optional(),
264
+ address: s.object({
265
+ street: s.string().nullish(),
266
+ city: s.string().nullable(),
267
+ date: s.date().optional(), // Optional date field
268
+ }),
269
+ tags: s.array(s.string()),
270
+ });
271
+ }
272
+ });
273
+
274
+ esmjSchemaResult['1_000'] = benchmark('@esmj/schema 1_000', () => {
275
+ for (let i = 0; i < 1_000; i++) {
276
+ const esmjSchema = s.object({
277
+ username: s.string(),
278
+ password: s.string(),
279
+ age: s.number().optional(),
280
+ address: s.object({
281
+ street: s.string().nullish(),
282
+ city: s.string().nullable(),
283
+ date: s.date().optional(), // Optional date field
284
+ }),
285
+ tags: s.array(s.string()),
286
+ });
287
+ }
288
+ });
289
+
290
+ esmjSchemaResult['1_000_000'] = benchmark('@esmj/schema 1_000_000', () => {
291
+ for (let i = 0; i < 1_000_000; i++) {
292
+ const esmjSchema = s.object({
293
+ username: s.string(),
294
+ password: s.string(),
295
+ age: s.number().optional(),
296
+ address: s.object({
297
+ street: s.string().nullish(),
298
+ city: s.string().nullable(),
299
+ date: s.date().optional(), // Optional date field
300
+ }),
301
+ tags: s.array(s.string()),
302
+ });
303
+ }
304
+ });
305
+ result.esmjSchema = esmjSchemaResult;
306
+
307
+ const zod = {};
308
+ zod['1'] = benchmark('zod 1', () => {
309
+ for (let i = 0; i < 1; i++) {
310
+ const zodSchema = z.object({
311
+ username: z.string(),
312
+ password: z.string(),
313
+ age: z.number().optional(),
314
+ address: z.object({
315
+ street: z.string().nullish(),
316
+ city: z.string().nullable(),
317
+ date: z.date().optional(), // Optional date field
318
+ }),
319
+ tags: z.array(z.string()),
320
+ });
321
+ }
322
+ });
323
+
324
+ zod['1_000'] = benchmark('zod 1_000', () => {
325
+ for (let i = 0; i < 1_000; i++) {
326
+ const zodSchema = z.object({
327
+ username: z.string(),
328
+ password: z.string(),
329
+ age: z.number().optional(),
330
+ address: z.object({
331
+ street: z.string().nullish(),
332
+ city: z.string().nullable(),
333
+ date: z.date().optional(), // Optional date field
334
+ }),
335
+ tags: z.array(z.string()),
336
+ });
337
+ }
338
+ });
339
+
340
+ zod['1_000_000'] = benchmark('zod 1_000_000', () => {
341
+ for (let i = 0; i < 1_000_000; i++) {
342
+ const zodSchema = z.object({
343
+ username: z.string(),
344
+ password: z.string(),
345
+ age: z.number().optional(),
346
+ address: z.object({
347
+ street: z.string().nullish(),
348
+ city: z.string().nullable(),
349
+ date: z.date().optional(), // Optional date field
350
+ }),
351
+ tags: z.array(z.string()),
352
+ });
353
+ }
354
+ });
355
+ result.zod = zod;
356
+
357
+ const zodMiniResult = {};
358
+ zodMiniResult['1'] = benchmark('@zod/mini 1', () => {
359
+ for (let i = 0; i < 1; i++) {
360
+ const zodMiniSchema = zodMini.object({
361
+ username: zodMini.string(),
362
+ password: zodMini.string(),
363
+ age: zodMini.optional(zodMini.number()),
364
+ address: zodMini.object({
365
+ street: zodMini.nullish(zodMini.string()),
366
+ city: zodMini.nullable(zodMini.string()),
367
+ date: zodMini.optional(zodMini.date()), // Optional date field
368
+ }),
369
+ tags: zodMini.array(zodMini.string()),
370
+ });
371
+ }
372
+ });
373
+
374
+ zodMiniResult['1_000'] = benchmark('@zod/mini 1_000', () => {
375
+ for (let i = 0; i < 1_000; i++) {
376
+ const zodMiniSchema = zodMini.object({
377
+ username: zodMini.string(),
378
+ password: zodMini.string(),
379
+ age: zodMini.optional(zodMini.number()),
380
+ address: zodMini.object({
381
+ street: zodMini.nullish(zodMini.string()),
382
+ city: zodMini.nullable(zodMini.string()),
383
+ date: zodMini.optional(zodMini.date()), // Optional date field
384
+ }),
385
+ tags: zodMini.array(zodMini.string()),
386
+ });
387
+ }
388
+ });
389
+
390
+ zodMiniResult['1_000_000'] = benchmark('@zod/mini 1_000_000', () => {
391
+ for (let i = 0; i < 1_000_000; i++) {
392
+ const zodMiniSchema = zodMini.object({
393
+ username: zodMini.string(),
394
+ password: zodMini.string(),
395
+ age: zodMini.optional(zodMini.number()),
396
+ address: zodMini.object({
397
+ street: zodMini.nullish(zodMini.string()),
398
+ city: zodMini.nullable(zodMini.string()),
399
+ date: zodMini.optional(zodMini.date()), // Optional date field
400
+ }),
401
+ tags: zodMini.array(zodMini.string()),
402
+ });
403
+ }
404
+ });
405
+ result.zodMini = zodMiniResult;
406
+
407
+ const yupResult = {};
408
+ yupResult['1'] = benchmark('yup 1', () => {
409
+ for (let i = 0; i < 1; i++) {
410
+ const yupSchema = yup.object({
411
+ username: yup.string().required(),
412
+ password: yup.string().required(),
413
+ age: yup.number().optional(),
414
+ address: yup.object({
415
+ street: yup.string().nullable(),
416
+ city: yup.string().nullable(),
417
+ date: yup.date().optional(), // Optional date field
418
+ }),
419
+ tags: yup.array().of(yup.string().required()).required(),
420
+ });
421
+ }
422
+ });
423
+
424
+ yupResult['1_000'] = benchmark('yup 1_000', () => {
425
+ for (let i = 0; i < 1_000; i++) {
426
+ const yupSchema = yup.object({
427
+ username: yup.string().required(),
428
+ password: yup.string().required(),
429
+ age: yup.number().optional(),
430
+ address: yup.object({
431
+ street: yup.string().nullable(),
432
+ city: yup.string().nullable(),
433
+ date: yup.date().optional(), // Optional date field
434
+ }),
435
+ tags: yup.array().of(yup.string().required()).required(),
436
+ });
437
+ }
438
+ });
439
+
440
+ yupResult['1_000_000'] = benchmark('yup 1_000_000', () => {
441
+ for (let i = 0; i < 1_000_000; i++) {
442
+ const yupSchema = yup.object({
443
+ username: yup.string().required(),
444
+ password: yup.string().required(),
445
+ age: yup.number().optional(),
446
+ address: yup.object({
447
+ street: yup.string().nullable(),
448
+ city: yup.string().nullable(),
449
+ date: yup.date().optional(), // Optional date field
450
+ }),
451
+ tags: yup.array().of(yup.string().required()).required(),
452
+ });
453
+ }
454
+ });
455
+ result.yup = yupResult;
456
+
457
+ const superStructResult = {};
458
+ superStructResult['1'] = benchmark('superStruct 1', () => {
459
+ for (let i = 0; i < 1; i++) {
460
+ const superStructSchema = superStruct.object({
461
+ username: superStruct.string(),
462
+ password: superStruct.string(),
463
+ age: superStruct.number(),
464
+ address: superStruct.object({
465
+ street: superStruct.optional(superStruct.string()),
466
+ city: superStruct.optional(superStruct.string()),
467
+ date: superStruct.optional(superStruct.date()), // Optional date field
468
+ }),
469
+ tags: superStruct.array(superStruct.string()),
470
+ });
471
+ }
472
+ });
473
+
474
+ superStructResult['1_000'] = benchmark('superStruct 1_000', () => {
475
+ for (let i = 0; i < 1_000; i++) {
476
+ const superStructSchema = superStruct.object({
477
+ username: superStruct.string(),
478
+ password: superStruct.string(),
479
+ age: superStruct.number(),
480
+ address: superStruct.object({
481
+ street: superStruct.optional(superStruct.string()),
482
+ city: superStruct.optional(superStruct.string()),
483
+ date: superStruct.optional(superStruct.date()), // Optional date field
484
+ }),
485
+ tags: superStruct.array(superStruct.string()),
486
+ });
487
+ }
488
+ });
489
+
490
+ superStructResult['1_000_000'] = benchmark('superStruct 1_000_000', () => {
491
+ for (let i = 0; i < 1_000_000; i++) {
492
+ const superStructSchema = superStruct.object({
493
+ username: superStruct.string(),
494
+ password: superStruct.string(),
495
+ age: superStruct.number(),
496
+ address: superStruct.object({
497
+ street: superStruct.optional(superStruct.string()),
498
+ city: superStruct.optional(superStruct.string()),
499
+ date: superStruct.optional(superStruct.date()), // Optional date field
500
+ }),
501
+ tags: superStruct.array(superStruct.string()),
502
+ });
503
+ }
504
+ });
505
+ result.superStruct = superStructResult;
506
+
507
+ const joiResult = {};
508
+ joiResult['1'] = benchmark('joi 1', () => {
509
+ for (let i = 0; i < 1; i++) {
510
+ const joiSchema = Joi.object({
511
+ username: Joi.string().required(),
512
+ password: Joi.string().required(),
513
+ age: Joi.number().required(),
514
+ address: Joi.object({
515
+ street: Joi.string().optional(), // Allow null for street
516
+ city: Joi.string().optional(),
517
+ date: Joi.date().optional(), // Optional date field
518
+ }),
519
+ tags: Joi.array().items(Joi.string().required()).required(),
520
+ });
521
+ }
522
+ });
523
+
524
+ joiResult['1_000'] = benchmark('joi 1_000', () => {
525
+ for (let i = 0; i < 1_000; i++) {
526
+ const joiSchema = Joi.object({
527
+ username: Joi.string().required(),
528
+ password: Joi.string().required(),
529
+ age: Joi.number().required(),
530
+ address: Joi.object({
531
+ street: Joi.string().optional(), // Allow null for street
532
+ city: Joi.string().optional(),
533
+ date: Joi.date().optional(), // Optional date field
534
+ }),
535
+ tags: Joi.array().items(Joi.string().required()).required(),
536
+ });
537
+ }
538
+ });
539
+
540
+ joiResult['1_000_000'] = benchmark('joi 1_000_000', () => {
541
+ for (let i = 0; i < 1_000_000; i++) {
542
+ const joiSchema = Joi.object({
543
+ username: Joi.string().required(),
544
+ password: Joi.string().required(),
545
+ age: Joi.number().required(),
546
+ address: Joi.object({
547
+ street: Joi.string().optional(), // Allow null for street
548
+ city: Joi.string().optional(),
549
+ date: Joi.date().optional(), // Optional date field
550
+ }),
551
+ tags: Joi.array().items(Joi.string().required()).required(),
552
+ });
553
+ }
554
+ });
555
+ result.joi = joiResult;
556
+
557
+ return result;
558
+ }
559
+
560
+ const testDataGood = {
561
+ username: 'john_doe',
562
+ password: 'securepassword',
563
+ age: 30,
564
+ address: {
565
+ street: '123 Main St',
566
+ city: 'New York',
567
+ date: new Date(),
568
+ },
569
+ tags: ['developer', 'typescript'],
570
+ };
571
+
572
+ const testDataBad = {
573
+ username: 'john_doe',
574
+ password: 'securepassword',
575
+ age: 30,
576
+ address: {
577
+ street: '123 Main St',
578
+ city: 'New York',
579
+ date: new Date(),
580
+ },
581
+ };
582
+
583
+ console.table(scenarioCreatingSchema());
584
+ console.table(scenarioParseSchema(testDataGood));
585
+ console.table(scenarioParseSchema(testDataBad));
package/dist/index.d.mts CHANGED
@@ -1,12 +1,22 @@
1
+ type ErrorStructure = {
2
+ message: string;
3
+ cause?: {
4
+ key?: string;
5
+ };
6
+ };
7
+ type Valid<Output> = {
8
+ success: true;
9
+ data: Output;
10
+ };
11
+ type Invalid = {
12
+ success: false;
13
+ error: ErrorStructure;
14
+ };
15
+ type InternalParseOutput<Output> = Valid<Output> | Invalid;
1
16
  interface SchemaInterface<Input, Output> {
17
+ _parse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
2
18
  parse(value: Input | Partial<Input> | unknown): Output;
3
- safeParse(value: Input | Partial<Input> | unknown): {
4
- success: true;
5
- data: Output;
6
- } | {
7
- success: false;
8
- error: Error;
9
- };
19
+ safeParse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
10
20
  optional(): SchemaInterface<Input, Partial<Output> | undefined>;
11
21
  transform<NewOutput>(callback: (value: Input) => NewOutput): SchemaInterface<Input, NewOutput>;
12
22
  nullable(): SchemaInterface<Input, Output | null>;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,22 @@
1
+ type ErrorStructure = {
2
+ message: string;
3
+ cause?: {
4
+ key?: string;
5
+ };
6
+ };
7
+ type Valid<Output> = {
8
+ success: true;
9
+ data: Output;
10
+ };
11
+ type Invalid = {
12
+ success: false;
13
+ error: ErrorStructure;
14
+ };
15
+ type InternalParseOutput<Output> = Valid<Output> | Invalid;
1
16
  interface SchemaInterface<Input, Output> {
17
+ _parse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
2
18
  parse(value: Input | Partial<Input> | unknown): Output;
3
- safeParse(value: Input | Partial<Input> | unknown): {
4
- success: true;
5
- data: Output;
6
- } | {
7
- success: false;
8
- error: Error;
9
- };
19
+ safeParse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
10
20
  optional(): SchemaInterface<Input, Partial<Output> | undefined>;
11
21
  transform<NewOutput>(callback: (value: Input) => NewOutput): SchemaInterface<Input, NewOutput>;
12
22
  nullable(): SchemaInterface<Input, Output | null>;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var y={object(n){let u=i(c=>typeof c=="object"&&c!==null&&!Array.isArray(c),{type:"object"});return s(u,"parse",(c,...o)=>{let e=c(...o);return n&&typeof e=="object"?Object.keys(n).reduce((r,t)=>{if(typeof n[t]?.parse=="function")try{r[t]=n[t].parse(e[t]);}catch(p){throw t=p.cause?.key?`${t}.${p.cause.key}`:t,new Error(`Error parsing key "${t}": ${p.message}`,{cause:{key:t}})}return r},{}):e}),u},string(){return i(a=>typeof a=="string",{type:"string"})},number(){return i(a=>typeof a=="number",{type:"number"})},boolean(){return i(a=>a===true||a===false,{type:"boolean"})},date(){return i(a=>a instanceof Date&&!Number.isNaN(a.getTime()),{type:"date"})},enum(n){let a=e=>n.includes(e),u=e=>`Invalid ${c} value. Expected ${n.map(r=>`"${r}"`).join(" | ")}, received "${e}".`,c="enum";return i(a,{type:c,message:u})},array(n){let u=i(c=>Array.isArray(c),{type:"array"});return s(u,"parse",(c,o)=>(o=c(o),n&&Array.isArray(o)?o.map((e,r)=>{try{return n.parse(e)}catch(t){let p=t.cause?.key?`${r}.${t.cause.key}`:r;throw new Error(`Error parsing index "${r}": ${t.message}`,{cause:{key:p}})}}):o)),u},any(){return i(()=>true)},preprocess(n,a){return s(a,"parse",(u,c)=>(c=n(c),u(c))),a}};function h(n){return a=>`The value "${a}" must be type of ${n} but is type of "${typeof a}".`}function s(n,a,u){let c=n[a];n[a]=(...o)=>u(c,...o);}function i(n,{type:a="any",message:u}={}){u=u||h(a);let c={message:u,type:a},o={parse(e){if(!n(e))throw new Error(u(e));return e},safeParse(e){try{return {success:!0,data:this.parse(e)}}catch(r){return {success:false,error:r}}},transform(e){return s(this,"parse",(r,t)=>e(r(t))),this},optional(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return}}),this},nullable(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return null}}),this},nullish(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return r==null?r:null}}),this},default(e){return s(this,"parse",(r,t)=>(t===void 0&&(t=e,t=typeof e=="function"?e():e),t=r(t),t)),this},pipe(e){return s(this,"parse",(r,t)=>e.parse(r(t))),this},refine(e,{message:r}){return s(this,"parse",(t,p)=>{let f=t(p);if(!e(f))throw new Error(r);return f}),this}};return m.reduce((e,r)=>r(e,n,c)??e,o)}var m=[];function I(n){m.push(n);}exports.extend=I;exports.s=y;
1
+ 'use strict';var h=r=>typeof r=="string"||r instanceof String,I=r=>typeof r=="number"||r instanceof Number,l=r=>r===true||r===false,y=r=>r instanceof Date&&!Number.isNaN(r.getTime()),d=r=>Array.isArray(r),S=r=>typeof r=="object"&&r!==null&&!Array.isArray(r),g={object(r){let a=p(S,{type:"object"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n={};for(let t in r){let e=r[t]._parse(c.data[t]);if(e.success)n[t]=e.data;else {e=e;let s=e?.error?.cause?.key?`${t}.${e?.error?.cause?.key}`:t;return e.error.message=`Error parsing key "${s}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},string(){return p(h,{type:"string"})},number(){return p(I,{type:"number"})},boolean(){return p(l,{type:"boolean"})},date(){return p(y,{type:"date"})},enum(r){let a=n=>r.includes(n),u=n=>`Invalid ${o} value. Expected ${r.map(t=>`"${t}"`).join(" | ")}, received "${n}".`,o="enum";return p(a,{type:o,message:u})},array(r){let a=p(d,{type:"array"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n=[];for(let t=0;t<c.data.length;t++){let e=r._parse(c.data[t]);if(e.success)n.push(e.data);else {e=e;let s=e.error?.cause?.key?`${t}.${e.error.cause.key}`:`${t}`;return e.error.message=`Error parsing index "${t}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},any(){return p(()=>true)},preprocess(r,a){return i(a,"_parse",(u,o)=>(o=r(o),u(o))),a}};function b(r){return a=>`The value "${a}" must be type of ${r} but is type of "${typeof a}".`}function i(r,a,u){let o=r[a];r[a]=(...c)=>u(o,...c);}function p(r,{type:a="any",message:u}={}){u=u||b(a);let o={message:u,type:a},c={_parse(n){return r(n)?{success:true,data:n}:{success:false,error:{message:u(n)}}},parse(n){let t=c._parse(n);if(!t.success)throw t=t,new Error(t.error.message,{cause:t.error.cause});return t.data},safeParse(n){return c._parse(n)},transform(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success&&(s.data=n(s.data)),s}),this},optional(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=void 0,e.success=true),e}),this},nullable(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=null,e.success=true),e}),this},nullish(){return i(this,"_parse",(n,t)=>{let e=n(t);return !e.success&&t==null&&(e.success=true,e.data=t),e}),this},default(n){return i(this,"_parse",(t,e)=>(e===void 0&&(e=n,e=typeof n=="function"?n():n),t(e))),this},pipe(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success?n._parse(s.data):s}),this},refine(n,{message:t}){return i(this,"_parse",(e,s)=>{let m=e(s);return n(m.data)?m:{success:false,error:{message:t}}}),this}};return f.length>0?f.reduce((n,t)=>t(n,r,o)??n,c):c}var f=[];function T(r){f.push(r);}exports.extend=T;exports.s=g;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var y={object(n){let u=i(c=>typeof c=="object"&&c!==null&&!Array.isArray(c),{type:"object"});return s(u,"parse",(c,...o)=>{let e=c(...o);return n&&typeof e=="object"?Object.keys(n).reduce((r,t)=>{if(typeof n[t]?.parse=="function")try{r[t]=n[t].parse(e[t]);}catch(p){throw t=p.cause?.key?`${t}.${p.cause.key}`:t,new Error(`Error parsing key "${t}": ${p.message}`,{cause:{key:t}})}return r},{}):e}),u},string(){return i(a=>typeof a=="string",{type:"string"})},number(){return i(a=>typeof a=="number",{type:"number"})},boolean(){return i(a=>a===true||a===false,{type:"boolean"})},date(){return i(a=>a instanceof Date&&!Number.isNaN(a.getTime()),{type:"date"})},enum(n){let a=e=>n.includes(e),u=e=>`Invalid ${c} value. Expected ${n.map(r=>`"${r}"`).join(" | ")}, received "${e}".`,c="enum";return i(a,{type:c,message:u})},array(n){let u=i(c=>Array.isArray(c),{type:"array"});return s(u,"parse",(c,o)=>(o=c(o),n&&Array.isArray(o)?o.map((e,r)=>{try{return n.parse(e)}catch(t){let p=t.cause?.key?`${r}.${t.cause.key}`:r;throw new Error(`Error parsing index "${r}": ${t.message}`,{cause:{key:p}})}}):o)),u},any(){return i(()=>true)},preprocess(n,a){return s(a,"parse",(u,c)=>(c=n(c),u(c))),a}};function h(n){return a=>`The value "${a}" must be type of ${n} but is type of "${typeof a}".`}function s(n,a,u){let c=n[a];n[a]=(...o)=>u(c,...o);}function i(n,{type:a="any",message:u}={}){u=u||h(a);let c={message:u,type:a},o={parse(e){if(!n(e))throw new Error(u(e));return e},safeParse(e){try{return {success:!0,data:this.parse(e)}}catch(r){return {success:false,error:r}}},transform(e){return s(this,"parse",(r,t)=>e(r(t))),this},optional(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return}}),this},nullable(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return null}}),this},nullish(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return r==null?r:null}}),this},default(e){return s(this,"parse",(r,t)=>(t===void 0&&(t=e,t=typeof e=="function"?e():e),t=r(t),t)),this},pipe(e){return s(this,"parse",(r,t)=>e.parse(r(t))),this},refine(e,{message:r}){return s(this,"parse",(t,p)=>{let f=t(p);if(!e(f))throw new Error(r);return f}),this}};return m.reduce((e,r)=>r(e,n,c)??e,o)}var m=[];function I(n){m.push(n);}export{I as extend,y as s};
1
+ var h=r=>typeof r=="string"||r instanceof String,I=r=>typeof r=="number"||r instanceof Number,l=r=>r===true||r===false,y=r=>r instanceof Date&&!Number.isNaN(r.getTime()),d=r=>Array.isArray(r),S=r=>typeof r=="object"&&r!==null&&!Array.isArray(r),g={object(r){let a=p(S,{type:"object"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n={};for(let t in r){let e=r[t]._parse(c.data[t]);if(e.success)n[t]=e.data;else {e=e;let s=e?.error?.cause?.key?`${t}.${e?.error?.cause?.key}`:t;return e.error.message=`Error parsing key "${s}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},string(){return p(h,{type:"string"})},number(){return p(I,{type:"number"})},boolean(){return p(l,{type:"boolean"})},date(){return p(y,{type:"date"})},enum(r){let a=n=>r.includes(n),u=n=>`Invalid ${o} value. Expected ${r.map(t=>`"${t}"`).join(" | ")}, received "${n}".`,o="enum";return p(a,{type:o,message:u})},array(r){let a=p(d,{type:"array"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n=[];for(let t=0;t<c.data.length;t++){let e=r._parse(c.data[t]);if(e.success)n.push(e.data);else {e=e;let s=e.error?.cause?.key?`${t}.${e.error.cause.key}`:`${t}`;return e.error.message=`Error parsing index "${t}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},any(){return p(()=>true)},preprocess(r,a){return i(a,"_parse",(u,o)=>(o=r(o),u(o))),a}};function b(r){return a=>`The value "${a}" must be type of ${r} but is type of "${typeof a}".`}function i(r,a,u){let o=r[a];r[a]=(...c)=>u(o,...c);}function p(r,{type:a="any",message:u}={}){u=u||b(a);let o={message:u,type:a},c={_parse(n){return r(n)?{success:true,data:n}:{success:false,error:{message:u(n)}}},parse(n){let t=c._parse(n);if(!t.success)throw t=t,new Error(t.error.message,{cause:t.error.cause});return t.data},safeParse(n){return c._parse(n)},transform(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success&&(s.data=n(s.data)),s}),this},optional(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=void 0,e.success=true),e}),this},nullable(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=null,e.success=true),e}),this},nullish(){return i(this,"_parse",(n,t)=>{let e=n(t);return !e.success&&t==null&&(e.success=true,e.data=t),e}),this},default(n){return i(this,"_parse",(t,e)=>(e===void 0&&(e=n,e=typeof n=="function"?n():n),t(e))),this},pipe(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success?n._parse(s.data):s}),this},refine(n,{message:t}){return i(this,"_parse",(e,s)=>{let m=e(s);return n(m.data)?m:{success:false,error:{message:t}}}),this}};return f.length>0?f.reduce((n,t)=>t(n,r,o)??n,c):c}var f=[];function T(r){f.push(r);}export{T as extend,g as s};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esmj/schema",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Tiny extendable package for schema validation.",
5
5
  "keywords": [
6
6
  "schema",
@@ -22,6 +22,7 @@
22
22
  "scripts": {
23
23
  "lint": "biome check --no-errors-on-unmatched",
24
24
  "lint:fix": "npm run lint -- --fix --unsafe",
25
+ "benchmark": "node benchmark/index.ts",
25
26
  "dev": "node_modules/.bin/tsup --dts --watch --onSuccess 'node ./dist/index.mjs'",
26
27
  "test": "node --test --experimental-strip-types",
27
28
  "test:watch": "npm run test -- --watch",
@@ -62,17 +63,22 @@
62
63
  "homepage": "https://github.com/mjancarik/esmj-schema#readme",
63
64
  "devDependencies": {
64
65
  "@biomejs/biome": "1.9.4",
65
- "@commitlint/cli": "^19.8.0",
66
- "@commitlint/config-conventional": "^19.8.0",
67
- "@typescript-eslint/eslint-plugin": "^8.31.0",
68
- "@typescript-eslint/parser": "^8.31.0",
66
+ "@commitlint/cli": "^19.8.1",
67
+ "@commitlint/config-conventional": "^19.8.1",
68
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
69
+ "@typescript-eslint/parser": "^8.33.0",
70
+ "@zod/mini": "^4.0.0-beta.0",
69
71
  "commitizen": "^4.3.1",
70
72
  "conventional-changelog-cli": "^5.0.0",
71
73
  "cz-conventional-changelog": "^3.3.0",
72
74
  "git-cz": "^4.9.0",
73
75
  "husky": "^9.1.7",
74
- "lint-staged": "^15.5.1",
76
+ "joi": "^17.13.3",
77
+ "lint-staged": "^16.1.0",
75
78
  "prettier": "^3.5.3",
76
- "tsup": "^8.4.0"
79
+ "superstruct": "^2.0.2",
80
+ "tsup": "^8.5.0",
81
+ "yup": "^1.6.1",
82
+ "zod": "^3.25.42"
77
83
  }
78
84
  }