z-schema 12.1.1 → 12.3.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.
Files changed (110) hide show
  1. package/README.md +2 -2
  2. package/bin/z-schema +4 -5
  3. package/cjs/{index.js → index.cjs} +696 -687
  4. package/cjs/{index.d.ts → index.d.cts} +47 -26
  5. package/dist/{errors.d.mts → errors.d.ts} +2 -2
  6. package/dist/{errors.mjs → errors.js} +1 -2
  7. package/dist/{format-validators.mjs → format-validators.js} +43 -36
  8. package/dist/{index.d.mts → index.d.ts} +9 -9
  9. package/dist/{index.mjs → index.js} +3 -3
  10. package/dist/{json-schema-versions.d.mts → json-schema-versions.d.ts} +34 -3
  11. package/dist/{json-schema.d.mts → json-schema.d.ts} +7 -7
  12. package/dist/{json-schema.mjs → json-schema.js} +7 -12
  13. package/dist/{json-validation.mjs → json-validation.js} +143 -127
  14. package/dist/{report.d.mts → report.d.ts} +7 -8
  15. package/dist/{report.mjs → report.js} +28 -31
  16. package/dist/{schema-cache.d.mts → schema-cache.d.ts} +4 -4
  17. package/dist/{schema-cache.mjs → schema-cache.js} +10 -11
  18. package/dist/{schema-compiler.d.mts → schema-compiler.d.ts} +4 -4
  19. package/dist/{schema-compiler.mjs → schema-compiler.js} +95 -77
  20. package/dist/{schema-validator.d.mts → schema-validator.d.ts} +5 -5
  21. package/dist/{schema-validator.mjs → schema-validator.js} +138 -166
  22. package/dist/utils/{array.mjs → array.js} +4 -3
  23. package/dist/utils/{base64.mjs → base64.js} +3 -2
  24. package/dist/utils/{clone.mjs → clone.js} +18 -20
  25. package/dist/utils/{hostname.mjs → hostname.js} +19 -22
  26. package/dist/utils/{json.mjs → json.js} +11 -7
  27. package/dist/utils/{schema-regex.mjs → schema-regex.js} +5 -5
  28. package/dist/utils/{time.mjs → time.js} +5 -5
  29. package/dist/utils/unicode.js +22 -0
  30. package/dist/utils/{what-is.mjs → what-is.js} +1 -2
  31. package/dist/validation/{array.mjs → array.js} +18 -20
  32. package/dist/validation/{combinators.mjs → combinators.js} +16 -16
  33. package/dist/validation/{numeric.mjs → numeric.js} +11 -11
  34. package/dist/validation/{object.mjs → object.js} +35 -34
  35. package/dist/validation/{ref.mjs → ref.js} +4 -4
  36. package/dist/validation/{shared.mjs → shared.js} +12 -11
  37. package/dist/validation/{string.mjs → string.js} +32 -32
  38. package/dist/validation/type.js +34 -0
  39. package/dist/{z-schema-base.d.mts → z-schema-base.d.ts} +11 -12
  40. package/dist/{z-schema-base.mjs → z-schema-base.js} +45 -40
  41. package/dist/{z-schema-options.d.mts → z-schema-options.d.ts} +3 -3
  42. package/dist/{z-schema-options.mjs → z-schema-options.js} +4 -4
  43. package/dist/{z-schema-reader.d.mts → z-schema-reader.d.ts} +1 -1
  44. package/dist/{z-schema-versions.mjs → z-schema-versions.js} +21 -21
  45. package/dist/{z-schema.d.mts → z-schema.d.ts} +5 -13
  46. package/dist/{z-schema.mjs → z-schema.js} +37 -47
  47. package/package.json +25 -23
  48. package/src/errors.ts +1 -2
  49. package/src/format-validators.ts +139 -59
  50. package/src/json-schema-versions.ts +56 -2
  51. package/src/json-schema.ts +10 -9
  52. package/src/json-validation.ts +189 -146
  53. package/src/report.ts +37 -49
  54. package/src/schema-cache.ts +13 -13
  55. package/src/schema-compiler.ts +170 -117
  56. package/src/schema-validator.ts +239 -238
  57. package/src/utils/array.ts +9 -6
  58. package/src/utils/base64.ts +13 -2
  59. package/src/utils/clone.ts +28 -30
  60. package/src/utils/date.ts +6 -3
  61. package/src/utils/hostname.ts +27 -27
  62. package/src/utils/json.ts +16 -9
  63. package/src/utils/properties.ts +2 -2
  64. package/src/utils/schema-regex.ts +4 -4
  65. package/src/utils/time.ts +5 -5
  66. package/src/utils/unicode.ts +12 -5
  67. package/src/utils/what-is.ts +1 -5
  68. package/src/validation/array.ts +24 -22
  69. package/src/validation/combinators.ts +14 -14
  70. package/src/validation/numeric.ts +14 -28
  71. package/src/validation/object.ts +32 -36
  72. package/src/validation/ref.ts +5 -6
  73. package/src/validation/shared.ts +22 -21
  74. package/src/validation/string.ts +29 -39
  75. package/src/validation/type.ts +17 -17
  76. package/src/z-schema-base.ts +49 -38
  77. package/src/z-schema-options.ts +4 -3
  78. package/src/z-schema.ts +35 -45
  79. package/umd/ZSchema.js +723 -697
  80. package/umd/ZSchema.min.js +2 -2
  81. package/umd/package.json +3 -0
  82. package/dist/utils/unicode.mjs +0 -12
  83. package/dist/validation/type.mjs +0 -32
  84. /package/dist/{format-validators.d.mts → format-validators.d.ts} +0 -0
  85. /package/dist/{json-schema-versions.mjs → json-schema-versions.js} +0 -0
  86. /package/dist/schemas/{draft-04-schema.mjs → draft-04-schema.js} +0 -0
  87. /package/dist/schemas/{draft-06-schema.mjs → draft-06-schema.js} +0 -0
  88. /package/dist/schemas/{draft-07-schema.mjs → draft-07-schema.js} +0 -0
  89. /package/dist/schemas/{draft-2019-09-meta-applicator.mjs → draft-2019-09-meta-applicator.js} +0 -0
  90. /package/dist/schemas/{draft-2019-09-meta-content.mjs → draft-2019-09-meta-content.js} +0 -0
  91. /package/dist/schemas/{draft-2019-09-meta-core.mjs → draft-2019-09-meta-core.js} +0 -0
  92. /package/dist/schemas/{draft-2019-09-meta-format.mjs → draft-2019-09-meta-format.js} +0 -0
  93. /package/dist/schemas/{draft-2019-09-meta-meta-data.mjs → draft-2019-09-meta-meta-data.js} +0 -0
  94. /package/dist/schemas/{draft-2019-09-meta-validation.mjs → draft-2019-09-meta-validation.js} +0 -0
  95. /package/dist/schemas/{draft-2019-09-schema.mjs → draft-2019-09-schema.js} +0 -0
  96. /package/dist/schemas/{draft-2020-12-meta-applicator.mjs → draft-2020-12-meta-applicator.js} +0 -0
  97. /package/dist/schemas/{draft-2020-12-meta-content.mjs → draft-2020-12-meta-content.js} +0 -0
  98. /package/dist/schemas/{draft-2020-12-meta-core.mjs → draft-2020-12-meta-core.js} +0 -0
  99. /package/dist/schemas/{draft-2020-12-meta-format-annotation.mjs → draft-2020-12-meta-format-annotation.js} +0 -0
  100. /package/dist/schemas/{draft-2020-12-meta-format-assertion.mjs → draft-2020-12-meta-format-assertion.js} +0 -0
  101. /package/dist/schemas/{draft-2020-12-meta-meta-data.mjs → draft-2020-12-meta-meta-data.js} +0 -0
  102. /package/dist/schemas/{draft-2020-12-meta-unevaluated.mjs → draft-2020-12-meta-unevaluated.js} +0 -0
  103. /package/dist/schemas/{draft-2020-12-meta-validation.mjs → draft-2020-12-meta-validation.js} +0 -0
  104. /package/dist/schemas/{draft-2020-12-schema.mjs → draft-2020-12-schema.js} +0 -0
  105. /package/dist/utils/{constants.mjs → constants.js} +0 -0
  106. /package/dist/utils/{date.mjs → date.js} +0 -0
  107. /package/dist/utils/{properties.mjs → properties.js} +0 -0
  108. /package/dist/utils/{symbols.mjs → symbols.js} +0 -0
  109. /package/dist/utils/{uri.mjs → uri.js} +0 -0
  110. /package/dist/{z-schema-reader.mjs → z-schema-reader.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  import type { JsonSchemaAll, JsonSchemaInternal } from './json-schema-versions.js';
2
+ import type { JsonValidatorFn } from './validation/shared.js';
2
3
  import type { ZSchemaBase } from './z-schema-base.js';
3
4
 
4
5
  import { getId } from './json-schema.js';
@@ -48,7 +49,6 @@ import { resolveDynamicRef, resolveRecursiveRef } from './validation/ref.js';
48
49
  import {
49
50
  getCachedValidationResult,
50
51
  isValidationVocabularyEnabled,
51
- type JsonValidatorFn,
52
52
  VALIDATION_VOCAB_KEYWORDS,
53
53
  } from './validation/shared.js';
54
54
  import {
@@ -65,34 +65,34 @@ import { constValidator, enumValidator, typeValidator } from './validation/type.
65
65
  // collectEvaluated — unified traversal for unevaluatedItems / unevaluatedProperties
66
66
  // ---------------------------------------------------------------------------
67
67
 
68
- type CollectEvaluatedItemsArgs = {
68
+ interface CollectEvaluatedItemsArgs {
69
69
  report: Report;
70
70
  currentSchema: JsonSchemaInternal | boolean | undefined;
71
71
  json: unknown;
72
72
  mode: 'items';
73
73
  jsonArr: unknown[];
74
74
  depth: number;
75
- };
75
+ }
76
76
 
77
- type CollectEvaluatedPropertiesArgs = {
77
+ interface CollectEvaluatedPropertiesArgs {
78
78
  report: Report;
79
79
  currentSchema: JsonSchemaInternal | boolean | undefined;
80
80
  json: unknown;
81
81
  mode: 'properties';
82
82
  jsonData: Record<string, unknown>;
83
83
  depth: number;
84
- };
84
+ }
85
85
 
86
86
  type CollectEvaluatedArgs = CollectEvaluatedItemsArgs | CollectEvaluatedPropertiesArgs;
87
87
 
88
- function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<number | string> | 'all' {
88
+ function collectEvaluated(ctx: ZSchemaBase, args: CollectEvaluatedArgs): Set<number | string> | 'all' {
89
89
  const { report, currentSchema, json, mode, depth } = args;
90
90
 
91
91
  if (!currentSchema || typeof currentSchema === 'boolean') {
92
92
  return new Set();
93
93
  }
94
94
 
95
- if (depth > (this.options.maxRecursionDepth ?? 100)) {
95
+ if (depth > (ctx.options.maxRecursionDepth ?? 100)) {
96
96
  report.addError('COLLECT_EVALUATED_DEPTH_EXCEEDED', [depth]);
97
97
  return new Set();
98
98
  }
@@ -100,7 +100,9 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
100
100
  const evaluated = new Set<number | string>();
101
101
 
102
102
  const merge = (other: Set<number | string> | 'all') => {
103
- if (other === 'all') return true;
103
+ if (other === 'all') {
104
+ return true;
105
+ }
104
106
  for (const v of other) {
105
107
  evaluated.add(v);
106
108
  }
@@ -109,28 +111,28 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
109
111
 
110
112
  const recurse = (subSchema: JsonSchemaInternal | boolean | undefined): Set<number | string> | 'all' => {
111
113
  if (mode === 'items') {
112
- return collectEvaluated.call(this, {
114
+ return collectEvaluated(ctx, {
113
115
  report,
114
116
  currentSchema: subSchema,
115
117
  json,
116
118
  mode: 'items',
117
- jsonArr: (args as CollectEvaluatedItemsArgs).jsonArr,
119
+ jsonArr: args.jsonArr,
118
120
  depth: depth + 1,
119
121
  });
120
122
  }
121
- return collectEvaluated.call(this, {
123
+ return collectEvaluated(ctx, {
122
124
  report,
123
125
  currentSchema: subSchema,
124
126
  json,
125
127
  mode: 'properties',
126
- jsonData: (args as CollectEvaluatedPropertiesArgs).jsonData,
128
+ jsonData: args.jsonData,
127
129
  depth: depth + 1,
128
130
  });
129
131
  };
130
132
 
131
133
  // --- Mode-specific leaf collection ---
132
134
  if (mode === 'items') {
133
- const jsonArr = (args as CollectEvaluatedItemsArgs).jsonArr;
135
+ const { jsonArr } = args;
134
136
 
135
137
  // prefixItems (2020-12 tuple)
136
138
  if (Array.isArray(currentSchema.prefixItems)) {
@@ -167,7 +169,7 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
167
169
  let passed = getCachedValidationResult(report, currentSchema.contains, jsonArr[i]);
168
170
  if (passed === undefined) {
169
171
  const subReport = new Report(report);
170
- validate.call(this, subReport, currentSchema.contains as JsonSchemaInternal | boolean, jsonArr[i]);
172
+ validate(ctx, subReport, currentSchema.contains as JsonSchemaInternal | boolean, jsonArr[i]);
171
173
  passed = subReport.errors.length === 0;
172
174
  }
173
175
  if (passed) {
@@ -182,11 +184,13 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
182
184
  }
183
185
  } else {
184
186
  // mode === 'properties'
185
- const jsonData = (args as CollectEvaluatedPropertiesArgs).jsonData;
187
+ const { jsonData } = args;
186
188
 
187
189
  // properties
188
190
  if (isObject(currentSchema.properties)) {
189
- for (const key of Object.keys(currentSchema.properties)) {
191
+ const propKeysCE = Object.keys(currentSchema.properties);
192
+ for (let i = 0; i < propKeysCE.length; i++) {
193
+ const key = propKeysCE[i];
190
194
  if (Object.hasOwn(jsonData, key)) {
191
195
  evaluated.add(key);
192
196
  }
@@ -198,9 +202,10 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
198
202
  for (const pattern of Object.keys(currentSchema.patternProperties)) {
199
203
  const result = compileSchemaRegex(pattern);
200
204
  if (result.ok) {
201
- for (const key of Object.keys(jsonData)) {
202
- if (result.value.test(key)) {
203
- evaluated.add(key);
205
+ const jdKeys = Object.keys(jsonData);
206
+ for (let i = 0; i < jdKeys.length; i++) {
207
+ if (result.value.test(jdKeys[i])) {
208
+ evaluated.add(jdKeys[i]);
204
209
  }
205
210
  }
206
211
  }
@@ -209,7 +214,7 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
209
214
 
210
215
  // additionalProperties - evaluates all non-properties/non-patternProperties keys
211
216
  if (currentSchema.additionalProperties !== undefined) {
212
- const propKeys = isObject(currentSchema.properties) ? Object.keys(currentSchema.properties) : [];
217
+ const propKeySet = new Set(isObject(currentSchema.properties) ? Object.keys(currentSchema.properties) : []);
213
218
  const patternRegexes: RegExp[] = [];
214
219
  if (isObject(currentSchema.patternProperties)) {
215
220
  for (const pattern of Object.keys(currentSchema.patternProperties)) {
@@ -219,9 +224,22 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
219
224
  }
220
225
  }
221
226
  }
222
- for (const key of Object.keys(jsonData)) {
223
- if (propKeys.includes(key)) continue;
224
- if (patternRegexes.some((re) => re.test(key))) continue;
227
+ const apKeys = Object.keys(jsonData);
228
+ for (let i = 0; i < apKeys.length; i++) {
229
+ const key = apKeys[i];
230
+ if (propKeySet.has(key)) {
231
+ continue;
232
+ }
233
+ let matchedPattern = false;
234
+ for (let pi = 0; pi < patternRegexes.length; pi++) {
235
+ if (patternRegexes[pi].test(key)) {
236
+ matchedPattern = true;
237
+ break;
238
+ }
239
+ }
240
+ if (matchedPattern) {
241
+ continue;
242
+ }
225
243
  evaluated.add(key);
226
244
  }
227
245
  }
@@ -229,10 +247,8 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
229
247
  // dependentSchemas - only applies when the dependency key is present in the data
230
248
  if (isObject(currentSchema.dependentSchemas)) {
231
249
  for (const [depKey, depSchema] of Object.entries(currentSchema.dependentSchemas as Record<string, unknown>)) {
232
- if (Object.hasOwn(jsonData, depKey)) {
233
- if (merge(recurse(depSchema as JsonSchemaInternal | boolean))) {
234
- return 'all';
235
- }
250
+ if (Object.hasOwn(jsonData, depKey) && merge(recurse(depSchema as JsonSchemaInternal | boolean))) {
251
+ return 'all';
236
252
  }
237
253
  }
238
254
  }
@@ -247,8 +263,8 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
247
263
 
248
264
  // allOf
249
265
  if (Array.isArray(currentSchema.allOf)) {
250
- for (const subSchema of currentSchema.allOf) {
251
- if (merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
266
+ for (let i = 0; i < currentSchema.allOf.length; i++) {
267
+ if (merge(recurse(currentSchema.allOf[i] as JsonSchemaInternal | boolean))) {
252
268
  return 'all';
253
269
  }
254
270
  }
@@ -256,34 +272,32 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
256
272
 
257
273
  // anyOf - only matching branches contribute
258
274
  if (Array.isArray(currentSchema.anyOf)) {
259
- for (const subSchema of currentSchema.anyOf) {
275
+ for (let i = 0; i < currentSchema.anyOf.length; i++) {
276
+ const subSchema = currentSchema.anyOf[i];
260
277
  let passed = getCachedValidationResult(report, subSchema, json);
261
278
  if (passed === undefined) {
262
279
  const subReport = new Report(report);
263
- validate.call(this, subReport, subSchema as JsonSchemaInternal | boolean, json);
280
+ validate(ctx, subReport, subSchema as JsonSchemaInternal | boolean, json);
264
281
  passed = subReport.errors.length === 0;
265
282
  }
266
- if (passed) {
267
- if (merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
268
- return 'all';
269
- }
283
+ if (passed && merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
284
+ return 'all';
270
285
  }
271
286
  }
272
287
  }
273
288
 
274
289
  // oneOf - only matching branches contribute
275
290
  if (Array.isArray(currentSchema.oneOf)) {
276
- for (const subSchema of currentSchema.oneOf) {
291
+ for (let i = 0; i < currentSchema.oneOf.length; i++) {
292
+ const subSchema = currentSchema.oneOf[i];
277
293
  let passed = getCachedValidationResult(report, subSchema, json);
278
294
  if (passed === undefined) {
279
295
  const subReport = new Report(report);
280
- validate.call(this, subReport, subSchema as JsonSchemaInternal | boolean, json);
296
+ validate(ctx, subReport, subSchema as JsonSchemaInternal | boolean, json);
281
297
  passed = subReport.errors.length === 0;
282
298
  }
283
- if (passed) {
284
- if (merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
285
- return 'all';
286
- }
299
+ if (passed && merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
300
+ return 'all';
287
301
  }
288
302
  }
289
303
  }
@@ -293,48 +307,40 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
293
307
  let condPassed = getCachedValidationResult(report, currentSchema.if, json);
294
308
  if (condPassed === undefined) {
295
309
  const condReport = new Report(report);
296
- validate.call(this, condReport, currentSchema.if as JsonSchemaInternal | boolean, json);
310
+ validate(ctx, condReport, currentSchema.if, json);
297
311
  condPassed = condReport.errors.length === 0;
298
312
  }
299
313
  if (condPassed) {
300
- if (merge(recurse(currentSchema.if as JsonSchemaInternal | boolean))) {
314
+ if (merge(recurse(currentSchema.if))) {
301
315
  return 'all';
302
316
  }
303
- if (currentSchema.then !== undefined) {
304
- if (merge(recurse(currentSchema.then as JsonSchemaInternal | boolean))) {
305
- return 'all';
306
- }
307
- }
308
- } else {
309
- if (currentSchema.else !== undefined) {
310
- if (merge(recurse(currentSchema.else as JsonSchemaInternal | boolean))) {
311
- return 'all';
312
- }
317
+ if (currentSchema.then !== undefined && merge(recurse(currentSchema.then))) {
318
+ return 'all';
313
319
  }
320
+ } else if (currentSchema.else !== undefined && merge(recurse(currentSchema.else))) {
321
+ return 'all';
314
322
  }
315
323
  }
316
324
 
317
325
  // $ref resolved
318
- if (currentSchema.__$refResolved && currentSchema.__$refResolved !== currentSchema) {
319
- if (merge(recurse(currentSchema.__$refResolved as JsonSchemaInternal))) {
320
- return 'all';
321
- }
326
+ if (
327
+ currentSchema.__$refResolved &&
328
+ currentSchema.__$refResolved !== currentSchema &&
329
+ merge(recurse(currentSchema.__$refResolved))
330
+ ) {
331
+ return 'all';
322
332
  }
323
333
 
324
334
  // $recursiveRef
325
335
  const recursiveTarget = resolveRecursiveRef(currentSchema, report.__$recursiveAnchorStack);
326
- if (recursiveTarget && recursiveTarget !== currentSchema) {
327
- if (merge(recurse(recursiveTarget))) {
328
- return 'all';
329
- }
336
+ if (recursiveTarget && recursiveTarget !== currentSchema && merge(recurse(recursiveTarget))) {
337
+ return 'all';
330
338
  }
331
339
 
332
340
  // $dynamicRef
333
341
  const dynamicTarget = resolveDynamicRef(currentSchema, report.__$dynamicScopeStack);
334
- if (dynamicTarget && dynamicTarget !== currentSchema) {
335
- if (merge(recurse(dynamicTarget as JsonSchemaInternal))) {
336
- return 'all';
337
- }
342
+ if (dynamicTarget && dynamicTarget !== currentSchema && merge(recurse(dynamicTarget as JsonSchemaInternal))) {
343
+ return 'all';
338
344
  }
339
345
 
340
346
  return evaluated;
@@ -344,7 +350,7 @@ function collectEvaluated(this: ZSchemaBase, args: CollectEvaluatedArgs): Set<nu
344
350
  // unevaluatedItems
345
351
  // ---------------------------------------------------------------------------
346
352
 
347
- function unevaluatedItemsValidator(this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
353
+ function unevaluatedItemsValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
348
354
  if (!Array.isArray(json)) {
349
355
  return;
350
356
  }
@@ -363,7 +369,7 @@ function unevaluatedItemsValidator(this: ZSchemaBase, report: Report, schema: Js
363
369
  return;
364
370
  }
365
371
 
366
- const evaluatedItems = collectEvaluated.call(this, {
372
+ const evaluatedItems = collectEvaluated(ctx, {
367
373
  report,
368
374
  currentSchema: schema,
369
375
  json,
@@ -391,9 +397,10 @@ function unevaluatedItemsValidator(this: ZSchemaBase, report: Report, schema: Js
391
397
  report.addError('ARRAY_UNEVALUATED_ITEMS', undefined, undefined, schema, 'unevaluatedItems');
392
398
  } else {
393
399
  // unevaluatedItems as a schema — validate each unevaluated item against it
394
- for (const idx of unevaluatedIndices) {
400
+ for (let i = 0; i < unevaluatedIndices.length; i++) {
401
+ const idx = unevaluatedIndices[i];
395
402
  const subReport = new Report(report);
396
- validate.call(this, subReport, unevalSchema as JsonSchemaInternal, json[idx]);
403
+ validate(ctx, subReport, unevalSchema, json[idx]);
397
404
  if (subReport.errors.length > 0) {
398
405
  report.addError('ARRAY_UNEVALUATED_ITEMS', undefined, undefined, schema, 'unevaluatedItems');
399
406
  break;
@@ -406,7 +413,7 @@ function unevaluatedItemsValidator(this: ZSchemaBase, report: Report, schema: Js
406
413
  // unevaluatedProperties
407
414
  // ---------------------------------------------------------------------------
408
415
 
409
- function unevaluatedPropertiesValidator(this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
416
+ function unevaluatedPropertiesValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
410
417
  if (!isObject(json)) {
411
418
  return;
412
419
  }
@@ -427,7 +434,7 @@ function unevaluatedPropertiesValidator(this: ZSchemaBase, report: Report, schem
427
434
  return;
428
435
  }
429
436
 
430
- const evaluatedProperties = collectEvaluated.call(this, {
437
+ const evaluatedProperties = collectEvaluated(ctx, {
431
438
  report,
432
439
  currentSchema: schema,
433
440
  json,
@@ -440,7 +447,12 @@ function unevaluatedPropertiesValidator(this: ZSchemaBase, report: Report, schem
440
447
  return;
441
448
  }
442
449
 
443
- const unevaluatedKeys = allKeys.filter((key) => !evaluatedProperties.has(key));
450
+ const unevaluatedKeys: string[] = [];
451
+ for (let i = 0; i < allKeys.length; i++) {
452
+ if (!evaluatedProperties.has(allKeys[i])) {
453
+ unevaluatedKeys.push(allKeys[i]);
454
+ }
455
+ }
444
456
 
445
457
  if (unevaluatedKeys.length === 0) {
446
458
  return;
@@ -456,9 +468,10 @@ function unevaluatedPropertiesValidator(this: ZSchemaBase, report: Report, schem
456
468
  );
457
469
  } else {
458
470
  // unevaluatedProperties as a schema — validate each unevaluated key against it
459
- for (const key of unevaluatedKeys) {
471
+ for (let i = 0; i < unevaluatedKeys.length; i++) {
472
+ const key = unevaluatedKeys[i];
460
473
  const subReport = new Report(report);
461
- validate.call(this, subReport, unevalSchema as JsonSchemaInternal, (json as Record<string, unknown>)[key]);
474
+ validate(ctx, subReport, unevalSchema, (json as Record<string, unknown>)[key]);
462
475
  if (subReport.errors.length > 0) {
463
476
  report.addError('OBJECT_UNEVALUATED_PROPERTIES', [key], undefined, schema, 'unevaluatedProperties');
464
477
  }
@@ -480,23 +493,27 @@ function definitionsValidator() {
480
493
  // JsonValidators — keyword dispatch table
481
494
  // ---------------------------------------------------------------------------
482
495
 
496
+ const noopValidator: JsonValidatorFn = () => {
497
+ // intentional no-op: metadata keyword or handled elsewhere in the pipeline
498
+ };
499
+
483
500
  export const JsonValidators: Record<keyof JsonSchemaAll, JsonValidatorFn> = {
484
501
  // no-op validators (metadata / handled elsewhere)
485
- id: () => {},
486
- $id: () => {},
487
- $ref: () => {},
488
- $schema: () => {},
489
- $dynamicAnchor: () => {},
490
- $dynamicRef: () => {},
491
- $anchor: () => {},
492
- $defs: () => {},
493
- $vocabulary: () => {},
494
- $recursiveAnchor: () => {},
495
- $recursiveRef: () => {},
496
- examples: () => {},
497
- title: () => {},
498
- description: () => {},
499
- default: () => {},
502
+ id: noopValidator,
503
+ $id: noopValidator,
504
+ $ref: noopValidator,
505
+ $schema: noopValidator,
506
+ $dynamicAnchor: noopValidator,
507
+ $dynamicRef: noopValidator,
508
+ $anchor: noopValidator,
509
+ $defs: noopValidator,
510
+ $vocabulary: noopValidator,
511
+ $recursiveAnchor: noopValidator,
512
+ $recursiveRef: noopValidator,
513
+ examples: noopValidator,
514
+ title: noopValidator,
515
+ description: noopValidator,
516
+ default: noopValidator,
500
517
 
501
518
  // type validators
502
519
  type: typeValidator,
@@ -560,25 +577,25 @@ export const JsonValidators: Record<keyof JsonSchemaAll, JsonValidatorFn> = {
560
577
  // recurseArray
561
578
  // ---------------------------------------------------------------------------
562
579
 
563
- const recurseArray = function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: Array<unknown>) {
580
+ function recurseArray(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown[]) {
564
581
  // http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.2
565
582
 
566
583
  const schemaUri = typeof schema.$schema === 'string' ? schema.$schema : undefined;
567
584
  const isDraft202012Schema =
568
585
  schemaUri === 'https://json-schema.org/draft/2020-12/schema' ||
569
- (!schemaUri && this.options.version === 'draft2020-12');
586
+ (!schemaUri && ctx.options.version === 'draft2020-12');
570
587
  const prefixItems = isDraft202012Schema && Array.isArray(schema.prefixItems) ? schema.prefixItems : undefined;
571
588
 
572
589
  if (prefixItems) {
573
590
  for (let idx = 0; idx < json.length; idx++) {
574
591
  if (idx < prefixItems.length) {
575
592
  report.path.push(idx);
576
- validate.call(this, report, prefixItems[idx], json[idx]);
593
+ validate(ctx, report, prefixItems[idx], json[idx]);
577
594
  report.path.pop();
578
595
  } else if (schema.items !== undefined && !Array.isArray(schema.items)) {
579
596
  report.path.push(idx);
580
597
  report.schemaPath.push('items');
581
- validate.call(this, report, schema.items, json[idx]);
598
+ validate(ctx, report, schema.items, json[idx]);
582
599
  report.schemaPath.pop();
583
600
  report.path.pop();
584
601
  }
@@ -595,15 +612,13 @@ const recurseArray = function (this: ZSchemaBase, report: Report, schema: JsonSc
595
612
  // equal to doesn't make sense here
596
613
  if (idx < schema.items.length) {
597
614
  report.path.push(idx);
598
- validate.call(this, report, schema.items[idx], json[idx]);
615
+ validate(ctx, report, schema.items[idx], json[idx]);
599
616
  report.path.pop();
600
- } else {
617
+ } else if (typeof schema.additionalItems === 'object') {
601
618
  // might be boolean, so check that it's an object
602
- if (typeof schema.additionalItems === 'object') {
603
- report.path.push(idx);
604
- validate.call(this, report, schema.additionalItems, json[idx]);
605
- report.path.pop();
606
- }
619
+ report.path.push(idx);
620
+ validate(ctx, report, schema.additionalItems, json[idx]);
621
+ report.path.pop();
607
622
  }
608
623
  }
609
624
  } else if (typeof schema.items === 'object' || typeof schema.items === 'boolean') {
@@ -613,23 +628,23 @@ const recurseArray = function (this: ZSchemaBase, report: Report, schema: JsonSc
613
628
  report.path.push(idx);
614
629
  // Track schema path for array items validation
615
630
  report.schemaPath.push('items');
616
- validate.call(this, report, schema.items, json[idx]);
631
+ validate(ctx, report, schema.items, json[idx]);
617
632
  report.schemaPath.pop();
618
633
  report.path.pop();
619
634
  }
620
635
  }
621
- };
636
+ }
622
637
 
623
638
  // ---------------------------------------------------------------------------
624
639
  // recurseObject
625
640
  // ---------------------------------------------------------------------------
626
641
 
627
- const recurseObject = function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: Record<any, any>) {
642
+ function recurseObject(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: Record<string, unknown>) {
628
643
  // http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.3
629
644
 
630
645
  // If "additionalProperties" is absent, it is considered present with an empty schema as a value.
631
646
  // In addition, boolean value true is considered equivalent to an empty schema.
632
- let additionalProperties = schema.additionalProperties;
647
+ let { additionalProperties } = schema;
633
648
  if (additionalProperties === true || additionalProperties === undefined) {
634
649
  additionalProperties = {};
635
650
  }
@@ -643,22 +658,33 @@ const recurseObject = function (this: ZSchemaBase, report: Report, schema: JsonS
643
658
  // m - The property name of the child.
644
659
  const keys = Object.keys(json);
645
660
 
661
+ // Precompile patternProperties regexes once before the per-key loop
662
+ const ppCompiled: Array<{ key: string; re: RegExp }> = [];
663
+ for (let i = 0; i < pp.length; i++) {
664
+ const r = compileSchemaRegex(pp[i]);
665
+ if (r.ok) {
666
+ ppCompiled.push({ key: pp[i], re: r.value });
667
+ }
668
+ }
669
+
646
670
  for (const m of keys) {
647
671
  const propertyValue = json[m];
648
672
 
673
+ // Hoist membership check: compute once per key
674
+ const isProp = p.includes(m);
675
+
649
676
  // s - The set of schemas for the child instance.
650
677
  const s = [];
651
678
 
652
679
  // 1. If set "p" contains value "m", then the corresponding schema in "properties" is added to "s".
653
- if (p.includes(m)) {
680
+ if (isProp) {
654
681
  s.push(schema.properties![m]);
655
682
  }
656
683
 
657
684
  // 2. For each regex in "pp", if it matches "m" successfully, the corresponding schema in "patternProperties" is added to "s".
658
- for (const regexString of pp) {
659
- const result = compileSchemaRegex(regexString);
660
- if (result.ok && result.value.test(m) === true) {
661
- s.push(schema.patternProperties![regexString]);
685
+ for (let i = 0; i < ppCompiled.length; i++) {
686
+ if (ppCompiled[i].re.test(m)) {
687
+ s.push(schema.patternProperties![ppCompiled[i].key]);
662
688
  }
663
689
  }
664
690
 
@@ -675,7 +701,7 @@ const recurseObject = function (this: ZSchemaBase, report: Report, schema: JsonS
675
701
  for (const schema_s of s) {
676
702
  report.path.push(m);
677
703
  // Track schema path for properties validation
678
- if (p.includes(m)) {
704
+ if (isProp) {
679
705
  // This is a defined property
680
706
  report.schemaPath.push('properties');
681
707
  report.schemaPath.push(m);
@@ -683,22 +709,22 @@ const recurseObject = function (this: ZSchemaBase, report: Report, schema: JsonS
683
709
  // This is additionalProperties or patternProperties
684
710
  report.schemaPath.push('additionalProperties');
685
711
  }
686
- validate.call(this, report, schema_s, propertyValue);
712
+ validate(ctx, report, schema_s, propertyValue);
687
713
  report.path.pop();
688
714
  report.schemaPath.pop();
689
- if (p.includes(m)) {
715
+ if (isProp) {
690
716
  report.schemaPath.pop(); // pop the property name for defined properties
691
717
  }
692
718
  }
693
719
  }
694
- };
720
+ }
695
721
 
696
722
  // ---------------------------------------------------------------------------
697
723
  // validate — main entry point
698
724
  // ---------------------------------------------------------------------------
699
725
 
700
726
  export function validate(
701
- this: ZSchemaBase,
727
+ ctx: ZSchemaBase,
702
728
  report: Report,
703
729
  schema: boolean | JsonSchemaInternal,
704
730
  json: unknown
@@ -738,9 +764,9 @@ export function validate(
738
764
  let pushedRecursiveAnchor = false;
739
765
  let pushedDynamicScope = false;
740
766
  const schemaId = getId(schema);
741
- const schemaResourceRoot = (schema as JsonSchemaInternal).__$resourceRoot;
767
+ const schemaResourceRoot = schema.__$resourceRoot;
742
768
  const dynamicScopeEntry = schemaResourceRoot || (isRoot || typeof schemaId === 'string' ? schema : undefined);
743
- if (dynamicScopeEntry && dynamicScopeStack[dynamicScopeStack.length - 1] !== dynamicScopeEntry) {
769
+ if (dynamicScopeEntry && dynamicScopeStack.at(-1) !== dynamicScopeEntry) {
744
770
  dynamicScopeStack.push(dynamicScopeEntry);
745
771
  pushedDynamicScope = true;
746
772
  }
@@ -752,15 +778,18 @@ export function validate(
752
778
  // follow schema.$ref keys
753
779
  if (schema.$ref !== undefined) {
754
780
  const applySiblingKeywordsWithRef =
755
- this.options.version === 'draft2019-09' || this.options.version === 'draft2020-12';
781
+ ctx.options.version === 'draft2019-09' || ctx.options.version === 'draft2020-12';
756
782
 
757
783
  if (applySiblingKeywordsWithRef) {
758
- if (!schema.__$refResolved) {
759
- report.addError('REF_UNRESOLVED', [schema.$ref], undefined, schema);
784
+ if (schema.__$refResolved) {
785
+ validate(ctx, report, schema.__$refResolved, json);
760
786
  } else {
761
- validate.call(this, report, schema.__$refResolved as JsonSchemaInternal, json);
787
+ report.addError('REF_UNRESOLVED', [schema.$ref], undefined, schema);
788
+ }
789
+ const refIdx = keys.indexOf('$ref');
790
+ if (refIdx !== -1) {
791
+ keys.splice(refIdx, 1);
762
792
  }
763
- keys = keys.filter((key) => key !== '$ref');
764
793
  } else {
765
794
  // avoid infinite loop with maxRefs
766
795
  let maxRefs = 99;
@@ -787,48 +816,60 @@ export function validate(
787
816
  // follow schema.$recursiveRef keys
788
817
  if (schema.$recursiveRef !== undefined) {
789
818
  const applySiblingKeywordsWithRecursiveRef =
790
- this.options.version === 'draft2019-09' || this.options.version === 'draft2020-12';
819
+ ctx.options.version === 'draft2019-09' || ctx.options.version === 'draft2020-12';
791
820
 
792
821
  if (applySiblingKeywordsWithRecursiveRef) {
793
822
  const recursiveRefTarget = resolveRecursiveRef(schema, recursiveAnchorStack);
794
823
 
795
- if (!recursiveRefTarget) {
796
- report.addError('REF_UNRESOLVED', [schema.$recursiveRef], undefined, schema);
824
+ if (recursiveRefTarget) {
825
+ validate(ctx, report, recursiveRefTarget, json);
797
826
  } else {
798
- validate.call(this, report, recursiveRefTarget, json);
827
+ report.addError('REF_UNRESOLVED', [schema.$recursiveRef], undefined, schema);
828
+ }
829
+ const recursiveRefIdx = keys.indexOf('$recursiveRef');
830
+ if (recursiveRefIdx !== -1) {
831
+ keys.splice(recursiveRefIdx, 1);
799
832
  }
800
- keys = keys.filter((key) => key !== '$recursiveRef');
801
833
  }
802
834
  }
803
835
 
804
836
  // follow schema.$dynamicRef keys
805
837
  if (schema.$dynamicRef !== undefined) {
806
- const applySiblingKeywordsWithDynamicRef = this.options.version === 'draft2020-12';
838
+ const applySiblingKeywordsWithDynamicRef = ctx.options.version === 'draft2020-12';
807
839
 
808
840
  if (applySiblingKeywordsWithDynamicRef) {
809
841
  const dynamicRefTarget = resolveDynamicRef(schema, dynamicScopeStack);
810
842
 
811
- if (typeof dynamicRefTarget === 'undefined') {
843
+ if (dynamicRefTarget === undefined) {
812
844
  report.addError('REF_UNRESOLVED', [schema.$dynamicRef], undefined, schema);
813
845
  } else {
814
- validate.call(this, report, dynamicRefTarget, json);
846
+ validate(ctx, report, dynamicRefTarget, json);
847
+ }
848
+ const dynamicRefIdx = keys.indexOf('$dynamicRef');
849
+ if (dynamicRefIdx !== -1) {
850
+ keys.splice(dynamicRefIdx, 1);
815
851
  }
816
- keys = keys.filter((key) => key !== '$dynamicRef');
817
852
  }
818
853
  }
819
854
 
820
- const validationVocabularyEnabled = isValidationVocabularyEnabled(schema, report, this.options.version);
855
+ const validationVocabularyEnabled = isValidationVocabularyEnabled(schema, report, ctx.options.version);
821
856
  if (!validationVocabularyEnabled) {
822
- keys = keys.filter((key) => !VALIDATION_VOCAB_KEYWORDS.has(key));
857
+ let wi = 0;
858
+ for (let ri = 0; ri < keys.length; ri++) {
859
+ if (!VALIDATION_VOCAB_KEYWORDS.has(keys[ri])) {
860
+ keys[wi++] = keys[ri];
861
+ }
862
+ }
863
+ keys.length = wi;
823
864
  }
824
865
 
825
866
  // type checking first
826
867
  if (validationVocabularyEnabled && schema.type) {
827
868
  keys.splice(keys.indexOf('type'), 1);
828
869
  report.schemaPath.push('type');
829
- JsonValidators.type.call(this, report, schema, json);
870
+ JsonValidators.type(ctx, report, schema, json);
830
871
  report.schemaPath.pop();
831
- if (report.errors.length && this.options.breakOnFirstError) {
872
+ if (report.errors.length && ctx.options.breakOnFirstError) {
832
873
  if (pushedRecursiveAnchor) {
833
874
  recursiveAnchorStack.pop();
834
875
  }
@@ -843,43 +884,45 @@ export function validate(
843
884
  // Defer unevaluatedItems/unevaluatedProperties to run after other validators,
844
885
  // so combinator validation results are cached and available for annotation collection
845
886
  const deferredUnevaluatedKeys: Array<keyof JsonSchemaAll> = [];
846
- for (const key of keys) {
887
+ for (let i = 0; i < keys.length; i++) {
888
+ const key = keys[i];
847
889
  if (key === 'unevaluatedItems' || key === 'unevaluatedProperties') {
848
890
  deferredUnevaluatedKeys.push(key);
849
891
  continue;
850
892
  }
851
893
  const validator = JsonValidators[key];
852
894
  if (validator) {
853
- validator.call(this, report, schema, json);
854
- if (report.errors.length && this.options.breakOnFirstError) {
895
+ validator(ctx, report, schema, json);
896
+ if (report.errors.length && ctx.options.breakOnFirstError) {
855
897
  break;
856
898
  }
857
899
  }
858
900
  }
859
901
 
860
902
  // Run unevaluated* validators after all others have cached their combinator results
861
- if (deferredUnevaluatedKeys.length > 0 && !(report.errors.length > 0 && this.options.breakOnFirstError)) {
862
- for (const key of deferredUnevaluatedKeys) {
903
+ if (deferredUnevaluatedKeys.length > 0 && !(report.errors.length > 0 && ctx.options.breakOnFirstError)) {
904
+ for (let i = 0; i < deferredUnevaluatedKeys.length; i++) {
905
+ const key = deferredUnevaluatedKeys[i];
863
906
  const validator = JsonValidators[key];
864
907
  if (validator) {
865
- validator.call(this, report, schema, json);
866
- if (report.errors.length && this.options.breakOnFirstError) {
908
+ validator(ctx, report, schema, json);
909
+ if (report.errors.length && ctx.options.breakOnFirstError) {
867
910
  break;
868
911
  }
869
912
  }
870
913
  }
871
914
  }
872
915
 
873
- if (report.errors.length === 0 || this.options.breakOnFirstError === false) {
916
+ if (report.errors.length === 0 || ctx.options.breakOnFirstError === false) {
874
917
  if (Array.isArray(json)) {
875
- recurseArray.call(this, report, schema, json);
918
+ recurseArray(ctx, report, schema, json);
876
919
  } else if (isObject(json)) {
877
- recurseObject.call(this, report, schema, json);
920
+ recurseObject(ctx, report, schema, json);
878
921
  }
879
922
  }
880
923
 
881
- if (typeof this.options.customValidator === 'function') {
882
- this.options.customValidator.call(this, report, schema, json);
924
+ if (typeof ctx.options.customValidator === 'function') {
925
+ ctx.options.customValidator.call(ctx, report, schema, json);
883
926
  }
884
927
 
885
928
  if (pushedRecursiveAnchor) {