@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 +59 -5
- package/benchmark/benchmark.ts +585 -0
- package/dist/index.d.mts +17 -7
- package/dist/index.d.ts +17 -7
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +13 -7
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
|
|
29
|
-
| Yup | ~12
|
|
30
|
-
| Zod
|
|
31
|
-
|
|
|
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');
|
|
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=
|
|
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=
|
|
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.
|
|
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.
|
|
66
|
-
"@commitlint/config-conventional": "^19.8.
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
68
|
-
"@typescript-eslint/parser": "^8.
|
|
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
|
-
"
|
|
76
|
+
"joi": "^17.13.3",
|
|
77
|
+
"lint-staged": "^16.1.0",
|
|
75
78
|
"prettier": "^3.5.3",
|
|
76
|
-
"
|
|
79
|
+
"superstruct": "^2.0.2",
|
|
80
|
+
"tsup": "^8.5.0",
|
|
81
|
+
"yup": "^1.6.1",
|
|
82
|
+
"zod": "^3.25.42"
|
|
77
83
|
}
|
|
78
84
|
}
|