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,31 +1,27 @@
1
- import { deepClone } from "./utils/clone.mjs";
2
- import { jsonSymbol, schemaSymbol } from "./utils/symbols.mjs";
3
- import { defaultOptions } from "./z-schema-options.mjs";
4
- import { SchemaCache, prepareRemoteSchema } from "./schema-cache.mjs";
5
- import "./z-schema-versions.mjs";
6
- import { getRegisteredFormats, registerFormat, unregisterFormat } from "./format-validators.mjs";
7
- import { getSchemaReader, setSchemaReader } from "./z-schema-reader.mjs";
8
- import { FACTORY_TOKEN, ZSchemaBase } from "./z-schema-base.mjs";
1
+ import { deepClone } from "./utils/clone.js";
2
+ import { jsonSymbol, schemaSymbol } from "./utils/symbols.js";
3
+ import { defaultOptions } from "./z-schema-options.js";
4
+ import { SchemaCache, prepareRemoteSchema } from "./schema-cache.js";
5
+ import "./z-schema-versions.js";
6
+ import { getRegisteredFormats, registerFormat, unregisterFormat } from "./format-validators.js";
7
+ import { getSchemaReader, setSchemaReader } from "./z-schema-reader.js";
8
+ import { FACTORY_TOKEN, ZSchemaBase } from "./z-schema-base.js";
9
9
  //#region src/z-schema.ts
10
10
  var ZSchema = class ZSchema extends ZSchemaBase {
11
- /** @internal Use ZSchema.create() instead. */
12
- constructor(options, token) {
13
- super(options, token);
14
- }
15
11
  /**
16
12
  * Register a global format validator available to all instances.
17
13
  * @param name - The format name (e.g. `'email'`, `'date'`).
18
14
  * @param validatorFunction - A sync or async function `(value: unknown) => boolean | Promise<boolean>`.
19
15
  */
20
16
  static registerFormat(name, validatorFunction) {
21
- return registerFormat(name, validatorFunction);
17
+ registerFormat(name, validatorFunction);
22
18
  }
23
19
  /**
24
20
  * Remove a globally registered format validator.
25
21
  * @param name - The format name to unregister.
26
22
  */
27
23
  static unregisterFormat(name) {
28
- return unregisterFormat(name);
24
+ unregisterFormat(name);
29
25
  }
30
26
  /** Returns the names of all globally registered format validators. */
31
27
  static getRegisteredFormats() {
@@ -54,7 +50,7 @@ var ZSchema = class ZSchema extends ZSchemaBase {
54
50
  * @param schemaReader - A function `(uri: string) => JsonSchema | undefined`, or `undefined` to clear.
55
51
  */
56
52
  static setSchemaReader(schemaReader) {
57
- return setSchemaReader(schemaReader);
53
+ setSchemaReader(schemaReader);
58
54
  }
59
55
  static schemaSymbol = schemaSymbol;
60
56
  static jsonSymbol = jsonSymbol;
@@ -90,10 +86,10 @@ var ZSchema = class ZSchema extends ZSchemaBase {
90
86
  try {
91
87
  this._validate(json, schema, options ?? {});
92
88
  return { valid: true };
93
- } catch (err) {
89
+ } catch (error) {
94
90
  return {
95
91
  valid: false,
96
- err
92
+ err: error
97
93
  };
98
94
  }
99
95
  }
@@ -108,9 +104,12 @@ var ZSchema = class ZSchema extends ZSchemaBase {
108
104
  validateAsync(json, schema, options) {
109
105
  return new Promise((resolve, reject) => {
110
106
  try {
111
- this._validate(json, schema, options || {}, (err, valid) => err || valid !== true ? reject(err) : resolve(valid));
112
- } catch (err) {
113
- reject(err);
107
+ this._validate(json, schema, options || {}, (err, valid) => {
108
+ if (err || !valid) reject(err ?? /* @__PURE__ */ new Error("Validation failed"));
109
+ else resolve(valid);
110
+ });
111
+ } catch (error) {
112
+ reject(error instanceof Error ? error : new Error(String(error), { cause: error }));
114
113
  }
115
114
  });
116
115
  }
@@ -131,10 +130,10 @@ var ZSchema = class ZSchema extends ZSchemaBase {
131
130
  err
132
131
  });
133
132
  });
134
- } catch (err) {
133
+ } catch (error) {
135
134
  resolve({
136
135
  valid: false,
137
- err
136
+ err: error
138
137
  });
139
138
  }
140
139
  });
@@ -157,10 +156,10 @@ var ZSchema = class ZSchema extends ZSchemaBase {
157
156
  try {
158
157
  this._validateSchema(schemaOrArr);
159
158
  return { valid: true };
160
- } catch (err) {
159
+ } catch (error) {
161
160
  return {
162
161
  valid: false,
163
- err
162
+ err: error
164
163
  };
165
164
  }
166
165
  }
@@ -170,10 +169,6 @@ var ZSchema = class ZSchema extends ZSchemaBase {
170
169
  * Created via `ZSchema.create({ safe: true })`.
171
170
  */
172
171
  var ZSchemaSafe = class extends ZSchemaBase {
173
- /** @internal Use ZSchema.create() instead. */
174
- constructor(options, token) {
175
- super(options, token);
176
- }
177
172
  /**
178
173
  * Validate JSON data against a schema.
179
174
  * @param json - The data to validate.
@@ -185,10 +180,10 @@ var ZSchemaSafe = class extends ZSchemaBase {
185
180
  try {
186
181
  this._validate(json, schema, options);
187
182
  return { valid: true };
188
- } catch (err) {
183
+ } catch (error) {
189
184
  return {
190
185
  valid: false,
191
- err
186
+ err: error
192
187
  };
193
188
  }
194
189
  }
@@ -201,10 +196,10 @@ var ZSchemaSafe = class extends ZSchemaBase {
201
196
  try {
202
197
  this._validateSchema(schemaOrArr);
203
198
  return { valid: true };
204
- } catch (err) {
199
+ } catch (error) {
205
200
  return {
206
201
  valid: false,
207
- err
202
+ err: error
208
203
  };
209
204
  }
210
205
  }
@@ -214,10 +209,6 @@ var ZSchemaSafe = class extends ZSchemaBase {
214
209
  * Created via `ZSchema.create({ async: true })`.
215
210
  */
216
211
  var ZSchemaAsync = class extends ZSchemaBase {
217
- /** @internal Use ZSchema.create() instead. */
218
- constructor(options, token) {
219
- super(options, token);
220
- }
221
212
  /**
222
213
  * Validate JSON data against a schema asynchronously.
223
214
  * @param json - The data to validate.
@@ -229,9 +220,12 @@ var ZSchemaAsync = class extends ZSchemaBase {
229
220
  validate(json, schema, options = {}) {
230
221
  return new Promise((resolve, reject) => {
231
222
  try {
232
- this._validate(json, schema, options, (err, valid) => err || valid !== true ? reject(err) : resolve(valid));
233
- } catch (err) {
234
- reject(err);
223
+ this._validate(json, schema, options, (err, valid) => {
224
+ if (err || !valid) reject(err ?? /* @__PURE__ */ new Error("Validation failed"));
225
+ else resolve(valid);
226
+ });
227
+ } catch (error) {
228
+ reject(error instanceof Error ? error : new Error(String(error), { cause: error }));
235
229
  }
236
230
  });
237
231
  }
@@ -250,10 +244,6 @@ var ZSchemaAsync = class extends ZSchemaBase {
250
244
  * Created via `ZSchema.create({ async: true, safe: true })`.
251
245
  */
252
246
  var ZSchemaAsyncSafe = class extends ZSchemaBase {
253
- /** @internal Use ZSchema.create() instead. */
254
- constructor(options, token) {
255
- super(options, token);
256
- }
257
247
  /**
258
248
  * Validate JSON data against a schema asynchronously.
259
249
  * The promise always resolves (never rejects).
@@ -271,10 +261,10 @@ var ZSchemaAsyncSafe = class extends ZSchemaBase {
271
261
  err
272
262
  });
273
263
  });
274
- } catch (err) {
264
+ } catch (error) {
275
265
  resolve({
276
266
  valid: false,
277
- err
267
+ err: error
278
268
  });
279
269
  }
280
270
  });
@@ -288,10 +278,10 @@ var ZSchemaAsyncSafe = class extends ZSchemaBase {
288
278
  try {
289
279
  this._validateSchema(schemaOrArr);
290
280
  return { valid: true };
291
- } catch (err) {
281
+ } catch (error) {
292
282
  return {
293
283
  valid: false,
294
- err
284
+ err: error
295
285
  };
296
286
  }
297
287
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "z-schema",
3
- "version": "12.1.1",
3
+ "version": "12.3.1",
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",
@@ -39,32 +39,30 @@
39
39
  "LICENSE",
40
40
  "README.md"
41
41
  ],
42
+ "type": "module",
42
43
  "sideEffects": [
43
- "./dist/z-schema-versions.mjs"
44
+ "./dist/z-schema-versions.js"
44
45
  ],
45
46
  "exports": {
46
47
  ".": {
47
48
  "import": {
48
- "types": "./dist/index.d.mts",
49
- "default": "./dist/index.mjs"
49
+ "types": "./dist/index.d.ts",
50
+ "default": "./dist/index.js"
50
51
  },
51
52
  "require": {
52
- "types": "./cjs/index.d.ts",
53
- "default": "./cjs/index.js"
53
+ "types": "./cjs/index.d.cts",
54
+ "default": "./cjs/index.cjs"
54
55
  },
55
- "default": "./dist/index.mjs"
56
+ "default": "./dist/index.js"
56
57
  }
57
58
  },
58
59
  "scripts": {
59
- "prepare": "husky",
60
- "pre-commit": "npx lint-staged",
60
+ "prepare": "lefthook install",
61
61
  "pre-push": "npm run build && npm run build:tests",
62
- "format": "oxfmt",
63
- "format:check": "oxfmt --check",
64
- "lint": "oxlint --fix",
65
- "lint:check": "oxlint",
62
+ "check": "ultracite check",
63
+ "fix": "ultracite fix",
66
64
  "clean": "rimraf ./cjs && rimraf ./dist && rimraf ./umd && rimraf --glob \"./src/schemas/*.json\"",
67
- "build": "npm run copy:schemas && tsdown",
65
+ "build": "npm run copy:schemas && tsdown && node ./scripts/write-build-markers.mjs",
68
66
  "build:watch": "npm run copy:schemas && tsdown --watch",
69
67
  "build:tests": "tsc --noEmit --project test/tsconfig.json",
70
68
  "copy:schemas": "node ./scripts/copy-schemas.mts",
@@ -87,22 +85,26 @@
87
85
  "@types/node": "^25.2.0",
88
86
  "@types/punycode": "^2.1.4",
89
87
  "@types/validator": "^13.15.10",
90
- "@vitest/browser-playwright": "^4.0.18",
91
- "@vitest/coverage-istanbul": "^4.0.18",
92
- "@vitest/coverage-v8": "^4.0.18",
93
- "husky": "^9.1.7",
94
- "lint-staged": "^16.2.7",
88
+ "@vitest/browser-playwright": "^4.1.0",
89
+ "@vitest/coverage-istanbul": "^4.1.0",
90
+ "@vitest/coverage-v8": "^4.1.0",
91
+ "lefthook": "^2.1.9",
95
92
  "lodash.isequal": "^4.5.0",
96
- "oxfmt": "^0.41.0",
93
+ "oxfmt": "^0.51.0",
97
94
  "oxlint": "^1.56.0",
95
+ "oxlint-tsgolint": "^0.23.0",
98
96
  "rimraf": "^6.1.2",
99
- "tsdown": "^0.21.4",
100
- "typescript": "^5.9.3",
101
- "vitest": "^4.0.18"
97
+ "tsdown": "^0.22.0",
98
+ "typescript": "^6.0.3",
99
+ "ultracite": "^7.8.1",
100
+ "vitest": "^4.1.0"
102
101
  },
103
102
  "optionalDependencies": {
104
103
  "commander": "^14.0.3"
105
104
  },
105
+ "overrides": {
106
+ "vite": ">=7.3.2"
107
+ },
106
108
  "engines": {
107
109
  "node": ">=22.0.0"
108
110
  },
package/src/errors.ts CHANGED
@@ -76,12 +76,11 @@ export const Errors = {
76
76
  };
77
77
 
78
78
  export class ValidateError extends Error {
79
- name: string;
79
+ override readonly name: string = 'ValidateError';
80
80
  details?: SchemaErrorDetail[];
81
81
 
82
82
  constructor(message: string, details?: SchemaErrorDetail[]) {
83
83
  super(message);
84
- this.name = 'z-schema validation error';
85
84
  this.details = details;
86
85
  }
87
86
  }
@@ -18,9 +18,9 @@ const dateValidator: FormatValidatorFn = (date: unknown) => {
18
18
  if (matches === null) {
19
19
  return false;
20
20
  }
21
- const year = parseInt(matches[1], 10);
22
- const month = parseInt(matches[2], 10);
23
- const day = parseInt(matches[3], 10);
21
+ const year = Number.parseInt(matches[1], 10);
22
+ const month = Number.parseInt(matches[2], 10);
23
+ const day = Number.parseInt(matches[3], 10);
24
24
  return isValidRfc3339Date(year, month, day);
25
25
  };
26
26
 
@@ -29,20 +29,23 @@ const dateTimeValidator: FormatValidatorFn = (dateTime: unknown) => {
29
29
  return true;
30
30
  }
31
31
  // date-time from http://tools.ietf.org/html/rfc3339#section-5.6
32
- const s = dateTime.toLowerCase().split('t');
33
- if (s.length !== 2) {
32
+ let tIdx = dateTime.indexOf('T');
33
+ if (tIdx === -1) {
34
+ tIdx = dateTime.indexOf('t');
35
+ }
36
+ if (tIdx === -1) {
34
37
  return false;
35
38
  }
36
- const datePart = s[0];
37
- const timePart = s[1];
39
+ const datePart = dateTime.slice(0, tIdx);
40
+ const timePart = dateTime.slice(tIdx + 1);
38
41
  // Check date
39
42
  const dateMatches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(datePart);
40
43
  if (dateMatches === null) {
41
44
  return false;
42
45
  }
43
- const year = parseInt(dateMatches[1], 10);
44
- const month = parseInt(dateMatches[2], 10);
45
- const day = parseInt(dateMatches[3], 10);
46
+ const year = Number.parseInt(dateMatches[1], 10);
47
+ const month = Number.parseInt(dateMatches[2], 10);
48
+ const day = Number.parseInt(dateMatches[3], 10);
46
49
  if (!isValidRfc3339Date(year, month, day)) {
47
50
  return false;
48
51
  }
@@ -96,12 +99,13 @@ const ipv6Validator: FormatValidatorFn = (ipv6: unknown) => {
96
99
  return isIPModule.default(ipv6, 6);
97
100
  };
98
101
 
102
+ const INVALID_REGEX_ESCAPES = new Set(['a']);
103
+
99
104
  const regexValidator: FormatValidatorFn = (input: unknown) => {
100
105
  if (typeof input !== 'string') {
101
106
  return true;
102
107
  }
103
108
 
104
- const invalidEscapes = new Set(['a']);
105
109
  for (let idx = 0; idx < input.length; idx++) {
106
110
  if (input[idx] !== '\\') {
107
111
  continue;
@@ -113,15 +117,19 @@ const regexValidator: FormatValidatorFn = (input: unknown) => {
113
117
  }
114
118
 
115
119
  const escaped = input[idx];
116
- if (invalidEscapes.has(escaped)) {
120
+ if (INVALID_REGEX_ESCAPES.has(escaped)) {
117
121
  return false;
118
122
  }
119
123
  }
120
124
 
121
125
  try {
122
- RegExp(input);
126
+ // Constructed purely to detect an invalid pattern (throws SyntaxError). The
127
+ // result is intentionally unused; `new` is required by new-for-builtins and
128
+ // `void`/call-form trip no-void/new-for-builtins, so no-new is disabled here.
129
+ // oxlint-disable-next-line no-new
130
+ new RegExp(input);
123
131
  return true;
124
- } catch (_e) {
132
+ } catch {
125
133
  return false;
126
134
  }
127
135
  };
@@ -132,7 +140,7 @@ const durationValidator: FormatValidatorFn = (input: unknown) => {
132
140
  }
133
141
 
134
142
  // eslint-disable-next-line no-control-regex
135
- if (!/^P[\x00-\x7F]*$/.test(input)) {
143
+ if (!/^P[\u0000-\u007F]*$/.test(input)) {
136
144
  return false;
137
145
  }
138
146
 
@@ -163,7 +171,7 @@ const durationValidator: FormatValidatorFn = (input: unknown) => {
163
171
  return false;
164
172
  }
165
173
 
166
- const hasDateComponent = /\d+[YMD]/.test(datePart);
174
+ const hasDateComponent = datePart.length > 0;
167
175
  let hasTimeComponent = false;
168
176
 
169
177
  if (timePart !== undefined) {
@@ -193,43 +201,55 @@ const uuidValidator: FormatValidatorFn = (input: unknown) => {
193
201
 
194
202
  const strictUriValidator: FormatValidatorFn = (uri: unknown) => typeof uri !== 'string' || isURLModule.default(uri);
195
203
 
204
+ const isHexChar = (c: number) => (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102);
205
+
196
206
  const hasValidPercentEncoding = (str: string): boolean => {
197
207
  for (let i = 0; i < str.length; i++) {
198
- if (str[i] === '%') {
199
- if (i + 2 >= str.length || !/[0-9a-fA-F]/.test(str[i + 1]) || !/[0-9a-fA-F]/.test(str[i + 2])) {
200
- return false;
201
- }
208
+ if (
209
+ str[i] === '%' &&
210
+ (i + 2 >= str.length || !isHexChar(str.charCodeAt(i + 1)) || !isHexChar(str.charCodeAt(i + 2)))
211
+ ) {
212
+ return false;
202
213
  }
203
214
  }
204
215
  return true;
205
216
  };
206
217
 
207
- const uriValidator: FormatValidatorFn = function (uri: unknown) {
208
- if (typeof uri !== 'string') return true;
218
+ const uriValidator: FormatValidatorFn = (uri: unknown) => {
219
+ if (typeof uri !== 'string') {
220
+ return true;
221
+ }
209
222
  // eslint-disable-next-line no-control-regex
210
- if (/[^\x00-\x7F]/.test(uri)) return false;
211
- if (!hasValidPercentEncoding(uri)) return false;
212
- const match = uri.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]*)/);
223
+ if (/[^\u0000-\u007F]/.test(uri)) {
224
+ return false;
225
+ }
226
+ if (!hasValidPercentEncoding(uri)) {
227
+ return false;
228
+ }
229
+ const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]*)/.exec(uri);
213
230
  if (match) {
214
231
  const authority = match[2];
215
232
  const atIndex = authority.indexOf('@');
216
233
  if (atIndex > 0) {
217
- const userinfo = authority.substring(0, atIndex);
234
+ // userinfo is a string; prefer-set-has misfires here — two String#includes
235
+ // calls are faster than building a Set or a regex for this membership check.
236
+ // oxlint-disable-next-line unicorn/prefer-set-has
237
+ const userinfo = authority.slice(0, atIndex);
218
238
  if (userinfo.includes('[') || userinfo.includes(']')) {
219
239
  return false;
220
240
  }
221
241
  }
222
242
  // Validate port: must be numeric
223
- let hostPort = atIndex >= 0 ? authority.substring(atIndex + 1) : authority;
243
+ let hostPort = atIndex === -1 ? authority : authority.slice(atIndex + 1);
224
244
  if (hostPort.startsWith('[')) {
225
245
  const bracketEnd = hostPort.indexOf(']');
226
- if (bracketEnd >= 0) {
227
- hostPort = hostPort.substring(bracketEnd + 1);
246
+ if (bracketEnd !== -1) {
247
+ hostPort = hostPort.slice(bracketEnd + 1);
228
248
  }
229
249
  }
230
250
  const colonIndex = hostPort.lastIndexOf(':');
231
- if (colonIndex >= 0) {
232
- const port = hostPort.substring(colonIndex + 1);
251
+ if (colonIndex !== -1) {
252
+ const port = hostPort.slice(colonIndex + 1);
233
253
  if (port.length > 0 && !/^\d+$/.test(port)) {
234
254
  return false;
235
255
  }
@@ -239,15 +259,21 @@ const uriValidator: FormatValidatorFn = function (uri: unknown) {
239
259
  };
240
260
 
241
261
  const uriReferenceValidator: FormatValidatorFn = (uri: unknown) => {
242
- if (typeof uri !== 'string') return true;
262
+ if (typeof uri !== 'string') {
263
+ return true;
264
+ }
243
265
  // eslint-disable-next-line no-control-regex
244
- if (/[^\x00-\x7F]/.test(uri)) return false;
266
+ if (/[^\u0000-\u007F]/.test(uri)) {
267
+ return false;
268
+ }
245
269
  // URI-reference allows relative URIs
246
270
  return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
247
271
  };
248
272
 
249
273
  const uriTemplateValidator: FormatValidatorFn = (uri: unknown) => {
250
- if (typeof uri !== 'string') return true;
274
+ if (typeof uri !== 'string') {
275
+ return true;
276
+ }
251
277
  // URI template allows braces for expressions.
252
278
  if (!/^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) {
253
279
  return false;
@@ -286,66 +312,100 @@ const hasValidTildeEscapes = (segment: string): boolean => {
286
312
  };
287
313
 
288
314
  const jsonPointerValidator: FormatValidatorFn = (pointer: unknown) => {
289
- if (typeof pointer !== 'string') return true;
315
+ if (typeof pointer !== 'string') {
316
+ return true;
317
+ }
290
318
  // JSON Pointer: empty, or a sequence of '/'-prefixed reference tokens.
291
319
  // In each token, '~' must be escaped as '~0' or '~1'.
292
- if (pointer === '') return true;
293
- if (!/^(?:\/[^/]*)+$/.test(pointer)) return false;
294
- const tokens = pointer.split('/').slice(1); // first element is empty before leading '/'
295
- for (const token of tokens) {
296
- if (!hasValidTildeEscapes(token)) return false;
320
+ if (pointer === '') {
321
+ return true;
322
+ }
323
+ if (!/^(?:\/[^/]*)+$/.test(pointer)) {
324
+ return false;
325
+ }
326
+ const tokens = pointer.split('/');
327
+ for (let i = 1; i < tokens.length; i++) {
328
+ if (!hasValidTildeEscapes(tokens[i])) {
329
+ return false;
330
+ }
297
331
  }
298
332
  return true;
299
333
  };
300
334
 
301
335
  const relativeJsonPointerValidator: FormatValidatorFn = (pointer: unknown) => {
302
- if (typeof pointer !== 'string') return true;
336
+ if (typeof pointer !== 'string') {
337
+ return true;
338
+ }
303
339
  // Relative JSON Pointer: non-negative integer prefix (no leading zeros unless zero),
304
340
  // followed by either '#', a JSON Pointer, or nothing.
305
- const match = pointer.match(/^(0|[1-9]\d*)(.*)$/);
306
- if (!match) return false;
341
+ const match = /^(0|[1-9]\d*)(.*)$/.exec(pointer);
342
+ if (!match) {
343
+ return false;
344
+ }
307
345
  const suffix = match[2];
308
- if (suffix === '' || suffix === '#') return true;
309
- if (!suffix.startsWith('/')) return false;
310
- if (!/^(?:\/[^/]*)+$/.test(suffix)) return false;
311
- const tokens = suffix.split('/').slice(1);
312
- for (const token of tokens) {
313
- if (!hasValidTildeEscapes(token)) return false;
346
+ if (suffix === '' || suffix === '#') {
347
+ return true;
348
+ }
349
+ if (!suffix.startsWith('/')) {
350
+ return false;
351
+ }
352
+ if (!/^(?:\/[^/]*)+$/.test(suffix)) {
353
+ return false;
354
+ }
355
+ const tokens = suffix.split('/');
356
+ for (let i = 1; i < tokens.length; i++) {
357
+ if (!hasValidTildeEscapes(tokens[i])) {
358
+ return false;
359
+ }
314
360
  }
315
361
  return true;
316
362
  };
317
363
 
318
364
  const timeValidator: FormatValidatorFn = (time: unknown) => {
319
- if (typeof time !== 'string') return true;
365
+ if (typeof time !== 'string') {
366
+ return true;
367
+ }
320
368
  return parseRfc3339Time(time) !== null;
321
369
  };
322
370
 
323
371
  const idnEmailValidator: FormatValidatorFn = (email: unknown) => {
324
- if (typeof email !== 'string') return true;
372
+ if (typeof email !== 'string') {
373
+ return true;
374
+ }
325
375
  // Simple email check, allowing international chars
326
376
  return /^[^\s@]+@[^\s@]+$/.test(email);
327
377
  };
328
378
 
329
379
  const idnHostnameValidator: FormatValidatorFn = (hostname: unknown) => {
330
- if (typeof hostname !== 'string') return true;
380
+ if (typeof hostname !== 'string') {
381
+ return true;
382
+ }
331
383
  return isValidIdnHostname(hostname);
332
384
  };
333
385
 
334
386
  const iriValidator: FormatValidatorFn = (iri: unknown) => {
335
- if (typeof iri !== 'string') return true;
387
+ if (typeof iri !== 'string') {
388
+ return true;
389
+ }
336
390
  if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:[^"\\<>^{}^`| ]*$/u.test(iri)) {
337
391
  return false;
338
392
  }
339
393
  try {
394
+ // Constructed purely to detect an unparseable IRI (throws). Avoids URL.canParse,
395
+ // which is unavailable in older browsers the UMD build still targets; no-new is
396
+ // disabled here as the construction is intentional and the result is unused.
397
+ // oxlint-disable-next-line no-new
340
398
  new URL(iri);
341
399
  return true;
342
- } catch (_e) {
400
+ } catch {
343
401
  return false;
344
402
  }
345
403
  };
346
404
 
347
405
  const iriReferenceValidator: FormatValidatorFn = (iriReference: unknown) => {
348
- if (typeof iriReference !== 'string') return true;
406
+ if (typeof iriReference !== 'string') {
407
+ return true;
408
+ }
349
409
  return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
350
410
  };
351
411
 
@@ -385,10 +445,24 @@ export function getFormatValidators(options?: FormatValidatorsOptions): Record<s
385
445
  ...inbuiltValidators,
386
446
  ...(options?.strictUris ? { uri: strictUriValidator } : {}),
387
447
  ...customValidators,
388
- ...(options?.customFormats || {}),
448
+ ...options?.customFormats,
389
449
  };
390
450
  }
391
451
 
452
+ export function resolveFormatValidator(name: string, options?: FormatValidatorsOptions): FormatValidatorFn | undefined {
453
+ const custom = options?.customFormats;
454
+ if (custom && Object.hasOwn(custom, name)) {
455
+ return custom[name] as FormatValidatorFn | undefined;
456
+ }
457
+ if (Object.hasOwn(customValidators, name)) {
458
+ return customValidators[name];
459
+ }
460
+ if (options?.strictUris && name === 'uri') {
461
+ return strictUriValidator;
462
+ }
463
+ return inbuiltValidators[name];
464
+ }
465
+
392
466
  export function registerFormat(name: string, validatorFunction: FormatValidatorFn) {
393
467
  customValidators[name] = validatorFunction;
394
468
  }
@@ -411,10 +485,16 @@ export function isFormatSupported(name: string, customFormats?: Record<string, F
411
485
  if (customFormats) {
412
486
  const custom = customFormats[name];
413
487
  // Explicitly null means unregistered at instance level
414
- if (custom === null) return false;
415
- if (custom != null) return true;
488
+ if (custom === null) {
489
+ return false;
490
+ }
491
+ if (custom != null) {
492
+ return true;
493
+ }
494
+ }
495
+ if (name in customValidators) {
496
+ return customValidators[name] != null;
416
497
  }
417
- if (name in customValidators) return customValidators[name] != null;
418
498
  return name in inbuiltValidators;
419
499
  }
420
500