@bluprynt/forms-core 3.0.0 → 4.0.1
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 +281 -9
- package/dist/index.d.cts +15 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +15 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +281 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1516,6 +1516,119 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1516
1516
|
"contentItem": { "oneOf": [{ "$ref": "#/$defs/fieldItem" }, { "$ref": "#/$defs/section" }] }
|
|
1517
1517
|
}
|
|
1518
1518
|
});
|
|
1519
|
+
const itemRequiredProperties = new Map([
|
|
1520
|
+
["string", new Set([
|
|
1521
|
+
"id",
|
|
1522
|
+
"type",
|
|
1523
|
+
"label"
|
|
1524
|
+
])],
|
|
1525
|
+
["number", new Set([
|
|
1526
|
+
"id",
|
|
1527
|
+
"type",
|
|
1528
|
+
"label"
|
|
1529
|
+
])],
|
|
1530
|
+
["boolean", new Set([
|
|
1531
|
+
"id",
|
|
1532
|
+
"type",
|
|
1533
|
+
"label"
|
|
1534
|
+
])],
|
|
1535
|
+
["date", new Set([
|
|
1536
|
+
"id",
|
|
1537
|
+
"type",
|
|
1538
|
+
"label"
|
|
1539
|
+
])],
|
|
1540
|
+
["select", new Set([
|
|
1541
|
+
"id",
|
|
1542
|
+
"type",
|
|
1543
|
+
"label",
|
|
1544
|
+
"options"
|
|
1545
|
+
])],
|
|
1546
|
+
["array", new Set([
|
|
1547
|
+
"id",
|
|
1548
|
+
"type",
|
|
1549
|
+
"label",
|
|
1550
|
+
"item"
|
|
1551
|
+
])],
|
|
1552
|
+
["file", new Set([
|
|
1553
|
+
"id",
|
|
1554
|
+
"type",
|
|
1555
|
+
"label"
|
|
1556
|
+
])],
|
|
1557
|
+
["section", new Set([
|
|
1558
|
+
"id",
|
|
1559
|
+
"type",
|
|
1560
|
+
"title",
|
|
1561
|
+
"content"
|
|
1562
|
+
])]
|
|
1563
|
+
]);
|
|
1564
|
+
const itemAllowedProperties = new Map([
|
|
1565
|
+
["string", new Set([
|
|
1566
|
+
"id",
|
|
1567
|
+
"type",
|
|
1568
|
+
"label",
|
|
1569
|
+
"description",
|
|
1570
|
+
"condition",
|
|
1571
|
+
"validation"
|
|
1572
|
+
])],
|
|
1573
|
+
["number", new Set([
|
|
1574
|
+
"id",
|
|
1575
|
+
"type",
|
|
1576
|
+
"label",
|
|
1577
|
+
"description",
|
|
1578
|
+
"condition",
|
|
1579
|
+
"validation"
|
|
1580
|
+
])],
|
|
1581
|
+
["boolean", new Set([
|
|
1582
|
+
"id",
|
|
1583
|
+
"type",
|
|
1584
|
+
"label",
|
|
1585
|
+
"description",
|
|
1586
|
+
"condition",
|
|
1587
|
+
"validation"
|
|
1588
|
+
])],
|
|
1589
|
+
["date", new Set([
|
|
1590
|
+
"id",
|
|
1591
|
+
"type",
|
|
1592
|
+
"label",
|
|
1593
|
+
"description",
|
|
1594
|
+
"condition",
|
|
1595
|
+
"validation"
|
|
1596
|
+
])],
|
|
1597
|
+
["select", new Set([
|
|
1598
|
+
"id",
|
|
1599
|
+
"type",
|
|
1600
|
+
"label",
|
|
1601
|
+
"description",
|
|
1602
|
+
"condition",
|
|
1603
|
+
"options",
|
|
1604
|
+
"validation"
|
|
1605
|
+
])],
|
|
1606
|
+
["array", new Set([
|
|
1607
|
+
"id",
|
|
1608
|
+
"type",
|
|
1609
|
+
"label",
|
|
1610
|
+
"description",
|
|
1611
|
+
"condition",
|
|
1612
|
+
"item",
|
|
1613
|
+
"validation"
|
|
1614
|
+
])],
|
|
1615
|
+
["file", new Set([
|
|
1616
|
+
"id",
|
|
1617
|
+
"type",
|
|
1618
|
+
"label",
|
|
1619
|
+
"description",
|
|
1620
|
+
"condition",
|
|
1621
|
+
"validation"
|
|
1622
|
+
])],
|
|
1623
|
+
["section", new Set([
|
|
1624
|
+
"id",
|
|
1625
|
+
"type",
|
|
1626
|
+
"title",
|
|
1627
|
+
"description",
|
|
1628
|
+
"condition",
|
|
1629
|
+
"content"
|
|
1630
|
+
])]
|
|
1631
|
+
]);
|
|
1519
1632
|
/**
|
|
1520
1633
|
* Validates form definitions at both the structural (JSON Schema) and
|
|
1521
1634
|
* semantic levels.
|
|
@@ -1549,18 +1662,177 @@ var FormDefinitionValidator = class {
|
|
|
1549
1662
|
*/
|
|
1550
1663
|
validateSchema(input) {
|
|
1551
1664
|
if (validateFn(input)) return [];
|
|
1552
|
-
return (validateFn.errors ?? [])
|
|
1665
|
+
return this.formatSchemaErrors(validateFn.errors ?? [], input);
|
|
1666
|
+
}
|
|
1667
|
+
formatSchemaErrors(errors, input) {
|
|
1668
|
+
const issues = this.collectSchemaIssues(errors, input);
|
|
1669
|
+
const dedupedIssues = this.deduplicateSchemaIssues(issues);
|
|
1670
|
+
const specificIssuePaths = dedupedIssues.filter((issue) => issue.keyword !== "oneOf").map((issue) => issue.path);
|
|
1671
|
+
return dedupedIssues.filter((issue) => !this.isRedundantOneOfIssue(issue, specificIssuePaths)).map((issue) => ({
|
|
1672
|
+
code: "SCHEMA_INVALID",
|
|
1673
|
+
message: issue.message,
|
|
1674
|
+
params: {
|
|
1675
|
+
path: issue.path,
|
|
1676
|
+
keyword: issue.keyword,
|
|
1677
|
+
...issue.property ? { property: issue.property } : {}
|
|
1678
|
+
}
|
|
1679
|
+
}));
|
|
1680
|
+
}
|
|
1681
|
+
collectSchemaIssues(errors, input) {
|
|
1682
|
+
const additionalByPath = /* @__PURE__ */ new Map();
|
|
1683
|
+
const issues = [];
|
|
1684
|
+
for (const err of errors) {
|
|
1553
1685
|
const path = err.instancePath || "/";
|
|
1554
|
-
|
|
1555
|
-
if (err.keyword === "additionalProperties")
|
|
1556
|
-
|
|
1557
|
-
|
|
1686
|
+
if (!this.shouldKeepSchemaError(err, input)) continue;
|
|
1687
|
+
if (err.keyword === "additionalProperties") {
|
|
1688
|
+
const property = err.params.additionalProperty;
|
|
1689
|
+
if (!property) continue;
|
|
1690
|
+
if (!this.shouldKeepAdditionalPropertyError(path, property, input)) continue;
|
|
1691
|
+
const properties = additionalByPath.get(path) ?? /* @__PURE__ */ new Set();
|
|
1692
|
+
properties.add(property);
|
|
1693
|
+
additionalByPath.set(path, properties);
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
issues.push(this.formatSchemaIssue(err));
|
|
1697
|
+
}
|
|
1698
|
+
for (const [path, properties] of additionalByPath) issues.push(this.formatAdditionalPropertiesIssue(path, [...properties].sort()));
|
|
1699
|
+
return issues;
|
|
1700
|
+
}
|
|
1701
|
+
shouldKeepSchemaError(err, input) {
|
|
1702
|
+
if (err.keyword === "const" && this.getLastPathSegment(err.instancePath) === "type") {
|
|
1703
|
+
const value = this.getValueAtPath(input, this.getParentPath(err.instancePath));
|
|
1704
|
+
return !(this.isRecord(value) && typeof value.type === "string" && itemAllowedProperties.has(value.type));
|
|
1705
|
+
}
|
|
1706
|
+
if (err.keyword !== "required") return true;
|
|
1707
|
+
const missingProperty = err.params.missingProperty;
|
|
1708
|
+
if (!missingProperty) return true;
|
|
1709
|
+
const value = this.getValueAtPath(input, err.instancePath);
|
|
1710
|
+
if (this.isRecord(value) && value.type === void 0 && this.isContentItemPath(err.instancePath)) return missingProperty === "id" || missingProperty === "type";
|
|
1711
|
+
if (!this.isRecord(value) || typeof value.type !== "string") return true;
|
|
1712
|
+
const requiredProperties = itemRequiredProperties.get(value.type);
|
|
1713
|
+
return requiredProperties ? requiredProperties.has(missingProperty) : true;
|
|
1714
|
+
}
|
|
1715
|
+
shouldKeepAdditionalPropertyError(path, property, input) {
|
|
1716
|
+
const value = this.getValueAtPath(input, path);
|
|
1717
|
+
if (!this.isRecord(value) || typeof value.type !== "string") return true;
|
|
1718
|
+
const allowedProperties = itemAllowedProperties.get(value.type);
|
|
1719
|
+
return allowedProperties ? !allowedProperties.has(property) : true;
|
|
1720
|
+
}
|
|
1721
|
+
formatSchemaIssue(err) {
|
|
1722
|
+
const path = err.instancePath || "/";
|
|
1723
|
+
switch (err.keyword) {
|
|
1724
|
+
case "required": {
|
|
1725
|
+
const property = err.params.missingProperty ?? "unknown";
|
|
1726
|
+
return {
|
|
1727
|
+
path,
|
|
1728
|
+
keyword: err.keyword,
|
|
1729
|
+
property,
|
|
1730
|
+
message: `${this.formatPath(path)} is missing required property "${property}".`
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
case "const": {
|
|
1734
|
+
const propertyPath = this.formatPath(path);
|
|
1735
|
+
const parentPath = this.getParentPath(path);
|
|
1736
|
+
const property = this.getLastPathSegment(path);
|
|
1737
|
+
return {
|
|
1738
|
+
path,
|
|
1739
|
+
keyword: err.keyword,
|
|
1740
|
+
property,
|
|
1741
|
+
message: property === "type" ? `${this.formatPath(parentPath)} has an invalid type.` : `${propertyPath} has an invalid value.`
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
case "type": {
|
|
1745
|
+
const params = err.params;
|
|
1746
|
+
return {
|
|
1747
|
+
path,
|
|
1748
|
+
keyword: err.keyword,
|
|
1749
|
+
property: this.getLastPathSegment(path),
|
|
1750
|
+
message: `${this.formatPath(path)} must be ${this.formatArticle(params.type)} ${params.type ?? "valid value"}.`
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
case "oneOf": return {
|
|
1754
|
+
path,
|
|
1755
|
+
keyword: err.keyword,
|
|
1756
|
+
message: `${this.formatPath(path)} is invalid.`
|
|
1558
1757
|
};
|
|
1559
|
-
return {
|
|
1560
|
-
|
|
1561
|
-
|
|
1758
|
+
default: return {
|
|
1759
|
+
path,
|
|
1760
|
+
keyword: err.keyword,
|
|
1761
|
+
property: this.getLastPathSegment(path),
|
|
1762
|
+
message: `${this.formatPath(path)} ${err.message ?? "is invalid"}.`
|
|
1562
1763
|
};
|
|
1563
|
-
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
formatAdditionalPropertiesIssue(path, properties) {
|
|
1767
|
+
const propertyList = properties.map((property) => `"${property}"`).join(", ");
|
|
1768
|
+
const noun = properties.length === 1 ? "property" : "properties";
|
|
1769
|
+
return {
|
|
1770
|
+
path,
|
|
1771
|
+
keyword: "additionalProperties",
|
|
1772
|
+
property: properties.join(","),
|
|
1773
|
+
message: `${this.formatPath(path)} has unsupported ${noun}: ${propertyList}.`
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
deduplicateSchemaIssues(issues) {
|
|
1777
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1778
|
+
const result = [];
|
|
1779
|
+
for (const issue of issues) {
|
|
1780
|
+
const key = `${issue.path}:${issue.keyword}:${issue.property ?? ""}:${issue.message}`;
|
|
1781
|
+
if (seen.has(key)) continue;
|
|
1782
|
+
seen.add(key);
|
|
1783
|
+
result.push(issue);
|
|
1784
|
+
}
|
|
1785
|
+
return result;
|
|
1786
|
+
}
|
|
1787
|
+
isRedundantOneOfIssue(issue, specificIssuePaths) {
|
|
1788
|
+
if (issue.keyword !== "oneOf") return false;
|
|
1789
|
+
return specificIssuePaths.some((path) => path === issue.path || path.startsWith(`${issue.path}/`));
|
|
1790
|
+
}
|
|
1791
|
+
formatPath(path) {
|
|
1792
|
+
if (!path || path === "/") return "Form definition";
|
|
1793
|
+
const segments = path.split("/").filter(Boolean);
|
|
1794
|
+
const parts = [];
|
|
1795
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
1796
|
+
const segment = segments[index];
|
|
1797
|
+
const nextSegment = segments[index + 1];
|
|
1798
|
+
if (segment === void 0) continue;
|
|
1799
|
+
if (segment === "content" && nextSegment !== void 0 && /^\d+$/.test(nextSegment)) {
|
|
1800
|
+
parts.push(`${parts.length === 0 ? "Content" : "content"} item ${Number(nextSegment) + 1}`);
|
|
1801
|
+
index += 1;
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
if (segment === "validation" && parts.length > 0) {
|
|
1805
|
+
parts[parts.length - 1] = `${parts[parts.length - 1]} validation`;
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
parts.push(segment);
|
|
1809
|
+
}
|
|
1810
|
+
return parts.join(" > ");
|
|
1811
|
+
}
|
|
1812
|
+
getValueAtPath(input, path) {
|
|
1813
|
+
if (!path) return input;
|
|
1814
|
+
return path.split("/").filter(Boolean).reduce((value, segment) => {
|
|
1815
|
+
if (Array.isArray(value)) return value[Number(segment)];
|
|
1816
|
+
if (this.isRecord(value)) return value[segment];
|
|
1817
|
+
}, input);
|
|
1818
|
+
}
|
|
1819
|
+
getParentPath(path) {
|
|
1820
|
+
const segments = path.split("/").filter(Boolean);
|
|
1821
|
+
return segments.length > 1 ? `/${segments.slice(0, -1).join("/")}` : "/";
|
|
1822
|
+
}
|
|
1823
|
+
getLastPathSegment(path) {
|
|
1824
|
+
return path.split("/").filter(Boolean).at(-1);
|
|
1825
|
+
}
|
|
1826
|
+
isContentItemPath(path) {
|
|
1827
|
+
const segments = path.split("/").filter(Boolean);
|
|
1828
|
+
return segments.at(-2) === "content" && /^\d+$/.test(segments.at(-1) ?? "");
|
|
1829
|
+
}
|
|
1830
|
+
formatArticle(value) {
|
|
1831
|
+
if (!value) return "a";
|
|
1832
|
+
return /^[aeiou]/i.test(value) ? "an" : "a";
|
|
1833
|
+
}
|
|
1834
|
+
isRecord(value) {
|
|
1835
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1564
1836
|
}
|
|
1565
1837
|
/**
|
|
1566
1838
|
* Validates a form definition semantically.
|