nox-validation 1.6.0 → 1.6.2
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/lib/helpers.js +70 -19
- package/lib/validate.js +97 -30
- package/package.json +1 -1
package/lib/helpers.js
CHANGED
|
@@ -319,7 +319,7 @@ const generateField = (
|
|
|
319
319
|
hidden: false,
|
|
320
320
|
}
|
|
321
321
|
) => {
|
|
322
|
-
const { staticType } = meta;
|
|
322
|
+
const { staticType = null, parentInterface = null } = meta;
|
|
323
323
|
childrenFields = childrenFields?.map((child) => {
|
|
324
324
|
const childKey = path ? `${path}.${child.key}` : child.key;
|
|
325
325
|
const childValue = path ? `${path}.${child.value}` : child.value;
|
|
@@ -327,7 +327,10 @@ const generateField = (
|
|
|
327
327
|
return {
|
|
328
328
|
...child,
|
|
329
329
|
value: childKey,
|
|
330
|
-
meta:
|
|
330
|
+
meta:
|
|
331
|
+
staticType || parentInterface
|
|
332
|
+
? { ...child?.meta, staticType, parentInterface }
|
|
333
|
+
: child?.meta,
|
|
331
334
|
key: childValue,
|
|
332
335
|
isRelationalUpdate: path.includes("update"),
|
|
333
336
|
};
|
|
@@ -418,7 +421,7 @@ const generateRelationalField = (
|
|
|
418
421
|
[],
|
|
419
422
|
"none",
|
|
420
423
|
[],
|
|
421
|
-
{ staticType: "existing" }
|
|
424
|
+
{ staticType: "existing", parentInterface: relationType }
|
|
422
425
|
);
|
|
423
426
|
existingField.children = [
|
|
424
427
|
generateField(
|
|
@@ -458,7 +461,7 @@ const generateRelationalField = (
|
|
|
458
461
|
[],
|
|
459
462
|
"none",
|
|
460
463
|
[],
|
|
461
|
-
{ staticType: "delete" }
|
|
464
|
+
{ staticType: "delete", parentInterface: relationType }
|
|
462
465
|
);
|
|
463
466
|
deleteField.children = [
|
|
464
467
|
generateField(
|
|
@@ -499,7 +502,7 @@ const generateRelationalField = (
|
|
|
499
502
|
[],
|
|
500
503
|
"none",
|
|
501
504
|
[],
|
|
502
|
-
{ staticType: "create" }
|
|
505
|
+
{ staticType: "create", parentInterface: relationType }
|
|
503
506
|
);
|
|
504
507
|
createField.children = [
|
|
505
508
|
generateField(
|
|
@@ -544,7 +547,7 @@ const generateRelationalField = (
|
|
|
544
547
|
[],
|
|
545
548
|
"none",
|
|
546
549
|
[],
|
|
547
|
-
{ staticType: "update" }
|
|
550
|
+
{ staticType: "update", parentInterface: relationType }
|
|
548
551
|
);
|
|
549
552
|
updateField.children = [
|
|
550
553
|
generateField(
|
|
@@ -591,7 +594,7 @@ const generateRelationalField = (
|
|
|
591
594
|
[],
|
|
592
595
|
"none",
|
|
593
596
|
[],
|
|
594
|
-
{ staticType: "existing" }
|
|
597
|
+
{ staticType: "existing", parentInterface: relationType }
|
|
595
598
|
),
|
|
596
599
|
generateField(
|
|
597
600
|
"delete",
|
|
@@ -601,7 +604,7 @@ const generateRelationalField = (
|
|
|
601
604
|
[],
|
|
602
605
|
"none",
|
|
603
606
|
[],
|
|
604
|
-
{ staticType: "delete" }
|
|
607
|
+
{ staticType: "delete", parentInterface: relationType }
|
|
605
608
|
),
|
|
606
609
|
generateField(
|
|
607
610
|
"create",
|
|
@@ -611,7 +614,7 @@ const generateRelationalField = (
|
|
|
611
614
|
collectionFields,
|
|
612
615
|
"none",
|
|
613
616
|
[],
|
|
614
|
-
{ staticType: "create" }
|
|
617
|
+
{ staticType: "create", parentInterface: relationType }
|
|
615
618
|
),
|
|
616
619
|
generateField(
|
|
617
620
|
"update",
|
|
@@ -621,7 +624,7 @@ const generateRelationalField = (
|
|
|
621
624
|
collectionFields,
|
|
622
625
|
"none",
|
|
623
626
|
[],
|
|
624
|
-
{ staticType: "update" }
|
|
627
|
+
{ staticType: "update", parentInterface: relationType }
|
|
625
628
|
),
|
|
626
629
|
];
|
|
627
630
|
}
|
|
@@ -1427,10 +1430,40 @@ const getSelectedNodes = ({
|
|
|
1427
1430
|
return updatedNode;
|
|
1428
1431
|
}
|
|
1429
1432
|
|
|
1430
|
-
function traverse(currentNode) {
|
|
1433
|
+
function traverse(currentNode, parentKey = null) {
|
|
1434
|
+
function mergePathLevels(currentPath, parentPath) {
|
|
1435
|
+
const cur = currentPath.split(".");
|
|
1436
|
+
const par = parentPath.split(".");
|
|
1437
|
+
|
|
1438
|
+
let matchIndex = -1;
|
|
1439
|
+
|
|
1440
|
+
// Find the first matching level
|
|
1441
|
+
for (let i = 0; i < par.length; i++) {
|
|
1442
|
+
if (par[i].replace(/\[\d+\]/, "") === cur[0].replace(/\[\d+\]/, "")) {
|
|
1443
|
+
matchIndex = i;
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// If match found
|
|
1449
|
+
if (matchIndex !== -1) {
|
|
1450
|
+
const parentPart = par.slice(0, matchIndex + 1);
|
|
1451
|
+
const currentTail = cur.slice(1); // everything after the matched level
|
|
1452
|
+
return [...parentPart, ...currentTail].join(".");
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// No match → normal join
|
|
1456
|
+
return [...par, ...cur].join(".");
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
if (parentKey)
|
|
1460
|
+
currentNode.key = mergePathLevels(currentNode.key, parentKey);
|
|
1461
|
+
|
|
1431
1462
|
if (conditionFn(currentNode)) {
|
|
1432
1463
|
actionFn(currentNode);
|
|
1433
|
-
|
|
1464
|
+
const mapped = mapFn(currentNode);
|
|
1465
|
+
|
|
1466
|
+
result.push(mapped);
|
|
1434
1467
|
}
|
|
1435
1468
|
|
|
1436
1469
|
if (Array.isArray(currentNode.children)) {
|
|
@@ -1447,12 +1480,15 @@ const getSelectedNodes = ({
|
|
|
1447
1480
|
}
|
|
1448
1481
|
}
|
|
1449
1482
|
|
|
1450
|
-
if (!skipFn(currentNode))
|
|
1483
|
+
if (!skipFn(currentNode)) {
|
|
1484
|
+
traverse(childNode, currentNode?.key);
|
|
1485
|
+
}
|
|
1451
1486
|
});
|
|
1452
1487
|
}
|
|
1453
1488
|
}
|
|
1454
1489
|
|
|
1455
|
-
traverse(node);
|
|
1490
|
+
traverse(node, node?.key);
|
|
1491
|
+
|
|
1456
1492
|
return result;
|
|
1457
1493
|
};
|
|
1458
1494
|
|
|
@@ -1503,12 +1539,9 @@ const formatDate = ({
|
|
|
1503
1539
|
];
|
|
1504
1540
|
|
|
1505
1541
|
if (!formats.includes(format)) format = formats[0];
|
|
1506
|
-
|
|
1507
|
-
throw new RangeError("Invalid time value");
|
|
1508
|
-
}
|
|
1509
|
-
const dateObj = date instanceof Date ? date : new Date(date);
|
|
1542
|
+
|
|
1510
1543
|
const { year, month, day, hour, minute, second, dayPeriod } = getParts(
|
|
1511
|
-
|
|
1544
|
+
date,
|
|
1512
1545
|
timeZone
|
|
1513
1546
|
);
|
|
1514
1547
|
|
|
@@ -1527,6 +1560,23 @@ const formatDate = ({
|
|
|
1527
1560
|
.replace("A", dayPeriod);
|
|
1528
1561
|
};
|
|
1529
1562
|
|
|
1563
|
+
const rebuildFullPath = (nodePath, fullPath, modifierFn) => {
|
|
1564
|
+
// 1. Find parent path
|
|
1565
|
+
const index = fullPath.lastIndexOf(nodePath);
|
|
1566
|
+
if (index === -1) {
|
|
1567
|
+
throw new Error("nodePath not found inside fullPath");
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
const parentPath = fullPath.slice(0, index).replace(/\.$/, "");
|
|
1571
|
+
|
|
1572
|
+
// 2. Modify node path (if function provided)
|
|
1573
|
+
const modifiedNodePath =
|
|
1574
|
+
typeof modifierFn === "function" ? modifierFn(nodePath) : nodePath;
|
|
1575
|
+
|
|
1576
|
+
// 3. Rebuild final full path
|
|
1577
|
+
return `${parentPath}.${modifiedNodePath}`.replace(/\.+/g, ".");
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1530
1580
|
module.exports = {
|
|
1531
1581
|
generateModifiedRules,
|
|
1532
1582
|
getFieldsGroupBySchemaId,
|
|
@@ -1550,4 +1600,5 @@ module.exports = {
|
|
|
1550
1600
|
getSelectedNodes,
|
|
1551
1601
|
remainingItems,
|
|
1552
1602
|
formatDate,
|
|
1603
|
+
rebuildFullPath,
|
|
1553
1604
|
};
|
package/lib/validate.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
getSelectedNodes,
|
|
14
14
|
remainingItems,
|
|
15
15
|
formatDate,
|
|
16
|
+
rebuildFullPath,
|
|
16
17
|
} = require("./helpers");
|
|
17
18
|
|
|
18
19
|
const choices = ["radio", "checkboxes", "dropdown_multiple", "dropdown"];
|
|
@@ -60,9 +61,20 @@ const typeChecks = {
|
|
|
60
61
|
}
|
|
61
62
|
return false;
|
|
62
63
|
},
|
|
63
|
-
[constants.types.TIME]: (val) =>
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
[constants.types.TIME]: (val, data) => {
|
|
65
|
+
if (val instanceof Date && !isNaN(val)) return true;
|
|
66
|
+
if (typeof val === "string") {
|
|
67
|
+
if (/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val)) return true;
|
|
68
|
+
const parsed = Date.parse(val);
|
|
69
|
+
if (!isNaN(parsed)) {
|
|
70
|
+
if (data && data?.key && data?.updateValue) {
|
|
71
|
+
data.updateValue(data.key, new Date(val));
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
},
|
|
66
78
|
[constants.types.TIMESTAMP]: (val, data) => {
|
|
67
79
|
if (val instanceof Date && !isNaN(val)) return true;
|
|
68
80
|
if (typeof val === "string" && !isNaN(Date.parse(val))) {
|
|
@@ -411,6 +423,7 @@ const validateMetaRules = (
|
|
|
411
423
|
if (
|
|
412
424
|
(isEmpty(fieldValue) &&
|
|
413
425
|
required &&
|
|
426
|
+
!field?.meta?.hidden &&
|
|
414
427
|
![
|
|
415
428
|
constants.interfaces.FILES,
|
|
416
429
|
constants.interfaces.FILE,
|
|
@@ -419,21 +432,22 @@ const validateMetaRules = (
|
|
|
419
432
|
constants.interfaces.ONE_TO_MANY,
|
|
420
433
|
constants.interfaces.MANY_TO_ONE,
|
|
421
434
|
constants.interfaces.MANY_TO_ANY,
|
|
422
|
-
|
|
435
|
+
|
|
423
436
|
"none",
|
|
424
437
|
].includes(field?.meta?.interface)) ||
|
|
425
438
|
(field?.meta?.interface === constants.interfaces.TRANSLATIONS &&
|
|
439
|
+
!field?.meta?.hidden &&
|
|
426
440
|
language_codes?.length &&
|
|
427
441
|
!onlyFormFields) ||
|
|
428
|
-
([
|
|
429
|
-
|
|
430
|
-
|
|
442
|
+
([
|
|
443
|
+
constants.interfaces.OBJECT,
|
|
444
|
+
constants.interfaces.SEO,
|
|
445
|
+
constants.interfaces.ITEMS,
|
|
446
|
+
].includes(field?.meta?.interface) &&
|
|
447
|
+
!field?.meta?.hidden &&
|
|
431
448
|
!onlyFormFields &&
|
|
432
449
|
getNodes)
|
|
433
450
|
) {
|
|
434
|
-
const isTranslationChild =
|
|
435
|
-
field?.meta?.interface === constants.interfaces.TRANSLATIONS;
|
|
436
|
-
|
|
437
451
|
getSelectedNodes({
|
|
438
452
|
node: field,
|
|
439
453
|
skipFn: (node) =>
|
|
@@ -445,17 +459,36 @@ const validateMetaRules = (
|
|
|
445
459
|
label: node.display_label,
|
|
446
460
|
}),
|
|
447
461
|
actionFn: (node) => {
|
|
448
|
-
const
|
|
462
|
+
const isTranslationNode =
|
|
463
|
+
node?.meta?.parentInterface === constants.interfaces.TRANSLATIONS ||
|
|
464
|
+
field?.meta?.interface === constants.interfaces.TRANSLATIONS;
|
|
449
465
|
let fPath = node.key?.replace("[0][0]", "[0]");
|
|
466
|
+
const types = ["update", "delete", "existing", "create"];
|
|
467
|
+
|
|
468
|
+
const extractFirstType = (key) => {
|
|
469
|
+
const regex = new RegExp(`(${types.join("|")})\\[`, "i");
|
|
470
|
+
const match = key.match(regex);
|
|
471
|
+
return match ? match[1] : null;
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const staticType =
|
|
475
|
+
node?.meta?.staticType && types.includes(node?.meta?.staticType)
|
|
476
|
+
? extractFirstType(node.key)
|
|
477
|
+
: null;
|
|
478
|
+
|
|
479
|
+
if (!fPath.includes(currentPath)) {
|
|
480
|
+
fPath = `${currentPath}.${fPath}`;
|
|
481
|
+
}
|
|
450
482
|
|
|
451
483
|
if (staticType === "update") return;
|
|
452
|
-
if (staticType === "delete" && !
|
|
453
|
-
if (staticType === "existing" && !
|
|
484
|
+
if (staticType === "delete" && !isTranslationNode) return;
|
|
485
|
+
if (staticType === "existing" && !isTranslationNode) return;
|
|
454
486
|
|
|
455
487
|
const label = formatLabel(node.display_label);
|
|
456
488
|
const message = error_messages.REQUIRED.replace("{field}", label);
|
|
457
489
|
|
|
458
490
|
const buildError = (path, translated) => {
|
|
491
|
+
if (!isEmpty(getValue(formData, path))) return;
|
|
459
492
|
let obj = {
|
|
460
493
|
label,
|
|
461
494
|
fieldPath: path,
|
|
@@ -469,7 +502,7 @@ const validateMetaRules = (
|
|
|
469
502
|
};
|
|
470
503
|
|
|
471
504
|
const isMultiLang =
|
|
472
|
-
|
|
505
|
+
isTranslationNode &&
|
|
473
506
|
Array.isArray(language_codes) &&
|
|
474
507
|
language_codes.length > 1;
|
|
475
508
|
|
|
@@ -479,17 +512,24 @@ const validateMetaRules = (
|
|
|
479
512
|
transformedPath,
|
|
480
513
|
isAdded = false;
|
|
481
514
|
|
|
482
|
-
if (
|
|
515
|
+
if (node?.value?.includes("create[0].")) {
|
|
483
516
|
const exist = fieldValue?.create?.find(
|
|
484
517
|
(item) => item.languages_code === lang
|
|
485
518
|
);
|
|
486
519
|
if (exist) isAdded = true;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
520
|
+
langPath = rebuildFullPath(node.value, node.key, (path) =>
|
|
521
|
+
path.replace("create[0].", `create[${index}].`)
|
|
522
|
+
);
|
|
523
|
+
transformedPath = rebuildFullPath(node.value, node.key, (path) =>
|
|
524
|
+
path.replace("create[0].", `${lang}.`)
|
|
525
|
+
);
|
|
490
526
|
} else {
|
|
491
|
-
langPath =
|
|
492
|
-
|
|
527
|
+
langPath = rebuildFullPath(node.value, node.key, (path) =>
|
|
528
|
+
path.replace("create.", `create[${index}].`)
|
|
529
|
+
);
|
|
530
|
+
transformedPath = rebuildFullPath(node.value, node.key, (path) =>
|
|
531
|
+
path.replace("create.", `${lang}.`)
|
|
532
|
+
);
|
|
493
533
|
}
|
|
494
534
|
|
|
495
535
|
if (!isAdded) buildError(langPath, transformedPath);
|
|
@@ -497,7 +537,7 @@ const validateMetaRules = (
|
|
|
497
537
|
} else {
|
|
498
538
|
const singlePath = fPath.replace(
|
|
499
539
|
"create.",
|
|
500
|
-
|
|
540
|
+
isTranslationNode ? "create[lang_code]." : "create[0]."
|
|
501
541
|
);
|
|
502
542
|
if (
|
|
503
543
|
constants.interfaces.ITEMS === field?.meta?.interface
|
|
@@ -626,10 +666,6 @@ const handleRegexValidation = (
|
|
|
626
666
|
message = message
|
|
627
667
|
?.replace(`{field}`, fieldLabel)
|
|
628
668
|
?.replace(`{value}`, rule.value[0]);
|
|
629
|
-
console.log(
|
|
630
|
-
"[handleRegexValidation] Validation failed. Adding error. Message:",
|
|
631
|
-
message
|
|
632
|
-
);
|
|
633
669
|
addError(
|
|
634
670
|
currentPath,
|
|
635
671
|
{ label: fieldLabel, fieldPath: currentPath, description: "", message },
|
|
@@ -660,10 +696,40 @@ const validateOperatorRule = (
|
|
|
660
696
|
const isTime = field.type === constants.types.TIME;
|
|
661
697
|
const isArray = field.type === constants.types.ARRAY;
|
|
662
698
|
|
|
663
|
-
const parseTime = (
|
|
664
|
-
if (!
|
|
665
|
-
|
|
666
|
-
|
|
699
|
+
const parseTime = (value) => {
|
|
700
|
+
if (!value) return null;
|
|
701
|
+
|
|
702
|
+
const toSeconds = (str) => {
|
|
703
|
+
const [hh = 0, mm = 0, ss = 0] = str.split(":").map(Number);
|
|
704
|
+
return hh * 3600 + mm * 60 + ss;
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
if (value instanceof Date && !isNaN(value)) {
|
|
708
|
+
const timePart = formatDate({
|
|
709
|
+
date: value,
|
|
710
|
+
format: "DD/MM/YYYY HH:mm:ss",
|
|
711
|
+
timeZone,
|
|
712
|
+
}).split(" ")[1];
|
|
713
|
+
const totalSeconds = toSeconds(timePart);
|
|
714
|
+
return totalSeconds || null;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (typeof value === "string") {
|
|
718
|
+
if (/^\d{1,2}:\d{2}:\d{2}$/.test(value)) {
|
|
719
|
+
return toSeconds(value);
|
|
720
|
+
}
|
|
721
|
+
if (!isNaN(Date.parse(value))) {
|
|
722
|
+
const timePart = formatDate({
|
|
723
|
+
date: value,
|
|
724
|
+
format: "DD/MM/YYYY HH:mm:ss",
|
|
725
|
+
timeZone,
|
|
726
|
+
}).split(" ")[1];
|
|
727
|
+
const totalSeconds = toSeconds(timePart);
|
|
728
|
+
return totalSeconds || null;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return null;
|
|
667
733
|
};
|
|
668
734
|
|
|
669
735
|
// Helper to parse date string to UTC midnight timestamp
|
|
@@ -700,7 +766,7 @@ const validateOperatorRule = (
|
|
|
700
766
|
}
|
|
701
767
|
if (isDateTime) {
|
|
702
768
|
if (forMessage) {
|
|
703
|
-
const format = field?.options?.format;
|
|
769
|
+
const format = field?.meta?.options?.format;
|
|
704
770
|
return formatDate({ date: value, format, timeZone });
|
|
705
771
|
}
|
|
706
772
|
// Return UTC timestamp for comparison
|
|
@@ -1514,6 +1580,7 @@ const validate = (data) => {
|
|
|
1514
1580
|
fieldOptions,
|
|
1515
1581
|
data.language_codes,
|
|
1516
1582
|
existingForm,
|
|
1583
|
+
true,
|
|
1517
1584
|
timeZone
|
|
1518
1585
|
);
|
|
1519
1586
|
});
|