nox-validation 1.7.1 → 1.7.3
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/index.js +21 -6
- package/lib/helpers.optimized.js +52 -13
- package/lib/validate.js +80 -57
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -5,19 +5,34 @@ const {
|
|
|
5
5
|
buildNestedStructure,
|
|
6
6
|
getValue,
|
|
7
7
|
setValue,
|
|
8
|
-
getDefaultValues
|
|
8
|
+
getDefaultValues,
|
|
9
9
|
} = require("./lib/helpers");
|
|
10
10
|
|
|
11
|
+
const optimizedValidate = require("./lib/validate.optimized");
|
|
12
|
+
const helpersOptimized = require("./lib/helpers.optimized");
|
|
13
|
+
|
|
11
14
|
module.exports = {
|
|
12
15
|
validate,
|
|
13
16
|
CONSTANTS: constants,
|
|
14
17
|
helpers: {
|
|
15
18
|
validateSingleField: validateField,
|
|
16
19
|
validateType: typeChecks,
|
|
17
|
-
fieldsMapping:getFieldsGroupBySchemaId,
|
|
18
|
-
convertTree:buildNestedStructure,
|
|
19
|
-
getValueByDynamicKey:getValue,
|
|
20
|
-
setValueByDynamicKey:setValue,
|
|
21
|
-
getTreeDefaultValues:getDefaultValues
|
|
20
|
+
fieldsMapping: getFieldsGroupBySchemaId,
|
|
21
|
+
convertTree: buildNestedStructure,
|
|
22
|
+
getValueByDynamicKey: getValue,
|
|
23
|
+
setValueByDynamicKey: setValue,
|
|
24
|
+
getTreeDefaultValues: getDefaultValues,
|
|
25
|
+
},
|
|
26
|
+
optimized: {
|
|
27
|
+
validate: optimizedValidate.validate,
|
|
28
|
+
helpers: {
|
|
29
|
+
validateSingleField: helpersOptimized.validateField,
|
|
30
|
+
validateType: helpersOptimized.typeChecks,
|
|
31
|
+
fieldsMapping: helpersOptimized.getFieldsGroupBySchemaId,
|
|
32
|
+
convertTree: helpersOptimized.buildNestedStructure,
|
|
33
|
+
getValueByDynamicKey: helpersOptimized.getValue,
|
|
34
|
+
setValueByDynamicKey: helpersOptimized.setValue,
|
|
35
|
+
getTreeDefaultValues: helpersOptimized.getDefaultValues,
|
|
36
|
+
},
|
|
22
37
|
},
|
|
23
38
|
};
|
package/lib/helpers.optimized.js
CHANGED
|
@@ -1105,21 +1105,53 @@ const buildNestedStructure = ({
|
|
|
1105
1105
|
const root = {};
|
|
1106
1106
|
const nodeMap = new Map();
|
|
1107
1107
|
|
|
1108
|
-
// Sort fields by path depth once
|
|
1109
1108
|
const sortedFields = [...schemaFields].sort(
|
|
1110
1109
|
(a, b) => a.path.split(".").length - b.path.split(".").length
|
|
1111
1110
|
);
|
|
1112
1111
|
|
|
1113
|
-
|
|
1114
|
-
const isV2FileInterface = (interface) =>
|
|
1112
|
+
const isV2FileInterface = (interfaceName) =>
|
|
1115
1113
|
apiVersion === constants.API_VERSION.V2 &&
|
|
1116
1114
|
[
|
|
1117
1115
|
constants.interfaces.FILES,
|
|
1118
1116
|
constants.interfaces.FILE,
|
|
1119
1117
|
constants.interfaces.FILE_IMAGE,
|
|
1120
|
-
].includes(
|
|
1118
|
+
].includes(interfaceName);
|
|
1119
|
+
|
|
1120
|
+
// utility to adjust children keys as required
|
|
1121
|
+
function adjustChildKeys(
|
|
1122
|
+
children,
|
|
1123
|
+
parentType,
|
|
1124
|
+
parentKey,
|
|
1125
|
+
parentMetaInterface
|
|
1126
|
+
) {
|
|
1127
|
+
if (!Array.isArray(children) || children.length === 0) return children;
|
|
1128
|
+
// For array parent type
|
|
1129
|
+
if (parentType === constants.types.ARRAY) {
|
|
1130
|
+
// If translation
|
|
1131
|
+
if (parentMetaInterface === constants.interfaces.TRANSLATIONS) {
|
|
1132
|
+
// e.g., parent.key[lang_code].child.key
|
|
1133
|
+
return children.map((child) => ({
|
|
1134
|
+
...child,
|
|
1135
|
+
key: `${parentKey}[lang_code].${child.key.split(".").pop()}`,
|
|
1136
|
+
value: `${parentKey}[lang_code].${child.key.split(".").pop()}`,
|
|
1137
|
+
}));
|
|
1138
|
+
} else {
|
|
1139
|
+
// e.g., parent.key[idx].child.key
|
|
1140
|
+
return children.map((child) => ({
|
|
1141
|
+
...child,
|
|
1142
|
+
key: `${parentKey}[idx].${child.key.split(".").pop()}`,
|
|
1143
|
+
value: `${parentKey}[idx].${child.key.split(".").pop()}`,
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
// For normal object type, just parent.key.child.key
|
|
1148
|
+
return children.map((child) => ({
|
|
1149
|
+
...child,
|
|
1150
|
+
key: `${parentKey}.${child.key.split(".").pop()}`,
|
|
1151
|
+
value: `${parentKey}.${child.key.split(".").pop()}`,
|
|
1152
|
+
}));
|
|
1153
|
+
}
|
|
1121
1154
|
|
|
1122
|
-
// Process each field in a single pass
|
|
1123
1155
|
for (const item of sortedFields) {
|
|
1124
1156
|
const pathParts = item.path.split(".");
|
|
1125
1157
|
const key = pathParts.join(".");
|
|
@@ -1215,6 +1247,13 @@ const buildNestedStructure = ({
|
|
|
1215
1247
|
} else {
|
|
1216
1248
|
children = childFields;
|
|
1217
1249
|
}
|
|
1250
|
+
// Now, adjust children keys as required
|
|
1251
|
+
children = adjustChildKeys(
|
|
1252
|
+
children,
|
|
1253
|
+
definedType.type,
|
|
1254
|
+
key,
|
|
1255
|
+
item.meta?.interface
|
|
1256
|
+
);
|
|
1218
1257
|
}
|
|
1219
1258
|
|
|
1220
1259
|
// Add _id field for arrays
|
|
@@ -1382,7 +1421,7 @@ const RULE_TRANSFORM_MAP = {
|
|
|
1382
1421
|
case: constants.rulesTypes.REGEX,
|
|
1383
1422
|
options: (rule) => ({
|
|
1384
1423
|
type: constants.regexTypes.CONTAINS,
|
|
1385
|
-
case_sensitive: !rule[rule.type]
|
|
1424
|
+
case_sensitive: !rule[rule.type]?.insensitive,
|
|
1386
1425
|
multiline: false,
|
|
1387
1426
|
global: false,
|
|
1388
1427
|
}),
|
|
@@ -1391,7 +1430,7 @@ const RULE_TRANSFORM_MAP = {
|
|
|
1391
1430
|
case: constants.rulesTypes.REGEX,
|
|
1392
1431
|
options: (rule) => ({
|
|
1393
1432
|
type: constants.regexTypes.NOT_CONTAINS,
|
|
1394
|
-
case_sensitive: !rule[rule.type]
|
|
1433
|
+
case_sensitive: !rule[rule.type]?.insensitive,
|
|
1395
1434
|
multiline: false,
|
|
1396
1435
|
global: false,
|
|
1397
1436
|
}),
|
|
@@ -1400,28 +1439,28 @@ const RULE_TRANSFORM_MAP = {
|
|
|
1400
1439
|
case: constants.rulesTypes.REGEX,
|
|
1401
1440
|
options: (rule) => ({
|
|
1402
1441
|
type: constants.regexTypes.START_WITH,
|
|
1403
|
-
case_sensitive: !rule[rule.type]
|
|
1442
|
+
case_sensitive: !rule[rule.type]?.insensitive,
|
|
1404
1443
|
}),
|
|
1405
1444
|
},
|
|
1406
1445
|
doesNotStartWith: {
|
|
1407
1446
|
case: constants.rulesTypes.REGEX,
|
|
1408
1447
|
options: (rule) => ({
|
|
1409
1448
|
type: constants.regexTypes.NOT_START_WITH,
|
|
1410
|
-
case_sensitive: !rule[rule.type]
|
|
1449
|
+
case_sensitive: !rule[rule.type]?.insensitive,
|
|
1411
1450
|
}),
|
|
1412
1451
|
},
|
|
1413
1452
|
endsWith: {
|
|
1414
1453
|
case: constants.rulesTypes.REGEX,
|
|
1415
1454
|
options: (rule) => ({
|
|
1416
1455
|
type: constants.regexTypes.ENDS_WITH,
|
|
1417
|
-
case_sensitive: !rule[rule.type]
|
|
1456
|
+
case_sensitive: !rule[rule.type]?.insensitive,
|
|
1418
1457
|
}),
|
|
1419
1458
|
},
|
|
1420
1459
|
doesNotEndWith: {
|
|
1421
1460
|
case: constants.rulesTypes.REGEX,
|
|
1422
1461
|
options: (rule) => ({
|
|
1423
1462
|
type: constants.regexTypes.NOT_ENDS_WITH,
|
|
1424
|
-
case_sensitive: !rule[rule.type]
|
|
1463
|
+
case_sensitive: !rule[rule.type]?.insensitive,
|
|
1425
1464
|
}),
|
|
1426
1465
|
},
|
|
1427
1466
|
matchesRegExp: {
|
|
@@ -1465,12 +1504,12 @@ const RULE_TRANSFORM_MAP = {
|
|
|
1465
1504
|
isOneOf: {
|
|
1466
1505
|
case: constants.rulesTypes.ONE_OF,
|
|
1467
1506
|
options: () => ({}),
|
|
1468
|
-
value: (rule) => rule[rule.type]
|
|
1507
|
+
value: (rule) => rule[rule.type]?.value,
|
|
1469
1508
|
},
|
|
1470
1509
|
isNotOneOf: {
|
|
1471
1510
|
case: constants.rulesTypes.NOT_ONE_OF,
|
|
1472
1511
|
options: () => ({}),
|
|
1473
|
-
value: (rule) => [rule[rule.type]
|
|
1512
|
+
value: (rule) => [rule[rule.type]?.value],
|
|
1474
1513
|
},
|
|
1475
1514
|
};
|
|
1476
1515
|
|
package/lib/validate.js
CHANGED
|
@@ -224,7 +224,18 @@ const isEmptyRelational = ({
|
|
|
224
224
|
}
|
|
225
225
|
} else if (api_version === constants.API_VERSION.V2) {
|
|
226
226
|
if (isEmpty(existingValue)) return true;
|
|
227
|
-
|
|
227
|
+
let all = Array.isArray(existingValue)
|
|
228
|
+
? existingValue.length === 0
|
|
229
|
+
? []
|
|
230
|
+
: typeof existingValue[0] === "string"
|
|
231
|
+
? existingValue
|
|
232
|
+
: existingValue.map((item) => item._id)
|
|
233
|
+
: typeof existingValue === "string"
|
|
234
|
+
? [existingValue]
|
|
235
|
+
: existingValue && typeof existingValue === "object"
|
|
236
|
+
? [existingValue._id]
|
|
237
|
+
: [];
|
|
238
|
+
all = Array.isArray(all) ? all?.filter(Boolean) : all;
|
|
228
239
|
switch (interface) {
|
|
229
240
|
case constants.interfaces.FILE:
|
|
230
241
|
case constants.interfaces.FILE_IMAGE:
|
|
@@ -232,30 +243,17 @@ const isEmptyRelational = ({
|
|
|
232
243
|
case constants.interfaces.MANY_TO_MANY:
|
|
233
244
|
case constants.interfaces.ONE_TO_MANY:
|
|
234
245
|
case constants.interfaces.MANY_TO_ONE:
|
|
235
|
-
case constants.interfaces.
|
|
246
|
+
case constants.interfaces.MANY_TO_ANY:
|
|
236
247
|
return remainingItems({
|
|
237
|
-
all:
|
|
238
|
-
? existingValue.length === 0
|
|
239
|
-
? []
|
|
240
|
-
: typeof existingValue[0] === "string"
|
|
241
|
-
? existingValue
|
|
242
|
-
: existingValue.map((item) => item._id)
|
|
243
|
-
: typeof existingValue === "string"
|
|
244
|
-
? [existingValue]
|
|
245
|
-
: existingValue && typeof existingValue === "object"
|
|
246
|
-
? [existingValue._id]
|
|
247
|
-
: [],
|
|
248
|
+
all: all,
|
|
248
249
|
obj: value,
|
|
250
|
+
isM2A: interface === constants.interfaces.MANY_TO_ANY,
|
|
249
251
|
});
|
|
250
|
-
case constants.interfaces.
|
|
252
|
+
case constants.interfaces.SEO:
|
|
251
253
|
return remainingItems({
|
|
252
|
-
all:
|
|
253
|
-
? existingValue?.map((item) => item.item)
|
|
254
|
-
: [],
|
|
254
|
+
all: all,
|
|
255
255
|
obj: value,
|
|
256
|
-
isM2A: true,
|
|
257
256
|
});
|
|
258
|
-
|
|
259
257
|
// constants.interfaces.TRANSLATIONS,
|
|
260
258
|
default:
|
|
261
259
|
return true;
|
|
@@ -361,12 +359,21 @@ const validateMetaRules = (
|
|
|
361
359
|
const isValidRelational =
|
|
362
360
|
required && isRelational
|
|
363
361
|
? isEmptyRelational({
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
362
|
+
api_version: apiVersion,
|
|
363
|
+
value: fieldValue,
|
|
364
|
+
interface: field?.meta?.interface,
|
|
365
|
+
onlyFormFields,
|
|
366
|
+
existingValue:
|
|
367
|
+
getValue(existingForm, currentPath) ||
|
|
368
|
+
getValue(
|
|
369
|
+
existingForm,
|
|
370
|
+
currentPath
|
|
371
|
+
?.replace(".create", "")
|
|
372
|
+
?.replace(".update", "")
|
|
373
|
+
?.replace(".existing", "")
|
|
374
|
+
?.replace(".delete", "")
|
|
375
|
+
), // TODO: Need to Generate Form Path Without create, update, existing, delete
|
|
376
|
+
})
|
|
370
377
|
: true;
|
|
371
378
|
|
|
372
379
|
const isEmptyVal = is_m2a_item ? !fieldValue : isEmpty(fieldValue);
|
|
@@ -385,29 +392,29 @@ const validateMetaRules = (
|
|
|
385
392
|
if (invalidRequire || !isValidRelational) {
|
|
386
393
|
const message = field?.meta?.required
|
|
387
394
|
? error_messages.REQUIRED.replace(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
395
|
+
`{field}`,
|
|
396
|
+
formatLabel(field.display_label)
|
|
397
|
+
)
|
|
391
398
|
: error_messages.NOT_EMPTY.replace(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
399
|
+
`{field}`,
|
|
400
|
+
formatLabel(field.display_label)
|
|
401
|
+
);
|
|
395
402
|
addError(
|
|
396
403
|
currentPath,
|
|
397
404
|
translatedPath
|
|
398
405
|
? {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
406
|
+
label: formatLabel(field.display_label),
|
|
407
|
+
fieldPath: currentPath,
|
|
408
|
+
description: "",
|
|
409
|
+
translation_path: translatedPath,
|
|
410
|
+
message,
|
|
411
|
+
}
|
|
405
412
|
: {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
413
|
+
label: formatLabel(field.display_label),
|
|
414
|
+
fieldPath: currentPath,
|
|
415
|
+
description: "",
|
|
416
|
+
message,
|
|
417
|
+
},
|
|
411
418
|
field
|
|
412
419
|
);
|
|
413
420
|
}
|
|
@@ -463,8 +470,19 @@ const validateMetaRules = (
|
|
|
463
470
|
) {
|
|
464
471
|
getSelectedNodes({
|
|
465
472
|
node: field,
|
|
466
|
-
skipFn: (node) =>
|
|
467
|
-
|
|
473
|
+
skipFn: (node) => {
|
|
474
|
+
let status = false;
|
|
475
|
+
if (
|
|
476
|
+
node.meta?.interface === constants.interfaces.MANY_TO_ANY
|
|
477
|
+
) status = true
|
|
478
|
+
|
|
479
|
+
if ([constants.interfaces.MANY_TO_MANY,
|
|
480
|
+
constants.interfaces.ONE_TO_MANY,
|
|
481
|
+
constants.interfaces.MANY_TO_ONE,
|
|
482
|
+
constants.interfaces.SEO,
|
|
483
|
+
].includes(node.meta?.interface) && (!node.meta?.required || !node.meta?.hidden)) status = true
|
|
484
|
+
return status
|
|
485
|
+
},
|
|
468
486
|
conditionFn: (node) =>
|
|
469
487
|
node.meta?.required === true && !node.isRelationalUpdate,
|
|
470
488
|
mapFn: (node) => ({
|
|
@@ -475,6 +493,7 @@ const validateMetaRules = (
|
|
|
475
493
|
const isTranslationNode =
|
|
476
494
|
node?.meta?.parentInterface === constants.interfaces.TRANSLATIONS ||
|
|
477
495
|
field?.meta?.interface === constants.interfaces.TRANSLATIONS;
|
|
496
|
+
|
|
478
497
|
let fPath = node.key?.replace("[0][0]", "[0]");
|
|
479
498
|
const types = ["update", "delete", "existing", "create"];
|
|
480
499
|
const extractFirstType = (key) => {
|
|
@@ -587,8 +606,8 @@ const validateMetaRules = (
|
|
|
587
606
|
const validType =
|
|
588
607
|
field?.alternateType?.length > 0
|
|
589
608
|
? [field.type, ...field?.alternateType].some((type) =>
|
|
590
|
-
|
|
591
|
-
|
|
609
|
+
typeChecks[type](fieldValue)
|
|
610
|
+
)
|
|
592
611
|
: typeChecks[field.type](fieldValue, { key: currentPath, updateValue });
|
|
593
612
|
|
|
594
613
|
if (!isEmpty(fieldValue) && !validType) {
|
|
@@ -626,9 +645,8 @@ const handleRegexValidation = (
|
|
|
626
645
|
const fieldLabel = formatLabel(field.display_label);
|
|
627
646
|
|
|
628
647
|
// Determine regex flags
|
|
629
|
-
const flags = `${rule.options?.case_sensitive ? "" : "i"}${
|
|
630
|
-
rule.options?.
|
|
631
|
-
}${rule.options?.global ? "g" : ""}`;
|
|
648
|
+
const flags = `${rule.options?.case_sensitive ? "" : "i"}${rule.options?.multiline ? "m" : ""
|
|
649
|
+
}${rule.options?.global ? "g" : ""}`;
|
|
632
650
|
|
|
633
651
|
// Build regex for MATCH/CONTAINS/NOT_CONTAINS
|
|
634
652
|
let regex;
|
|
@@ -1203,10 +1221,12 @@ const validateField = (
|
|
|
1203
1221
|
if (itemType) {
|
|
1204
1222
|
value.forEach((item, index) => {
|
|
1205
1223
|
const itemPath = `${currentPath}[${index}]`;
|
|
1206
|
-
|
|
1207
1224
|
if (
|
|
1208
1225
|
(choices.includes(field?.meta?.interface) && !isEmpty(item)) ||
|
|
1209
|
-
([
|
|
1226
|
+
([
|
|
1227
|
+
constants.interfaces.TAGS,
|
|
1228
|
+
constants.interfaces.ARRAY_OF_VALUES,
|
|
1229
|
+
].includes(field?.meta?.interface) &&
|
|
1210
1230
|
!isEmpty(item))
|
|
1211
1231
|
) {
|
|
1212
1232
|
applyValidations(
|
|
@@ -1404,11 +1424,14 @@ const validate = (data) => {
|
|
|
1404
1424
|
};
|
|
1405
1425
|
|
|
1406
1426
|
const addError = (fieldPath, obj, field) => {
|
|
1407
|
-
fieldPath = [
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1427
|
+
// fieldPath = [
|
|
1428
|
+
// ...choices,
|
|
1429
|
+
// constants.interfaces.TAGS,
|
|
1430
|
+
// constants.interfaces.ARRAY_OF_VALUES,
|
|
1431
|
+
// ].includes(field?.meta?.interface)
|
|
1432
|
+
// ? fieldPath?.replace(/\[\d+\].*$/, "")
|
|
1433
|
+
// : fieldPath;
|
|
1434
|
+
// TODO: Path is Sometimes Become Wrong (Like Unflatten) if we uncomment this
|
|
1412
1435
|
|
|
1413
1436
|
const fieldKey = getLastChildKey(fieldPath);
|
|
1414
1437
|
const isBypass = byPassKeys?.some((key) => key === fieldKey);
|
|
@@ -1422,7 +1445,7 @@ const validate = (data) => {
|
|
|
1422
1445
|
if (
|
|
1423
1446
|
secondParentField &&
|
|
1424
1447
|
secondParentField?.meta?.interface ===
|
|
1425
|
-
|
|
1448
|
+
constants.interfaces.TRANSLATIONS
|
|
1426
1449
|
) {
|
|
1427
1450
|
const languageKey = secondParentField?.meta?.options?.language_field;
|
|
1428
1451
|
const firstParentValue = getValue(formData, firstParent);
|