nox-validation 1.4.2 → 1.4.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/lib/helpers.js CHANGED
@@ -59,6 +59,52 @@ const keyExists = (obj, key, separator = ".") => {
59
59
  });
60
60
  };
61
61
 
62
+ const remainingItems = ({ all = [], obj = {}, isM2A = false }) => {
63
+ const resultSet = new Set(all);
64
+
65
+ if (Array.isArray(obj.existing)) {
66
+ obj.existing.forEach((id) => {
67
+ if (isM2A && id && typeof id === "object" && "item" in id) {
68
+ resultSet.add(id.item);
69
+ } else {
70
+ resultSet.add(id);
71
+ }
72
+ });
73
+ }
74
+
75
+ if (Array.isArray(obj.create)) {
76
+ obj.create.forEach((item, idx) => {
77
+ if (item && item._id) {
78
+ resultSet.add(item._id);
79
+ } else if (item) {
80
+ resultSet.add(`create_${idx}`);
81
+ }
82
+ });
83
+ }
84
+
85
+ if (Array.isArray(obj.update)) {
86
+ obj.update.forEach((item) => {
87
+ if (isM2A && item && item.item) {
88
+ resultSet.add(item.item);
89
+ } else if (item && item._id) {
90
+ resultSet.add(item._id);
91
+ }
92
+ });
93
+ }
94
+
95
+ if (Array.isArray(obj.delete)) {
96
+ obj.delete.forEach((id) => {
97
+ if (isM2A && id && typeof id === "object" && "item" in id) {
98
+ resultSet.delete(id.item);
99
+ } else {
100
+ resultSet.delete(id);
101
+ }
102
+ });
103
+ }
104
+
105
+ return resultSet.size > 0;
106
+ };
107
+
62
108
  const formatLabel = (str) => {
63
109
  return str
64
110
  ?.replace(/_/g, " ")
@@ -275,6 +321,7 @@ const generateField = (
275
321
  ...child,
276
322
  value: childKey,
277
323
  key: childValue,
324
+ isRelationalUpdate: path.includes("update"),
278
325
  };
279
326
  });
280
327
 
@@ -780,7 +827,7 @@ const buildNestedStructure = ({
780
827
  childFields = getCachedFields(relationDetail, relational_fields);
781
828
  }
782
829
  if (item.meta.interface === constants.interfaces.TRANSLATIONS) {
783
- childFields = childFields.map(f => {
830
+ childFields = childFields.map((f) => {
784
831
  if (
785
832
  f.field === relationDetail.foreign_field ||
786
833
  f.field === relationDetail.junction_field_this
@@ -789,8 +836,8 @@ const buildNestedStructure = ({
789
836
  ...f,
790
837
  meta: {
791
838
  ...f.meta,
792
- interface: "none"
793
- }
839
+ interface: "none",
840
+ },
794
841
  };
795
842
  }
796
843
  return f;
@@ -926,7 +973,7 @@ const buildNestedStructure = ({
926
973
 
927
974
  if (parentNode) {
928
975
  parentNode.children.push(node);
929
- }
976
+ }
930
977
  // else {
931
978
  // console.warn("[WARN] Parent node not found for:", node);
932
979
  // }
@@ -1325,7 +1372,37 @@ const getSelectedNodes = ({
1325
1372
 
1326
1373
  if (Array.isArray(currentNode.children)) {
1327
1374
  currentNode.children.forEach((child) => {
1328
- if (!skipFn(currentNode)) traverse(child);
1375
+ let childNode = {
1376
+ ...child,
1377
+ key:
1378
+ currentNode.type === constants.types.ARRAY
1379
+ ? child.key.replace(currentNode.key, `${currentNode.key}[0]`)
1380
+ : child.key,
1381
+ };
1382
+
1383
+ if (Array.isArray(child.children) && child.children.length > 0) {
1384
+ const updateChildKeys = (node, parentKey, newParentKey) => {
1385
+ let updatedNode = {
1386
+ ...node,
1387
+ key: node.key.replace(parentKey, newParentKey),
1388
+ };
1389
+ if (Array.isArray(node.children) && node.children.length > 0) {
1390
+ updatedNode.children = node.children.map((c) =>
1391
+ updateChildKeys(c, parentKey, newParentKey)
1392
+ );
1393
+ }
1394
+ return updatedNode;
1395
+ };
1396
+ if (currentNode.type === constants.types.ARRAY) {
1397
+ childNode = updateChildKeys(
1398
+ child,
1399
+ currentNode.key,
1400
+ `${currentNode.key}[0]`
1401
+ );
1402
+ }
1403
+ }
1404
+
1405
+ if (!skipFn(currentNode)) traverse(childNode);
1329
1406
  });
1330
1407
  }
1331
1408
  }
@@ -1355,4 +1432,5 @@ module.exports = {
1355
1432
  extractRelationalParents,
1356
1433
  getM2AItemParentPath,
1357
1434
  getSelectedNodes,
1435
+ remainingItems,
1358
1436
  };
package/lib/validate.js CHANGED
@@ -11,6 +11,7 @@ const {
11
11
  extractRelationalParents,
12
12
  getM2AItemParentPath,
13
13
  getSelectedNodes,
14
+ remainingItems,
14
15
  } = require("./helpers");
15
16
 
16
17
  const choices = ["radio", "checkboxes", "dropdown_multiple", "dropdown"];
@@ -171,36 +172,70 @@ const handleMinMaxValidation = (
171
172
  }
172
173
  };
173
174
 
174
- const isEmptyRelational = ({ api_version, value, interface }) => {
175
- if (
176
- api_version === constants.API_VERSION.V1 &&
177
- interface !== constants.interfaces.TRANSLATIONS
178
- ) {
179
- if (interface === constants.interfaces.MANY_TO_ANY) {
175
+ const isEmptyRelational = ({
176
+ api_version,
177
+ value,
178
+ interface,
179
+ onlyFormFields,
180
+ existingValue,
181
+ }) => {
182
+ if (!onlyFormFields) {
183
+ if (
184
+ api_version === constants.API_VERSION.V1 &&
185
+ interface !== constants.interfaces.TRANSLATIONS
186
+ ) {
187
+ if (interface === constants.interfaces.MANY_TO_ANY) {
188
+ return (
189
+ value &&
190
+ typeChecks[constants.types.OBJECT](value) &&
191
+ value.hasOwnProperty("collection") &&
192
+ value.hasOwnProperty("sort") &&
193
+ value.hasOwnProperty("item") &&
194
+ value.collection !== null &&
195
+ value.collection !== "" &&
196
+ value.sort !== null &&
197
+ value.sort !== "" &&
198
+ value.item !== null &&
199
+ value.item !== ""
200
+ );
201
+ } else {
202
+ return value?.length > 0;
203
+ }
204
+ } else {
180
205
  return (
181
- value &&
182
- typeChecks[constants.types.OBJECT](value) &&
183
- value.hasOwnProperty("collection") &&
184
- value.hasOwnProperty("sort") &&
185
- value.hasOwnProperty("item") &&
186
- value.collection !== null &&
187
- value.collection !== "" &&
188
- value.sort !== null &&
189
- value.sort !== "" &&
190
- value.item !== null &&
191
- value.item !== ""
206
+ value?.create?.length > 0 ||
207
+ value?.existing?.length > 0 ||
208
+ value?.update?.length > 0
192
209
  );
193
- } else {
194
- return value?.length > 0;
210
+ }
211
+ } else if (api_version === constants.API_VERSION.V2) {
212
+ if (isEmpty(existingValue)) return true;
213
+
214
+ switch (interface) {
215
+ case constants.interfaces.FILE:
216
+ case constants.interfaces.FILE_IMAGE:
217
+ case constants.interfaces.FILES:
218
+ case constants.interfaces.MANY_TO_MANY:
219
+ case constants.interfaces.ONE_TO_MANY:
220
+ case constants.interfaces.MANY_TO_ONE:
221
+ return remainingItems({
222
+ all: existingValue.map((item) => item._id),
223
+ obj: value,
224
+ });
225
+ case constants.interfaces.MANY_TO_ANY:
226
+ return remainingItems({
227
+ all: existingValue.map((item) => item.item),
228
+ obj: value,
229
+ isM2A: true,
230
+ });
231
+
232
+ // constants.interfaces.TRANSLATIONS,
233
+ default:
234
+ return true;
195
235
  }
196
236
  } else {
197
- return (
198
- value?.create?.length > 0 ||
199
- value?.existing?.length > 0 ||
200
- value?.update?.length > 0
201
- );
237
+ return true;
202
238
  }
203
- return false;
204
239
  };
205
240
 
206
241
  const validateMetaRules = (
@@ -214,12 +249,16 @@ const validateMetaRules = (
214
249
  apiVersion,
215
250
  fieldOptions,
216
251
  formData,
217
- language_codes = []
252
+ language_codes = [],
253
+ existingForm = {}
218
254
  ) => {
219
255
  const fieldValue = providedValue;
220
256
 
221
257
  const { required = false, nullable = false, options } = field?.meta ?? {};
222
258
 
259
+ if (field.isRelationalUpdate) {
260
+ onlyFormFields = true;
261
+ }
223
262
  const isRelational = relational_interfaces.includes(field?.meta?.interface);
224
263
 
225
264
  if (
@@ -275,15 +314,20 @@ const validateMetaRules = (
275
314
  }
276
315
 
277
316
  const isValidRelational =
278
- required && isRelational && !onlyFormFields
317
+ required && isRelational
279
318
  ? isEmptyRelational({
280
319
  api_version: apiVersion,
281
320
  value: fieldValue,
282
321
  interface: field?.meta?.interface,
322
+ onlyFormFields,
323
+ existingValue: getValue(existingForm, currentPath),
283
324
  })
284
325
  : true;
285
326
 
286
- if ((required && isEmpty(fieldValue)) || !isValidRelational) {
327
+ if (
328
+ (required && !onlyFormFields && isEmpty(fieldValue)) ||
329
+ !isValidRelational
330
+ ) {
287
331
  const message = error_messages.REQUIRED.replace(
288
332
  `{field}`,
289
333
  formatLabel(field.display_label)
@@ -318,18 +362,21 @@ const validateMetaRules = (
318
362
  }
319
363
 
320
364
  if (
321
- !fieldValue &&
322
- !field?.meta?.hidden &&
323
- ![
324
- constants.interfaces.FILES,
325
- constants.interfaces.FILE,
326
- constants.interfaces.FILE_IMAGE,
327
- constants.interfaces.MANY_TO_MANY,
328
- constants.interfaces.ONE_TO_MANY,
329
- constants.interfaces.MANY_TO_ONE,
330
- constants.interfaces.MANY_TO_ANY,
331
- "none",
332
- ].includes(field?.meta?.interface)
365
+ (isEmpty(fieldValue) &&
366
+ !field?.meta?.hidden &&
367
+ ![
368
+ constants.interfaces.FILES,
369
+ constants.interfaces.FILE,
370
+ constants.interfaces.FILE_IMAGE,
371
+ constants.interfaces.MANY_TO_MANY,
372
+ constants.interfaces.ONE_TO_MANY,
373
+ constants.interfaces.MANY_TO_ONE,
374
+ constants.interfaces.MANY_TO_ANY,
375
+ "none",
376
+ ].includes(field?.meta?.interface)) ||
377
+ (field?.meta?.interface === constants.interfaces.TRANSLATIONS &&
378
+ language_codes?.length &&
379
+ !onlyFormFields)
333
380
  ) {
334
381
  const isTranslationChild =
335
382
  field?.meta?.interface === constants.interfaces.TRANSLATIONS;
@@ -338,11 +385,14 @@ const validateMetaRules = (
338
385
  node: field,
339
386
  skipFn: (node) =>
340
387
  node.meta?.interface === constants.interfaces.MANY_TO_ANY,
341
- conditionFn: (node) => node.meta?.required === true,
342
- mapFn: (node) => ({ key: node.key, label: node.display_label }),
388
+ conditionFn: (node) =>
389
+ node.meta?.required === true && !node.isRelationalUpdate,
390
+ mapFn: (node) => ({
391
+ key: node.key,
392
+ label: node.display_label,
393
+ }),
343
394
  actionFn: (node) => {
344
395
  let fPath = node.key;
345
-
346
396
  if (fPath.includes("update.")) return;
347
397
  if (fPath.includes("create.") && !isTranslationChild) return;
348
398
  if (fPath.includes("delete.") && !isTranslationChild) return;
@@ -371,16 +421,31 @@ const validateMetaRules = (
371
421
 
372
422
  if (isMultiLang) {
373
423
  language_codes.forEach((lang, index) => {
374
- const langPath = fPath.replace("create.", `create[${index}].`);
375
- const transformedPath = fPath.replace("create.", `${lang}.`);
376
- buildError(langPath, transformedPath);
424
+ let langPath,
425
+ transformedPath,
426
+ isAdded = false;
427
+
428
+ if (fPath.includes("create[0].")) {
429
+ const exist = fieldValue?.create?.find(
430
+ (item) => item.languages_code === lang
431
+ );
432
+ if (exist) isAdded = true;
433
+
434
+ langPath = fPath.replace("create[0].", `create[${index}].`);
435
+ transformedPath = fPath.replace("create[0].", `create.${lang}.`);
436
+ } else {
437
+ langPath = fPath.replace("create.", `create[${index}].`);
438
+ transformedPath = fPath.replace("create.", `create.${lang}.`);
439
+ }
440
+
441
+ if (!isAdded) buildError(langPath, transformedPath);
377
442
  });
378
443
  } else {
379
444
  const singlePath = fPath.replace(
380
445
  "create.",
381
446
  isTranslationChild ? "create[lang_code]." : "create[0]."
382
447
  );
383
- buildError(singlePath);
448
+ if (isEmpty(fieldValue)) buildError(singlePath);
384
449
  }
385
450
  },
386
451
  });
@@ -828,7 +893,8 @@ const validateField = (
828
893
  onlyFormFields,
829
894
  apiVersion,
830
895
  fieldOptions,
831
- language_codes
896
+ language_codes,
897
+ existingForm
832
898
  ) => {
833
899
  if (onlyFormFields == true && (value === undefined || value === null)) return;
834
900
 
@@ -850,7 +916,8 @@ const validateField = (
850
916
  apiVersion,
851
917
  fieldOptions,
852
918
  formData,
853
- language_codes
919
+ language_codes,
920
+ existingForm
854
921
  );
855
922
 
856
923
  if (
@@ -886,7 +953,8 @@ const validateField = (
886
953
  onlyFormFields,
887
954
  apiVersion,
888
955
  fieldOptions,
889
- language_codes
956
+ language_codes,
957
+ existingForm
890
958
  )
891
959
  );
892
960
  } else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
@@ -937,7 +1005,8 @@ const validateField = (
937
1005
  onlyFormFields,
938
1006
  apiVersion,
939
1007
  fieldOptions,
940
- language_codes
1008
+ language_codes,
1009
+ existingForm
941
1010
  )
942
1011
  );
943
1012
  });
@@ -1044,6 +1113,7 @@ const validate = (data) => {
1044
1113
  language,
1045
1114
  maxLevel,
1046
1115
  onlyFormFields,
1116
+ existingForm = {},
1047
1117
  } = data;
1048
1118
 
1049
1119
  const error_messages =
@@ -1294,7 +1364,8 @@ const validate = (data) => {
1294
1364
  onlyFormFields,
1295
1365
  apiVersion,
1296
1366
  fieldOptions,
1297
- data.language_codes
1367
+ data.language_codes,
1368
+ existingForm
1298
1369
  );
1299
1370
  });
1300
1371
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nox-validation",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "validate dynamic schema",
5
5
  "main": "index.js",
6
6
  "scripts": {