@makehq/forman-schema 1.7.1 → 1.8.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 +240 -20
- package/dist/index.d.cts +32 -5
- package/dist/index.d.ts +32 -5
- package/dist/index.js +240 -20
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -89,6 +89,15 @@ function normalizeFormanFieldType(field) {
|
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
function findValueInSelectOptions(field, value, optionsAndGroups) {
|
|
93
|
+
if (!optionsAndGroups) return void 0;
|
|
94
|
+
if (field.grouped) {
|
|
95
|
+
const found2 = optionsAndGroups.flatMap((group) => "options" in group ? group.options : []).find((option) => option.value === value);
|
|
96
|
+
if (found2) return found2;
|
|
97
|
+
}
|
|
98
|
+
const found = optionsAndGroups.find((option) => "value" in option && option.value === value);
|
|
99
|
+
return found;
|
|
100
|
+
}
|
|
92
101
|
function buildRestoreStructure(items) {
|
|
93
102
|
const result = {};
|
|
94
103
|
for (const item of items) {
|
|
@@ -316,11 +325,11 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
|
|
|
316
325
|
case "aiagent":
|
|
317
326
|
case "file":
|
|
318
327
|
case "folder":
|
|
319
|
-
return
|
|
328
|
+
return handleSelectOrPathType(normalizedField, result, context);
|
|
320
329
|
case "filter":
|
|
321
330
|
return handleFilterType(normalizedField, result, context);
|
|
322
331
|
default:
|
|
323
|
-
return handlePrimitiveType(normalizedField, result);
|
|
332
|
+
return handlePrimitiveType(normalizedField, result, context);
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
function handleCollectionType(field, result, context) {
|
|
@@ -436,8 +445,22 @@ function handleFilterType(field, result, context) {
|
|
|
436
445
|
});
|
|
437
446
|
return result;
|
|
438
447
|
}
|
|
439
|
-
function
|
|
448
|
+
function handleSelectOrPathType(field, result, context) {
|
|
440
449
|
const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
450
|
+
if (["file", "folder"].includes(field.type)) {
|
|
451
|
+
const optionsWrapper = isObject(field.options) ? field.options : void 0;
|
|
452
|
+
Object.defineProperty(result, "x-path", {
|
|
453
|
+
configurable: true,
|
|
454
|
+
enumerable: true,
|
|
455
|
+
writable: true,
|
|
456
|
+
value: {
|
|
457
|
+
type: field.type,
|
|
458
|
+
showRoot: optionsWrapper?.showRoot ?? true,
|
|
459
|
+
singleLevel: optionsWrapper?.singleLevel ?? false,
|
|
460
|
+
ownName: field.name
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
441
464
|
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
|
|
442
465
|
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
443
466
|
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
|
|
@@ -449,12 +472,15 @@ function handleSelectType(field, result, context) {
|
|
|
449
472
|
value: appendQueryString(optionsOrGroups, context.domain, context.tail)
|
|
450
473
|
});
|
|
451
474
|
} else {
|
|
452
|
-
const options = optionsOrGroups?.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
475
|
+
const options = optionsOrGroups?.flatMap((optionOrGroup) => {
|
|
476
|
+
if (isOptionGroup(optionOrGroup)) {
|
|
477
|
+
return optionOrGroup.options.map((option) => ({
|
|
478
|
+
...option,
|
|
479
|
+
label: `${optionOrGroup.label}: ${option.label || option.value}`
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
return optionOrGroup;
|
|
483
|
+
});
|
|
458
484
|
if (options?.some((option) => {
|
|
459
485
|
if (isOptionGroup(option)) return option.options.some((option2) => option2.label || option2.nested);
|
|
460
486
|
return option.label || option.nested;
|
|
@@ -546,9 +572,10 @@ function handleSelectType(field, result, context) {
|
|
|
546
572
|
)
|
|
547
573
|
});
|
|
548
574
|
}
|
|
575
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
549
576
|
return result;
|
|
550
577
|
}
|
|
551
|
-
function handlePrimitiveType(field, result) {
|
|
578
|
+
function handlePrimitiveType(field, result, context) {
|
|
552
579
|
if (field.default !== "" && field.default != null) {
|
|
553
580
|
result.default = field.default;
|
|
554
581
|
}
|
|
@@ -566,6 +593,21 @@ function handlePrimitiveType(field, result) {
|
|
|
566
593
|
result.enum = field.validate.enum;
|
|
567
594
|
}
|
|
568
595
|
}
|
|
596
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
function processRpcDirective(field, result, context) {
|
|
600
|
+
if (!field.rpc) return result;
|
|
601
|
+
Object.defineProperty(result, "x-search", {
|
|
602
|
+
configurable: true,
|
|
603
|
+
enumerable: true,
|
|
604
|
+
writable: true,
|
|
605
|
+
value: {
|
|
606
|
+
url: appendQueryString(field.rpc.url, context.domain, context.tail),
|
|
607
|
+
label: field.rpc.label,
|
|
608
|
+
inputSchema: typeof field.rpc.parameters === "string" ? { $ref: field.rpc.parameters } : toJSONSchemaInternal({ type: "collection", spec: field.rpc.parameters }, context)
|
|
609
|
+
}
|
|
610
|
+
});
|
|
569
611
|
return result;
|
|
570
612
|
}
|
|
571
613
|
|
|
@@ -654,7 +696,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
654
696
|
validateNestedFields: () => {
|
|
655
697
|
throw new Error("Cannot validate nested fields without parent field.");
|
|
656
698
|
},
|
|
657
|
-
resolveRemote: async (path, context) => {
|
|
699
|
+
resolveRemote: async (path, context, localData) => {
|
|
658
700
|
if (!options?.resolveRemote) {
|
|
659
701
|
throw new Error("Remote resource not supported when resolver is not provided.");
|
|
660
702
|
}
|
|
@@ -665,7 +707,10 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
665
707
|
},
|
|
666
708
|
{}
|
|
667
709
|
);
|
|
668
|
-
return await options.resolveRemote(path,
|
|
710
|
+
return await options.resolveRemote(path, {
|
|
711
|
+
...data,
|
|
712
|
+
...localData
|
|
713
|
+
});
|
|
669
714
|
}
|
|
670
715
|
}
|
|
671
716
|
);
|
|
@@ -752,9 +797,10 @@ async function validateFormanValue(value, field, context) {
|
|
|
752
797
|
case "aiagent":
|
|
753
798
|
case "udt":
|
|
754
799
|
case "scenario":
|
|
800
|
+
return handleSelectType(value, normalizedField, context);
|
|
755
801
|
case "file":
|
|
756
802
|
case "folder":
|
|
757
|
-
return
|
|
803
|
+
return handlePathType(value, normalizedField, context);
|
|
758
804
|
case "filter":
|
|
759
805
|
return handleFilterType2(value, normalizedField, context);
|
|
760
806
|
default:
|
|
@@ -919,7 +965,152 @@ async function handleFilterType2(value, field, context) {
|
|
|
919
965
|
};
|
|
920
966
|
return handleArrayType2(value, inlineSchema, context);
|
|
921
967
|
}
|
|
922
|
-
async function
|
|
968
|
+
async function handlePathType(value, field, context) {
|
|
969
|
+
const errors = [];
|
|
970
|
+
if (typeof value !== "string") {
|
|
971
|
+
return {
|
|
972
|
+
valid: false,
|
|
973
|
+
errors: [
|
|
974
|
+
...errors,
|
|
975
|
+
{
|
|
976
|
+
domain: context.domain,
|
|
977
|
+
path: context.path.join("."),
|
|
978
|
+
message: `Expected type 'string' for path, got type '${typeof value}'.`
|
|
979
|
+
}
|
|
980
|
+
]
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
let showRoot = true;
|
|
984
|
+
let ids = false;
|
|
985
|
+
let singleLevel = false;
|
|
986
|
+
let options;
|
|
987
|
+
if (isObject(field.options)) {
|
|
988
|
+
if (field.options.showRoot !== void 0) showRoot = field.options.showRoot;
|
|
989
|
+
if (field.options.ids !== void 0) ids = field.options.ids;
|
|
990
|
+
if (field.options.singleLevel !== void 0) singleLevel = field.options.singleLevel;
|
|
991
|
+
options = field.options.store;
|
|
992
|
+
} else {
|
|
993
|
+
options = field.options;
|
|
994
|
+
}
|
|
995
|
+
if (singleLevel && (!showRoot && value.includes("/") || showRoot && value.lastIndexOf("/") > 0)) {
|
|
996
|
+
return {
|
|
997
|
+
valid: false,
|
|
998
|
+
errors: [
|
|
999
|
+
...errors,
|
|
1000
|
+
{
|
|
1001
|
+
domain: context.domain,
|
|
1002
|
+
path: context.path.join("."),
|
|
1003
|
+
message: `Single level path cannot contain slashes.`
|
|
1004
|
+
}
|
|
1005
|
+
]
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
1009
|
+
const levels = value.split("/");
|
|
1010
|
+
if (levels[0] !== "") levels.unshift("");
|
|
1011
|
+
if (showRoot && value === "/" && field.type === "folder") {
|
|
1012
|
+
return {
|
|
1013
|
+
valid: true,
|
|
1014
|
+
errors: []
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
if (levels.length < 2) {
|
|
1018
|
+
return {
|
|
1019
|
+
valid: false,
|
|
1020
|
+
errors: [
|
|
1021
|
+
...errors,
|
|
1022
|
+
{
|
|
1023
|
+
domain: context.domain,
|
|
1024
|
+
path: context.path.join("."),
|
|
1025
|
+
message: `Invalid path "${value}" encountered.`
|
|
1026
|
+
}
|
|
1027
|
+
]
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
const selectedPath = [];
|
|
1031
|
+
let levelOptions = [];
|
|
1032
|
+
for (let levelIndex = 0; levelIndex < levels.length - 1; ++levelIndex) {
|
|
1033
|
+
const isLastLevel = levelIndex === levels.length - 2;
|
|
1034
|
+
const levelSelectedValue = levels[levelIndex + 1];
|
|
1035
|
+
const selectedPathValue = selectedPath.map(({ value: value2 }) => value2).join("/");
|
|
1036
|
+
if (!levelSelectedValue) {
|
|
1037
|
+
return {
|
|
1038
|
+
valid: false,
|
|
1039
|
+
errors: [
|
|
1040
|
+
...errors,
|
|
1041
|
+
{
|
|
1042
|
+
domain: context.domain,
|
|
1043
|
+
path: context.path.join("."),
|
|
1044
|
+
message: `Invalid selected value of "${value}" encountered.`
|
|
1045
|
+
}
|
|
1046
|
+
]
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof options === "string") {
|
|
1050
|
+
try {
|
|
1051
|
+
levelOptions = await context.resolveRemote(options, context, {
|
|
1052
|
+
[field.name]: (showRoot && !selectedPathValue.startsWith("/") ? "/" : "") + selectedPathValue
|
|
1053
|
+
});
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
return {
|
|
1056
|
+
valid: false,
|
|
1057
|
+
errors: [
|
|
1058
|
+
...errors,
|
|
1059
|
+
{
|
|
1060
|
+
domain: context.domain,
|
|
1061
|
+
path: context.path.join("."),
|
|
1062
|
+
message: `Failed to resolve remote resource ${options}: ${error}`
|
|
1063
|
+
}
|
|
1064
|
+
]
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
} else if (isLastLevel) {
|
|
1068
|
+
levelOptions = options;
|
|
1069
|
+
}
|
|
1070
|
+
const selectableOptions = levelOptions.flatMap((candidate) => {
|
|
1071
|
+
if (isLastLevel && (field.type === "file" && !candidate.file || field.type === "folder" && candidate.file)) {
|
|
1072
|
+
return [];
|
|
1073
|
+
}
|
|
1074
|
+
if (!isLastLevel && candidate.file) {
|
|
1075
|
+
return [];
|
|
1076
|
+
}
|
|
1077
|
+
return candidate;
|
|
1078
|
+
});
|
|
1079
|
+
const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
|
|
1080
|
+
if (!selectedOption) {
|
|
1081
|
+
return {
|
|
1082
|
+
valid: false,
|
|
1083
|
+
errors: [
|
|
1084
|
+
...errors,
|
|
1085
|
+
{
|
|
1086
|
+
domain: context.domain,
|
|
1087
|
+
path: context.path.join("."),
|
|
1088
|
+
message: `Path '${levelSelectedValue}' not found in options.`
|
|
1089
|
+
}
|
|
1090
|
+
]
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
selectedPath.push(selectedOption);
|
|
1094
|
+
}
|
|
1095
|
+
if (ids) {
|
|
1096
|
+
context.roots[context.domain].fieldStates.push({
|
|
1097
|
+
path: context.path,
|
|
1098
|
+
state: {
|
|
1099
|
+
mode: "chose",
|
|
1100
|
+
path: selectedPath.map(({ label, value: value2 }) => label ?? value2)
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
if (nested) {
|
|
1105
|
+
const result = await handleNestedFields(nested, value, field, context);
|
|
1106
|
+
errors.push(...result.errors);
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
valid: errors.length === 0,
|
|
1110
|
+
errors
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
async function handleSelectType(value, field, context) {
|
|
923
1114
|
const errors = [];
|
|
924
1115
|
let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
925
1116
|
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
@@ -955,9 +1146,11 @@ async function handleSelectType2(value, field, context) {
|
|
|
955
1146
|
};
|
|
956
1147
|
}
|
|
957
1148
|
for (const singleValue of value) {
|
|
958
|
-
const found =
|
|
959
|
-
|
|
960
|
-
|
|
1149
|
+
const found = findValueInSelectOptions(
|
|
1150
|
+
field,
|
|
1151
|
+
singleValue,
|
|
1152
|
+
optionsOrGroups
|
|
1153
|
+
);
|
|
961
1154
|
if (!found) {
|
|
962
1155
|
errors.push({
|
|
963
1156
|
domain: context.domain,
|
|
@@ -983,7 +1176,7 @@ async function handleSelectType2(value, field, context) {
|
|
|
983
1176
|
}
|
|
984
1177
|
}
|
|
985
1178
|
} else {
|
|
986
|
-
const item = field
|
|
1179
|
+
const item = findValueInSelectOptions(field, value, optionsOrGroups);
|
|
987
1180
|
if (!item) {
|
|
988
1181
|
return {
|
|
989
1182
|
valid: false,
|
|
@@ -1191,6 +1384,19 @@ function toFormanSchema(field) {
|
|
|
1191
1384
|
}
|
|
1192
1385
|
return formanSchema;
|
|
1193
1386
|
case "string":
|
|
1387
|
+
const pathInfo = Object.getOwnPropertyDescriptor(field, "x-path");
|
|
1388
|
+
if (pathInfo) {
|
|
1389
|
+
return {
|
|
1390
|
+
type: pathInfo.value.type,
|
|
1391
|
+
label: noEmpty(field.title),
|
|
1392
|
+
help: noEmpty(field.description),
|
|
1393
|
+
options: {
|
|
1394
|
+
store: Object.getOwnPropertyDescriptor(field, "x-fetch")?.value,
|
|
1395
|
+
showRoot: pathInfo.value.showRoot,
|
|
1396
|
+
singleLevel: pathInfo.value.singleLevel
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1194
1400
|
if (field.enum) {
|
|
1195
1401
|
return {
|
|
1196
1402
|
type: "select",
|
|
@@ -1206,7 +1412,7 @@ function toFormanSchema(field) {
|
|
|
1206
1412
|
options: field.oneOf.filter((value) => value).map((value) => ({ value: value.const }))
|
|
1207
1413
|
};
|
|
1208
1414
|
}
|
|
1209
|
-
|
|
1415
|
+
let textField = {
|
|
1210
1416
|
type: "text",
|
|
1211
1417
|
label: noEmpty(field.title),
|
|
1212
1418
|
help: noEmpty(field.description),
|
|
@@ -1217,9 +1423,12 @@ function toFormanSchema(field) {
|
|
|
1217
1423
|
if (field.pattern) textField.validate.pattern = field.pattern;
|
|
1218
1424
|
if (field.enum) textField.validate.enum = field.enum;
|
|
1219
1425
|
}
|
|
1426
|
+
if ("x-search" in field) {
|
|
1427
|
+
textField = handleSearchDirective(textField, field["x-search"]);
|
|
1428
|
+
}
|
|
1220
1429
|
return textField;
|
|
1221
1430
|
default:
|
|
1222
|
-
|
|
1431
|
+
let primitiveField = {
|
|
1223
1432
|
type: JSON_PRIMITIVE_TYPE_MAP[field.type] || "any",
|
|
1224
1433
|
label: noEmpty(field.title),
|
|
1225
1434
|
help: noEmpty(field.description),
|
|
@@ -1230,9 +1439,20 @@ function toFormanSchema(field) {
|
|
|
1230
1439
|
if (field.minimum !== void 0) primitiveField.validate.min = field.minimum;
|
|
1231
1440
|
if (field.maximum !== void 0) primitiveField.validate.max = field.maximum;
|
|
1232
1441
|
}
|
|
1442
|
+
if ("x-search" in field) {
|
|
1443
|
+
primitiveField = handleSearchDirective(primitiveField, field["x-search"]);
|
|
1444
|
+
}
|
|
1233
1445
|
return primitiveField;
|
|
1234
1446
|
}
|
|
1235
1447
|
}
|
|
1448
|
+
function handleSearchDirective(formanField, directive) {
|
|
1449
|
+
formanField.rpc = {
|
|
1450
|
+
url: directive.url,
|
|
1451
|
+
label: directive.label,
|
|
1452
|
+
parameters: typeof directive.inputSchema.$ref === "string" ? directive.inputSchema.$ref : toFormanSchema(directive.inputSchema).spec
|
|
1453
|
+
};
|
|
1454
|
+
return formanField;
|
|
1455
|
+
}
|
|
1236
1456
|
|
|
1237
1457
|
// src/index.ts
|
|
1238
1458
|
function toJSONSchema(field) {
|
package/dist/index.d.cts
CHANGED
|
@@ -35,8 +35,8 @@ type FormanSchemaField = {
|
|
|
35
35
|
required?: boolean;
|
|
36
36
|
/** Default value for the field */
|
|
37
37
|
default?: FormanSchemaValue;
|
|
38
|
-
/** Available options for
|
|
39
|
-
options?: FormanSchemaOption[] |
|
|
38
|
+
/** Available options for fields which support them */
|
|
39
|
+
options?: (FormanSchemaOption | FormanSchemaOptionGroup)[] | FormanSchemaDirectoryOption[] | FormanSchemaPathExtendedOptions | FormanSchemaExtendedOptions | string;
|
|
40
40
|
/** Help text or description for the field */
|
|
41
41
|
help?: string;
|
|
42
42
|
/** Sub-fields specification for collection or array types */
|
|
@@ -87,16 +87,42 @@ type FormanSchemaField = {
|
|
|
87
87
|
*/
|
|
88
88
|
type FormanSchemaRPCButton = {
|
|
89
89
|
/** RPC button label */
|
|
90
|
-
label
|
|
90
|
+
label?: string;
|
|
91
91
|
/** RPC button URL */
|
|
92
92
|
url: string;
|
|
93
93
|
/** RPC button parameters */
|
|
94
|
-
parameters: FormanSchemaField[];
|
|
94
|
+
parameters: FormanSchemaField[] | string;
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
97
97
|
* Valid Forman Schema values
|
|
98
98
|
*/
|
|
99
99
|
type FormanSchemaValue = string | number | boolean | null;
|
|
100
|
+
/**
|
|
101
|
+
* Extended options for file and folder path selector fields
|
|
102
|
+
*/
|
|
103
|
+
type FormanSchemaPathExtendedOptions = {
|
|
104
|
+
/** Store for the options */
|
|
105
|
+
store: FormanSchemaDirectoryOption[] | string;
|
|
106
|
+
/** Nested fields for every option */
|
|
107
|
+
nested?: FormanSchemaNested;
|
|
108
|
+
/** When set to true, all paths start with '/' */
|
|
109
|
+
showRoot?: boolean;
|
|
110
|
+
/** Used when the directory entries have labels available, and these are different from their actual IDs */
|
|
111
|
+
ids?: boolean;
|
|
112
|
+
/** When set to true, only the entries from the root level are shown, without their children */
|
|
113
|
+
singleLevel?: boolean;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Directory entry option for file and folder path selectors
|
|
117
|
+
*/
|
|
118
|
+
type FormanSchemaDirectoryOption = {
|
|
119
|
+
/** Option value */
|
|
120
|
+
value: FormanSchemaValue;
|
|
121
|
+
/** Option label */
|
|
122
|
+
label?: string;
|
|
123
|
+
/** Set to true when the entry is a file in combined select mode */
|
|
124
|
+
file?: boolean;
|
|
125
|
+
};
|
|
100
126
|
/**
|
|
101
127
|
* Option for a select field
|
|
102
128
|
*/
|
|
@@ -171,6 +197,7 @@ type FormanValidationResult = {
|
|
|
171
197
|
type FormanSchemaFieldState = {
|
|
172
198
|
mode?: 'chose' | 'edit';
|
|
173
199
|
label?: string;
|
|
200
|
+
path?: Array<string>;
|
|
174
201
|
data?: Record<string, unknown>;
|
|
175
202
|
nested?: Record<string, FormanSchemaFieldState>;
|
|
176
203
|
items?: Record<string, FormanSchemaFieldState>[];
|
|
@@ -216,4 +243,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
216
243
|
*/
|
|
217
244
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions): Promise<FormanValidationResult>;
|
|
218
245
|
|
|
219
|
-
export { type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaRPCButton, type FormanSchemaValue, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
|
|
246
|
+
export { type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,8 +35,8 @@ type FormanSchemaField = {
|
|
|
35
35
|
required?: boolean;
|
|
36
36
|
/** Default value for the field */
|
|
37
37
|
default?: FormanSchemaValue;
|
|
38
|
-
/** Available options for
|
|
39
|
-
options?: FormanSchemaOption[] |
|
|
38
|
+
/** Available options for fields which support them */
|
|
39
|
+
options?: (FormanSchemaOption | FormanSchemaOptionGroup)[] | FormanSchemaDirectoryOption[] | FormanSchemaPathExtendedOptions | FormanSchemaExtendedOptions | string;
|
|
40
40
|
/** Help text or description for the field */
|
|
41
41
|
help?: string;
|
|
42
42
|
/** Sub-fields specification for collection or array types */
|
|
@@ -87,16 +87,42 @@ type FormanSchemaField = {
|
|
|
87
87
|
*/
|
|
88
88
|
type FormanSchemaRPCButton = {
|
|
89
89
|
/** RPC button label */
|
|
90
|
-
label
|
|
90
|
+
label?: string;
|
|
91
91
|
/** RPC button URL */
|
|
92
92
|
url: string;
|
|
93
93
|
/** RPC button parameters */
|
|
94
|
-
parameters: FormanSchemaField[];
|
|
94
|
+
parameters: FormanSchemaField[] | string;
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
97
97
|
* Valid Forman Schema values
|
|
98
98
|
*/
|
|
99
99
|
type FormanSchemaValue = string | number | boolean | null;
|
|
100
|
+
/**
|
|
101
|
+
* Extended options for file and folder path selector fields
|
|
102
|
+
*/
|
|
103
|
+
type FormanSchemaPathExtendedOptions = {
|
|
104
|
+
/** Store for the options */
|
|
105
|
+
store: FormanSchemaDirectoryOption[] | string;
|
|
106
|
+
/** Nested fields for every option */
|
|
107
|
+
nested?: FormanSchemaNested;
|
|
108
|
+
/** When set to true, all paths start with '/' */
|
|
109
|
+
showRoot?: boolean;
|
|
110
|
+
/** Used when the directory entries have labels available, and these are different from their actual IDs */
|
|
111
|
+
ids?: boolean;
|
|
112
|
+
/** When set to true, only the entries from the root level are shown, without their children */
|
|
113
|
+
singleLevel?: boolean;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Directory entry option for file and folder path selectors
|
|
117
|
+
*/
|
|
118
|
+
type FormanSchemaDirectoryOption = {
|
|
119
|
+
/** Option value */
|
|
120
|
+
value: FormanSchemaValue;
|
|
121
|
+
/** Option label */
|
|
122
|
+
label?: string;
|
|
123
|
+
/** Set to true when the entry is a file in combined select mode */
|
|
124
|
+
file?: boolean;
|
|
125
|
+
};
|
|
100
126
|
/**
|
|
101
127
|
* Option for a select field
|
|
102
128
|
*/
|
|
@@ -171,6 +197,7 @@ type FormanValidationResult = {
|
|
|
171
197
|
type FormanSchemaFieldState = {
|
|
172
198
|
mode?: 'chose' | 'edit';
|
|
173
199
|
label?: string;
|
|
200
|
+
path?: Array<string>;
|
|
174
201
|
data?: Record<string, unknown>;
|
|
175
202
|
nested?: Record<string, FormanSchemaFieldState>;
|
|
176
203
|
items?: Record<string, FormanSchemaFieldState>[];
|
|
@@ -216,4 +243,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
216
243
|
*/
|
|
217
244
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions): Promise<FormanValidationResult>;
|
|
218
245
|
|
|
219
|
-
export { type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaRPCButton, type FormanSchemaValue, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
|
|
246
|
+
export { type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,15 @@ function normalizeFormanFieldType(field) {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
function findValueInSelectOptions(field, value, optionsAndGroups) {
|
|
64
|
+
if (!optionsAndGroups) return void 0;
|
|
65
|
+
if (field.grouped) {
|
|
66
|
+
const found2 = optionsAndGroups.flatMap((group) => "options" in group ? group.options : []).find((option) => option.value === value);
|
|
67
|
+
if (found2) return found2;
|
|
68
|
+
}
|
|
69
|
+
const found = optionsAndGroups.find((option) => "value" in option && option.value === value);
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
63
72
|
function buildRestoreStructure(items) {
|
|
64
73
|
const result = {};
|
|
65
74
|
for (const item of items) {
|
|
@@ -287,11 +296,11 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
|
|
|
287
296
|
case "aiagent":
|
|
288
297
|
case "file":
|
|
289
298
|
case "folder":
|
|
290
|
-
return
|
|
299
|
+
return handleSelectOrPathType(normalizedField, result, context);
|
|
291
300
|
case "filter":
|
|
292
301
|
return handleFilterType(normalizedField, result, context);
|
|
293
302
|
default:
|
|
294
|
-
return handlePrimitiveType(normalizedField, result);
|
|
303
|
+
return handlePrimitiveType(normalizedField, result, context);
|
|
295
304
|
}
|
|
296
305
|
}
|
|
297
306
|
function handleCollectionType(field, result, context) {
|
|
@@ -407,8 +416,22 @@ function handleFilterType(field, result, context) {
|
|
|
407
416
|
});
|
|
408
417
|
return result;
|
|
409
418
|
}
|
|
410
|
-
function
|
|
419
|
+
function handleSelectOrPathType(field, result, context) {
|
|
411
420
|
const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
421
|
+
if (["file", "folder"].includes(field.type)) {
|
|
422
|
+
const optionsWrapper = isObject(field.options) ? field.options : void 0;
|
|
423
|
+
Object.defineProperty(result, "x-path", {
|
|
424
|
+
configurable: true,
|
|
425
|
+
enumerable: true,
|
|
426
|
+
writable: true,
|
|
427
|
+
value: {
|
|
428
|
+
type: field.type,
|
|
429
|
+
showRoot: optionsWrapper?.showRoot ?? true,
|
|
430
|
+
singleLevel: optionsWrapper?.singleLevel ?? false,
|
|
431
|
+
ownName: field.name
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
412
435
|
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
|
|
413
436
|
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
414
437
|
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
|
|
@@ -420,12 +443,15 @@ function handleSelectType(field, result, context) {
|
|
|
420
443
|
value: appendQueryString(optionsOrGroups, context.domain, context.tail)
|
|
421
444
|
});
|
|
422
445
|
} else {
|
|
423
|
-
const options = optionsOrGroups?.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
446
|
+
const options = optionsOrGroups?.flatMap((optionOrGroup) => {
|
|
447
|
+
if (isOptionGroup(optionOrGroup)) {
|
|
448
|
+
return optionOrGroup.options.map((option) => ({
|
|
449
|
+
...option,
|
|
450
|
+
label: `${optionOrGroup.label}: ${option.label || option.value}`
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
return optionOrGroup;
|
|
454
|
+
});
|
|
429
455
|
if (options?.some((option) => {
|
|
430
456
|
if (isOptionGroup(option)) return option.options.some((option2) => option2.label || option2.nested);
|
|
431
457
|
return option.label || option.nested;
|
|
@@ -517,9 +543,10 @@ function handleSelectType(field, result, context) {
|
|
|
517
543
|
)
|
|
518
544
|
});
|
|
519
545
|
}
|
|
546
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
520
547
|
return result;
|
|
521
548
|
}
|
|
522
|
-
function handlePrimitiveType(field, result) {
|
|
549
|
+
function handlePrimitiveType(field, result, context) {
|
|
523
550
|
if (field.default !== "" && field.default != null) {
|
|
524
551
|
result.default = field.default;
|
|
525
552
|
}
|
|
@@ -537,6 +564,21 @@ function handlePrimitiveType(field, result) {
|
|
|
537
564
|
result.enum = field.validate.enum;
|
|
538
565
|
}
|
|
539
566
|
}
|
|
567
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
function processRpcDirective(field, result, context) {
|
|
571
|
+
if (!field.rpc) return result;
|
|
572
|
+
Object.defineProperty(result, "x-search", {
|
|
573
|
+
configurable: true,
|
|
574
|
+
enumerable: true,
|
|
575
|
+
writable: true,
|
|
576
|
+
value: {
|
|
577
|
+
url: appendQueryString(field.rpc.url, context.domain, context.tail),
|
|
578
|
+
label: field.rpc.label,
|
|
579
|
+
inputSchema: typeof field.rpc.parameters === "string" ? { $ref: field.rpc.parameters } : toJSONSchemaInternal({ type: "collection", spec: field.rpc.parameters }, context)
|
|
580
|
+
}
|
|
581
|
+
});
|
|
540
582
|
return result;
|
|
541
583
|
}
|
|
542
584
|
|
|
@@ -625,7 +667,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
625
667
|
validateNestedFields: () => {
|
|
626
668
|
throw new Error("Cannot validate nested fields without parent field.");
|
|
627
669
|
},
|
|
628
|
-
resolveRemote: async (path, context) => {
|
|
670
|
+
resolveRemote: async (path, context, localData) => {
|
|
629
671
|
if (!options?.resolveRemote) {
|
|
630
672
|
throw new Error("Remote resource not supported when resolver is not provided.");
|
|
631
673
|
}
|
|
@@ -636,7 +678,10 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
636
678
|
},
|
|
637
679
|
{}
|
|
638
680
|
);
|
|
639
|
-
return await options.resolveRemote(path,
|
|
681
|
+
return await options.resolveRemote(path, {
|
|
682
|
+
...data,
|
|
683
|
+
...localData
|
|
684
|
+
});
|
|
640
685
|
}
|
|
641
686
|
}
|
|
642
687
|
);
|
|
@@ -723,9 +768,10 @@ async function validateFormanValue(value, field, context) {
|
|
|
723
768
|
case "aiagent":
|
|
724
769
|
case "udt":
|
|
725
770
|
case "scenario":
|
|
771
|
+
return handleSelectType(value, normalizedField, context);
|
|
726
772
|
case "file":
|
|
727
773
|
case "folder":
|
|
728
|
-
return
|
|
774
|
+
return handlePathType(value, normalizedField, context);
|
|
729
775
|
case "filter":
|
|
730
776
|
return handleFilterType2(value, normalizedField, context);
|
|
731
777
|
default:
|
|
@@ -890,7 +936,152 @@ async function handleFilterType2(value, field, context) {
|
|
|
890
936
|
};
|
|
891
937
|
return handleArrayType2(value, inlineSchema, context);
|
|
892
938
|
}
|
|
893
|
-
async function
|
|
939
|
+
async function handlePathType(value, field, context) {
|
|
940
|
+
const errors = [];
|
|
941
|
+
if (typeof value !== "string") {
|
|
942
|
+
return {
|
|
943
|
+
valid: false,
|
|
944
|
+
errors: [
|
|
945
|
+
...errors,
|
|
946
|
+
{
|
|
947
|
+
domain: context.domain,
|
|
948
|
+
path: context.path.join("."),
|
|
949
|
+
message: `Expected type 'string' for path, got type '${typeof value}'.`
|
|
950
|
+
}
|
|
951
|
+
]
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
let showRoot = true;
|
|
955
|
+
let ids = false;
|
|
956
|
+
let singleLevel = false;
|
|
957
|
+
let options;
|
|
958
|
+
if (isObject(field.options)) {
|
|
959
|
+
if (field.options.showRoot !== void 0) showRoot = field.options.showRoot;
|
|
960
|
+
if (field.options.ids !== void 0) ids = field.options.ids;
|
|
961
|
+
if (field.options.singleLevel !== void 0) singleLevel = field.options.singleLevel;
|
|
962
|
+
options = field.options.store;
|
|
963
|
+
} else {
|
|
964
|
+
options = field.options;
|
|
965
|
+
}
|
|
966
|
+
if (singleLevel && (!showRoot && value.includes("/") || showRoot && value.lastIndexOf("/") > 0)) {
|
|
967
|
+
return {
|
|
968
|
+
valid: false,
|
|
969
|
+
errors: [
|
|
970
|
+
...errors,
|
|
971
|
+
{
|
|
972
|
+
domain: context.domain,
|
|
973
|
+
path: context.path.join("."),
|
|
974
|
+
message: `Single level path cannot contain slashes.`
|
|
975
|
+
}
|
|
976
|
+
]
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
980
|
+
const levels = value.split("/");
|
|
981
|
+
if (levels[0] !== "") levels.unshift("");
|
|
982
|
+
if (showRoot && value === "/" && field.type === "folder") {
|
|
983
|
+
return {
|
|
984
|
+
valid: true,
|
|
985
|
+
errors: []
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
if (levels.length < 2) {
|
|
989
|
+
return {
|
|
990
|
+
valid: false,
|
|
991
|
+
errors: [
|
|
992
|
+
...errors,
|
|
993
|
+
{
|
|
994
|
+
domain: context.domain,
|
|
995
|
+
path: context.path.join("."),
|
|
996
|
+
message: `Invalid path "${value}" encountered.`
|
|
997
|
+
}
|
|
998
|
+
]
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
const selectedPath = [];
|
|
1002
|
+
let levelOptions = [];
|
|
1003
|
+
for (let levelIndex = 0; levelIndex < levels.length - 1; ++levelIndex) {
|
|
1004
|
+
const isLastLevel = levelIndex === levels.length - 2;
|
|
1005
|
+
const levelSelectedValue = levels[levelIndex + 1];
|
|
1006
|
+
const selectedPathValue = selectedPath.map(({ value: value2 }) => value2).join("/");
|
|
1007
|
+
if (!levelSelectedValue) {
|
|
1008
|
+
return {
|
|
1009
|
+
valid: false,
|
|
1010
|
+
errors: [
|
|
1011
|
+
...errors,
|
|
1012
|
+
{
|
|
1013
|
+
domain: context.domain,
|
|
1014
|
+
path: context.path.join("."),
|
|
1015
|
+
message: `Invalid selected value of "${value}" encountered.`
|
|
1016
|
+
}
|
|
1017
|
+
]
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
if (typeof options === "string") {
|
|
1021
|
+
try {
|
|
1022
|
+
levelOptions = await context.resolveRemote(options, context, {
|
|
1023
|
+
[field.name]: (showRoot && !selectedPathValue.startsWith("/") ? "/" : "") + selectedPathValue
|
|
1024
|
+
});
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
return {
|
|
1027
|
+
valid: false,
|
|
1028
|
+
errors: [
|
|
1029
|
+
...errors,
|
|
1030
|
+
{
|
|
1031
|
+
domain: context.domain,
|
|
1032
|
+
path: context.path.join("."),
|
|
1033
|
+
message: `Failed to resolve remote resource ${options}: ${error}`
|
|
1034
|
+
}
|
|
1035
|
+
]
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
} else if (isLastLevel) {
|
|
1039
|
+
levelOptions = options;
|
|
1040
|
+
}
|
|
1041
|
+
const selectableOptions = levelOptions.flatMap((candidate) => {
|
|
1042
|
+
if (isLastLevel && (field.type === "file" && !candidate.file || field.type === "folder" && candidate.file)) {
|
|
1043
|
+
return [];
|
|
1044
|
+
}
|
|
1045
|
+
if (!isLastLevel && candidate.file) {
|
|
1046
|
+
return [];
|
|
1047
|
+
}
|
|
1048
|
+
return candidate;
|
|
1049
|
+
});
|
|
1050
|
+
const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
|
|
1051
|
+
if (!selectedOption) {
|
|
1052
|
+
return {
|
|
1053
|
+
valid: false,
|
|
1054
|
+
errors: [
|
|
1055
|
+
...errors,
|
|
1056
|
+
{
|
|
1057
|
+
domain: context.domain,
|
|
1058
|
+
path: context.path.join("."),
|
|
1059
|
+
message: `Path '${levelSelectedValue}' not found in options.`
|
|
1060
|
+
}
|
|
1061
|
+
]
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
selectedPath.push(selectedOption);
|
|
1065
|
+
}
|
|
1066
|
+
if (ids) {
|
|
1067
|
+
context.roots[context.domain].fieldStates.push({
|
|
1068
|
+
path: context.path,
|
|
1069
|
+
state: {
|
|
1070
|
+
mode: "chose",
|
|
1071
|
+
path: selectedPath.map(({ label, value: value2 }) => label ?? value2)
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
if (nested) {
|
|
1076
|
+
const result = await handleNestedFields(nested, value, field, context);
|
|
1077
|
+
errors.push(...result.errors);
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
valid: errors.length === 0,
|
|
1081
|
+
errors
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
async function handleSelectType(value, field, context) {
|
|
894
1085
|
const errors = [];
|
|
895
1086
|
let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
896
1087
|
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
@@ -926,9 +1117,11 @@ async function handleSelectType2(value, field, context) {
|
|
|
926
1117
|
};
|
|
927
1118
|
}
|
|
928
1119
|
for (const singleValue of value) {
|
|
929
|
-
const found =
|
|
930
|
-
|
|
931
|
-
|
|
1120
|
+
const found = findValueInSelectOptions(
|
|
1121
|
+
field,
|
|
1122
|
+
singleValue,
|
|
1123
|
+
optionsOrGroups
|
|
1124
|
+
);
|
|
932
1125
|
if (!found) {
|
|
933
1126
|
errors.push({
|
|
934
1127
|
domain: context.domain,
|
|
@@ -954,7 +1147,7 @@ async function handleSelectType2(value, field, context) {
|
|
|
954
1147
|
}
|
|
955
1148
|
}
|
|
956
1149
|
} else {
|
|
957
|
-
const item = field
|
|
1150
|
+
const item = findValueInSelectOptions(field, value, optionsOrGroups);
|
|
958
1151
|
if (!item) {
|
|
959
1152
|
return {
|
|
960
1153
|
valid: false,
|
|
@@ -1162,6 +1355,19 @@ function toFormanSchema(field) {
|
|
|
1162
1355
|
}
|
|
1163
1356
|
return formanSchema;
|
|
1164
1357
|
case "string":
|
|
1358
|
+
const pathInfo = Object.getOwnPropertyDescriptor(field, "x-path");
|
|
1359
|
+
if (pathInfo) {
|
|
1360
|
+
return {
|
|
1361
|
+
type: pathInfo.value.type,
|
|
1362
|
+
label: noEmpty(field.title),
|
|
1363
|
+
help: noEmpty(field.description),
|
|
1364
|
+
options: {
|
|
1365
|
+
store: Object.getOwnPropertyDescriptor(field, "x-fetch")?.value,
|
|
1366
|
+
showRoot: pathInfo.value.showRoot,
|
|
1367
|
+
singleLevel: pathInfo.value.singleLevel
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1165
1371
|
if (field.enum) {
|
|
1166
1372
|
return {
|
|
1167
1373
|
type: "select",
|
|
@@ -1177,7 +1383,7 @@ function toFormanSchema(field) {
|
|
|
1177
1383
|
options: field.oneOf.filter((value) => value).map((value) => ({ value: value.const }))
|
|
1178
1384
|
};
|
|
1179
1385
|
}
|
|
1180
|
-
|
|
1386
|
+
let textField = {
|
|
1181
1387
|
type: "text",
|
|
1182
1388
|
label: noEmpty(field.title),
|
|
1183
1389
|
help: noEmpty(field.description),
|
|
@@ -1188,9 +1394,12 @@ function toFormanSchema(field) {
|
|
|
1188
1394
|
if (field.pattern) textField.validate.pattern = field.pattern;
|
|
1189
1395
|
if (field.enum) textField.validate.enum = field.enum;
|
|
1190
1396
|
}
|
|
1397
|
+
if ("x-search" in field) {
|
|
1398
|
+
textField = handleSearchDirective(textField, field["x-search"]);
|
|
1399
|
+
}
|
|
1191
1400
|
return textField;
|
|
1192
1401
|
default:
|
|
1193
|
-
|
|
1402
|
+
let primitiveField = {
|
|
1194
1403
|
type: JSON_PRIMITIVE_TYPE_MAP[field.type] || "any",
|
|
1195
1404
|
label: noEmpty(field.title),
|
|
1196
1405
|
help: noEmpty(field.description),
|
|
@@ -1201,9 +1410,20 @@ function toFormanSchema(field) {
|
|
|
1201
1410
|
if (field.minimum !== void 0) primitiveField.validate.min = field.minimum;
|
|
1202
1411
|
if (field.maximum !== void 0) primitiveField.validate.max = field.maximum;
|
|
1203
1412
|
}
|
|
1413
|
+
if ("x-search" in field) {
|
|
1414
|
+
primitiveField = handleSearchDirective(primitiveField, field["x-search"]);
|
|
1415
|
+
}
|
|
1204
1416
|
return primitiveField;
|
|
1205
1417
|
}
|
|
1206
1418
|
}
|
|
1419
|
+
function handleSearchDirective(formanField, directive) {
|
|
1420
|
+
formanField.rpc = {
|
|
1421
|
+
url: directive.url,
|
|
1422
|
+
label: directive.label,
|
|
1423
|
+
parameters: typeof directive.inputSchema.$ref === "string" ? directive.inputSchema.$ref : toFormanSchema(directive.inputSchema).spec
|
|
1424
|
+
};
|
|
1425
|
+
return formanField;
|
|
1426
|
+
}
|
|
1207
1427
|
|
|
1208
1428
|
// src/index.ts
|
|
1209
1429
|
function toJSONSchema(field) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makehq/forman-schema",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Forman Schema Tools",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Make",
|
|
7
|
-
"repository":
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/integromat/make-forman-schema.git"
|
|
10
|
+
},
|
|
8
11
|
"homepage": "https://www.make.com",
|
|
9
12
|
"type": "module",
|
|
10
13
|
"main": "./dist/index.js",
|