nox-validation 1.4.1 → 1.4.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/lib/constant.js CHANGED
@@ -143,6 +143,11 @@ const interfaces = Object.freeze({
143
143
  FILES: "files",
144
144
  TRANSLATIONS: "translations",
145
145
  RELATIONAL: "relational",
146
+ CHECKBOX: "checkboxes",
147
+ ITEMS: "items",
148
+ MULTIPLE_DROPDOWN: "dropdown_multiple",
149
+ TAGS: "tags",
150
+ ARRAY_OF_VALUES: "array_of_values",
146
151
  });
147
152
 
148
153
  const API_VERSION = Object.freeze({
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"];
@@ -60,9 +61,16 @@ const typeChecks = {
60
61
  [constants.types.TIME]: (val) =>
61
62
  typeof val === "string" &&
62
63
  /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
63
- [constants.types.TIMESTAMP]: (val) =>
64
- typeof val === "string" &&
65
- /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
64
+ [constants.types.TIMESTAMP]: (val, data) => {
65
+ if (val instanceof Date && !isNaN(val)) return true;
66
+ if (typeof val === "string" && !isNaN(Date.parse(val))) {
67
+ if (data && data?.key && data?.updateValue) {
68
+ data.updateValue(data.key, new Date(val));
69
+ }
70
+ return true;
71
+ }
72
+ return false;
73
+ },
66
74
 
67
75
  [constants.types.BOOLEAN]: (val, data) => {
68
76
  if (typeof val === "boolean") return true;
@@ -164,36 +172,70 @@ const handleMinMaxValidation = (
164
172
  }
165
173
  };
166
174
 
167
- const isEmptyRelational = ({ api_version, value, interface }) => {
168
- if (
169
- api_version === constants.API_VERSION.V1 &&
170
- interface !== constants.interfaces.TRANSLATIONS
171
- ) {
172
- 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 {
173
205
  return (
174
- value &&
175
- typeChecks[constants.types.OBJECT](value) &&
176
- value.hasOwnProperty("collection") &&
177
- value.hasOwnProperty("sort") &&
178
- value.hasOwnProperty("item") &&
179
- value.collection !== null &&
180
- value.collection !== "" &&
181
- value.sort !== null &&
182
- value.sort !== "" &&
183
- value.item !== null &&
184
- value.item !== ""
206
+ value?.create?.length > 0 ||
207
+ value?.existing?.length > 0 ||
208
+ value?.update?.length > 0
185
209
  );
186
- } else {
187
- 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;
188
235
  }
189
236
  } else {
190
- return (
191
- value?.create?.length > 0 ||
192
- value?.existing?.length > 0 ||
193
- value?.update?.length > 0
194
- );
237
+ return true;
195
238
  }
196
- return false;
197
239
  };
198
240
 
199
241
  const validateMetaRules = (
@@ -207,12 +249,16 @@ const validateMetaRules = (
207
249
  apiVersion,
208
250
  fieldOptions,
209
251
  formData,
210
- language_codes = []
252
+ language_codes = [],
253
+ existingForm = {}
211
254
  ) => {
212
255
  const fieldValue = providedValue;
213
256
 
214
257
  const { required = false, nullable = false, options } = field?.meta ?? {};
215
258
 
259
+ if (field.isRelationalUpdate) {
260
+ onlyFormFields = true;
261
+ }
216
262
  const isRelational = relational_interfaces.includes(field?.meta?.interface);
217
263
 
218
264
  if (
@@ -235,16 +281,53 @@ const validateMetaRules = (
235
281
  );
236
282
  }
237
283
 
284
+ const arrayInterfaces = [
285
+ constants.interfaces.CHECKBOX,
286
+ constants.interfaces.ITEMS,
287
+ constants.interfaces.MULTIPLE_DROPDOWN,
288
+ constants.interfaces.TAGS,
289
+ constants.interfaces.ARRAY_OF_VALUES,
290
+ ];
291
+
292
+ if (
293
+ required &&
294
+ arrayInterfaces.includes(field?.meta?.interface) &&
295
+ onlyFormFields &&
296
+ fieldValue &&
297
+ Array.isArray(fieldValue) &&
298
+ fieldValue?.length === 0
299
+ ) {
300
+ const message = error_messages.NOT_EMPTY.replace(
301
+ `{field}`,
302
+ formatLabel(field.display_label)
303
+ );
304
+ addError(
305
+ currentPath,
306
+ {
307
+ label: formatLabel(field.display_label),
308
+ fieldPath: currentPath,
309
+ description: "",
310
+ message,
311
+ },
312
+ field
313
+ );
314
+ }
315
+
238
316
  const isValidRelational =
239
- required && isRelational && !onlyFormFields
317
+ required && isRelational
240
318
  ? isEmptyRelational({
241
319
  api_version: apiVersion,
242
320
  value: fieldValue,
243
321
  interface: field?.meta?.interface,
322
+ onlyFormFields,
323
+ existingValue: getValue(existingForm, currentPath),
244
324
  })
245
325
  : true;
246
326
 
247
- if ((required && isEmpty(fieldValue)) || !isValidRelational) {
327
+ if (
328
+ (required && !onlyFormFields && isEmpty(fieldValue)) ||
329
+ !isValidRelational
330
+ ) {
248
331
  const message = error_messages.REQUIRED.replace(
249
332
  `{field}`,
250
333
  formatLabel(field.display_label)
@@ -279,18 +362,21 @@ const validateMetaRules = (
279
362
  }
280
363
 
281
364
  if (
282
- !fieldValue &&
283
- !field?.meta?.hidden &&
284
- ![
285
- constants.interfaces.FILES,
286
- constants.interfaces.FILE,
287
- constants.interfaces.FILE_IMAGE,
288
- constants.interfaces.MANY_TO_MANY,
289
- constants.interfaces.ONE_TO_MANY,
290
- constants.interfaces.MANY_TO_ONE,
291
- constants.interfaces.MANY_TO_ANY,
292
- "none",
293
- ].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)
294
380
  ) {
295
381
  const isTranslationChild =
296
382
  field?.meta?.interface === constants.interfaces.TRANSLATIONS;
@@ -299,11 +385,14 @@ const validateMetaRules = (
299
385
  node: field,
300
386
  skipFn: (node) =>
301
387
  node.meta?.interface === constants.interfaces.MANY_TO_ANY,
302
- conditionFn: (node) => node.meta?.required === true,
303
- 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
+ }),
304
394
  actionFn: (node) => {
305
395
  let fPath = node.key;
306
-
307
396
  if (fPath.includes("update.")) return;
308
397
  if (fPath.includes("create.") && !isTranslationChild) return;
309
398
  if (fPath.includes("delete.") && !isTranslationChild) return;
@@ -332,16 +421,31 @@ const validateMetaRules = (
332
421
 
333
422
  if (isMultiLang) {
334
423
  language_codes.forEach((lang, index) => {
335
- const langPath = fPath.replace("create.", `create[${index}].`);
336
- const transformedPath = fPath.replace("create.", `${lang}.`);
337
- 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);
338
442
  });
339
443
  } else {
340
444
  const singlePath = fPath.replace(
341
445
  "create.",
342
446
  isTranslationChild ? "create[lang_code]." : "create[0]."
343
447
  );
344
- buildError(singlePath);
448
+ if (isEmpty(fieldValue)) buildError(singlePath);
345
449
  }
346
450
  },
347
451
  });
@@ -789,16 +893,10 @@ const validateField = (
789
893
  onlyFormFields,
790
894
  apiVersion,
791
895
  fieldOptions,
792
- language_codes
896
+ language_codes,
897
+ existingForm
793
898
  ) => {
794
- const isRelational = relational_interfaces.includes(field?.meta?.interface);
795
-
796
- if (
797
- onlyFormFields == true &&
798
- isRelational &&
799
- (value === undefined || value === null)
800
- )
801
- return;
899
+ if (onlyFormFields == true && (value === undefined || value === null)) return;
802
900
 
803
901
  const { is_m2a_item } = field?.meta;
804
902
  const currentPath = fieldPath
@@ -818,7 +916,8 @@ const validateField = (
818
916
  apiVersion,
819
917
  fieldOptions,
820
918
  formData,
821
- language_codes
919
+ language_codes,
920
+ existingForm
822
921
  );
823
922
 
824
923
  if (
@@ -854,7 +953,8 @@ const validateField = (
854
953
  onlyFormFields,
855
954
  apiVersion,
856
955
  fieldOptions,
857
- language_codes
956
+ language_codes,
957
+ existingForm
858
958
  )
859
959
  );
860
960
  } else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
@@ -905,7 +1005,8 @@ const validateField = (
905
1005
  onlyFormFields,
906
1006
  apiVersion,
907
1007
  fieldOptions,
908
- language_codes
1008
+ language_codes,
1009
+ existingForm
909
1010
  )
910
1011
  );
911
1012
  });
@@ -1012,6 +1113,7 @@ const validate = (data) => {
1012
1113
  language,
1013
1114
  maxLevel,
1014
1115
  onlyFormFields,
1116
+ existingForm = {},
1015
1117
  } = data;
1016
1118
 
1017
1119
  const error_messages =
@@ -1262,7 +1364,8 @@ const validate = (data) => {
1262
1364
  onlyFormFields,
1263
1365
  apiVersion,
1264
1366
  fieldOptions,
1265
- data.language_codes
1367
+ data.language_codes,
1368
+ existingForm
1266
1369
  );
1267
1370
  });
1268
1371
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nox-validation",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "validate dynamic schema",
5
5
  "main": "index.js",
6
6
  "scripts": {