@schema-ts/core 0.1.4 → 0.1.5

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 CHANGED
@@ -146,6 +146,10 @@ function setJsonPointer(obj, jsonPointer, value) {
146
146
  }
147
147
  return true;
148
148
  }
149
+ function getJsonPointerParent(path) {
150
+ const lastSlash = path.lastIndexOf("/");
151
+ return lastSlash <= 0 ? "" : path.substring(0, lastSlash);
152
+ }
149
153
  function get(obj, path) {
150
154
  let current = obj;
151
155
  for (const segment of path) {
@@ -465,6 +469,11 @@ function detectSchemaDraft(schema) {
465
469
  }
466
470
 
467
471
  // src/normalize.ts
472
+ var DraftNormalizer = class {
473
+ normalize(schema) {
474
+ return normalizeSchema(schema);
475
+ }
476
+ };
468
477
  function normalizeSchema(schema, options = {}) {
469
478
  if (typeof schema === "boolean") {
470
479
  return schema ? {} : { not: {} };
@@ -548,8 +557,6 @@ function normalizeDraft07(schema) {
548
557
  } else {
549
558
  delete legacy.items;
550
559
  }
551
- } else if ("additionalItems" in legacy) {
552
- delete legacy.additionalItems;
553
560
  }
554
561
  if ("dependencies" in legacy) {
555
562
  const deps = legacy.dependencies;
@@ -625,18 +632,16 @@ function normalizeNestedSchemas(schema, options) {
625
632
  }
626
633
  if (schema.properties) {
627
634
  schema.properties = Object.fromEntries(
628
- Object.entries(schema.properties).map(([key, subSchema]) => [
629
- key,
630
- normalizeSchema(subSchema, options)
631
- ])
635
+ Object.entries(schema.properties).filter(
636
+ ([_, subSchema]) => subSchema !== null && subSchema !== void 0
637
+ ).map(([key, subSchema]) => [key, normalizeSchema(subSchema, options)])
632
638
  );
633
639
  }
634
640
  if (schema.patternProperties) {
635
641
  schema.patternProperties = Object.fromEntries(
636
- Object.entries(schema.patternProperties).map(([key, subSchema]) => [
637
- key,
638
- normalizeSchema(subSchema, options)
639
- ])
642
+ Object.entries(schema.patternProperties).filter(
643
+ ([_, subSchema]) => subSchema !== null && subSchema !== void 0
644
+ ).map(([key, subSchema]) => [key, normalizeSchema(subSchema, options)])
640
645
  );
641
646
  }
642
647
  if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
@@ -679,10 +684,9 @@ function normalizeNestedSchemas(schema, options) {
679
684
  }
680
685
  if (schema.dependentSchemas) {
681
686
  schema.dependentSchemas = Object.fromEntries(
682
- Object.entries(schema.dependentSchemas).map(([key, subSchema]) => [
683
- key,
684
- normalizeSchema(subSchema, options)
685
- ])
687
+ Object.entries(schema.dependentSchemas).filter(
688
+ ([_, subSchema]) => subSchema !== null && subSchema !== void 0
689
+ ).map(([key, subSchema]) => [key, normalizeSchema(subSchema, options)])
686
690
  );
687
691
  }
688
692
  if (schema.unevaluatedItems) {
@@ -701,6 +705,120 @@ function normalizeNestedSchemas(schema, options) {
701
705
  schema.contentSchema = normalizeSchema(schema.contentSchema, options);
702
706
  }
703
707
  }
708
+ var BetterNormalizer = class {
709
+ /**
710
+ * Performs the normalization process:
711
+ * 1. Base draft normalization (DraftNormalizer)
712
+ * 2. Enhanced default value completion (Better logic)
713
+ */
714
+ normalize(schema) {
715
+ const normalized = normalizeSchema(schema);
716
+ this.defaultSchema(normalized);
717
+ return normalized;
718
+ }
719
+ /**
720
+ * Recursively processes all sub-nodes of the schema, applying default rules.
721
+ */
722
+ default(schema, forceRequired = false) {
723
+ this.defaultSchema(schema.if, true);
724
+ this.defaultSchema(schema.then);
725
+ this.defaultSchema(schema.else);
726
+ schema.anyOf?.forEach((s) => this.defaultSchema(s));
727
+ schema.oneOf?.forEach((s) => this.defaultSchema(s));
728
+ schema.allOf?.forEach((s) => this.defaultSchema(s));
729
+ this.defaultSchemaMap(schema.properties, forceRequired);
730
+ this.defaultSchemaMap(schema.patternProperties, forceRequired);
731
+ this.defaultSchema(schema.additionalProperties, forceRequired);
732
+ this.defaultSchema(schema.propertyNames);
733
+ this.defaultSchemaMap(schema.dependentSchemas, true);
734
+ this.defaultSchema(schema.items);
735
+ schema.prefixItems?.forEach((s) => this.defaultSchema(s));
736
+ this.defaultSchema(schema.contains);
737
+ this.defaultSchema(schema.unevaluatedItems);
738
+ this.defaultSchema(schema.unevaluatedProperties);
739
+ this.defaultSchema(schema.contentSchema);
740
+ return schema;
741
+ }
742
+ /**
743
+ * Batch processes Record-type sub-schemas.
744
+ */
745
+ defaultSchemaMap(kvs, forceRequired = false) {
746
+ if (!kvs) {
747
+ return;
748
+ }
749
+ Object.entries(kvs).forEach(([_, value]) => {
750
+ this.defaultSchema(value, forceRequired);
751
+ });
752
+ }
753
+ /**
754
+ * Applies inference and heuristic rules to a single schema object.
755
+ * Includes:
756
+ * - `type` inference based on properties (object/array)
757
+ * - Automatically setting properties with `const`/`default`/`enum` as `required`
758
+ * - Handling `required` fields from OpenAPI `discriminator`
759
+ * - Handling `x-required` and `x-kubernetes-patch-merge-key`
760
+ */
761
+ defaultSchema(schema, forceRequired = false) {
762
+ if (!schema || typeof schema === "boolean") {
763
+ return;
764
+ }
765
+ if (!schema.type) {
766
+ if (schema.properties || schema.patternProperties || schema.additionalProperties) {
767
+ schema.type = "object";
768
+ } else if (schema.items || schema.prefixItems || schema.additionalItems) {
769
+ schema.type = "array";
770
+ }
771
+ }
772
+ if (schema.patternProperties && schema.minProperties === void 0) {
773
+ schema.minProperties = 1;
774
+ }
775
+ if (schema.required === void 0) {
776
+ const required = [];
777
+ for (const [key, value] of Object.entries(schema.properties || {})) {
778
+ if (forceRequired || value.const !== void 0 || Array.isArray(value.enum) && value.enum.length > 0) {
779
+ required.push(key);
780
+ }
781
+ }
782
+ if (required.length > 0) {
783
+ schema.required = required;
784
+ }
785
+ }
786
+ const discriminator = toObject(schema.discriminator);
787
+ if (discriminator.propertyName) {
788
+ if (typeof discriminator.propertyName === "string") {
789
+ if (schema.required === void 0) {
790
+ schema.required = [discriminator.propertyName];
791
+ } else if (!schema.required.includes(discriminator.propertyName)) {
792
+ schema.required.push(discriminator.propertyName);
793
+ }
794
+ }
795
+ }
796
+ for (const [key, value] of Object.entries(schema.properties || {})) {
797
+ if (value["x-required"] === true) {
798
+ if (schema.required === void 0) {
799
+ schema.required = [key];
800
+ } else if (!schema.required.includes(key)) {
801
+ schema.required.push(key);
802
+ }
803
+ }
804
+ }
805
+ const mergeKey = schema["x-kubernetes-patch-merge-key"];
806
+ if (typeof mergeKey === "string" && typeof schema.items === "object" && schema.items !== null) {
807
+ const items = schema.items;
808
+ if (!items.required) items.required = [];
809
+ if (!items.required.includes(mergeKey)) {
810
+ items.required.push(mergeKey);
811
+ }
812
+ }
813
+ this.default(schema, forceRequired);
814
+ }
815
+ };
816
+ function toObject(val) {
817
+ if (typeof val === "object" && val !== null) {
818
+ return val;
819
+ }
820
+ return {};
821
+ }
704
822
 
705
823
  // src/validate.ts
706
824
  var Validator = class {
@@ -1334,14 +1452,15 @@ function validateSchema(schema, value, instancePath = "", schemaPath = "#", fast
1334
1452
  }
1335
1453
 
1336
1454
  // src/effective.ts
1337
- function resolveEffectiveSchema(validator, schema, value, keywordLocation, instanceLocation, isRequired = true) {
1455
+ function resolveEffectiveSchema(validator, schema, value, keywordLocation, instanceLocation, validate = false) {
1338
1456
  let effective = schema;
1339
1457
  if (effective.if) {
1340
1458
  const output = validator.validate(
1341
1459
  effective.if,
1342
1460
  value,
1343
1461
  `${keywordLocation}/if`,
1344
- instanceLocation
1462
+ instanceLocation,
1463
+ { fastFail: true }
1345
1464
  );
1346
1465
  if (output.valid) {
1347
1466
  if (effective.then) {
@@ -1350,9 +1469,14 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1350
1469
  effective.then,
1351
1470
  value,
1352
1471
  `${keywordLocation}/then`,
1353
- instanceLocation
1472
+ instanceLocation,
1473
+ validate
1474
+ );
1475
+ effective = mergeSchema(
1476
+ effective,
1477
+ res.effectiveSchema,
1478
+ `${keywordLocation}/then`
1354
1479
  );
1355
- effective = mergeSchema(effective, res.effectiveSchema);
1356
1480
  }
1357
1481
  } else {
1358
1482
  if (effective.else) {
@@ -1361,9 +1485,14 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1361
1485
  effective.else,
1362
1486
  value,
1363
1487
  `${keywordLocation}/else`,
1364
- instanceLocation
1488
+ instanceLocation,
1489
+ validate
1490
+ );
1491
+ effective = mergeSchema(
1492
+ effective,
1493
+ res.effectiveSchema,
1494
+ `${keywordLocation}/else`
1365
1495
  );
1366
- effective = mergeSchema(effective, res.effectiveSchema);
1367
1496
  }
1368
1497
  }
1369
1498
  const { if: _, then: __, else: ___, ...rest } = effective;
@@ -1371,24 +1500,31 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1371
1500
  }
1372
1501
  if (effective.allOf) {
1373
1502
  for (const [index, subschema] of effective.allOf.entries()) {
1503
+ const subKeywordLocation = `${keywordLocation}/allOf/${index}`;
1374
1504
  const res = resolveEffectiveSchema(
1375
1505
  validator,
1376
1506
  subschema,
1377
1507
  value,
1378
- `${keywordLocation}/allOf/${index}`,
1379
- instanceLocation
1508
+ subKeywordLocation,
1509
+ instanceLocation,
1510
+ validate
1511
+ );
1512
+ effective = mergeSchema(
1513
+ effective,
1514
+ res.effectiveSchema,
1515
+ subKeywordLocation
1380
1516
  );
1381
- effective = mergeSchema(effective, res.effectiveSchema);
1382
1517
  }
1383
1518
  const { allOf: _, ...rest } = effective;
1384
1519
  effective = rest;
1385
1520
  }
1386
1521
  if (effective.anyOf) {
1387
1522
  for (const [index, subschema] of effective.anyOf.entries()) {
1523
+ const subKeywordLocation = `${keywordLocation}/anyOf/${index}`;
1388
1524
  const output = validator.validate(
1389
1525
  subschema,
1390
1526
  value,
1391
- keywordLocation + `/anyOf/` + index,
1527
+ subKeywordLocation,
1392
1528
  instanceLocation
1393
1529
  );
1394
1530
  if (output.valid) {
@@ -1396,10 +1532,15 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1396
1532
  validator,
1397
1533
  subschema,
1398
1534
  value,
1399
- keywordLocation + `/anyOf/` + index,
1400
- instanceLocation
1535
+ subKeywordLocation,
1536
+ instanceLocation,
1537
+ validate
1538
+ );
1539
+ effective = mergeSchema(
1540
+ effective,
1541
+ res.effectiveSchema,
1542
+ subKeywordLocation
1401
1543
  );
1402
- effective = mergeSchema(effective, res.effectiveSchema);
1403
1544
  }
1404
1545
  }
1405
1546
  const { anyOf: _, ...rest } = effective;
@@ -1408,6 +1549,7 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1408
1549
  if (effective.oneOf) {
1409
1550
  let validCount = 0;
1410
1551
  let lastValidSchema = null;
1552
+ let lastValidIndex = -1;
1411
1553
  for (const [index, subschema] of effective.oneOf.entries()) {
1412
1554
  const output = validator.validate(
1413
1555
  subschema,
@@ -1418,10 +1560,15 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1418
1560
  if (output.valid) {
1419
1561
  validCount++;
1420
1562
  lastValidSchema = subschema;
1563
+ lastValidIndex = index;
1421
1564
  }
1422
1565
  }
1423
1566
  if (validCount === 1 && lastValidSchema) {
1424
- effective = mergeSchema(effective, lastValidSchema);
1567
+ effective = mergeSchema(
1568
+ effective,
1569
+ lastValidSchema,
1570
+ `${keywordLocation}/oneOf/${lastValidIndex}`
1571
+ );
1425
1572
  }
1426
1573
  const { oneOf: _, ...rest } = effective;
1427
1574
  effective = rest;
@@ -1438,7 +1585,7 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1438
1585
  } else {
1439
1586
  type = detectSchemaType(value);
1440
1587
  }
1441
- if (!isRequired && value === void 0) {
1588
+ if (!validate) {
1442
1589
  return {
1443
1590
  effectiveSchema: effective,
1444
1591
  type,
@@ -1478,25 +1625,42 @@ function mergeSchemaArrays(a, b) {
1478
1625
  if (b === void 0) return a;
1479
1626
  return [...a, ...b];
1480
1627
  }
1481
- function mergeSchemaMap(base, override) {
1482
- if (base === void 0) return override;
1628
+ function mergeSchemaMap(base, override, overrideOrigin) {
1629
+ if (base === void 0) {
1630
+ if (override === void 0) return void 0;
1631
+ if (overrideOrigin) {
1632
+ const result = {};
1633
+ for (const [key, schema] of Object.entries(override)) {
1634
+ result[key] = {
1635
+ ...schema,
1636
+ "x-origin-keyword": `${overrideOrigin}/${key}`
1637
+ };
1638
+ }
1639
+ return result;
1640
+ }
1641
+ return override;
1642
+ }
1483
1643
  if (override === void 0) return base;
1484
1644
  const merged = { ...base };
1485
1645
  for (const [key, schema] of Object.entries(override)) {
1646
+ const childOrigin = overrideOrigin ? `${overrideOrigin}/${key}` : void 0;
1486
1647
  if (merged[key]) {
1487
- merged[key] = mergeSchema(merged[key], schema);
1648
+ merged[key] = mergeSchema(merged[key], schema, childOrigin);
1488
1649
  } else {
1489
- merged[key] = schema;
1650
+ merged[key] = childOrigin ? { ...schema, "x-origin-keyword": childOrigin } : schema;
1490
1651
  }
1491
1652
  }
1492
1653
  return merged;
1493
1654
  }
1494
- function mergeSchema(base, override) {
1655
+ function mergeSchema(base, override, overrideOrigin) {
1495
1656
  if (!override) return base;
1496
1657
  const merged = {
1497
1658
  ...base,
1498
1659
  ...override
1499
1660
  };
1661
+ if (overrideOrigin) {
1662
+ merged["x-origin-keyword"] = overrideOrigin;
1663
+ }
1500
1664
  if (base.$defs || override.$defs) {
1501
1665
  merged.$defs = {
1502
1666
  ...base.$defs,
@@ -1517,23 +1681,31 @@ function mergeSchema(base, override) {
1517
1681
  ...override.dependentRequired
1518
1682
  };
1519
1683
  }
1520
- const mergedProperties = mergeSchemaMap(base.properties, override.properties);
1684
+ const propertiesOrigin = overrideOrigin ? `${overrideOrigin}/properties` : void 0;
1685
+ const mergedProperties = mergeSchemaMap(
1686
+ base.properties,
1687
+ override.properties,
1688
+ propertiesOrigin
1689
+ );
1521
1690
  if (mergedProperties !== void 0) {
1522
1691
  merged.properties = mergedProperties;
1523
1692
  }
1693
+ const patternPropertiesOrigin = overrideOrigin ? `${overrideOrigin}/patternProperties` : void 0;
1524
1694
  const mergedPatternProperties = mergeSchemaMap(
1525
1695
  base.patternProperties,
1526
- override.patternProperties
1696
+ override.patternProperties,
1697
+ patternPropertiesOrigin
1527
1698
  );
1528
1699
  if (mergedPatternProperties !== void 0) {
1529
1700
  merged.patternProperties = mergedPatternProperties;
1530
1701
  }
1702
+ const itemsOrigin = overrideOrigin ? `${overrideOrigin}/items` : void 0;
1531
1703
  if (base.items && override.items) {
1532
- merged.items = mergeSchema(base.items, override.items);
1704
+ merged.items = mergeSchema(base.items, override.items, itemsOrigin);
1533
1705
  } else if (base.items) {
1534
1706
  merged.items = base.items;
1535
1707
  } else if (override.items) {
1536
- merged.items = override.items;
1708
+ merged.items = itemsOrigin ? { ...override.items, "x-origin-keyword": itemsOrigin } : override.items;
1537
1709
  }
1538
1710
  if (base.prefixItems || override.prefixItems) {
1539
1711
  merged.prefixItems = [];
@@ -1544,13 +1716,17 @@ function mergeSchema(base, override) {
1544
1716
  for (let i = 0; i < len; i++) {
1545
1717
  const baseSchema = base.prefixItems?.[i];
1546
1718
  const overrideSchema = override.prefixItems?.[i];
1719
+ const prefixItemOrigin = overrideOrigin ? `${overrideOrigin}/prefixItems/${i}` : void 0;
1547
1720
  if (baseSchema && overrideSchema) {
1548
- merged.prefixItems.push(mergeSchema(baseSchema, overrideSchema));
1549
- } else {
1550
- const schema = baseSchema || overrideSchema;
1551
- if (schema) {
1552
- merged.prefixItems.push(schema);
1553
- }
1721
+ merged.prefixItems.push(
1722
+ mergeSchema(baseSchema, overrideSchema, prefixItemOrigin)
1723
+ );
1724
+ } else if (overrideSchema) {
1725
+ merged.prefixItems.push(
1726
+ prefixItemOrigin ? { ...overrideSchema, "x-origin-keyword": prefixItemOrigin } : overrideSchema
1727
+ );
1728
+ } else if (baseSchema) {
1729
+ merged.prefixItems.push(baseSchema);
1554
1730
  }
1555
1731
  }
1556
1732
  }
@@ -1561,9 +1737,11 @@ function mergeSchema(base, override) {
1561
1737
  merged[keyword] = mergedArray;
1562
1738
  }
1563
1739
  }
1740
+ const dependentSchemasOrigin = overrideOrigin ? `${overrideOrigin}/dependentSchemas` : void 0;
1564
1741
  const mergedDependentSchemas = mergeSchemaMap(
1565
1742
  base.dependentSchemas,
1566
- override.dependentSchemas
1743
+ override.dependentSchemas,
1744
+ dependentSchemasOrigin
1567
1745
  );
1568
1746
  if (mergedDependentSchemas !== void 0) {
1569
1747
  merged.dependentSchemas = mergedDependentSchemas;
@@ -1742,7 +1920,8 @@ function getSubSchema(schema, key) {
1742
1920
  if (schema.properties && schema.properties[key]) {
1743
1921
  return {
1744
1922
  schema: schema.properties[key],
1745
- keywordLocationToken: `properties/${key}`
1923
+ keywordLocationToken: `properties/${key}`,
1924
+ required: schema.required?.includes(key) || false
1746
1925
  };
1747
1926
  }
1748
1927
  if (schema.patternProperties) {
@@ -1752,7 +1931,8 @@ function getSubSchema(schema, key) {
1752
1931
  if (safeRegexTest(pattern, key)) {
1753
1932
  return {
1754
1933
  schema: subschema,
1755
- keywordLocationToken: `patternProperties/${pattern}`
1934
+ keywordLocationToken: `patternProperties/${pattern}`,
1935
+ required: false
1756
1936
  };
1757
1937
  }
1758
1938
  }
@@ -1760,7 +1940,8 @@ function getSubSchema(schema, key) {
1760
1940
  if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
1761
1941
  return {
1762
1942
  schema: typeof schema.additionalProperties === "object" ? schema.additionalProperties : {},
1763
- keywordLocationToken: "additionalProperties"
1943
+ keywordLocationToken: "additionalProperties",
1944
+ required: false
1764
1945
  };
1765
1946
  }
1766
1947
  if (schema.items || schema.prefixItems) {
@@ -1769,102 +1950,124 @@ function getSubSchema(schema, key) {
1769
1950
  if (schema.prefixItems && index < schema.prefixItems.length) {
1770
1951
  return {
1771
1952
  schema: schema.prefixItems[index],
1772
- keywordLocationToken: `prefixItems/${index}`
1953
+ keywordLocationToken: `prefixItems/${index}`,
1954
+ required: true
1955
+ // prefixItems are always required
1773
1956
  };
1774
1957
  }
1775
1958
  if (schema.items) {
1776
1959
  return {
1777
1960
  schema: schema.items,
1778
- keywordLocationToken: "items"
1961
+ keywordLocationToken: "items",
1962
+ // items beyond prefixItems are not required by default
1963
+ // but usually new item is considered required to hold the place
1964
+ required: true
1779
1965
  };
1780
1966
  }
1781
1967
  }
1782
1968
  }
1783
1969
  return {
1784
1970
  schema: {},
1785
- keywordLocationToken: ""
1971
+ keywordLocationToken: "",
1972
+ required: false
1786
1973
  };
1787
1974
  }
1788
1975
 
1789
1976
  // src/default.ts
1790
- function getDefaultValue(schema, options = {}) {
1791
- const { value, strategy = "explicit" } = options;
1792
- if (value === void 0) {
1793
- if (schema.const !== void 0) return schema.const;
1794
- if (schema.default !== void 0) return schema.default;
1795
- }
1796
- const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1797
- if (type === "object" || !type && schema.properties) {
1798
- let obj;
1799
- if (value === void 0) {
1800
- if (strategy === "explicit") return void 0;
1801
- obj = {};
1802
- } else if (typeof value === "object" && value !== null) {
1803
- obj = value;
1804
- } else {
1805
- return value;
1806
- }
1807
- if (schema.properties) {
1808
- for (const [key, subschema] of Object.entries(schema.properties)) {
1809
- if (obj[key] !== void 0) {
1810
- obj[key] = getDefaultValue(subschema, { value: obj[key], strategy });
1811
- } else if (schema.required?.includes(key)) {
1812
- obj[key] = getDefaultValue(subschema, { strategy });
1977
+ function typeNullable(schema) {
1978
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
1979
+ const nullable = types.includes("null");
1980
+ const type = types.find((t) => t !== "null");
1981
+ return [type, nullable];
1982
+ }
1983
+ function getDefaultValue(schema, required = false) {
1984
+ if (schema.const !== void 0) return schema.const;
1985
+ if (schema.default !== void 0) return schema.default;
1986
+ const [type, nullable] = typeNullable(schema);
1987
+ switch (type) {
1988
+ case "object": {
1989
+ const obj = {};
1990
+ for (const [key, subschema] of Object.entries(schema.properties || {})) {
1991
+ if (schema.required?.includes(key)) {
1992
+ obj[key] = getDefaultValue(subschema, true);
1813
1993
  }
1814
1994
  }
1995
+ if (Object.keys(obj).length === 0) {
1996
+ return required ? nullable ? null : {} : void 0;
1997
+ }
1998
+ return obj;
1815
1999
  }
1816
- return obj;
1817
- }
1818
- if (type === "array") {
1819
- let arr;
1820
- if (value === void 0) {
1821
- if (strategy === "explicit") return void 0;
1822
- arr = [];
1823
- } else if (Array.isArray(value)) {
1824
- arr = value;
1825
- } else {
1826
- return value;
1827
- }
1828
- if (schema.prefixItems) {
1829
- schema.prefixItems.forEach((subschema, index) => {
1830
- if (index < arr.length) {
1831
- arr[index] = getDefaultValue(subschema, {
1832
- value: arr[index],
1833
- strategy
1834
- });
1835
- } else if (value === void 0) {
1836
- arr.push(getDefaultValue(subschema, { strategy }));
1837
- }
2000
+ case "array": {
2001
+ const arr = [];
2002
+ schema.prefixItems?.forEach((subschema) => {
2003
+ arr.push(getDefaultValue(subschema, true));
1838
2004
  });
1839
- }
1840
- if (value !== void 0 && schema.items) {
1841
- const startIndex = schema.prefixItems ? schema.prefixItems.length : 0;
1842
- for (let i = startIndex; i < arr.length; i++) {
1843
- arr[i] = getDefaultValue(schema.items, { value: arr[i], strategy });
2005
+ if (arr.length === 0) {
2006
+ return required ? nullable ? null : [] : void 0;
1844
2007
  }
2008
+ return arr;
1845
2009
  }
1846
- return arr;
1847
- }
1848
- if (value !== void 0) {
1849
- return value;
1850
- }
1851
- if (strategy === "explicit") {
1852
- return void 0;
1853
- }
1854
- switch (type) {
1855
2010
  case "string":
1856
- return "";
2011
+ return required ? nullable ? null : "" : void 0;
1857
2012
  case "number":
2013
+ return required ? nullable ? null : 0 : void 0;
1858
2014
  case "integer":
1859
- return 0;
2015
+ return required ? nullable ? null : 0 : void 0;
1860
2016
  case "boolean":
1861
- return false;
2017
+ return required ? nullable ? null : false : void 0;
1862
2018
  case "null":
1863
2019
  return null;
1864
2020
  default:
1865
2021
  return void 0;
1866
2022
  }
1867
2023
  }
2024
+ function applyDefaults(type, value, schema, required = false) {
2025
+ if (value === void 0) {
2026
+ if (!required) {
2027
+ return [value, false];
2028
+ }
2029
+ const defaultValue = getDefaultValue(schema, required);
2030
+ return [defaultValue, defaultValue !== void 0];
2031
+ }
2032
+ const [_, nullable] = typeNullable(schema);
2033
+ if (nullable && value === null) {
2034
+ return [null, false];
2035
+ }
2036
+ let changed = false;
2037
+ if (type === "object") {
2038
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2039
+ return [value, false];
2040
+ }
2041
+ const obj = value;
2042
+ for (const [key, subschema] of Object.entries(schema.properties || {})) {
2043
+ if (obj[key] !== void 0) continue;
2044
+ if (schema.required?.includes(key) || subschema.default) {
2045
+ const defaultValue = getDefaultValue(subschema, required);
2046
+ if (defaultValue !== void 0) {
2047
+ obj[key] = defaultValue;
2048
+ changed = true;
2049
+ }
2050
+ }
2051
+ }
2052
+ return [obj, changed];
2053
+ }
2054
+ if (type === "array") {
2055
+ if (!Array.isArray(value)) {
2056
+ return [value, false];
2057
+ }
2058
+ const arr = value;
2059
+ schema.prefixItems?.forEach((subschema, index) => {
2060
+ if (arr[index] !== void 0) return;
2061
+ const defaultValue = getDefaultValue(subschema, true);
2062
+ if (defaultValue !== void 0) {
2063
+ arr[index] = defaultValue;
2064
+ changed = true;
2065
+ }
2066
+ });
2067
+ return [arr, changed];
2068
+ }
2069
+ return [value, false];
2070
+ }
1868
2071
 
1869
2072
  // src/dependency.ts
1870
2073
  function collectDependencies(schema, instanceLocation) {
@@ -2026,12 +2229,8 @@ function extractReferencedPaths(conditionSchema, basePath = "", depth = 0) {
2026
2229
  }
2027
2230
  }
2028
2231
  }
2029
- if (schema.dependentSchemas) {
2030
- for (const [key, subSchema] of Object.entries(schema.dependentSchemas)) {
2031
- const keyPath = basePath ? `${basePath}/${key}` : `/${key}`;
2032
- paths.push(keyPath);
2033
- paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
2034
- }
2232
+ if (schema.not) {
2233
+ paths.push(...extractReferencedPaths(schema.not, basePath, depth + 1));
2035
2234
  }
2036
2235
  if (schema.contains) {
2037
2236
  paths.push(basePath || "/");
@@ -2041,12 +2240,9 @@ function extractReferencedPaths(conditionSchema, basePath = "", depth = 0) {
2041
2240
  }
2042
2241
 
2043
2242
  // src/render.ts
2044
- var ROOT_PATH = "";
2045
- function normalizeRootPath(path) {
2046
- return path === "#" ? ROOT_PATH : path;
2047
- }
2048
2243
  var SchemaRuntime = class {
2049
2244
  validator;
2245
+ normalizer;
2050
2246
  watchers = {};
2051
2247
  globalWatchers = /* @__PURE__ */ new Set();
2052
2248
  // Reverse dependency index: path -> nodes that depend on this path's value
@@ -2073,17 +2269,30 @@ var SchemaRuntime = class {
2073
2269
  */
2074
2270
  constructor(validator, schema, value, options = {}) {
2075
2271
  this.validator = validator;
2076
- this.value = value;
2077
2272
  this.options = {
2078
- autoFillDefaults: "explicit",
2273
+ fillDefaults: "auto",
2079
2274
  removeEmptyContainers: "auto",
2080
2275
  ...options
2081
2276
  };
2082
- const normalized = normalizeSchema(schema);
2083
- this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
2277
+ this.normalizer = options.schemaNormalizer || new DraftNormalizer();
2278
+ this.value = value;
2279
+ this.rootSchema = this.resolveSchema(schema);
2280
+ this.root = this.createEmptyNode("", "#");
2281
+ this.buildNode(this.root, this.rootSchema);
2282
+ }
2283
+ resolveSchema(schema) {
2284
+ const normalized = this.normalizer.normalize(schema);
2285
+ const dereferenced = dereferenceSchemaDeep(normalized, normalized);
2286
+ return dereferenced;
2287
+ }
2288
+ /**
2289
+ * Update the entire schema.
2290
+ * This triggers a full rebuild of the node tree while preserving the current value.
2291
+ */
2292
+ setSchema(schema) {
2293
+ this.rootSchema = this.resolveSchema(schema);
2084
2294
  this.root = this.createEmptyNode("", "#");
2085
2295
  this.buildNode(this.root, this.rootSchema);
2086
- this.notify({ type: "schema", path: ROOT_PATH });
2087
2296
  }
2088
2297
  /**
2089
2298
  * Register a node as dependent on a path
@@ -2140,8 +2349,9 @@ var SchemaRuntime = class {
2140
2349
  originalSchema: {},
2141
2350
  canRemove: false,
2142
2351
  canAdd: false,
2143
- isRequired: false,
2144
- children: []
2352
+ required: false,
2353
+ children: [],
2354
+ activated: false
2145
2355
  };
2146
2356
  }
2147
2357
  /**
@@ -2149,13 +2359,12 @@ var SchemaRuntime = class {
2149
2359
  * Uses findNearestExistingNode to find the target node (or parent if path doesn't exist).
2150
2360
  * Only rebuilds the affected subtree, not the entire tree.
2151
2361
  */
2152
- reconcile(path) {
2153
- const normalizedPath = normalizeRootPath(path);
2154
- const targetNode = this.findNearestExistingNode(normalizedPath);
2362
+ reconcile(path, config = {}) {
2363
+ const targetNode = this.findNearestExistingNode(path);
2155
2364
  if (!targetNode) {
2156
2365
  return;
2157
2366
  }
2158
- this.buildNode(targetNode, targetNode.originalSchema);
2367
+ this.buildNode(targetNode, targetNode.originalSchema, config);
2159
2368
  }
2160
2369
  /**
2161
2370
  * Get the current version number.
@@ -2182,17 +2391,16 @@ var SchemaRuntime = class {
2182
2391
  * // Later: unsubscribe();
2183
2392
  */
2184
2393
  subscribe(path, cb) {
2185
- const normalizedPath = normalizeRootPath(path);
2186
- if (!this.watchers[normalizedPath]) {
2187
- this.watchers[normalizedPath] = /* @__PURE__ */ new Set();
2394
+ if (!this.watchers[path]) {
2395
+ this.watchers[path] = /* @__PURE__ */ new Set();
2188
2396
  }
2189
- this.watchers[normalizedPath].add(cb);
2397
+ this.watchers[path].add(cb);
2190
2398
  return () => {
2191
- const watcherSet = this.watchers[normalizedPath];
2399
+ const watcherSet = this.watchers[path];
2192
2400
  if (watcherSet) {
2193
2401
  watcherSet.delete(cb);
2194
2402
  if (watcherSet.size === 0) {
2195
- delete this.watchers[normalizedPath];
2403
+ delete this.watchers[path];
2196
2404
  }
2197
2405
  }
2198
2406
  };
@@ -2218,8 +2426,7 @@ var SchemaRuntime = class {
2218
2426
  */
2219
2427
  notify(event) {
2220
2428
  this.version++;
2221
- const normalizedPath = normalizeRootPath(event.path);
2222
- const watchers = this.watchers[normalizedPath];
2429
+ const watchers = this.watchers[event.path];
2223
2430
  if (watchers) {
2224
2431
  for (const cb of watchers) {
2225
2432
  try {
@@ -2237,17 +2444,6 @@ var SchemaRuntime = class {
2237
2444
  }
2238
2445
  }
2239
2446
  }
2240
- /**
2241
- * Update the entire schema.
2242
- * This triggers a full rebuild of the node tree while preserving the current value.
2243
- */
2244
- setSchema(schema) {
2245
- const normalized = normalizeSchema(schema);
2246
- this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
2247
- this.root = this.createEmptyNode("", "#");
2248
- this.buildNode(this.root, this.rootSchema);
2249
- this.notify({ type: "schema", path: ROOT_PATH });
2250
- }
2251
2447
  /**
2252
2448
  * Get the value at a specific path.
2253
2449
  *
@@ -2259,24 +2455,28 @@ var SchemaRuntime = class {
2259
2455
  * runtime.getValue("/name"); // returns value at /name
2260
2456
  */
2261
2457
  getValue(path) {
2262
- const normalizedPath = normalizeRootPath(path);
2263
- if (normalizedPath === ROOT_PATH) return this.value;
2264
- return getJsonPointer(this.value, normalizedPath);
2458
+ if (path === "" || path === "#") {
2459
+ return this.value;
2460
+ }
2461
+ return getJsonPointer(this.value, path);
2265
2462
  }
2266
2463
  /**
2267
2464
  * Internal helper to set a value at a normalized path.
2268
2465
  * Handles both root and non-root paths.
2269
2466
  *
2270
- * @param normalizedPath - The normalized JSON Pointer path
2467
+ * @param path - The JSON Pointer path
2271
2468
  * @param value - The value to set
2272
2469
  * @returns true if successful, false if the path cannot be set
2273
2470
  */
2274
- setValueAtPath(normalizedPath, value) {
2275
- if (normalizedPath === ROOT_PATH) {
2471
+ setJsonPointer(path, value) {
2472
+ if (path === "" || path === "#") {
2276
2473
  this.value = value;
2277
2474
  return true;
2278
2475
  }
2279
- return setJsonPointer(this.value, normalizedPath, value);
2476
+ if (!this.value) {
2477
+ this.value = {};
2478
+ }
2479
+ return setJsonPointer(this.value, path, value);
2280
2480
  }
2281
2481
  /**
2282
2482
  * Internal method to remove a value at a path.
@@ -2290,9 +2490,9 @@ var SchemaRuntime = class {
2290
2490
  if (!success) {
2291
2491
  return false;
2292
2492
  }
2293
- const lastSlash = path.lastIndexOf("/");
2294
- const parentPath = lastSlash <= 0 ? ROOT_PATH : path.substring(0, lastSlash);
2295
- const reconcilePath = this.cleanupEmptyContainers(parentPath);
2493
+ const reconcilePath = this.cleanupEmptyContainers(
2494
+ getJsonPointerParent(path)
2495
+ );
2296
2496
  this.reconcile(reconcilePath);
2297
2497
  this.notify({ type: "value", path: reconcilePath });
2298
2498
  return true;
@@ -2305,37 +2505,29 @@ var SchemaRuntime = class {
2305
2505
  * @returns true if successful, false if the path cannot be removed
2306
2506
  */
2307
2507
  removeValue(path) {
2308
- const normalizedPath = normalizeRootPath(path);
2309
- if (normalizedPath === ROOT_PATH) {
2310
- return false;
2311
- }
2312
- const node = this.findNode(normalizedPath);
2313
- if (!node || !node.canRemove) {
2314
- return false;
2315
- }
2316
- return this.removeValueInternal(normalizedPath);
2508
+ return this.removeValueInternal(path);
2317
2509
  }
2318
2510
  /**
2319
2511
  * Clean up empty parent containers after element removal.
2320
2512
  * Recursively removes empty arrays/objects based on removeEmptyContainers option.
2321
2513
  * @param path - The path to check for empty container
2322
- * @returns The topmost parent path for reconciliation
2514
+ * @returns The topmost parent path changed
2323
2515
  */
2324
2516
  cleanupEmptyContainers(path) {
2325
2517
  const strategy = this.options.removeEmptyContainers;
2326
- if (strategy === "never" || path === ROOT_PATH) {
2327
- return path;
2518
+ if (strategy === "never") {
2519
+ return "";
2328
2520
  }
2329
- const node = this.findNode(path);
2521
+ const node = this.getNode(path);
2330
2522
  if (!node) {
2331
2523
  return path;
2332
2524
  }
2333
2525
  const value = this.getValue(path);
2334
- const isEmpty = Array.isArray(value) && value.length === 0 || value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
2526
+ const isEmpty = value === null || typeof value === "object" && Object.keys(value).length === 0;
2335
2527
  if (!isEmpty) {
2336
2528
  return path;
2337
2529
  }
2338
- const shouldRemove = strategy === "always" || strategy === "auto" && !node.isRequired;
2530
+ const shouldRemove = strategy === "auto" && !node.required;
2339
2531
  if (!shouldRemove) {
2340
2532
  return path;
2341
2533
  }
@@ -2343,24 +2535,70 @@ var SchemaRuntime = class {
2343
2535
  if (!success) {
2344
2536
  return path;
2345
2537
  }
2346
- const lastSlash = path.lastIndexOf("/");
2347
- const parentPath = lastSlash <= 0 ? ROOT_PATH : path.substring(0, lastSlash);
2348
- return this.cleanupEmptyContainers(parentPath);
2538
+ return this.cleanupEmptyContainers(getJsonPointerParent(path));
2349
2539
  }
2350
2540
  /**
2351
2541
  * Get default value for a schema, respecting autoFillDefaults option.
2352
2542
  * Falls back to 'always' strategy if configured strategy returns undefined.
2543
+ * If still undefined (e.g., schema has no type), falls back to null as a valid JSON value.
2353
2544
  */
2354
- getDefaultValueForAdd(schema) {
2355
- const strategy = this.options.autoFillDefaults;
2356
- let defaultValue;
2357
- if (strategy && strategy !== "never") {
2358
- defaultValue = getDefaultValue(schema, { strategy });
2545
+ newDefaultValue(schema, required) {
2546
+ const strategy = this.options.fillDefaults;
2547
+ if (strategy === "never") {
2548
+ return void 0;
2359
2549
  }
2360
- if (defaultValue === void 0) {
2361
- defaultValue = getDefaultValue(schema, { strategy: "always" });
2550
+ return getDefaultValue(schema, required);
2551
+ }
2552
+ addObjectProperty(parent, parentValue, propertyName, propertyValue) {
2553
+ if (!propertyName) {
2554
+ return false;
2555
+ }
2556
+ if (parentValue === void 0 || parentValue === null) {
2557
+ parentValue = {};
2558
+ this.setJsonPointer(parent.instanceLocation, parentValue);
2559
+ }
2560
+ if (typeof parentValue !== "object") {
2561
+ return false;
2562
+ }
2563
+ const { schema, keywordLocationToken, required } = getSubSchema(
2564
+ parent.schema,
2565
+ propertyName
2566
+ );
2567
+ if (!keywordLocationToken) {
2568
+ return false;
2362
2569
  }
2363
- return defaultValue;
2570
+ const defaultValue = propertyValue !== void 0 ? propertyValue : this.newDefaultValue(schema, required);
2571
+ const propertyPath = jsonPointerJoin(parent.instanceLocation, propertyName);
2572
+ const success = setJsonPointer(this.value, propertyPath, defaultValue);
2573
+ if (!success) return false;
2574
+ this.reconcile(parent.instanceLocation);
2575
+ this.notify({ type: "value", path: parent.instanceLocation });
2576
+ return true;
2577
+ }
2578
+ addArrayItem(parent, parentValue, initialValue) {
2579
+ if (parentValue === void 0 || parentValue === null) {
2580
+ parentValue = [];
2581
+ this.setJsonPointer(parent.instanceLocation, parentValue);
2582
+ }
2583
+ if (!Array.isArray(parentValue)) {
2584
+ return false;
2585
+ }
2586
+ const newItemIndex = String(parentValue.length);
2587
+ const {
2588
+ schema: subschema,
2589
+ keywordLocationToken,
2590
+ required
2591
+ } = getSubSchema(parent.schema, newItemIndex);
2592
+ if (!keywordLocationToken) {
2593
+ return false;
2594
+ }
2595
+ const defaultValue = initialValue !== void 0 ? initialValue : this.newDefaultValue(subschema, required);
2596
+ const itemPath = jsonPointerJoin(parent.instanceLocation, newItemIndex);
2597
+ const success = setJsonPointer(this.value, itemPath, defaultValue);
2598
+ if (!success) return false;
2599
+ this.reconcile(parent.instanceLocation);
2600
+ this.notify({ type: "value", path: parent.instanceLocation });
2601
+ return true;
2364
2602
  }
2365
2603
  /**
2366
2604
  * Add a new child to an array or object at the specified parent path.
@@ -2371,63 +2609,19 @@ var SchemaRuntime = class {
2371
2609
  * @param initialValue - Optional initial value to set. If not provided, uses default from schema.
2372
2610
  * @returns true if successful, false if cannot add
2373
2611
  */
2374
- addValue(parentPath, key, initialValue) {
2375
- const normalizedPath = normalizeRootPath(parentPath);
2376
- const parentNode = this.findNode(normalizedPath);
2612
+ addChild(parentPath, key, initialValue) {
2613
+ const parentNode = this.getNode(parentPath);
2377
2614
  if (!parentNode || !parentNode.canAdd) {
2378
2615
  return false;
2379
2616
  }
2380
- let parentValue = this.getValue(normalizedPath);
2381
- const parentSchema = parentNode.schema;
2617
+ const parentValue = this.getValue(parentPath);
2382
2618
  if (parentNode.type === "array") {
2383
- if (Array.isArray(parentValue)) ; else if (parentValue === void 0 || parentValue === null) {
2384
- parentValue = [];
2385
- this.setValueAtPath(normalizedPath, parentValue);
2386
- } else {
2387
- return false;
2388
- }
2619
+ return this.addArrayItem(parentNode, parentValue, initialValue);
2389
2620
  } else if (parentNode.type === "object") {
2390
- if (parentValue && typeof parentValue === "object") ; else if (parentValue === void 0 || parentValue === null) {
2391
- parentValue = {};
2392
- this.setValueAtPath(normalizedPath, parentValue);
2393
- } else {
2394
- return false;
2395
- }
2396
- }
2397
- if (parentNode.type === "array" && Array.isArray(parentValue)) {
2398
- const newIndex = parentValue.length;
2399
- const { schema: subschema, keywordLocationToken } = getSubSchema(
2400
- parentSchema,
2401
- String(newIndex)
2402
- );
2403
- if (!keywordLocationToken) {
2404
- return false;
2405
- }
2406
- const defaultValue = initialValue !== void 0 ? initialValue : this.getDefaultValueForAdd(subschema);
2407
- const itemPath = jsonPointerJoin(normalizedPath, String(newIndex));
2408
- const success = setJsonPointer(this.value, itemPath, defaultValue);
2409
- if (!success) return false;
2410
- this.reconcile(normalizedPath);
2411
- this.notify({ type: "value", path: normalizedPath });
2412
- return true;
2413
- } else if (parentNode.type === "object" && typeof parentValue === "object") {
2414
2621
  if (!key) {
2415
2622
  return false;
2416
2623
  }
2417
- const { schema: subschema, keywordLocationToken } = getSubSchema(
2418
- parentSchema,
2419
- key
2420
- );
2421
- if (!keywordLocationToken) {
2422
- return false;
2423
- }
2424
- const defaultValue = initialValue !== void 0 ? initialValue : this.getDefaultValueForAdd(subschema);
2425
- const propertyPath = jsonPointerJoin(normalizedPath, key);
2426
- const success = setJsonPointer(this.value, propertyPath, defaultValue);
2427
- if (!success) return false;
2428
- this.reconcile(normalizedPath);
2429
- this.notify({ type: "value", path: normalizedPath });
2430
- return true;
2624
+ return this.addObjectProperty(parentNode, parentValue, key, initialValue);
2431
2625
  }
2432
2626
  return false;
2433
2627
  }
@@ -2449,34 +2643,29 @@ var SchemaRuntime = class {
2449
2643
  * runtime.setValue("/optional", undefined); // remove optional field
2450
2644
  */
2451
2645
  setValue(path, value) {
2452
- const normalizedPath = normalizeRootPath(path);
2453
- if (value === void 0 && normalizedPath !== ROOT_PATH) {
2454
- const node = this.findNode(normalizedPath);
2455
- if (node && !node.isRequired) {
2456
- return this.removeValueInternal(normalizedPath);
2457
- }
2646
+ if (value === void 0) {
2647
+ return this.removeValueInternal(path);
2458
2648
  }
2459
- const success = this.setValueAtPath(normalizedPath, value);
2649
+ const success = this.setJsonPointer(path, value);
2460
2650
  if (!success) return false;
2461
- this.reconcile(normalizedPath);
2462
- this.notify({ type: "value", path: normalizedPath });
2651
+ this.reconcile(path);
2652
+ this.notify({ type: "value", path });
2463
2653
  return true;
2464
2654
  }
2465
2655
  /**
2466
- * Find the FieldNode at a specific path.
2656
+ * Get the FieldNode at a specific path.
2467
2657
  * Returns the node tree representation that includes schema, type, error, and children.
2468
2658
  *
2469
2659
  * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
2470
2660
  * @returns The FieldNode at the path, or undefined if not found
2471
2661
  *
2472
2662
  * @example
2473
- * const node = runtime.findNode("/name");
2663
+ * const node = runtime.getNode("/name");
2474
2664
  * console.log(node?.schema, node?.type, node?.error);
2475
2665
  */
2476
- findNode(path) {
2477
- const normalizedPath = normalizeRootPath(path);
2478
- if (normalizedPath === ROOT_PATH) return this.root;
2479
- const segments = parseJsonPointer(normalizedPath);
2666
+ getNode(path) {
2667
+ if (path === "") return this.root;
2668
+ const segments = parseJsonPointer(path);
2480
2669
  let current = this.root;
2481
2670
  for (const segment of segments) {
2482
2671
  if (!current?.children) return void 0;
@@ -2490,23 +2679,28 @@ var SchemaRuntime = class {
2490
2679
  return current;
2491
2680
  }
2492
2681
  findNearestExistingNode(path) {
2493
- const normalizedPath = normalizeRootPath(path);
2494
- let currentPath = normalizedPath;
2495
- while (currentPath !== ROOT_PATH) {
2496
- const node = this.findNode(currentPath);
2682
+ let currentPath = path;
2683
+ while (currentPath) {
2684
+ const node = this.getNode(currentPath);
2497
2685
  if (node) return node;
2498
- const lastSlash = currentPath.lastIndexOf("/");
2499
- currentPath = lastSlash <= 0 ? ROOT_PATH : currentPath.substring(0, lastSlash);
2686
+ currentPath = getJsonPointerParent(currentPath);
2500
2687
  }
2501
2688
  return this.root;
2502
2689
  }
2690
+ validate(path) {
2691
+ const node = this.getNode(path);
2692
+ if (node) {
2693
+ this.buildNode(node, void 0, { setActivated: true });
2694
+ }
2695
+ this.reconcile(path);
2696
+ this.notify({ type: "value", path });
2697
+ }
2503
2698
  /**
2504
2699
  * Update node dependencies when schema changes.
2505
2700
  * Unregisters old dependencies and registers new ones.
2506
2701
  */
2507
2702
  updateNodeDependencies(node, schema) {
2508
2703
  const { instanceLocation } = node;
2509
- node.originalSchema = schema;
2510
2704
  const dependencies = collectDependencies(schema, instanceLocation);
2511
2705
  for (const depPath of node.dependencies || []) {
2512
2706
  this.unregisterDependent(depPath, node);
@@ -2522,75 +2716,37 @@ var SchemaRuntime = class {
2522
2716
  * may appear when conditions change.
2523
2717
  *
2524
2718
  * Container initialization rules:
2525
- * - Root node is always considered required and will be initialized if it has defaults or required properties
2719
+ * - Required containers will be initialized if it has defaults or required properties
2526
2720
  * - Nested containers are initialized only if they are in parent's required array
2527
2721
  *
2528
2722
  * @param instanceLocation - The path to the node
2529
2723
  * @param newSchema - The new effective schema
2530
2724
  * @param type - The schema type
2531
- * @param isRequired - Whether this node is required by its parent
2725
+ * @param required - Whether this node is required by its parent
2532
2726
  */
2533
- applySchemaDefaults(instanceLocation, newSchema, type, isRequired = true) {
2534
- const strategy = this.options.autoFillDefaults;
2727
+ applySchemaDefaults(instanceLocation, newSchema, type, required = true) {
2728
+ const strategy = this.options.fillDefaults;
2535
2729
  if (strategy === "never") {
2536
2730
  return;
2537
2731
  }
2538
- let value = this.getValue(instanceLocation);
2539
- const isRoot = instanceLocation === ROOT_PATH;
2540
- if (type === "object" && newSchema.properties) {
2541
- const requiredSet = new Set(newSchema.required || []);
2542
- const hasAnyDefaults = Object.entries(newSchema.properties).some(
2543
- ([key, subschema]) => {
2544
- const defaultValue = getDefaultValue(subschema, { strategy });
2545
- if (defaultValue !== void 0) return true;
2546
- const isChildRequired = requiredSet.has(key);
2547
- if (isChildRequired && (subschema.type === "object" || subschema.type === "array")) {
2548
- return true;
2549
- }
2550
- return false;
2551
- }
2552
- );
2553
- const shouldInitialize = (value === void 0 || value === null) && hasAnyDefaults && (isRoot || isRequired);
2554
- if (shouldInitialize) {
2555
- value = {};
2556
- this.setValueAtPath(normalizeRootPath(instanceLocation), value);
2557
- }
2558
- const obj = value && typeof value === "object" ? value : null;
2559
- if (!obj) return;
2560
- for (const [key, subschema] of Object.entries(newSchema.properties)) {
2561
- const hasValue = obj[key] !== void 0;
2562
- const isChildRequired = requiredSet.has(key);
2563
- if (!hasValue) {
2564
- const defaultValue = getDefaultValue(subschema, { strategy });
2565
- if (defaultValue !== void 0) {
2566
- const propertyPath = jsonPointerJoin(instanceLocation, key);
2567
- setJsonPointer(this.value, propertyPath, defaultValue);
2568
- } else if (isChildRequired && subschema.type === "object") {
2569
- const propertyPath = jsonPointerJoin(instanceLocation, key);
2570
- setJsonPointer(this.value, propertyPath, {});
2571
- } else if (isChildRequired && subschema.type === "array") {
2572
- const propertyPath = jsonPointerJoin(instanceLocation, key);
2573
- setJsonPointer(this.value, propertyPath, []);
2574
- }
2575
- }
2576
- }
2577
- return;
2578
- }
2579
- if (type === "array" && newSchema.prefixItems) {
2580
- const arr = Array.isArray(value) ? value : null;
2581
- if (!arr) return;
2582
- for (let i = 0; i < newSchema.prefixItems.length; i++) {
2583
- if (arr[i] === void 0) {
2584
- const itemSchema = newSchema.prefixItems[i];
2585
- const defaultValue = getDefaultValue(itemSchema, { strategy });
2586
- if (defaultValue !== void 0) {
2587
- const itemPath = jsonPointerJoin(instanceLocation, String(i));
2588
- setJsonPointer(this.value, itemPath, defaultValue);
2589
- }
2590
- }
2591
- }
2732
+ const value = this.getValue(instanceLocation);
2733
+ const [newValue, changed] = applyDefaults(type, value, newSchema, required);
2734
+ if (changed) {
2735
+ this.setJsonPointer(instanceLocation, newValue);
2592
2736
  }
2593
2737
  }
2738
+ /**
2739
+ * Sort property entries by x-order.
2740
+ * Properties with lower x-order values come first.
2741
+ * Properties without x-order are placed at the end.
2742
+ */
2743
+ sortPropertiesByOrder(entries) {
2744
+ return entries.sort(([, schemaA], [, schemaB]) => {
2745
+ const orderA = typeof schemaA["x-order"] === "number" ? schemaA["x-order"] : Infinity;
2746
+ const orderB = typeof schemaB["x-order"] === "number" ? schemaB["x-order"] : Infinity;
2747
+ return orderA - orderB;
2748
+ });
2749
+ }
2594
2750
  /**
2595
2751
  * Build children for object and array nodes.
2596
2752
  * Reuses existing child nodes where possible.
@@ -2599,40 +2755,41 @@ var SchemaRuntime = class {
2599
2755
  const { keywordLocation, instanceLocation } = node;
2600
2756
  const effectiveSchema = node.schema;
2601
2757
  const type = node.type;
2758
+ const processedKeys = /* @__PURE__ */ new Set();
2759
+ const newChildren = [];
2602
2760
  const oldChildrenMap = /* @__PURE__ */ new Map();
2603
- if (node.children) {
2604
- for (const child of node.children) {
2605
- oldChildrenMap.set(child.instanceLocation, child);
2606
- }
2761
+ for (const child of node.children || []) {
2762
+ oldChildrenMap.set(child.instanceLocation, child);
2607
2763
  }
2608
- const newChildren = [];
2609
2764
  const processChild = (childKey, childSchema, childkeywordLocation, canRemove = false, isRequired = false) => {
2610
2765
  const childinstanceLocation = jsonPointerJoin(instanceLocation, childKey);
2766
+ if (processedKeys.has(childinstanceLocation)) {
2767
+ return;
2768
+ }
2769
+ processedKeys.add(childinstanceLocation);
2611
2770
  let childNode = oldChildrenMap.get(childinstanceLocation);
2612
- if (childNode) {
2613
- oldChildrenMap.delete(childinstanceLocation);
2614
- childNode.keywordLocation = childkeywordLocation;
2615
- } else {
2771
+ if (!childNode) {
2616
2772
  childNode = this.createEmptyNode(
2617
2773
  childinstanceLocation,
2618
2774
  childkeywordLocation
2619
2775
  );
2620
2776
  }
2777
+ childNode.keywordLocation = childkeywordLocation;
2778
+ childNode.originKeywordLocation = typeof childSchema["x-origin-keyword"] === "string" ? childSchema["x-origin-keyword"] : childkeywordLocation;
2621
2779
  childNode.canRemove = canRemove;
2622
- childNode.isRequired = isRequired;
2623
- this.buildNode(childNode, childSchema, { ...options, isRequired });
2780
+ childNode.required = isRequired;
2781
+ this.buildNode(childNode, childSchema, { ...options });
2624
2782
  newChildren.push(childNode);
2625
2783
  };
2626
2784
  switch (type) {
2627
2785
  case "object": {
2628
2786
  const valueKeys = value && typeof value === "object" ? Object.keys(value) : [];
2629
- const processedKeys = /* @__PURE__ */ new Set();
2630
- node.canAdd = !!effectiveSchema.additionalProperties;
2787
+ node.canAdd = !!effectiveSchema.additionalProperties || !!effectiveSchema.patternProperties;
2631
2788
  if (effectiveSchema.properties) {
2632
- for (const [key, subschema] of Object.entries(
2633
- effectiveSchema.properties
2634
- )) {
2635
- processedKeys.add(key);
2789
+ const propertyEntries = this.sortPropertiesByOrder(
2790
+ Object.entries(effectiveSchema.properties)
2791
+ );
2792
+ for (const [key, subschema] of propertyEntries) {
2636
2793
  const isChildRequired = effectiveSchema.required?.includes(key) ?? false;
2637
2794
  processChild(
2638
2795
  key,
@@ -2648,8 +2805,7 @@ var SchemaRuntime = class {
2648
2805
  effectiveSchema.patternProperties
2649
2806
  )) {
2650
2807
  for (const key of valueKeys) {
2651
- if (safeRegexTest(pattern, key) && !processedKeys.has(key)) {
2652
- processedKeys.add(key);
2808
+ if (safeRegexTest(pattern, key)) {
2653
2809
  processChild(
2654
2810
  key,
2655
2811
  subschema,
@@ -2665,16 +2821,14 @@ var SchemaRuntime = class {
2665
2821
  if (effectiveSchema.additionalProperties) {
2666
2822
  const subschema = typeof effectiveSchema.additionalProperties === "object" ? effectiveSchema.additionalProperties : {};
2667
2823
  for (const key of valueKeys) {
2668
- if (!processedKeys.has(key)) {
2669
- processChild(
2670
- key,
2671
- subschema,
2672
- `${keywordLocation}/additionalProperties`,
2673
- true,
2674
- false
2675
- // additionalProperties are never required
2676
- );
2677
- }
2824
+ processChild(
2825
+ key,
2826
+ subschema,
2827
+ `${keywordLocation}/additionalProperties`,
2828
+ true,
2829
+ false
2830
+ // additionalProperties are never required
2831
+ );
2678
2832
  }
2679
2833
  }
2680
2834
  break;
@@ -2692,7 +2846,7 @@ var SchemaRuntime = class {
2692
2846
  `${keywordLocation}/prefixItems/${i}`,
2693
2847
  false,
2694
2848
  true
2695
- // array items are always considered required
2849
+ // array prefix items are always considered required
2696
2850
  );
2697
2851
  }
2698
2852
  }
@@ -2712,8 +2866,10 @@ var SchemaRuntime = class {
2712
2866
  break;
2713
2867
  }
2714
2868
  }
2715
- for (const oldChild of oldChildrenMap.values()) {
2716
- this.unregisterNodeDependencies(oldChild);
2869
+ for (const [location, child] of oldChildrenMap) {
2870
+ if (!processedKeys.has(location)) {
2871
+ this.unregisterNodeDependencies(child);
2872
+ }
2717
2873
  }
2718
2874
  node.children = newChildren;
2719
2875
  }
@@ -2726,6 +2882,9 @@ var SchemaRuntime = class {
2726
2882
  buildNode(node, schema, options = {}) {
2727
2883
  const { keywordLocation, instanceLocation } = node;
2728
2884
  const value = this.getValue(instanceLocation);
2885
+ if (options.setActivated) {
2886
+ node.activated = true;
2887
+ }
2729
2888
  if (this.updatingNodes.has(instanceLocation)) {
2730
2889
  return;
2731
2890
  }
@@ -2737,26 +2896,25 @@ var SchemaRuntime = class {
2737
2896
  try {
2738
2897
  const schemaChanged = schema !== void 0 && !deepEqual(schema, node.originalSchema);
2739
2898
  if (schemaChanged) {
2899
+ node.originalSchema = schema;
2740
2900
  this.updateNodeDependencies(node, schema);
2741
2901
  }
2742
- const isRequired = options.isRequired ?? true;
2743
2902
  const { type, effectiveSchema, error } = resolveEffectiveSchema(
2744
2903
  this.validator,
2745
2904
  node.originalSchema,
2746
2905
  value,
2747
2906
  keywordLocation,
2748
2907
  instanceLocation,
2749
- isRequired
2908
+ node.activated
2750
2909
  );
2751
- const isInitialBuild = node.version === -1;
2752
- const effectiveSchemaChanged = isInitialBuild || !deepEqual(effectiveSchema, node.schema) || type !== node.type;
2753
- const errorChanged = isInitialBuild || !deepEqual(error, node.error);
2910
+ const effectiveSchemaChanged = !deepEqual(effectiveSchema, node.schema) || type !== node.type;
2911
+ const errorChanged = !deepEqual(error, node.error);
2754
2912
  if (effectiveSchemaChanged) {
2755
2913
  this.applySchemaDefaults(
2756
2914
  instanceLocation,
2757
2915
  effectiveSchema,
2758
2916
  type,
2759
- isRequired
2917
+ node.required
2760
2918
  );
2761
2919
  }
2762
2920
  node.schema = effectiveSchema;
@@ -2764,34 +2922,42 @@ var SchemaRuntime = class {
2764
2922
  node.error = error;
2765
2923
  node.version++;
2766
2924
  const currentValue = this.getValue(instanceLocation);
2767
- this.buildNodeChildren(node, currentValue, { ...options, updatedNodes });
2768
- if (effectiveSchemaChanged) {
2769
- this.notify({ type: "schema", path: instanceLocation });
2770
- }
2771
- if (errorChanged) {
2772
- this.notify({ type: "error", path: instanceLocation });
2773
- }
2925
+ this.buildNodeChildren(node, currentValue, {
2926
+ setActivated: options.setActivated || void 0,
2927
+ // propagate activation
2928
+ updatedNodes
2929
+ });
2774
2930
  updatedNodes.add(instanceLocation);
2775
2931
  const dependentNodes = this.dependentsMap.get(instanceLocation);
2776
2932
  if (dependentNodes) {
2777
2933
  for (const dependentNode of dependentNodes) {
2778
2934
  this.buildNode(dependentNode, void 0, {
2779
- ...options,
2935
+ setActivated: void 0,
2936
+ // do not change dependent node activation state
2780
2937
  updatedNodes
2781
2938
  });
2782
2939
  }
2783
2940
  }
2941
+ if (effectiveSchemaChanged) {
2942
+ this.notify({ type: "schema", path: instanceLocation });
2943
+ }
2944
+ if (errorChanged) {
2945
+ this.notify({ type: "error", path: instanceLocation });
2946
+ }
2784
2947
  } finally {
2785
2948
  this.updatingNodes.delete(instanceLocation);
2786
2949
  }
2787
2950
  }
2788
2951
  };
2789
2952
 
2953
+ exports.BetterNormalizer = BetterNormalizer;
2790
2954
  exports.DRAFT_URIS = DRAFT_URIS;
2955
+ exports.DraftNormalizer = DraftNormalizer;
2791
2956
  exports.MESSAGES = MESSAGES;
2792
2957
  exports.SchemaRuntime = SchemaRuntime;
2793
2958
  exports.StringFormatValidator = StringFormatValidator;
2794
2959
  exports.Validator = Validator;
2960
+ exports.applyDefaults = applyDefaults;
2795
2961
  exports.collectDependencies = collectDependencies;
2796
2962
  exports.deepEqual = deepEqual;
2797
2963
  exports.defaultErrorFormatter = defaultErrorFormatter;
@@ -2801,6 +2967,7 @@ exports.extractReferencedPaths = extractReferencedPaths;
2801
2967
  exports.get = get;
2802
2968
  exports.getDefaultValue = getDefaultValue;
2803
2969
  exports.getJsonPointer = getJsonPointer;
2970
+ exports.getJsonPointerParent = getJsonPointerParent;
2804
2971
  exports.jsonPointerEscape = jsonPointerEscape;
2805
2972
  exports.jsonPointerJoin = jsonPointerJoin;
2806
2973
  exports.jsonPointerUnescape = jsonPointerUnescape;
@@ -2812,6 +2979,7 @@ exports.resolveAbsolutePath = resolveAbsolutePath;
2812
2979
  exports.safeRegexTest = safeRegexTest;
2813
2980
  exports.setJsonPointer = setJsonPointer;
2814
2981
  exports.stringFormatValidator = stringFormatValidator;
2982
+ exports.typeNullable = typeNullable;
2815
2983
  exports.validateSchema = validateSchema;
2816
2984
  //# sourceMappingURL=index.cjs.map
2817
2985
  //# sourceMappingURL=index.cjs.map