hybrid-validator 0.5.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrii Kotsiuba and contributors
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,213 @@
1
+
2
+ # JSON Schema Validator
3
+
4
+ High-performance JSON schema validator for Node.js.
5
+
6
+ Designed for:
7
+
8
+ - ⚡ maximum performance
9
+ - 🧠 smart compiled validators
10
+ - 🔧 extensibility
11
+ - 📦 minimal overhead
12
+
13
+ ---
14
+
15
+ # Why another validator?
16
+
17
+ Most validators focus on flexibility but sacrifice performance.
18
+
19
+ This project focuses on **raw validation speed** while still supporting:
20
+
21
+ - runtime validation
22
+ - compiled validators
23
+ - custom types
24
+ - custom validators
25
+
26
+ ---
27
+
28
+ # Installation
29
+
30
+ ```bash
31
+ npm install json-schema-validator
32
+ ````
33
+
34
+ ---
35
+
36
+ # Quick Example
37
+
38
+ ```js
39
+ import { Validator } from "json-schema-validator";
40
+
41
+ const validator = new Validator();
42
+
43
+ const schema = {
44
+ type: "object",
45
+ properties: {
46
+ name: { type: "string" },
47
+ age: { type: "number" }
48
+ }
49
+ };
50
+
51
+ const data = {
52
+ name: "John",
53
+ age: 30
54
+ };
55
+
56
+ const result = validator.validate({
57
+ schema,
58
+ data
59
+ });
60
+
61
+ console.log(result);
62
+ ```
63
+
64
+ ---
65
+
66
+ # Two Validation Modes
67
+
68
+ ## Runtime validation
69
+
70
+ Use when data is validated **only once**.
71
+
72
+ ```js
73
+ validator.validate({ schema, data });
74
+ ```
75
+
76
+ Advantages:
77
+
78
+ * no compilation cost
79
+ * fastest for single validation
80
+
81
+ ---
82
+
83
+ ## Compiled validation
84
+
85
+ Use when validating **large datasets**.
86
+
87
+ ```js
88
+ const validate = validator.compile(schema);
89
+
90
+ validate(data1);
91
+ validate(data2);
92
+ validate(data3);
93
+ ```
94
+
95
+ Advantages:
96
+
97
+ * schema compiled once
98
+ * extremely fast repeated validation
99
+
100
+ ---
101
+
102
+ # Performance
103
+
104
+ Benchmark executed using **tinybench**.
105
+
106
+ Libraries compared:
107
+
108
+ * Ajv
109
+ * Zod
110
+ * Yup
111
+ * Fastest Validator
112
+
113
+ Results:
114
+
115
+ | Validator | ops/sec | relative speed |
116
+ | ----------------- | ------------- | -------------- |
117
+ | **NativeEngine** | **1,186,514** | 🥇 fastest |
118
+ | CompiledValidator | 94,039 | 12× slower |
119
+ | Yup | 11,225 | 100× slower |
120
+ | Zod | 7,493 | 158× slower |
121
+ | FastestValidator | 3,796 | 312× slower |
122
+ | AJV (cold start) | 225 | 5270× slower |
123
+
124
+ ---
125
+
126
+ # Benchmark Chart
127
+
128
+ ```
129
+
130
+ NativeEngine █████████████████████████████████████████ 1,186,514
131
+ CompiledValidator ████ 94,039
132
+ Yup █ 11,225
133
+ Zod █ 7,493
134
+ FastestValidator ▌ 3,796
135
+ AJV cold ▏ 225
136
+
137
+ ```
138
+
139
+ ---
140
+
141
+ # When to use which mode
142
+
143
+ | Scenario | Method |
144
+ | -------------------------- | ------------- |
145
+ | single validation | `.validate()` |
146
+ | large dataset | `.compile()` |
147
+ | high performance pipelines | `.compile()` |
148
+
149
+ ---
150
+
151
+ # Cache
152
+
153
+ Compiled schemas are automatically cached using `WeakMap`.
154
+
155
+ ```js
156
+ const validator = new Validator();
157
+
158
+ const validate = validator.compile(schema);
159
+
160
+ // reused from cache
161
+ const validate2 = validator.compile(schema);
162
+ ```
163
+
164
+ Clear cache:
165
+
166
+ ```js
167
+ validator.clearCache();
168
+ ```
169
+
170
+ ---
171
+
172
+ # Options
173
+
174
+ ```js
175
+ const validator = new Validator({
176
+ recursive: true,
177
+ strict: true,
178
+ customTypes: {},
179
+ customValidators: []
180
+ });
181
+ ```
182
+
183
+ | option | description |
184
+ | ---------------- | -------------------------- |
185
+ | recursive | validate nested structures |
186
+ | strict | strict schema validation |
187
+ | customTypes | user defined types |
188
+ | customValidators | custom validators |
189
+
190
+ ---
191
+
192
+ # Architecture
193
+
194
+ Validator uses two internal engines:
195
+
196
+ | Engine | Description |
197
+ | ------------- | ------------------ |
198
+ | NativeEngine | runtime validation |
199
+ | CodeGenerator | compiled validator |
200
+
201
+ ---
202
+
203
+ # Roadmap
204
+
205
+ * more JSON Schema features
206
+ * async validation
207
+ * schema precompilation
208
+ * browser support
209
+
210
+ ---
211
+
212
+ # License
213
+
@@ -0,0 +1,304 @@
1
+ export class CodeGenerator {
2
+ constructor(options = {}) {
3
+ this.options = {
4
+ recursive: true,
5
+ strict: true,
6
+ customTypes: {},
7
+ customValidators: [],
8
+ ...options,
9
+ };
10
+ }
11
+
12
+ compile(schema) {
13
+ const { customTypes, customValidators, recursive } = this.options;
14
+
15
+ const code = [];
16
+ code.push(`const errors = [];`);
17
+
18
+ const gen = (schema, dataRef, pathExpr, depth = 0) => {
19
+ if (schema === true) return;
20
+
21
+ if (schema === false) {
22
+ code.push(`
23
+ errors.push({
24
+ path: ${pathExpr},
25
+ message: "Schema is false, value is not allowed"
26
+ });
27
+ `);
28
+ return;
29
+ }
30
+
31
+ if (!schema) return;
32
+
33
+ if (schema.type) {
34
+ const typeCheck = this.generateTypeCheck(schema.type, dataRef);
35
+ const typeLabel = Array.isArray(schema.type)
36
+ ? schema.type.join(', ')
37
+ : schema.type;
38
+
39
+ code.push(`
40
+ if (${typeCheck}) {
41
+ `);
42
+
43
+ if (schema.enum) {
44
+ const values = JSON.stringify(schema.enum);
45
+ code.push(`
46
+ if (!${values}.includes(${dataRef})) {
47
+ errors.push({
48
+ path: ${pathExpr},
49
+ message: "Value '" + ${dataRef} + "' is not one of: ${schema.enum.join(', ')}"
50
+ });
51
+ }
52
+ `);
53
+ }
54
+
55
+ if (schema.minimum !== undefined) {
56
+ code.push(`
57
+ if (${dataRef} < ${schema.minimum}) {
58
+ errors.push({
59
+ path: ${pathExpr},
60
+ message: "Value " + ${dataRef} + " < minimum ${schema.minimum}"
61
+ });
62
+ }
63
+ `);
64
+ }
65
+
66
+ if (schema.maximum !== undefined) {
67
+ code.push(`
68
+ if (${dataRef} > ${schema.maximum}) {
69
+ errors.push({
70
+ path: ${pathExpr},
71
+ message: "Value " + ${dataRef} + " > maximum ${schema.maximum}"
72
+ });
73
+ }
74
+ `);
75
+ }
76
+
77
+ if (schema.exclusiveMinimum !== undefined) {
78
+ code.push(`
79
+ if (${dataRef} <= ${schema.exclusiveMinimum}) {
80
+ errors.push({
81
+ path: ${pathExpr},
82
+ message: "Value " + ${dataRef} + " <= exclusiveMinimum ${schema.exclusiveMinimum}"
83
+ });
84
+ }
85
+ `);
86
+ }
87
+
88
+ if (schema.exclusiveMaximum !== undefined) {
89
+ code.push(`
90
+ if (${dataRef} >= ${schema.exclusiveMaximum}) {
91
+ errors.push({
92
+ path: ${pathExpr},
93
+ message: "Value " + ${dataRef} + " >= exclusiveMaximum ${schema.exclusiveMaximum}"
94
+ });
95
+ }
96
+ `);
97
+ }
98
+
99
+ if (schema.multipleOf !== undefined) {
100
+ code.push(`
101
+ if (${dataRef} % ${schema.multipleOf} !== 0) {
102
+ errors.push({
103
+ path: ${pathExpr},
104
+ message: "Value " + ${dataRef} + " not multipleOf ${schema.multipleOf}"
105
+ });
106
+ }
107
+ `);
108
+ }
109
+
110
+ if (recursive && schema.type === 'object') {
111
+ code.push(`
112
+ if (${dataRef} && typeof ${dataRef} === 'object' && !Array.isArray(${dataRef})) {
113
+ `);
114
+ if (schema.properties) {
115
+ for (const key in schema.properties) {
116
+ const prop = schema.properties[key];
117
+ const ref = `${dataRef}.${key}`;
118
+
119
+ const childPath =
120
+ pathExpr === '""' ? `"${key}"` : `${pathExpr} + "." + "${key}"`;
121
+
122
+ code.push(`
123
+ if (${dataRef}.${key} !== undefined) {
124
+ `);
125
+ gen(prop, ref, childPath, depth);
126
+ code.push(`}`);
127
+ }
128
+ }
129
+
130
+ if (schema.additionalProperties === false && schema.properties) {
131
+ const keys = JSON.stringify(Object.keys(schema.properties));
132
+
133
+ code.push(`
134
+ for (const k in ${dataRef}) {
135
+ if (!${keys}.includes(k)) {
136
+ errors.push({
137
+ path: k,
138
+ message: "Unexpected field '" + k + "'"
139
+ });
140
+ }
141
+ }
142
+ `);
143
+ }
144
+
145
+ if (typeof schema.additionalProperties === 'object') {
146
+ code.push(`
147
+ for (const k in ${dataRef}) {
148
+ `);
149
+ if (schema.properties) {
150
+ const keys = JSON.stringify(Object.keys(schema.properties));
151
+
152
+ code.push(`
153
+ if (!${keys}.includes(k)) {
154
+ `);
155
+
156
+ gen(
157
+ schema.additionalProperties,
158
+ `${dataRef}[k]`,
159
+ pathExpr === '""' ? `k` : `${pathExpr} + "." + k`,
160
+ depth,
161
+ );
162
+
163
+ code.push(`}`);
164
+ } else {
165
+ gen(
166
+ schema.additionalProperties,
167
+ `${dataRef}[k]`,
168
+ pathExpr === '""' ? `k` : `${pathExpr} + "." + k`,
169
+ depth,
170
+ );
171
+ }
172
+
173
+ code.push(`}`);
174
+ }
175
+
176
+ if (schema.patternProperties) {
177
+ for (const pattern in schema.patternProperties) {
178
+ const propSchema = schema.patternProperties[pattern];
179
+ const regexName = `regex_${pattern.replace(/\W/g, '_')}`;
180
+
181
+ code.push(`
182
+ const ${regexName} = new RegExp(${JSON.stringify(pattern)});
183
+ for (const k in ${dataRef}) {
184
+ if (${regexName}.test(k)) {
185
+ `);
186
+
187
+ gen(
188
+ propSchema,
189
+ `${dataRef}[k]`,
190
+ pathExpr === '""' ? `k` : `${pathExpr} + "." + k`,
191
+ depth,
192
+ );
193
+
194
+ code.push(`
195
+ }
196
+ }
197
+ `);
198
+ }
199
+ }
200
+
201
+ code.push(`
202
+ } // end if object
203
+ `);
204
+ }
205
+
206
+ if (recursive && schema.type === 'array' && schema.items) {
207
+ const loopVar = `i${depth}`;
208
+ code.push(`
209
+ if (Array.isArray(${dataRef})) {
210
+ for (let ${loopVar} = 0; ${loopVar} < ${dataRef}.length; ${loopVar}++) {
211
+ `);
212
+
213
+ gen(
214
+ schema.items,
215
+ `${dataRef}[${loopVar}]`,
216
+ pathExpr === '""'
217
+ ? `"[" + ${loopVar} + "]"`
218
+ : `${pathExpr} + "[" + ${loopVar} + "]"`,
219
+ depth + 1,
220
+ );
221
+
222
+ code.push(`
223
+ }
224
+ } // end if array
225
+ `);
226
+ }
227
+
228
+ code.push(`
229
+ } else {
230
+ const actual = Array.isArray(${dataRef}) ? "array" : typeof ${dataRef};
231
+ errors.push({
232
+ path: ${pathExpr},
233
+ message: "Expected type '${typeLabel}', got '" + actual + "'"
234
+ });
235
+ }
236
+ `);
237
+ } else if (schema.enum) {
238
+ const values = JSON.stringify(schema.enum);
239
+ code.push(`
240
+ if (!${values}.includes(${dataRef})) {
241
+ errors.push({
242
+ path: ${pathExpr},
243
+ message: "Value '" + ${dataRef} + "' is not one of: ${schema.enum.join(', ')}"
244
+ });
245
+ }
246
+ `);
247
+ }
248
+ };
249
+
250
+ gen(schema, 'data', `""`, 0);
251
+
252
+ code.push(`
253
+ for (let i = 0; i < customValidators.length; i++) {
254
+ customValidators[i](data, "", errors);
255
+ }
256
+
257
+ return {
258
+ isValid: errors.length === 0,
259
+ errors
260
+ };
261
+ `);
262
+
263
+ const fn = new Function(
264
+ 'data',
265
+ 'customTypes',
266
+ 'customValidators',
267
+ code.join('\n'),
268
+ );
269
+
270
+ return (data) => fn(data, customTypes, customValidators);
271
+ }
272
+
273
+ generateTypeCheck(type, ref) {
274
+ if (Array.isArray(type)) {
275
+ return type.map((t) => this.generateTypeCheck(t, ref)).join(' || ');
276
+ }
277
+
278
+ switch (type) {
279
+ case 'string':
280
+ return `typeof ${ref} === "string"`;
281
+
282
+ case 'number':
283
+ return `typeof ${ref} === "number" && Number.isFinite(${ref})`;
284
+
285
+ case 'integer':
286
+ return `Number.isInteger(${ref})`;
287
+
288
+ case 'boolean':
289
+ return `typeof ${ref} === "boolean"`;
290
+
291
+ case 'array':
292
+ return `Array.isArray(${ref})`;
293
+
294
+ case 'object':
295
+ return `${ref} !== null && typeof ${ref} === "object" && !Array.isArray(${ref})`;
296
+
297
+ case 'null':
298
+ return `${ref} === null`;
299
+
300
+ default:
301
+ return `customTypes["${type}"] ? customTypes["${type}"](${ref}) : typeof ${ref} === "${type}"`;
302
+ }
303
+ }
304
+ }
@@ -0,0 +1,305 @@
1
+ export class NativeEngine {
2
+ constructor(options = {}) {
3
+ const {
4
+ recursive = true,
5
+ strict = false,
6
+ customValidators = [],
7
+ customTypes = {},
8
+ } = options;
9
+
10
+ this.options = { recursive, strict };
11
+ this.customValidators = customValidators;
12
+ this.customTypes = customTypes;
13
+ this.errors = [];
14
+ }
15
+
16
+ // ==================================================
17
+ // PUBLIC
18
+ // ==================================================
19
+
20
+ validate({ schema, data }) {
21
+ this.errors = [];
22
+
23
+ this.#validateBySchema(schema, data, '');
24
+
25
+ for (const validator of this.customValidators) {
26
+ validator(data, '', this.errors);
27
+ }
28
+
29
+ return {
30
+ isValid: this.errors.length === 0,
31
+ errors: this.errors,
32
+ };
33
+ }
34
+
35
+ // ==================================================
36
+ // TYPE CHECKING
37
+ // ==================================================
38
+
39
+ #checkType(value, expectedType) {
40
+ if (Array.isArray(expectedType)) {
41
+ return expectedType.some((t) => this.#checkType(value, t));
42
+ }
43
+
44
+ if (this.customTypes[expectedType]) {
45
+ return this.customTypes[expectedType](value);
46
+ }
47
+
48
+ switch (expectedType) {
49
+ case 'integer':
50
+ return Number.isInteger(value);
51
+
52
+ case 'number':
53
+ return typeof value === 'number' && Number.isFinite(value);
54
+
55
+ case 'string':
56
+ return typeof value === 'string';
57
+
58
+ case 'boolean':
59
+ return typeof value === 'boolean';
60
+
61
+ case 'array':
62
+ return Array.isArray(value);
63
+
64
+ case 'object':
65
+ return (
66
+ typeof value === 'object' && value !== null && !Array.isArray(value)
67
+ );
68
+
69
+ case 'null':
70
+ return value === null;
71
+
72
+ default:
73
+ return typeof value === expectedType;
74
+ }
75
+ }
76
+
77
+ #typeError(path, expected, value) {
78
+ const actual = Array.isArray(value) ? 'array' : typeof value;
79
+
80
+ const expectedStr = Array.isArray(expected)
81
+ ? expected.join(', ')
82
+ : expected;
83
+
84
+ this.errors.push({
85
+ path: path || 'root',
86
+ message: `Expected type '${expectedStr}', got '${actual}'`,
87
+ });
88
+ }
89
+
90
+ // ==================================================
91
+ // COMMON RULES
92
+ // ==================================================
93
+
94
+ #validateEnum(value, schema, path) {
95
+ if (schema.enum && !schema.enum.includes(value)) {
96
+ this.errors.push({
97
+ path,
98
+ message: `Value '${value}' is not one of: ${schema.enum.join(', ')}`,
99
+ });
100
+ }
101
+ }
102
+
103
+ #validateFormat(value, schema, path) {
104
+ if (!schema.format) return;
105
+
106
+ if (schema.format === 'date-time') {
107
+ const date = new Date(value);
108
+
109
+ if (Number.isNaN(date.getTime())) {
110
+ this.errors.push({
111
+ path,
112
+ message: `Value '${value}' is not a valid ISO 8601 date-time`,
113
+ });
114
+ }
115
+ }
116
+ }
117
+
118
+ #validateNumberRules(value, schema, path) {
119
+ if (typeof value !== 'number' || !Number.isFinite(value)) return;
120
+
121
+ if (schema.minimum !== undefined && value < schema.minimum) {
122
+ this.errors.push({
123
+ path,
124
+ message: `Value ${value} is less than minimum ${schema.minimum}`,
125
+ });
126
+ }
127
+
128
+ if (
129
+ schema.exclusiveMinimum !== undefined &&
130
+ value <= schema.exclusiveMinimum
131
+ ) {
132
+ this.errors.push({
133
+ path,
134
+ message: `Value ${value} must be greater than ${schema.exclusiveMinimum}`,
135
+ });
136
+ }
137
+
138
+ if (schema.maximum !== undefined && value > schema.maximum) {
139
+ this.errors.push({
140
+ path,
141
+ message: `Value ${value} is greater than maximum ${schema.maximum}`,
142
+ });
143
+ }
144
+
145
+ if (
146
+ schema.exclusiveMaximum !== undefined &&
147
+ value >= schema.exclusiveMaximum
148
+ ) {
149
+ this.errors.push({
150
+ path,
151
+ message: `Value ${value} must be less than ${schema.exclusiveMaximum}`,
152
+ });
153
+ }
154
+
155
+ if (schema.multipleOf !== undefined) {
156
+ if (value % schema.multipleOf !== 0) {
157
+ this.errors.push({
158
+ path,
159
+ message: `Value ${value} is not a multiple of ${schema.multipleOf}`,
160
+ });
161
+ }
162
+ }
163
+ }
164
+
165
+ #applyCommonRules(value, schema, path) {
166
+ this.#validateEnum(value, schema, path);
167
+ this.#validateFormat(value, schema, path);
168
+
169
+ if (schema.type === 'number' || schema.type === 'integer') {
170
+ this.#validateNumberRules(value, schema, path);
171
+ }
172
+ }
173
+
174
+ // ==================================================
175
+ // OBJECT
176
+ // ==================================================
177
+
178
+ #validateObject(schema, data, path = '') {
179
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return;
180
+
181
+ const { strict } = this.options;
182
+ const validatedKeys = new Set();
183
+
184
+ if (schema.required && Array.isArray(schema.required)) {
185
+ for (const field of schema.required) {
186
+ if (!(field in data)) {
187
+ this.errors.push({
188
+ path: path ? `${path}.${field}` : field,
189
+ message: `Required field '${field}' is missing`,
190
+ });
191
+ }
192
+ }
193
+ }
194
+
195
+ if (schema.properties) {
196
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
197
+ validatedKeys.add(key);
198
+
199
+ if (key in data) {
200
+ const currentPath = path ? `${path}.${key}` : key;
201
+
202
+ this.#validateBySchema(propSchema, data[key], currentPath);
203
+ }
204
+ }
205
+ }
206
+
207
+ if (schema.patternProperties) {
208
+ for (const [pattern, propSchema] of Object.entries(
209
+ schema.patternProperties,
210
+ )) {
211
+ const regex = new RegExp(pattern);
212
+
213
+ for (const key of Object.keys(data)) {
214
+ if (regex.test(key)) {
215
+ validatedKeys.add(key);
216
+
217
+ const currentPath = path ? `${path}.${key}` : key;
218
+
219
+ this.#validateBySchema(propSchema, data[key], currentPath);
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ for (const key of Object.keys(data)) {
226
+ if (validatedKeys.has(key)) continue;
227
+
228
+ const currentPath = path ? `${path}.${key}` : key;
229
+
230
+ if (schema.additionalProperties === false) {
231
+ this.errors.push({
232
+ path: currentPath,
233
+ message: `Unexpected field '${key}'`,
234
+ });
235
+
236
+ continue;
237
+ }
238
+
239
+ if (typeof schema.additionalProperties === 'object') {
240
+ this.#validateBySchema(
241
+ schema.additionalProperties,
242
+ data[key],
243
+ currentPath,
244
+ );
245
+ } else if (strict && !schema.additionalProperties) {
246
+ this.errors.push({
247
+ path: currentPath,
248
+ message: `Unexpected field '${key}'`,
249
+ });
250
+ }
251
+ }
252
+ }
253
+
254
+ // ==================================================
255
+ // ARRAY
256
+ // ==================================================
257
+
258
+ #validateArray(schema, data, path) {
259
+ if (!Array.isArray(data)) {
260
+ this.#typeError(path, 'array', data);
261
+ return;
262
+ }
263
+
264
+ if (!schema.items) return;
265
+
266
+ data.forEach((item, index) => {
267
+ const itemPath = `${path}[${index}]`;
268
+
269
+ this.#validateBySchema(schema.items, item, itemPath);
270
+ });
271
+ }
272
+
273
+ // ==================================================
274
+ // UNIVERSAL VALIDATOR
275
+ // ==================================================
276
+
277
+ #validateBySchema(schema, value, path) {
278
+ if (schema === true) return;
279
+ if (schema === false) {
280
+ this.errors.push({
281
+ path,
282
+ message: 'Schema is false, value is not allowed',
283
+ });
284
+ return;
285
+ }
286
+ if (!schema) return;
287
+
288
+ if (schema.type && !this.#checkType(value, schema.type)) {
289
+ this.#typeError(path, schema.type, value);
290
+ return;
291
+ }
292
+
293
+ this.#applyCommonRules(value, schema, path);
294
+
295
+ if (!this.options.recursive) return;
296
+
297
+ if (schema.type === 'object') {
298
+ this.#validateObject(schema, value, path);
299
+ }
300
+
301
+ if (schema.type === 'array') {
302
+ this.#validateArray(schema, value, path);
303
+ }
304
+ }
305
+ }
@@ -0,0 +1,44 @@
1
+ import { NativeEngine } from './NativeEngine.js';
2
+ import { CodeGenerator } from './CodeGenerator.js';
3
+
4
+ export class Validator {
5
+ #compiledCache;
6
+
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ recursive: true,
10
+ strict: true,
11
+ customTypes: {},
12
+ customValidators: [],
13
+ ...options,
14
+ };
15
+
16
+ this.#compiledCache = new WeakMap();
17
+ }
18
+
19
+ // ==================================================
20
+ // PUBLIC API
21
+ // ==================================================
22
+
23
+ validate({ schema, data }) {
24
+ const engine = new NativeEngine(this.options);
25
+ return engine.validate({ schema, data });
26
+ }
27
+
28
+ compile(schema) {
29
+ if (this.#compiledCache.has(schema)) {
30
+ return this.#compiledCache.get(schema);
31
+ }
32
+
33
+ const generator = new CodeGenerator(this.options);
34
+ const compiledFn = generator.compile(schema);
35
+
36
+ this.#compiledCache.set(schema, compiledFn);
37
+
38
+ return compiledFn;
39
+ }
40
+
41
+ clearCache() {
42
+ this.#compiledCache = new WeakMap();
43
+ }
44
+ }
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Validator } from './Validator.js';
2
+ export { NativeEngine } from './NativeEngine.js';
3
+ export { CodeGenerator } from './CodeGenerator.js';
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "hybrid-validator",
3
+ "version": "0.5.1",
4
+ "description": "Lightweight JSON Schema validator for Node.js and modern JavaScript environments",
5
+ "main": "./lib/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./lib/index.js",
9
+ "default": "./lib/index.js"
10
+ }
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "prepare": "node tools/setGitHooks.js",
19
+ "lint": "prettier -c \"**/*.js\" && eslint .",
20
+ "fix": "prettier --write \"**/*.js\" && eslint . --fix",
21
+ "test": "node --test",
22
+ "prepublishOnly": "npm run lint && npm test"
23
+ },
24
+ "imports": {
25
+ "#lib": "./lib/index.js",
26
+ "#tools": "./tools/index.js"
27
+ },
28
+ "keywords": [
29
+ "json",
30
+ "json-schema",
31
+ "validator",
32
+ "schema-validation",
33
+ "validation",
34
+ "json-validator"
35
+ ],
36
+ "author": "Andrii Kotsiuba",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/AndriiKot/json-schema-validator.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/AndriiKot/json-schema-validator/issues"
44
+ },
45
+ "homepage": "https://github.com/AndriiKot/json-schema-validator#readme",
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "private": false,
50
+ "devDependencies": {
51
+ "eslint": "^9.39.2",
52
+ "eslint-config-metarhia": "^9.1.5",
53
+ "prettier": "^3.8.1"
54
+ }
55
+ }