@makehq/forman-schema 1.7.0 → 1.8.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 +219 -11
- package/dist/index.d.cts +33 -6
- package/dist/index.d.ts +33 -6
- package/dist/index.js +219 -11
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -67,7 +67,7 @@ var API_ENDPOINTS = {
|
|
|
67
67
|
aiagent: "api://ai-agents/v1/agents",
|
|
68
68
|
datastore: "api://data-stores",
|
|
69
69
|
hook: "api://hooks/{{kind}}",
|
|
70
|
-
device: "api://devices",
|
|
70
|
+
device: "api://devices/{{kind}}",
|
|
71
71
|
keychain: "api://keys/{{kind}}",
|
|
72
72
|
udt: "api://data-structures",
|
|
73
73
|
scenario: "api://scenario-list"
|
|
@@ -310,16 +310,17 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
|
|
|
310
310
|
case "select":
|
|
311
311
|
case "account":
|
|
312
312
|
case "hook":
|
|
313
|
+
case "device":
|
|
313
314
|
case "keychain":
|
|
314
315
|
case "datastore":
|
|
315
316
|
case "aiagent":
|
|
316
317
|
case "file":
|
|
317
318
|
case "folder":
|
|
318
|
-
return
|
|
319
|
+
return handleSelectOrPathType(normalizedField, result, context);
|
|
319
320
|
case "filter":
|
|
320
321
|
return handleFilterType(normalizedField, result, context);
|
|
321
322
|
default:
|
|
322
|
-
return handlePrimitiveType(normalizedField, result);
|
|
323
|
+
return handlePrimitiveType(normalizedField, result, context);
|
|
323
324
|
}
|
|
324
325
|
}
|
|
325
326
|
function handleCollectionType(field, result, context) {
|
|
@@ -435,8 +436,22 @@ function handleFilterType(field, result, context) {
|
|
|
435
436
|
});
|
|
436
437
|
return result;
|
|
437
438
|
}
|
|
438
|
-
function
|
|
439
|
+
function handleSelectOrPathType(field, result, context) {
|
|
439
440
|
const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
441
|
+
if (["file", "folder"].includes(field.type)) {
|
|
442
|
+
const optionsWrapper = isObject(field.options) ? field.options : void 0;
|
|
443
|
+
Object.defineProperty(result, "x-path", {
|
|
444
|
+
configurable: true,
|
|
445
|
+
enumerable: true,
|
|
446
|
+
writable: true,
|
|
447
|
+
value: {
|
|
448
|
+
type: field.type,
|
|
449
|
+
showRoot: optionsWrapper?.showRoot ?? true,
|
|
450
|
+
singleLevel: optionsWrapper?.singleLevel ?? false,
|
|
451
|
+
ownName: field.name
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
440
455
|
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
|
|
441
456
|
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
442
457
|
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
|
|
@@ -545,9 +560,10 @@ function handleSelectType(field, result, context) {
|
|
|
545
560
|
)
|
|
546
561
|
});
|
|
547
562
|
}
|
|
563
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
548
564
|
return result;
|
|
549
565
|
}
|
|
550
|
-
function handlePrimitiveType(field, result) {
|
|
566
|
+
function handlePrimitiveType(field, result, context) {
|
|
551
567
|
if (field.default !== "" && field.default != null) {
|
|
552
568
|
result.default = field.default;
|
|
553
569
|
}
|
|
@@ -565,6 +581,21 @@ function handlePrimitiveType(field, result) {
|
|
|
565
581
|
result.enum = field.validate.enum;
|
|
566
582
|
}
|
|
567
583
|
}
|
|
584
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
function processRpcDirective(field, result, context) {
|
|
588
|
+
if (!field.rpc) return result;
|
|
589
|
+
Object.defineProperty(result, "x-search", {
|
|
590
|
+
configurable: true,
|
|
591
|
+
enumerable: true,
|
|
592
|
+
writable: true,
|
|
593
|
+
value: {
|
|
594
|
+
url: appendQueryString(field.rpc.url, context.domain, context.tail),
|
|
595
|
+
label: field.rpc.label,
|
|
596
|
+
inputSchema: typeof field.rpc.parameters === "string" ? { $ref: field.rpc.parameters } : toJSONSchemaInternal({ type: "collection", spec: field.rpc.parameters }, context)
|
|
597
|
+
}
|
|
598
|
+
});
|
|
568
599
|
return result;
|
|
569
600
|
}
|
|
570
601
|
|
|
@@ -572,6 +603,7 @@ function handlePrimitiveType(field, result) {
|
|
|
572
603
|
var FORMAN_TYPE_MAP2 = {
|
|
573
604
|
account: "number",
|
|
574
605
|
hook: "number",
|
|
606
|
+
device: "number",
|
|
575
607
|
keychain: "number",
|
|
576
608
|
datastore: "number",
|
|
577
609
|
aiagent: "string",
|
|
@@ -652,7 +684,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
652
684
|
validateNestedFields: () => {
|
|
653
685
|
throw new Error("Cannot validate nested fields without parent field.");
|
|
654
686
|
},
|
|
655
|
-
resolveRemote: async (path, context) => {
|
|
687
|
+
resolveRemote: async (path, context, localData) => {
|
|
656
688
|
if (!options?.resolveRemote) {
|
|
657
689
|
throw new Error("Remote resource not supported when resolver is not provided.");
|
|
658
690
|
}
|
|
@@ -663,7 +695,10 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
663
695
|
},
|
|
664
696
|
{}
|
|
665
697
|
);
|
|
666
|
-
return await options.resolveRemote(path,
|
|
698
|
+
return await options.resolveRemote(path, {
|
|
699
|
+
...data,
|
|
700
|
+
...localData
|
|
701
|
+
});
|
|
667
702
|
}
|
|
668
703
|
}
|
|
669
704
|
);
|
|
@@ -750,9 +785,10 @@ async function validateFormanValue(value, field, context) {
|
|
|
750
785
|
case "aiagent":
|
|
751
786
|
case "udt":
|
|
752
787
|
case "scenario":
|
|
788
|
+
return handleSelectType(value, normalizedField, context);
|
|
753
789
|
case "file":
|
|
754
790
|
case "folder":
|
|
755
|
-
return
|
|
791
|
+
return handlePathType(value, normalizedField, context);
|
|
756
792
|
case "filter":
|
|
757
793
|
return handleFilterType2(value, normalizedField, context);
|
|
758
794
|
default:
|
|
@@ -917,7 +953,152 @@ async function handleFilterType2(value, field, context) {
|
|
|
917
953
|
};
|
|
918
954
|
return handleArrayType2(value, inlineSchema, context);
|
|
919
955
|
}
|
|
920
|
-
async function
|
|
956
|
+
async function handlePathType(value, field, context) {
|
|
957
|
+
const errors = [];
|
|
958
|
+
if (typeof value !== "string") {
|
|
959
|
+
return {
|
|
960
|
+
valid: false,
|
|
961
|
+
errors: [
|
|
962
|
+
...errors,
|
|
963
|
+
{
|
|
964
|
+
domain: context.domain,
|
|
965
|
+
path: context.path.join("."),
|
|
966
|
+
message: `Expected type 'string' for path, got type '${typeof value}'.`
|
|
967
|
+
}
|
|
968
|
+
]
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
let showRoot = true;
|
|
972
|
+
let ids = false;
|
|
973
|
+
let singleLevel = false;
|
|
974
|
+
let options;
|
|
975
|
+
if (isObject(field.options)) {
|
|
976
|
+
if (field.options.showRoot !== void 0) showRoot = field.options.showRoot;
|
|
977
|
+
if (field.options.ids !== void 0) ids = field.options.ids;
|
|
978
|
+
if (field.options.singleLevel !== void 0) singleLevel = field.options.singleLevel;
|
|
979
|
+
options = field.options.store;
|
|
980
|
+
} else {
|
|
981
|
+
options = field.options;
|
|
982
|
+
}
|
|
983
|
+
if (singleLevel && (!showRoot && value.includes("/") || showRoot && value.lastIndexOf("/") > 0)) {
|
|
984
|
+
return {
|
|
985
|
+
valid: false,
|
|
986
|
+
errors: [
|
|
987
|
+
...errors,
|
|
988
|
+
{
|
|
989
|
+
domain: context.domain,
|
|
990
|
+
path: context.path.join("."),
|
|
991
|
+
message: `Single level path cannot contain slashes.`
|
|
992
|
+
}
|
|
993
|
+
]
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
997
|
+
const levels = value.split("/");
|
|
998
|
+
if (levels[0] !== "") levels.unshift("");
|
|
999
|
+
if (showRoot && value === "/" && field.type === "folder") {
|
|
1000
|
+
return {
|
|
1001
|
+
valid: true,
|
|
1002
|
+
errors: []
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
if (levels.length < 2) {
|
|
1006
|
+
return {
|
|
1007
|
+
valid: false,
|
|
1008
|
+
errors: [
|
|
1009
|
+
...errors,
|
|
1010
|
+
{
|
|
1011
|
+
domain: context.domain,
|
|
1012
|
+
path: context.path.join("."),
|
|
1013
|
+
message: `Invalid path "${value}" encountered.`
|
|
1014
|
+
}
|
|
1015
|
+
]
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
const selectedPath = [];
|
|
1019
|
+
let levelOptions = [];
|
|
1020
|
+
for (let levelIndex = 0; levelIndex < levels.length - 1; ++levelIndex) {
|
|
1021
|
+
const isLastLevel = levelIndex === levels.length - 2;
|
|
1022
|
+
const levelSelectedValue = levels[levelIndex + 1];
|
|
1023
|
+
const selectedPathValue = selectedPath.map(({ value: value2 }) => value2).join("/");
|
|
1024
|
+
if (!levelSelectedValue) {
|
|
1025
|
+
return {
|
|
1026
|
+
valid: false,
|
|
1027
|
+
errors: [
|
|
1028
|
+
...errors,
|
|
1029
|
+
{
|
|
1030
|
+
domain: context.domain,
|
|
1031
|
+
path: context.path.join("."),
|
|
1032
|
+
message: `Invalid selected value of "${value}" encountered.`
|
|
1033
|
+
}
|
|
1034
|
+
]
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
if (typeof options === "string") {
|
|
1038
|
+
try {
|
|
1039
|
+
levelOptions = await context.resolveRemote(options, context, {
|
|
1040
|
+
[field.name]: (showRoot && !selectedPathValue.startsWith("/") ? "/" : "") + selectedPathValue
|
|
1041
|
+
});
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
return {
|
|
1044
|
+
valid: false,
|
|
1045
|
+
errors: [
|
|
1046
|
+
...errors,
|
|
1047
|
+
{
|
|
1048
|
+
domain: context.domain,
|
|
1049
|
+
path: context.path.join("."),
|
|
1050
|
+
message: `Failed to resolve remote resource ${options}: ${error}`
|
|
1051
|
+
}
|
|
1052
|
+
]
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
} else if (isLastLevel) {
|
|
1056
|
+
levelOptions = options;
|
|
1057
|
+
}
|
|
1058
|
+
const selectableOptions = levelOptions.flatMap((candidate) => {
|
|
1059
|
+
if (isLastLevel && (field.type === "file" && !candidate.file || field.type === "folder" && candidate.file)) {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
if (!isLastLevel && candidate.file) {
|
|
1063
|
+
return [];
|
|
1064
|
+
}
|
|
1065
|
+
return candidate;
|
|
1066
|
+
});
|
|
1067
|
+
const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
|
|
1068
|
+
if (!selectedOption) {
|
|
1069
|
+
return {
|
|
1070
|
+
valid: false,
|
|
1071
|
+
errors: [
|
|
1072
|
+
...errors,
|
|
1073
|
+
{
|
|
1074
|
+
domain: context.domain,
|
|
1075
|
+
path: context.path.join("."),
|
|
1076
|
+
message: `Path '${levelSelectedValue}' not found in options.`
|
|
1077
|
+
}
|
|
1078
|
+
]
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
selectedPath.push(selectedOption);
|
|
1082
|
+
}
|
|
1083
|
+
if (ids) {
|
|
1084
|
+
context.roots[context.domain].fieldStates.push({
|
|
1085
|
+
path: context.path,
|
|
1086
|
+
state: {
|
|
1087
|
+
mode: "chose",
|
|
1088
|
+
path: selectedPath.map(({ label, value: value2 }) => label ?? value2)
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
if (nested) {
|
|
1093
|
+
const result = await handleNestedFields(nested, value, field, context);
|
|
1094
|
+
errors.push(...result.errors);
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
valid: errors.length === 0,
|
|
1098
|
+
errors
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
async function handleSelectType(value, field, context) {
|
|
921
1102
|
const errors = [];
|
|
922
1103
|
let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
923
1104
|
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
@@ -1189,6 +1370,19 @@ function toFormanSchema(field) {
|
|
|
1189
1370
|
}
|
|
1190
1371
|
return formanSchema;
|
|
1191
1372
|
case "string":
|
|
1373
|
+
const pathInfo = Object.getOwnPropertyDescriptor(field, "x-path");
|
|
1374
|
+
if (pathInfo) {
|
|
1375
|
+
return {
|
|
1376
|
+
type: pathInfo.value.type,
|
|
1377
|
+
label: noEmpty(field.title),
|
|
1378
|
+
help: noEmpty(field.description),
|
|
1379
|
+
options: {
|
|
1380
|
+
store: Object.getOwnPropertyDescriptor(field, "x-fetch")?.value,
|
|
1381
|
+
showRoot: pathInfo.value.showRoot,
|
|
1382
|
+
singleLevel: pathInfo.value.singleLevel
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1192
1386
|
if (field.enum) {
|
|
1193
1387
|
return {
|
|
1194
1388
|
type: "select",
|
|
@@ -1204,7 +1398,7 @@ function toFormanSchema(field) {
|
|
|
1204
1398
|
options: field.oneOf.filter((value) => value).map((value) => ({ value: value.const }))
|
|
1205
1399
|
};
|
|
1206
1400
|
}
|
|
1207
|
-
|
|
1401
|
+
let textField = {
|
|
1208
1402
|
type: "text",
|
|
1209
1403
|
label: noEmpty(field.title),
|
|
1210
1404
|
help: noEmpty(field.description),
|
|
@@ -1215,9 +1409,12 @@ function toFormanSchema(field) {
|
|
|
1215
1409
|
if (field.pattern) textField.validate.pattern = field.pattern;
|
|
1216
1410
|
if (field.enum) textField.validate.enum = field.enum;
|
|
1217
1411
|
}
|
|
1412
|
+
if ("x-search" in field) {
|
|
1413
|
+
textField = handleSearchDirective(textField, field["x-search"]);
|
|
1414
|
+
}
|
|
1218
1415
|
return textField;
|
|
1219
1416
|
default:
|
|
1220
|
-
|
|
1417
|
+
let primitiveField = {
|
|
1221
1418
|
type: JSON_PRIMITIVE_TYPE_MAP[field.type] || "any",
|
|
1222
1419
|
label: noEmpty(field.title),
|
|
1223
1420
|
help: noEmpty(field.description),
|
|
@@ -1228,9 +1425,20 @@ function toFormanSchema(field) {
|
|
|
1228
1425
|
if (field.minimum !== void 0) primitiveField.validate.min = field.minimum;
|
|
1229
1426
|
if (field.maximum !== void 0) primitiveField.validate.max = field.maximum;
|
|
1230
1427
|
}
|
|
1428
|
+
if ("x-search" in field) {
|
|
1429
|
+
primitiveField = handleSearchDirective(primitiveField, field["x-search"]);
|
|
1430
|
+
}
|
|
1231
1431
|
return primitiveField;
|
|
1232
1432
|
}
|
|
1233
1433
|
}
|
|
1434
|
+
function handleSearchDirective(formanField, directive) {
|
|
1435
|
+
formanField.rpc = {
|
|
1436
|
+
url: directive.url,
|
|
1437
|
+
label: directive.label,
|
|
1438
|
+
parameters: typeof directive.inputSchema.$ref === "string" ? directive.inputSchema.$ref : toFormanSchema(directive.inputSchema).spec
|
|
1439
|
+
};
|
|
1440
|
+
return formanField;
|
|
1441
|
+
}
|
|
1234
1442
|
|
|
1235
1443
|
// src/index.ts
|
|
1236
1444
|
function toJSONSchema(field) {
|
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' | '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}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
|
|
6
|
+
type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | '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;
|
|
7
7
|
/**
|
|
8
8
|
* Validation configuration for Forman Schema fields
|
|
9
9
|
*/
|
|
@@ -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[] | FormanSchemaOptionGroup[] | FormanSchemaExtendedOptions | string;
|
|
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
|
@@ -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' | '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}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
|
|
6
|
+
type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | '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;
|
|
7
7
|
/**
|
|
8
8
|
* Validation configuration for Forman Schema fields
|
|
9
9
|
*/
|
|
@@ -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[] | FormanSchemaOptionGroup[] | FormanSchemaExtendedOptions | string;
|
|
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
|
@@ -38,7 +38,7 @@ var API_ENDPOINTS = {
|
|
|
38
38
|
aiagent: "api://ai-agents/v1/agents",
|
|
39
39
|
datastore: "api://data-stores",
|
|
40
40
|
hook: "api://hooks/{{kind}}",
|
|
41
|
-
device: "api://devices",
|
|
41
|
+
device: "api://devices/{{kind}}",
|
|
42
42
|
keychain: "api://keys/{{kind}}",
|
|
43
43
|
udt: "api://data-structures",
|
|
44
44
|
scenario: "api://scenario-list"
|
|
@@ -281,16 +281,17 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
|
|
|
281
281
|
case "select":
|
|
282
282
|
case "account":
|
|
283
283
|
case "hook":
|
|
284
|
+
case "device":
|
|
284
285
|
case "keychain":
|
|
285
286
|
case "datastore":
|
|
286
287
|
case "aiagent":
|
|
287
288
|
case "file":
|
|
288
289
|
case "folder":
|
|
289
|
-
return
|
|
290
|
+
return handleSelectOrPathType(normalizedField, result, context);
|
|
290
291
|
case "filter":
|
|
291
292
|
return handleFilterType(normalizedField, result, context);
|
|
292
293
|
default:
|
|
293
|
-
return handlePrimitiveType(normalizedField, result);
|
|
294
|
+
return handlePrimitiveType(normalizedField, result, context);
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
function handleCollectionType(field, result, context) {
|
|
@@ -406,8 +407,22 @@ function handleFilterType(field, result, context) {
|
|
|
406
407
|
});
|
|
407
408
|
return result;
|
|
408
409
|
}
|
|
409
|
-
function
|
|
410
|
+
function handleSelectOrPathType(field, result, context) {
|
|
410
411
|
const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
412
|
+
if (["file", "folder"].includes(field.type)) {
|
|
413
|
+
const optionsWrapper = isObject(field.options) ? field.options : void 0;
|
|
414
|
+
Object.defineProperty(result, "x-path", {
|
|
415
|
+
configurable: true,
|
|
416
|
+
enumerable: true,
|
|
417
|
+
writable: true,
|
|
418
|
+
value: {
|
|
419
|
+
type: field.type,
|
|
420
|
+
showRoot: optionsWrapper?.showRoot ?? true,
|
|
421
|
+
singleLevel: optionsWrapper?.singleLevel ?? false,
|
|
422
|
+
ownName: field.name
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
411
426
|
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
|
|
412
427
|
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
413
428
|
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
|
|
@@ -516,9 +531,10 @@ function handleSelectType(field, result, context) {
|
|
|
516
531
|
)
|
|
517
532
|
});
|
|
518
533
|
}
|
|
534
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
519
535
|
return result;
|
|
520
536
|
}
|
|
521
|
-
function handlePrimitiveType(field, result) {
|
|
537
|
+
function handlePrimitiveType(field, result, context) {
|
|
522
538
|
if (field.default !== "" && field.default != null) {
|
|
523
539
|
result.default = field.default;
|
|
524
540
|
}
|
|
@@ -536,6 +552,21 @@ function handlePrimitiveType(field, result) {
|
|
|
536
552
|
result.enum = field.validate.enum;
|
|
537
553
|
}
|
|
538
554
|
}
|
|
555
|
+
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
556
|
+
return result;
|
|
557
|
+
}
|
|
558
|
+
function processRpcDirective(field, result, context) {
|
|
559
|
+
if (!field.rpc) return result;
|
|
560
|
+
Object.defineProperty(result, "x-search", {
|
|
561
|
+
configurable: true,
|
|
562
|
+
enumerable: true,
|
|
563
|
+
writable: true,
|
|
564
|
+
value: {
|
|
565
|
+
url: appendQueryString(field.rpc.url, context.domain, context.tail),
|
|
566
|
+
label: field.rpc.label,
|
|
567
|
+
inputSchema: typeof field.rpc.parameters === "string" ? { $ref: field.rpc.parameters } : toJSONSchemaInternal({ type: "collection", spec: field.rpc.parameters }, context)
|
|
568
|
+
}
|
|
569
|
+
});
|
|
539
570
|
return result;
|
|
540
571
|
}
|
|
541
572
|
|
|
@@ -543,6 +574,7 @@ function handlePrimitiveType(field, result) {
|
|
|
543
574
|
var FORMAN_TYPE_MAP2 = {
|
|
544
575
|
account: "number",
|
|
545
576
|
hook: "number",
|
|
577
|
+
device: "number",
|
|
546
578
|
keychain: "number",
|
|
547
579
|
datastore: "number",
|
|
548
580
|
aiagent: "string",
|
|
@@ -623,7 +655,7 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
623
655
|
validateNestedFields: () => {
|
|
624
656
|
throw new Error("Cannot validate nested fields without parent field.");
|
|
625
657
|
},
|
|
626
|
-
resolveRemote: async (path, context) => {
|
|
658
|
+
resolveRemote: async (path, context, localData) => {
|
|
627
659
|
if (!options?.resolveRemote) {
|
|
628
660
|
throw new Error("Remote resource not supported when resolver is not provided.");
|
|
629
661
|
}
|
|
@@ -634,7 +666,10 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
634
666
|
},
|
|
635
667
|
{}
|
|
636
668
|
);
|
|
637
|
-
return await options.resolveRemote(path,
|
|
669
|
+
return await options.resolveRemote(path, {
|
|
670
|
+
...data,
|
|
671
|
+
...localData
|
|
672
|
+
});
|
|
638
673
|
}
|
|
639
674
|
}
|
|
640
675
|
);
|
|
@@ -721,9 +756,10 @@ async function validateFormanValue(value, field, context) {
|
|
|
721
756
|
case "aiagent":
|
|
722
757
|
case "udt":
|
|
723
758
|
case "scenario":
|
|
759
|
+
return handleSelectType(value, normalizedField, context);
|
|
724
760
|
case "file":
|
|
725
761
|
case "folder":
|
|
726
|
-
return
|
|
762
|
+
return handlePathType(value, normalizedField, context);
|
|
727
763
|
case "filter":
|
|
728
764
|
return handleFilterType2(value, normalizedField, context);
|
|
729
765
|
default:
|
|
@@ -888,7 +924,152 @@ async function handleFilterType2(value, field, context) {
|
|
|
888
924
|
};
|
|
889
925
|
return handleArrayType2(value, inlineSchema, context);
|
|
890
926
|
}
|
|
891
|
-
async function
|
|
927
|
+
async function handlePathType(value, field, context) {
|
|
928
|
+
const errors = [];
|
|
929
|
+
if (typeof value !== "string") {
|
|
930
|
+
return {
|
|
931
|
+
valid: false,
|
|
932
|
+
errors: [
|
|
933
|
+
...errors,
|
|
934
|
+
{
|
|
935
|
+
domain: context.domain,
|
|
936
|
+
path: context.path.join("."),
|
|
937
|
+
message: `Expected type 'string' for path, got type '${typeof value}'.`
|
|
938
|
+
}
|
|
939
|
+
]
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
let showRoot = true;
|
|
943
|
+
let ids = false;
|
|
944
|
+
let singleLevel = false;
|
|
945
|
+
let options;
|
|
946
|
+
if (isObject(field.options)) {
|
|
947
|
+
if (field.options.showRoot !== void 0) showRoot = field.options.showRoot;
|
|
948
|
+
if (field.options.ids !== void 0) ids = field.options.ids;
|
|
949
|
+
if (field.options.singleLevel !== void 0) singleLevel = field.options.singleLevel;
|
|
950
|
+
options = field.options.store;
|
|
951
|
+
} else {
|
|
952
|
+
options = field.options;
|
|
953
|
+
}
|
|
954
|
+
if (singleLevel && (!showRoot && value.includes("/") || showRoot && value.lastIndexOf("/") > 0)) {
|
|
955
|
+
return {
|
|
956
|
+
valid: false,
|
|
957
|
+
errors: [
|
|
958
|
+
...errors,
|
|
959
|
+
{
|
|
960
|
+
domain: context.domain,
|
|
961
|
+
path: context.path.join("."),
|
|
962
|
+
message: `Single level path cannot contain slashes.`
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
968
|
+
const levels = value.split("/");
|
|
969
|
+
if (levels[0] !== "") levels.unshift("");
|
|
970
|
+
if (showRoot && value === "/" && field.type === "folder") {
|
|
971
|
+
return {
|
|
972
|
+
valid: true,
|
|
973
|
+
errors: []
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
if (levels.length < 2) {
|
|
977
|
+
return {
|
|
978
|
+
valid: false,
|
|
979
|
+
errors: [
|
|
980
|
+
...errors,
|
|
981
|
+
{
|
|
982
|
+
domain: context.domain,
|
|
983
|
+
path: context.path.join("."),
|
|
984
|
+
message: `Invalid path "${value}" encountered.`
|
|
985
|
+
}
|
|
986
|
+
]
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
const selectedPath = [];
|
|
990
|
+
let levelOptions = [];
|
|
991
|
+
for (let levelIndex = 0; levelIndex < levels.length - 1; ++levelIndex) {
|
|
992
|
+
const isLastLevel = levelIndex === levels.length - 2;
|
|
993
|
+
const levelSelectedValue = levels[levelIndex + 1];
|
|
994
|
+
const selectedPathValue = selectedPath.map(({ value: value2 }) => value2).join("/");
|
|
995
|
+
if (!levelSelectedValue) {
|
|
996
|
+
return {
|
|
997
|
+
valid: false,
|
|
998
|
+
errors: [
|
|
999
|
+
...errors,
|
|
1000
|
+
{
|
|
1001
|
+
domain: context.domain,
|
|
1002
|
+
path: context.path.join("."),
|
|
1003
|
+
message: `Invalid selected value of "${value}" encountered.`
|
|
1004
|
+
}
|
|
1005
|
+
]
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
if (typeof options === "string") {
|
|
1009
|
+
try {
|
|
1010
|
+
levelOptions = await context.resolveRemote(options, context, {
|
|
1011
|
+
[field.name]: (showRoot && !selectedPathValue.startsWith("/") ? "/" : "") + selectedPathValue
|
|
1012
|
+
});
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
return {
|
|
1015
|
+
valid: false,
|
|
1016
|
+
errors: [
|
|
1017
|
+
...errors,
|
|
1018
|
+
{
|
|
1019
|
+
domain: context.domain,
|
|
1020
|
+
path: context.path.join("."),
|
|
1021
|
+
message: `Failed to resolve remote resource ${options}: ${error}`
|
|
1022
|
+
}
|
|
1023
|
+
]
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
} else if (isLastLevel) {
|
|
1027
|
+
levelOptions = options;
|
|
1028
|
+
}
|
|
1029
|
+
const selectableOptions = levelOptions.flatMap((candidate) => {
|
|
1030
|
+
if (isLastLevel && (field.type === "file" && !candidate.file || field.type === "folder" && candidate.file)) {
|
|
1031
|
+
return [];
|
|
1032
|
+
}
|
|
1033
|
+
if (!isLastLevel && candidate.file) {
|
|
1034
|
+
return [];
|
|
1035
|
+
}
|
|
1036
|
+
return candidate;
|
|
1037
|
+
});
|
|
1038
|
+
const selectedOption = selectableOptions.find((candidate) => candidate.value === levelSelectedValue);
|
|
1039
|
+
if (!selectedOption) {
|
|
1040
|
+
return {
|
|
1041
|
+
valid: false,
|
|
1042
|
+
errors: [
|
|
1043
|
+
...errors,
|
|
1044
|
+
{
|
|
1045
|
+
domain: context.domain,
|
|
1046
|
+
path: context.path.join("."),
|
|
1047
|
+
message: `Path '${levelSelectedValue}' not found in options.`
|
|
1048
|
+
}
|
|
1049
|
+
]
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
selectedPath.push(selectedOption);
|
|
1053
|
+
}
|
|
1054
|
+
if (ids) {
|
|
1055
|
+
context.roots[context.domain].fieldStates.push({
|
|
1056
|
+
path: context.path,
|
|
1057
|
+
state: {
|
|
1058
|
+
mode: "chose",
|
|
1059
|
+
path: selectedPath.map(({ label, value: value2 }) => label ?? value2)
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
if (nested) {
|
|
1064
|
+
const result = await handleNestedFields(nested, value, field, context);
|
|
1065
|
+
errors.push(...result.errors);
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
valid: errors.length === 0,
|
|
1069
|
+
errors
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
async function handleSelectType(value, field, context) {
|
|
892
1073
|
const errors = [];
|
|
893
1074
|
let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
894
1075
|
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
@@ -1160,6 +1341,19 @@ function toFormanSchema(field) {
|
|
|
1160
1341
|
}
|
|
1161
1342
|
return formanSchema;
|
|
1162
1343
|
case "string":
|
|
1344
|
+
const pathInfo = Object.getOwnPropertyDescriptor(field, "x-path");
|
|
1345
|
+
if (pathInfo) {
|
|
1346
|
+
return {
|
|
1347
|
+
type: pathInfo.value.type,
|
|
1348
|
+
label: noEmpty(field.title),
|
|
1349
|
+
help: noEmpty(field.description),
|
|
1350
|
+
options: {
|
|
1351
|
+
store: Object.getOwnPropertyDescriptor(field, "x-fetch")?.value,
|
|
1352
|
+
showRoot: pathInfo.value.showRoot,
|
|
1353
|
+
singleLevel: pathInfo.value.singleLevel
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1163
1357
|
if (field.enum) {
|
|
1164
1358
|
return {
|
|
1165
1359
|
type: "select",
|
|
@@ -1175,7 +1369,7 @@ function toFormanSchema(field) {
|
|
|
1175
1369
|
options: field.oneOf.filter((value) => value).map((value) => ({ value: value.const }))
|
|
1176
1370
|
};
|
|
1177
1371
|
}
|
|
1178
|
-
|
|
1372
|
+
let textField = {
|
|
1179
1373
|
type: "text",
|
|
1180
1374
|
label: noEmpty(field.title),
|
|
1181
1375
|
help: noEmpty(field.description),
|
|
@@ -1186,9 +1380,12 @@ function toFormanSchema(field) {
|
|
|
1186
1380
|
if (field.pattern) textField.validate.pattern = field.pattern;
|
|
1187
1381
|
if (field.enum) textField.validate.enum = field.enum;
|
|
1188
1382
|
}
|
|
1383
|
+
if ("x-search" in field) {
|
|
1384
|
+
textField = handleSearchDirective(textField, field["x-search"]);
|
|
1385
|
+
}
|
|
1189
1386
|
return textField;
|
|
1190
1387
|
default:
|
|
1191
|
-
|
|
1388
|
+
let primitiveField = {
|
|
1192
1389
|
type: JSON_PRIMITIVE_TYPE_MAP[field.type] || "any",
|
|
1193
1390
|
label: noEmpty(field.title),
|
|
1194
1391
|
help: noEmpty(field.description),
|
|
@@ -1199,9 +1396,20 @@ function toFormanSchema(field) {
|
|
|
1199
1396
|
if (field.minimum !== void 0) primitiveField.validate.min = field.minimum;
|
|
1200
1397
|
if (field.maximum !== void 0) primitiveField.validate.max = field.maximum;
|
|
1201
1398
|
}
|
|
1399
|
+
if ("x-search" in field) {
|
|
1400
|
+
primitiveField = handleSearchDirective(primitiveField, field["x-search"]);
|
|
1401
|
+
}
|
|
1202
1402
|
return primitiveField;
|
|
1203
1403
|
}
|
|
1204
1404
|
}
|
|
1405
|
+
function handleSearchDirective(formanField, directive) {
|
|
1406
|
+
formanField.rpc = {
|
|
1407
|
+
url: directive.url,
|
|
1408
|
+
label: directive.label,
|
|
1409
|
+
parameters: typeof directive.inputSchema.$ref === "string" ? directive.inputSchema.$ref : toFormanSchema(directive.inputSchema).spec
|
|
1410
|
+
};
|
|
1411
|
+
return formanField;
|
|
1412
|
+
}
|
|
1205
1413
|
|
|
1206
1414
|
// src/index.ts
|
|
1207
1415
|
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.0",
|
|
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",
|