@schema-ts/core 0.1.3 → 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) {
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,6 +1585,13 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1438
1585
  } else {
1439
1586
  type = detectSchemaType(value);
1440
1587
  }
1588
+ if (!validate) {
1589
+ return {
1590
+ effectiveSchema: effective,
1591
+ type,
1592
+ error: void 0
1593
+ };
1594
+ }
1441
1595
  const validationOutput = validator.validate(
1442
1596
  effective,
1443
1597
  value,
@@ -1471,25 +1625,42 @@ function mergeSchemaArrays(a, b) {
1471
1625
  if (b === void 0) return a;
1472
1626
  return [...a, ...b];
1473
1627
  }
1474
- function mergeSchemaMap(base, override) {
1475
- 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
+ }
1476
1643
  if (override === void 0) return base;
1477
1644
  const merged = { ...base };
1478
1645
  for (const [key, schema] of Object.entries(override)) {
1646
+ const childOrigin = overrideOrigin ? `${overrideOrigin}/${key}` : void 0;
1479
1647
  if (merged[key]) {
1480
- merged[key] = mergeSchema(merged[key], schema);
1648
+ merged[key] = mergeSchema(merged[key], schema, childOrigin);
1481
1649
  } else {
1482
- merged[key] = schema;
1650
+ merged[key] = childOrigin ? { ...schema, "x-origin-keyword": childOrigin } : schema;
1483
1651
  }
1484
1652
  }
1485
1653
  return merged;
1486
1654
  }
1487
- function mergeSchema(base, override) {
1655
+ function mergeSchema(base, override, overrideOrigin) {
1488
1656
  if (!override) return base;
1489
1657
  const merged = {
1490
1658
  ...base,
1491
1659
  ...override
1492
1660
  };
1661
+ if (overrideOrigin) {
1662
+ merged["x-origin-keyword"] = overrideOrigin;
1663
+ }
1493
1664
  if (base.$defs || override.$defs) {
1494
1665
  merged.$defs = {
1495
1666
  ...base.$defs,
@@ -1510,23 +1681,31 @@ function mergeSchema(base, override) {
1510
1681
  ...override.dependentRequired
1511
1682
  };
1512
1683
  }
1513
- 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
+ );
1514
1690
  if (mergedProperties !== void 0) {
1515
1691
  merged.properties = mergedProperties;
1516
1692
  }
1693
+ const patternPropertiesOrigin = overrideOrigin ? `${overrideOrigin}/patternProperties` : void 0;
1517
1694
  const mergedPatternProperties = mergeSchemaMap(
1518
1695
  base.patternProperties,
1519
- override.patternProperties
1696
+ override.patternProperties,
1697
+ patternPropertiesOrigin
1520
1698
  );
1521
1699
  if (mergedPatternProperties !== void 0) {
1522
1700
  merged.patternProperties = mergedPatternProperties;
1523
1701
  }
1702
+ const itemsOrigin = overrideOrigin ? `${overrideOrigin}/items` : void 0;
1524
1703
  if (base.items && override.items) {
1525
- merged.items = mergeSchema(base.items, override.items);
1704
+ merged.items = mergeSchema(base.items, override.items, itemsOrigin);
1526
1705
  } else if (base.items) {
1527
1706
  merged.items = base.items;
1528
1707
  } else if (override.items) {
1529
- merged.items = override.items;
1708
+ merged.items = itemsOrigin ? { ...override.items, "x-origin-keyword": itemsOrigin } : override.items;
1530
1709
  }
1531
1710
  if (base.prefixItems || override.prefixItems) {
1532
1711
  merged.prefixItems = [];
@@ -1537,13 +1716,17 @@ function mergeSchema(base, override) {
1537
1716
  for (let i = 0; i < len; i++) {
1538
1717
  const baseSchema = base.prefixItems?.[i];
1539
1718
  const overrideSchema = override.prefixItems?.[i];
1719
+ const prefixItemOrigin = overrideOrigin ? `${overrideOrigin}/prefixItems/${i}` : void 0;
1540
1720
  if (baseSchema && overrideSchema) {
1541
- merged.prefixItems.push(mergeSchema(baseSchema, overrideSchema));
1542
- } else {
1543
- const schema = baseSchema || overrideSchema;
1544
- if (schema) {
1545
- merged.prefixItems.push(schema);
1546
- }
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);
1547
1730
  }
1548
1731
  }
1549
1732
  }
@@ -1554,9 +1737,11 @@ function mergeSchema(base, override) {
1554
1737
  merged[keyword] = mergedArray;
1555
1738
  }
1556
1739
  }
1740
+ const dependentSchemasOrigin = overrideOrigin ? `${overrideOrigin}/dependentSchemas` : void 0;
1557
1741
  const mergedDependentSchemas = mergeSchemaMap(
1558
1742
  base.dependentSchemas,
1559
- override.dependentSchemas
1743
+ override.dependentSchemas,
1744
+ dependentSchemasOrigin
1560
1745
  );
1561
1746
  if (mergedDependentSchemas !== void 0) {
1562
1747
  merged.dependentSchemas = mergedDependentSchemas;
@@ -1735,7 +1920,8 @@ function getSubSchema(schema, key) {
1735
1920
  if (schema.properties && schema.properties[key]) {
1736
1921
  return {
1737
1922
  schema: schema.properties[key],
1738
- keywordLocationToken: `properties/${key}`
1923
+ keywordLocationToken: `properties/${key}`,
1924
+ required: schema.required?.includes(key) || false
1739
1925
  };
1740
1926
  }
1741
1927
  if (schema.patternProperties) {
@@ -1745,7 +1931,8 @@ function getSubSchema(schema, key) {
1745
1931
  if (safeRegexTest(pattern, key)) {
1746
1932
  return {
1747
1933
  schema: subschema,
1748
- keywordLocationToken: `patternProperties/${pattern}`
1934
+ keywordLocationToken: `patternProperties/${pattern}`,
1935
+ required: false
1749
1936
  };
1750
1937
  }
1751
1938
  }
@@ -1753,7 +1940,8 @@ function getSubSchema(schema, key) {
1753
1940
  if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
1754
1941
  return {
1755
1942
  schema: typeof schema.additionalProperties === "object" ? schema.additionalProperties : {},
1756
- keywordLocationToken: "additionalProperties"
1943
+ keywordLocationToken: "additionalProperties",
1944
+ required: false
1757
1945
  };
1758
1946
  }
1759
1947
  if (schema.items || schema.prefixItems) {
@@ -1762,94 +1950,124 @@ function getSubSchema(schema, key) {
1762
1950
  if (schema.prefixItems && index < schema.prefixItems.length) {
1763
1951
  return {
1764
1952
  schema: schema.prefixItems[index],
1765
- keywordLocationToken: `prefixItems/${index}`
1953
+ keywordLocationToken: `prefixItems/${index}`,
1954
+ required: true
1955
+ // prefixItems are always required
1766
1956
  };
1767
1957
  }
1768
1958
  if (schema.items) {
1769
1959
  return {
1770
1960
  schema: schema.items,
1771
- 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
1772
1965
  };
1773
1966
  }
1774
1967
  }
1775
1968
  }
1776
1969
  return {
1777
1970
  schema: {},
1778
- keywordLocationToken: ""
1971
+ keywordLocationToken: "",
1972
+ required: false
1779
1973
  };
1780
1974
  }
1781
1975
 
1782
1976
  // src/default.ts
1783
- function getDefaultValue(schema, value, strategy = "explicit") {
1784
- if (value === void 0) {
1785
- if (schema.const !== void 0) return schema.const;
1786
- if (schema.default !== void 0) return schema.default;
1787
- if (strategy === "explicit") return void 0;
1788
- }
1789
- const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1790
- if (type === "object" || !type && schema.properties) {
1791
- let obj;
1792
- if (value === void 0) {
1793
- obj = {};
1794
- } else if (typeof value === "object" && value !== null) {
1795
- obj = value;
1796
- } else {
1797
- return value;
1798
- }
1799
- if (schema.properties) {
1800
- for (const [key, subschema] of Object.entries(schema.properties)) {
1801
- if (obj[key] !== void 0) {
1802
- obj[key] = getDefaultValue(subschema, obj[key], strategy);
1803
- } else if (schema.required?.includes(key)) {
1804
- obj[key] = getDefaultValue(subschema, void 0, 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);
1805
1993
  }
1806
1994
  }
1995
+ if (Object.keys(obj).length === 0) {
1996
+ return required ? nullable ? null : {} : void 0;
1997
+ }
1998
+ return obj;
1807
1999
  }
1808
- return obj;
1809
- }
1810
- if (type === "array") {
1811
- let arr;
1812
- if (value === void 0) {
1813
- arr = [];
1814
- } else if (Array.isArray(value)) {
1815
- arr = value;
1816
- } else {
1817
- return value;
1818
- }
1819
- if (schema.prefixItems) {
1820
- schema.prefixItems.forEach((subschema, index) => {
1821
- if (index < arr.length) {
1822
- arr[index] = getDefaultValue(subschema, arr[index], strategy);
1823
- } else if (value === void 0) {
1824
- arr.push(getDefaultValue(subschema, void 0, strategy));
1825
- }
2000
+ case "array": {
2001
+ const arr = [];
2002
+ schema.prefixItems?.forEach((subschema) => {
2003
+ arr.push(getDefaultValue(subschema, true));
1826
2004
  });
1827
- }
1828
- if (value !== void 0 && schema.items) {
1829
- const startIndex = schema.prefixItems ? schema.prefixItems.length : 0;
1830
- for (let i = startIndex; i < arr.length; i++) {
1831
- arr[i] = getDefaultValue(schema.items, arr[i], strategy);
2005
+ if (arr.length === 0) {
2006
+ return required ? nullable ? null : [] : void 0;
1832
2007
  }
2008
+ return arr;
1833
2009
  }
1834
- return arr;
1835
- }
1836
- if (value !== void 0) {
1837
- return value;
1838
- }
1839
- switch (type) {
1840
2010
  case "string":
1841
- return "";
2011
+ return required ? nullable ? null : "" : void 0;
1842
2012
  case "number":
2013
+ return required ? nullable ? null : 0 : void 0;
1843
2014
  case "integer":
1844
- return 0;
2015
+ return required ? nullable ? null : 0 : void 0;
1845
2016
  case "boolean":
1846
- return false;
2017
+ return required ? nullable ? null : false : void 0;
1847
2018
  case "null":
1848
2019
  return null;
1849
2020
  default:
1850
2021
  return void 0;
1851
2022
  }
1852
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
+ }
1853
2071
 
1854
2072
  // src/dependency.ts
1855
2073
  function collectDependencies(schema, instanceLocation) {
@@ -2011,12 +2229,8 @@ function extractReferencedPaths(conditionSchema, basePath = "", depth = 0) {
2011
2229
  }
2012
2230
  }
2013
2231
  }
2014
- if (schema.dependentSchemas) {
2015
- for (const [key, subSchema] of Object.entries(schema.dependentSchemas)) {
2016
- const keyPath = basePath ? `${basePath}/${key}` : `/${key}`;
2017
- paths.push(keyPath);
2018
- paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
2019
- }
2232
+ if (schema.not) {
2233
+ paths.push(...extractReferencedPaths(schema.not, basePath, depth + 1));
2020
2234
  }
2021
2235
  if (schema.contains) {
2022
2236
  paths.push(basePath || "/");
@@ -2026,12 +2240,9 @@ function extractReferencedPaths(conditionSchema, basePath = "", depth = 0) {
2026
2240
  }
2027
2241
 
2028
2242
  // src/render.ts
2029
- var ROOT_PATH = "";
2030
- function normalizeRootPath(path) {
2031
- return path === "#" ? ROOT_PATH : path;
2032
- }
2033
2243
  var SchemaRuntime = class {
2034
2244
  validator;
2245
+ normalizer;
2035
2246
  watchers = {};
2036
2247
  globalWatchers = /* @__PURE__ */ new Set();
2037
2248
  // Reverse dependency index: path -> nodes that depend on this path's value
@@ -2058,13 +2269,30 @@ var SchemaRuntime = class {
2058
2269
  */
2059
2270
  constructor(validator, schema, value, options = {}) {
2060
2271
  this.validator = validator;
2272
+ this.options = {
2273
+ fillDefaults: "auto",
2274
+ removeEmptyContainers: "auto",
2275
+ ...options
2276
+ };
2277
+ this.normalizer = options.schemaNormalizer || new DraftNormalizer();
2061
2278
  this.value = value;
2062
- this.options = { autoFillDefaults: "explicit", ...options };
2063
- const normalized = normalizeSchema(schema);
2064
- this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
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);
2065
2294
  this.root = this.createEmptyNode("", "#");
2066
2295
  this.buildNode(this.root, this.rootSchema);
2067
- this.notify({ type: "schema", path: ROOT_PATH });
2068
2296
  }
2069
2297
  /**
2070
2298
  * Register a node as dependent on a path
@@ -2113,13 +2341,17 @@ var SchemaRuntime = class {
2113
2341
  return {
2114
2342
  type: "null",
2115
2343
  schema: {},
2344
+ // Placeholder, will be set in buildNode
2116
2345
  version: -1,
2346
+ // -1 indicates initial construction (not yet built)
2117
2347
  instanceLocation,
2118
2348
  keywordLocation,
2119
2349
  originalSchema: {},
2120
2350
  canRemove: false,
2121
2351
  canAdd: false,
2122
- children: []
2352
+ required: false,
2353
+ children: [],
2354
+ activated: false
2123
2355
  };
2124
2356
  }
2125
2357
  /**
@@ -2127,13 +2359,12 @@ var SchemaRuntime = class {
2127
2359
  * Uses findNearestExistingNode to find the target node (or parent if path doesn't exist).
2128
2360
  * Only rebuilds the affected subtree, not the entire tree.
2129
2361
  */
2130
- reconcile(path) {
2131
- const normalizedPath = normalizeRootPath(path);
2132
- const targetNode = this.findNearestExistingNode(normalizedPath);
2362
+ reconcile(path, config = {}) {
2363
+ const targetNode = this.findNearestExistingNode(path);
2133
2364
  if (!targetNode) {
2134
2365
  return;
2135
2366
  }
2136
- this.buildNode(targetNode, targetNode.originalSchema);
2367
+ this.buildNode(targetNode, targetNode.originalSchema, config);
2137
2368
  }
2138
2369
  /**
2139
2370
  * Get the current version number.
@@ -2160,17 +2391,16 @@ var SchemaRuntime = class {
2160
2391
  * // Later: unsubscribe();
2161
2392
  */
2162
2393
  subscribe(path, cb) {
2163
- const normalizedPath = normalizeRootPath(path);
2164
- if (!this.watchers[normalizedPath]) {
2165
- this.watchers[normalizedPath] = /* @__PURE__ */ new Set();
2394
+ if (!this.watchers[path]) {
2395
+ this.watchers[path] = /* @__PURE__ */ new Set();
2166
2396
  }
2167
- this.watchers[normalizedPath].add(cb);
2397
+ this.watchers[path].add(cb);
2168
2398
  return () => {
2169
- const watcherSet = this.watchers[normalizedPath];
2399
+ const watcherSet = this.watchers[path];
2170
2400
  if (watcherSet) {
2171
2401
  watcherSet.delete(cb);
2172
2402
  if (watcherSet.size === 0) {
2173
- delete this.watchers[normalizedPath];
2403
+ delete this.watchers[path];
2174
2404
  }
2175
2405
  }
2176
2406
  };
@@ -2196,8 +2426,7 @@ var SchemaRuntime = class {
2196
2426
  */
2197
2427
  notify(event) {
2198
2428
  this.version++;
2199
- const normalizedPath = normalizeRootPath(event.path);
2200
- const watchers = this.watchers[normalizedPath];
2429
+ const watchers = this.watchers[event.path];
2201
2430
  if (watchers) {
2202
2431
  for (const cb of watchers) {
2203
2432
  try {
@@ -2215,17 +2444,6 @@ var SchemaRuntime = class {
2215
2444
  }
2216
2445
  }
2217
2446
  }
2218
- /**
2219
- * Update the entire schema.
2220
- * This triggers a full rebuild of the node tree while preserving the current value.
2221
- */
2222
- setSchema(schema) {
2223
- const normalized = normalizeSchema(schema);
2224
- this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
2225
- this.root = this.createEmptyNode("", "#");
2226
- this.buildNode(this.root, this.rootSchema);
2227
- this.notify({ type: "schema", path: ROOT_PATH });
2228
- }
2229
2447
  /**
2230
2448
  * Get the value at a specific path.
2231
2449
  *
@@ -2237,48 +2455,149 @@ var SchemaRuntime = class {
2237
2455
  * runtime.getValue("/name"); // returns value at /name
2238
2456
  */
2239
2457
  getValue(path) {
2240
- const normalizedPath = normalizeRootPath(path);
2241
- if (normalizedPath === ROOT_PATH) return this.value;
2242
- return getJsonPointer(this.value, normalizedPath);
2458
+ if (path === "" || path === "#") {
2459
+ return this.value;
2460
+ }
2461
+ return getJsonPointer(this.value, path);
2243
2462
  }
2244
2463
  /**
2245
2464
  * Internal helper to set a value at a normalized path.
2246
2465
  * Handles both root and non-root paths.
2247
2466
  *
2248
- * @param normalizedPath - The normalized JSON Pointer path
2467
+ * @param path - The JSON Pointer path
2249
2468
  * @param value - The value to set
2250
2469
  * @returns true if successful, false if the path cannot be set
2251
2470
  */
2252
- setValueAtPath(normalizedPath, value) {
2253
- if (normalizedPath === ROOT_PATH) {
2471
+ setJsonPointer(path, value) {
2472
+ if (path === "" || path === "#") {
2254
2473
  this.value = value;
2255
2474
  return true;
2256
2475
  }
2257
- return setJsonPointer(this.value, normalizedPath, value);
2476
+ if (!this.value) {
2477
+ this.value = {};
2478
+ }
2479
+ return setJsonPointer(this.value, path, value);
2480
+ }
2481
+ /**
2482
+ * Internal method to remove a value at a path.
2483
+ * Shared logic for removeValue and setValue(undefined).
2484
+ * @param path - The normalized path to remove
2485
+ * @param canRemove - Whether the removal is allowed (pre-checked by caller)
2486
+ * @returns true if successful, false if removal failed
2487
+ */
2488
+ removeValueInternal(path) {
2489
+ const success = removeJsonPointer(this.value, path);
2490
+ if (!success) {
2491
+ return false;
2492
+ }
2493
+ const reconcilePath = this.cleanupEmptyContainers(
2494
+ getJsonPointerParent(path)
2495
+ );
2496
+ this.reconcile(reconcilePath);
2497
+ this.notify({ type: "value", path: reconcilePath });
2498
+ return true;
2258
2499
  }
2259
2500
  /**
2260
2501
  * Remove a node at the specified path.
2261
2502
  * This deletes the value from the data structure (array splice or object delete).
2503
+ * After removal, may also remove empty parent containers based on removeEmptyContainers option.
2262
2504
  * @param path - The path to remove
2263
2505
  * @returns true if successful, false if the path cannot be removed
2264
2506
  */
2265
2507
  removeValue(path) {
2266
- const normalizedPath = normalizeRootPath(path);
2267
- if (normalizedPath === ROOT_PATH) {
2508
+ return this.removeValueInternal(path);
2509
+ }
2510
+ /**
2511
+ * Clean up empty parent containers after element removal.
2512
+ * Recursively removes empty arrays/objects based on removeEmptyContainers option.
2513
+ * @param path - The path to check for empty container
2514
+ * @returns The topmost parent path changed
2515
+ */
2516
+ cleanupEmptyContainers(path) {
2517
+ const strategy = this.options.removeEmptyContainers;
2518
+ if (strategy === "never") {
2519
+ return "";
2520
+ }
2521
+ const node = this.getNode(path);
2522
+ if (!node) {
2523
+ return path;
2524
+ }
2525
+ const value = this.getValue(path);
2526
+ const isEmpty = value === null || typeof value === "object" && Object.keys(value).length === 0;
2527
+ if (!isEmpty) {
2528
+ return path;
2529
+ }
2530
+ const shouldRemove = strategy === "auto" && !node.required;
2531
+ if (!shouldRemove) {
2532
+ return path;
2533
+ }
2534
+ const success = removeJsonPointer(this.value, path);
2535
+ if (!success) {
2536
+ return path;
2537
+ }
2538
+ return this.cleanupEmptyContainers(getJsonPointerParent(path));
2539
+ }
2540
+ /**
2541
+ * Get default value for a schema, respecting autoFillDefaults option.
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.
2544
+ */
2545
+ newDefaultValue(schema, required) {
2546
+ const strategy = this.options.fillDefaults;
2547
+ if (strategy === "never") {
2548
+ return void 0;
2549
+ }
2550
+ return getDefaultValue(schema, required);
2551
+ }
2552
+ addObjectProperty(parent, parentValue, propertyName, propertyValue) {
2553
+ if (!propertyName) {
2268
2554
  return false;
2269
2555
  }
2270
- const node = this.findNode(normalizedPath);
2271
- if (!node || !node.canRemove) {
2556
+ if (parentValue === void 0 || parentValue === null) {
2557
+ parentValue = {};
2558
+ this.setJsonPointer(parent.instanceLocation, parentValue);
2559
+ }
2560
+ if (typeof parentValue !== "object") {
2272
2561
  return false;
2273
2562
  }
2274
- const success = removeJsonPointer(this.value, normalizedPath);
2275
- if (!success) {
2563
+ const { schema, keywordLocationToken, required } = getSubSchema(
2564
+ parent.schema,
2565
+ propertyName
2566
+ );
2567
+ if (!keywordLocationToken) {
2276
2568
  return false;
2277
2569
  }
2278
- const lastSlash = normalizedPath.lastIndexOf("/");
2279
- const parentPath = lastSlash <= 0 ? ROOT_PATH : normalizedPath.substring(0, lastSlash);
2280
- this.reconcile(parentPath);
2281
- this.notify({ type: "value", path: parentPath });
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 });
2282
2601
  return true;
2283
2602
  }
2284
2603
  /**
@@ -2290,63 +2609,19 @@ var SchemaRuntime = class {
2290
2609
  * @param initialValue - Optional initial value to set. If not provided, uses default from schema.
2291
2610
  * @returns true if successful, false if cannot add
2292
2611
  */
2293
- addValue(parentPath, key, initialValue) {
2294
- const normalizedPath = normalizeRootPath(parentPath);
2295
- const parentNode = this.findNode(normalizedPath);
2612
+ addChild(parentPath, key, initialValue) {
2613
+ const parentNode = this.getNode(parentPath);
2296
2614
  if (!parentNode || !parentNode.canAdd) {
2297
2615
  return false;
2298
2616
  }
2299
- let parentValue = this.getValue(normalizedPath);
2300
- const parentSchema = parentNode.schema;
2617
+ const parentValue = this.getValue(parentPath);
2301
2618
  if (parentNode.type === "array") {
2302
- if (Array.isArray(parentValue)) ; else if (parentValue === void 0 || parentValue === null) {
2303
- parentValue = [];
2304
- this.setValueAtPath(normalizedPath, parentValue);
2305
- } else {
2306
- return false;
2307
- }
2619
+ return this.addArrayItem(parentNode, parentValue, initialValue);
2308
2620
  } else if (parentNode.type === "object") {
2309
- if (parentValue && typeof parentValue === "object") ; else if (parentValue === void 0 || parentValue === null) {
2310
- parentValue = {};
2311
- this.setValueAtPath(normalizedPath, parentValue);
2312
- } else {
2313
- return false;
2314
- }
2315
- }
2316
- if (parentNode.type === "array" && Array.isArray(parentValue)) {
2317
- const newIndex = parentValue.length;
2318
- const { schema: subschema, keywordLocationToken } = getSubSchema(
2319
- parentSchema,
2320
- String(newIndex)
2321
- );
2322
- if (!keywordLocationToken) {
2323
- return false;
2324
- }
2325
- const defaultValue = initialValue !== void 0 ? initialValue : getDefaultValue(subschema);
2326
- const itemPath = jsonPointerJoin(normalizedPath, String(newIndex));
2327
- const success = setJsonPointer(this.value, itemPath, defaultValue);
2328
- if (!success) return false;
2329
- this.reconcile(normalizedPath);
2330
- this.notify({ type: "value", path: normalizedPath });
2331
- return true;
2332
- } else if (parentNode.type === "object" && typeof parentValue === "object") {
2333
2621
  if (!key) {
2334
2622
  return false;
2335
2623
  }
2336
- const { schema: subschema, keywordLocationToken } = getSubSchema(
2337
- parentSchema,
2338
- key
2339
- );
2340
- if (!keywordLocationToken) {
2341
- return false;
2342
- }
2343
- const defaultValue = initialValue !== void 0 ? initialValue : getDefaultValue(subschema);
2344
- const propertyPath = jsonPointerJoin(normalizedPath, key);
2345
- const success = setJsonPointer(this.value, propertyPath, defaultValue);
2346
- if (!success) return false;
2347
- this.reconcile(normalizedPath);
2348
- this.notify({ type: "value", path: normalizedPath });
2349
- return true;
2624
+ return this.addObjectProperty(parentNode, parentValue, key, initialValue);
2350
2625
  }
2351
2626
  return false;
2352
2627
  }
@@ -2355,37 +2630,42 @@ var SchemaRuntime = class {
2355
2630
  * Creates intermediate containers (objects/arrays) as needed.
2356
2631
  * Triggers reconciliation and notifies subscribers.
2357
2632
  *
2633
+ * When value is undefined and the field is not required, the field will be
2634
+ * removed from the parent container (similar to removeValue behavior).
2635
+ *
2358
2636
  * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
2359
- * @param value - The new value to set
2637
+ * @param value - The new value to set. If undefined and field is optional, removes the field.
2360
2638
  * @returns true if successful, false if the path cannot be set
2361
2639
  *
2362
2640
  * @example
2363
2641
  * runtime.setValue("/name", "Bob"); // set name to "Bob"
2364
2642
  * runtime.setValue("", { name: "Alice" }); // replace entire root value
2643
+ * runtime.setValue("/optional", undefined); // remove optional field
2365
2644
  */
2366
2645
  setValue(path, value) {
2367
- const normalizedPath = normalizeRootPath(path);
2368
- const success = this.setValueAtPath(normalizedPath, value);
2646
+ if (value === void 0) {
2647
+ return this.removeValueInternal(path);
2648
+ }
2649
+ const success = this.setJsonPointer(path, value);
2369
2650
  if (!success) return false;
2370
- this.reconcile(normalizedPath);
2371
- this.notify({ type: "value", path: normalizedPath });
2651
+ this.reconcile(path);
2652
+ this.notify({ type: "value", path });
2372
2653
  return true;
2373
2654
  }
2374
2655
  /**
2375
- * Find the FieldNode at a specific path.
2656
+ * Get the FieldNode at a specific path.
2376
2657
  * Returns the node tree representation that includes schema, type, error, and children.
2377
2658
  *
2378
2659
  * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
2379
2660
  * @returns The FieldNode at the path, or undefined if not found
2380
2661
  *
2381
2662
  * @example
2382
- * const node = runtime.findNode("/name");
2663
+ * const node = runtime.getNode("/name");
2383
2664
  * console.log(node?.schema, node?.type, node?.error);
2384
2665
  */
2385
- findNode(path) {
2386
- const normalizedPath = normalizeRootPath(path);
2387
- if (normalizedPath === ROOT_PATH) return this.root;
2388
- const segments = parseJsonPointer(normalizedPath);
2666
+ getNode(path) {
2667
+ if (path === "") return this.root;
2668
+ const segments = parseJsonPointer(path);
2389
2669
  let current = this.root;
2390
2670
  for (const segment of segments) {
2391
2671
  if (!current?.children) return void 0;
@@ -2399,23 +2679,28 @@ var SchemaRuntime = class {
2399
2679
  return current;
2400
2680
  }
2401
2681
  findNearestExistingNode(path) {
2402
- const normalizedPath = normalizeRootPath(path);
2403
- let currentPath = normalizedPath;
2404
- while (currentPath !== ROOT_PATH) {
2405
- const node = this.findNode(currentPath);
2682
+ let currentPath = path;
2683
+ while (currentPath) {
2684
+ const node = this.getNode(currentPath);
2406
2685
  if (node) return node;
2407
- const lastSlash = currentPath.lastIndexOf("/");
2408
- currentPath = lastSlash <= 0 ? ROOT_PATH : currentPath.substring(0, lastSlash);
2686
+ currentPath = getJsonPointerParent(currentPath);
2409
2687
  }
2410
2688
  return this.root;
2411
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
+ }
2412
2698
  /**
2413
2699
  * Update node dependencies when schema changes.
2414
2700
  * Unregisters old dependencies and registers new ones.
2415
2701
  */
2416
2702
  updateNodeDependencies(node, schema) {
2417
2703
  const { instanceLocation } = node;
2418
- node.originalSchema = schema;
2419
2704
  const dependencies = collectDependencies(schema, instanceLocation);
2420
2705
  for (const depPath of node.dependencies || []) {
2421
2706
  this.unregisterDependent(depPath, node);
@@ -2430,53 +2715,38 @@ var SchemaRuntime = class {
2430
2715
  * This handles cases like if-then-else where new properties with defaults
2431
2716
  * may appear when conditions change.
2432
2717
  *
2718
+ * Container initialization rules:
2719
+ * - Required containers will be initialized if it has defaults or required properties
2720
+ * - Nested containers are initialized only if they are in parent's required array
2721
+ *
2433
2722
  * @param instanceLocation - The path to the node
2434
2723
  * @param newSchema - The new effective schema
2435
2724
  * @param type - The schema type
2725
+ * @param required - Whether this node is required by its parent
2436
2726
  */
2437
- applySchemaDefaults(instanceLocation, newSchema, type) {
2438
- if (this.options.autoFillDefaults === "never") {
2727
+ applySchemaDefaults(instanceLocation, newSchema, type, required = true) {
2728
+ const strategy = this.options.fillDefaults;
2729
+ if (strategy === "never") {
2439
2730
  return;
2440
2731
  }
2441
2732
  const value = this.getValue(instanceLocation);
2442
- if (type === "object" && newSchema.properties) {
2443
- const obj = value && typeof value === "object" ? value : null;
2444
- if (!obj) return;
2445
- for (const [key, subschema] of Object.entries(newSchema.properties)) {
2446
- const hasValue = obj[key] !== void 0;
2447
- if (!hasValue) {
2448
- const defaultValue = getDefaultValue(
2449
- subschema,
2450
- void 0,
2451
- this.options.autoFillDefaults
2452
- );
2453
- if (defaultValue !== void 0) {
2454
- const propertyPath = jsonPointerJoin(instanceLocation, key);
2455
- setJsonPointer(this.value, propertyPath, defaultValue);
2456
- }
2457
- }
2458
- }
2459
- return;
2460
- }
2461
- if (type === "array" && newSchema.prefixItems) {
2462
- const arr = Array.isArray(value) ? value : null;
2463
- if (!arr) return;
2464
- for (let i = 0; i < newSchema.prefixItems.length; i++) {
2465
- if (arr[i] === void 0) {
2466
- const itemSchema = newSchema.prefixItems[i];
2467
- const defaultValue = getDefaultValue(
2468
- itemSchema,
2469
- void 0,
2470
- this.options.autoFillDefaults
2471
- );
2472
- if (defaultValue !== void 0) {
2473
- const itemPath = jsonPointerJoin(instanceLocation, String(i));
2474
- setJsonPointer(this.value, itemPath, defaultValue);
2475
- }
2476
- }
2477
- }
2733
+ const [newValue, changed] = applyDefaults(type, value, newSchema, required);
2734
+ if (changed) {
2735
+ this.setJsonPointer(instanceLocation, newValue);
2478
2736
  }
2479
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
+ }
2480
2750
  /**
2481
2751
  * Build children for object and array nodes.
2482
2752
  * Reuses existing child nodes where possible.
@@ -2485,44 +2755,48 @@ var SchemaRuntime = class {
2485
2755
  const { keywordLocation, instanceLocation } = node;
2486
2756
  const effectiveSchema = node.schema;
2487
2757
  const type = node.type;
2758
+ const processedKeys = /* @__PURE__ */ new Set();
2759
+ const newChildren = [];
2488
2760
  const oldChildrenMap = /* @__PURE__ */ new Map();
2489
- if (node.children) {
2490
- for (const child of node.children) {
2491
- oldChildrenMap.set(child.instanceLocation, child);
2492
- }
2761
+ for (const child of node.children || []) {
2762
+ oldChildrenMap.set(child.instanceLocation, child);
2493
2763
  }
2494
- const newChildren = [];
2495
- const processChild = (childKey, childSchema, childkeywordLocation, canRemove = false) => {
2764
+ const processChild = (childKey, childSchema, childkeywordLocation, canRemove = false, isRequired = false) => {
2496
2765
  const childinstanceLocation = jsonPointerJoin(instanceLocation, childKey);
2766
+ if (processedKeys.has(childinstanceLocation)) {
2767
+ return;
2768
+ }
2769
+ processedKeys.add(childinstanceLocation);
2497
2770
  let childNode = oldChildrenMap.get(childinstanceLocation);
2498
- if (childNode) {
2499
- oldChildrenMap.delete(childinstanceLocation);
2500
- childNode.keywordLocation = childkeywordLocation;
2501
- } else {
2771
+ if (!childNode) {
2502
2772
  childNode = this.createEmptyNode(
2503
2773
  childinstanceLocation,
2504
2774
  childkeywordLocation
2505
2775
  );
2506
2776
  }
2777
+ childNode.keywordLocation = childkeywordLocation;
2778
+ childNode.originKeywordLocation = typeof childSchema["x-origin-keyword"] === "string" ? childSchema["x-origin-keyword"] : childkeywordLocation;
2507
2779
  childNode.canRemove = canRemove;
2508
- this.buildNode(childNode, childSchema, options);
2780
+ childNode.required = isRequired;
2781
+ this.buildNode(childNode, childSchema, { ...options });
2509
2782
  newChildren.push(childNode);
2510
2783
  };
2511
2784
  switch (type) {
2512
2785
  case "object": {
2513
2786
  const valueKeys = value && typeof value === "object" ? Object.keys(value) : [];
2514
- const processedKeys = /* @__PURE__ */ new Set();
2515
- node.canAdd = !!effectiveSchema.additionalProperties;
2787
+ node.canAdd = !!effectiveSchema.additionalProperties || !!effectiveSchema.patternProperties;
2516
2788
  if (effectiveSchema.properties) {
2517
- for (const [key, subschema] of Object.entries(
2518
- effectiveSchema.properties
2519
- )) {
2520
- processedKeys.add(key);
2789
+ const propertyEntries = this.sortPropertiesByOrder(
2790
+ Object.entries(effectiveSchema.properties)
2791
+ );
2792
+ for (const [key, subschema] of propertyEntries) {
2793
+ const isChildRequired = effectiveSchema.required?.includes(key) ?? false;
2521
2794
  processChild(
2522
2795
  key,
2523
2796
  subschema,
2524
2797
  `${keywordLocation}/properties/${key}`,
2525
- false
2798
+ false,
2799
+ isChildRequired
2526
2800
  );
2527
2801
  }
2528
2802
  }
@@ -2531,13 +2805,14 @@ var SchemaRuntime = class {
2531
2805
  effectiveSchema.patternProperties
2532
2806
  )) {
2533
2807
  for (const key of valueKeys) {
2534
- if (safeRegexTest(pattern, key) && !processedKeys.has(key)) {
2535
- processedKeys.add(key);
2808
+ if (safeRegexTest(pattern, key)) {
2536
2809
  processChild(
2537
2810
  key,
2538
2811
  subschema,
2539
2812
  `${keywordLocation}/patternProperties/${jsonPointerEscape(pattern)}`,
2540
- true
2813
+ true,
2814
+ false
2815
+ // patternProperties are never required
2541
2816
  );
2542
2817
  }
2543
2818
  }
@@ -2546,14 +2821,14 @@ var SchemaRuntime = class {
2546
2821
  if (effectiveSchema.additionalProperties) {
2547
2822
  const subschema = typeof effectiveSchema.additionalProperties === "object" ? effectiveSchema.additionalProperties : {};
2548
2823
  for (const key of valueKeys) {
2549
- if (!processedKeys.has(key)) {
2550
- processChild(
2551
- key,
2552
- subschema,
2553
- `${keywordLocation}/additionalProperties`,
2554
- true
2555
- );
2556
- }
2824
+ processChild(
2825
+ key,
2826
+ subschema,
2827
+ `${keywordLocation}/additionalProperties`,
2828
+ true,
2829
+ false
2830
+ // additionalProperties are never required
2831
+ );
2557
2832
  }
2558
2833
  }
2559
2834
  break;
@@ -2569,7 +2844,9 @@ var SchemaRuntime = class {
2569
2844
  String(i),
2570
2845
  effectiveSchema.prefixItems[i],
2571
2846
  `${keywordLocation}/prefixItems/${i}`,
2572
- false
2847
+ false,
2848
+ true
2849
+ // array prefix items are always considered required
2573
2850
  );
2574
2851
  }
2575
2852
  }
@@ -2579,7 +2856,9 @@ var SchemaRuntime = class {
2579
2856
  String(i),
2580
2857
  effectiveSchema.items,
2581
2858
  `${keywordLocation}/items`,
2859
+ true,
2582
2860
  true
2861
+ // array items are always considered required
2583
2862
  );
2584
2863
  }
2585
2864
  }
@@ -2587,8 +2866,10 @@ var SchemaRuntime = class {
2587
2866
  break;
2588
2867
  }
2589
2868
  }
2590
- for (const oldChild of oldChildrenMap.values()) {
2591
- this.unregisterNodeDependencies(oldChild);
2869
+ for (const [location, child] of oldChildrenMap) {
2870
+ if (!processedKeys.has(location)) {
2871
+ this.unregisterNodeDependencies(child);
2872
+ }
2592
2873
  }
2593
2874
  node.children = newChildren;
2594
2875
  }
@@ -2596,10 +2877,14 @@ var SchemaRuntime = class {
2596
2877
  * Build/update a FieldNode in place.
2597
2878
  * Updates the node's schema, type, error, and children based on the current value.
2598
2879
  * @param schema - Optional. If provided, updates node.originalSchema. Otherwise uses existing.
2880
+ * @param isRequired - Whether this node is required by its parent schema.
2599
2881
  */
2600
2882
  buildNode(node, schema, options = {}) {
2601
2883
  const { keywordLocation, instanceLocation } = node;
2602
2884
  const value = this.getValue(instanceLocation);
2885
+ if (options.setActivated) {
2886
+ node.activated = true;
2887
+ }
2603
2888
  if (this.updatingNodes.has(instanceLocation)) {
2604
2889
  return;
2605
2890
  }
@@ -2611,6 +2896,7 @@ var SchemaRuntime = class {
2611
2896
  try {
2612
2897
  const schemaChanged = schema !== void 0 && !deepEqual(schema, node.originalSchema);
2613
2898
  if (schemaChanged) {
2899
+ node.originalSchema = schema;
2614
2900
  this.updateNodeDependencies(node, schema);
2615
2901
  }
2616
2902
  const { type, effectiveSchema, error } = resolveEffectiveSchema(
@@ -2618,46 +2904,60 @@ var SchemaRuntime = class {
2618
2904
  node.originalSchema,
2619
2905
  value,
2620
2906
  keywordLocation,
2621
- instanceLocation
2907
+ instanceLocation,
2908
+ node.activated
2622
2909
  );
2623
2910
  const effectiveSchemaChanged = !deepEqual(effectiveSchema, node.schema) || type !== node.type;
2624
2911
  const errorChanged = !deepEqual(error, node.error);
2625
2912
  if (effectiveSchemaChanged) {
2626
- this.applySchemaDefaults(instanceLocation, effectiveSchema, type);
2913
+ this.applySchemaDefaults(
2914
+ instanceLocation,
2915
+ effectiveSchema,
2916
+ type,
2917
+ node.required
2918
+ );
2627
2919
  }
2628
2920
  node.schema = effectiveSchema;
2629
2921
  node.type = type;
2630
2922
  node.error = error;
2631
2923
  node.version++;
2632
2924
  const currentValue = this.getValue(instanceLocation);
2633
- this.buildNodeChildren(node, currentValue, { ...options, updatedNodes });
2634
- if (effectiveSchemaChanged) {
2635
- this.notify({ type: "schema", path: instanceLocation });
2636
- }
2637
- if (errorChanged) {
2638
- this.notify({ type: "error", path: instanceLocation });
2639
- }
2925
+ this.buildNodeChildren(node, currentValue, {
2926
+ setActivated: options.setActivated || void 0,
2927
+ // propagate activation
2928
+ updatedNodes
2929
+ });
2640
2930
  updatedNodes.add(instanceLocation);
2641
2931
  const dependentNodes = this.dependentsMap.get(instanceLocation);
2642
2932
  if (dependentNodes) {
2643
2933
  for (const dependentNode of dependentNodes) {
2644
2934
  this.buildNode(dependentNode, void 0, {
2645
- ...options,
2935
+ setActivated: void 0,
2936
+ // do not change dependent node activation state
2646
2937
  updatedNodes
2647
2938
  });
2648
2939
  }
2649
2940
  }
2941
+ if (effectiveSchemaChanged) {
2942
+ this.notify({ type: "schema", path: instanceLocation });
2943
+ }
2944
+ if (errorChanged) {
2945
+ this.notify({ type: "error", path: instanceLocation });
2946
+ }
2650
2947
  } finally {
2651
2948
  this.updatingNodes.delete(instanceLocation);
2652
2949
  }
2653
2950
  }
2654
2951
  };
2655
2952
 
2953
+ exports.BetterNormalizer = BetterNormalizer;
2656
2954
  exports.DRAFT_URIS = DRAFT_URIS;
2955
+ exports.DraftNormalizer = DraftNormalizer;
2657
2956
  exports.MESSAGES = MESSAGES;
2658
2957
  exports.SchemaRuntime = SchemaRuntime;
2659
2958
  exports.StringFormatValidator = StringFormatValidator;
2660
2959
  exports.Validator = Validator;
2960
+ exports.applyDefaults = applyDefaults;
2661
2961
  exports.collectDependencies = collectDependencies;
2662
2962
  exports.deepEqual = deepEqual;
2663
2963
  exports.defaultErrorFormatter = defaultErrorFormatter;
@@ -2667,6 +2967,7 @@ exports.extractReferencedPaths = extractReferencedPaths;
2667
2967
  exports.get = get;
2668
2968
  exports.getDefaultValue = getDefaultValue;
2669
2969
  exports.getJsonPointer = getJsonPointer;
2970
+ exports.getJsonPointerParent = getJsonPointerParent;
2670
2971
  exports.jsonPointerEscape = jsonPointerEscape;
2671
2972
  exports.jsonPointerJoin = jsonPointerJoin;
2672
2973
  exports.jsonPointerUnescape = jsonPointerUnescape;
@@ -2678,6 +2979,7 @@ exports.resolveAbsolutePath = resolveAbsolutePath;
2678
2979
  exports.safeRegexTest = safeRegexTest;
2679
2980
  exports.setJsonPointer = setJsonPointer;
2680
2981
  exports.stringFormatValidator = stringFormatValidator;
2982
+ exports.typeNullable = typeNullable;
2681
2983
  exports.validateSchema = validateSchema;
2682
2984
  //# sourceMappingURL=index.cjs.map
2683
2985
  //# sourceMappingURL=index.cjs.map