z-schema 12.3.1 → 12.4.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.
@@ -3,6 +3,94 @@ import punycode from "punycode/punycode.js";
3
3
  //#region src/utils/hostname.ts
4
4
  const IDN_SEPARATOR_REGEX = /[\u3002\uFF0E\uFF61]/g;
5
5
  const IDN_SEPARATOR_TEST_REGEX = /[\u3002\uFF0E\uFF61]/;
6
+ const XN_LABEL_REGEX = /^xn--/i;
7
+ const DISALLOWED_CATEGORY_REGEX = /[\p{P}\p{S}\p{Z}\p{C}]/u;
8
+ const DISALLOWED_CATEGORY_WHITELIST = /* @__PURE__ */ new Set([
9
+ "-",
10
+ "·",
11
+ "͵",
12
+ "׳",
13
+ "״",
14
+ "・",
15
+ "۽",
16
+ "۾",
17
+ "་",
18
+ "‌",
19
+ "‍"
20
+ ]);
21
+ const isDisallowedCodePoint = (char) => DISALLOWED_CATEGORY_REGEX.test(char) && !DISALLOWED_CATEGORY_WHITELIST.has(char);
22
+ const TRANSPARENT_MARK_REGEX = /\p{Mn}/u;
23
+ const ARABIC_SCRIPT_REGEX = /\p{Script=Arabic}/u;
24
+ const ARABIC_INDIC_DIGITS_REGEX = /[\u0660-\u0669\u06F0-\u06F9]/;
25
+ const isArabicJoiningLetter = (char) => char !== void 0 && ARABIC_SCRIPT_REGEX.test(char) && !ARABIC_INDIC_DIGITS_REGEX.test(char);
26
+ const hasZwnjJoiningContext = (label, idx) => {
27
+ let before = idx - 1;
28
+ while (before >= 0 && TRANSPARENT_MARK_REGEX.test(label[before])) before--;
29
+ let after = idx + 1;
30
+ while (after < label.length && TRANSPARENT_MARK_REGEX.test(label[after])) after++;
31
+ return isArabicJoiningLetter(label[before]) && isArabicJoiningLetter(label[after]);
32
+ };
33
+ const BIDI_NSM_REGEX = /[\p{Mn}\p{Me}]/u;
34
+ const BIDI_ARABIC_INDIC_DIGIT_REGEX = /[\u0660-\u0669]/;
35
+ const BIDI_EXTENDED_ARABIC_INDIC_DIGIT_REGEX = /[\u06F0-\u06F9]/;
36
+ const BIDI_HEBREW_SCRIPT_REGEX = /\p{Script=Hebrew}/u;
37
+ const BIDI_AL_SCRIPT_REGEX = /[\p{Script=Arabic}\p{Script=Syriac}\p{Script=Thaana}]/u;
38
+ const BIDI_ASCII_DIGIT_REGEX = /[0-9]/;
39
+ const BIDI_LETTER_REGEX = /\p{L}/u;
40
+ const classifyBidi = (char) => {
41
+ if (BIDI_NSM_REGEX.test(char)) return "NSM";
42
+ if (BIDI_ARABIC_INDIC_DIGIT_REGEX.test(char)) return "AN";
43
+ if (BIDI_EXTENDED_ARABIC_INDIC_DIGIT_REGEX.test(char)) return "EN";
44
+ if (BIDI_HEBREW_SCRIPT_REGEX.test(char)) return "R";
45
+ if (BIDI_AL_SCRIPT_REGEX.test(char)) return "AL";
46
+ if (BIDI_ASCII_DIGIT_REGEX.test(char)) return "EN";
47
+ if (BIDI_LETTER_REGEX.test(char)) return "L";
48
+ return "ON";
49
+ };
50
+ const isLabelBidiTriggering = (label) => {
51
+ for (const char of label) {
52
+ const bidiClass = classifyBidi(char);
53
+ if (bidiClass === "R" || bidiClass === "AL" || bidiClass === "AN") return true;
54
+ }
55
+ return false;
56
+ };
57
+ const isLabelBidiRuleValid = (label) => {
58
+ const classes = [];
59
+ for (const char of label) classes.push(classifyBidi(char));
60
+ if (classes.length === 0) return false;
61
+ const first = classes[0];
62
+ if (first !== "L" && first !== "R" && first !== "AL") return false;
63
+ let lastIdx = classes.length - 1;
64
+ while (lastIdx > 0 && classes[lastIdx] === "NSM") lastIdx--;
65
+ const last = classes[lastIdx];
66
+ if (first === "R" || first === "AL") {
67
+ let hasEN = false;
68
+ let hasAN = false;
69
+ for (let i = 0; i < classes.length; i++) {
70
+ const c = classes[i];
71
+ if (c !== "R" && c !== "AL" && c !== "AN" && c !== "EN" && c !== "NSM" && c !== "ON") return false;
72
+ if (c === "EN") hasEN = true;
73
+ else if (c === "AN") hasAN = true;
74
+ }
75
+ if (hasEN && hasAN) return false;
76
+ return last === "R" || last === "AL" || last === "EN" || last === "AN";
77
+ }
78
+ for (let i = 0; i < classes.length; i++) {
79
+ const c = classes[i];
80
+ if (c !== "L" && c !== "EN" && c !== "NSM" && c !== "ON") return false;
81
+ }
82
+ return last === "L" || last === "EN";
83
+ };
84
+ const isDomainBidiValid = (unicodeLabels) => {
85
+ let isBidiDomain = false;
86
+ for (let i = 0; i < unicodeLabels.length; i++) if (isLabelBidiTriggering(unicodeLabels[i])) {
87
+ isBidiDomain = true;
88
+ break;
89
+ }
90
+ if (!isBidiDomain) return true;
91
+ for (let i = 0; i < unicodeLabels.length; i++) if (!isLabelBidiRuleValid(unicodeLabels[i])) return false;
92
+ return true;
93
+ };
6
94
  const splitHostnameLabels = (hostname) => {
7
95
  if (hostname.length === 0 || hostname.length > 255) return null;
8
96
  if (hostname.startsWith(".") || hostname.endsWith(".")) return null;
@@ -18,7 +106,7 @@ const isGreek = (char) => /\p{Script=Greek}/u.test(char);
18
106
  const isHebrew = (char) => /\p{Script=Hebrew}/u.test(char);
19
107
  const hasCjkKanaOrHan = (input) => /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u.test(input);
20
108
  const toUnicodeLabel = (label) => {
21
- if (!/^xn--/i.test(label)) return label;
109
+ if (!XN_LABEL_REGEX.test(label)) return label;
22
110
  try {
23
111
  return punycode.toUnicode(label.toLowerCase());
24
112
  } catch {
@@ -27,7 +115,7 @@ const toUnicodeLabel = (label) => {
27
115
  };
28
116
  const isValidIdnUnicodeLabel = (label) => {
29
117
  if (label.startsWith("-") || label.endsWith("-")) return false;
30
- if (label.length >= 4 && label[2] === "-" && label[3] === "-" && !/^xn--/i.test(label)) return false;
118
+ if (label.length >= 4 && label[2] === "-" && label[3] === "-" && !XN_LABEL_REGEX.test(label)) return false;
31
119
  if (/^\p{M}/u.test(label)) return false;
32
120
  if (/[\u302E\u302F\u0640\u07FA]/u.test(label)) return false;
33
121
  for (let idx = 0; idx < label.length; idx++) {
@@ -36,7 +124,9 @@ const isValidIdnUnicodeLabel = (label) => {
36
124
  if (char === "͵" && (idx === label.length - 1 || !isGreek(label[idx + 1]))) return false;
37
125
  if ((char === "׳" || char === "״") && (idx === 0 || !isHebrew(label[idx - 1]))) return false;
38
126
  if (char === "‍" && (idx === 0 || label[idx - 1] !== "्")) return false;
127
+ if (char === "‌" && (idx === 0 || label[idx - 1] !== "्" && !hasZwnjJoiningContext(label, idx))) return false;
39
128
  }
129
+ for (const char of label) if (isDisallowedCodePoint(char)) return false;
40
130
  if (label.includes("・") && !hasCjkKanaOrHan(label.replaceAll("・", ""))) return false;
41
131
  const hasArabicIndic = /[\u0660-\u0669]/.test(label);
42
132
  const hasExtendedArabicIndic = /[\u06F0-\u06F9]/.test(label);
@@ -51,7 +141,7 @@ const isValidHostname = (hostname) => {
51
141
  for (let i = 0; i < labels.length; i++) {
52
142
  const label = labels[i];
53
143
  if (!isAsciiHostnameLabel(label)) return false;
54
- if (/^xn--/i.test(label)) {
144
+ if (XN_LABEL_REGEX.test(label)) {
55
145
  const unicodeLabel = toUnicodeLabel(label);
56
146
  if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) return false;
57
147
  }
@@ -59,14 +149,28 @@ const isValidHostname = (hostname) => {
59
149
  return true;
60
150
  };
61
151
  const isValidIdnHostname = (hostname) => {
62
- const labels = splitHostnameLabels(hostname.replace(IDN_SEPARATOR_REGEX, "."));
152
+ const normalizedHostname = hostname.replace(IDN_SEPARATOR_REGEX, ".");
153
+ const labels = splitHostnameLabels(normalizedHostname);
63
154
  if (labels === null) return false;
155
+ const unicodeLabels = [];
156
+ let totalLength = labels.length - 1;
64
157
  for (let i = 0; i < labels.length; i++) {
65
158
  const label = labels[i];
66
159
  const unicodeLabel = toUnicodeLabel(label);
67
160
  if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) return false;
161
+ let aLabel;
162
+ try {
163
+ aLabel = punycode.toASCII(unicodeLabel);
164
+ } catch {
165
+ return false;
166
+ }
167
+ if (XN_LABEL_REGEX.test(label) && aLabel.toLowerCase() !== label.toLowerCase()) return false;
168
+ if (aLabel.length > 63) return false;
169
+ totalLength += aLabel.length;
170
+ unicodeLabels.push(unicodeLabel);
68
171
  }
69
- return true;
172
+ if (totalLength > 253) return false;
173
+ return isDomainBidiValid(unicodeLabels);
70
174
  };
71
175
  //#endregion
72
176
  export { isValidHostname, isValidIdnHostname };
@@ -16,7 +16,8 @@ const areEqual = (json1, json2, options, _depth = 0) => {
16
16
  }
17
17
  if (isObject(json1) && isObject(json2)) {
18
18
  const keys1 = sortedKeys(json1);
19
- if (!areEqual(keys1, sortedKeys(json2), options, _depth + 1)) return false;
19
+ const keys2 = sortedKeys(json2);
20
+ if (!areEqual(keys1, keys2, options, _depth + 1)) return false;
20
21
  len = keys1.length;
21
22
  for (i = 0; i < len; i++) if (!areEqual(json1[keys1[i]], json2[keys1[i]], options, _depth + 1)) return false;
22
23
  return true;
@@ -8,7 +8,7 @@ const toUtcTime = (hour, minute, offsetSign, offsetHour, offsetMinute) => {
8
8
  minute: normalizedUtcTotalMinutes % 60
9
9
  };
10
10
  };
11
- const RFC3339_TIME_REGEX = /^([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$/i;
11
+ const RFC3339_TIME_REGEX = /^(?<hour>[0-9]{2}):(?<minute>[0-9]{2}):(?<second>[0-9]{2})(?<fraction>\.[0-9]+)?(?<offset>z|(?<offsetValue>[+-][0-9]{2}:[0-9]{2}))$/i;
12
12
  const parseRfc3339Time = (time) => {
13
13
  const matches = RFC3339_TIME_REGEX.exec(time);
14
14
  if (matches === null) return null;
@@ -19,7 +19,7 @@ const parseRfc3339Time = (time) => {
19
19
  let utcHour = hour;
20
20
  let utcMinute = minute;
21
21
  if (matches[5].toLowerCase() !== "z") {
22
- const offsetMatches = /^([+-])([0-9]{2}):([0-9]{2})$/.exec(matches[5]);
22
+ const offsetMatches = /^(?<sign>[+-])(?<hour>[0-9]{2}):(?<minute>[0-9]{2})$/.exec(matches[5]);
23
23
  if (offsetMatches === null) return null;
24
24
  const offsetSign = offsetMatches[1];
25
25
  const offsetHour = Number.parseInt(offsetMatches[2], 10);
@@ -39,4 +39,4 @@ const parseRfc3339Time = (time) => {
39
39
  };
40
40
  };
41
41
  //#endregion
42
- export { parseRfc3339Time };
42
+ export { parseRfc3339Time, toUtcTime };
@@ -46,4 +46,4 @@ const resolveDynamicRef = (schema, dynamicScopeStack) => {
46
46
  return target;
47
47
  };
48
48
  //#endregion
49
- export { resolveDynamicRef, resolveRecursiveRef };
49
+ export { findDynamicAnchorInScope, getDynamicRefAnchorName, resolveDynamicRef, resolveRecursiveRef };
@@ -9,7 +9,7 @@ const VOCAB_VALIDATION_2019_09 = "https://json-schema.org/draft/2019-09/vocab/va
9
9
  const VOCAB_VALIDATION_2020_12 = "https://json-schema.org/draft/2020-12/vocab/validation";
10
10
  const VOCAB_FORMAT_2019_09 = "https://json-schema.org/draft/2019-09/vocab/format";
11
11
  const VOCAB_FORMAT_ASSERTION_2020_12 = "https://json-schema.org/draft/2020-12/vocab/format-assertion";
12
- const VALIDATION_VOCAB_KEYWORDS = new Set([
12
+ const VALIDATION_VOCAB_KEYWORDS = /* @__PURE__ */ new Set([
13
13
  "type",
14
14
  "multipleOf",
15
15
  "maximum",
@@ -40,7 +40,9 @@ const isValidationVocabularyEnabled = (schema, report, version) => {
40
40
  const metaSchema = currentSchemaMeta || rootSchemaMeta;
41
41
  if (!metaSchema || typeof metaSchema !== "object" || !isObject(metaSchema.$vocabulary)) return true;
42
42
  const vocabulary = metaSchema.$vocabulary;
43
- if (Object.hasOwn(vocabulary, VOCAB_VALIDATION_2019_09) || Object.hasOwn(vocabulary, VOCAB_VALIDATION_2020_12)) return vocabulary[VOCAB_VALIDATION_2019_09] || vocabulary[VOCAB_VALIDATION_2020_12];
43
+ const has2019 = Object.hasOwn(vocabulary, VOCAB_VALIDATION_2019_09);
44
+ const has2020 = Object.hasOwn(vocabulary, VOCAB_VALIDATION_2020_12);
45
+ if (has2019 || has2020) return vocabulary[VOCAB_VALIDATION_2019_09] || vocabulary[VOCAB_VALIDATION_2020_12];
44
46
  return false;
45
47
  };
46
48
  /**
@@ -45,11 +45,11 @@ function formatValidator(ctx, report, schema, json) {
45
45
  if (result instanceof Promise) {
46
46
  const promiseResult = result;
47
47
  report.addAsyncTaskWithPath(async (callback) => {
48
+ let resolved = false;
48
49
  try {
49
- callback(await promiseResult);
50
- } catch {
51
- callback(false);
52
- }
50
+ resolved = await promiseResult;
51
+ } catch {}
52
+ callback(resolved);
53
53
  }, [], (resolvedResult) => {
54
54
  if (resolvedResult !== true) report.addError("INVALID_FORMAT", [schema.format, JSON.stringify(json)], void 0, schema, "format");
55
55
  });
@@ -52,4 +52,4 @@ const normalizeOptions = (options) => {
52
52
  return normalized;
53
53
  };
54
54
  //#endregion
55
- export { defaultOptions, normalizeOptions };
55
+ export { MAX_ASYNC_TIMEOUT, defaultOptions, normalizeOptions };
package/dist/z-schema.js CHANGED
@@ -287,4 +287,4 @@ var ZSchemaAsyncSafe = class extends ZSchemaBase {
287
287
  }
288
288
  };
289
289
  //#endregion
290
- export { ZSchema };
290
+ export { ZSchema, ZSchemaAsync, ZSchemaAsyncSafe, ZSchemaSafe };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "z-schema",
3
- "version": "12.3.1",
3
+ "version": "12.4.0",
4
4
  "description": "Fast, lightweight JSON Schema validator for Node.js and browsers — full support for draft-04, draft-06, draft-07, draft-2019-09, and draft-2020-12 (latest)",
5
5
  "keywords": [
6
6
  "draft-04",
@@ -82,7 +82,7 @@
82
82
  },
83
83
  "devDependencies": {
84
84
  "@types/lodash.isequal": "^4.5.8",
85
- "@types/node": "^25.2.0",
85
+ "@types/node": "^26.0.1",
86
86
  "@types/punycode": "^2.1.4",
87
87
  "@types/validator": "^13.15.10",
88
88
  "@vitest/browser-playwright": "^4.1.0",
@@ -90,9 +90,9 @@
90
90
  "@vitest/coverage-v8": "^4.1.0",
91
91
  "lefthook": "^2.1.9",
92
92
  "lodash.isequal": "^4.5.0",
93
- "oxfmt": "^0.51.0",
93
+ "oxfmt": "^0.57.0",
94
94
  "oxlint": "^1.56.0",
95
- "oxlint-tsgolint": "^0.23.0",
95
+ "oxlint-tsgolint": "^0.24.0",
96
96
  "rimraf": "^6.1.2",
97
97
  "tsdown": "^0.22.0",
98
98
  "typescript": "^6.0.3",
@@ -100,7 +100,7 @@
100
100
  "vitest": "^4.1.0"
101
101
  },
102
102
  "optionalDependencies": {
103
- "commander": "^14.0.3"
103
+ "commander": "^15.0.0"
104
104
  },
105
105
  "overrides": {
106
106
  "vite": ">=7.3.2"
@@ -14,7 +14,7 @@ const dateValidator: FormatValidatorFn = (date: unknown) => {
14
14
  return true;
15
15
  }
16
16
  // full-date from http://tools.ietf.org/html/rfc3339#section-5.6
17
- const matches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(date);
17
+ const matches = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/.exec(date);
18
18
  if (matches === null) {
19
19
  return false;
20
20
  }
@@ -39,7 +39,7 @@ const dateTimeValidator: FormatValidatorFn = (dateTime: unknown) => {
39
39
  const datePart = dateTime.slice(0, tIdx);
40
40
  const timePart = dateTime.slice(tIdx + 1);
41
41
  // Check date
42
- const dateMatches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(datePart);
42
+ const dateMatches = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/.exec(datePart);
43
43
  if (dateMatches === null) {
44
44
  return false;
45
45
  }
@@ -61,7 +61,7 @@ const emailValidator: FormatValidatorFn = (email: unknown) => {
61
61
  return true;
62
62
  }
63
63
 
64
- const ipv6Literal = /^(.+)@\[IPv6:([^\]]+)\]$/i.exec(email);
64
+ const ipv6Literal = /^(?<localPart>.+)@\[IPv6:(?<address>[^\]]+)\]$/i.exec(email);
65
65
  if (!ipv6Literal) {
66
66
  return false;
67
67
  }
@@ -226,7 +226,7 @@ const uriValidator: FormatValidatorFn = (uri: unknown) => {
226
226
  if (!hasValidPercentEncoding(uri)) {
227
227
  return false;
228
228
  }
229
- const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]*)/.exec(uri);
229
+ const match = /^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]*):\/\/(?<authority>[^/?#]*)/.exec(uri);
230
230
  if (match) {
231
231
  const authority = match[2];
232
232
  const atIndex = authority.indexOf('@');
@@ -267,7 +267,7 @@ const uriReferenceValidator: FormatValidatorFn = (uri: unknown) => {
267
267
  return false;
268
268
  }
269
269
  // URI-reference allows relative URIs
270
- return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
270
+ return /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
271
271
  };
272
272
 
273
273
  const uriTemplateValidator: FormatValidatorFn = (uri: unknown) => {
@@ -275,7 +275,7 @@ const uriTemplateValidator: FormatValidatorFn = (uri: unknown) => {
275
275
  return true;
276
276
  }
277
277
  // URI template allows braces for expressions.
278
- if (!/^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) {
278
+ if (!/^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) {
279
279
  return false;
280
280
  }
281
281
 
@@ -338,7 +338,7 @@ const relativeJsonPointerValidator: FormatValidatorFn = (pointer: unknown) => {
338
338
  }
339
339
  // Relative JSON Pointer: non-negative integer prefix (no leading zeros unless zero),
340
340
  // followed by either '#', a JSON Pointer, or nothing.
341
- const match = /^(0|[1-9]\d*)(.*)$/.exec(pointer);
341
+ const match = /^(?<int>0|[1-9]\d*)(?<suffix>.*)$/.exec(pointer);
342
342
  if (!match) {
343
343
  return false;
344
344
  }
@@ -368,12 +368,38 @@ const timeValidator: FormatValidatorFn = (time: unknown) => {
368
368
  return parseRfc3339Time(time) !== null;
369
369
  };
370
370
 
371
+ // Matches a lone (unpaired) UTF-16 surrogate — either a high surrogate not
372
+ // followed by a low surrogate, or a low surrogate not preceded by a high one.
373
+ const LONE_SURROGATE_REGEX = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
374
+ // A quoted local part: any char except a bare quote/backslash; backslash escapes
375
+ // allowed. Leniently permits raw control chars (e.g. bare CR/LF), which stricter
376
+ // RFC 5321 parsing forbids — an accepted limitation, not exercised by the suite.
377
+ const QUOTED_LOCAL_PART_REGEX = /^"(?:[^"\\]|\\.)*"$/u;
378
+ const UNQUOTED_LOCAL_PART_REGEX = /^[^\s@]+$/u;
379
+
380
+ const isValidIdnEmailLocalPart = (localPart: string): boolean => {
381
+ if (localPart.length === 0 || LONE_SURROGATE_REGEX.test(localPart)) {
382
+ return false;
383
+ }
384
+ if (localPart.length >= 2 && localPart.startsWith('"') && localPart.endsWith('"')) {
385
+ return QUOTED_LOCAL_PART_REGEX.test(localPart);
386
+ }
387
+ return UNQUOTED_LOCAL_PART_REGEX.test(localPart);
388
+ };
389
+
371
390
  const idnEmailValidator: FormatValidatorFn = (email: unknown) => {
372
391
  if (typeof email !== 'string') {
373
392
  return true;
374
393
  }
375
- // Simple email check, allowing international chars
376
- return /^[^\s@]+@[^\s@]+$/.test(email);
394
+ // Split on the last '@': everything after it is the (idn-)hostname domain,
395
+ // everything before it is the local part (which may itself be quoted).
396
+ // Note: unlike the ASCII `email` validator, IP-literal domains
397
+ // (`user@[192.168.1.1]` / `@[IPv6:...]`) are not supported here.
398
+ const atIdx = email.lastIndexOf('@');
399
+ if (atIdx <= 0 || atIdx === email.length - 1) {
400
+ return false;
401
+ }
402
+ return isValidIdnEmailLocalPart(email.slice(0, atIdx)) && isValidIdnHostname(email.slice(atIdx + 1));
377
403
  };
378
404
 
379
405
  const idnHostnameValidator: FormatValidatorFn = (hostname: unknown) => {
@@ -406,7 +432,7 @@ const iriReferenceValidator: FormatValidatorFn = (iriReference: unknown) => {
406
432
  if (typeof iriReference !== 'string') {
407
433
  return true;
408
434
  }
409
- return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
435
+ return /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
410
436
  };
411
437
 
412
438
  export interface FormatValidatorsOptions {
@@ -169,7 +169,7 @@ function collectEvaluated(ctx: ZSchemaBase, args: CollectEvaluatedArgs): Set<num
169
169
  let passed = getCachedValidationResult(report, currentSchema.contains, jsonArr[i]);
170
170
  if (passed === undefined) {
171
171
  const subReport = new Report(report);
172
- validate(ctx, subReport, currentSchema.contains as JsonSchemaInternal | boolean, jsonArr[i]);
172
+ validate(ctx, subReport, currentSchema.contains, jsonArr[i]);
173
173
  passed = subReport.errors.length === 0;
174
174
  }
175
175
  if (passed) {
@@ -264,7 +264,7 @@ function collectEvaluated(ctx: ZSchemaBase, args: CollectEvaluatedArgs): Set<num
264
264
  // allOf
265
265
  if (Array.isArray(currentSchema.allOf)) {
266
266
  for (let i = 0; i < currentSchema.allOf.length; i++) {
267
- if (merge(recurse(currentSchema.allOf[i] as JsonSchemaInternal | boolean))) {
267
+ if (merge(recurse(currentSchema.allOf[i]))) {
268
268
  return 'all';
269
269
  }
270
270
  }
@@ -277,10 +277,10 @@ function collectEvaluated(ctx: ZSchemaBase, args: CollectEvaluatedArgs): Set<num
277
277
  let passed = getCachedValidationResult(report, subSchema, json);
278
278
  if (passed === undefined) {
279
279
  const subReport = new Report(report);
280
- validate(ctx, subReport, subSchema as JsonSchemaInternal | boolean, json);
280
+ validate(ctx, subReport, subSchema, json);
281
281
  passed = subReport.errors.length === 0;
282
282
  }
283
- if (passed && merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
283
+ if (passed && merge(recurse(subSchema))) {
284
284
  return 'all';
285
285
  }
286
286
  }
@@ -293,10 +293,10 @@ function collectEvaluated(ctx: ZSchemaBase, args: CollectEvaluatedArgs): Set<num
293
293
  let passed = getCachedValidationResult(report, subSchema, json);
294
294
  if (passed === undefined) {
295
295
  const subReport = new Report(report);
296
- validate(ctx, subReport, subSchema as JsonSchemaInternal | boolean, json);
296
+ validate(ctx, subReport, subSchema, json);
297
297
  passed = subReport.errors.length === 0;
298
298
  }
299
- if (passed && merge(recurse(subSchema as JsonSchemaInternal | boolean))) {
299
+ if (passed && merge(recurse(subSchema))) {
300
300
  return 'all';
301
301
  }
302
302
  }
@@ -339,7 +339,7 @@ function collectEvaluated(ctx: ZSchemaBase, args: CollectEvaluatedArgs): Set<num
339
339
 
340
340
  // $dynamicRef
341
341
  const dynamicTarget = resolveDynamicRef(currentSchema, report.__$dynamicScopeStack);
342
- if (dynamicTarget && dynamicTarget !== currentSchema && merge(recurse(dynamicTarget as JsonSchemaInternal))) {
342
+ if (dynamicTarget && dynamicTarget !== currentSchema && merge(recurse(dynamicTarget))) {
343
343
  return 'all';
344
344
  }
345
345
 
@@ -28,7 +28,7 @@ export const decodeBase64 = (value: string): string | undefined => {
28
28
  // where `atob` above is used instead.)
29
29
  const bufferCtor = (
30
30
  globalThis as {
31
- Buffer?: { from(data: string, encoding: string): { toString(encoding: string): string } };
31
+ Buffer?: { from: (data: string, encoding: string) => { toString: (encoding: string) => string } };
32
32
  }
33
33
  ).Buffer;
34
34
  if (bufferCtor !== undefined) {