@makehq/forman-schema 1.9.4 → 1.9.6
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 +44 -14
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -14
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -524,6 +524,7 @@ function udtspecCollapse(field) {
|
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
// src/forman.ts
|
|
527
|
+
var EMPTY_OPTION_DESCRIPTION = "Optional field, can be left empty.";
|
|
527
528
|
var SchemaConversionError = class extends Error {
|
|
528
529
|
/** Field that caused the error */
|
|
529
530
|
field;
|
|
@@ -886,11 +887,10 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
886
887
|
const placeholder = field.options.placeholder;
|
|
887
888
|
if (isObject(placeholder) && placeholder.nested) {
|
|
888
889
|
(options ||= []).push({
|
|
889
|
-
value:
|
|
890
|
+
value: "",
|
|
890
891
|
label: placeholder.label,
|
|
891
892
|
nested: placeholder.nested
|
|
892
893
|
});
|
|
893
|
-
result.type = [result.type, "null"];
|
|
894
894
|
}
|
|
895
895
|
}
|
|
896
896
|
if (options?.some((option) => {
|
|
@@ -944,6 +944,19 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
944
944
|
result.enum = (options || []).map((option) => option.value);
|
|
945
945
|
}
|
|
946
946
|
}
|
|
947
|
+
if (!field.required) {
|
|
948
|
+
if (result.enum && !result.enum.includes("")) {
|
|
949
|
+
result.enum = ["", ...result.enum];
|
|
950
|
+
} else if (result.oneOf && !result.oneOf.some((entry) => isObject(entry) && entry.const === "")) {
|
|
951
|
+
result.oneOf = [{ title: "Empty", const: "" }, ...result.oneOf];
|
|
952
|
+
}
|
|
953
|
+
if (result.default === void 0) {
|
|
954
|
+
result.default = "";
|
|
955
|
+
}
|
|
956
|
+
if (!result.description) {
|
|
957
|
+
result.description = EMPTY_OPTION_DESCRIPTION;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
947
960
|
result = handleNestedWithDomain(field, nested, domain, result, context);
|
|
948
961
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
949
962
|
return result;
|
|
@@ -1137,6 +1150,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1137
1150
|
path: [],
|
|
1138
1151
|
tail: [],
|
|
1139
1152
|
strict: options?.strict === true,
|
|
1153
|
+
domainAliases: options?.domainAliases ?? {},
|
|
1140
1154
|
validateNestedFields: () => {
|
|
1141
1155
|
throw new Error("Cannot validate nested fields without parent field.");
|
|
1142
1156
|
},
|
|
@@ -1179,6 +1193,18 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1179
1193
|
}
|
|
1180
1194
|
}
|
|
1181
1195
|
}
|
|
1196
|
+
if (options?.strict) {
|
|
1197
|
+
for (const domain of Object.keys(domains)) {
|
|
1198
|
+
if (!domains[domain]) continue;
|
|
1199
|
+
const root = roots[domain];
|
|
1200
|
+
for (const key of Object.keys(domains[domain].values)) {
|
|
1201
|
+
if (!root.seenFields.has(key)) {
|
|
1202
|
+
root.seenFields.add(key);
|
|
1203
|
+
errors.push({ domain, path: "", message: `Unknown field '${key}'.` });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1182
1208
|
return {
|
|
1183
1209
|
valid: errors.length === 0,
|
|
1184
1210
|
errors,
|
|
@@ -1207,7 +1233,7 @@ async function validateFormanValue(value, field, context) {
|
|
|
1207
1233
|
]
|
|
1208
1234
|
};
|
|
1209
1235
|
}
|
|
1210
|
-
if (value == null) {
|
|
1236
|
+
if (value == null || value === "") {
|
|
1211
1237
|
if (normalizedField.type === "select" && hasPlaceholderNested(normalizedField)) {
|
|
1212
1238
|
return handleSelectType(value, normalizedField, context);
|
|
1213
1239
|
}
|
|
@@ -1361,7 +1387,7 @@ async function handleCollectionType2(value, field, context) {
|
|
|
1361
1387
|
errors.push(...result.errors);
|
|
1362
1388
|
}
|
|
1363
1389
|
}
|
|
1364
|
-
if (context.strict) {
|
|
1390
|
+
if (context.strict && context.path.length > 0) {
|
|
1365
1391
|
for (const key of Object.keys(value)) {
|
|
1366
1392
|
if (!seen.has(key)) {
|
|
1367
1393
|
seen.add(key);
|
|
@@ -1687,7 +1713,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1687
1713
|
});
|
|
1688
1714
|
}
|
|
1689
1715
|
}
|
|
1690
|
-
} else if (value == null && hasPlaceholderNested(field)) {
|
|
1716
|
+
} else if ((value == null || value === "") && hasPlaceholderNested(field)) {
|
|
1691
1717
|
const placeholder = field.options.placeholder;
|
|
1692
1718
|
if (placeholder.label) {
|
|
1693
1719
|
context.roots[context.domain].fieldStates.push({
|
|
@@ -1775,15 +1801,16 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
1775
1801
|
}
|
|
1776
1802
|
store = resolvedStore;
|
|
1777
1803
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1804
|
+
const resolvedDomain = domain ? context.domainAliases[domain] ?? domain : domain;
|
|
1805
|
+
if (store && resolvedDomain && resolvedDomain !== context.domain) {
|
|
1806
|
+
if (!context.roots[resolvedDomain]) {
|
|
1780
1807
|
errors.push({
|
|
1781
1808
|
domain: context.domain,
|
|
1782
1809
|
path: context.path.join("."),
|
|
1783
|
-
message: `Unable to process nested fields: Domain '${domain}' not found.`
|
|
1810
|
+
message: resolvedDomain !== domain ? `Unable to process nested fields: Resolved domain '${resolvedDomain}' (from alias '${domain}') not found.` : `Unable to process nested fields: Domain '${domain}' not found.`
|
|
1784
1811
|
});
|
|
1785
1812
|
} else {
|
|
1786
|
-
const result = await context.roots[
|
|
1813
|
+
const result = await context.roots[resolvedDomain].validateFields(store, context);
|
|
1787
1814
|
errors.push(...result.errors);
|
|
1788
1815
|
}
|
|
1789
1816
|
} else if (store) {
|
|
@@ -1919,10 +1946,11 @@ function toFormanSchema(field) {
|
|
|
1919
1946
|
case "string":
|
|
1920
1947
|
const pathInfo = Object.getOwnPropertyDescriptor(field, "x-path");
|
|
1921
1948
|
if (pathInfo) {
|
|
1949
|
+
const help = field.description === EMPTY_OPTION_DESCRIPTION ? void 0 : noEmpty(field.description);
|
|
1922
1950
|
return {
|
|
1923
1951
|
type: pathInfo.value.type,
|
|
1924
1952
|
label: noEmpty(field.title),
|
|
1925
|
-
help
|
|
1953
|
+
help,
|
|
1926
1954
|
options: {
|
|
1927
1955
|
store: Object.getOwnPropertyDescriptor(field, "x-fetch")?.value,
|
|
1928
1956
|
showRoot: pathInfo.value.showRoot,
|
|
@@ -1931,18 +1959,20 @@ function toFormanSchema(field) {
|
|
|
1931
1959
|
};
|
|
1932
1960
|
}
|
|
1933
1961
|
if (field.enum) {
|
|
1962
|
+
const help = field.description === EMPTY_OPTION_DESCRIPTION ? void 0 : noEmpty(field.description);
|
|
1934
1963
|
return {
|
|
1935
1964
|
type: "select",
|
|
1936
1965
|
label: noEmpty(field.title),
|
|
1937
|
-
help
|
|
1938
|
-
options: field.enum.map((value) => ({ value }))
|
|
1966
|
+
help,
|
|
1967
|
+
options: field.enum.filter((value) => value !== "").map((value) => ({ value }))
|
|
1939
1968
|
};
|
|
1940
1969
|
} else if (field.oneOf) {
|
|
1970
|
+
const help = field.description === EMPTY_OPTION_DESCRIPTION ? void 0 : noEmpty(field.description);
|
|
1941
1971
|
return {
|
|
1942
1972
|
type: "select",
|
|
1943
1973
|
label: noEmpty(field.title),
|
|
1944
|
-
help
|
|
1945
|
-
options: field.oneOf.filter((value) => value).map((value) => ({ value: value.const }))
|
|
1974
|
+
help,
|
|
1975
|
+
options: field.oneOf.filter((value) => value && value.const !== "").map((value) => ({ value: value.const }))
|
|
1946
1976
|
};
|
|
1947
1977
|
}
|
|
1948
1978
|
let textField = {
|
package/dist/index.d.cts
CHANGED
|
@@ -216,6 +216,8 @@ type FormanValidationOptions = {
|
|
|
216
216
|
states?: boolean;
|
|
217
217
|
/** Remote resource resolver */
|
|
218
218
|
resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
|
|
219
|
+
/** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
|
|
220
|
+
domainAliases?: Record<string, string>;
|
|
219
221
|
};
|
|
220
222
|
|
|
221
223
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -216,6 +216,8 @@ type FormanValidationOptions = {
|
|
|
216
216
|
states?: boolean;
|
|
217
217
|
/** Remote resource resolver */
|
|
218
218
|
resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
|
|
219
|
+
/** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
|
|
220
|
+
domainAliases?: Record<string, string>;
|
|
219
221
|
};
|
|
220
222
|
|
|
221
223
|
/**
|
package/dist/index.js
CHANGED
|
@@ -495,6 +495,7 @@ function udtspecCollapse(field) {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
// src/forman.ts
|
|
498
|
+
var EMPTY_OPTION_DESCRIPTION = "Optional field, can be left empty.";
|
|
498
499
|
var SchemaConversionError = class extends Error {
|
|
499
500
|
/** Field that caused the error */
|
|
500
501
|
field;
|
|
@@ -857,11 +858,10 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
857
858
|
const placeholder = field.options.placeholder;
|
|
858
859
|
if (isObject(placeholder) && placeholder.nested) {
|
|
859
860
|
(options ||= []).push({
|
|
860
|
-
value:
|
|
861
|
+
value: "",
|
|
861
862
|
label: placeholder.label,
|
|
862
863
|
nested: placeholder.nested
|
|
863
864
|
});
|
|
864
|
-
result.type = [result.type, "null"];
|
|
865
865
|
}
|
|
866
866
|
}
|
|
867
867
|
if (options?.some((option) => {
|
|
@@ -915,6 +915,19 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
915
915
|
result.enum = (options || []).map((option) => option.value);
|
|
916
916
|
}
|
|
917
917
|
}
|
|
918
|
+
if (!field.required) {
|
|
919
|
+
if (result.enum && !result.enum.includes("")) {
|
|
920
|
+
result.enum = ["", ...result.enum];
|
|
921
|
+
} else if (result.oneOf && !result.oneOf.some((entry) => isObject(entry) && entry.const === "")) {
|
|
922
|
+
result.oneOf = [{ title: "Empty", const: "" }, ...result.oneOf];
|
|
923
|
+
}
|
|
924
|
+
if (result.default === void 0) {
|
|
925
|
+
result.default = "";
|
|
926
|
+
}
|
|
927
|
+
if (!result.description) {
|
|
928
|
+
result.description = EMPTY_OPTION_DESCRIPTION;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
918
931
|
result = handleNestedWithDomain(field, nested, domain, result, context);
|
|
919
932
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
920
933
|
return result;
|
|
@@ -1108,6 +1121,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1108
1121
|
path: [],
|
|
1109
1122
|
tail: [],
|
|
1110
1123
|
strict: options?.strict === true,
|
|
1124
|
+
domainAliases: options?.domainAliases ?? {},
|
|
1111
1125
|
validateNestedFields: () => {
|
|
1112
1126
|
throw new Error("Cannot validate nested fields without parent field.");
|
|
1113
1127
|
},
|
|
@@ -1150,6 +1164,18 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1150
1164
|
}
|
|
1151
1165
|
}
|
|
1152
1166
|
}
|
|
1167
|
+
if (options?.strict) {
|
|
1168
|
+
for (const domain of Object.keys(domains)) {
|
|
1169
|
+
if (!domains[domain]) continue;
|
|
1170
|
+
const root = roots[domain];
|
|
1171
|
+
for (const key of Object.keys(domains[domain].values)) {
|
|
1172
|
+
if (!root.seenFields.has(key)) {
|
|
1173
|
+
root.seenFields.add(key);
|
|
1174
|
+
errors.push({ domain, path: "", message: `Unknown field '${key}'.` });
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1153
1179
|
return {
|
|
1154
1180
|
valid: errors.length === 0,
|
|
1155
1181
|
errors,
|
|
@@ -1178,7 +1204,7 @@ async function validateFormanValue(value, field, context) {
|
|
|
1178
1204
|
]
|
|
1179
1205
|
};
|
|
1180
1206
|
}
|
|
1181
|
-
if (value == null) {
|
|
1207
|
+
if (value == null || value === "") {
|
|
1182
1208
|
if (normalizedField.type === "select" && hasPlaceholderNested(normalizedField)) {
|
|
1183
1209
|
return handleSelectType(value, normalizedField, context);
|
|
1184
1210
|
}
|
|
@@ -1332,7 +1358,7 @@ async function handleCollectionType2(value, field, context) {
|
|
|
1332
1358
|
errors.push(...result.errors);
|
|
1333
1359
|
}
|
|
1334
1360
|
}
|
|
1335
|
-
if (context.strict) {
|
|
1361
|
+
if (context.strict && context.path.length > 0) {
|
|
1336
1362
|
for (const key of Object.keys(value)) {
|
|
1337
1363
|
if (!seen.has(key)) {
|
|
1338
1364
|
seen.add(key);
|
|
@@ -1658,7 +1684,7 @@ async function handleSelectType(value, field, context) {
|
|
|
1658
1684
|
});
|
|
1659
1685
|
}
|
|
1660
1686
|
}
|
|
1661
|
-
} else if (value == null && hasPlaceholderNested(field)) {
|
|
1687
|
+
} else if ((value == null || value === "") && hasPlaceholderNested(field)) {
|
|
1662
1688
|
const placeholder = field.options.placeholder;
|
|
1663
1689
|
if (placeholder.label) {
|
|
1664
1690
|
context.roots[context.domain].fieldStates.push({
|
|
@@ -1746,15 +1772,16 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
1746
1772
|
}
|
|
1747
1773
|
store = resolvedStore;
|
|
1748
1774
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1775
|
+
const resolvedDomain = domain ? context.domainAliases[domain] ?? domain : domain;
|
|
1776
|
+
if (store && resolvedDomain && resolvedDomain !== context.domain) {
|
|
1777
|
+
if (!context.roots[resolvedDomain]) {
|
|
1751
1778
|
errors.push({
|
|
1752
1779
|
domain: context.domain,
|
|
1753
1780
|
path: context.path.join("."),
|
|
1754
|
-
message: `Unable to process nested fields: Domain '${domain}' not found.`
|
|
1781
|
+
message: resolvedDomain !== domain ? `Unable to process nested fields: Resolved domain '${resolvedDomain}' (from alias '${domain}') not found.` : `Unable to process nested fields: Domain '${domain}' not found.`
|
|
1755
1782
|
});
|
|
1756
1783
|
} else {
|
|
1757
|
-
const result = await context.roots[
|
|
1784
|
+
const result = await context.roots[resolvedDomain].validateFields(store, context);
|
|
1758
1785
|
errors.push(...result.errors);
|
|
1759
1786
|
}
|
|
1760
1787
|
} else if (store) {
|
|
@@ -1890,10 +1917,11 @@ function toFormanSchema(field) {
|
|
|
1890
1917
|
case "string":
|
|
1891
1918
|
const pathInfo = Object.getOwnPropertyDescriptor(field, "x-path");
|
|
1892
1919
|
if (pathInfo) {
|
|
1920
|
+
const help = field.description === EMPTY_OPTION_DESCRIPTION ? void 0 : noEmpty(field.description);
|
|
1893
1921
|
return {
|
|
1894
1922
|
type: pathInfo.value.type,
|
|
1895
1923
|
label: noEmpty(field.title),
|
|
1896
|
-
help
|
|
1924
|
+
help,
|
|
1897
1925
|
options: {
|
|
1898
1926
|
store: Object.getOwnPropertyDescriptor(field, "x-fetch")?.value,
|
|
1899
1927
|
showRoot: pathInfo.value.showRoot,
|
|
@@ -1902,18 +1930,20 @@ function toFormanSchema(field) {
|
|
|
1902
1930
|
};
|
|
1903
1931
|
}
|
|
1904
1932
|
if (field.enum) {
|
|
1933
|
+
const help = field.description === EMPTY_OPTION_DESCRIPTION ? void 0 : noEmpty(field.description);
|
|
1905
1934
|
return {
|
|
1906
1935
|
type: "select",
|
|
1907
1936
|
label: noEmpty(field.title),
|
|
1908
|
-
help
|
|
1909
|
-
options: field.enum.map((value) => ({ value }))
|
|
1937
|
+
help,
|
|
1938
|
+
options: field.enum.filter((value) => value !== "").map((value) => ({ value }))
|
|
1910
1939
|
};
|
|
1911
1940
|
} else if (field.oneOf) {
|
|
1941
|
+
const help = field.description === EMPTY_OPTION_DESCRIPTION ? void 0 : noEmpty(field.description);
|
|
1912
1942
|
return {
|
|
1913
1943
|
type: "select",
|
|
1914
1944
|
label: noEmpty(field.title),
|
|
1915
|
-
help
|
|
1916
|
-
options: field.oneOf.filter((value) => value).map((value) => ({ value: value.const }))
|
|
1945
|
+
help,
|
|
1946
|
+
options: field.oneOf.filter((value) => value && value.const !== "").map((value) => ({ value: value.const }))
|
|
1917
1947
|
};
|
|
1918
1948
|
}
|
|
1919
1949
|
let textField = {
|