@jetio/validator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1362 -0
  3. package/dist/cli.js +219 -0
  4. package/dist/compileSchema.d.ts +148 -0
  5. package/dist/compileSchema.js +2199 -0
  6. package/dist/compileSchema.js.map +1 -0
  7. package/dist/formats.d.ts +41 -0
  8. package/dist/formats.js +166 -0
  9. package/dist/formats.js.map +1 -0
  10. package/dist/index.cjs.js +6167 -0
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.esm.js +6148 -0
  13. package/dist/index.js +28 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/jet-validator.d.ts +88 -0
  16. package/dist/jet-validator.js +983 -0
  17. package/dist/jet-validator.js.map +1 -0
  18. package/dist/resolver.d.ts +348 -0
  19. package/dist/resolver.js +2459 -0
  20. package/dist/resolver.js.map +1 -0
  21. package/dist/scripts/load-metaschemas.d.ts +1 -0
  22. package/dist/scripts/metaschema-loader.d.ts +2 -0
  23. package/dist/src/compileSchema.d.ts +148 -0
  24. package/dist/src/formats.d.ts +41 -0
  25. package/dist/src/index.d.ts +9 -0
  26. package/dist/src/jet-validator.d.ts +88 -0
  27. package/dist/src/resolver.d.ts +348 -0
  28. package/dist/src/types/format.d.ts +7 -0
  29. package/dist/src/types/keywords.d.ts +78 -0
  30. package/dist/src/types/schema.d.ts +123 -0
  31. package/dist/src/types/standalone.d.ts +4 -0
  32. package/dist/src/types/validation.d.ts +49 -0
  33. package/dist/src/utilities/index.d.ts +11 -0
  34. package/dist/src/utilities/schema.d.ts +10 -0
  35. package/dist/types/format.d.ts +7 -0
  36. package/dist/types/format.js +3 -0
  37. package/dist/types/format.js.map +1 -0
  38. package/dist/types/keywords.d.ts +78 -0
  39. package/dist/types/keywords.js +4 -0
  40. package/dist/types/keywords.js.map +1 -0
  41. package/dist/types/schema.d.ts +123 -0
  42. package/dist/types/schema.js +3 -0
  43. package/dist/types/schema.js.map +1 -0
  44. package/dist/types/standalone.d.ts +4 -0
  45. package/dist/types/standalone.js +3 -0
  46. package/dist/types/standalone.js.map +1 -0
  47. package/dist/types/validation.d.ts +49 -0
  48. package/dist/types/validation.js +3 -0
  49. package/dist/types/validation.js.map +1 -0
  50. package/dist/utilities/index.d.ts +11 -0
  51. package/dist/utilities/index.js +146 -0
  52. package/dist/utilities/index.js.map +1 -0
  53. package/dist/utilities/schema.d.ts +10 -0
  54. package/dist/utilities/schema.js +232 -0
  55. package/dist/utilities/schema.js.map +1 -0
  56. package/dist/validator.umd.js +6196 -0
  57. package/package.json +79 -0
@@ -0,0 +1,983 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JetValidator = void 0;
4
+ const compileSchema_1 = require("./compileSchema");
5
+ const formats_1 = require("./formats");
6
+ const resolver_1 = require("./resolver");
7
+ const utilities_1 = require("./utilities");
8
+ const schema_1 = require("./utilities/schema");
9
+ class JetValidator {
10
+ constructor(options = {}) {
11
+ this.schemas = {};
12
+ this.metaSchemas = {};
13
+ this.customKeywords = new Map();
14
+ this.hasMacros = false;
15
+ this.counter = 0;
16
+ this.aliases = {
17
+ "draft-06": "https://json-schema.org/draft-06/schema",
18
+ "draft-07": "https://json-schema.org/draft-07/schema",
19
+ "draft/2019-09": "https://json-schema.org/draft/2019-09/schema",
20
+ "draft/2020-12": "https://json-schema.org/draft/2020-12/schema",
21
+ };
22
+ this.options = {
23
+ allErrors: options.allErrors ?? false,
24
+ inlineRefs: options.inlineRefs ?? true,
25
+ overwrittenFormats: options.overwrittenFormats ?? [],
26
+ verbose: options.verbose ?? false,
27
+ debug: options.debug ?? false,
28
+ logFunction: options.logFunction ?? false,
29
+ strict: options.strict ?? true,
30
+ metaSchema: options.metaSchema ?? "",
31
+ draft: options.draft ?? "draft2019-09",
32
+ validateFormats: options.validateFormats ?? true,
33
+ formatMode: options.formatMode ?? "full",
34
+ loopEnum: options.loopEnum ?? 200,
35
+ loopRequired: options.loopRequired ?? 200,
36
+ formats: options.formats ?? [],
37
+ loadSchema: options.loadSchema ??
38
+ (() => {
39
+ throw new Error("loadSchema not provided");
40
+ }),
41
+ allowFormatOverride: options.allowFormatOverride ?? false,
42
+ $data: options.$data ?? false,
43
+ removeAdditional: options.removeAdditional ?? false,
44
+ useDefaults: options.useDefaults ?? false,
45
+ coerceTypes: options.coerceTypes ?? false,
46
+ cache: options.cache ?? true,
47
+ strictSchema: options.strictSchema ?? false,
48
+ strictNumbers: options.strictNumbers ?? false,
49
+ strictRequired: options.strictRequired ?? false,
50
+ strictTypes: options.strictTypes ?? false,
51
+ async: options.async ?? false,
52
+ validateSchema: options.validateSchema ?? false,
53
+ addUsedSchema: options.addUsedSchema ?? true,
54
+ errorMessage: options.errorMessage ?? false,
55
+ };
56
+ if (this.options.formatMode !== false) {
57
+ this.formatValidators =
58
+ this.options.formatMode === "full"
59
+ ? { ...formats_1.FULL_FORMAT_VALIDATORS }
60
+ : { ...formats_1.FAST_FORMAT_VALIDATORS };
61
+ }
62
+ else {
63
+ this.formatValidators = {};
64
+ }
65
+ this.compilationCache = new Map();
66
+ }
67
+ //#region
68
+ addFormat(key, validator, options) {
69
+ const shouldOverride = options?.override ?? this.options.allowFormatOverride;
70
+ if (!shouldOverride && key in this.formatValidators)
71
+ throw Error(`Format "${key}" is already registered, call removeFormat("${key}") before Attempting to add.`);
72
+ this.formatValidators[key] = validator;
73
+ }
74
+ removeFormat(key) {
75
+ if (!(key in this.formatValidators)) {
76
+ throw new Error(`Format "${key}" is not registered.`);
77
+ }
78
+ delete this.formatValidators[key];
79
+ }
80
+ getFormat(format) {
81
+ return this.formatValidators[format];
82
+ }
83
+ testFormat(value, format) {
84
+ const validator = this.formatValidators[format];
85
+ if (!validator)
86
+ return true;
87
+ if (validator instanceof RegExp) {
88
+ return validator.test(value);
89
+ }
90
+ else if (typeof validator === "function") {
91
+ return validator(value);
92
+ }
93
+ else {
94
+ const validate = validator.validate;
95
+ if (validate instanceof RegExp) {
96
+ return validate.test(value);
97
+ }
98
+ else {
99
+ return validate(value);
100
+ }
101
+ }
102
+ }
103
+ isFormatRegistered(key) {
104
+ return key in this.formatValidators;
105
+ }
106
+ getRegisteredFormats() {
107
+ return Object.keys(this.formatValidators);
108
+ }
109
+ validateFormat(value, format) {
110
+ const isValid = this.testFormat(value, format);
111
+ return isValid
112
+ ? { valid: true }
113
+ : {
114
+ valid: false,
115
+ errors: { message: `Failed to validate format '${format}'` },
116
+ };
117
+ }
118
+ getAllFormats() {
119
+ return { ...this.formatValidators };
120
+ }
121
+ //#endregion
122
+ //#region
123
+ addKeyword(definition) {
124
+ if (schema_1.baseSchemaKeys.has(definition.keyword)) {
125
+ throw new Error(`Keyword "${definition.keyword}" is a predefined keyword and cannot be registered.`);
126
+ }
127
+ this.validateKeywordDefinition(definition);
128
+ this.customKeywords.set(definition.keyword, definition);
129
+ if ("macro" in definition) {
130
+ this.hasMacros = true;
131
+ }
132
+ return this;
133
+ }
134
+ removeKeyword(keyword) {
135
+ this.customKeywords.delete(keyword);
136
+ this.hasMacros = false;
137
+ for (const def of this.customKeywords.values()) {
138
+ if ("macro" in def) {
139
+ this.hasMacros = true;
140
+ break;
141
+ }
142
+ }
143
+ return this;
144
+ }
145
+ hasMacroKeywords() {
146
+ return this.hasMacros;
147
+ }
148
+ getKeyword(keyword) {
149
+ return this.customKeywords.get(keyword);
150
+ }
151
+ validateKeywordDefinition(def) {
152
+ const approaches = [];
153
+ if ("macro" in def && def.macro)
154
+ approaches.push(def.macro);
155
+ if ("compile" in def && def.compile)
156
+ approaches.push(def.compile);
157
+ if ("validate" in def && def.validate)
158
+ approaches.push(def.validate);
159
+ if ("code" in def && def.code)
160
+ approaches.push(def.code);
161
+ if (approaches.length === 0) {
162
+ throw new Error(`Keyword "${def.keyword}" must have at least one of: macro, compile, validate, or code`);
163
+ }
164
+ if (approaches.length > 1) {
165
+ throw new Error(`Keyword "${def.keyword}" can only have ONE of: macro, compile, validate, or code`);
166
+ }
167
+ if (def.schemaType) {
168
+ const validTypes = [
169
+ "string",
170
+ "number",
171
+ "boolean",
172
+ "array",
173
+ "object",
174
+ "null",
175
+ ];
176
+ const types = Array.isArray(def.schemaType)
177
+ ? def.schemaType
178
+ : [def.schemaType];
179
+ for (const type of types) {
180
+ if (!validTypes.includes(type)) {
181
+ throw new Error(`Invalid schemaType "${type}" for keyword "${def.keyword}"`);
182
+ }
183
+ }
184
+ }
185
+ if (def.type) {
186
+ const validTypes = [
187
+ "string",
188
+ "number",
189
+ "integer",
190
+ "boolean",
191
+ "array",
192
+ "object",
193
+ "null",
194
+ ];
195
+ const types = Array.isArray(def.type)
196
+ ? def.type
197
+ : [def.type];
198
+ for (const type of types) {
199
+ if (!validTypes.includes(type)) {
200
+ throw new Error(`Invalid type "${type}" for keyword "${def.keyword}"`);
201
+ }
202
+ }
203
+ }
204
+ if (def.metaSchema) {
205
+ if (typeof def.metaSchema !== "object" ||
206
+ def.metaSchema === null) {
207
+ throw new Error(`metaSchema for keyword "${def.keyword}" must be an object`);
208
+ }
209
+ }
210
+ }
211
+ isKeywordAdded(key) {
212
+ return this.customKeywords.has(key);
213
+ }
214
+ getAddedKeywords() {
215
+ return Object.keys(this.customKeywords);
216
+ }
217
+ clearKeywords() {
218
+ this.customKeywords.clear();
219
+ }
220
+ getAllKeywords() {
221
+ return this.customKeywords;
222
+ }
223
+ //#endregion
224
+ //#region
225
+ addSchema(schema, id) {
226
+ const key = id || schema.$id;
227
+ if (!key)
228
+ throw Error("Attempting to register a schema that has no defined id.");
229
+ schema.$id = key;
230
+ this.schemas[key] = structuredClone(schema);
231
+ }
232
+ getSchema(key) {
233
+ return structuredClone(this.schemas[key]);
234
+ }
235
+ getCompiledSchema(key, config) {
236
+ if (this.schemas[key] !== undefined) {
237
+ return this.compile(this.schemas[key], config);
238
+ }
239
+ else {
240
+ throw Error(`Schema ${key} not found in registry.`);
241
+ }
242
+ }
243
+ async getCompiledSchemaAsync(key, config) {
244
+ if (this.schemas[key] !== undefined) {
245
+ return await this.compileAsync(this.schemas[key], config);
246
+ }
247
+ else {
248
+ throw Error(`Schema ${key} not found in registry.`);
249
+ }
250
+ }
251
+ isSchemaAdded(key) {
252
+ return key in this.schemas;
253
+ }
254
+ getAddedSchemas() {
255
+ return Object.keys(this.schemas);
256
+ }
257
+ removeSchema(pattern) {
258
+ if (pattern === undefined) {
259
+ this.schemas = {};
260
+ if (this.options.cache) {
261
+ this.compilationCache.clear();
262
+ }
263
+ return;
264
+ }
265
+ if (typeof pattern === "string") {
266
+ if (!(pattern in this.schemas)) {
267
+ throw new Error(`Schema "${pattern}" is not registered.`);
268
+ }
269
+ delete this.schemas[pattern];
270
+ if (this.options.cache) {
271
+ this.compilationCache.delete(pattern);
272
+ }
273
+ return;
274
+ }
275
+ if (pattern instanceof RegExp) {
276
+ const keys = Object.keys(this.schemas);
277
+ let removed = 0;
278
+ for (const key of keys) {
279
+ if (pattern.test(key)) {
280
+ delete this.schemas[key];
281
+ if (this.options.cache) {
282
+ this.compilationCache.delete(key);
283
+ }
284
+ removed++;
285
+ }
286
+ }
287
+ if (removed === 0) {
288
+ console.warn(`No schemas matched pattern: ${pattern}`);
289
+ }
290
+ return;
291
+ }
292
+ if (typeof pattern === "object") {
293
+ const keys = Object.keys(this.schemas);
294
+ let found = false;
295
+ for (const key of keys) {
296
+ if (this.schemas[key] === pattern) {
297
+ delete this.schemas[key];
298
+ if (this.options.cache) {
299
+ this.compilationCache.delete(key);
300
+ }
301
+ found = true;
302
+ break;
303
+ }
304
+ }
305
+ if (!found) {
306
+ throw new Error("Schema object not found in registry");
307
+ }
308
+ return;
309
+ }
310
+ throw new Error("Invalid pattern type for removeSchema");
311
+ }
312
+ clearSchemas() {
313
+ this.schemas = {};
314
+ }
315
+ getAllSchemas() {
316
+ return { ...this.schemas };
317
+ }
318
+ //#endregion
319
+ //#region
320
+ validate(schema, data, config) {
321
+ let finalSchema;
322
+ if (typeof schema === "object" || typeof schema === "boolean") {
323
+ finalSchema = schema;
324
+ }
325
+ else {
326
+ finalSchema = this.schemas[schema];
327
+ }
328
+ if (finalSchema !== undefined) {
329
+ let validator;
330
+ if (typeof finalSchema !== "boolean") {
331
+ const func = typeof schema === "string"
332
+ ? this.compilationCache.get(schema)
333
+ : this.compilationCache.get(finalSchema.$id ?? finalSchema);
334
+ if (func)
335
+ validator = func;
336
+ }
337
+ if (!validator) {
338
+ validator = this.compile(finalSchema, config);
339
+ }
340
+ const valid = validator(data);
341
+ return { valid, errors: validator.errors };
342
+ }
343
+ else {
344
+ throw Error(`Schema ${schema} was not found in registry.`);
345
+ }
346
+ }
347
+ async validateAsync(schema, data, config) {
348
+ let finalSchema;
349
+ if (typeof schema === "object" || typeof schema === "boolean") {
350
+ finalSchema = schema;
351
+ }
352
+ else {
353
+ finalSchema = this.schemas[schema];
354
+ }
355
+ if (finalSchema !== undefined) {
356
+ let validator;
357
+ if (typeof finalSchema !== "boolean") {
358
+ const func = typeof schema === "string"
359
+ ? this.compilationCache.get(schema)
360
+ : this.compilationCache.get(finalSchema.$id ?? finalSchema);
361
+ if (func)
362
+ validator = func;
363
+ }
364
+ if (!validator) {
365
+ validator = await this.compileAsync(finalSchema, config);
366
+ }
367
+ const valid = await validator(data);
368
+ return { valid, errors: validator.errors };
369
+ }
370
+ else {
371
+ throw Error(`Schema ${schema} was not found in registry.`);
372
+ }
373
+ }
374
+ //#endregion
375
+ //#region
376
+ getMetaSchema($schema, options) {
377
+ let metaSchemaId = options?.metaSchema;
378
+ if (!metaSchemaId && $schema) {
379
+ metaSchemaId = $schema;
380
+ }
381
+ if (!metaSchemaId) {
382
+ metaSchemaId =
383
+ this.options.metaSchema || "https://json-schema.org/draft-07/schema";
384
+ }
385
+ const finalId = this.aliases[metaSchemaId] ?? metaSchemaId;
386
+ let metaSchema = this.metaSchemas[finalId];
387
+ if (!metaSchema) {
388
+ if (options?.strictSchema) {
389
+ throw new Error(`Meta-schema "${metaSchemaId}" is not loaded.\n` +
390
+ `Load it using: loadDraft07(jetValidator) or loadAllMetaSchemas(jetValidator)\n` +
391
+ `Or disable validation: new JetValidator({ validateSchema: false })`);
392
+ }
393
+ }
394
+ return { metaSchema, metaSchemaId };
395
+ }
396
+ validateSchemaSync(schema, options) {
397
+ const { metaSchema } = this.getMetaSchema(schema.$schema, options);
398
+ if (!metaSchema)
399
+ return { valid: false, errors: [{ message: "metaSchema not found" }] };
400
+ const validator = this.compile(metaSchema, {
401
+ ...options,
402
+ logFunction: true,
403
+ validateSchema: false,
404
+ });
405
+ const result = validator(schema);
406
+ if (!result && options?.strictSchema) {
407
+ console.log(validator.errors);
408
+ throw Error();
409
+ }
410
+ if (!result)
411
+ validator.errors[0]["metaSchemaError"] = true;
412
+ return { valid: result, errors: validator.errors };
413
+ }
414
+ async validateSchemaAsync(schema, options) {
415
+ let metaSchema;
416
+ let { metaSchema: mSchema, metaSchemaId } = this.getMetaSchema(schema.$schema, options);
417
+ if (mSchema) {
418
+ metaSchema = mSchema;
419
+ }
420
+ else {
421
+ metaSchema = await this.options.loadSchema(metaSchemaId);
422
+ }
423
+ const validator = await this.compileAsync(metaSchema, {
424
+ ...options,
425
+ validateSchema: false,
426
+ async: true,
427
+ });
428
+ const result = await validator(schema);
429
+ if (!result && options?.strictSchema) {
430
+ console.log(validator.errors);
431
+ throw Error();
432
+ }
433
+ if (!result)
434
+ validator.errors[0]["metaSchemaError"] = true;
435
+ return { valid: result, errors: validator.errors };
436
+ }
437
+ addMetaSchema(schema, key) {
438
+ let Key = key || schema.$id;
439
+ if (!Key) {
440
+ throw new Error("Meta-schema must have an $id or explicit key");
441
+ }
442
+ const schemaKey = this.aliases[Key] ?? Key;
443
+ if (!(schemaKey in this.metaSchemas)) {
444
+ this.metaSchemas[schemaKey] = structuredClone(schema);
445
+ }
446
+ return this;
447
+ }
448
+ //#endregion
449
+ clearRegistries() {
450
+ this.schemas = {};
451
+ this.formatValidators = {};
452
+ this.clearKeywords();
453
+ if (this.options.cache) {
454
+ this.compilationCache.clear();
455
+ }
456
+ }
457
+ compileResolved(resolvedSchema, mainSchema, refables, allFormats, allKeywords, config, compileContext) {
458
+ const includesItemsRef = compileContext.hasUnevaluatedItems;
459
+ const includesPropRef = compileContext.hasUnevaluatedProperties;
460
+ const fconfig = {
461
+ ...config,
462
+ };
463
+ if (typeof resolvedSchema === "boolean")
464
+ fconfig.allErrors = false;
465
+ const compiler = new compileSchema_1.Compiler(refables, mainSchema, fconfig, this, allKeywords, compileContext, false);
466
+ let source;
467
+ if (compileContext.hasRootReference) {
468
+ source = compiler.compileSchema(resolvedSchema, {
469
+ schema: `\${path.schema}`,
470
+ data: "${path.data}",
471
+ $data: "",
472
+ }, {
473
+ parentHasUnevaluatedProperties: includesPropRef,
474
+ parentUnevaluatedPropVar: includesPropRef
475
+ ? "evaluatedProperties"
476
+ : undefined,
477
+ parentHasUnevaluatedItems: includesItemsRef,
478
+ parentUnevaluatedItemVar: includesItemsRef
479
+ ? "evaluatedItems"
480
+ : undefined,
481
+ isSubschema: true,
482
+ }, "rootData");
483
+ }
484
+ else {
485
+ source = compiler.compileSchema(resolvedSchema, undefined, undefined, "rootData");
486
+ }
487
+ const keywords = compiler.getCompiledKeywords();
488
+ const formatValidators = {};
489
+ const customKeywords = new Map(keywords.compiledKeywords);
490
+ for (const keywordDef of keywords.validateKeywords) {
491
+ const validate = this.customKeywords.get(keywordDef)?.validate;
492
+ if (validate)
493
+ customKeywords.set(keywordDef, validate);
494
+ }
495
+ if (typeof resolvedSchema !== "boolean") {
496
+ if (allFormats.size > 0) {
497
+ for (const validatorKey of allFormats) {
498
+ const validator = this.formatValidators[validatorKey];
499
+ if (validator) {
500
+ if (typeof validator === "function" ||
501
+ validator instanceof RegExp) {
502
+ formatValidators[validatorKey] = validator;
503
+ }
504
+ else {
505
+ formatValidators[validatorKey] = validator.validate;
506
+ }
507
+ }
508
+ }
509
+ }
510
+ }
511
+ const asyncPrefix = config.async ? "async " : "";
512
+ let functionDeclaration = "validate(rootData";
513
+ if (compileContext.hasRootReference) {
514
+ if (includesItemsRef || includesPropRef) {
515
+ if (includesPropRef)
516
+ functionDeclaration = functionDeclaration + ",evaluatedProperties";
517
+ if (includesItemsRef)
518
+ functionDeclaration = functionDeclaration + ",evaluatedItems";
519
+ }
520
+ }
521
+ if (compileContext.hasRootReference)
522
+ functionDeclaration = functionDeclaration + ",path";
523
+ functionDeclaration = functionDeclaration + ")";
524
+ const finalSource = `
525
+ ${compiler.hoistedFunctions.join("")}
526
+ ${asyncPrefix}function ${functionDeclaration}{${compileContext.hasRootReference
527
+ ? 'if (!path) {path = { schema: "#", data: "" };}'
528
+ : ""}${source}${fconfig.allErrors
529
+ ? `validate.errors = allErrors; return allErrors.length == 0`
530
+ : "return true"};} return validate;
531
+ `;
532
+ if (config.logFunction) {
533
+ console.log(finalSource);
534
+ }
535
+ const regexParams = [];
536
+ const regexArgs = [];
537
+ if (compiler.regexCache.size > 0) {
538
+ for (const [key, value] of compiler.regexCache.entries()) {
539
+ regexParams.push(value);
540
+ regexArgs.push(new RegExp(key));
541
+ }
542
+ }
543
+ return new Function("formatValidators", "deepEqual", "canonicalStringify", "customKeywords", "len_of", ...regexParams, finalSource)(formatValidators, utilities_1.deepEqual, utilities_1.canonicalStringify, customKeywords, utilities_1.len_of, ...regexArgs);
544
+ }
545
+ compile(fschema, config) {
546
+ const schema = typeof fschema === "boolean" ? fschema : fschema;
547
+ const finalConfig = {
548
+ ...this.options,
549
+ ...config,
550
+ };
551
+ if (typeof schema === "object" &&
552
+ finalConfig.validateSchema &&
553
+ (finalConfig.metaSchema || schema.$schema)) {
554
+ const result = this.validateSchemaSync(schema, {
555
+ metaSchema: finalConfig?.metaSchema,
556
+ });
557
+ if (!result.valid) {
558
+ const validator = (data) => result.valid;
559
+ validator.errors = result.errors || [];
560
+ return validator;
561
+ }
562
+ }
563
+ if (finalConfig.cache && typeof schema !== "boolean") {
564
+ if (this.compilationCache.has(schema?.$id) ||
565
+ this.compilationCache.has(schema)) {
566
+ return (this.compilationCache.get(schema?.$id) ??
567
+ this.compilationCache.get(schema));
568
+ }
569
+ }
570
+ const resolver = new resolver_1.SchemaResolver(this, finalConfig);
571
+ const resolved = resolver.resolveSync(schema);
572
+ const validator = this.compileResolved(resolved.schema, schema, resolved.refables, resolved.allFormats, resolved.keywords, finalConfig, resolved.compileContext);
573
+ if (finalConfig.cache && typeof schema === "object" && schema !== null) {
574
+ const schem = schema;
575
+ this.compilationCache.set(schem.$id ?? schem.id ?? schema, validator);
576
+ }
577
+ return validator;
578
+ }
579
+ async compileAsync(fschema, config) {
580
+ const schema = typeof fschema === "boolean" ? fschema : fschema;
581
+ const finalConfig = {
582
+ ...this.options,
583
+ ...config,
584
+ };
585
+ if (typeof schema === "object" &&
586
+ finalConfig.validateSchema &&
587
+ (finalConfig.metaSchema || schema.$schema)) {
588
+ const result = await this.validateSchemaAsync(schema, {
589
+ metaSchema: finalConfig?.metaSchema,
590
+ });
591
+ if (!result.valid) {
592
+ const validator = (data) => result.valid;
593
+ validator.errors = result.errors || [];
594
+ return validator;
595
+ }
596
+ }
597
+ if (finalConfig.cache && typeof schema !== "boolean") {
598
+ if (this.compilationCache.has(schema?.$id) ||
599
+ this.compilationCache.has(schema)) {
600
+ return (this.compilationCache.get(schema?.$id) ??
601
+ this.compilationCache.get(schema));
602
+ }
603
+ }
604
+ const resolver = new resolver_1.SchemaResolver(this, finalConfig);
605
+ const resolved = await resolver.resolveAsync(schema, finalConfig.loadSchema);
606
+ const validator = this.compileResolved(resolved.schema, schema, resolved.refables, resolved.allFormats, resolved.keywords, finalConfig, resolved.compileContext);
607
+ if (finalConfig.cache && typeof schema === "object") {
608
+ this.compilationCache.set(schema, validator);
609
+ }
610
+ return validator;
611
+ }
612
+ logErrors(errors, indent = 0) {
613
+ const spacer = " ".repeat(indent); // Create indentation
614
+ if (Array.isArray(errors)) {
615
+ // If the input is an array (like subErrors), iterate through each error
616
+ errors.forEach((err) => this.logErrors(err, indent));
617
+ }
618
+ else if (errors && typeof errors === "object") {
619
+ // If the input is a single error object
620
+ console.log(`${spacer}❌ Validation Failed: ${errors.message || "Unknown error"}`);
621
+ if (errors.dataPath) {
622
+ console.log(`${spacer} - Data Path: ${errors.dataPath}`);
623
+ }
624
+ if (errors.schemaPath) {
625
+ console.log(`${spacer} - Schema Path: ${errors.schemaPath}`);
626
+ }
627
+ if (errors.rule) {
628
+ console.log(`${spacer} - Rule: ${errors.rule}`);
629
+ }
630
+ if (errors.expected) {
631
+ console.log(`${spacer} - Expected: ${errors.expected}`);
632
+ }
633
+ if (errors.subErrors) {
634
+ console.log(`${spacer} - Sub-errors:`);
635
+ this.logErrors(errors.subErrors, indent + 1); // Recursive call for nested errors
636
+ }
637
+ }
638
+ }
639
+ getFieldFromPath(dataPath) {
640
+ if (!dataPath || dataPath === "/")
641
+ return "";
642
+ const segments = dataPath.split("/").filter(Boolean);
643
+ return segments[segments.length - 1];
644
+ }
645
+ getFullFieldPath(dataPath) {
646
+ if (!dataPath || dataPath === "/")
647
+ return "";
648
+ return dataPath
649
+ .slice(1)
650
+ .replace(/\/(\d+)/g, "[$1]")
651
+ .replace(/\//g, ".");
652
+ }
653
+ getFieldErrors(errors) {
654
+ const byField = {};
655
+ for (const error of errors) {
656
+ const field = error.dataPath || "/";
657
+ if (!byField[field])
658
+ byField[field] = [];
659
+ byField[field].push(error.message);
660
+ }
661
+ return byField;
662
+ }
663
+ errorsText(errors, options) {
664
+ const sep = options?.separator ?? ", ";
665
+ const dataVar = options?.dataVar ?? "data";
666
+ return errors
667
+ .map((e) => {
668
+ const path = e.dataPath || "/";
669
+ const fullPath = path === "/" ? dataVar : `${dataVar}${path.replace(/\//g, ".")}`;
670
+ return `${fullPath}: ${e.message}`;
671
+ })
672
+ .join(sep);
673
+ }
674
+ generateStandalone(schema, sconfig) {
675
+ const code = [];
676
+ const formatImports = [];
677
+ const config = { ...this.options, ...sconfig };
678
+ let generatedFunctionName;
679
+ if (this.counter === 0) {
680
+ generatedFunctionName = "validate" + this.counter;
681
+ }
682
+ else {
683
+ generatedFunctionName = "validate" + this.counter++;
684
+ }
685
+ const resolver = new resolver_1.SchemaResolver(this, config);
686
+ resolver.rootFunctionName = generatedFunctionName;
687
+ const resolved = resolver.resolveSync(schema);
688
+ const includesItemsRef = resolved.compileContext.hasUnevaluatedItems;
689
+ const includesPropRef = resolved.compileContext.hasUnevaluatedProperties;
690
+ const has$Data = resolved.compileContext.uses$Data;
691
+ const compiler = new compileSchema_1.Compiler(resolved.refables, schema, { ...config }, this, resolved.keywords, resolved.compileContext);
692
+ compiler.mainFunctionName = generatedFunctionName;
693
+ let source;
694
+ if (resolved.compileContext.hasRootReference) {
695
+ source = compiler.compileSchema(resolved.schema, {
696
+ schema: `\${path.schema}`,
697
+ data: "${path.data}",
698
+ $data: "",
699
+ }, {
700
+ parentHasUnevaluatedProperties: includesPropRef,
701
+ parentUnevaluatedPropVar: includesPropRef
702
+ ? "evaluatedProperties"
703
+ : undefined,
704
+ parentHasUnevaluatedItems: includesItemsRef,
705
+ parentUnevaluatedItemVar: includesItemsRef
706
+ ? "evaluatedItems"
707
+ : undefined,
708
+ isSubschema: true,
709
+ }, "rootData");
710
+ }
711
+ else {
712
+ source = compiler.compileSchema(resolved.schema, undefined, undefined, "rootData");
713
+ }
714
+ const keywords = compiler.getCompiledKeywords();
715
+ if (keywords.hasCompileKeyword) {
716
+ code.push(`const compilerOptions = ${JSON.stringify(config)};\n`);
717
+ code.push(`const mainRootSchema = ${JSON.stringify(schema)};\n`);
718
+ }
719
+ const inlinedFormats = new Set();
720
+ if (has$Data) {
721
+ this.inlineAllConfiguredFormats(code, config, inlinedFormats);
722
+ this.createFormatObject(code, config, inlinedFormats);
723
+ }
724
+ else if (resolved.allFormats.size > 0) {
725
+ this.inlineUsedFormats(code, resolved.allFormats, config, formatImports, inlinedFormats);
726
+ }
727
+ for (const keywordDef of keywords.validateKeywords) {
728
+ const validate = this.customKeywords.get(keywordDef)?.validate;
729
+ if (validate) {
730
+ code.push(`const ${keywordDef} = ${validate.toString()};\n`);
731
+ }
732
+ }
733
+ if (compiler.needslen_of)
734
+ code.push(utilities_1.len_of.toString() + ";");
735
+ if (compiler.needsStringify)
736
+ code.push(utilities_1.canonicalStringify.toString() + ";");
737
+ if (compiler.needsDeepEqual)
738
+ code.push(utilities_1.deepEqual.toString() + ";");
739
+ const asyncPrefix = config.async ? "async " : "";
740
+ let functionDeclaration = "validate(rootData";
741
+ if (resolved.compileContext.hasRootReference) {
742
+ if (includesItemsRef || includesPropRef) {
743
+ if (includesPropRef)
744
+ functionDeclaration += ",evaluatedProperties";
745
+ if (includesItemsRef)
746
+ functionDeclaration += ",evaluatedItems";
747
+ }
748
+ functionDeclaration += ",path";
749
+ }
750
+ functionDeclaration += ")";
751
+ let regexDeclaration = "";
752
+ if (compiler.regexCache.size > 0) {
753
+ for (const [key, value] of compiler.regexCache.entries()) {
754
+ regexDeclaration =
755
+ regexDeclaration +
756
+ `const ${value} = new RegExp(${JSON.stringify(key)});\n`;
757
+ }
758
+ }
759
+ code.push(compiler.hoistedFunctions.join(""));
760
+ const finalSource = `
761
+ ${asyncPrefix}function ${functionDeclaration} {
762
+ ${regexDeclaration}${resolved.compileContext.hasRootReference
763
+ ? '\n if (!path) { path = { schema: "#", data: "" }; }'
764
+ : ""}
765
+ ${source}
766
+ ${this.options.allErrors
767
+ ? `validate.errors = allErrors; return allErrors.length == 0`
768
+ : " return true"}
769
+ }
770
+ `;
771
+ code.push(finalSource);
772
+ let formatSetup;
773
+ if (formatImports.length > 0) {
774
+ formatSetup = this.generateFormatSetup(formatImports);
775
+ }
776
+ return {
777
+ code: code.join("\n"),
778
+ functionName: generatedFunctionName,
779
+ formatSetup,
780
+ imports: formatImports,
781
+ };
782
+ }
783
+ inlineUsedFormats(code, usedFormats, config, formatImports, inlinedFormats) {
784
+ const overwrittenFormats = config.overwrittenFormats || [];
785
+ code.push("// Format validators\n");
786
+ for (const formatName of usedFormats) {
787
+ if (inlinedFormats.has(formatName))
788
+ continue;
789
+ const validator = this.formatValidators[formatName];
790
+ if (!validator) {
791
+ formatImports.push(formatName);
792
+ continue;
793
+ }
794
+ const isOverwritten = overwrittenFormats.includes(formatName);
795
+ if (typeof validator === "function" || validator instanceof RegExp) {
796
+ this.resolveFormats(validator, code, formatName, inlinedFormats, isOverwritten, formatImports);
797
+ }
798
+ else if (typeof validator === "object" && "validate" in validator) {
799
+ this.resolveFormats(validator.validate, code, formatName, inlinedFormats, isOverwritten, formatImports);
800
+ }
801
+ }
802
+ code.push("\n");
803
+ }
804
+ resolveFormats(validator, code, formatName, inlinedFormats, isOverwritten, formatImports) {
805
+ if (validator instanceof RegExp) {
806
+ const safeName = this.getSafeFormatName(formatName);
807
+ code.push(`const ${safeName} = new RegExp(${JSON.stringify(validator.source)}, '${validator.flags}');\n`);
808
+ inlinedFormats.add(formatName);
809
+ }
810
+ else if (typeof validator === "function") {
811
+ if (isOverwritten) {
812
+ const fnString = validator.toString();
813
+ if (this.isSelfContained(fnString)) {
814
+ code.push(`const ${this.getSafeFormatName(formatName)} = ${fnString};\n`);
815
+ inlinedFormats.add(formatName);
816
+ }
817
+ else {
818
+ formatImports.push(formatName);
819
+ }
820
+ }
821
+ else {
822
+ const needsExternalDeps = this.formatNeedsExternalDeps(formatName);
823
+ if (needsExternalDeps) {
824
+ this.inlineFormatWithDeps(code, formatName, inlinedFormats);
825
+ }
826
+ else {
827
+ if (validator.name) {
828
+ code.push(`${validator.toString()};\n`);
829
+ }
830
+ else {
831
+ code.push(`const ${this.getSafeFormatName(formatName)} = ${validator.toString()};\n`);
832
+ }
833
+ inlinedFormats.add(formatName);
834
+ }
835
+ }
836
+ }
837
+ }
838
+ inlineAllConfiguredFormats(code, config, inlinedFormats) {
839
+ const configuredFormats = config.formats ?? [];
840
+ code.push("// Format validators (all configured for $data support)\n");
841
+ if (configuredFormats?.length > 0) {
842
+ for (const formatName of configuredFormats) {
843
+ if (inlinedFormats.has(formatName))
844
+ continue;
845
+ const validator = this.formatValidators[formatName];
846
+ if (validator) {
847
+ if (validator instanceof RegExp || typeof validator === "function") {
848
+ this.resolve$DataFormat(validator, formatName, inlinedFormats, code);
849
+ }
850
+ else if (typeof validator === "object" && "validate" in validator) {
851
+ this.resolve$DataFormat(validator.validate, formatName, inlinedFormats, code);
852
+ }
853
+ }
854
+ }
855
+ }
856
+ else if (config.formatMode === "fast" || config.formatMode === "full") {
857
+ const validators = this.formatValidators;
858
+ for (const [formatName, validator] of Object.entries(validators)) {
859
+ if (inlinedFormats.has(formatName))
860
+ continue;
861
+ if (validator instanceof RegExp || typeof validator === "function") {
862
+ this.resolve$DataFormat(validator, formatName, inlinedFormats, code);
863
+ }
864
+ else if (typeof validator === "object" && "validate" in validator) {
865
+ this.resolve$DataFormat(validator.validate, formatName, inlinedFormats, code);
866
+ }
867
+ }
868
+ }
869
+ code.push("\n");
870
+ }
871
+ resolve$DataFormat(validator, formatName, inlinedFormats, code) {
872
+ if (validator instanceof RegExp) {
873
+ const safeName = this.getSafeFormatName(formatName);
874
+ code.push(`const ${safeName} = new RegExp(${JSON.stringify(validator.source)}, '${validator.flags}');\n`);
875
+ inlinedFormats.add(formatName);
876
+ }
877
+ else if (typeof validator === "function") {
878
+ this.inlineFormatWithDeps(code, formatName, inlinedFormats);
879
+ }
880
+ }
881
+ createFormatObject(code, config, inlinedFormats) {
882
+ code.push("// Format object for $data access\n");
883
+ code.push("const formatValidators = {\n");
884
+ const formatsToMap = [];
885
+ if (config.formats && config.formats.length > 0) {
886
+ formatsToMap.push(...config.formats);
887
+ }
888
+ else if (config.formatMode === "fast" || config.formatMode === "full") {
889
+ const validators = config.formatMode === "fast"
890
+ ? formats_1.FAST_FORMAT_VALIDATORS
891
+ : formats_1.FULL_FORMAT_VALIDATORS;
892
+ formatsToMap.push(...Object.keys(validators));
893
+ }
894
+ for (const formatName of formatsToMap) {
895
+ if (inlinedFormats.has(formatName)) {
896
+ const safeName = this.getSafeFormatName(formatName);
897
+ code.push(` "${formatName}": ${safeName},\n`);
898
+ }
899
+ }
900
+ code.push("};\n\n");
901
+ }
902
+ getSafeFormatName(formatName) {
903
+ return "format_" + formatName.replace(/[^a-zA-Z0-9]/g, "_");
904
+ }
905
+ formatNeedsExternalDeps(formatName) {
906
+ const formatsWithDeps = new Set(["date-time", "iso-date-time", "time"]);
907
+ return formatsWithDeps.has(formatName);
908
+ }
909
+ inlineFormatWithDeps(code, formatName, inlinedFormats) {
910
+ const validator = this.formatValidators[formatName];
911
+ if (!validator || typeof validator !== "function")
912
+ return;
913
+ if (inlinedFormats.has(formatName))
914
+ return;
915
+ switch (formatName) {
916
+ case "date-time":
917
+ code.push(`// date-time format with dependencies\n`);
918
+ if (!inlinedFormats.has("date")) {
919
+ code.push(this.serializeFormatFunction("date"));
920
+ inlinedFormats.add("date");
921
+ }
922
+ if (!inlinedFormats.has("time")) {
923
+ code.push(this.serializeFormatFunction("time"));
924
+ inlinedFormats.add("time");
925
+ }
926
+ code.push(`${validator.toString()};\n`);
927
+ inlinedFormats.add(formatName);
928
+ break;
929
+ case "iso-date-time":
930
+ code.push(`// iso-date-time format with dependencies\n`);
931
+ if (!inlinedFormats.has("date")) {
932
+ code.push(this.serializeFormatFunction("date"));
933
+ inlinedFormats.add("date");
934
+ }
935
+ if (!inlinedFormats.has("iso-time")) {
936
+ code.push(this.serializeFormatFunction("iso-time"));
937
+ inlinedFormats.add("iso-time");
938
+ }
939
+ code.push(`${validator.toString()};\n`);
940
+ inlinedFormats.add(formatName);
941
+ break;
942
+ case "time":
943
+ code.push(`// time format with dependencies\n`);
944
+ code.push(`${validator.toString()};\n`);
945
+ inlinedFormats.add(formatName);
946
+ break;
947
+ default:
948
+ code.push(`${validator.toString()};\n`);
949
+ inlinedFormats.add(formatName);
950
+ }
951
+ }
952
+ serializeFormatFunction(formatName) {
953
+ const validator = this.formatValidators[formatName];
954
+ if (!validator)
955
+ return "";
956
+ if (validator instanceof RegExp) {
957
+ const safeName = this.getSafeFormatName(formatName);
958
+ return `const ${safeName} = new RegExp(${JSON.stringify(validator.source)}, '${validator.flags}');\n`;
959
+ }
960
+ if (typeof validator === "function") {
961
+ return `${validator.toString()};\n`;
962
+ }
963
+ return "";
964
+ }
965
+ isSelfContained(fnString) {
966
+ const externalPatterns = [/\bimport\s+/, /\brequire\(/, /\bfetch\(/];
967
+ return !externalPatterns.some((pattern) => pattern.test(fnString));
968
+ }
969
+ generateFormatSetup(formatImports) {
970
+ const lines = [
971
+ "// Format validators that need to be provided",
972
+ "// Import these and pass them to the validator\n",
973
+ "const formatValidators = {",
974
+ ];
975
+ for (const format of formatImports) {
976
+ lines.push(` ${format}: /* import your ${format} validator */,`);
977
+ }
978
+ lines.push("};\n");
979
+ return lines.join("\n");
980
+ }
981
+ }
982
+ exports.JetValidator = JetValidator;
983
+ //# sourceMappingURL=jet-validator.js.map