@makehq/forman-schema 1.11.1 → 1.12.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 CHANGED
@@ -1056,6 +1056,9 @@ function processNestedDirective(field, nested, domain, result, context) {
1056
1056
  }
1057
1057
 
1058
1058
  // src/validator.ts
1059
+ function fieldAllowsCustomValue(field) {
1060
+ return field.mappable === true || field.editable === true;
1061
+ }
1059
1062
  function hasPlaceholderNested(field) {
1060
1063
  if (!isObject(field.options)) return false;
1061
1064
  const placeholder = field.options.placeholder;
@@ -1158,6 +1161,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
1158
1161
  seenFields: /* @__PURE__ */ new Set(),
1159
1162
  fieldStates: [],
1160
1163
  schemaFields: [],
1164
+ allowDynamicValues: domains[domain].allowDynamicValues ?? options?.allowDynamicValues ?? false,
1161
1165
  validateFields: (fields, context) => {
1162
1166
  return validateFormanValue(
1163
1167
  domains[domain].values,
@@ -1308,7 +1312,7 @@ async function validateFormanValue(value, field, context) {
1308
1312
  };
1309
1313
  }
1310
1314
  if (containsIMLExpression(value)) {
1311
- if (normalizedField.mappable === false) {
1315
+ if (normalizedField.mappable === false || !context.roots[context.domain].allowDynamicValues) {
1312
1316
  return {
1313
1317
  valid: false,
1314
1318
  errors: [
@@ -1677,7 +1681,7 @@ async function handlePathType(value, field, context) {
1677
1681
  });
1678
1682
  const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
1679
1683
  if (!selectedOption) {
1680
- if (optionsFromRPC) {
1684
+ if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
1681
1685
  warnings.push({
1682
1686
  domain: context.domain,
1683
1687
  path: context.path.join("."),
@@ -1783,7 +1787,7 @@ async function handleSelectType(value, field, context) {
1783
1787
  optionsOrGroups
1784
1788
  );
1785
1789
  if (!found) {
1786
- (optionsFromRPC ? warnings : errors).push({
1790
+ (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) ? warnings : errors).push({
1787
1791
  domain: context.domain,
1788
1792
  path: context.path.join("."),
1789
1793
  message: `Value '${singleValue}' not found in options.`
@@ -1791,7 +1795,7 @@ async function handleSelectType(value, field, context) {
1791
1795
  hasUnresolvedValue = true;
1792
1796
  }
1793
1797
  }
1794
- if (optionsFromRPC && hasUnresolvedValue) {
1798
+ if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) && hasUnresolvedValue) {
1795
1799
  context.roots[context.domain].fieldStates.push({
1796
1800
  path: context.path,
1797
1801
  state: { mode: "edit" }
@@ -1828,7 +1832,7 @@ async function handleSelectType(value, field, context) {
1828
1832
  } else {
1829
1833
  const item = findValueInSelectOptions(field, value, optionsOrGroups);
1830
1834
  if (!item) {
1831
- if (optionsFromRPC) {
1835
+ if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
1832
1836
  warnings.push({
1833
1837
  domain: context.domain,
1834
1838
  path: context.path.join("."),
@@ -2158,7 +2162,10 @@ function validateFormanWithDomains(domains, options) {
2158
2162
  return validateFormanWithDomainsInternal(domains, options);
2159
2163
  }
2160
2164
  function validateForman(values, schema, options, restoreExtras) {
2161
- return validateFormanWithDomains({ default: { values, schema, restoreExtras } }, options);
2165
+ return validateFormanWithDomains(
2166
+ { default: { values, schema, restoreExtras, allowDynamicValues: options?.allowDynamicValues } },
2167
+ options
2168
+ );
2162
2169
  }
2163
2170
  // Annotate the CommonJS export names for ESM import in node:
2164
2171
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -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 */
@@ -231,6 +233,10 @@ type FormanValidationOptions = {
231
233
  resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
232
234
  /** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
233
235
  domainAliases?: Record<string, string>;
236
+ /** Whether to allow dynamic values (IML expressions, unresolved RPC options).
237
+ * When false (default), IML expressions cause errors and unresolved RPC options are treated as errors.
238
+ * Applies as a global default; can be overridden per-domain in validateFormanWithDomains(). */
239
+ allowDynamicValues?: boolean;
234
240
  };
235
241
 
236
242
  /**
@@ -257,6 +263,9 @@ declare function validateFormanWithDomains(domains: Record<string, {
257
263
  schema: FormanSchemaField[];
258
264
  /** Extra values injected into restore states, keyed by string path (dot notation, `[index]`, backtick-escaping). */
259
265
  restoreExtras?: Record<string, Record<string, unknown>>;
266
+ /** Whether the domain allows dynamic values (IML expressions, unresolved RPC select options).
267
+ * Defaults to false. When false, IML expressions cause errors and unresolved RPC options are treated as errors. */
268
+ allowDynamicValues?: boolean;
260
269
  }>, options?: FormanValidationOptions): Promise<FormanValidationResult>;
261
270
  /**
262
271
  * Validates a simple Forman values against a schema
package/dist/index.d.ts CHANGED
@@ -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 */
@@ -231,6 +233,10 @@ type FormanValidationOptions = {
231
233
  resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
232
234
  /** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
233
235
  domainAliases?: Record<string, string>;
236
+ /** Whether to allow dynamic values (IML expressions, unresolved RPC options).
237
+ * When false (default), IML expressions cause errors and unresolved RPC options are treated as errors.
238
+ * Applies as a global default; can be overridden per-domain in validateFormanWithDomains(). */
239
+ allowDynamicValues?: boolean;
234
240
  };
235
241
 
236
242
  /**
@@ -257,6 +263,9 @@ declare function validateFormanWithDomains(domains: Record<string, {
257
263
  schema: FormanSchemaField[];
258
264
  /** Extra values injected into restore states, keyed by string path (dot notation, `[index]`, backtick-escaping). */
259
265
  restoreExtras?: Record<string, Record<string, unknown>>;
266
+ /** Whether the domain allows dynamic values (IML expressions, unresolved RPC select options).
267
+ * Defaults to false. When false, IML expressions cause errors and unresolved RPC options are treated as errors. */
268
+ allowDynamicValues?: boolean;
260
269
  }>, options?: FormanValidationOptions): Promise<FormanValidationResult>;
261
270
  /**
262
271
  * Validates a simple Forman values against a schema
package/dist/index.js CHANGED
@@ -1027,6 +1027,9 @@ function processNestedDirective(field, nested, domain, result, context) {
1027
1027
  }
1028
1028
 
1029
1029
  // src/validator.ts
1030
+ function fieldAllowsCustomValue(field) {
1031
+ return field.mappable === true || field.editable === true;
1032
+ }
1030
1033
  function hasPlaceholderNested(field) {
1031
1034
  if (!isObject(field.options)) return false;
1032
1035
  const placeholder = field.options.placeholder;
@@ -1129,6 +1132,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
1129
1132
  seenFields: /* @__PURE__ */ new Set(),
1130
1133
  fieldStates: [],
1131
1134
  schemaFields: [],
1135
+ allowDynamicValues: domains[domain].allowDynamicValues ?? options?.allowDynamicValues ?? false,
1132
1136
  validateFields: (fields, context) => {
1133
1137
  return validateFormanValue(
1134
1138
  domains[domain].values,
@@ -1279,7 +1283,7 @@ async function validateFormanValue(value, field, context) {
1279
1283
  };
1280
1284
  }
1281
1285
  if (containsIMLExpression(value)) {
1282
- if (normalizedField.mappable === false) {
1286
+ if (normalizedField.mappable === false || !context.roots[context.domain].allowDynamicValues) {
1283
1287
  return {
1284
1288
  valid: false,
1285
1289
  errors: [
@@ -1648,7 +1652,7 @@ async function handlePathType(value, field, context) {
1648
1652
  });
1649
1653
  const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
1650
1654
  if (!selectedOption) {
1651
- if (optionsFromRPC) {
1655
+ if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
1652
1656
  warnings.push({
1653
1657
  domain: context.domain,
1654
1658
  path: context.path.join("."),
@@ -1754,7 +1758,7 @@ async function handleSelectType(value, field, context) {
1754
1758
  optionsOrGroups
1755
1759
  );
1756
1760
  if (!found) {
1757
- (optionsFromRPC ? warnings : errors).push({
1761
+ (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) ? warnings : errors).push({
1758
1762
  domain: context.domain,
1759
1763
  path: context.path.join("."),
1760
1764
  message: `Value '${singleValue}' not found in options.`
@@ -1762,7 +1766,7 @@ async function handleSelectType(value, field, context) {
1762
1766
  hasUnresolvedValue = true;
1763
1767
  }
1764
1768
  }
1765
- if (optionsFromRPC && hasUnresolvedValue) {
1769
+ if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field)) && hasUnresolvedValue) {
1766
1770
  context.roots[context.domain].fieldStates.push({
1767
1771
  path: context.path,
1768
1772
  state: { mode: "edit" }
@@ -1799,7 +1803,7 @@ async function handleSelectType(value, field, context) {
1799
1803
  } else {
1800
1804
  const item = findValueInSelectOptions(field, value, optionsOrGroups);
1801
1805
  if (!item) {
1802
- if (optionsFromRPC) {
1806
+ if (optionsFromRPC && (context.roots[context.domain].allowDynamicValues || fieldAllowsCustomValue(field))) {
1803
1807
  warnings.push({
1804
1808
  domain: context.domain,
1805
1809
  path: context.path.join("."),
@@ -2129,7 +2133,10 @@ function validateFormanWithDomains(domains, options) {
2129
2133
  return validateFormanWithDomainsInternal(domains, options);
2130
2134
  }
2131
2135
  function validateForman(values, schema, options, restoreExtras) {
2132
- return validateFormanWithDomains({ default: { values, schema, restoreExtras } }, options);
2136
+ return validateFormanWithDomains(
2137
+ { default: { values, schema, restoreExtras, allowDynamicValues: options?.allowDynamicValues } },
2138
+ options
2139
+ );
2133
2140
  }
2134
2141
  export {
2135
2142
  toFormanSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.11.1",
3
+ "version": "1.12.1",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",