@makehq/forman-schema 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +50 -6
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +50 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -93,13 +93,20 @@ function normalizeFormanFieldType(field) {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
+
function valuesMatch(a, b) {
|
|
97
|
+
if (a === b) return true;
|
|
98
|
+
if (isObject(a) && isObject(b)) return JSON.stringify(a) === JSON.stringify(b);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
96
101
|
function findValueInSelectOptions(field, value, optionsAndGroups) {
|
|
97
102
|
if (!optionsAndGroups) return void 0;
|
|
103
|
+
const valueKey = isObject(field.options) && field.options.value ? field.options.value : "value";
|
|
104
|
+
const matches = (option) => valuesMatch(option[valueKey], value);
|
|
98
105
|
if (field.grouped) {
|
|
99
|
-
const found2 = optionsAndGroups.flatMap((group) => "options" in group ? group.options : []).find(
|
|
106
|
+
const found2 = optionsAndGroups.flatMap((group) => "options" in group ? group.options : []).find(matches);
|
|
100
107
|
if (found2) return found2;
|
|
101
108
|
}
|
|
102
|
-
const found = optionsAndGroups.find((option) =>
|
|
109
|
+
const found = optionsAndGroups.find((option) => valueKey in option && matches(option));
|
|
103
110
|
return found;
|
|
104
111
|
}
|
|
105
112
|
function pathToString(path) {
|
|
@@ -569,6 +576,8 @@ var FORMAN_TYPE_MAP = {
|
|
|
569
576
|
path: "string",
|
|
570
577
|
pkey: "string",
|
|
571
578
|
port: "number",
|
|
579
|
+
list: "string",
|
|
580
|
+
radio: "string",
|
|
572
581
|
select: "string",
|
|
573
582
|
udttype: "string",
|
|
574
583
|
udtspec: "array",
|
|
@@ -659,6 +668,8 @@ function toJSONSchemaInternal(field, context) {
|
|
|
659
668
|
return handleCollectionType(normalizedField, result, context);
|
|
660
669
|
case "array":
|
|
661
670
|
return handleArrayType(normalizedField, result, context);
|
|
671
|
+
case "list":
|
|
672
|
+
case "radio":
|
|
662
673
|
case "select":
|
|
663
674
|
case "account":
|
|
664
675
|
case "hook":
|
|
@@ -873,6 +884,32 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
873
884
|
writable: true,
|
|
874
885
|
value: appendQueryString(optionsOrGroups, context.domain, context.tail)
|
|
875
886
|
});
|
|
887
|
+
const xFetchOptions = {};
|
|
888
|
+
if (isObject(field.options)) {
|
|
889
|
+
for (const transferKey of ["label", "value"]) {
|
|
890
|
+
const transferValue = field.options[transferKey];
|
|
891
|
+
if (transferValue) xFetchOptions[transferKey] = transferValue;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (![
|
|
895
|
+
// Reference Types are distinguished by the Loader Tool Name directly
|
|
896
|
+
...FORMAN_REFERENCE_TYPES,
|
|
897
|
+
// Select is default
|
|
898
|
+
"select",
|
|
899
|
+
// File and Folder are distinguished by the x-path metadata, so we don't need to append it here
|
|
900
|
+
"file",
|
|
901
|
+
"folder"
|
|
902
|
+
].includes(field.type)) {
|
|
903
|
+
xFetchOptions["type"] = field.type;
|
|
904
|
+
}
|
|
905
|
+
if (Object.keys(xFetchOptions).length) {
|
|
906
|
+
Object.defineProperty(result, "x-fetch-options", {
|
|
907
|
+
configurable: true,
|
|
908
|
+
enumerable: true,
|
|
909
|
+
writable: true,
|
|
910
|
+
value: xFetchOptions
|
|
911
|
+
});
|
|
912
|
+
}
|
|
876
913
|
} else {
|
|
877
914
|
let options = optionsOrGroups?.flatMap((optionOrGroup) => {
|
|
878
915
|
if (isOptionGroup(optionOrGroup)) {
|
|
@@ -1056,6 +1093,9 @@ function processNestedDirective(field, nested, domain, result, context) {
|
|
|
1056
1093
|
}
|
|
1057
1094
|
|
|
1058
1095
|
// src/validator.ts
|
|
1096
|
+
function fieldAllowsCustomValue(field) {
|
|
1097
|
+
return field.mappable === true || field.editable === true;
|
|
1098
|
+
}
|
|
1059
1099
|
function hasPlaceholderNested(field) {
|
|
1060
1100
|
if (!isObject(field.options)) return false;
|
|
1061
1101
|
const placeholder = field.options.placeholder;
|
|
@@ -1138,6 +1178,8 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
1138
1178
|
path: "string",
|
|
1139
1179
|
pkey: "string",
|
|
1140
1180
|
port: "number",
|
|
1181
|
+
list: void 0,
|
|
1182
|
+
radio: void 0,
|
|
1141
1183
|
select: void 0,
|
|
1142
1184
|
udttype: "string",
|
|
1143
1185
|
udtspec: "array",
|
|
@@ -1349,6 +1391,8 @@ async function validateFormanValue(value, field, context) {
|
|
|
1349
1391
|
return handleCollectionType2(value, normalizedField, context);
|
|
1350
1392
|
case "array":
|
|
1351
1393
|
return handleArrayType2(value, normalizedField, context);
|
|
1394
|
+
case "list":
|
|
1395
|
+
case "radio":
|
|
1352
1396
|
case "select":
|
|
1353
1397
|
case "account":
|
|
1354
1398
|
case "hook":
|
|
@@ -1678,7 +1722,7 @@ async function handlePathType(value, field, context) {
|
|
|
1678
1722
|
});
|
|
1679
1723
|
const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
|
|
1680
1724
|
if (!selectedOption) {
|
|
1681
|
-
if (optionsFromRPC) {
|
|
1725
|
+
if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
|
|
1682
1726
|
warnings.push({
|
|
1683
1727
|
domain: context.domain,
|
|
1684
1728
|
path: context.path.join("."),
|
|
@@ -1784,7 +1828,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1784
1828
|
optionsOrGroups
|
|
1785
1829
|
);
|
|
1786
1830
|
if (!found) {
|
|
1787
|
-
(optionsFromRPC && context.roots[context.domain].allowDynamicValues ? warnings : errors).push({
|
|
1831
|
+
(optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) ? warnings : errors).push({
|
|
1788
1832
|
domain: context.domain,
|
|
1789
1833
|
path: context.path.join("."),
|
|
1790
1834
|
message: `Value '${singleValue}' not found in options.`
|
|
@@ -1792,7 +1836,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1792
1836
|
hasUnresolvedValue = true;
|
|
1793
1837
|
}
|
|
1794
1838
|
}
|
|
1795
|
-
if (optionsFromRPC && context.roots[context.domain].allowDynamicValues && hasUnresolvedValue) {
|
|
1839
|
+
if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) && hasUnresolvedValue) {
|
|
1796
1840
|
context.roots[context.domain].fieldStates.push({
|
|
1797
1841
|
path: context.path,
|
|
1798
1842
|
state: { mode: "edit" }
|
|
@@ -1829,7 +1873,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1829
1873
|
} else {
|
|
1830
1874
|
const item = findValueInSelectOptions(field, value, optionsOrGroups);
|
|
1831
1875
|
if (!item) {
|
|
1832
|
-
if (optionsFromRPC && context.roots[context.domain].allowDynamicValues) {
|
|
1876
|
+
if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
|
|
1833
1877
|
warnings.push({
|
|
1834
1878
|
domain: context.domain,
|
|
1835
1879
|
path: context.path.join("."),
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,7 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
3
3
|
/**
|
|
4
4
|
* Valid Forman Schema field types
|
|
5
5
|
*/
|
|
6
|
-
type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'checkbox' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'filter' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | `device:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
|
|
6
|
+
type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'checkbox' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'filter' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'list' | 'radio' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | `device:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
|
|
7
7
|
/**
|
|
8
8
|
* Validation configuration for Forman Schema fields
|
|
9
9
|
*/
|
|
@@ -53,6 +53,8 @@ type FormanSchemaField = {
|
|
|
53
53
|
disabled?: boolean;
|
|
54
54
|
/** Whether the field is mappable */
|
|
55
55
|
mappable?: boolean;
|
|
56
|
+
/** Whether the field allows custom (typed-in) values even when dynamic values are not allowed in the domain */
|
|
57
|
+
editable?: boolean;
|
|
56
58
|
/** Whether the user will be able to insert new lines in GUI (a textarea will be displayed instead of the text field) */
|
|
57
59
|
multiline?: boolean;
|
|
58
60
|
/** Whether the select field allows multiple values */
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
3
3
|
/**
|
|
4
4
|
* Valid Forman Schema field types
|
|
5
5
|
*/
|
|
6
|
-
type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'checkbox' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'filter' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | `device:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
|
|
6
|
+
type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'checkbox' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'filter' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'list' | 'radio' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | `device:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
|
|
7
7
|
/**
|
|
8
8
|
* Validation configuration for Forman Schema fields
|
|
9
9
|
*/
|
|
@@ -53,6 +53,8 @@ type FormanSchemaField = {
|
|
|
53
53
|
disabled?: boolean;
|
|
54
54
|
/** Whether the field is mappable */
|
|
55
55
|
mappable?: boolean;
|
|
56
|
+
/** Whether the field allows custom (typed-in) values even when dynamic values are not allowed in the domain */
|
|
57
|
+
editable?: boolean;
|
|
56
58
|
/** Whether the user will be able to insert new lines in GUI (a textarea will be displayed instead of the text field) */
|
|
57
59
|
multiline?: boolean;
|
|
58
60
|
/** Whether the select field allows multiple values */
|
package/dist/index.js
CHANGED
|
@@ -64,13 +64,20 @@ function normalizeFormanFieldType(field) {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
+
function valuesMatch(a, b) {
|
|
68
|
+
if (a === b) return true;
|
|
69
|
+
if (isObject(a) && isObject(b)) return JSON.stringify(a) === JSON.stringify(b);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
67
72
|
function findValueInSelectOptions(field, value, optionsAndGroups) {
|
|
68
73
|
if (!optionsAndGroups) return void 0;
|
|
74
|
+
const valueKey = isObject(field.options) && field.options.value ? field.options.value : "value";
|
|
75
|
+
const matches = (option) => valuesMatch(option[valueKey], value);
|
|
69
76
|
if (field.grouped) {
|
|
70
|
-
const found2 = optionsAndGroups.flatMap((group) => "options" in group ? group.options : []).find(
|
|
77
|
+
const found2 = optionsAndGroups.flatMap((group) => "options" in group ? group.options : []).find(matches);
|
|
71
78
|
if (found2) return found2;
|
|
72
79
|
}
|
|
73
|
-
const found = optionsAndGroups.find((option) =>
|
|
80
|
+
const found = optionsAndGroups.find((option) => valueKey in option && matches(option));
|
|
74
81
|
return found;
|
|
75
82
|
}
|
|
76
83
|
function pathToString(path) {
|
|
@@ -540,6 +547,8 @@ var FORMAN_TYPE_MAP = {
|
|
|
540
547
|
path: "string",
|
|
541
548
|
pkey: "string",
|
|
542
549
|
port: "number",
|
|
550
|
+
list: "string",
|
|
551
|
+
radio: "string",
|
|
543
552
|
select: "string",
|
|
544
553
|
udttype: "string",
|
|
545
554
|
udtspec: "array",
|
|
@@ -630,6 +639,8 @@ function toJSONSchemaInternal(field, context) {
|
|
|
630
639
|
return handleCollectionType(normalizedField, result, context);
|
|
631
640
|
case "array":
|
|
632
641
|
return handleArrayType(normalizedField, result, context);
|
|
642
|
+
case "list":
|
|
643
|
+
case "radio":
|
|
633
644
|
case "select":
|
|
634
645
|
case "account":
|
|
635
646
|
case "hook":
|
|
@@ -844,6 +855,32 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
844
855
|
writable: true,
|
|
845
856
|
value: appendQueryString(optionsOrGroups, context.domain, context.tail)
|
|
846
857
|
});
|
|
858
|
+
const xFetchOptions = {};
|
|
859
|
+
if (isObject(field.options)) {
|
|
860
|
+
for (const transferKey of ["label", "value"]) {
|
|
861
|
+
const transferValue = field.options[transferKey];
|
|
862
|
+
if (transferValue) xFetchOptions[transferKey] = transferValue;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (![
|
|
866
|
+
// Reference Types are distinguished by the Loader Tool Name directly
|
|
867
|
+
...FORMAN_REFERENCE_TYPES,
|
|
868
|
+
// Select is default
|
|
869
|
+
"select",
|
|
870
|
+
// File and Folder are distinguished by the x-path metadata, so we don't need to append it here
|
|
871
|
+
"file",
|
|
872
|
+
"folder"
|
|
873
|
+
].includes(field.type)) {
|
|
874
|
+
xFetchOptions["type"] = field.type;
|
|
875
|
+
}
|
|
876
|
+
if (Object.keys(xFetchOptions).length) {
|
|
877
|
+
Object.defineProperty(result, "x-fetch-options", {
|
|
878
|
+
configurable: true,
|
|
879
|
+
enumerable: true,
|
|
880
|
+
writable: true,
|
|
881
|
+
value: xFetchOptions
|
|
882
|
+
});
|
|
883
|
+
}
|
|
847
884
|
} else {
|
|
848
885
|
let options = optionsOrGroups?.flatMap((optionOrGroup) => {
|
|
849
886
|
if (isOptionGroup(optionOrGroup)) {
|
|
@@ -1027,6 +1064,9 @@ function processNestedDirective(field, nested, domain, result, context) {
|
|
|
1027
1064
|
}
|
|
1028
1065
|
|
|
1029
1066
|
// src/validator.ts
|
|
1067
|
+
function fieldAllowsCustomValue(field) {
|
|
1068
|
+
return field.mappable === true || field.editable === true;
|
|
1069
|
+
}
|
|
1030
1070
|
function hasPlaceholderNested(field) {
|
|
1031
1071
|
if (!isObject(field.options)) return false;
|
|
1032
1072
|
const placeholder = field.options.placeholder;
|
|
@@ -1109,6 +1149,8 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
1109
1149
|
path: "string",
|
|
1110
1150
|
pkey: "string",
|
|
1111
1151
|
port: "number",
|
|
1152
|
+
list: void 0,
|
|
1153
|
+
radio: void 0,
|
|
1112
1154
|
select: void 0,
|
|
1113
1155
|
udttype: "string",
|
|
1114
1156
|
udtspec: "array",
|
|
@@ -1320,6 +1362,8 @@ async function validateFormanValue(value, field, context) {
|
|
|
1320
1362
|
return handleCollectionType2(value, normalizedField, context);
|
|
1321
1363
|
case "array":
|
|
1322
1364
|
return handleArrayType2(value, normalizedField, context);
|
|
1365
|
+
case "list":
|
|
1366
|
+
case "radio":
|
|
1323
1367
|
case "select":
|
|
1324
1368
|
case "account":
|
|
1325
1369
|
case "hook":
|
|
@@ -1649,7 +1693,7 @@ async function handlePathType(value, field, context) {
|
|
|
1649
1693
|
});
|
|
1650
1694
|
const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
|
|
1651
1695
|
if (!selectedOption) {
|
|
1652
|
-
if (optionsFromRPC) {
|
|
1696
|
+
if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
|
|
1653
1697
|
warnings.push({
|
|
1654
1698
|
domain: context.domain,
|
|
1655
1699
|
path: context.path.join("."),
|
|
@@ -1755,7 +1799,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1755
1799
|
optionsOrGroups
|
|
1756
1800
|
);
|
|
1757
1801
|
if (!found) {
|
|
1758
|
-
(optionsFromRPC && context.roots[context.domain].allowDynamicValues ? warnings : errors).push({
|
|
1802
|
+
(optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) ? warnings : errors).push({
|
|
1759
1803
|
domain: context.domain,
|
|
1760
1804
|
path: context.path.join("."),
|
|
1761
1805
|
message: `Value '${singleValue}' not found in options.`
|
|
@@ -1763,7 +1807,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1763
1807
|
hasUnresolvedValue = true;
|
|
1764
1808
|
}
|
|
1765
1809
|
}
|
|
1766
|
-
if (optionsFromRPC && context.roots[context.domain].allowDynamicValues && hasUnresolvedValue) {
|
|
1810
|
+
if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) && hasUnresolvedValue) {
|
|
1767
1811
|
context.roots[context.domain].fieldStates.push({
|
|
1768
1812
|
path: context.path,
|
|
1769
1813
|
state: { mode: "edit" }
|
|
@@ -1800,7 +1844,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1800
1844
|
} else {
|
|
1801
1845
|
const item = findValueInSelectOptions(field, value, optionsOrGroups);
|
|
1802
1846
|
if (!item) {
|
|
1803
|
-
if (optionsFromRPC && context.roots[context.domain].allowDynamicValues) {
|
|
1847
|
+
if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
|
|
1804
1848
|
warnings.push({
|
|
1805
1849
|
domain: context.domain,
|
|
1806
1850
|
path: context.path.join("."),
|