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