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,21 +1,21 @@
1
1
  import type { JsonSchemaInternal } from './json-schema-versions.js';
2
2
  import type { ZSchemaBase } from './z-schema-base.js';
3
3
 
4
- import { getId, isInternalKey, NON_SCHEMA_KEYWORDS } from './json-schema.js';
4
+ import { getId, isInternalKey, NON_SCHEMA_KEYWORDS_SET } from './json-schema.js';
5
5
  import { Report } from './report.js';
6
6
  import { DEFAULT_MAX_RECURSION_DEPTH } from './utils/constants.js';
7
7
  import { getRemotePath, isAbsoluteUri } from './utils/uri.js';
8
8
  import { getSchemaReader } from './z-schema-reader.js';
9
9
 
10
- const UNSAFE_TARGETS = [
10
+ const UNSAFE_TARGETS = new Set<Record<string, unknown>>([
11
11
  Object.prototype as unknown as Record<string, unknown>,
12
12
  Function.prototype as unknown as Record<string, unknown>,
13
13
  Array.prototype as unknown as Record<string, unknown>,
14
- ];
14
+ ]);
15
15
 
16
16
  /** Returns true if `obj` is a built-in prototype that must not be mutated. */
17
17
  function isUnsafeTarget(obj: Record<string, unknown>): boolean {
18
- return UNSAFE_TARGETS.includes(obj);
18
+ return UNSAFE_TARGETS.has(obj);
19
19
  }
20
20
 
21
21
  /** Safely assign a property on `obj`, refusing prototype-polluting keys. */
@@ -47,10 +47,77 @@ interface Id {
47
47
  absoluteUri?: string;
48
48
  }
49
49
 
50
+ const resolveReference = (base: string | undefined, ref: string) => {
51
+ if (isAbsoluteUri(ref)) {
52
+ return ref;
53
+ }
54
+
55
+ const baseStr = base ?? '';
56
+
57
+ if (ref.startsWith('#')) {
58
+ const hashIndex = baseStr.indexOf('#');
59
+ const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
60
+ return baseNoFrag + ref;
61
+ }
62
+
63
+ if (!baseStr) {
64
+ return ref;
65
+ }
66
+
67
+ const hashIndex = baseStr.indexOf('#');
68
+ const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
69
+
70
+ if (isAbsoluteUri(baseNoFrag)) {
71
+ try {
72
+ return new URL(ref, baseNoFrag).toString();
73
+ } catch {
74
+ // fall back to manual resolution below
75
+ }
76
+ }
77
+
78
+ let baseDir = baseNoFrag;
79
+ if (!baseDir.endsWith('/')) {
80
+ const lastSlash = baseDir.lastIndexOf('/');
81
+ baseDir = lastSlash === -1 ? '' : baseDir.slice(0, lastSlash + 1);
82
+ }
83
+ return baseDir + ref;
84
+ };
85
+
86
+ const isSimpleIdentifier = (id: string) =>
87
+ !id.startsWith('#') && !id.includes('/') && !id.includes('.') && !id.includes('#');
88
+
89
+ const resolveIdScope = (base: string | undefined, id: string) => {
90
+ if (isAbsoluteUri(id)) {
91
+ return id;
92
+ }
93
+
94
+ const baseStr = base ?? '';
95
+
96
+ // Treat simple identifiers (no '/', '.', or '#') as same-document fragment ids
97
+ if (isSimpleIdentifier(id)) {
98
+ const hashIndex = baseStr.indexOf('#');
99
+ const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
100
+ return `${baseNoFrag}#${id}`;
101
+ }
102
+
103
+ return resolveReference(base, id);
104
+ };
105
+
106
+ const resolveSchemaScopeId = (base: string | undefined, schema: JsonSchemaInternal, id: string) => {
107
+ if (typeof schema.$id === 'string') {
108
+ return resolveReference(base, id);
109
+ }
110
+ return resolveIdScope(base, id);
111
+ };
112
+
50
113
  export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECURSION_DEPTH) => {
51
114
  const ids: Id[] = [];
52
115
  function walk(node: any, scope: Id[], _depth = 0) {
53
- if (typeof node !== 'object' || node == null) return;
116
+ if (typeof node !== 'object' || node == null) {
117
+ return;
118
+ }
119
+
120
+ const schemaNode = node as JsonSchemaInternal;
54
121
 
55
122
  if (_depth >= maxDepth) {
56
123
  throw new Error(
@@ -61,7 +128,7 @@ export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECUR
61
128
 
62
129
  let addedScope = false;
63
130
 
64
- const nodeId = getId(node as JsonSchemaInternal);
131
+ const nodeId = getId(schemaNode);
65
132
  if (typeof nodeId === 'string') {
66
133
  let type: Id['type'] = isAbsoluteUri(nodeId) ? 'absolute' : 'relative';
67
134
  if (scope.length === 0) {
@@ -70,19 +137,30 @@ export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECUR
70
137
  const id: Id = {
71
138
  id: nodeId,
72
139
  type,
73
- obj: node,
140
+ obj: schemaNode,
74
141
  };
75
142
  if (type === 'absolute' || (type === 'root' && isAbsoluteUri(nodeId))) {
76
143
  id.absoluteUri = nodeId;
77
- } else if (type === 'root' && typeof node.id === 'string' && isAbsoluteUri(node.id) && node.id !== nodeId) {
78
- id.absoluteUri = resolveSchemaScopeId(node.id, node as JsonSchemaInternal, nodeId);
144
+ } else if (
145
+ type === 'root' &&
146
+ typeof schemaNode.id === 'string' &&
147
+ isAbsoluteUri(schemaNode.id) &&
148
+ schemaNode.id !== nodeId
149
+ ) {
150
+ id.absoluteUri = resolveSchemaScopeId(schemaNode.id, schemaNode, nodeId);
79
151
  } else if (type === 'relative') {
80
- id.absoluteParent = scope
81
- .filter((x) => x.type === 'absolute' || (x.type === 'root' && x.absoluteUri))
82
- .slice(-1)[0];
152
+ let absoluteParent: Id | undefined;
153
+ for (let i = scope.length - 1; i >= 0; i--) {
154
+ const sc = scope[i];
155
+ if (sc.type === 'absolute' || (sc.type === 'root' && sc.absoluteUri)) {
156
+ absoluteParent = sc;
157
+ break;
158
+ }
159
+ }
160
+ id.absoluteParent = absoluteParent;
83
161
  if (id.absoluteParent) {
84
162
  const parentUri = id.absoluteParent.absoluteUri || id.absoluteParent.id;
85
- id.absoluteUri = resolveSchemaScopeId(parentUri, node as JsonSchemaInternal, id.id);
163
+ id.absoluteUri = resolveSchemaScopeId(parentUri, schemaNode, id.id);
86
164
  }
87
165
  }
88
166
  ids.push(id);
@@ -95,9 +173,11 @@ export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECUR
95
173
  walk(item, scope, _depth + 1);
96
174
  }
97
175
  } else {
98
- for (const key of Object.keys(node)) {
99
- if (isInternalKey(key) || NON_SCHEMA_KEYWORDS.includes(key as any)) continue;
100
- walk(node[key], scope, _depth + 1);
176
+ for (const key of Object.keys(schemaNode)) {
177
+ if (isInternalKey(key) || NON_SCHEMA_KEYWORDS_SET.has(key)) {
178
+ continue;
179
+ }
180
+ walk((schemaNode as Record<string, unknown>)[key], scope, _depth + 1);
101
181
  }
102
182
  }
103
183
 
@@ -125,10 +205,10 @@ export const collectReferences = (
125
205
  maxDepth = DEFAULT_MAX_RECURSION_DEPTH,
126
206
  _depth = 0
127
207
  ) => {
128
- results = results || [];
129
- scope = scope || [];
130
- path = path || [];
131
- options = options || {};
208
+ results ||= [];
209
+ scope ||= [];
210
+ path ||= [];
211
+ options ||= {};
132
212
 
133
213
  if (typeof obj !== 'object' || obj === null) {
134
214
  return results;
@@ -141,7 +221,7 @@ export const collectReferences = (
141
221
  );
142
222
  }
143
223
 
144
- const hasRef = typeof obj.$ref === 'string' && typeof obj.__$refResolved === 'undefined';
224
+ const hasRef = typeof obj.$ref === 'string' && obj.__$refResolved === undefined;
145
225
  let addedScope = false;
146
226
  const isRootScope = scope.length === 0;
147
227
  const objId = getId(obj);
@@ -151,40 +231,40 @@ export const collectReferences = (
151
231
  }
152
232
 
153
233
  if (typeof scopeId === 'string' && (isRootScope || !hasRef || options.useRefObjectScope === true)) {
154
- const base = scope.length > 0 ? scope[scope.length - 1] : undefined;
234
+ const base = scope.length > 0 ? scope.at(-1) : undefined;
155
235
  scope.push(resolveSchemaScopeId(base, obj, scopeId));
156
236
  addedScope = true;
157
237
  }
158
238
 
159
239
  if (hasRef) {
160
240
  results.push({
161
- ref: resolveReference(scope[scope.length - 1], obj.$ref!),
241
+ ref: resolveReference(scope.at(-1), obj.$ref!),
162
242
  key: '$ref',
163
- obj: obj,
243
+ obj,
164
244
  path: path.slice(0),
165
245
  });
166
246
  }
167
- if (typeof obj.$recursiveRef === 'string' && typeof obj.__$recursiveRefResolved === 'undefined') {
247
+ if (typeof obj.$recursiveRef === 'string' && obj.__$recursiveRefResolved === undefined) {
168
248
  results.push({
169
- ref: resolveReference(scope[scope.length - 1], obj.$recursiveRef),
249
+ ref: resolveReference(scope.at(-1), obj.$recursiveRef),
170
250
  key: '$recursiveRef',
171
- obj: obj,
251
+ obj,
172
252
  path: path.slice(0),
173
253
  });
174
254
  }
175
- if (typeof obj.$dynamicRef === 'string' && typeof obj.__$dynamicRefResolved === 'undefined') {
255
+ if (typeof obj.$dynamicRef === 'string' && obj.__$dynamicRefResolved === undefined) {
176
256
  results.push({
177
- ref: resolveReference(scope[scope.length - 1], obj.$dynamicRef),
257
+ ref: resolveReference(scope.at(-1), obj.$dynamicRef),
178
258
  key: '$dynamicRef',
179
- obj: obj,
259
+ obj,
180
260
  path: path.slice(0),
181
261
  });
182
262
  }
183
- if (typeof obj.$schema === 'string' && typeof obj.__$schemaResolved === 'undefined') {
263
+ if (typeof obj.$schema === 'string' && obj.__$schemaResolved === undefined) {
184
264
  results.push({
185
- ref: resolveReference(scope[scope.length - 1], obj.$schema),
265
+ ref: resolveReference(scope.at(-1), obj.$schema),
186
266
  key: '$schema',
187
- obj: obj,
267
+ obj,
188
268
  path: path.slice(0),
189
269
  });
190
270
  }
@@ -192,18 +272,26 @@ export const collectReferences = (
192
272
  if (Array.isArray(obj)) {
193
273
  for (let i = 0; i < obj.length; i++) {
194
274
  path.push(i);
195
- collectReferences(obj[i], results, scope, path, options, maxDepth, _depth + 1);
275
+ collectReferences(obj[i] as JsonSchemaInternal, results, scope, path, options, maxDepth, _depth + 1);
196
276
  path.pop();
197
277
  }
198
278
  } else {
199
279
  const keys = Object.keys(obj);
200
280
  for (const key of keys) {
201
281
  // do not recurse through resolved references and other z-schema props
202
- if (isInternalKey(key) || NON_SCHEMA_KEYWORDS.includes(key as any)) {
282
+ if (isInternalKey(key) || NON_SCHEMA_KEYWORDS_SET.has(key)) {
203
283
  continue;
204
284
  }
205
285
  path.push(key);
206
- collectReferences((obj as any)[key], results, scope, path, options, maxDepth, _depth + 1);
286
+ collectReferences(
287
+ (obj as Record<string, JsonSchemaInternal>)[key],
288
+ results,
289
+ scope,
290
+ path,
291
+ options,
292
+ maxDepth,
293
+ _depth + 1
294
+ );
207
295
  path.pop();
208
296
  }
209
297
  }
@@ -215,70 +303,12 @@ export const collectReferences = (
215
303
  return results;
216
304
  };
217
305
 
218
- const resolveReference = (base: string | undefined, ref: string) => {
219
- if (isAbsoluteUri(ref)) {
220
- return ref;
221
- }
222
-
223
- const baseStr = base ?? '';
224
-
225
- if (ref[0] === '#') {
226
- const hashIndex = baseStr.indexOf('#');
227
- const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
228
- return baseNoFrag + ref;
229
- }
230
-
231
- if (!baseStr) {
232
- return ref;
233
- }
234
-
235
- const hashIndex = baseStr.indexOf('#');
236
- const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
237
-
238
- if (isAbsoluteUri(baseNoFrag)) {
239
- try {
240
- return new URL(ref, baseNoFrag).toString();
241
- } catch {
242
- // fall back to manual resolution below
243
- }
244
- }
245
-
246
- let baseDir = baseNoFrag;
247
- if (!baseDir.endsWith('/')) {
248
- const lastSlash = baseDir.lastIndexOf('/');
249
- baseDir = lastSlash === -1 ? '' : baseDir.slice(0, lastSlash + 1);
250
- }
251
- return baseDir + ref;
252
- };
253
-
254
- const isSimpleIdentifier = (id: string) => id[0] !== '#' && !id.includes('/') && !id.includes('.') && !id.includes('#');
255
-
256
- const resolveIdScope = (base: string | undefined, id: string) => {
257
- if (isAbsoluteUri(id)) {
258
- return id;
259
- }
260
-
261
- const baseStr = base ?? '';
262
-
263
- // Treat simple identifiers (no '/', '.', or '#') as same-document fragment ids
264
- if (isSimpleIdentifier(id)) {
265
- const hashIndex = baseStr.indexOf('#');
266
- const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
267
- return baseNoFrag + '#' + id;
268
- }
269
-
270
- return resolveReference(base, id);
271
- };
306
+ export class SchemaCompiler {
307
+ private readonly validator: ZSchemaBase;
272
308
 
273
- const resolveSchemaScopeId = (base: string | undefined, schema: JsonSchemaInternal, id: string) => {
274
- if (typeof schema.$id === 'string') {
275
- return resolveReference(base, id);
309
+ constructor(validator: ZSchemaBase) {
310
+ this.validator = validator;
276
311
  }
277
- return resolveIdScope(base, id);
278
- };
279
-
280
- export class SchemaCompiler {
281
- constructor(private validator: ZSchemaBase) {}
282
312
 
283
313
  collectAndCacheIds(schema: JsonSchemaInternal) {
284
314
  const ids = collectIds(schema, this.validator.options.maxRecursionDepth);
@@ -303,7 +333,7 @@ export class SchemaCompiler {
303
333
  // if schema is a string, assume it's a uri
304
334
  if (typeof schema === 'string') {
305
335
  const loadedSchema = this.validator.scache.getSchemaByUri(report, schema);
306
- if (typeof loadedSchema === 'undefined') {
336
+ if (loadedSchema === undefined) {
307
337
  report.addError('SCHEMA_NOT_REACHABLE', [schema]);
308
338
  return false;
309
339
  }
@@ -313,15 +343,16 @@ export class SchemaCompiler {
313
343
  // if schema is an array, assume it's an array of schemas
314
344
  if (Array.isArray(schema)) {
315
345
  if (!options?.noCache) {
316
- schema.forEach((s) => this.collectAndCacheIds(s));
346
+ for (let i = 0; i < schema.length; i++) {
347
+ this.collectAndCacheIds(schema[i]);
348
+ }
317
349
  }
318
350
  return this.compileArrayOfSchemas(report, schema);
319
351
  } else if (typeof schema === 'boolean') {
320
352
  return true;
321
- } else {
322
- if (!options?.noCache) {
323
- this.collectAndCacheIds(schema);
324
- }
353
+ }
354
+ if (!options?.noCache) {
355
+ this.collectAndCacheIds(schema);
325
356
  }
326
357
 
327
358
  const canMutateSchemaObject =
@@ -334,7 +365,7 @@ export class SchemaCompiler {
334
365
  canMutateSchemaObject &&
335
366
  schema.__$compiled &&
336
367
  schema.id &&
337
- this.validator.scache.checkCacheForUri(schema.id) === false
368
+ !this.validator.scache.checkCacheForUri(schema.id)
338
369
  ) {
339
370
  schema.__$compiled = undefined;
340
371
  }
@@ -383,7 +414,7 @@ export class SchemaCompiler {
383
414
  let response = this.validator.scache.getSchemaByUri(report, refObj.ref, schema);
384
415
 
385
416
  // we can try to use custom schemaReader if available
386
- if (typeof response === 'undefined') {
417
+ if (response === undefined) {
387
418
  const schemaReader = getSchemaReader();
388
419
  if (schemaReader) {
389
420
  const remotePath = getRemotePath(refObj.ref);
@@ -395,17 +426,19 @@ export class SchemaCompiler {
395
426
  (s as JsonSchemaInternal).id = remotePath;
396
427
  // try to compile the schema
397
428
  const subreport = new Report(report);
398
- if (!this.compileSchema(subreport, s)) {
399
- // copy errors to report
400
- report.errors = report.errors.concat(subreport.errors);
401
- } else {
429
+ if (this.compileSchema(subreport, s)) {
402
430
  response = this.validator.scache.getSchemaByUri(report, refObj.ref, schema);
431
+ } else {
432
+ // copy errors to report
433
+ for (let i = 0; i < subreport.errors.length; i++) {
434
+ report.errors.push(subreport.errors[i]);
435
+ }
403
436
  }
404
437
  }
405
438
  }
406
439
  }
407
440
 
408
- if (typeof response === 'undefined') {
441
+ if (response === undefined) {
409
442
  const hasNotValid = report.hasError('REMOTE_NOT_VALID', [refObj.ref]);
410
443
  const isAbsolute = isAbsoluteUri(refObj.ref);
411
444
  let isDownloaded = false;
@@ -424,9 +457,12 @@ export class SchemaCompiler {
424
457
  } else if (isDownloaded) {
425
458
  // remote is downloaded, so no UNRESOLVABLE_REFERENCE
426
459
  } else {
427
- report.path.push(...refObj.path);
460
+ const pathLen = refObj.path.length;
461
+ for (let i = 0; i < pathLen; i++) {
462
+ report.path.push(refObj.path[i]);
463
+ }
428
464
  report.addError('UNRESOLVABLE_REFERENCE', [refObj.ref]);
429
- report.path = report.path.slice(0, -refObj.path.length);
465
+ report.path.length -= pathLen;
430
466
 
431
467
  // publish unresolved references out
432
468
  if (
@@ -434,7 +470,7 @@ export class SchemaCompiler {
434
470
  canMutateSchemaObject &&
435
471
  !isUnsafeTarget(schema as unknown as Record<string, unknown>)
436
472
  ) {
437
- schema.__$missingReferences = schema.__$missingReferences || [];
473
+ schema.__$missingReferences ||= [];
438
474
  schema.__$missingReferences.push(refObj);
439
475
  }
440
476
  }
@@ -469,7 +505,13 @@ export class SchemaCompiler {
469
505
 
470
506
  do {
471
507
  // remove all UNRESOLVABLE_REFERENCE errors before compiling array again
472
- report.errors = report.errors.filter((e) => e.code !== 'UNRESOLVABLE_REFERENCE');
508
+ let wi = 0;
509
+ for (let ri = 0; ri < report.errors.length; ri++) {
510
+ if (report.errors[ri].code !== 'UNRESOLVABLE_REFERENCE') {
511
+ report.errors[wi++] = report.errors[ri];
512
+ }
513
+ }
514
+ report.errors.length = wi;
473
515
 
474
516
  // remember how many were compiled in the last loop
475
517
  lastLoopCompiled = compiled;
@@ -478,11 +520,20 @@ export class SchemaCompiler {
478
520
  compiled = this.compileArrayOfSchemasLoop(report, arr);
479
521
 
480
522
  // fix __$missingReferences if possible
523
+ // Keep the FIRST schema per id to match the prior `arr.find(x => x.id === ref)`
524
+ // semantics (first match wins) when an array contains duplicate ids.
525
+ const idMap = new Map<string, JsonSchemaInternal>();
526
+ for (let i = 0; i < arr.length; i++) {
527
+ const schemaId = arr[i].id;
528
+ if (schemaId && !idMap.has(schemaId)) {
529
+ idMap.set(schemaId, arr[i]);
530
+ }
531
+ }
481
532
  for (const sch of arr) {
482
533
  if (sch.__$missingReferences) {
483
534
  for (let idx2 = sch.__$missingReferences.length - 1; idx2 >= 0; idx2--) {
484
535
  const refObj = sch.__$missingReferences[idx2];
485
- const response = arr.find((x) => x.id === refObj.ref);
536
+ const response = idMap.get(refObj.ref);
486
537
  if (response) {
487
538
  // this might create circular references
488
539
  safeSetProperty(refObj.obj as unknown as Record<string, unknown>, `__${refObj.key}Resolved`, response);
@@ -514,7 +565,9 @@ export class SchemaCompiler {
514
565
  }
515
566
 
516
567
  // copy errors to report
517
- mainReport.errors = mainReport.errors.concat(report.errors);
568
+ for (let i = 0; i < report.errors.length; i++) {
569
+ mainReport.errors.push(report.errors[i]);
570
+ }
518
571
  }
519
572
 
520
573
  return compiledCount;