@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 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((option) => option.value === value);
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) => "value" in option && option.value === value);
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((option) => option.value === value);
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) => "value" in option && option.value === value);
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("."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",