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