@makehq/forman-schema 1.8.2 → 1.8.4
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 +486 -73
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +486 -73
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -223,6 +223,248 @@ var IML_BINARY_FILTER_OPERATORS = [
|
|
|
223
223
|
];
|
|
224
224
|
var IML_FILTER_OPERATORS = [...IML_UNARY_FILTER_OPERATORS, ...IML_BINARY_FILTER_OPERATORS];
|
|
225
225
|
|
|
226
|
+
// src/composites/udttype.ts
|
|
227
|
+
function udttypeExpand(field) {
|
|
228
|
+
field.type = "select";
|
|
229
|
+
field.options = {
|
|
230
|
+
store: [
|
|
231
|
+
{
|
|
232
|
+
label: "Array",
|
|
233
|
+
value: "array",
|
|
234
|
+
nested: [
|
|
235
|
+
{
|
|
236
|
+
name: "spec",
|
|
237
|
+
type: "collection",
|
|
238
|
+
label: "Array Item Specification",
|
|
239
|
+
spec: [
|
|
240
|
+
{
|
|
241
|
+
name: "type",
|
|
242
|
+
label: "Type",
|
|
243
|
+
type: "udttype",
|
|
244
|
+
required: true,
|
|
245
|
+
default: "text"
|
|
246
|
+
}
|
|
247
|
+
],
|
|
248
|
+
mappable: false
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "required",
|
|
252
|
+
label: "Required",
|
|
253
|
+
type: "boolean",
|
|
254
|
+
required: true,
|
|
255
|
+
default: false,
|
|
256
|
+
mappable: false
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
label: "Collection",
|
|
262
|
+
value: "collection",
|
|
263
|
+
nested: [
|
|
264
|
+
{
|
|
265
|
+
name: "spec",
|
|
266
|
+
label: "Specification",
|
|
267
|
+
type: "udtspec"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "sequence",
|
|
271
|
+
label: "Preserve the order of object keys",
|
|
272
|
+
type: "boolean",
|
|
273
|
+
mappable: false
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "required",
|
|
277
|
+
label: "Required",
|
|
278
|
+
type: "boolean",
|
|
279
|
+
required: true,
|
|
280
|
+
default: false,
|
|
281
|
+
mappable: false
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
label: "Date",
|
|
287
|
+
value: "date",
|
|
288
|
+
nested: [
|
|
289
|
+
{
|
|
290
|
+
name: "required",
|
|
291
|
+
label: "Required",
|
|
292
|
+
type: "boolean",
|
|
293
|
+
required: true,
|
|
294
|
+
default: false,
|
|
295
|
+
mappable: false
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
label: "Text",
|
|
301
|
+
value: "text",
|
|
302
|
+
nested: [
|
|
303
|
+
{
|
|
304
|
+
name: "default",
|
|
305
|
+
label: "Default value",
|
|
306
|
+
placeholder: "Enter default value",
|
|
307
|
+
type: "text"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "required",
|
|
311
|
+
label: "Required",
|
|
312
|
+
type: "boolean",
|
|
313
|
+
required: true,
|
|
314
|
+
default: false,
|
|
315
|
+
mappable: false
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "multiline",
|
|
319
|
+
label: "Multi-line",
|
|
320
|
+
type: "boolean",
|
|
321
|
+
required: true,
|
|
322
|
+
default: false,
|
|
323
|
+
mappable: false
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
label: "Number",
|
|
329
|
+
value: "number",
|
|
330
|
+
nested: [
|
|
331
|
+
{
|
|
332
|
+
name: "default",
|
|
333
|
+
label: "Default value",
|
|
334
|
+
placeholder: "Enter default value",
|
|
335
|
+
type: "number"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "required",
|
|
339
|
+
label: "Required",
|
|
340
|
+
type: "boolean",
|
|
341
|
+
required: true,
|
|
342
|
+
default: false,
|
|
343
|
+
mappable: false
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
label: "Boolean",
|
|
349
|
+
value: "boolean",
|
|
350
|
+
nested: [
|
|
351
|
+
{
|
|
352
|
+
name: "default",
|
|
353
|
+
label: "Default value",
|
|
354
|
+
placeholder: "Enter default value",
|
|
355
|
+
type: "boolean",
|
|
356
|
+
mappable: false
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "required",
|
|
360
|
+
label: "Required",
|
|
361
|
+
type: "boolean",
|
|
362
|
+
required: true,
|
|
363
|
+
default: false,
|
|
364
|
+
mappable: false
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
label: "Binary Data",
|
|
370
|
+
value: "buffer",
|
|
371
|
+
nested: [
|
|
372
|
+
{
|
|
373
|
+
name: "required",
|
|
374
|
+
label: "Required",
|
|
375
|
+
type: "boolean",
|
|
376
|
+
required: true,
|
|
377
|
+
default: false,
|
|
378
|
+
mappable: false
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "codepage",
|
|
382
|
+
label: "Codepage",
|
|
383
|
+
type: "text",
|
|
384
|
+
help: "Possible values: `binary`, `utf8`. Leave empty if you're not sure."
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
]
|
|
389
|
+
};
|
|
390
|
+
return field;
|
|
391
|
+
}
|
|
392
|
+
function udttypeExtractInner(schema) {
|
|
393
|
+
const { title, description, ...inner } = schema;
|
|
394
|
+
return inner;
|
|
395
|
+
}
|
|
396
|
+
function udttypeWrapRef(ref, field) {
|
|
397
|
+
return {
|
|
398
|
+
allOf: [{ $ref: ref }],
|
|
399
|
+
title: noEmpty(field.label),
|
|
400
|
+
description: noEmpty(field.help),
|
|
401
|
+
...field.default !== "" && field.default != null ? { default: field.default } : {}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function udttypeCollapse(field) {
|
|
405
|
+
return {
|
|
406
|
+
type: "udttype",
|
|
407
|
+
label: noEmpty(field.title),
|
|
408
|
+
help: noEmpty(field.description),
|
|
409
|
+
...field.default !== "" && field.default != null ? { default: field.default } : {}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/composites/udtspec.ts
|
|
414
|
+
function udtspecExpand(field) {
|
|
415
|
+
field.type = "array";
|
|
416
|
+
field.spec = [
|
|
417
|
+
{
|
|
418
|
+
name: "name",
|
|
419
|
+
label: "Name",
|
|
420
|
+
placeholder: "Enter name",
|
|
421
|
+
type: "text",
|
|
422
|
+
required: true
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: "label",
|
|
426
|
+
label: "Label",
|
|
427
|
+
help: "Display name for better readability.",
|
|
428
|
+
type: "text",
|
|
429
|
+
advanced: true
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "help",
|
|
433
|
+
label: "Description",
|
|
434
|
+
type: "text",
|
|
435
|
+
multiline: true,
|
|
436
|
+
required: false,
|
|
437
|
+
placeholder: "Enter description"
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "type",
|
|
441
|
+
label: "Type",
|
|
442
|
+
type: "udttype",
|
|
443
|
+
required: true,
|
|
444
|
+
default: "text"
|
|
445
|
+
}
|
|
446
|
+
];
|
|
447
|
+
return field;
|
|
448
|
+
}
|
|
449
|
+
function udtspecExtractInner(schema) {
|
|
450
|
+
return schema.items;
|
|
451
|
+
}
|
|
452
|
+
function udtspecWrapRef(ref, field) {
|
|
453
|
+
return {
|
|
454
|
+
type: "array",
|
|
455
|
+
title: noEmpty(field.label),
|
|
456
|
+
description: noEmpty(field.help),
|
|
457
|
+
items: { $ref: ref }
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function udtspecCollapse(field) {
|
|
461
|
+
return {
|
|
462
|
+
type: "udtspec",
|
|
463
|
+
label: noEmpty(field.title),
|
|
464
|
+
help: noEmpty(field.description)
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
226
468
|
// src/forman.ts
|
|
227
469
|
var SchemaConversionError = class extends Error {
|
|
228
470
|
/** Field that caused the error */
|
|
@@ -269,6 +511,8 @@ var FORMAN_TYPE_MAP = {
|
|
|
269
511
|
pkey: "string",
|
|
270
512
|
port: "number",
|
|
271
513
|
select: "string",
|
|
514
|
+
udttype: "string",
|
|
515
|
+
udtspec: "array",
|
|
272
516
|
time: "string",
|
|
273
517
|
timestamp: "string",
|
|
274
518
|
timezone: "string",
|
|
@@ -298,14 +542,52 @@ function createDefaultContext() {
|
|
|
298
542
|
tail: [],
|
|
299
543
|
path: [],
|
|
300
544
|
roots: {},
|
|
545
|
+
definitions: {},
|
|
301
546
|
addConditionalFields: () => {
|
|
302
547
|
throw new SchemaConversionError("Cannot serialize nested fields without parent field.");
|
|
303
548
|
}
|
|
304
549
|
};
|
|
305
550
|
}
|
|
306
|
-
|
|
551
|
+
var compositeHandlers = {
|
|
552
|
+
udtspec: { expand: udtspecExpand, extractInner: udtspecExtractInner, wrapRef: udtspecWrapRef },
|
|
553
|
+
udttype: { expand: udttypeExpand, extractInner: udttypeExtractInner, wrapRef: udttypeWrapRef }
|
|
554
|
+
};
|
|
555
|
+
function toJSONSchemaInternal(field, context) {
|
|
307
556
|
validateFormanField(field);
|
|
308
557
|
const normalizedField = normalizeFormanFieldType(field);
|
|
558
|
+
const handler = compositeHandlers[normalizedField.type];
|
|
559
|
+
if (handler) {
|
|
560
|
+
const type = normalizedField.type;
|
|
561
|
+
const ref = `#/definitions/${type}`;
|
|
562
|
+
if (!context.definitions?.[type]) {
|
|
563
|
+
if (context.definitions) {
|
|
564
|
+
context.definitions[type] = {};
|
|
565
|
+
}
|
|
566
|
+
const expandField = { ...normalizedField };
|
|
567
|
+
delete expandField.label;
|
|
568
|
+
delete expandField.help;
|
|
569
|
+
const expanded = handler.expand(expandField);
|
|
570
|
+
const expandedResult = toJSONSchemaInternal(expanded, context);
|
|
571
|
+
const inner = handler.extractInner(expandedResult);
|
|
572
|
+
Object.defineProperty(inner, "x-composite", {
|
|
573
|
+
configurable: true,
|
|
574
|
+
enumerable: true,
|
|
575
|
+
writable: true,
|
|
576
|
+
value: type
|
|
577
|
+
});
|
|
578
|
+
if (context.definitions) {
|
|
579
|
+
context.definitions[type] = inner;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const wrapper = handler.wrapRef(ref, normalizedField);
|
|
583
|
+
Object.defineProperty(wrapper, "x-composite", {
|
|
584
|
+
configurable: true,
|
|
585
|
+
enumerable: true,
|
|
586
|
+
writable: true,
|
|
587
|
+
value: type
|
|
588
|
+
});
|
|
589
|
+
return wrapper;
|
|
590
|
+
}
|
|
309
591
|
const result = {
|
|
310
592
|
type: FORMAN_TYPE_MAP[normalizedField.type],
|
|
311
593
|
title: noEmpty(normalizedField.label),
|
|
@@ -412,31 +694,92 @@ function handleArrayType(field, result, context) {
|
|
|
412
694
|
return result;
|
|
413
695
|
}
|
|
414
696
|
function handleFilterType(field, result, context) {
|
|
415
|
-
const
|
|
697
|
+
const operandOptions = isObject(field.options) ? field.options.store : field.options;
|
|
698
|
+
const operandSchema = {
|
|
699
|
+
title: "Operand A",
|
|
700
|
+
description: "The first operand of the filter."
|
|
701
|
+
};
|
|
702
|
+
if (Array.isArray(operandOptions)) {
|
|
703
|
+
operandSchema.type = "string";
|
|
704
|
+
operandSchema.oneOf = operandOptions.flatMap((optionOrGroup) => {
|
|
705
|
+
if (isOptionGroup(optionOrGroup)) {
|
|
706
|
+
return optionOrGroup.options.map((option) => ({
|
|
707
|
+
title: `${optionOrGroup.label}: ${option.label || option.value}`,
|
|
708
|
+
const: option.value
|
|
709
|
+
}));
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
title: optionOrGroup.label || String(optionOrGroup.value),
|
|
713
|
+
const: optionOrGroup.value
|
|
714
|
+
};
|
|
715
|
+
});
|
|
716
|
+
} else if (typeof operandOptions === "string") {
|
|
717
|
+
operandSchema.type = "string";
|
|
718
|
+
Object.defineProperty(operandSchema, "x-fetch", {
|
|
719
|
+
configurable: true,
|
|
720
|
+
enumerable: true,
|
|
721
|
+
writable: true,
|
|
722
|
+
value: appendQueryString(operandOptions, context.domain, context.tail)
|
|
723
|
+
});
|
|
724
|
+
} else {
|
|
725
|
+
operandSchema.type = IML_FILTER_ENTRY_TYPES;
|
|
726
|
+
}
|
|
727
|
+
const operatorOptions = isObject(field.options) ? field.options.operators : void 0;
|
|
728
|
+
const operatorSchema = {
|
|
729
|
+
title: "Operator",
|
|
730
|
+
description: "The operator of the filter."
|
|
731
|
+
};
|
|
732
|
+
if (Array.isArray(operatorOptions)) {
|
|
733
|
+
operatorSchema.type = "string";
|
|
734
|
+
operatorSchema.oneOf = operatorOptions.flatMap((optionOrGroup) => {
|
|
735
|
+
if (isOptionGroup(optionOrGroup)) {
|
|
736
|
+
return optionOrGroup.options.map((option) => ({
|
|
737
|
+
title: `${optionOrGroup.label}: ${option.label || option.value}`,
|
|
738
|
+
const: option.value
|
|
739
|
+
}));
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
title: optionOrGroup.label || String(optionOrGroup.value),
|
|
743
|
+
const: optionOrGroup.value
|
|
744
|
+
};
|
|
745
|
+
});
|
|
746
|
+
} else {
|
|
747
|
+
operatorSchema.enum = IML_BINARY_FILTER_OPERATORS;
|
|
748
|
+
}
|
|
749
|
+
const filterDefinition = {
|
|
416
750
|
oneOf: [
|
|
417
751
|
{
|
|
418
752
|
type: "object",
|
|
419
753
|
properties: {
|
|
420
|
-
a:
|
|
421
|
-
o:
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
properties: {
|
|
428
|
-
a: { type: IML_FILTER_ENTRY_TYPES },
|
|
429
|
-
b: { type: IML_FILTER_ENTRY_TYPES },
|
|
430
|
-
o: { enum: IML_BINARY_FILTER_OPERATORS }
|
|
754
|
+
a: operandSchema,
|
|
755
|
+
o: operatorSchema,
|
|
756
|
+
b: {
|
|
757
|
+
title: "Operand B",
|
|
758
|
+
description: "The second operand of the filter.",
|
|
759
|
+
type: IML_FILTER_ENTRY_TYPES
|
|
760
|
+
}
|
|
431
761
|
},
|
|
432
762
|
required: ["a", "b", "o"]
|
|
433
763
|
}
|
|
434
764
|
]
|
|
435
765
|
};
|
|
766
|
+
if (!operatorOptions) {
|
|
767
|
+
filterDefinition.oneOf?.push({
|
|
768
|
+
type: "object",
|
|
769
|
+
properties: {
|
|
770
|
+
a: operandSchema,
|
|
771
|
+
o: {
|
|
772
|
+
...operatorSchema,
|
|
773
|
+
enum: IML_UNARY_FILTER_OPERATORS
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
required: ["a", "o"]
|
|
777
|
+
});
|
|
778
|
+
}
|
|
436
779
|
const logic = field.logic ?? "default";
|
|
437
|
-
result.items = ["and", "or"].includes(logic) ?
|
|
780
|
+
result.items = ["and", "or"].includes(logic) ? filterDefinition : {
|
|
438
781
|
type: "array",
|
|
439
|
-
items:
|
|
782
|
+
items: filterDefinition
|
|
440
783
|
};
|
|
441
784
|
Object.defineProperty(result, "x-filter", {
|
|
442
785
|
configurable: true,
|
|
@@ -462,9 +805,7 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
462
805
|
}
|
|
463
806
|
});
|
|
464
807
|
}
|
|
465
|
-
const nested
|
|
466
|
-
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
467
|
-
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
|
|
808
|
+
const { nested, domain } = extractNestedAndDomain(field);
|
|
468
809
|
if (typeof optionsOrGroups === "string") {
|
|
469
810
|
Object.defineProperty(result, "x-fetch", {
|
|
470
811
|
configurable: true,
|
|
@@ -533,46 +874,7 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
533
874
|
result.enum = (options || []).map((option) => option.value);
|
|
534
875
|
}
|
|
535
876
|
}
|
|
536
|
-
|
|
537
|
-
const normalizedNested = typeof nested === "string" ? [nested] : nested;
|
|
538
|
-
let root = context.roots[domain];
|
|
539
|
-
if (!root) {
|
|
540
|
-
const buffer = [];
|
|
541
|
-
root = context.roots[domain] = {
|
|
542
|
-
buffer,
|
|
543
|
-
addFields: (nested2, tail) => {
|
|
544
|
-
buffer.push(...nested2.map((field2) => ({ field: field2, tail })));
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
root.addFields(normalizedNested, [...context.tail, field.name]);
|
|
549
|
-
} else if (nested) {
|
|
550
|
-
Object.defineProperty(result, "x-nested", {
|
|
551
|
-
configurable: true,
|
|
552
|
-
enumerable: true,
|
|
553
|
-
writable: true,
|
|
554
|
-
value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : nestedContainsStrings ? {
|
|
555
|
-
type: "object",
|
|
556
|
-
allOf: nested.map(
|
|
557
|
-
(item) => typeof item === "string" ? { $ref: appendQueryString(item, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
|
|
558
|
-
{ type: "collection", spec: [item] },
|
|
559
|
-
{
|
|
560
|
-
...context,
|
|
561
|
-
domain: domain || context.domain,
|
|
562
|
-
tail: [...context.tail, field.name]
|
|
563
|
-
}
|
|
564
|
-
)
|
|
565
|
-
)
|
|
566
|
-
} : toJSONSchemaInternal(
|
|
567
|
-
{ type: "collection", spec: nested },
|
|
568
|
-
{
|
|
569
|
-
...context,
|
|
570
|
-
domain: domain || context.domain,
|
|
571
|
-
tail: [...context.tail, field.name]
|
|
572
|
-
}
|
|
573
|
-
)
|
|
574
|
-
});
|
|
575
|
-
}
|
|
877
|
+
result = handleNestedWithDomain(field, nested, domain, result, context);
|
|
576
878
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
577
879
|
return result;
|
|
578
880
|
}
|
|
@@ -595,6 +897,10 @@ function handlePrimitiveType(field, result, context) {
|
|
|
595
897
|
}
|
|
596
898
|
}
|
|
597
899
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
900
|
+
if (field.nested) {
|
|
901
|
+
const { nested, domain } = extractNestedAndDomain(field);
|
|
902
|
+
result = handleNestedWithDomain(field, nested, domain, result, context);
|
|
903
|
+
}
|
|
598
904
|
return result;
|
|
599
905
|
}
|
|
600
906
|
function processRpcDirective(field, result, context) {
|
|
@@ -611,6 +917,60 @@ function processRpcDirective(field, result, context) {
|
|
|
611
917
|
});
|
|
612
918
|
return result;
|
|
613
919
|
}
|
|
920
|
+
function extractNestedAndDomain(field) {
|
|
921
|
+
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : isObject(field.nested) ? field.nested.store : field.nested;
|
|
922
|
+
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : isObject(field.nested) && field.nested.domain ? field.nested.domain : void 0;
|
|
923
|
+
return { nested, domain };
|
|
924
|
+
}
|
|
925
|
+
function handleNestedWithDomain(field, nested, domain, result, context) {
|
|
926
|
+
if (!nested) return result;
|
|
927
|
+
if (domain && domain !== context.domain) {
|
|
928
|
+
const normalizedNested = typeof nested === "string" ? [nested] : nested;
|
|
929
|
+
let root = context.roots[domain];
|
|
930
|
+
if (!root) {
|
|
931
|
+
const buffer = [];
|
|
932
|
+
root = context.roots[domain] = {
|
|
933
|
+
buffer,
|
|
934
|
+
addFields: (nested2, tail) => {
|
|
935
|
+
buffer.push(...nested2.map((field2) => ({ field: field2, tail })));
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
root.addFields(normalizedNested, [...context.tail, field.name]);
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
return processNestedDirective(field, nested, domain, result, context);
|
|
943
|
+
}
|
|
944
|
+
function processNestedDirective(field, nested, domain, result, context) {
|
|
945
|
+
if (!nested) return result;
|
|
946
|
+
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
947
|
+
Object.defineProperty(result, "x-nested", {
|
|
948
|
+
configurable: true,
|
|
949
|
+
enumerable: true,
|
|
950
|
+
writable: true,
|
|
951
|
+
value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : nestedContainsStrings ? {
|
|
952
|
+
type: "object",
|
|
953
|
+
allOf: nested.map(
|
|
954
|
+
(item) => typeof item === "string" ? { $ref: appendQueryString(item, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
|
|
955
|
+
{ type: "collection", spec: [item] },
|
|
956
|
+
{
|
|
957
|
+
...context,
|
|
958
|
+
domain: domain || context.domain,
|
|
959
|
+
tail: [...context.tail, field.name]
|
|
960
|
+
}
|
|
961
|
+
)
|
|
962
|
+
)
|
|
963
|
+
} : toJSONSchemaInternal(
|
|
964
|
+
{ type: "collection", spec: nested },
|
|
965
|
+
{
|
|
966
|
+
...context,
|
|
967
|
+
domain: domain || context.domain,
|
|
968
|
+
tail: [...context.tail, field.name]
|
|
969
|
+
}
|
|
970
|
+
)
|
|
971
|
+
});
|
|
972
|
+
return result;
|
|
973
|
+
}
|
|
614
974
|
|
|
615
975
|
// src/validator.ts
|
|
616
976
|
var FORMAN_TYPE_MAP2 = {
|
|
@@ -645,6 +1005,8 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
645
1005
|
pkey: "string",
|
|
646
1006
|
port: "number",
|
|
647
1007
|
select: void 0,
|
|
1008
|
+
udttype: "string",
|
|
1009
|
+
udtspec: "array",
|
|
648
1010
|
time: "string",
|
|
649
1011
|
timestamp: "string",
|
|
650
1012
|
timezone: "string",
|
|
@@ -785,6 +1147,12 @@ async function validateFormanValue(value, field, context) {
|
|
|
785
1147
|
errors: []
|
|
786
1148
|
};
|
|
787
1149
|
}
|
|
1150
|
+
if (normalizedField.type === "udttype") {
|
|
1151
|
+
return validateFormanValue(value, udttypeExpand({ ...normalizedField }), context);
|
|
1152
|
+
}
|
|
1153
|
+
if (normalizedField.type === "udtspec") {
|
|
1154
|
+
return validateFormanValue(value, udtspecExpand({ ...normalizedField }), context);
|
|
1155
|
+
}
|
|
788
1156
|
switch (normalizedField.type) {
|
|
789
1157
|
case "collection":
|
|
790
1158
|
return handleCollectionType2(value, normalizedField, context);
|
|
@@ -932,24 +1300,40 @@ async function handleArrayType2(value, field, context) {
|
|
|
932
1300
|
};
|
|
933
1301
|
}
|
|
934
1302
|
async function handleFilterType2(value, field, context) {
|
|
1303
|
+
const operandOptions = isObject(field.options) ? field.options.store : field.options;
|
|
1304
|
+
const operandSpec = {
|
|
1305
|
+
name: "a",
|
|
1306
|
+
type: "any",
|
|
1307
|
+
required: true
|
|
1308
|
+
};
|
|
1309
|
+
if (Array.isArray(operandOptions)) {
|
|
1310
|
+
operandSpec.type = "select";
|
|
1311
|
+
operandSpec.options = operandOptions;
|
|
1312
|
+
}
|
|
1313
|
+
const operatorOptions = isObject(field.options) ? field.options.operators : void 0;
|
|
1314
|
+
const operatorSpec = {
|
|
1315
|
+
name: "o",
|
|
1316
|
+
type: "any",
|
|
1317
|
+
required: true
|
|
1318
|
+
};
|
|
1319
|
+
if (Array.isArray(operatorOptions)) {
|
|
1320
|
+
operatorSpec.type = "select";
|
|
1321
|
+
operatorSpec.grouped = true;
|
|
1322
|
+
operatorSpec.options = operatorOptions;
|
|
1323
|
+
} else {
|
|
1324
|
+
operatorSpec.type = "text";
|
|
1325
|
+
operatorSpec.validate = {
|
|
1326
|
+
enum: IML_FILTER_OPERATORS
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
935
1329
|
const filterEntry = {
|
|
936
1330
|
type: "collection",
|
|
937
1331
|
spec: [
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
type: "any",
|
|
941
|
-
required: true
|
|
942
|
-
},
|
|
1332
|
+
operandSpec,
|
|
1333
|
+
operatorSpec,
|
|
943
1334
|
{
|
|
944
1335
|
name: "b",
|
|
945
1336
|
type: "any"
|
|
946
|
-
},
|
|
947
|
-
{
|
|
948
|
-
name: "o",
|
|
949
|
-
type: "text",
|
|
950
|
-
validate: {
|
|
951
|
-
enum: IML_FILTER_OPERATORS
|
|
952
|
-
}
|
|
953
1337
|
}
|
|
954
1338
|
]
|
|
955
1339
|
};
|
|
@@ -1118,7 +1502,18 @@ async function handleSelectType(value, field, context) {
|
|
|
1118
1502
|
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
1119
1503
|
if (typeof optionsOrGroups === "string") {
|
|
1120
1504
|
try {
|
|
1121
|
-
|
|
1505
|
+
const resolved = await context.resolveRemote(optionsOrGroups, context);
|
|
1506
|
+
if (resolved == null || typeof resolved !== "object") {
|
|
1507
|
+
return {
|
|
1508
|
+
valid: false,
|
|
1509
|
+
errors: [...errors, {
|
|
1510
|
+
domain: context.domain,
|
|
1511
|
+
path: context.path.join("."),
|
|
1512
|
+
message: `Remote resource ${optionsOrGroups} returned no data.`
|
|
1513
|
+
}]
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
optionsOrGroups = Array.isArray(resolved) ? resolved : [resolved];
|
|
1122
1517
|
} catch (error) {
|
|
1123
1518
|
return {
|
|
1124
1519
|
valid: false,
|
|
@@ -1228,7 +1623,12 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
1228
1623
|
for (const item of store) {
|
|
1229
1624
|
if (typeof item === "string") {
|
|
1230
1625
|
try {
|
|
1231
|
-
|
|
1626
|
+
const resolved = await context.resolveRemote(item, context);
|
|
1627
|
+
if (Array.isArray(resolved)) {
|
|
1628
|
+
resolvedStore.push(...resolved);
|
|
1629
|
+
} else if (resolved && typeof resolved === "object") {
|
|
1630
|
+
resolvedStore.push(resolved);
|
|
1631
|
+
}
|
|
1232
1632
|
} catch (error) {
|
|
1233
1633
|
return {
|
|
1234
1634
|
valid: false,
|
|
@@ -1336,6 +1736,9 @@ var JSON_PRIMITIVE_TYPE_MAP = {
|
|
|
1336
1736
|
boolean: "boolean"
|
|
1337
1737
|
};
|
|
1338
1738
|
function toFormanSchema(field) {
|
|
1739
|
+
const compositeType = Object.getOwnPropertyDescriptor(field, "x-composite")?.value;
|
|
1740
|
+
if (compositeType === "udttype") return udttypeCollapse(field);
|
|
1741
|
+
if (compositeType === "udtspec") return udtspecCollapse(field);
|
|
1339
1742
|
switch (field.type) {
|
|
1340
1743
|
case "object":
|
|
1341
1744
|
if (!field.properties || !Object.entries(field.properties).length) {
|
|
@@ -1458,7 +1861,17 @@ function handleSearchDirective(formanField, directive) {
|
|
|
1458
1861
|
|
|
1459
1862
|
// src/index.ts
|
|
1460
1863
|
function toJSONSchema(field) {
|
|
1461
|
-
|
|
1864
|
+
const context = createDefaultContext();
|
|
1865
|
+
const result = toJSONSchemaInternal(field, context);
|
|
1866
|
+
if (Object.keys(context.definitions ?? {}).length > 0) {
|
|
1867
|
+
Object.defineProperty(result, "definitions", {
|
|
1868
|
+
configurable: true,
|
|
1869
|
+
enumerable: true,
|
|
1870
|
+
writable: true,
|
|
1871
|
+
value: context.definitions
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
return result;
|
|
1462
1875
|
}
|
|
1463
1876
|
function validateFormanWithDomains(domains, options) {
|
|
1464
1877
|
return validateFormanWithDomainsInternal(domains, options);
|
package/dist/index.d.cts
CHANGED
|
@@ -145,6 +145,10 @@ type FormanSchemaOptionGroup = {
|
|
|
145
145
|
/** Group options */
|
|
146
146
|
options: FormanSchemaOption[];
|
|
147
147
|
};
|
|
148
|
+
/**
|
|
149
|
+
* Helper type for store of select, where options can be either plain options, or groups
|
|
150
|
+
*/
|
|
151
|
+
type FormanSchemaSelectOptionsStore = (FormanSchemaOption | FormanSchemaOptionGroup)[];
|
|
148
152
|
/**
|
|
149
153
|
* Extended options for a select field
|
|
150
154
|
*/
|
|
@@ -162,6 +166,8 @@ type FormanSchemaExtendedOptions = {
|
|
|
162
166
|
label: string;
|
|
163
167
|
nested?: FormanSchemaNested;
|
|
164
168
|
};
|
|
169
|
+
/** Definition of field operators, applicable on Filter fields. */
|
|
170
|
+
operators?: FormanSchemaSelectOptionsStore;
|
|
165
171
|
};
|
|
166
172
|
/**
|
|
167
173
|
* Nested fields
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,10 @@ type FormanSchemaOptionGroup = {
|
|
|
145
145
|
/** Group options */
|
|
146
146
|
options: FormanSchemaOption[];
|
|
147
147
|
};
|
|
148
|
+
/**
|
|
149
|
+
* Helper type for store of select, where options can be either plain options, or groups
|
|
150
|
+
*/
|
|
151
|
+
type FormanSchemaSelectOptionsStore = (FormanSchemaOption | FormanSchemaOptionGroup)[];
|
|
148
152
|
/**
|
|
149
153
|
* Extended options for a select field
|
|
150
154
|
*/
|
|
@@ -162,6 +166,8 @@ type FormanSchemaExtendedOptions = {
|
|
|
162
166
|
label: string;
|
|
163
167
|
nested?: FormanSchemaNested;
|
|
164
168
|
};
|
|
169
|
+
/** Definition of field operators, applicable on Filter fields. */
|
|
170
|
+
operators?: FormanSchemaSelectOptionsStore;
|
|
165
171
|
};
|
|
166
172
|
/**
|
|
167
173
|
* Nested fields
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,248 @@ var IML_BINARY_FILTER_OPERATORS = [
|
|
|
194
194
|
];
|
|
195
195
|
var IML_FILTER_OPERATORS = [...IML_UNARY_FILTER_OPERATORS, ...IML_BINARY_FILTER_OPERATORS];
|
|
196
196
|
|
|
197
|
+
// src/composites/udttype.ts
|
|
198
|
+
function udttypeExpand(field) {
|
|
199
|
+
field.type = "select";
|
|
200
|
+
field.options = {
|
|
201
|
+
store: [
|
|
202
|
+
{
|
|
203
|
+
label: "Array",
|
|
204
|
+
value: "array",
|
|
205
|
+
nested: [
|
|
206
|
+
{
|
|
207
|
+
name: "spec",
|
|
208
|
+
type: "collection",
|
|
209
|
+
label: "Array Item Specification",
|
|
210
|
+
spec: [
|
|
211
|
+
{
|
|
212
|
+
name: "type",
|
|
213
|
+
label: "Type",
|
|
214
|
+
type: "udttype",
|
|
215
|
+
required: true,
|
|
216
|
+
default: "text"
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
mappable: false
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "required",
|
|
223
|
+
label: "Required",
|
|
224
|
+
type: "boolean",
|
|
225
|
+
required: true,
|
|
226
|
+
default: false,
|
|
227
|
+
mappable: false
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: "Collection",
|
|
233
|
+
value: "collection",
|
|
234
|
+
nested: [
|
|
235
|
+
{
|
|
236
|
+
name: "spec",
|
|
237
|
+
label: "Specification",
|
|
238
|
+
type: "udtspec"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "sequence",
|
|
242
|
+
label: "Preserve the order of object keys",
|
|
243
|
+
type: "boolean",
|
|
244
|
+
mappable: false
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "required",
|
|
248
|
+
label: "Required",
|
|
249
|
+
type: "boolean",
|
|
250
|
+
required: true,
|
|
251
|
+
default: false,
|
|
252
|
+
mappable: false
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
label: "Date",
|
|
258
|
+
value: "date",
|
|
259
|
+
nested: [
|
|
260
|
+
{
|
|
261
|
+
name: "required",
|
|
262
|
+
label: "Required",
|
|
263
|
+
type: "boolean",
|
|
264
|
+
required: true,
|
|
265
|
+
default: false,
|
|
266
|
+
mappable: false
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
label: "Text",
|
|
272
|
+
value: "text",
|
|
273
|
+
nested: [
|
|
274
|
+
{
|
|
275
|
+
name: "default",
|
|
276
|
+
label: "Default value",
|
|
277
|
+
placeholder: "Enter default value",
|
|
278
|
+
type: "text"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "required",
|
|
282
|
+
label: "Required",
|
|
283
|
+
type: "boolean",
|
|
284
|
+
required: true,
|
|
285
|
+
default: false,
|
|
286
|
+
mappable: false
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "multiline",
|
|
290
|
+
label: "Multi-line",
|
|
291
|
+
type: "boolean",
|
|
292
|
+
required: true,
|
|
293
|
+
default: false,
|
|
294
|
+
mappable: false
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
label: "Number",
|
|
300
|
+
value: "number",
|
|
301
|
+
nested: [
|
|
302
|
+
{
|
|
303
|
+
name: "default",
|
|
304
|
+
label: "Default value",
|
|
305
|
+
placeholder: "Enter default value",
|
|
306
|
+
type: "number"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "required",
|
|
310
|
+
label: "Required",
|
|
311
|
+
type: "boolean",
|
|
312
|
+
required: true,
|
|
313
|
+
default: false,
|
|
314
|
+
mappable: false
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
label: "Boolean",
|
|
320
|
+
value: "boolean",
|
|
321
|
+
nested: [
|
|
322
|
+
{
|
|
323
|
+
name: "default",
|
|
324
|
+
label: "Default value",
|
|
325
|
+
placeholder: "Enter default value",
|
|
326
|
+
type: "boolean",
|
|
327
|
+
mappable: false
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "required",
|
|
331
|
+
label: "Required",
|
|
332
|
+
type: "boolean",
|
|
333
|
+
required: true,
|
|
334
|
+
default: false,
|
|
335
|
+
mappable: false
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
label: "Binary Data",
|
|
341
|
+
value: "buffer",
|
|
342
|
+
nested: [
|
|
343
|
+
{
|
|
344
|
+
name: "required",
|
|
345
|
+
label: "Required",
|
|
346
|
+
type: "boolean",
|
|
347
|
+
required: true,
|
|
348
|
+
default: false,
|
|
349
|
+
mappable: false
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: "codepage",
|
|
353
|
+
label: "Codepage",
|
|
354
|
+
type: "text",
|
|
355
|
+
help: "Possible values: `binary`, `utf8`. Leave empty if you're not sure."
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
};
|
|
361
|
+
return field;
|
|
362
|
+
}
|
|
363
|
+
function udttypeExtractInner(schema) {
|
|
364
|
+
const { title, description, ...inner } = schema;
|
|
365
|
+
return inner;
|
|
366
|
+
}
|
|
367
|
+
function udttypeWrapRef(ref, field) {
|
|
368
|
+
return {
|
|
369
|
+
allOf: [{ $ref: ref }],
|
|
370
|
+
title: noEmpty(field.label),
|
|
371
|
+
description: noEmpty(field.help),
|
|
372
|
+
...field.default !== "" && field.default != null ? { default: field.default } : {}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function udttypeCollapse(field) {
|
|
376
|
+
return {
|
|
377
|
+
type: "udttype",
|
|
378
|
+
label: noEmpty(field.title),
|
|
379
|
+
help: noEmpty(field.description),
|
|
380
|
+
...field.default !== "" && field.default != null ? { default: field.default } : {}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/composites/udtspec.ts
|
|
385
|
+
function udtspecExpand(field) {
|
|
386
|
+
field.type = "array";
|
|
387
|
+
field.spec = [
|
|
388
|
+
{
|
|
389
|
+
name: "name",
|
|
390
|
+
label: "Name",
|
|
391
|
+
placeholder: "Enter name",
|
|
392
|
+
type: "text",
|
|
393
|
+
required: true
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "label",
|
|
397
|
+
label: "Label",
|
|
398
|
+
help: "Display name for better readability.",
|
|
399
|
+
type: "text",
|
|
400
|
+
advanced: true
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: "help",
|
|
404
|
+
label: "Description",
|
|
405
|
+
type: "text",
|
|
406
|
+
multiline: true,
|
|
407
|
+
required: false,
|
|
408
|
+
placeholder: "Enter description"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "type",
|
|
412
|
+
label: "Type",
|
|
413
|
+
type: "udttype",
|
|
414
|
+
required: true,
|
|
415
|
+
default: "text"
|
|
416
|
+
}
|
|
417
|
+
];
|
|
418
|
+
return field;
|
|
419
|
+
}
|
|
420
|
+
function udtspecExtractInner(schema) {
|
|
421
|
+
return schema.items;
|
|
422
|
+
}
|
|
423
|
+
function udtspecWrapRef(ref, field) {
|
|
424
|
+
return {
|
|
425
|
+
type: "array",
|
|
426
|
+
title: noEmpty(field.label),
|
|
427
|
+
description: noEmpty(field.help),
|
|
428
|
+
items: { $ref: ref }
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function udtspecCollapse(field) {
|
|
432
|
+
return {
|
|
433
|
+
type: "udtspec",
|
|
434
|
+
label: noEmpty(field.title),
|
|
435
|
+
help: noEmpty(field.description)
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
197
439
|
// src/forman.ts
|
|
198
440
|
var SchemaConversionError = class extends Error {
|
|
199
441
|
/** Field that caused the error */
|
|
@@ -240,6 +482,8 @@ var FORMAN_TYPE_MAP = {
|
|
|
240
482
|
pkey: "string",
|
|
241
483
|
port: "number",
|
|
242
484
|
select: "string",
|
|
485
|
+
udttype: "string",
|
|
486
|
+
udtspec: "array",
|
|
243
487
|
time: "string",
|
|
244
488
|
timestamp: "string",
|
|
245
489
|
timezone: "string",
|
|
@@ -269,14 +513,52 @@ function createDefaultContext() {
|
|
|
269
513
|
tail: [],
|
|
270
514
|
path: [],
|
|
271
515
|
roots: {},
|
|
516
|
+
definitions: {},
|
|
272
517
|
addConditionalFields: () => {
|
|
273
518
|
throw new SchemaConversionError("Cannot serialize nested fields without parent field.");
|
|
274
519
|
}
|
|
275
520
|
};
|
|
276
521
|
}
|
|
277
|
-
|
|
522
|
+
var compositeHandlers = {
|
|
523
|
+
udtspec: { expand: udtspecExpand, extractInner: udtspecExtractInner, wrapRef: udtspecWrapRef },
|
|
524
|
+
udttype: { expand: udttypeExpand, extractInner: udttypeExtractInner, wrapRef: udttypeWrapRef }
|
|
525
|
+
};
|
|
526
|
+
function toJSONSchemaInternal(field, context) {
|
|
278
527
|
validateFormanField(field);
|
|
279
528
|
const normalizedField = normalizeFormanFieldType(field);
|
|
529
|
+
const handler = compositeHandlers[normalizedField.type];
|
|
530
|
+
if (handler) {
|
|
531
|
+
const type = normalizedField.type;
|
|
532
|
+
const ref = `#/definitions/${type}`;
|
|
533
|
+
if (!context.definitions?.[type]) {
|
|
534
|
+
if (context.definitions) {
|
|
535
|
+
context.definitions[type] = {};
|
|
536
|
+
}
|
|
537
|
+
const expandField = { ...normalizedField };
|
|
538
|
+
delete expandField.label;
|
|
539
|
+
delete expandField.help;
|
|
540
|
+
const expanded = handler.expand(expandField);
|
|
541
|
+
const expandedResult = toJSONSchemaInternal(expanded, context);
|
|
542
|
+
const inner = handler.extractInner(expandedResult);
|
|
543
|
+
Object.defineProperty(inner, "x-composite", {
|
|
544
|
+
configurable: true,
|
|
545
|
+
enumerable: true,
|
|
546
|
+
writable: true,
|
|
547
|
+
value: type
|
|
548
|
+
});
|
|
549
|
+
if (context.definitions) {
|
|
550
|
+
context.definitions[type] = inner;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const wrapper = handler.wrapRef(ref, normalizedField);
|
|
554
|
+
Object.defineProperty(wrapper, "x-composite", {
|
|
555
|
+
configurable: true,
|
|
556
|
+
enumerable: true,
|
|
557
|
+
writable: true,
|
|
558
|
+
value: type
|
|
559
|
+
});
|
|
560
|
+
return wrapper;
|
|
561
|
+
}
|
|
280
562
|
const result = {
|
|
281
563
|
type: FORMAN_TYPE_MAP[normalizedField.type],
|
|
282
564
|
title: noEmpty(normalizedField.label),
|
|
@@ -383,31 +665,92 @@ function handleArrayType(field, result, context) {
|
|
|
383
665
|
return result;
|
|
384
666
|
}
|
|
385
667
|
function handleFilterType(field, result, context) {
|
|
386
|
-
const
|
|
668
|
+
const operandOptions = isObject(field.options) ? field.options.store : field.options;
|
|
669
|
+
const operandSchema = {
|
|
670
|
+
title: "Operand A",
|
|
671
|
+
description: "The first operand of the filter."
|
|
672
|
+
};
|
|
673
|
+
if (Array.isArray(operandOptions)) {
|
|
674
|
+
operandSchema.type = "string";
|
|
675
|
+
operandSchema.oneOf = operandOptions.flatMap((optionOrGroup) => {
|
|
676
|
+
if (isOptionGroup(optionOrGroup)) {
|
|
677
|
+
return optionOrGroup.options.map((option) => ({
|
|
678
|
+
title: `${optionOrGroup.label}: ${option.label || option.value}`,
|
|
679
|
+
const: option.value
|
|
680
|
+
}));
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
title: optionOrGroup.label || String(optionOrGroup.value),
|
|
684
|
+
const: optionOrGroup.value
|
|
685
|
+
};
|
|
686
|
+
});
|
|
687
|
+
} else if (typeof operandOptions === "string") {
|
|
688
|
+
operandSchema.type = "string";
|
|
689
|
+
Object.defineProperty(operandSchema, "x-fetch", {
|
|
690
|
+
configurable: true,
|
|
691
|
+
enumerable: true,
|
|
692
|
+
writable: true,
|
|
693
|
+
value: appendQueryString(operandOptions, context.domain, context.tail)
|
|
694
|
+
});
|
|
695
|
+
} else {
|
|
696
|
+
operandSchema.type = IML_FILTER_ENTRY_TYPES;
|
|
697
|
+
}
|
|
698
|
+
const operatorOptions = isObject(field.options) ? field.options.operators : void 0;
|
|
699
|
+
const operatorSchema = {
|
|
700
|
+
title: "Operator",
|
|
701
|
+
description: "The operator of the filter."
|
|
702
|
+
};
|
|
703
|
+
if (Array.isArray(operatorOptions)) {
|
|
704
|
+
operatorSchema.type = "string";
|
|
705
|
+
operatorSchema.oneOf = operatorOptions.flatMap((optionOrGroup) => {
|
|
706
|
+
if (isOptionGroup(optionOrGroup)) {
|
|
707
|
+
return optionOrGroup.options.map((option) => ({
|
|
708
|
+
title: `${optionOrGroup.label}: ${option.label || option.value}`,
|
|
709
|
+
const: option.value
|
|
710
|
+
}));
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
title: optionOrGroup.label || String(optionOrGroup.value),
|
|
714
|
+
const: optionOrGroup.value
|
|
715
|
+
};
|
|
716
|
+
});
|
|
717
|
+
} else {
|
|
718
|
+
operatorSchema.enum = IML_BINARY_FILTER_OPERATORS;
|
|
719
|
+
}
|
|
720
|
+
const filterDefinition = {
|
|
387
721
|
oneOf: [
|
|
388
722
|
{
|
|
389
723
|
type: "object",
|
|
390
724
|
properties: {
|
|
391
|
-
a:
|
|
392
|
-
o:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
properties: {
|
|
399
|
-
a: { type: IML_FILTER_ENTRY_TYPES },
|
|
400
|
-
b: { type: IML_FILTER_ENTRY_TYPES },
|
|
401
|
-
o: { enum: IML_BINARY_FILTER_OPERATORS }
|
|
725
|
+
a: operandSchema,
|
|
726
|
+
o: operatorSchema,
|
|
727
|
+
b: {
|
|
728
|
+
title: "Operand B",
|
|
729
|
+
description: "The second operand of the filter.",
|
|
730
|
+
type: IML_FILTER_ENTRY_TYPES
|
|
731
|
+
}
|
|
402
732
|
},
|
|
403
733
|
required: ["a", "b", "o"]
|
|
404
734
|
}
|
|
405
735
|
]
|
|
406
736
|
};
|
|
737
|
+
if (!operatorOptions) {
|
|
738
|
+
filterDefinition.oneOf?.push({
|
|
739
|
+
type: "object",
|
|
740
|
+
properties: {
|
|
741
|
+
a: operandSchema,
|
|
742
|
+
o: {
|
|
743
|
+
...operatorSchema,
|
|
744
|
+
enum: IML_UNARY_FILTER_OPERATORS
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
required: ["a", "o"]
|
|
748
|
+
});
|
|
749
|
+
}
|
|
407
750
|
const logic = field.logic ?? "default";
|
|
408
|
-
result.items = ["and", "or"].includes(logic) ?
|
|
751
|
+
result.items = ["and", "or"].includes(logic) ? filterDefinition : {
|
|
409
752
|
type: "array",
|
|
410
|
-
items:
|
|
753
|
+
items: filterDefinition
|
|
411
754
|
};
|
|
412
755
|
Object.defineProperty(result, "x-filter", {
|
|
413
756
|
configurable: true,
|
|
@@ -433,9 +776,7 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
433
776
|
}
|
|
434
777
|
});
|
|
435
778
|
}
|
|
436
|
-
const nested
|
|
437
|
-
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
438
|
-
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
|
|
779
|
+
const { nested, domain } = extractNestedAndDomain(field);
|
|
439
780
|
if (typeof optionsOrGroups === "string") {
|
|
440
781
|
Object.defineProperty(result, "x-fetch", {
|
|
441
782
|
configurable: true,
|
|
@@ -504,46 +845,7 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
504
845
|
result.enum = (options || []).map((option) => option.value);
|
|
505
846
|
}
|
|
506
847
|
}
|
|
507
|
-
|
|
508
|
-
const normalizedNested = typeof nested === "string" ? [nested] : nested;
|
|
509
|
-
let root = context.roots[domain];
|
|
510
|
-
if (!root) {
|
|
511
|
-
const buffer = [];
|
|
512
|
-
root = context.roots[domain] = {
|
|
513
|
-
buffer,
|
|
514
|
-
addFields: (nested2, tail) => {
|
|
515
|
-
buffer.push(...nested2.map((field2) => ({ field: field2, tail })));
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
root.addFields(normalizedNested, [...context.tail, field.name]);
|
|
520
|
-
} else if (nested) {
|
|
521
|
-
Object.defineProperty(result, "x-nested", {
|
|
522
|
-
configurable: true,
|
|
523
|
-
enumerable: true,
|
|
524
|
-
writable: true,
|
|
525
|
-
value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : nestedContainsStrings ? {
|
|
526
|
-
type: "object",
|
|
527
|
-
allOf: nested.map(
|
|
528
|
-
(item) => typeof item === "string" ? { $ref: appendQueryString(item, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
|
|
529
|
-
{ type: "collection", spec: [item] },
|
|
530
|
-
{
|
|
531
|
-
...context,
|
|
532
|
-
domain: domain || context.domain,
|
|
533
|
-
tail: [...context.tail, field.name]
|
|
534
|
-
}
|
|
535
|
-
)
|
|
536
|
-
)
|
|
537
|
-
} : toJSONSchemaInternal(
|
|
538
|
-
{ type: "collection", spec: nested },
|
|
539
|
-
{
|
|
540
|
-
...context,
|
|
541
|
-
domain: domain || context.domain,
|
|
542
|
-
tail: [...context.tail, field.name]
|
|
543
|
-
}
|
|
544
|
-
)
|
|
545
|
-
});
|
|
546
|
-
}
|
|
848
|
+
result = handleNestedWithDomain(field, nested, domain, result, context);
|
|
547
849
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
548
850
|
return result;
|
|
549
851
|
}
|
|
@@ -566,6 +868,10 @@ function handlePrimitiveType(field, result, context) {
|
|
|
566
868
|
}
|
|
567
869
|
}
|
|
568
870
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
871
|
+
if (field.nested) {
|
|
872
|
+
const { nested, domain } = extractNestedAndDomain(field);
|
|
873
|
+
result = handleNestedWithDomain(field, nested, domain, result, context);
|
|
874
|
+
}
|
|
569
875
|
return result;
|
|
570
876
|
}
|
|
571
877
|
function processRpcDirective(field, result, context) {
|
|
@@ -582,6 +888,60 @@ function processRpcDirective(field, result, context) {
|
|
|
582
888
|
});
|
|
583
889
|
return result;
|
|
584
890
|
}
|
|
891
|
+
function extractNestedAndDomain(field) {
|
|
892
|
+
const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : isObject(field.nested) ? field.nested.store : field.nested;
|
|
893
|
+
const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : isObject(field.nested) && field.nested.domain ? field.nested.domain : void 0;
|
|
894
|
+
return { nested, domain };
|
|
895
|
+
}
|
|
896
|
+
function handleNestedWithDomain(field, nested, domain, result, context) {
|
|
897
|
+
if (!nested) return result;
|
|
898
|
+
if (domain && domain !== context.domain) {
|
|
899
|
+
const normalizedNested = typeof nested === "string" ? [nested] : nested;
|
|
900
|
+
let root = context.roots[domain];
|
|
901
|
+
if (!root) {
|
|
902
|
+
const buffer = [];
|
|
903
|
+
root = context.roots[domain] = {
|
|
904
|
+
buffer,
|
|
905
|
+
addFields: (nested2, tail) => {
|
|
906
|
+
buffer.push(...nested2.map((field2) => ({ field: field2, tail })));
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
root.addFields(normalizedNested, [...context.tail, field.name]);
|
|
911
|
+
return result;
|
|
912
|
+
}
|
|
913
|
+
return processNestedDirective(field, nested, domain, result, context);
|
|
914
|
+
}
|
|
915
|
+
function processNestedDirective(field, nested, domain, result, context) {
|
|
916
|
+
if (!nested) return result;
|
|
917
|
+
const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
|
|
918
|
+
Object.defineProperty(result, "x-nested", {
|
|
919
|
+
configurable: true,
|
|
920
|
+
enumerable: true,
|
|
921
|
+
writable: true,
|
|
922
|
+
value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : nestedContainsStrings ? {
|
|
923
|
+
type: "object",
|
|
924
|
+
allOf: nested.map(
|
|
925
|
+
(item) => typeof item === "string" ? { $ref: appendQueryString(item, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
|
|
926
|
+
{ type: "collection", spec: [item] },
|
|
927
|
+
{
|
|
928
|
+
...context,
|
|
929
|
+
domain: domain || context.domain,
|
|
930
|
+
tail: [...context.tail, field.name]
|
|
931
|
+
}
|
|
932
|
+
)
|
|
933
|
+
)
|
|
934
|
+
} : toJSONSchemaInternal(
|
|
935
|
+
{ type: "collection", spec: nested },
|
|
936
|
+
{
|
|
937
|
+
...context,
|
|
938
|
+
domain: domain || context.domain,
|
|
939
|
+
tail: [...context.tail, field.name]
|
|
940
|
+
}
|
|
941
|
+
)
|
|
942
|
+
});
|
|
943
|
+
return result;
|
|
944
|
+
}
|
|
585
945
|
|
|
586
946
|
// src/validator.ts
|
|
587
947
|
var FORMAN_TYPE_MAP2 = {
|
|
@@ -616,6 +976,8 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
616
976
|
pkey: "string",
|
|
617
977
|
port: "number",
|
|
618
978
|
select: void 0,
|
|
979
|
+
udttype: "string",
|
|
980
|
+
udtspec: "array",
|
|
619
981
|
time: "string",
|
|
620
982
|
timestamp: "string",
|
|
621
983
|
timezone: "string",
|
|
@@ -756,6 +1118,12 @@ async function validateFormanValue(value, field, context) {
|
|
|
756
1118
|
errors: []
|
|
757
1119
|
};
|
|
758
1120
|
}
|
|
1121
|
+
if (normalizedField.type === "udttype") {
|
|
1122
|
+
return validateFormanValue(value, udttypeExpand({ ...normalizedField }), context);
|
|
1123
|
+
}
|
|
1124
|
+
if (normalizedField.type === "udtspec") {
|
|
1125
|
+
return validateFormanValue(value, udtspecExpand({ ...normalizedField }), context);
|
|
1126
|
+
}
|
|
759
1127
|
switch (normalizedField.type) {
|
|
760
1128
|
case "collection":
|
|
761
1129
|
return handleCollectionType2(value, normalizedField, context);
|
|
@@ -903,24 +1271,40 @@ async function handleArrayType2(value, field, context) {
|
|
|
903
1271
|
};
|
|
904
1272
|
}
|
|
905
1273
|
async function handleFilterType2(value, field, context) {
|
|
1274
|
+
const operandOptions = isObject(field.options) ? field.options.store : field.options;
|
|
1275
|
+
const operandSpec = {
|
|
1276
|
+
name: "a",
|
|
1277
|
+
type: "any",
|
|
1278
|
+
required: true
|
|
1279
|
+
};
|
|
1280
|
+
if (Array.isArray(operandOptions)) {
|
|
1281
|
+
operandSpec.type = "select";
|
|
1282
|
+
operandSpec.options = operandOptions;
|
|
1283
|
+
}
|
|
1284
|
+
const operatorOptions = isObject(field.options) ? field.options.operators : void 0;
|
|
1285
|
+
const operatorSpec = {
|
|
1286
|
+
name: "o",
|
|
1287
|
+
type: "any",
|
|
1288
|
+
required: true
|
|
1289
|
+
};
|
|
1290
|
+
if (Array.isArray(operatorOptions)) {
|
|
1291
|
+
operatorSpec.type = "select";
|
|
1292
|
+
operatorSpec.grouped = true;
|
|
1293
|
+
operatorSpec.options = operatorOptions;
|
|
1294
|
+
} else {
|
|
1295
|
+
operatorSpec.type = "text";
|
|
1296
|
+
operatorSpec.validate = {
|
|
1297
|
+
enum: IML_FILTER_OPERATORS
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
906
1300
|
const filterEntry = {
|
|
907
1301
|
type: "collection",
|
|
908
1302
|
spec: [
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
type: "any",
|
|
912
|
-
required: true
|
|
913
|
-
},
|
|
1303
|
+
operandSpec,
|
|
1304
|
+
operatorSpec,
|
|
914
1305
|
{
|
|
915
1306
|
name: "b",
|
|
916
1307
|
type: "any"
|
|
917
|
-
},
|
|
918
|
-
{
|
|
919
|
-
name: "o",
|
|
920
|
-
type: "text",
|
|
921
|
-
validate: {
|
|
922
|
-
enum: IML_FILTER_OPERATORS
|
|
923
|
-
}
|
|
924
1308
|
}
|
|
925
1309
|
]
|
|
926
1310
|
};
|
|
@@ -1089,7 +1473,18 @@ async function handleSelectType(value, field, context) {
|
|
|
1089
1473
|
let nested = field.nested ? field.nested : isObject(field.options) ? field.options.nested : void 0;
|
|
1090
1474
|
if (typeof optionsOrGroups === "string") {
|
|
1091
1475
|
try {
|
|
1092
|
-
|
|
1476
|
+
const resolved = await context.resolveRemote(optionsOrGroups, context);
|
|
1477
|
+
if (resolved == null || typeof resolved !== "object") {
|
|
1478
|
+
return {
|
|
1479
|
+
valid: false,
|
|
1480
|
+
errors: [...errors, {
|
|
1481
|
+
domain: context.domain,
|
|
1482
|
+
path: context.path.join("."),
|
|
1483
|
+
message: `Remote resource ${optionsOrGroups} returned no data.`
|
|
1484
|
+
}]
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
optionsOrGroups = Array.isArray(resolved) ? resolved : [resolved];
|
|
1093
1488
|
} catch (error) {
|
|
1094
1489
|
return {
|
|
1095
1490
|
valid: false,
|
|
@@ -1199,7 +1594,12 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
1199
1594
|
for (const item of store) {
|
|
1200
1595
|
if (typeof item === "string") {
|
|
1201
1596
|
try {
|
|
1202
|
-
|
|
1597
|
+
const resolved = await context.resolveRemote(item, context);
|
|
1598
|
+
if (Array.isArray(resolved)) {
|
|
1599
|
+
resolvedStore.push(...resolved);
|
|
1600
|
+
} else if (resolved && typeof resolved === "object") {
|
|
1601
|
+
resolvedStore.push(resolved);
|
|
1602
|
+
}
|
|
1203
1603
|
} catch (error) {
|
|
1204
1604
|
return {
|
|
1205
1605
|
valid: false,
|
|
@@ -1307,6 +1707,9 @@ var JSON_PRIMITIVE_TYPE_MAP = {
|
|
|
1307
1707
|
boolean: "boolean"
|
|
1308
1708
|
};
|
|
1309
1709
|
function toFormanSchema(field) {
|
|
1710
|
+
const compositeType = Object.getOwnPropertyDescriptor(field, "x-composite")?.value;
|
|
1711
|
+
if (compositeType === "udttype") return udttypeCollapse(field);
|
|
1712
|
+
if (compositeType === "udtspec") return udtspecCollapse(field);
|
|
1310
1713
|
switch (field.type) {
|
|
1311
1714
|
case "object":
|
|
1312
1715
|
if (!field.properties || !Object.entries(field.properties).length) {
|
|
@@ -1429,7 +1832,17 @@ function handleSearchDirective(formanField, directive) {
|
|
|
1429
1832
|
|
|
1430
1833
|
// src/index.ts
|
|
1431
1834
|
function toJSONSchema(field) {
|
|
1432
|
-
|
|
1835
|
+
const context = createDefaultContext();
|
|
1836
|
+
const result = toJSONSchemaInternal(field, context);
|
|
1837
|
+
if (Object.keys(context.definitions ?? {}).length > 0) {
|
|
1838
|
+
Object.defineProperty(result, "definitions", {
|
|
1839
|
+
configurable: true,
|
|
1840
|
+
enumerable: true,
|
|
1841
|
+
writable: true,
|
|
1842
|
+
value: context.definitions
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
return result;
|
|
1433
1846
|
}
|
|
1434
1847
|
function validateFormanWithDomains(domains, options) {
|
|
1435
1848
|
return validateFormanWithDomainsInternal(domains, options);
|