@schema-ts/core 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,2505 @@
1
+ // src/util.ts
2
+ function matchSchemaType(value, type) {
3
+ switch (type) {
4
+ case "string":
5
+ return typeof value === "string";
6
+ case "number":
7
+ return typeof value === "number" || typeof value === "bigint";
8
+ case "integer":
9
+ return typeof value === "number" && Number.isInteger(value);
10
+ case "boolean":
11
+ return typeof value === "boolean";
12
+ case "object":
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ case "array":
15
+ return Array.isArray(value);
16
+ case "null":
17
+ return value === null || value === void 0;
18
+ default:
19
+ return false;
20
+ }
21
+ }
22
+ function detectSchemaType(value) {
23
+ if (value === null || value === void 0) {
24
+ return "null";
25
+ }
26
+ if (Array.isArray(value)) {
27
+ return "array";
28
+ }
29
+ const type = typeof value;
30
+ if (type === "string") {
31
+ return "string";
32
+ }
33
+ if (type === "boolean") {
34
+ return "boolean";
35
+ }
36
+ if (type === "number") {
37
+ return Number.isInteger(value) ? "integer" : "number";
38
+ }
39
+ if (type === "bigint") {
40
+ return "number";
41
+ }
42
+ if (type === "object") {
43
+ return "object";
44
+ }
45
+ return "unknown";
46
+ }
47
+ function parseJsonPointer(jsonPointer) {
48
+ if (jsonPointer === "") return [];
49
+ const pointer = jsonPointer.charAt(0) === "/" ? jsonPointer.substring(1) : jsonPointer;
50
+ return pointer.split("/").map((t) => jsonPointerUnescape(decodeURIComponent(t)));
51
+ }
52
+ function getJsonPointer(obj, jsonPointer) {
53
+ return get(obj, parseJsonPointer(jsonPointer));
54
+ }
55
+ function removeJsonPointer(obj, jsonPointer) {
56
+ const path = parseJsonPointer(jsonPointer);
57
+ if (path.length === 0) {
58
+ return false;
59
+ }
60
+ if (obj === null || obj === void 0) {
61
+ return false;
62
+ }
63
+ let current = obj;
64
+ for (let i = 0; i < path.length - 1; i++) {
65
+ const segment = path[i];
66
+ if (Array.isArray(current)) {
67
+ const idx = Number(segment);
68
+ if (isNaN(idx) || idx < 0 || idx >= current.length) {
69
+ return false;
70
+ }
71
+ current = current[idx];
72
+ } else if (typeof current === "object" && current !== null) {
73
+ if (!Object.hasOwn(current, segment)) {
74
+ return false;
75
+ }
76
+ current = current[segment];
77
+ } else {
78
+ return false;
79
+ }
80
+ if (current === null || current === void 0) {
81
+ return false;
82
+ }
83
+ }
84
+ const lastSegment = path[path.length - 1];
85
+ if (Array.isArray(current)) {
86
+ const idx = Number(lastSegment);
87
+ if (isNaN(idx) || idx < 0 || idx >= current.length) {
88
+ return false;
89
+ }
90
+ current.splice(idx, 1);
91
+ return true;
92
+ } else if (typeof current === "object" && current !== null) {
93
+ if (Object.hasOwn(current, lastSegment)) {
94
+ delete current[lastSegment];
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ function setJsonPointer(obj, jsonPointer, value) {
101
+ const path = parseJsonPointer(jsonPointer);
102
+ if (path.length === 0) {
103
+ return false;
104
+ }
105
+ if (obj === null || obj === void 0) {
106
+ return false;
107
+ }
108
+ let current = obj;
109
+ for (let i = 0; i < path.length; i++) {
110
+ const segment = path[i];
111
+ const isLast = i === path.length - 1;
112
+ if (Array.isArray(current)) {
113
+ const idx = Number(segment);
114
+ if (isNaN(idx) || idx < 0) {
115
+ return false;
116
+ }
117
+ if (idx >= current.length) {
118
+ for (let k = current.length; k <= idx; k++) current.push(void 0);
119
+ }
120
+ if (isLast) {
121
+ current[idx] = value;
122
+ return true;
123
+ }
124
+ if (current[idx] === null || current[idx] === void 0 || typeof current[idx] !== "object") {
125
+ const nextToken = path[i + 1];
126
+ const nextIdx = Number(nextToken);
127
+ current[idx] = !isNaN(nextIdx) ? [] : {};
128
+ }
129
+ current = current[idx];
130
+ } else if (typeof current === "object" && current !== null) {
131
+ if (isLast) {
132
+ current[segment] = value;
133
+ return true;
134
+ }
135
+ if (!Object.hasOwn(current, segment) || current[segment] === void 0) {
136
+ const nextToken = path[i + 1];
137
+ const nextIdx = Number(nextToken);
138
+ current[segment] = !isNaN(nextIdx) ? [] : {};
139
+ }
140
+ current = current[segment];
141
+ } else {
142
+ return false;
143
+ }
144
+ }
145
+ return true;
146
+ }
147
+ function get(obj, path) {
148
+ let current = obj;
149
+ for (const segment of path) {
150
+ if (Array.isArray(current)) {
151
+ const currentIndex = Number(segment);
152
+ if (isNaN(currentIndex) || currentIndex < 0 || currentIndex >= current.length) {
153
+ return void 0;
154
+ } else {
155
+ current = current[currentIndex];
156
+ }
157
+ } else if (typeof current === "object" && current !== null) {
158
+ if (!Object.hasOwn(current, segment)) {
159
+ return void 0;
160
+ } else {
161
+ current = current[segment];
162
+ }
163
+ } else {
164
+ return void 0;
165
+ }
166
+ }
167
+ return current;
168
+ }
169
+ function deepEqual(a, b) {
170
+ if (a === b) return true;
171
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
172
+ return false;
173
+ }
174
+ if (a instanceof Date && b instanceof Date) {
175
+ return a.getTime() === b.getTime();
176
+ }
177
+ if (a instanceof Date || b instanceof Date) {
178
+ return false;
179
+ }
180
+ if (a instanceof RegExp && b instanceof RegExp) {
181
+ return a.source === b.source && a.flags === b.flags;
182
+ }
183
+ if (a instanceof RegExp || b instanceof RegExp) {
184
+ return false;
185
+ }
186
+ if (a instanceof Map && b instanceof Map) {
187
+ if (a.size !== b.size) return false;
188
+ for (const [key, value] of a) {
189
+ if (!b.has(key) || !deepEqual(value, b.get(key))) {
190
+ return false;
191
+ }
192
+ }
193
+ return true;
194
+ }
195
+ if (a instanceof Map || b instanceof Map) {
196
+ return false;
197
+ }
198
+ if (a instanceof Set && b instanceof Set) {
199
+ if (a.size !== b.size) return false;
200
+ for (const value of a) {
201
+ let found = false;
202
+ for (const bValue of b) {
203
+ if (deepEqual(value, bValue)) {
204
+ found = true;
205
+ break;
206
+ }
207
+ }
208
+ if (!found) return false;
209
+ }
210
+ return true;
211
+ }
212
+ if (a instanceof Set || b instanceof Set) {
213
+ return false;
214
+ }
215
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
216
+ if (Array.isArray(a)) {
217
+ const arrA = a;
218
+ const arrB = b;
219
+ if (arrA.length !== arrB.length) return false;
220
+ for (let i = 0; i < arrA.length; i++) {
221
+ if (!deepEqual(arrA[i], arrB[i])) return false;
222
+ }
223
+ return true;
224
+ }
225
+ const keysA = Object.keys(a);
226
+ const keysB = Object.keys(b);
227
+ if (keysA.length !== keysB.length) return false;
228
+ for (const key of keysA) {
229
+ if (!Object.hasOwn(b, key)) return false;
230
+ if (!deepEqual(
231
+ a[key],
232
+ b[key]
233
+ )) {
234
+ return false;
235
+ }
236
+ }
237
+ return true;
238
+ }
239
+ function jsonPointerEscape(str) {
240
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
241
+ }
242
+ function jsonPointerUnescape(str) {
243
+ return str.replace(/~1/g, "/").replace(/~0/g, "~");
244
+ }
245
+ function jsonPointerJoin(base, token) {
246
+ if (base === "") return "/" + jsonPointerEscape(token);
247
+ else return base + "/" + jsonPointerEscape(token);
248
+ }
249
+ function resolveAbsolutePath(nodePath, relativePath) {
250
+ if (relativePath.startsWith("/")) {
251
+ return nodePath === "" ? relativePath : nodePath + relativePath;
252
+ }
253
+ return relativePath;
254
+ }
255
+ var MAX_REGEX_CACHE_SIZE = 1e3;
256
+ var regexCache = /* @__PURE__ */ new Map();
257
+ function safeRegexTest(pattern, value) {
258
+ let regex = regexCache.get(pattern);
259
+ if (regex === void 0) {
260
+ try {
261
+ regex = new RegExp(pattern);
262
+ if (regexCache.size >= MAX_REGEX_CACHE_SIZE) {
263
+ const firstKey = regexCache.keys().next().value;
264
+ if (firstKey !== void 0) {
265
+ regexCache.delete(firstKey);
266
+ }
267
+ }
268
+ regexCache.set(pattern, regex);
269
+ } catch {
270
+ regexCache.set(pattern, null);
271
+ return false;
272
+ }
273
+ }
274
+ if (regex === null) {
275
+ return false;
276
+ }
277
+ return regex.test(value);
278
+ }
279
+
280
+ // src/stringformat.ts
281
+ var DATE_RE = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
282
+ var DURATION_RE = /^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$/;
283
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
284
+ var HOSTNAME_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
285
+ var IPV4_RE = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
286
+ var IPV6_RE = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){1,7}:)|(([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})|(([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2})|(([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3})|(([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4})|(([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5})|([0-9a-fA-F]{1,4}:)((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9]))$/;
287
+ var RFC3339_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
288
+ var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
289
+ var StringFormatValidator = class {
290
+ validate(format, value) {
291
+ switch (format) {
292
+ case "date-time":
293
+ return this.isDateTime(value);
294
+ case "date":
295
+ return this.isDate(value);
296
+ case "email":
297
+ return this.isEmail(value);
298
+ case "hostname":
299
+ return this.isHostname(value);
300
+ case "ipv4":
301
+ return this.isIPv4(value);
302
+ case "ipv6":
303
+ return this.isIPv6(value);
304
+ case "uri":
305
+ return this.isUri(value);
306
+ case "uuid":
307
+ return this.isUuid(value);
308
+ case "duration":
309
+ return this.isDuration(value);
310
+ default:
311
+ return true;
312
+ }
313
+ }
314
+ isDateTime(value) {
315
+ if (!RFC3339_RE.test(value)) return false;
316
+ return !isNaN(Date.parse(value));
317
+ }
318
+ isDate(value) {
319
+ const m = DATE_RE.exec(value);
320
+ if (!m) return false;
321
+ const year = Number(m[1]), month = Number(m[2]), day = Number(m[3]), daysInMonth = [
322
+ 31,
323
+ year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
324
+ 31,
325
+ 30,
326
+ 31,
327
+ 30,
328
+ 31,
329
+ 31,
330
+ 30,
331
+ 31,
332
+ 30,
333
+ 31
334
+ ];
335
+ return day <= daysInMonth[month - 1];
336
+ }
337
+ isEmail(value) {
338
+ return EMAIL_RE.test(value);
339
+ }
340
+ isHostname(value) {
341
+ return HOSTNAME_RE.test(value);
342
+ }
343
+ isIPv4(value) {
344
+ return IPV4_RE.test(value);
345
+ }
346
+ isIPv6(value) {
347
+ return IPV6_RE.test(value);
348
+ }
349
+ isUri(value) {
350
+ try {
351
+ const u = new URL(value);
352
+ return typeof u.protocol === "string" && u.protocol.length > 0;
353
+ } catch {
354
+ return false;
355
+ }
356
+ }
357
+ isUuid(value) {
358
+ return UUID_RE.test(value);
359
+ }
360
+ isDuration(value) {
361
+ return DURATION_RE.test(value) && value !== "P";
362
+ }
363
+ };
364
+ var stringFormatValidator = new StringFormatValidator();
365
+
366
+ // src/i18n.ts
367
+ var MESSAGES = {
368
+ "validation.anyOf": "must match at least one schema",
369
+ "validation.oneOf": "must match exactly one schema, matched {count}",
370
+ "validation.not": "must not match schema",
371
+ "validation.const": "must be equal to {value}",
372
+ "validation.enum": "must be equal to one of the allowed values",
373
+ "validation.type": "must be {expected}",
374
+ "validation.maximum": "must be <= {value}",
375
+ "validation.exclusiveMaximum": "must be < {value}",
376
+ "validation.minimum": "must be >= {value}",
377
+ "validation.exclusiveMinimum": "must be > {value}",
378
+ "validation.multipleOf": "must be multiple of {value}",
379
+ "validation.maxLength": "must be shorter than or equal to {value} characters",
380
+ "validation.minLength": "must be longer than or equal to {value} characters",
381
+ "validation.pattern": 'must match pattern "{pattern}"',
382
+ "validation.format": 'must match format "{format}"',
383
+ "validation.maxItems": "must have at most {value} items",
384
+ "validation.minItems": "must have at least {value} items",
385
+ "validation.uniqueItems": "must not contain duplicate items",
386
+ "validation.contains": "must contain at least one valid item",
387
+ "validation.minContains": "must contain at least {value} valid items",
388
+ "validation.maxContains": "must contain at most {value} valid items",
389
+ "validation.maxProperties": "must have at most {value} properties",
390
+ "validation.minProperties": "must have at least {value} properties",
391
+ "validation.required": "must have required property '{property}'",
392
+ "validation.dependentRequired": "property '{source}' requires property '{target}'",
393
+ "validation.additionalProperties": "must not have additional properties: {properties}",
394
+ "validation.failed": "validation failed"
395
+ };
396
+ var defaultErrorFormatter = (msg) => {
397
+ const template = MESSAGES[msg.key] ?? msg.key;
398
+ if (!msg.params) return template;
399
+ let result = template;
400
+ for (const [k, v] of Object.entries(msg.params)) {
401
+ const value = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
402
+ result = result.replace(`{${k}}`, value);
403
+ }
404
+ return result;
405
+ };
406
+
407
+ // src/version.ts
408
+ var DRAFT_URIS = {
409
+ "draft-04": [
410
+ "http://json-schema.org/draft-04/schema#",
411
+ "http://json-schema.org/draft-04/schema"
412
+ ],
413
+ "draft-07": [
414
+ "http://json-schema.org/draft-07/schema#",
415
+ "http://json-schema.org/draft-07/schema"
416
+ ],
417
+ "draft-2019-09": [
418
+ "https://json-schema.org/draft/2019-09/schema",
419
+ "https://json-schema.org/draft/2019-09/schema#"
420
+ ],
421
+ "draft-2020-12": [
422
+ "https://json-schema.org/draft/2020-12/schema",
423
+ "https://json-schema.org/draft/2020-12/schema#"
424
+ ]
425
+ };
426
+ function detectSchemaDraft(schema) {
427
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
428
+ return "draft-2020-12";
429
+ }
430
+ const s = schema;
431
+ if (s.$schema && typeof s.$schema === "string") {
432
+ for (const [draft, uris] of Object.entries(DRAFT_URIS)) {
433
+ if (uris.some((uri) => s.$schema === uri)) {
434
+ return draft;
435
+ }
436
+ }
437
+ }
438
+ if ("prefixItems" in s) {
439
+ return "draft-2020-12";
440
+ }
441
+ if ("$recursiveRef" in s || "$recursiveAnchor" in s || "unevaluatedProperties" in s || "unevaluatedItems" in s) {
442
+ return "draft-2019-09";
443
+ }
444
+ if ("id" in s && !("$id" in s)) {
445
+ return "draft-04";
446
+ }
447
+ if (typeof s.exclusiveMaximum === "boolean" || typeof s.exclusiveMinimum === "boolean") {
448
+ return "draft-04";
449
+ }
450
+ if ("dependencies" in s) {
451
+ if ("$id" in s) {
452
+ return "draft-07";
453
+ }
454
+ return "draft-04";
455
+ }
456
+ if ("additionalItems" in s) {
457
+ if ("$id" in s) {
458
+ return "draft-07";
459
+ }
460
+ return "draft-04";
461
+ }
462
+ return "draft-2020-12";
463
+ }
464
+
465
+ // src/normalize.ts
466
+ function normalizeSchema(schema, options = {}) {
467
+ if (typeof schema === "boolean") {
468
+ return schema ? {} : { not: {} };
469
+ }
470
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
471
+ return {};
472
+ }
473
+ const schemaObj = schema;
474
+ const sourceDraft = options.sourceDraft ?? detectSchemaDraft(schemaObj);
475
+ const normalized = { ...schemaObj };
476
+ switch (sourceDraft) {
477
+ case "draft-04":
478
+ normalizeDraft04(normalized);
479
+ normalizeDraft07(normalized);
480
+ normalizeDraft201909(normalized);
481
+ break;
482
+ case "draft-07":
483
+ normalizeDraft07(normalized);
484
+ normalizeDraft201909(normalized);
485
+ break;
486
+ case "draft-2019-09":
487
+ normalizeDraft07(normalized);
488
+ normalizeDraft201909(normalized);
489
+ break;
490
+ }
491
+ normalizeGeneral(normalized);
492
+ normalizeNestedSchemas(normalized, options);
493
+ if (normalized.$schema) {
494
+ normalized.$schema = "https://json-schema.org/draft/2020-12/schema";
495
+ }
496
+ return normalized;
497
+ }
498
+ function normalizeDraft04(schema) {
499
+ const legacy = schema;
500
+ if ("id" in legacy && !("$id" in schema)) {
501
+ schema.$id = legacy.id;
502
+ delete legacy.id;
503
+ }
504
+ if ("$ref" in schema) {
505
+ for (const key of Object.keys(schema)) {
506
+ if (key !== "$ref" && key !== "$schema" && key !== "id" && key !== "$id" && key !== "$comment") {
507
+ delete legacy[key];
508
+ }
509
+ }
510
+ }
511
+ if (typeof legacy.exclusiveMaximum === "boolean" && legacy.exclusiveMaximum === true) {
512
+ if (schema.maximum !== void 0) {
513
+ schema.exclusiveMaximum = schema.maximum;
514
+ delete schema.maximum;
515
+ } else {
516
+ delete legacy.exclusiveMaximum;
517
+ }
518
+ }
519
+ if (typeof legacy.exclusiveMinimum === "boolean" && legacy.exclusiveMinimum === true) {
520
+ if (schema.minimum !== void 0) {
521
+ schema.exclusiveMinimum = schema.minimum;
522
+ delete schema.minimum;
523
+ } else {
524
+ delete legacy.exclusiveMinimum;
525
+ }
526
+ }
527
+ if (legacy.exclusiveMaximum === false) {
528
+ delete legacy.exclusiveMaximum;
529
+ }
530
+ if (legacy.exclusiveMinimum === false) {
531
+ delete legacy.exclusiveMinimum;
532
+ }
533
+ if (Array.isArray(schema.enum) && schema.enum.length === 1 && schema.const === void 0) {
534
+ schema.const = schema.enum[0];
535
+ }
536
+ }
537
+ function normalizeDraft07(schema) {
538
+ const legacy = schema;
539
+ if (Array.isArray(legacy.items)) {
540
+ schema.prefixItems = legacy.items;
541
+ if ("additionalItems" in legacy) {
542
+ if (typeof legacy.additionalItems === "object" || typeof legacy.additionalItems === "boolean") {
543
+ schema.items = legacy.additionalItems;
544
+ }
545
+ delete legacy.additionalItems;
546
+ } else {
547
+ delete legacy.items;
548
+ }
549
+ } else if ("additionalItems" in legacy) {
550
+ delete legacy.additionalItems;
551
+ }
552
+ if ("dependencies" in legacy) {
553
+ const deps = legacy.dependencies;
554
+ for (const [prop, value] of Object.entries(deps)) {
555
+ if (Array.isArray(value)) {
556
+ if (!schema.dependentRequired) {
557
+ schema.dependentRequired = {};
558
+ }
559
+ schema.dependentRequired[prop] = value;
560
+ } else {
561
+ if (!schema.dependentSchemas) {
562
+ schema.dependentSchemas = {};
563
+ }
564
+ schema.dependentSchemas[prop] = value;
565
+ }
566
+ }
567
+ delete legacy.dependencies;
568
+ }
569
+ }
570
+ function normalizeGeneral(schema) {
571
+ const legacy = schema;
572
+ if (legacy.nullable === true) {
573
+ if (typeof schema.type === "string") {
574
+ schema.type = [schema.type, "null"];
575
+ } else if (Array.isArray(schema.type)) {
576
+ if (!schema.type.includes("null")) {
577
+ schema.type = [...schema.type, "null"];
578
+ }
579
+ }
580
+ delete legacy.nullable;
581
+ }
582
+ if ("example" in legacy) {
583
+ if (!("examples" in schema)) {
584
+ schema.examples = [legacy.example];
585
+ }
586
+ delete legacy.example;
587
+ }
588
+ }
589
+ function normalizeDraft201909(schema) {
590
+ if ("$recursiveRef" in schema) {
591
+ let ref = schema.$recursiveRef;
592
+ if (ref === "#") {
593
+ ref = "#recursiveAnchor";
594
+ }
595
+ schema.$dynamicRef = ref;
596
+ delete schema.$recursiveRef;
597
+ }
598
+ if ("$recursiveAnchor" in schema) {
599
+ if (schema.$recursiveAnchor === true) {
600
+ schema.$dynamicAnchor = schema.$dynamicAnchor ?? "recursiveAnchor";
601
+ }
602
+ delete schema.$recursiveAnchor;
603
+ }
604
+ }
605
+ function normalizeNestedSchemas(schema, options) {
606
+ if (schema.$defs) {
607
+ schema.$defs = Object.fromEntries(
608
+ Object.entries(schema.$defs).map(([key, subSchema]) => [
609
+ key,
610
+ normalizeSchema(subSchema, options)
611
+ ])
612
+ );
613
+ }
614
+ if ("definitions" in schema) {
615
+ const defs = schema.definitions;
616
+ if (!schema.$defs) {
617
+ schema.$defs = {};
618
+ }
619
+ for (const [key, subSchema] of Object.entries(defs)) {
620
+ schema.$defs[key] = normalizeSchema(subSchema, options);
621
+ }
622
+ delete schema.definitions;
623
+ }
624
+ if (schema.properties) {
625
+ schema.properties = Object.fromEntries(
626
+ Object.entries(schema.properties).map(([key, subSchema]) => [
627
+ key,
628
+ normalizeSchema(subSchema, options)
629
+ ])
630
+ );
631
+ }
632
+ if (schema.patternProperties) {
633
+ schema.patternProperties = Object.fromEntries(
634
+ Object.entries(schema.patternProperties).map(([key, subSchema]) => [
635
+ key,
636
+ normalizeSchema(subSchema, options)
637
+ ])
638
+ );
639
+ }
640
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
641
+ schema.additionalProperties = normalizeSchema(
642
+ schema.additionalProperties,
643
+ options
644
+ );
645
+ }
646
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
647
+ schema.items = normalizeSchema(schema.items, options);
648
+ }
649
+ if (schema.prefixItems) {
650
+ schema.prefixItems = schema.prefixItems.map(
651
+ (s) => normalizeSchema(s, options)
652
+ );
653
+ }
654
+ if (schema.contains) {
655
+ schema.contains = normalizeSchema(schema.contains, options);
656
+ }
657
+ if (schema.allOf) {
658
+ schema.allOf = schema.allOf.map((s) => normalizeSchema(s, options));
659
+ }
660
+ if (schema.anyOf) {
661
+ schema.anyOf = schema.anyOf.map((s) => normalizeSchema(s, options));
662
+ }
663
+ if (schema.oneOf) {
664
+ schema.oneOf = schema.oneOf.map((s) => normalizeSchema(s, options));
665
+ }
666
+ if (schema.not) {
667
+ schema.not = normalizeSchema(schema.not, options);
668
+ }
669
+ if (schema.if) {
670
+ schema.if = normalizeSchema(schema.if, options);
671
+ }
672
+ if (schema.then) {
673
+ schema.then = normalizeSchema(schema.then, options);
674
+ }
675
+ if (schema.else) {
676
+ schema.else = normalizeSchema(schema.else, options);
677
+ }
678
+ if (schema.dependentSchemas) {
679
+ schema.dependentSchemas = Object.fromEntries(
680
+ Object.entries(schema.dependentSchemas).map(([key, subSchema]) => [
681
+ key,
682
+ normalizeSchema(subSchema, options)
683
+ ])
684
+ );
685
+ }
686
+ if (schema.unevaluatedItems) {
687
+ schema.unevaluatedItems = normalizeSchema(schema.unevaluatedItems, options);
688
+ }
689
+ if (schema.unevaluatedProperties) {
690
+ schema.unevaluatedProperties = normalizeSchema(
691
+ schema.unevaluatedProperties,
692
+ options
693
+ );
694
+ }
695
+ if (schema.propertyNames) {
696
+ schema.propertyNames = normalizeSchema(schema.propertyNames, options);
697
+ }
698
+ if (schema.contentSchema) {
699
+ schema.contentSchema = normalizeSchema(schema.contentSchema, options);
700
+ }
701
+ }
702
+
703
+ // src/validate.ts
704
+ var Validator = class {
705
+ formatValidator;
706
+ errorFormatter;
707
+ constructor(config = {}) {
708
+ this.formatValidator = config.formatValidator ?? stringFormatValidator;
709
+ this.errorFormatter = config.errorFormatter ?? defaultErrorFormatter;
710
+ }
711
+ /**
712
+ * Format an ErrorMessage to a localized string.
713
+ */
714
+ formatError(msg) {
715
+ return this.errorFormatter(msg);
716
+ }
717
+ validate(schema, value, keywordLocation = "#", instanceLocation = "", options = {}) {
718
+ const { fastFail = false } = options;
719
+ const output = {
720
+ valid: false,
721
+ keywordLocation,
722
+ absoluteKeywordLocation: keywordLocation,
723
+ instanceLocation,
724
+ absoluteInstanceLocation: instanceLocation,
725
+ errors: []
726
+ };
727
+ if (schema.if) {
728
+ const ifResult = this.validate(
729
+ schema.if,
730
+ value,
731
+ keywordLocation + `/if`,
732
+ `${instanceLocation}`,
733
+ options
734
+ );
735
+ if (ifResult.valid) {
736
+ if (schema.then) {
737
+ const thenResult = this.validate(
738
+ schema.then,
739
+ value,
740
+ keywordLocation + `/then`,
741
+ instanceLocation,
742
+ options
743
+ );
744
+ if (!thenResult.valid) {
745
+ output.errors.push(...thenResult.errors || []);
746
+ if (fastFail) return output;
747
+ }
748
+ }
749
+ } else if (schema.else) {
750
+ const elseResult = this.validate(
751
+ schema.else,
752
+ value,
753
+ keywordLocation + `/else`,
754
+ instanceLocation,
755
+ options
756
+ );
757
+ if (!elseResult.valid) {
758
+ output.errors.push(...elseResult.errors || []);
759
+ if (fastFail) return output;
760
+ }
761
+ }
762
+ }
763
+ if (schema.allOf) {
764
+ for (let index = 0; index < schema.allOf.length; index++) {
765
+ const subSchema = schema.allOf[index];
766
+ const result = this.validate(
767
+ subSchema,
768
+ value,
769
+ keywordLocation + `/allOf/${index}`,
770
+ instanceLocation,
771
+ options
772
+ );
773
+ if (!result.valid) {
774
+ output.errors.push(...result.errors || []);
775
+ if (fastFail) return output;
776
+ }
777
+ }
778
+ }
779
+ if (schema.anyOf) {
780
+ let hasValid = false;
781
+ const errors = [];
782
+ for (let index = 0; index < schema.anyOf.length; index++) {
783
+ const subSchema = schema.anyOf[index];
784
+ const result = this.validate(
785
+ subSchema,
786
+ value,
787
+ keywordLocation + `/anyOf/${index}`,
788
+ instanceLocation,
789
+ options
790
+ );
791
+ if (result.valid) {
792
+ hasValid = true;
793
+ break;
794
+ } else {
795
+ errors.push(...result.errors || []);
796
+ }
797
+ }
798
+ if (!hasValid) {
799
+ output.errors.push({
800
+ valid: false,
801
+ keywordLocation: `${keywordLocation}/anyOf`,
802
+ instanceLocation,
803
+ errors,
804
+ error: this.formatError({ key: "validation.anyOf" })
805
+ });
806
+ if (fastFail) return output;
807
+ }
808
+ }
809
+ if (schema.oneOf) {
810
+ const results = schema.oneOf.map(
811
+ (subSchema, index) => this.validate(
812
+ subSchema,
813
+ value,
814
+ keywordLocation + `/oneOf/${index}`,
815
+ instanceLocation,
816
+ options
817
+ )
818
+ ), validCount = results.filter((r) => r.valid).length;
819
+ if (validCount !== 1) {
820
+ output.errors.push({
821
+ valid: false,
822
+ keywordLocation: `${keywordLocation}/oneOf`,
823
+ instanceLocation,
824
+ errors: [],
825
+ annotations: results,
826
+ error: this.formatError({
827
+ key: "validation.oneOf",
828
+ params: { count: validCount }
829
+ })
830
+ });
831
+ if (fastFail) return output;
832
+ }
833
+ }
834
+ if (schema.not) {
835
+ const result = this.validate(
836
+ schema.not,
837
+ value,
838
+ keywordLocation + `/not`,
839
+ instanceLocation,
840
+ options
841
+ );
842
+ if (result.valid) {
843
+ output.errors.push({
844
+ valid: false,
845
+ keywordLocation: `${keywordLocation}/not`,
846
+ instanceLocation,
847
+ errors: [],
848
+ annotations: [result],
849
+ error: this.formatError({ key: "validation.not" })
850
+ });
851
+ if (fastFail) return output;
852
+ }
853
+ }
854
+ if (schema.const !== void 0) {
855
+ if (!deepEqual(value, schema.const)) {
856
+ output.errors.push({
857
+ valid: false,
858
+ keywordLocation: `${keywordLocation}/const`,
859
+ instanceLocation,
860
+ errors: [],
861
+ error: this.formatError({
862
+ key: "validation.const",
863
+ params: { value: schema.const }
864
+ })
865
+ });
866
+ if (fastFail) return output;
867
+ }
868
+ }
869
+ if (schema.enum) {
870
+ if (!schema.enum.some((v) => deepEqual(value, v))) {
871
+ output.errors.push({
872
+ valid: false,
873
+ keywordLocation: `${keywordLocation}/enum`,
874
+ instanceLocation,
875
+ errors: [],
876
+ error: this.formatError({ key: "validation.enum" })
877
+ });
878
+ if (fastFail) return output;
879
+ }
880
+ }
881
+ let instanceType = "";
882
+ if (schema.type) {
883
+ const allowedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
884
+ for (const type of allowedTypes) {
885
+ if (this.checkType(value, type)) {
886
+ instanceType = type;
887
+ break;
888
+ }
889
+ }
890
+ if (!instanceType) {
891
+ output.errors.push({
892
+ valid: false,
893
+ keywordLocation: `${keywordLocation}/type`,
894
+ instanceLocation,
895
+ errors: [],
896
+ error: this.formatError({
897
+ key: "validation.type",
898
+ params: { expected: allowedTypes.join(" or ") }
899
+ })
900
+ });
901
+ if (fastFail) return output;
902
+ }
903
+ } else {
904
+ instanceType = this.detectType(value);
905
+ }
906
+ if (instanceType === "object") {
907
+ this.validateObject(
908
+ schema,
909
+ value,
910
+ instanceLocation,
911
+ keywordLocation,
912
+ output,
913
+ options
914
+ );
915
+ } else if (instanceType === "array") {
916
+ this.validateArray(
917
+ schema,
918
+ value,
919
+ keywordLocation,
920
+ instanceLocation,
921
+ output,
922
+ options
923
+ );
924
+ } else if (instanceType === "string") {
925
+ this.validateString(
926
+ schema,
927
+ value,
928
+ keywordLocation,
929
+ instanceLocation,
930
+ output,
931
+ options
932
+ );
933
+ } else if (instanceType === "number") {
934
+ this.validateNumber(
935
+ schema,
936
+ value,
937
+ keywordLocation,
938
+ instanceLocation,
939
+ output,
940
+ options
941
+ );
942
+ }
943
+ output.valid = output.errors.length == 0;
944
+ output.error = output.valid ? void 0 : this.formatError({ key: "validation.failed" });
945
+ return output;
946
+ }
947
+ validateNumber(schema, value, keywordLocation, instanceLocation, output, options) {
948
+ const { fastFail = false } = options;
949
+ const addError = (keyword, msg) => {
950
+ output.errors.push({
951
+ valid: false,
952
+ keywordLocation: `${keywordLocation}/${keyword}`,
953
+ instanceLocation,
954
+ errors: [],
955
+ error: this.formatError(msg)
956
+ });
957
+ };
958
+ if (schema.maximum !== void 0 && value > schema.maximum) {
959
+ addError("maximum", {
960
+ key: "validation.maximum",
961
+ params: { value: schema.maximum }
962
+ });
963
+ if (fastFail) return;
964
+ }
965
+ if (schema.exclusiveMaximum !== void 0 && value >= schema.exclusiveMaximum) {
966
+ addError("exclusiveMaximum", {
967
+ key: "validation.exclusiveMaximum",
968
+ params: { value: schema.exclusiveMaximum }
969
+ });
970
+ if (fastFail) return;
971
+ }
972
+ if (schema.minimum !== void 0 && value < schema.minimum) {
973
+ addError("minimum", {
974
+ key: "validation.minimum",
975
+ params: { value: schema.minimum }
976
+ });
977
+ if (fastFail) return;
978
+ }
979
+ if (schema.exclusiveMinimum !== void 0 && value <= schema.exclusiveMinimum) {
980
+ addError("exclusiveMinimum", {
981
+ key: "validation.exclusiveMinimum",
982
+ params: { value: schema.exclusiveMinimum }
983
+ });
984
+ if (fastFail) return;
985
+ }
986
+ if (schema.multipleOf !== void 0) {
987
+ const remainder = value % schema.multipleOf;
988
+ if (Math.abs(remainder) > 1e-5 && Math.abs(remainder - schema.multipleOf) > 1e-5) {
989
+ addError("multipleOf", {
990
+ key: "validation.multipleOf",
991
+ params: { value: schema.multipleOf }
992
+ });
993
+ if (fastFail) return;
994
+ }
995
+ }
996
+ }
997
+ validateString(schema, value, keywordLocation, instanceLocation, output, options) {
998
+ const { fastFail = false } = options;
999
+ const addError = (keyword, msg) => {
1000
+ output.errors.push({
1001
+ valid: false,
1002
+ keywordLocation: `${keywordLocation}/${keyword}`,
1003
+ instanceLocation,
1004
+ errors: [],
1005
+ error: this.formatError(msg)
1006
+ });
1007
+ }, { length } = [...value];
1008
+ if (schema.maxLength !== void 0 && length > schema.maxLength) {
1009
+ addError("maxLength", {
1010
+ key: "validation.maxLength",
1011
+ params: { value: schema.maxLength }
1012
+ });
1013
+ if (fastFail) return;
1014
+ }
1015
+ if (schema.minLength !== void 0 && length < schema.minLength) {
1016
+ addError("minLength", {
1017
+ key: "validation.minLength",
1018
+ params: { value: schema.minLength }
1019
+ });
1020
+ if (fastFail) return;
1021
+ }
1022
+ if (schema.pattern !== void 0) {
1023
+ const regex = new RegExp(schema.pattern);
1024
+ if (!regex.test(value)) {
1025
+ addError("pattern", {
1026
+ key: "validation.pattern",
1027
+ params: { pattern: schema.pattern }
1028
+ });
1029
+ if (fastFail) return;
1030
+ }
1031
+ }
1032
+ if (schema.format !== void 0) {
1033
+ if (!this.validateFormat(schema.format, value)) {
1034
+ addError("format", {
1035
+ key: "validation.format",
1036
+ params: { format: schema.format }
1037
+ });
1038
+ if (fastFail) return;
1039
+ }
1040
+ }
1041
+ }
1042
+ validateArray(schema, value, keywordLocation, instanceLocation, output, options) {
1043
+ const { fastFail = false, shallow = false } = options;
1044
+ const addError = (keyword, msg) => {
1045
+ output.errors.push({
1046
+ valid: false,
1047
+ keywordLocation: `${keywordLocation}/${keyword}`,
1048
+ instanceLocation,
1049
+ errors: [],
1050
+ error: this.formatError(msg)
1051
+ });
1052
+ };
1053
+ if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
1054
+ addError("maxItems", {
1055
+ key: "validation.maxItems",
1056
+ params: { value: schema.maxItems }
1057
+ });
1058
+ if (fastFail) return;
1059
+ }
1060
+ if (schema.minItems !== void 0 && value.length < schema.minItems) {
1061
+ addError("minItems", {
1062
+ key: "validation.minItems",
1063
+ params: { value: schema.minItems }
1064
+ });
1065
+ if (fastFail) return;
1066
+ }
1067
+ if (schema.uniqueItems) {
1068
+ for (let i = 0; i < value.length; i++) {
1069
+ for (let j = i + 1; j < value.length; j++) {
1070
+ if (deepEqual(value[i], value[j])) {
1071
+ addError("uniqueItems", { key: "validation.uniqueItems" });
1072
+ return;
1073
+ }
1074
+ }
1075
+ }
1076
+ }
1077
+ let prefixItemsLength = 0;
1078
+ if (schema.prefixItems) {
1079
+ prefixItemsLength = schema.prefixItems.length;
1080
+ for (let index = 0; index < schema.prefixItems.length; index++) {
1081
+ const itemSchema = schema.prefixItems[index];
1082
+ if (index < value.length) {
1083
+ if (!shallow) {
1084
+ const result = this.validate(
1085
+ itemSchema,
1086
+ value[index],
1087
+ `${keywordLocation}/prefixItems/${index}`,
1088
+ `${instanceLocation}/${index}`,
1089
+ options
1090
+ );
1091
+ if (!result.valid) {
1092
+ output.errors.push(result);
1093
+ if (fastFail) return;
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ if (schema.items && value.length > prefixItemsLength) {
1100
+ for (let i = prefixItemsLength; i < value.length; i++) {
1101
+ if (!shallow) {
1102
+ const result = this.validate(
1103
+ schema.items,
1104
+ value[i],
1105
+ `${keywordLocation}/items`,
1106
+ `${instanceLocation}/${i}`,
1107
+ options
1108
+ );
1109
+ if (!result.valid) {
1110
+ output.errors.push(result);
1111
+ if (fastFail) return;
1112
+ }
1113
+ }
1114
+ }
1115
+ }
1116
+ if (schema.contains) {
1117
+ let containsCount = 0;
1118
+ for (let i = 0; i < value.length; i++) {
1119
+ const result = this.validate(
1120
+ schema.contains,
1121
+ value[i],
1122
+ `${keywordLocation}/contains`,
1123
+ `${instanceLocation}/${i}`,
1124
+ options
1125
+ );
1126
+ if (result.valid) {
1127
+ containsCount++;
1128
+ }
1129
+ }
1130
+ if (schema.minContains !== void 0) {
1131
+ if (containsCount < schema.minContains) {
1132
+ addError("minContains", {
1133
+ key: "validation.minContains",
1134
+ params: { value: schema.minContains }
1135
+ });
1136
+ if (fastFail) return;
1137
+ }
1138
+ } else if (schema.minContains === void 0 && containsCount === 0) {
1139
+ addError("contains", { key: "validation.contains" });
1140
+ if (fastFail) return;
1141
+ }
1142
+ if (schema.maxContains !== void 0) {
1143
+ if (containsCount > schema.maxContains) {
1144
+ addError("maxContains", {
1145
+ key: "validation.maxContains",
1146
+ params: { value: schema.maxContains }
1147
+ });
1148
+ if (fastFail) return;
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ validateObject(schema, value, keywordLocation, instanceLocation, output, options) {
1154
+ const { fastFail = false, shallow = false } = options;
1155
+ const addError = (keyword, msg) => {
1156
+ output.errors.push({
1157
+ valid: false,
1158
+ keywordLocation: `${keywordLocation}/${keyword}`,
1159
+ instanceLocation,
1160
+ errors: [],
1161
+ error: this.formatError(msg)
1162
+ });
1163
+ }, keys = Object.keys(value);
1164
+ if (schema.maxProperties !== void 0 && keys.length > schema.maxProperties) {
1165
+ addError("maxProperties", {
1166
+ key: "validation.maxProperties",
1167
+ params: { value: schema.maxProperties }
1168
+ });
1169
+ if (fastFail) return;
1170
+ }
1171
+ if (schema.minProperties !== void 0 && keys.length < schema.minProperties) {
1172
+ addError("minProperties", {
1173
+ key: "validation.minProperties",
1174
+ params: { value: schema.minProperties }
1175
+ });
1176
+ if (fastFail) return;
1177
+ }
1178
+ if (schema.required) {
1179
+ for (const req of schema.required) {
1180
+ if (!(req in value)) {
1181
+ addError("required", {
1182
+ key: "validation.required",
1183
+ params: { property: req }
1184
+ });
1185
+ if (fastFail) return;
1186
+ }
1187
+ }
1188
+ }
1189
+ if (schema.dependentRequired) {
1190
+ for (const [prop, requiredProps] of Object.entries(
1191
+ schema.dependentRequired
1192
+ )) {
1193
+ if (prop in value) {
1194
+ for (const req of requiredProps) {
1195
+ if (!(req in value)) {
1196
+ addError("dependentRequired", {
1197
+ key: "validation.dependentRequired",
1198
+ params: { source: prop, target: req }
1199
+ });
1200
+ if (fastFail) return;
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ const validatedKeys = /* @__PURE__ */ new Set();
1207
+ if (schema.properties) {
1208
+ for (const [prop, propSchema] of Object.entries(schema.properties)) {
1209
+ if (prop in value) {
1210
+ validatedKeys.add(prop);
1211
+ if (!shallow) {
1212
+ const result = this.validate(
1213
+ propSchema,
1214
+ value[prop],
1215
+ `${keywordLocation}/properties/${prop}`,
1216
+ `${instanceLocation}/${prop}`,
1217
+ options
1218
+ );
1219
+ if (!result.valid) {
1220
+ output.errors.push(result);
1221
+ if (fastFail) return;
1222
+ }
1223
+ }
1224
+ }
1225
+ }
1226
+ }
1227
+ if (schema.patternProperties) {
1228
+ for (const [pattern, propSchema] of Object.entries(
1229
+ schema.patternProperties
1230
+ )) {
1231
+ const regex = new RegExp(pattern);
1232
+ for (const key of keys) {
1233
+ if (regex.test(key)) {
1234
+ validatedKeys.add(key);
1235
+ if (!shallow) {
1236
+ const result = this.validate(
1237
+ propSchema,
1238
+ value[key],
1239
+ `${keywordLocation}/patternProperties/${jsonPointerEscape(pattern)}`,
1240
+ `${instanceLocation}/${jsonPointerEscape(key)}`,
1241
+ options
1242
+ );
1243
+ if (!result.valid) {
1244
+ output.errors.push(...result.errors || []);
1245
+ if (fastFail) return;
1246
+ }
1247
+ }
1248
+ }
1249
+ }
1250
+ }
1251
+ }
1252
+ if (schema.additionalProperties !== void 0) {
1253
+ const additionalKeys = keys.filter((k) => !validatedKeys.has(k));
1254
+ if (typeof schema.additionalProperties === "boolean") {
1255
+ if (!schema.additionalProperties && additionalKeys.length > 0) {
1256
+ addError("additionalProperties", {
1257
+ key: "validation.additionalProperties",
1258
+ params: { properties: additionalKeys.join(", ") }
1259
+ });
1260
+ if (fastFail) return;
1261
+ }
1262
+ } else {
1263
+ for (const key of additionalKeys) {
1264
+ if (!shallow) {
1265
+ const result = this.validate(
1266
+ schema.additionalProperties,
1267
+ value[key],
1268
+ `${keywordLocation}/additionalProperties`,
1269
+ `${instanceLocation}/${jsonPointerEscape(key)}`,
1270
+ options
1271
+ );
1272
+ if (!result.valid) {
1273
+ output.errors.push(result);
1274
+ if (fastFail) return;
1275
+ }
1276
+ }
1277
+ }
1278
+ }
1279
+ }
1280
+ if (schema.propertyNames) {
1281
+ for (const key of keys) {
1282
+ const result = this.validate(
1283
+ schema.propertyNames,
1284
+ key,
1285
+ `${keywordLocation}/propertyNames`,
1286
+ `${instanceLocation}/${jsonPointerEscape(key)}`,
1287
+ options
1288
+ );
1289
+ if (!result.valid) {
1290
+ output.errors.push(result);
1291
+ if (fastFail) return;
1292
+ }
1293
+ }
1294
+ }
1295
+ if (schema.dependentSchemas) {
1296
+ for (const [prop, depSchema] of Object.entries(schema.dependentSchemas)) {
1297
+ if (prop in value) {
1298
+ const result = this.validate(
1299
+ depSchema,
1300
+ value,
1301
+ `${keywordLocation}/dependentSchemas/${jsonPointerEscape(prop)}`,
1302
+ instanceLocation,
1303
+ options
1304
+ );
1305
+ if (!result.valid) {
1306
+ output.errors.push(result);
1307
+ if (fastFail) return;
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ detectType(value) {
1314
+ if (value === null || value === void 0) return "null";
1315
+ if (Array.isArray(value)) return "array";
1316
+ if (Number.isInteger(value)) return "integer";
1317
+ return typeof value;
1318
+ }
1319
+ checkType(value, type) {
1320
+ return matchSchemaType(value, type);
1321
+ }
1322
+ validateFormat(format, value) {
1323
+ return this.formatValidator.validate(format, value);
1324
+ }
1325
+ };
1326
+ function validateSchema(schema, value, instancePath = "", schemaPath = "#", fastFail = false) {
1327
+ const normalizedSchema = normalizeSchema(schema);
1328
+ const validator = new Validator();
1329
+ return validator.validate(normalizedSchema, value, schemaPath, instancePath, {
1330
+ fastFail
1331
+ });
1332
+ }
1333
+
1334
+ // src/schema-util.ts
1335
+ var schemaUtilLogger = {
1336
+ warn: (message) => console.warn(message)
1337
+ };
1338
+ function resolveRef(ref, rootSchema, visited = /* @__PURE__ */ new Set()) {
1339
+ if (!ref.startsWith("#")) {
1340
+ schemaUtilLogger?.warn(`External $ref not supported: ${ref}`);
1341
+ return void 0;
1342
+ }
1343
+ if (visited.has(ref)) {
1344
+ schemaUtilLogger?.warn(`Circular $ref detected: ${ref}`);
1345
+ return void 0;
1346
+ }
1347
+ visited.add(ref);
1348
+ const pointer = ref.slice(1);
1349
+ if (pointer === "" || pointer === "/") {
1350
+ return rootSchema;
1351
+ }
1352
+ const segments = parseJsonPointer(pointer);
1353
+ let current = rootSchema;
1354
+ for (const segment of segments) {
1355
+ if (current === null || current === void 0) {
1356
+ return void 0;
1357
+ }
1358
+ if (typeof current !== "object") {
1359
+ return void 0;
1360
+ }
1361
+ current = current[segment];
1362
+ }
1363
+ if (current === null || current === void 0) {
1364
+ return void 0;
1365
+ }
1366
+ const resolved = current;
1367
+ if (resolved.$ref) {
1368
+ return resolveRef(resolved.$ref, rootSchema, visited);
1369
+ }
1370
+ return resolved;
1371
+ }
1372
+ function dereferenceSchema(schema, rootSchema) {
1373
+ if (!schema.$ref) {
1374
+ return schema;
1375
+ }
1376
+ const resolved = resolveRef(schema.$ref, rootSchema);
1377
+ if (!resolved) {
1378
+ const { $ref: _2, ...rest } = schema;
1379
+ return rest;
1380
+ }
1381
+ const { $ref: _, ...siblings } = schema;
1382
+ return mergeSchema(resolved, siblings);
1383
+ }
1384
+ function dereferenceSchemaDeep(schema, rootSchema, processed = /* @__PURE__ */ new WeakMap(), inProgress = /* @__PURE__ */ new WeakSet()) {
1385
+ const cached = processed.get(schema);
1386
+ if (cached !== void 0) {
1387
+ return cached;
1388
+ }
1389
+ if (inProgress.has(schema)) {
1390
+ return schema;
1391
+ }
1392
+ inProgress.add(schema);
1393
+ let result = dereferenceSchema(schema, rootSchema);
1394
+ if (result === schema) {
1395
+ result = { ...schema };
1396
+ }
1397
+ if (result.properties) {
1398
+ result.properties = Object.fromEntries(
1399
+ Object.entries(result.properties).map(([key, subSchema]) => [
1400
+ key,
1401
+ dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
1402
+ ])
1403
+ );
1404
+ }
1405
+ if (result.patternProperties) {
1406
+ result.patternProperties = Object.fromEntries(
1407
+ Object.entries(result.patternProperties).map(([key, subSchema]) => [
1408
+ key,
1409
+ dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
1410
+ ])
1411
+ );
1412
+ }
1413
+ if (result.additionalProperties && typeof result.additionalProperties === "object") {
1414
+ result.additionalProperties = dereferenceSchemaDeep(
1415
+ result.additionalProperties,
1416
+ rootSchema,
1417
+ processed,
1418
+ inProgress
1419
+ );
1420
+ }
1421
+ if (result.items) {
1422
+ result.items = dereferenceSchemaDeep(
1423
+ result.items,
1424
+ rootSchema,
1425
+ processed,
1426
+ inProgress
1427
+ );
1428
+ }
1429
+ if (result.prefixItems) {
1430
+ result.prefixItems = result.prefixItems.map(
1431
+ (s) => dereferenceSchemaDeep(s, rootSchema, processed, inProgress)
1432
+ );
1433
+ }
1434
+ if (result.contains) {
1435
+ result.contains = dereferenceSchemaDeep(
1436
+ result.contains,
1437
+ rootSchema,
1438
+ processed,
1439
+ inProgress
1440
+ );
1441
+ }
1442
+ for (const keyword of ["allOf", "anyOf", "oneOf"]) {
1443
+ const subSchemas = result[keyword];
1444
+ if (subSchemas) {
1445
+ result[keyword] = subSchemas.map(
1446
+ (s) => dereferenceSchemaDeep(s, rootSchema, processed, inProgress)
1447
+ );
1448
+ }
1449
+ }
1450
+ if (result.not) {
1451
+ result.not = dereferenceSchemaDeep(
1452
+ result.not,
1453
+ rootSchema,
1454
+ processed,
1455
+ inProgress
1456
+ );
1457
+ }
1458
+ if (result.if) {
1459
+ result.if = dereferenceSchemaDeep(
1460
+ result.if,
1461
+ rootSchema,
1462
+ processed,
1463
+ inProgress
1464
+ );
1465
+ }
1466
+ if (result.then) {
1467
+ result.then = dereferenceSchemaDeep(
1468
+ result.then,
1469
+ rootSchema,
1470
+ processed,
1471
+ inProgress
1472
+ );
1473
+ }
1474
+ if (result.else) {
1475
+ result.else = dereferenceSchemaDeep(
1476
+ result.else,
1477
+ rootSchema,
1478
+ processed,
1479
+ inProgress
1480
+ );
1481
+ }
1482
+ if (result.dependentSchemas) {
1483
+ result.dependentSchemas = Object.fromEntries(
1484
+ Object.entries(result.dependentSchemas).map(([key, subSchema]) => [
1485
+ key,
1486
+ dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
1487
+ ])
1488
+ );
1489
+ }
1490
+ if (result.$defs) {
1491
+ result.$defs = Object.fromEntries(
1492
+ Object.entries(result.$defs).map(([key, subSchema]) => [
1493
+ key,
1494
+ dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
1495
+ ])
1496
+ );
1497
+ }
1498
+ processed.set(schema, result);
1499
+ return result;
1500
+ }
1501
+ var MAX_EXTRACT_DEPTH = 100;
1502
+ function extractReferencedPaths(conditionSchema, basePath = "", depth = 0) {
1503
+ if (depth > MAX_EXTRACT_DEPTH) {
1504
+ console.warn(
1505
+ `extractReferencedPaths: max depth (${MAX_EXTRACT_DEPTH}) exceeded at path: ${basePath}`
1506
+ );
1507
+ return [];
1508
+ }
1509
+ const paths = [];
1510
+ const schema = conditionSchema;
1511
+ if (schema.properties) {
1512
+ for (const key of Object.keys(schema.properties)) {
1513
+ const childPath = basePath ? `${basePath}/${key}` : `/${key}`;
1514
+ paths.push(childPath);
1515
+ paths.push(
1516
+ ...extractReferencedPaths(schema.properties[key], childPath, depth + 1)
1517
+ );
1518
+ }
1519
+ }
1520
+ if (schema.items && typeof schema.items === "object") {
1521
+ paths.push(basePath || "/");
1522
+ paths.push(...extractReferencedPaths(schema.items, basePath, depth + 1));
1523
+ }
1524
+ if (schema.prefixItems) {
1525
+ schema.prefixItems.forEach((itemSchema, index) => {
1526
+ const indexPath = basePath ? `${basePath}/${index}` : `/${index}`;
1527
+ paths.push(indexPath);
1528
+ paths.push(...extractReferencedPaths(itemSchema, indexPath, depth + 1));
1529
+ });
1530
+ }
1531
+ if (schema.const !== void 0 || schema.enum) {
1532
+ if (basePath) {
1533
+ paths.push(basePath);
1534
+ }
1535
+ }
1536
+ if (schema.type && basePath) {
1537
+ paths.push(basePath);
1538
+ }
1539
+ const valueConstraints = [
1540
+ "minimum",
1541
+ "maximum",
1542
+ "exclusiveMinimum",
1543
+ "exclusiveMaximum",
1544
+ "minLength",
1545
+ "maxLength",
1546
+ "pattern",
1547
+ "format",
1548
+ "minItems",
1549
+ "maxItems",
1550
+ "uniqueItems",
1551
+ "minProperties",
1552
+ "maxProperties"
1553
+ ];
1554
+ for (const constraint of valueConstraints) {
1555
+ if (schema[constraint] !== void 0 && basePath) {
1556
+ paths.push(basePath);
1557
+ break;
1558
+ }
1559
+ }
1560
+ if (schema.required) {
1561
+ for (const req of schema.required) {
1562
+ paths.push(basePath ? `${basePath}/${req}` : `/${req}`);
1563
+ }
1564
+ }
1565
+ if (schema.dependentRequired) {
1566
+ for (const [prop, reqs] of Object.entries(schema.dependentRequired)) {
1567
+ paths.push(basePath ? `${basePath}/${prop}` : `/${prop}`);
1568
+ for (const req of reqs) {
1569
+ paths.push(basePath ? `${basePath}/${req}` : `/${req}`);
1570
+ }
1571
+ }
1572
+ }
1573
+ if (schema.dependentSchemas) {
1574
+ for (const [prop, subSchema] of Object.entries(schema.dependentSchemas)) {
1575
+ paths.push(basePath ? `${basePath}/${prop}` : `/${prop}`);
1576
+ paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
1577
+ }
1578
+ }
1579
+ if (schema.if) {
1580
+ paths.push(...extractReferencedPaths(schema.if, basePath, depth + 1));
1581
+ }
1582
+ if (schema.then) {
1583
+ paths.push(...extractReferencedPaths(schema.then, basePath, depth + 1));
1584
+ }
1585
+ if (schema.else) {
1586
+ paths.push(...extractReferencedPaths(schema.else, basePath, depth + 1));
1587
+ }
1588
+ for (const keyword of ["allOf", "anyOf", "oneOf"]) {
1589
+ const subSchemas = schema[keyword];
1590
+ if (subSchemas) {
1591
+ for (const subSchema of subSchemas) {
1592
+ paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
1593
+ }
1594
+ }
1595
+ }
1596
+ if (schema.dependentSchemas) {
1597
+ for (const [key, subSchema] of Object.entries(schema.dependentSchemas)) {
1598
+ const keyPath = basePath ? `${basePath}/${key}` : `/${key}`;
1599
+ paths.push(keyPath);
1600
+ paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
1601
+ }
1602
+ }
1603
+ if (schema.contains) {
1604
+ paths.push(basePath || "/");
1605
+ paths.push(...extractReferencedPaths(schema.contains, basePath, depth + 1));
1606
+ }
1607
+ return [...new Set(paths)];
1608
+ }
1609
+ function resolveEffectiveSchema(validator, schema, value, keywordLocation, instanceLocation) {
1610
+ let effective = schema;
1611
+ if (effective.if) {
1612
+ const output = validator.validate(
1613
+ effective.if,
1614
+ value,
1615
+ `${keywordLocation}/if`,
1616
+ instanceLocation
1617
+ );
1618
+ if (output.valid) {
1619
+ if (effective.then) {
1620
+ const res = resolveEffectiveSchema(
1621
+ validator,
1622
+ effective.then,
1623
+ value,
1624
+ `${keywordLocation}/then`,
1625
+ instanceLocation
1626
+ );
1627
+ effective = mergeSchema(effective, res.effectiveSchema);
1628
+ }
1629
+ } else {
1630
+ if (effective.else) {
1631
+ const res = resolveEffectiveSchema(
1632
+ validator,
1633
+ effective.else,
1634
+ value,
1635
+ `${keywordLocation}/else`,
1636
+ instanceLocation
1637
+ );
1638
+ effective = mergeSchema(effective, res.effectiveSchema);
1639
+ }
1640
+ }
1641
+ const { if: _, then: __, else: ___, ...rest } = effective;
1642
+ effective = rest;
1643
+ }
1644
+ if (effective.allOf) {
1645
+ for (const [index, subschema] of effective.allOf.entries()) {
1646
+ const res = resolveEffectiveSchema(
1647
+ validator,
1648
+ subschema,
1649
+ value,
1650
+ `${keywordLocation}/allOf/${index}`,
1651
+ instanceLocation
1652
+ );
1653
+ effective = mergeSchema(effective, res.effectiveSchema);
1654
+ }
1655
+ const { allOf: _, ...rest } = effective;
1656
+ effective = rest;
1657
+ }
1658
+ if (effective.anyOf) {
1659
+ for (const [index, subschema] of effective.anyOf.entries()) {
1660
+ const output = validator.validate(
1661
+ subschema,
1662
+ value,
1663
+ keywordLocation + `/anyOf/` + index,
1664
+ instanceLocation
1665
+ );
1666
+ if (output.valid) {
1667
+ const res = resolveEffectiveSchema(
1668
+ validator,
1669
+ subschema,
1670
+ value,
1671
+ keywordLocation + `/anyOf/` + index,
1672
+ instanceLocation
1673
+ );
1674
+ effective = mergeSchema(effective, res.effectiveSchema);
1675
+ break;
1676
+ }
1677
+ }
1678
+ const { anyOf: _, ...rest } = effective;
1679
+ effective = rest;
1680
+ }
1681
+ if (effective.oneOf) {
1682
+ let validCount = 0;
1683
+ let lastValidSchema = null;
1684
+ for (const [index, subschema] of effective.oneOf.entries()) {
1685
+ const output = validator.validate(
1686
+ subschema,
1687
+ value,
1688
+ `${keywordLocation}/oneOf/${index}`,
1689
+ instanceLocation
1690
+ );
1691
+ if (output.valid) {
1692
+ validCount++;
1693
+ lastValidSchema = subschema;
1694
+ }
1695
+ }
1696
+ if (validCount === 1 && lastValidSchema) {
1697
+ effective = mergeSchema(effective, lastValidSchema);
1698
+ }
1699
+ const { oneOf: _, ...rest } = effective;
1700
+ effective = rest;
1701
+ }
1702
+ let type = "unknown";
1703
+ if (effective.type) {
1704
+ const allowedTypes = Array.isArray(effective.type) ? effective.type : [effective.type];
1705
+ const matched = allowedTypes.find((t) => matchSchemaType(value, t));
1706
+ if (matched) {
1707
+ type = matched;
1708
+ } else {
1709
+ type = allowedTypes[0];
1710
+ }
1711
+ } else {
1712
+ type = detectSchemaType(value);
1713
+ }
1714
+ const validationOutput = validator.validate(
1715
+ effective,
1716
+ value,
1717
+ keywordLocation,
1718
+ instanceLocation,
1719
+ { shallow: true }
1720
+ );
1721
+ return {
1722
+ effectiveSchema: effective,
1723
+ type,
1724
+ error: validationOutput.valid ? void 0 : validationOutput
1725
+ };
1726
+ }
1727
+ function mergeStrings(a, b) {
1728
+ if (a === void 0) return b;
1729
+ if (b === void 0) return a;
1730
+ const merged = Array.from(/* @__PURE__ */ new Set([...a, ...b]));
1731
+ return merged.length === 0 ? void 0 : merged;
1732
+ }
1733
+ function mergeType(a, b) {
1734
+ if (a === void 0) return b;
1735
+ if (b === void 0) return a;
1736
+ const arrayA = Array.isArray(a) ? a : [a];
1737
+ const arrayB = Array.isArray(b) ? b : [b];
1738
+ const merged = arrayA.filter((t) => arrayB.includes(t));
1739
+ if (merged.length === 0) return void 0;
1740
+ return merged.length === 1 ? merged[0] : merged;
1741
+ }
1742
+ function mergeSchemaArrays(a, b) {
1743
+ if (a === void 0) return b;
1744
+ if (b === void 0) return a;
1745
+ return [...a, ...b];
1746
+ }
1747
+ function mergeSchema(base, override) {
1748
+ if (!override) return base;
1749
+ const merged = {
1750
+ ...base,
1751
+ ...override
1752
+ };
1753
+ if (base.$defs || override.$defs) {
1754
+ merged.$defs = {
1755
+ ...base.$defs,
1756
+ ...override.$defs
1757
+ };
1758
+ }
1759
+ const mergedRequired = mergeStrings(base.required, override.required);
1760
+ if (mergedRequired !== void 0) {
1761
+ merged.required = mergedRequired;
1762
+ }
1763
+ const mergedType = mergeType(base.type, override.type);
1764
+ if (mergedType !== void 0) {
1765
+ merged.type = mergedType;
1766
+ }
1767
+ if (base.dependentRequired || override.dependentRequired) {
1768
+ merged.dependentRequired = {
1769
+ ...base.dependentRequired,
1770
+ ...override.dependentRequired
1771
+ };
1772
+ }
1773
+ if (base.properties || override.properties) {
1774
+ merged.properties = {
1775
+ ...base.properties,
1776
+ ...override.properties
1777
+ };
1778
+ }
1779
+ if (base.patternProperties || override.patternProperties) {
1780
+ merged.patternProperties = {
1781
+ ...base.patternProperties,
1782
+ ...override.patternProperties
1783
+ };
1784
+ }
1785
+ if (base.items && override.items) {
1786
+ merged.items = mergeSchema(base.items, override.items);
1787
+ } else if (base.items) {
1788
+ merged.items = base.items;
1789
+ } else if (override.items) {
1790
+ merged.items = override.items;
1791
+ }
1792
+ if (base.prefixItems || override.prefixItems) {
1793
+ merged.prefixItems = [];
1794
+ const len = Math.max(
1795
+ base.prefixItems?.length || 0,
1796
+ override.prefixItems?.length || 0
1797
+ );
1798
+ for (let i = 0; i < len; i++) {
1799
+ const baseSchema = base.prefixItems?.[i];
1800
+ const overrideSchema = override.prefixItems?.[i];
1801
+ if (baseSchema && overrideSchema) {
1802
+ merged.prefixItems.push(mergeSchema(baseSchema, overrideSchema));
1803
+ } else {
1804
+ const schema = baseSchema || overrideSchema;
1805
+ if (schema) {
1806
+ merged.prefixItems.push(schema);
1807
+ }
1808
+ }
1809
+ }
1810
+ }
1811
+ const combinatorKeywords = ["allOf", "anyOf", "oneOf"];
1812
+ for (const keyword of combinatorKeywords) {
1813
+ const mergedArray = mergeSchemaArrays(base[keyword], override[keyword]);
1814
+ if (mergedArray !== void 0) {
1815
+ merged[keyword] = mergedArray;
1816
+ }
1817
+ }
1818
+ if (base.dependentSchemas || override.dependentSchemas) {
1819
+ merged.dependentSchemas = {
1820
+ ...base.dependentSchemas,
1821
+ ...override.dependentSchemas
1822
+ };
1823
+ }
1824
+ return merged;
1825
+ }
1826
+ function getSubSchema(schema, key) {
1827
+ if (schema.properties && schema.properties[key]) {
1828
+ return {
1829
+ schema: schema.properties[key],
1830
+ keywordLocationToken: `properties/${key}`
1831
+ };
1832
+ }
1833
+ if (schema.patternProperties) {
1834
+ for (const [pattern, subschema] of Object.entries(
1835
+ schema.patternProperties
1836
+ )) {
1837
+ if (safeRegexTest(pattern, key)) {
1838
+ return {
1839
+ schema: subschema,
1840
+ keywordLocationToken: `patternProperties/${pattern}`
1841
+ };
1842
+ }
1843
+ }
1844
+ }
1845
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
1846
+ return {
1847
+ schema: typeof schema.additionalProperties === "object" ? schema.additionalProperties : {},
1848
+ keywordLocationToken: "additionalProperties"
1849
+ };
1850
+ }
1851
+ if (schema.items || schema.prefixItems) {
1852
+ const index = parseInt(key, 10);
1853
+ if (!isNaN(index)) {
1854
+ if (schema.prefixItems && index < schema.prefixItems.length) {
1855
+ return {
1856
+ schema: schema.prefixItems[index],
1857
+ keywordLocationToken: `prefixItems/${index}`
1858
+ };
1859
+ }
1860
+ if (schema.items) {
1861
+ return {
1862
+ schema: schema.items,
1863
+ keywordLocationToken: "items"
1864
+ };
1865
+ }
1866
+ }
1867
+ }
1868
+ return {
1869
+ schema: {},
1870
+ keywordLocationToken: ""
1871
+ };
1872
+ }
1873
+ function getDefaultValue(schema) {
1874
+ if (schema.const !== void 0) return schema.const;
1875
+ if (schema.default !== void 0) return schema.default;
1876
+ const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1877
+ switch (type) {
1878
+ case "string":
1879
+ return "";
1880
+ case "number":
1881
+ case "integer":
1882
+ return 0;
1883
+ case "boolean":
1884
+ return false;
1885
+ case "null":
1886
+ return null;
1887
+ case "object": {
1888
+ const obj = {};
1889
+ if (schema.properties) {
1890
+ for (const [key, subschema] of Object.entries(schema.properties)) {
1891
+ if (schema.required?.includes(key)) {
1892
+ obj[key] = getDefaultValue(subschema);
1893
+ }
1894
+ }
1895
+ }
1896
+ return obj;
1897
+ }
1898
+ case "array":
1899
+ return [];
1900
+ default:
1901
+ if (schema.properties) {
1902
+ return getDefaultValue({ ...schema, type: "object" });
1903
+ }
1904
+ return void 0;
1905
+ }
1906
+ }
1907
+
1908
+ // src/render.ts
1909
+ var ROOT_PATH = "";
1910
+ function normalizeRootPath(path) {
1911
+ return path === "#" ? ROOT_PATH : path;
1912
+ }
1913
+ var SchemaRuntime = class {
1914
+ validator;
1915
+ watchers = {};
1916
+ globalWatchers = /* @__PURE__ */ new Set();
1917
+ // Reverse dependency index: path -> nodes that depend on this path's value
1918
+ dependentsMap = /* @__PURE__ */ new Map();
1919
+ // Track nodes currently being updated to prevent circular updates
1920
+ updatingNodes = /* @__PURE__ */ new Set();
1921
+ root;
1922
+ value;
1923
+ version = 0;
1924
+ rootSchema = {};
1925
+ /**
1926
+ * Create a new SchemaRuntime instance.
1927
+ *
1928
+ * @param validator - The validator instance for schema validation
1929
+ * @param schema - The JSON Schema definition (will be normalized and dereferenced)
1930
+ * @param value - The initial data value to manage
1931
+ *
1932
+ * @example
1933
+ * const validator = new Validator();
1934
+ * const schema = { type: "object", properties: { name: { type: "string" } } };
1935
+ * const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
1936
+ */
1937
+ constructor(validator, schema, value) {
1938
+ const normalized = normalizeSchema(schema);
1939
+ this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
1940
+ this.validator = validator;
1941
+ this.value = value;
1942
+ this.root = this.createEmptyNode("", "#");
1943
+ this.buildNode(this.root, this.rootSchema);
1944
+ }
1945
+ /**
1946
+ * Collect all dependencies for a node's schema.
1947
+ *
1948
+ * Key insight: We extract paths from condition keywords (if, oneOf, anyOf)
1949
+ * using extractReferencedPaths, but for then/else/allOf keywords, we recursively
1950
+ * call collectDependencies to ensure proper dependency isolation between
1951
+ * parent and child nodes.
1952
+ */
1953
+ collectDependencies(schema, instanceLocation) {
1954
+ const deps = /* @__PURE__ */ new Set();
1955
+ if (schema.required) {
1956
+ for (const req of schema.required) {
1957
+ deps.add(resolveAbsolutePath(instanceLocation, `/${req}`));
1958
+ }
1959
+ }
1960
+ if (schema.dependentRequired) {
1961
+ for (const [prop, reqs] of Object.entries(schema.dependentRequired)) {
1962
+ deps.add(resolveAbsolutePath(instanceLocation, `/${prop}`));
1963
+ for (const req of reqs) {
1964
+ deps.add(resolveAbsolutePath(instanceLocation, `/${req}`));
1965
+ }
1966
+ }
1967
+ }
1968
+ if (schema.dependentSchemas) {
1969
+ for (const [prop, subSchema] of Object.entries(schema.dependentSchemas)) {
1970
+ deps.add(resolveAbsolutePath(instanceLocation, `/${prop}`));
1971
+ const subDeps = this.collectDependencies(subSchema, instanceLocation);
1972
+ subDeps.forEach((d) => deps.add(d));
1973
+ }
1974
+ }
1975
+ if (schema.if) {
1976
+ const relativePaths = extractReferencedPaths(schema.if, "");
1977
+ for (const relPath of relativePaths) {
1978
+ deps.add(resolveAbsolutePath(instanceLocation, relPath));
1979
+ }
1980
+ if (schema.then) {
1981
+ const thenDeps = this.collectDependencies(
1982
+ schema.then,
1983
+ instanceLocation
1984
+ );
1985
+ thenDeps.forEach((d) => deps.add(d));
1986
+ }
1987
+ if (schema.else) {
1988
+ const elseDeps = this.collectDependencies(
1989
+ schema.else,
1990
+ instanceLocation
1991
+ );
1992
+ elseDeps.forEach((d) => deps.add(d));
1993
+ }
1994
+ }
1995
+ if (schema.oneOf) {
1996
+ for (const subSchema of schema.oneOf) {
1997
+ const relativePaths = extractReferencedPaths(subSchema, "");
1998
+ for (const relPath of relativePaths) {
1999
+ deps.add(resolveAbsolutePath(instanceLocation, relPath));
2000
+ }
2001
+ const subDeps = this.collectDependencies(subSchema, instanceLocation);
2002
+ subDeps.forEach((d) => deps.add(d));
2003
+ }
2004
+ }
2005
+ if (schema.anyOf) {
2006
+ for (const subSchema of schema.anyOf) {
2007
+ const relativePaths = extractReferencedPaths(subSchema, "");
2008
+ for (const relPath of relativePaths) {
2009
+ deps.add(resolveAbsolutePath(instanceLocation, relPath));
2010
+ }
2011
+ const subDeps = this.collectDependencies(subSchema, instanceLocation);
2012
+ subDeps.forEach((d) => deps.add(d));
2013
+ }
2014
+ }
2015
+ if (schema.allOf) {
2016
+ for (const subSchema of schema.allOf) {
2017
+ const subDeps = this.collectDependencies(subSchema, instanceLocation);
2018
+ subDeps.forEach((d) => deps.add(d));
2019
+ }
2020
+ }
2021
+ return deps;
2022
+ }
2023
+ /**
2024
+ * Register a node as dependent on a path
2025
+ */
2026
+ registerDependent(path, node) {
2027
+ let dependents = this.dependentsMap.get(path);
2028
+ if (!dependents) {
2029
+ dependents = /* @__PURE__ */ new Set();
2030
+ this.dependentsMap.set(path, dependents);
2031
+ }
2032
+ dependents.add(node);
2033
+ }
2034
+ /**
2035
+ * Unregister a node from a dependency path
2036
+ */
2037
+ unregisterDependent(path, node) {
2038
+ const dependents = this.dependentsMap.get(path);
2039
+ if (dependents) {
2040
+ dependents.delete(node);
2041
+ if (dependents.size === 0) {
2042
+ this.dependentsMap.delete(path);
2043
+ }
2044
+ }
2045
+ }
2046
+ /**
2047
+ * Unregister all dependencies for a node and its children.
2048
+ * Note: Does NOT clean up external watchers - callers are responsible
2049
+ * for calling unsubscribe() when they no longer need updates.
2050
+ */
2051
+ unregisterNodeDependencies(node) {
2052
+ if (node.dependencies) {
2053
+ for (const depPath of node.dependencies) {
2054
+ this.unregisterDependent(depPath, node);
2055
+ }
2056
+ }
2057
+ if (node.children) {
2058
+ for (const child of node.children) {
2059
+ this.unregisterNodeDependencies(child);
2060
+ }
2061
+ }
2062
+ }
2063
+ /**
2064
+ * Create an empty FieldNode with default values.
2065
+ */
2066
+ createEmptyNode(instanceLocation, keywordLocation) {
2067
+ return {
2068
+ type: "null",
2069
+ schema: {},
2070
+ version: -1,
2071
+ instanceLocation,
2072
+ keywordLocation,
2073
+ originalSchema: {},
2074
+ canRemove: false,
2075
+ canAdd: false,
2076
+ children: []
2077
+ };
2078
+ }
2079
+ /**
2080
+ * Reconcile the tree starting from the specified path.
2081
+ * Uses findNearestExistingNode to find the target node (or parent if path doesn't exist).
2082
+ * Only rebuilds the affected subtree, not the entire tree.
2083
+ */
2084
+ reconcile(path) {
2085
+ const normalizedPath = normalizeRootPath(path);
2086
+ const targetNode = this.findNearestExistingNode(normalizedPath);
2087
+ if (!targetNode) {
2088
+ return;
2089
+ }
2090
+ this.buildNode(targetNode, targetNode.originalSchema);
2091
+ }
2092
+ /**
2093
+ * Get the current version number.
2094
+ * The version increments on every notify call (value, schema, or error changes).
2095
+ * Useful for detecting if the runtime state has changed.
2096
+ *
2097
+ * @returns The current version number
2098
+ */
2099
+ getVersion() {
2100
+ return this.version;
2101
+ }
2102
+ /**
2103
+ * Subscribe to changes at a specific path.
2104
+ * The callback is invoked when the value, schema, or error at the path changes.
2105
+ *
2106
+ * @param path - The JSON Pointer path to watch (e.g., "/user/name", "" for root)
2107
+ * @param cb - Callback function invoked with the change event
2108
+ * @returns Unsubscribe function to remove the listener
2109
+ *
2110
+ * @example
2111
+ * const unsubscribe = runtime.subscribe("/name", (event) => {
2112
+ * console.log(`${event.type} changed at ${event.path}`);
2113
+ * });
2114
+ * // Later: unsubscribe();
2115
+ */
2116
+ subscribe(path, cb) {
2117
+ const normalizedPath = normalizeRootPath(path);
2118
+ if (!this.watchers[normalizedPath]) {
2119
+ this.watchers[normalizedPath] = /* @__PURE__ */ new Set();
2120
+ }
2121
+ this.watchers[normalizedPath].add(cb);
2122
+ return () => {
2123
+ const watcherSet = this.watchers[normalizedPath];
2124
+ if (watcherSet) {
2125
+ watcherSet.delete(cb);
2126
+ if (watcherSet.size === 0) {
2127
+ delete this.watchers[normalizedPath];
2128
+ }
2129
+ }
2130
+ };
2131
+ }
2132
+ /**
2133
+ * Subscribe to all events in the runtime.
2134
+ * The callback is invoked for any change at any path.
2135
+ *
2136
+ * @param cb - Callback function invoked with every change event
2137
+ * @returns Unsubscribe function to remove the listener
2138
+ */
2139
+ subscribeAll(cb) {
2140
+ this.globalWatchers.add(cb);
2141
+ return () => {
2142
+ this.globalWatchers.delete(cb);
2143
+ };
2144
+ }
2145
+ /**
2146
+ * Emit a change event to all relevant subscribers.
2147
+ * Increments the version number and notifies both path-specific and global watchers.
2148
+ *
2149
+ * @param event - The change event containing type and path
2150
+ */
2151
+ notify(event) {
2152
+ this.version++;
2153
+ const normalizedPath = normalizeRootPath(event.path);
2154
+ const watchers = this.watchers[normalizedPath];
2155
+ if (watchers) {
2156
+ for (const cb of watchers) {
2157
+ try {
2158
+ cb(event);
2159
+ } catch (err) {
2160
+ console.error("SchemaRuntime: watcher callback error:", err);
2161
+ }
2162
+ }
2163
+ }
2164
+ for (const cb of this.globalWatchers) {
2165
+ try {
2166
+ cb(event);
2167
+ } catch (err) {
2168
+ console.error("SchemaRuntime: global watcher callback error:", err);
2169
+ }
2170
+ }
2171
+ }
2172
+ /**
2173
+ * Update the entire schema.
2174
+ * This triggers a full rebuild of the node tree while preserving the current value.
2175
+ */
2176
+ setSchema(schema) {
2177
+ const normalized = normalizeSchema(schema);
2178
+ this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
2179
+ this.root = this.createEmptyNode("", "#");
2180
+ this.buildNode(this.root, this.rootSchema);
2181
+ this.notify({ type: "schema", path: ROOT_PATH });
2182
+ }
2183
+ /**
2184
+ * Get the value at a specific path.
2185
+ *
2186
+ * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
2187
+ * @returns The value at the path, or undefined if not found
2188
+ *
2189
+ * @example
2190
+ * runtime.getValue(""); // returns entire root value
2191
+ * runtime.getValue("/name"); // returns value at /name
2192
+ */
2193
+ getValue(path) {
2194
+ const normalizedPath = normalizeRootPath(path);
2195
+ if (normalizedPath === ROOT_PATH) return this.value;
2196
+ return getJsonPointer(this.value, normalizedPath);
2197
+ }
2198
+ /**
2199
+ * Remove a node at the specified path.
2200
+ * This deletes the value from the data structure (array splice or object delete).
2201
+ * @param path - The path to remove
2202
+ * @returns true if successful, false if the path cannot be removed
2203
+ */
2204
+ removeValue(path) {
2205
+ const normalizedPath = normalizeRootPath(path);
2206
+ if (normalizedPath === ROOT_PATH) {
2207
+ return false;
2208
+ }
2209
+ const node = this.findNode(normalizedPath);
2210
+ if (!node || !node.canRemove) {
2211
+ return false;
2212
+ }
2213
+ const success = removeJsonPointer(this.value, normalizedPath);
2214
+ if (!success) {
2215
+ return false;
2216
+ }
2217
+ const lastSlash = normalizedPath.lastIndexOf("/");
2218
+ const parentPath = lastSlash <= 0 ? ROOT_PATH : normalizedPath.substring(0, lastSlash);
2219
+ this.reconcile(parentPath);
2220
+ this.notify({ type: "value", path: parentPath });
2221
+ return true;
2222
+ }
2223
+ /**
2224
+ * Add a new child to an array or object at the specified parent path.
2225
+ * For arrays, appends a new item with default value based on items schema.
2226
+ * For objects, adds a new property with the given key and default value based on additionalProperties schema.
2227
+ * @param parentPath - The path to the parent array or object
2228
+ * @param key - For objects: the property key. For arrays: optional, ignored (appends to end)
2229
+ * @param initialValue - Optional initial value to set. If not provided, uses default from schema.
2230
+ * @returns true if successful, false if cannot add
2231
+ */
2232
+ addValue(parentPath, key, initialValue) {
2233
+ const normalizedPath = normalizeRootPath(parentPath);
2234
+ const parentNode = this.findNode(normalizedPath);
2235
+ if (!parentNode || !parentNode.canAdd) {
2236
+ return false;
2237
+ }
2238
+ const parentValue = this.getValue(normalizedPath);
2239
+ const parentSchema = parentNode.schema;
2240
+ if (parentNode.type === "array" && Array.isArray(parentValue)) {
2241
+ const newIndex = parentValue.length;
2242
+ const { schema: subschema } = getSubSchema(
2243
+ parentSchema,
2244
+ String(newIndex)
2245
+ );
2246
+ const defaultValue = initialValue !== void 0 ? initialValue : getDefaultValue(subschema);
2247
+ const itemPath = jsonPointerJoin(normalizedPath, String(newIndex));
2248
+ return this.setValue(itemPath, defaultValue);
2249
+ } else if (parentNode.type === "object" && parentValue && typeof parentValue === "object") {
2250
+ if (!key) {
2251
+ return false;
2252
+ }
2253
+ const { schema: subschema } = getSubSchema(parentSchema, key);
2254
+ if (!parentSchema.additionalProperties) {
2255
+ return false;
2256
+ }
2257
+ const defaultValue = initialValue !== void 0 ? initialValue : getDefaultValue(subschema);
2258
+ const propertyPath = jsonPointerJoin(normalizedPath, key);
2259
+ return this.setValue(propertyPath, defaultValue);
2260
+ }
2261
+ return false;
2262
+ }
2263
+ /**
2264
+ * Set the value at a specific path.
2265
+ * Creates intermediate containers (objects/arrays) as needed.
2266
+ * Triggers reconciliation and notifies subscribers.
2267
+ *
2268
+ * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
2269
+ * @param value - The new value to set
2270
+ * @returns true if successful, false if the path cannot be set
2271
+ *
2272
+ * @example
2273
+ * runtime.setValue("/name", "Bob"); // set name to "Bob"
2274
+ * runtime.setValue("", { name: "Alice" }); // replace entire root value
2275
+ */
2276
+ setValue(path, value) {
2277
+ const normalizedPath = normalizeRootPath(path);
2278
+ if (normalizedPath === ROOT_PATH) {
2279
+ this.value = value;
2280
+ } else {
2281
+ const success = setJsonPointer(this.value, normalizedPath, value);
2282
+ if (!success) return false;
2283
+ }
2284
+ this.reconcile(normalizedPath);
2285
+ this.notify({ type: "value", path: normalizedPath });
2286
+ return true;
2287
+ }
2288
+ /**
2289
+ * Find the FieldNode at a specific path.
2290
+ * Returns the node tree representation that includes schema, type, error, and children.
2291
+ *
2292
+ * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
2293
+ * @returns The FieldNode at the path, or undefined if not found
2294
+ *
2295
+ * @example
2296
+ * const node = runtime.findNode("/name");
2297
+ * console.log(node?.schema, node?.type, node?.error);
2298
+ */
2299
+ findNode(path) {
2300
+ const normalizedPath = normalizeRootPath(path);
2301
+ if (normalizedPath === ROOT_PATH) return this.root;
2302
+ const segments = parseJsonPointer(normalizedPath);
2303
+ let current = this.root;
2304
+ for (const segment of segments) {
2305
+ if (!current?.children) return void 0;
2306
+ const expectedPath = jsonPointerJoin(current.instanceLocation, segment);
2307
+ const found = current.children?.find(
2308
+ (child) => child.instanceLocation === expectedPath
2309
+ );
2310
+ if (!found) return void 0;
2311
+ current = found;
2312
+ }
2313
+ return current;
2314
+ }
2315
+ findNearestExistingNode(path) {
2316
+ const normalizedPath = normalizeRootPath(path);
2317
+ let currentPath = normalizedPath;
2318
+ while (currentPath !== ROOT_PATH) {
2319
+ const node = this.findNode(currentPath);
2320
+ if (node) return node;
2321
+ const lastSlash = currentPath.lastIndexOf("/");
2322
+ currentPath = lastSlash <= 0 ? ROOT_PATH : currentPath.substring(0, lastSlash);
2323
+ }
2324
+ return this.root;
2325
+ }
2326
+ /**
2327
+ * Build/update a FieldNode in place.
2328
+ * Updates the node's schema, type, error, and children based on the current value.
2329
+ * @param schema - Optional. If provided, updates node.originalSchema. Otherwise uses existing.
2330
+ */
2331
+ buildNode(node, schema, options = {}) {
2332
+ const { keywordLocation, instanceLocation } = node;
2333
+ const value = this.getValue(instanceLocation);
2334
+ if (value === void 0) {
2335
+ const defaultValue = getDefaultValue(schema || node.originalSchema);
2336
+ this.setValue(instanceLocation, defaultValue);
2337
+ return;
2338
+ }
2339
+ if (this.updatingNodes.has(instanceLocation)) {
2340
+ return;
2341
+ }
2342
+ const updatedNodes = options.updatedNodes || /* @__PURE__ */ new Set();
2343
+ if (updatedNodes.has(instanceLocation)) {
2344
+ return;
2345
+ }
2346
+ this.updatingNodes.add(instanceLocation);
2347
+ const schemaChanged = schema !== void 0 && !deepEqual(schema, node.originalSchema);
2348
+ if (schemaChanged) {
2349
+ node.originalSchema = schema;
2350
+ const dependencies = this.collectDependencies(
2351
+ node.originalSchema,
2352
+ instanceLocation
2353
+ );
2354
+ for (const depPath of node.dependencies || []) {
2355
+ this.unregisterDependent(depPath, node);
2356
+ }
2357
+ node.dependencies = dependencies;
2358
+ if (!options.skipDependencyRegistration) {
2359
+ for (const depPath of dependencies) {
2360
+ this.registerDependent(depPath, node);
2361
+ }
2362
+ }
2363
+ }
2364
+ const { type, effectiveSchema, error } = resolveEffectiveSchema(
2365
+ this.validator,
2366
+ node.originalSchema,
2367
+ value,
2368
+ keywordLocation,
2369
+ instanceLocation
2370
+ );
2371
+ const effectiveSchemaChanged = !deepEqual(effectiveSchema, node.schema) || type !== node.type;
2372
+ const errorChanged = !deepEqual(error, node.error);
2373
+ node.schema = effectiveSchema;
2374
+ node.type = type;
2375
+ node.error = error;
2376
+ node.version++;
2377
+ const oldChildrenMap = /* @__PURE__ */ new Map();
2378
+ if (node.children) {
2379
+ for (const child of node.children) {
2380
+ oldChildrenMap.set(child.instanceLocation, child);
2381
+ }
2382
+ }
2383
+ const newChildren = [];
2384
+ const processChild = (childKey, childSchema, childkeywordLocation, canRemove = false) => {
2385
+ const childinstanceLocation = jsonPointerJoin(instanceLocation, childKey);
2386
+ let childNode = oldChildrenMap.get(childinstanceLocation);
2387
+ if (childNode) {
2388
+ oldChildrenMap.delete(childinstanceLocation);
2389
+ childNode.keywordLocation = childkeywordLocation;
2390
+ } else {
2391
+ childNode = this.createEmptyNode(
2392
+ childinstanceLocation,
2393
+ childkeywordLocation
2394
+ );
2395
+ }
2396
+ childNode.canRemove = canRemove;
2397
+ this.buildNode(childNode, childSchema, options);
2398
+ newChildren.push(childNode);
2399
+ };
2400
+ switch (type) {
2401
+ case "object": {
2402
+ const valueKeys = value && typeof value === "object" ? Object.keys(value) : [];
2403
+ const processedKeys = /* @__PURE__ */ new Set();
2404
+ node.canAdd = !!effectiveSchema.additionalProperties;
2405
+ if (effectiveSchema.properties) {
2406
+ for (const [key, subschema] of Object.entries(
2407
+ effectiveSchema.properties
2408
+ )) {
2409
+ processedKeys.add(key);
2410
+ processChild(
2411
+ key,
2412
+ subschema,
2413
+ `${keywordLocation}/properties/${key}`,
2414
+ false
2415
+ );
2416
+ }
2417
+ }
2418
+ if (effectiveSchema.patternProperties) {
2419
+ for (const [pattern, subschema] of Object.entries(
2420
+ effectiveSchema.patternProperties
2421
+ )) {
2422
+ for (const key of valueKeys) {
2423
+ if (safeRegexTest(pattern, key) && !processedKeys.has(key)) {
2424
+ processedKeys.add(key);
2425
+ processChild(
2426
+ key,
2427
+ subschema,
2428
+ `${keywordLocation}/patternProperties/${jsonPointerEscape(pattern)}`,
2429
+ true
2430
+ );
2431
+ }
2432
+ }
2433
+ }
2434
+ }
2435
+ if (effectiveSchema.additionalProperties) {
2436
+ const subschema = typeof effectiveSchema.additionalProperties === "object" ? effectiveSchema.additionalProperties : {};
2437
+ for (const key of valueKeys) {
2438
+ if (!processedKeys.has(key)) {
2439
+ processChild(
2440
+ key,
2441
+ subschema,
2442
+ `${keywordLocation}/additionalProperties`,
2443
+ true
2444
+ );
2445
+ }
2446
+ }
2447
+ }
2448
+ break;
2449
+ }
2450
+ case "array": {
2451
+ node.canAdd = !!effectiveSchema.items;
2452
+ if (Array.isArray(value)) {
2453
+ let prefixItemsLength = 0;
2454
+ if (effectiveSchema.prefixItems) {
2455
+ prefixItemsLength = effectiveSchema.prefixItems.length;
2456
+ for (let i = 0; i < Math.min(value.length, prefixItemsLength); i++) {
2457
+ processChild(
2458
+ String(i),
2459
+ effectiveSchema.prefixItems[i],
2460
+ `${keywordLocation}/prefixItems/${i}`,
2461
+ false
2462
+ );
2463
+ }
2464
+ }
2465
+ if (effectiveSchema.items && value.length > prefixItemsLength) {
2466
+ for (let i = prefixItemsLength; i < value.length; i++) {
2467
+ processChild(
2468
+ String(i),
2469
+ effectiveSchema.items,
2470
+ `${keywordLocation}/items`,
2471
+ true
2472
+ );
2473
+ }
2474
+ }
2475
+ }
2476
+ break;
2477
+ }
2478
+ }
2479
+ for (const oldChild of oldChildrenMap.values()) {
2480
+ this.unregisterNodeDependencies(oldChild);
2481
+ }
2482
+ node.children = newChildren;
2483
+ if (effectiveSchemaChanged) {
2484
+ this.notify({ type: "schema", path: node.instanceLocation });
2485
+ }
2486
+ if (errorChanged) {
2487
+ this.notify({ type: "error", path: node.instanceLocation });
2488
+ }
2489
+ updatedNodes.add(instanceLocation);
2490
+ this.updatingNodes.delete(instanceLocation);
2491
+ const dependentNodes = this.dependentsMap.get(instanceLocation);
2492
+ if (dependentNodes) {
2493
+ for (const dependentNode of dependentNodes) {
2494
+ this.buildNode(dependentNode, void 0, {
2495
+ ...options,
2496
+ updatedNodes
2497
+ });
2498
+ }
2499
+ }
2500
+ }
2501
+ };
2502
+
2503
+ export { DRAFT_URIS, MESSAGES, SchemaRuntime, StringFormatValidator, Validator, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, get, getJsonPointer, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, validateSchema };
2504
+ //# sourceMappingURL=index.js.map
2505
+ //# sourceMappingURL=index.js.map