nox-validation 1.3.1 → 1.3.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/README.md CHANGED
@@ -32,9 +32,9 @@ npm install nox-validation
32
32
  ```javascript
33
33
  const { validate, helpers } = require("nox-validation");
34
34
 
35
- const all_fields = []; // fields of all collection
35
+ const all_fields = [];
36
36
 
37
- const relations = []; // all relations data
37
+ const relations = [];
38
38
 
39
39
  const schema = [
40
40
  {
@@ -113,6 +113,8 @@ const result = validate({
113
113
  apiVersion: "v1",
114
114
  language: "nl",
115
115
  maxLevel: 3,
116
+ onlyFormFields: false,
117
+ language_codes:[]
116
118
  });
117
119
  ```
118
120
 
@@ -133,7 +135,9 @@ const result = validate({
133
135
  byPassKeys: [],
134
136
  apiVersion: "v1",
135
137
  language: "nl",
136
- maxLevel: 3
138
+ maxLevel: 3,
139
+ onlyFormFields: false,
140
+ language_codes:[]
137
141
  });
138
142
  ```
139
143
 
@@ -141,9 +145,9 @@ const result = validate({
141
145
 
142
146
  ```javascript
143
147
  {
144
- status: true, // Validation passed
145
- error: {}, // Empty if no errors
146
- data: {} // Validated data
148
+ status: true,
149
+ error: {},
150
+ data: {}
147
151
  }
148
152
  ```
149
153
 
@@ -207,6 +211,16 @@ const result = validate({
207
211
 
208
212
  - Defines the level for generate tree structure.
209
213
 
214
+ #### `onlyFormFields` (Boolean)
215
+
216
+ Indicates the context in which the form is being used.
217
+ - `false`: Used when creating new data (full form).
218
+ - `true`: Used when editing or displaying only existing form fields.
219
+
220
+ #### `language_codes` (Array of IDs)
221
+ - Represents the selected translation language IDs.
222
+ - Each item in the array should be a valid language code or `_id` used for handling multilingual content.
223
+
210
224
  ## Result
211
225
 
212
226
  The `validate` function returns an object with two keys:
package/lib/helpers.js CHANGED
@@ -10,7 +10,9 @@ const getAllFields = (obj, parentPath = "") => {
10
10
  : `${path}[${index}]`
11
11
  );
12
12
  }
13
- return typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])
13
+ return typeof obj[key] === "object" &&
14
+ obj[key] !== null &&
15
+ !Array.isArray(obj[key])
14
16
  ? getAllFields(obj[key], path)
15
17
  : path;
16
18
  });
@@ -133,7 +135,10 @@ const getFormPath = (inputFields, fieldPath) => {
133
135
  const field = inputFields.find((f) => f.path === currentPath);
134
136
 
135
137
  if (field) {
136
- formPath += field.type === constants.types.ARRAY ? `${field.field}[0]` : field.field;
138
+ formPath +=
139
+ field.type === constants.types.ARRAY
140
+ ? `${field.field}[0]`
141
+ : field.field;
137
142
  } else {
138
143
  formPath += `.${pathParts[i]}`;
139
144
  }
@@ -142,7 +147,9 @@ const getFormPath = (inputFields, fieldPath) => {
142
147
  }
143
148
 
144
149
  const cleanedFormPath = formPath.replace(/\.\[/g, "[");
145
- return cleanedFormPath.endsWith("[0]") ? cleanedFormPath.split("[0]")[0] : cleanedFormPath;
150
+ return cleanedFormPath.endsWith("[0]")
151
+ ? cleanedFormPath.split("[0]")[0]
152
+ : cleanedFormPath;
146
153
  };
147
154
 
148
155
  const isEmpty = (val) => {
@@ -157,28 +164,6 @@ const isEmpty = (val) => {
157
164
  return false;
158
165
  };
159
166
 
160
- // field?.field_type (Possible Values => Single, Object, Array)
161
- // 1. Single => Root Field Then Its Single
162
- // 2. Object => Nested Field Like Inside Array Or Object
163
- // 3. Array => Any Kind Of Array Array Of String, Object etc
164
-
165
- // field?.type (Possible Values)
166
- // 1. String
167
- // 2. Number
168
- // 3. Date
169
- // 4. Buffer
170
- // 5. Boolean
171
- // 6. Mixed
172
- // 7. ObjectId
173
- // 8. Object
174
- // 9. Array
175
- // 10. Alias
176
-
177
- // field?.schema_definition?.type
178
- // it is used for specially when field?.type is Array, but some times both are Array Then We Have to check
179
-
180
- // field?.meta?.interface
181
-
182
167
  const generateType = (field, api) => {
183
168
  let { type, schema_definition, meta } = field;
184
169
  let interfaceType = meta?.interface;
@@ -201,9 +186,10 @@ const generateType = (field, api) => {
201
186
  if (interfaceType && interfaceType !== "none") {
202
187
  // We Need to find Relation
203
188
  if (
204
- [constants.interfaces.MANY_TO_ANY, constants.interfaces.TRANSLATIONS].includes(
205
- interfaceType
206
- )
189
+ [
190
+ constants.interfaces.MANY_TO_ANY,
191
+ constants.interfaces.TRANSLATIONS,
192
+ ].includes(interfaceType)
207
193
  ) {
208
194
  find_relations = true;
209
195
  // update type and array type accordingly interface
@@ -274,13 +260,21 @@ const generateField = (
274
260
  type,
275
261
  childrenFields = [],
276
262
  relationType = "none",
277
- alternateType=[]
263
+ alternateType = [],
264
+ meta = {
265
+ required: false,
266
+ nullable: false,
267
+ hidden: false,
268
+ }
278
269
  ) => {
279
270
  childrenFields = childrenFields?.map((child) => {
271
+ const childKey = path ? `${path}.${child.key}` : child.key;
272
+ const childValue = path ? `${path}.${child.value}` : child.value;
273
+
280
274
  return {
281
275
  ...child,
282
- value: `${path}.${child.value}`,
283
- key: `${path}.${child.key}`,
276
+ value: childKey,
277
+ key: childValue,
284
278
  };
285
279
  });
286
280
 
@@ -293,9 +287,7 @@ const generateField = (
293
287
  type: type,
294
288
  alternateType,
295
289
  meta: {
296
- required: false,
297
- nullable: false,
298
- hidden: false,
290
+ ...meta,
299
291
  interface: relationType,
300
292
  },
301
293
  validations: [],
@@ -321,7 +313,11 @@ const createChildrenFieldsFiles = (key) => {
321
313
  return [existingField, deleteField];
322
314
  };
323
315
 
324
- const generateRelationalFieldV1 = (key = "", collectionFields = [], relationType) => {
316
+ const generateRelationalFieldV1 = (
317
+ key = "",
318
+ collectionFields = [],
319
+ relationType
320
+ ) => {
325
321
  if (relationType === constants.interfaces.MANY_TO_ANY) {
326
322
  return [
327
323
  generateField(
@@ -330,16 +326,35 @@ const generateRelationalFieldV1 = (key = "", collectionFields = [], relationType
330
326
  constants.types.STRING,
331
327
  constants.types.STRING
332
328
  ),
333
- generateField("sort", `${key}.sort`, constants.types.NUMBER, constants.types.NUMBER),
334
- generateField("item", `${key}.item`, constants.types.OBJECT_ID, constants.types.OBJECT_ID),
329
+ generateField(
330
+ "sort",
331
+ `${key}.sort`,
332
+ constants.types.NUMBER,
333
+ constants.types.NUMBER
334
+ ),
335
+ generateField(
336
+ "item",
337
+ `${key}.item`,
338
+ constants.types.OBJECT_ID,
339
+ constants.types.OBJECT_ID
340
+ ),
335
341
  ];
336
342
  } else {
337
343
  return null;
338
344
  }
339
345
  };
340
346
 
341
- const generateRelationalField = (key = "", collectionFields = [], relationType) => {
347
+ const generateRelationalField = (
348
+ key = "",
349
+ collectionFields = [],
350
+ relationType
351
+ ) => {
342
352
  if (relationType === constants.interfaces.MANY_TO_ANY) {
353
+ const collection_id_meta = {
354
+ required: true,
355
+ nullable: false,
356
+ hidden: false,
357
+ };
343
358
  const existingField = generateField(
344
359
  "existing",
345
360
  `${key}.existing`,
@@ -353,7 +368,12 @@ const generateRelationalField = (key = "", collectionFields = [], relationType)
353
368
  constants.types.STRING,
354
369
  constants.types.STRING
355
370
  ),
356
- generateField("sort", `${key}.existing.sort`, constants.types.NUMBER, constants.types.NUMBER),
371
+ generateField(
372
+ "sort",
373
+ `${key}.existing.sort`,
374
+ constants.types.NUMBER,
375
+ constants.types.NUMBER
376
+ ),
357
377
  generateField(
358
378
  "item",
359
379
  `${key}.existing.item`,
@@ -374,7 +394,12 @@ const generateRelationalField = (key = "", collectionFields = [], relationType)
374
394
  constants.types.STRING,
375
395
  constants.types.STRING
376
396
  ),
377
- generateField("sort", `${key}.delete.sort`, constants.types.NUMBER, constants.types.NUMBER),
397
+ generateField(
398
+ "sort",
399
+ `${key}.delete.sort`,
400
+ constants.types.NUMBER,
401
+ constants.types.NUMBER
402
+ ),
378
403
  generateField(
379
404
  "item",
380
405
  `${key}.delete.item`,
@@ -396,15 +421,31 @@ const generateRelationalField = (key = "", collectionFields = [], relationType)
396
421
  constants.types.STRING,
397
422
  constants.types.STRING
398
423
  ),
399
- generateField("sort", `${key}.create.sort`, constants.types.NUMBER, constants.types.NUMBER),
424
+ generateField(
425
+ "collection",
426
+ `${key}.create.collection_id`,
427
+ constants.types.OBJECT_ID,
428
+ constants.types.OBJECT_ID,
429
+ [],
430
+ "none",
431
+ [],
432
+ collection_id_meta
433
+ ),
434
+ generateField(
435
+ "sort",
436
+ `${key}.create.sort`,
437
+ constants.types.NUMBER,
438
+ constants.types.NUMBER
439
+ ),
400
440
  generateField(
401
441
  "item",
402
442
  `${key}.create.item`,
403
443
  constants.types.OBJECT,
404
444
  constants.types.OBJECT,
405
445
  collectionFields,
406
- 'none',
407
- [constants.types.OBJECT_ID]
446
+ "none",
447
+ [constants.types.OBJECT_ID],
448
+ { ...collection_id_meta, is_m2a_item: true }
408
449
  ),
409
450
  ];
410
451
  const updateField = generateField(
@@ -421,7 +462,22 @@ const generateRelationalField = (key = "", collectionFields = [], relationType)
421
462
  constants.types.STRING,
422
463
  constants.types.STRING
423
464
  ),
424
- generateField("sort", `${key}.update.sort`, constants.types.NUMBER, constants.types.NUMBER),
465
+ generateField(
466
+ "collection",
467
+ `${key}.update.collection_id`,
468
+ constants.types.OBJECT_ID,
469
+ constants.types.OBJECT_ID,
470
+ [],
471
+ "none",
472
+ [],
473
+ collection_id_meta
474
+ ),
475
+ generateField(
476
+ "sort",
477
+ `${key}.update.sort`,
478
+ constants.types.NUMBER,
479
+ constants.types.NUMBER
480
+ ),
425
481
  generateField(
426
482
  "item",
427
483
  `${key}.update.item`,
@@ -441,7 +497,12 @@ const generateRelationalField = (key = "", collectionFields = [], relationType)
441
497
  constants.types.OBJECT_ID,
442
498
  constants.types.ARRAY
443
499
  ),
444
- generateField("delete", `${key}.delete`, constants.types.OBJECT_ID, constants.types.ARRAY),
500
+ generateField(
501
+ "delete",
502
+ `${key}.delete`,
503
+ constants.types.OBJECT_ID,
504
+ constants.types.ARRAY
505
+ ),
445
506
  generateField(
446
507
  "create",
447
508
  `${key}.create`,
@@ -510,7 +571,8 @@ const getForeignCollectionDetails = ({
510
571
  ? relational?.one_allowed_collections_id
511
572
  : mainTable.many_collection_id,
512
573
  foreign_field:
513
- iFace === constants.interfaces.MANY_TO_MANY || iFace === constants.interfaces.TRANSLATIONS
574
+ iFace === constants.interfaces.MANY_TO_MANY ||
575
+ iFace === constants.interfaces.TRANSLATIONS
514
576
  ? "_id"
515
577
  : iFace === constants.interfaces.MANY_TO_ANY
516
578
  ? "Primary Key"
@@ -519,11 +581,14 @@ const getForeignCollectionDetails = ({
519
581
  junction_collection: relational?.many_collection_id,
520
582
  junction_field_this: relational?.junction_field,
521
583
  junction_field_foreign:
522
- iFace === constants.interfaces.MANY_TO_ANY ? "item" : relational?.many_field,
584
+ iFace === constants.interfaces.MANY_TO_ANY
585
+ ? "item"
586
+ : relational?.many_field,
523
587
  }),
524
588
  ...(iFace === constants.interfaces.MANY_TO_ANY && {
525
589
  junction_field_ref: "collection",
526
- foreign_collection_ref: relational?.one_allowed_collections?.join(", "),
590
+ foreign_collection_ref:
591
+ relational?.one_allowed_collections?.join(", "),
527
592
  }),
528
593
  };
529
594
  }
@@ -576,7 +641,9 @@ const getCachedFields = (relationDetail, relational_fields) => {
576
641
  if (!isMultiple) {
577
642
  return getField(relationDetail.foreign_collection);
578
643
  } else {
579
- return relationDetail.foreign_collection.flatMap((schemaId) => getField(schemaId));
644
+ return relationDetail.foreign_collection.flatMap((schemaId) =>
645
+ getField(schemaId)
646
+ );
580
647
  }
581
648
  };
582
649
 
@@ -585,12 +652,19 @@ const getCachedOrFetchFields = (schemaId, allFields, relational_fields) => {
585
652
  return relational_fields[schemaId]; // Return cached fields if available
586
653
  }
587
654
 
588
- const fields = allFields?.filter((field) => field.schema_id === schemaId) || [];
655
+ const fields =
656
+ allFields?.filter((field) => field.schema_id === schemaId) || [];
589
657
  relational_fields[schemaId] = fields; // Cache the fields
590
658
  return fields;
591
659
  };
592
660
 
593
- const getChildFields = (relationDetail, allFields, relational_fields, isTranslation, name) => {
661
+ const getChildFields = (
662
+ relationDetail,
663
+ allFields,
664
+ relational_fields,
665
+ isTranslation,
666
+ name
667
+ ) => {
594
668
  let key = isTranslation
595
669
  ? [
596
670
  ...(Array.isArray(relationDetail.junction_collection)
@@ -640,7 +714,9 @@ const buildNestedStructure = ({
640
714
  const root = {};
641
715
  const nodeMap = new Map();
642
716
 
643
- schemaFields.sort((a, b) => a.path.split(".").length - b.path.split(".").length);
717
+ schemaFields.sort(
718
+ (a, b) => a.path.split(".").length - b.path.split(".").length
719
+ );
644
720
 
645
721
  schemaFields.forEach((item) => {
646
722
  const pathParts = item.path.split(".");
@@ -653,7 +729,8 @@ const buildNestedStructure = ({
653
729
  constants.interfaces.FILE_IMAGE,
654
730
  ].includes(item?.meta?.interface);
655
731
 
656
- const currentDepth = currentDepthMap.get(isRoot ? item.path : rootPath) || 0;
732
+ const currentDepth =
733
+ currentDepthMap.get(isRoot ? item.path : rootPath) || 0;
657
734
 
658
735
  let childFields;
659
736
 
@@ -706,7 +783,8 @@ const buildNestedStructure = ({
706
783
  }
707
784
 
708
785
  const isArray =
709
- item.type === item?.schema_definition.type && item.type === constants.types.ARRAY;
786
+ item.type === item?.schema_definition.type &&
787
+ item.type === constants.types.ARRAY;
710
788
 
711
789
  let children = [];
712
790
 
@@ -717,9 +795,17 @@ const buildNestedStructure = ({
717
795
  apiVersion === constants.API_VERSION.V1 &&
718
796
  item.meta?.interface !== constants.interfaces.TRANSLATIONS
719
797
  ) {
720
- children = generateRelationalFieldV1(key, childFields, item.meta?.interface);
798
+ children = generateRelationalFieldV1(
799
+ key,
800
+ childFields,
801
+ item.meta?.interface
802
+ );
721
803
  } else {
722
- children = generateRelationalField(key, childFields, item.meta?.interface);
804
+ children = generateRelationalField(
805
+ key,
806
+ childFields,
807
+ item.meta?.interface
808
+ );
723
809
  }
724
810
  }
725
811
 
@@ -730,7 +816,7 @@ const buildNestedStructure = ({
730
816
  display_label: "_id",
731
817
  key: `${key}._id`,
732
818
  value: `${key}._id`,
733
- alternateType:[],
819
+ alternateType: [],
734
820
  meta: {
735
821
  interface: "none",
736
822
  required: false,
@@ -761,17 +847,20 @@ const buildNestedStructure = ({
761
847
  hidden: item.meta?.hidden || false,
762
848
  options: item.meta?.options || {},
763
849
  },
764
- validations: generateModifiedRules(item?.meta, item?.meta?.validations?.validation_msg),
850
+ validations: generateModifiedRules(
851
+ item?.meta,
852
+ item?.meta?.validations?.validation_msg
853
+ ),
765
854
  custom_error_message: item?.meta?.validations?.validation_msg,
766
855
  children,
767
- // childFields?.length > 0
768
- // ? isV2File
769
- // ? childFields
770
- // : apiVersion === constants.API_VERSION.V1 &&
771
- // item.meta?.interface !== constants.interfaces.TRANSLATIONS
772
- // ? generateRelationalFieldV1(key, childFields, item.meta?.interface)
773
- // : generateRelationalField(key, childFields, item.meta?.interface)
774
- // : [],
856
+ // childFields?.length > 0
857
+ // ? isV2File
858
+ // ? childFields
859
+ // : apiVersion === constants.API_VERSION.V1 &&
860
+ // item.meta?.interface !== constants.interfaces.TRANSLATIONS
861
+ // ? generateRelationalFieldV1(key, childFields, item.meta?.interface)
862
+ // : generateRelationalField(key, childFields, item.meta?.interface)
863
+ // : [],
775
864
  type: definedType.type,
776
865
  array_type: definedType.array_type,
777
866
  default_value: item?.schema_definition?.default,
@@ -792,7 +881,9 @@ const buildNestedStructure = ({
792
881
 
793
882
  const removeEmptyChildren = (nodes) => {
794
883
  return nodes.map(({ children, ...node }) =>
795
- children && children?.length ? { ...node, children: removeEmptyChildren(children) } : node
884
+ children && children?.length
885
+ ? { ...node, children: removeEmptyChildren(children) }
886
+ : node
796
887
  );
797
888
  };
798
889
 
@@ -801,7 +892,6 @@ const buildNestedStructure = ({
801
892
 
802
893
  const getAllKeys = (structure) => {
803
894
  const keys = new Set();
804
-
805
895
  const traverse = (nodes) => {
806
896
  nodes.forEach((node) => {
807
897
  keys.add(node.key);
@@ -825,7 +915,9 @@ const findDisallowedKeys = (formData, structure, maxLevel) => {
825
915
  const keyParts = normalizeKey(key).split(".");
826
916
  const keyLevel = keyParts.length;
827
917
  const levelParent = keyParts.slice(0, maxLevel - 1).join(".");
828
- return !validKeys.has(normalizeKey(keyLevel > maxLevel ? levelParent : key));
918
+ return !validKeys.has(
919
+ normalizeKey(keyLevel > maxLevel ? levelParent : key)
920
+ );
829
921
  });
830
922
  };
831
923
 
@@ -922,7 +1014,8 @@ const generateFieldCompareRules = (rule) => {
922
1014
  break;
923
1015
  case "greaterThanOrEqualTo":
924
1016
  modifiedRule.case = constants.rulesTypes.OPERATOR;
925
- modifiedRule.options.operator = constants.operatorTypes.GREATER_THAN_EQUAL;
1017
+ modifiedRule.options.operator =
1018
+ constants.operatorTypes.GREATER_THAN_EQUAL;
926
1019
  modifiedRule.value = [rule[rule.type].value];
927
1020
  break;
928
1021
  case "isEmpty":
@@ -1032,7 +1125,8 @@ const generateModifiedRules = (meta, custom_message) => {
1032
1125
  break;
1033
1126
  case "lessThanOrEqualTo":
1034
1127
  modifiedRule.case = constants.rulesTypes.OPERATOR;
1035
- modifiedRule.options.operator = constants.operatorTypes.LESS_THAN_EQUAL;
1128
+ modifiedRule.options.operator =
1129
+ constants.operatorTypes.LESS_THAN_EQUAL;
1036
1130
  modifiedRule.value = [rule[rule.rule].value];
1037
1131
  break;
1038
1132
  case "greaterThan":
@@ -1042,7 +1136,8 @@ const generateModifiedRules = (meta, custom_message) => {
1042
1136
  break;
1043
1137
  case "greaterThanOrEqualTo":
1044
1138
  modifiedRule.case = constants.rulesTypes.OPERATOR;
1045
- modifiedRule.options.operator = constants.operatorTypes.GREATER_THAN_EQUAL;
1139
+ modifiedRule.options.operator =
1140
+ constants.operatorTypes.GREATER_THAN_EQUAL;
1046
1141
  modifiedRule.value = [rule[rule.rule].value];
1047
1142
  break;
1048
1143
  case "isEmpty":
@@ -1111,13 +1206,21 @@ const getDefaultValues = (tree) => {
1111
1206
  const cleanKey = keyParts[keyParts.length - 1];
1112
1207
 
1113
1208
  if (type === "Object") {
1114
- setValue(defaultValues, cleanKey, children?.length ? getDefaultValues(children) : {});
1209
+ setValue(
1210
+ defaultValues,
1211
+ cleanKey,
1212
+ children?.length ? getDefaultValues(children) : {}
1213
+ );
1115
1214
  } else if (type === "Array") {
1116
1215
  if (array_type === "String" || array_type === "Number") {
1117
1216
  setValue(defaultValues, cleanKey, []);
1118
1217
  } else {
1119
1218
  // Prevent extra nesting by ensuring the array contains objects, not arrays
1120
- setValue(defaultValues, cleanKey, children?.length ? [getDefaultValues(children)] : []);
1219
+ setValue(
1220
+ defaultValues,
1221
+ cleanKey,
1222
+ children?.length ? [getDefaultValues(children)] : []
1223
+ );
1121
1224
  }
1122
1225
  } else {
1123
1226
  setValue(defaultValues, cleanKey, defaultValue);
@@ -1128,17 +1231,53 @@ const getDefaultValues = (tree) => {
1128
1231
  };
1129
1232
 
1130
1233
  const extractRelationalParents = (path) => {
1131
- const match = path?.match(/^([^.\[\]]+)\.(create|update|delete|existing)\[(\d+)\](?:\.(.*))?/);
1234
+ const match = path?.match(
1235
+ /^([^.\[\]]+)\.(create|update|delete|existing)\[(\d+)\](?:\.(.*))?/
1236
+ );
1132
1237
  if (match) {
1133
1238
  const secondParent = match[1];
1134
1239
  const firstParent =
1135
- match[0].split(".")[0] + "." + match[2] + "[" + path.match(/\[(\d+)\]/)[1] + "]"; //
1240
+ match[0].split(".")[0] +
1241
+ "." +
1242
+ match[2] +
1243
+ "[" +
1244
+ path.match(/\[(\d+)\]/)[1] +
1245
+ "]"; //
1136
1246
  const afterKey = match[3] || "";
1137
1247
  return { firstParent, secondParent, afterKey };
1138
1248
  }
1139
1249
  return null;
1140
1250
  };
1141
1251
 
1252
+ const getM2AItemParentPath = (path) => {
1253
+ const match = path.match(/^(.*)\.(create|update)\[\d+\]\.item$/);
1254
+ return match ? path.replace(".item", "") : null;
1255
+ };
1256
+
1257
+ const getSelectedNodes = ({
1258
+ node,
1259
+ skipFn= (node)=> node.meta?.interface === constants.interfaces.MANY_TO_ANY,
1260
+ conditionFn = (node) => node.meta?.required === true,
1261
+ mapFn = (node) => ({ key: node.key, label: node.display_label }),
1262
+ actionFn = (node) => {},
1263
+ }) => {
1264
+ const result = [];
1265
+
1266
+ function traverse(currentNode) {
1267
+ if (conditionFn(currentNode)) {
1268
+ actionFn(currentNode);
1269
+ result.push(mapFn(currentNode));
1270
+ }
1271
+
1272
+ if (Array.isArray(currentNode.children)) {
1273
+ currentNode.children.forEach((child) =>{ if(!skipFn(currentNode)) traverse(child,)});
1274
+ }
1275
+ }
1276
+
1277
+ traverse(node);
1278
+ return result;
1279
+ };
1280
+
1142
1281
  module.exports = {
1143
1282
  generateModifiedRules,
1144
1283
  getFieldsGroupBySchemaId,
@@ -1158,4 +1297,6 @@ module.exports = {
1158
1297
  getForeignCollectionDetails,
1159
1298
  getDefaultValues,
1160
1299
  extractRelationalParents,
1300
+ getM2AItemParentPath,
1301
+ getSelectedNodes,
1161
1302
  };
package/lib/validate.js CHANGED
@@ -9,6 +9,8 @@ const {
9
9
  isEmpty,
10
10
  getParentKey,
11
11
  extractRelationalParents,
12
+ getM2AItemParentPath,
13
+ getSelectedNodes,
12
14
  } = require("./helpers");
13
15
 
14
16
  const choices = ["radio", "checkboxes", "dropdown_multiple", "dropdown"];
@@ -69,11 +71,14 @@ const typeChecks = {
69
71
  },
70
72
 
71
73
  [constants.types.TIME]: (val) =>
72
- typeof val === "string" && /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
74
+ typeof val === "string" &&
75
+ /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
73
76
  [constants.types.STRING]: (val) => typeof val === "string",
74
- [constants.types.OBJECT]: (val) => typeof val === "object" && val !== null && !Array.isArray(val),
77
+ [constants.types.OBJECT]: (val) =>
78
+ typeof val === "object" && val !== null && !Array.isArray(val),
75
79
  [constants.types.ARRAY]: (val) => Array.isArray(val),
76
- [constants.types.OBJECT_ID]: (val) => typeof val === "string" && /^[0-9a-fA-F]{24}$/.test(val),
80
+ [constants.types.OBJECT_ID]: (val) =>
81
+ typeof val === "string" && /^[0-9a-fA-F]{24}$/.test(val),
77
82
  [constants.types.MIXED]: (val) => (val ? true : false),
78
83
  [constants.types.BUFFER]: (val) => val instanceof Buffer,
79
84
  [constants.types.ALIAS]: (val) => (val ? true : false),
@@ -99,14 +104,18 @@ const handleMinMaxValidation = (
99
104
  fieldValue?.length < ruleValue[0]
100
105
  ) {
101
106
  message = custom_message ?? error_messages.MIN_STRING;
102
- message = message?.replace(`{field}`, fieldLabel).replace(`{min}`, ruleValue[0]);
107
+ message = message
108
+ ?.replace(`{field}`, fieldLabel)
109
+ .replace(`{min}`, ruleValue[0]);
103
110
  } else if (
104
111
  field.type === constants.types.NUMBER &&
105
112
  typeChecks[constants.types.NUMBER](fieldValue) &&
106
113
  fieldValue < ruleValue[0]
107
114
  ) {
108
115
  message = custom_message ?? error_messages.MIN_NUMBER;
109
- message = message?.replace(`{field}`, fieldLabel).replace(`{min}`, ruleValue[0]);
116
+ message = message
117
+ ?.replace(`{field}`, fieldLabel)
118
+ .replace(`{min}`, ruleValue[0]);
110
119
  }
111
120
  }
112
121
 
@@ -117,14 +126,18 @@ const handleMinMaxValidation = (
117
126
  fieldValue?.length > ruleValue[0]
118
127
  ) {
119
128
  message = custom_message ?? error_messages.MAX_STRING;
120
- message = message?.replace(`{field}`, fieldLabel).replace(`{max}`, ruleValue[0]);
129
+ message = message
130
+ ?.replace(`{field}`, fieldLabel)
131
+ .replace(`{max}`, ruleValue[0]);
121
132
  } else if (
122
133
  field.type === constants.types.NUMBER &&
123
134
  typeChecks[constants.types.NUMBER](fieldValue) &&
124
135
  fieldValue > ruleValue[0]
125
136
  ) {
126
137
  message = custom_message ?? error_messages.MAX_NUMBER;
127
- message = message?.replace(`{field}`, fieldLabel).replace(`{max}`, ruleValue[0]);
138
+ message = message
139
+ ?.replace(`{field}`, fieldLabel)
140
+ .replace(`{max}`, ruleValue[0]);
128
141
  }
129
142
  }
130
143
 
@@ -138,7 +151,10 @@ const handleMinMaxValidation = (
138
151
  };
139
152
 
140
153
  const isEmptyRelational = ({ api_version, value, interface }) => {
141
- if (api_version === constants.API_VERSION.V1 && interface !== constants.interfaces.TRANSLATIONS) {
154
+ if (
155
+ api_version === constants.API_VERSION.V1 &&
156
+ interface !== constants.interfaces.TRANSLATIONS
157
+ ) {
142
158
  if (interface === constants.interfaces.MANY_TO_ANY) {
143
159
  return (
144
160
  value &&
@@ -157,7 +173,11 @@ const isEmptyRelational = ({ api_version, value, interface }) => {
157
173
  return value?.length > 0;
158
174
  }
159
175
  } else {
160
- return value?.create?.length > 0 || value?.existing?.length > 0 || value?.update?.length > 0;
176
+ return (
177
+ value?.create?.length > 0 ||
178
+ value?.existing?.length > 0 ||
179
+ value?.update?.length > 0
180
+ );
161
181
  }
162
182
  return false;
163
183
  };
@@ -172,11 +192,13 @@ const validateMetaRules = (
172
192
  onlyFormFields,
173
193
  apiVersion,
174
194
  fieldOptions,
175
- formData
195
+ formData,
196
+ language_codes = []
176
197
  ) => {
177
198
  const fieldValue = providedValue;
199
+
178
200
  const { required = false, nullable = false, options } = field?.meta ?? {};
179
- const isRelational = [
201
+ const relational_interfaces = [
180
202
  constants.interfaces.FILES,
181
203
  constants.interfaces.FILE,
182
204
  constants.interfaces.FILE_IMAGE,
@@ -185,13 +207,17 @@ const validateMetaRules = (
185
207
  constants.interfaces.MANY_TO_ONE,
186
208
  constants.interfaces.MANY_TO_ANY,
187
209
  constants.interfaces.TRANSLATIONS,
188
- ].includes(field?.meta?.interface);
210
+ ];
211
+ const isRelational = relational_interfaces.includes(field?.meta?.interface);
189
212
 
190
213
  if (
191
214
  choices.includes(field?.meta?.interface) &&
192
215
  (!options?.choices || !options?.choices?.length > 0)
193
216
  ) {
194
- const message = error_messages.ADD_CHOICE.replace(`{field}`, formatLabel(field.display_label));
217
+ const message = error_messages.ADD_CHOICE.replace(
218
+ `{field}`,
219
+ formatLabel(field.display_label)
220
+ );
195
221
  addError(
196
222
  currentPath,
197
223
  {
@@ -214,7 +240,10 @@ const validateMetaRules = (
214
240
  : true;
215
241
 
216
242
  if ((required && isEmpty(fieldValue)) || !isValidRelational) {
217
- const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(field.display_label));
243
+ const message = error_messages.REQUIRED.replace(
244
+ `{field}`,
245
+ formatLabel(field.display_label)
246
+ );
218
247
  addError(
219
248
  currentPath,
220
249
  {
@@ -244,15 +273,79 @@ const validateMetaRules = (
244
273
  );
245
274
  }
246
275
 
276
+ if (
277
+ !fieldValue &&
278
+ !field?.meta?.hidden &&
279
+ ![
280
+ constants.interfaces.FILES,
281
+ constants.interfaces.FILE,
282
+ constants.interfaces.FILE_IMAGE,
283
+ constants.interfaces.MANY_TO_MANY,
284
+ constants.interfaces.ONE_TO_MANY,
285
+ constants.interfaces.MANY_TO_ONE,
286
+ constants.interfaces.MANY_TO_ANY,
287
+ ].includes(field?.meta?.interface)
288
+ ) {
289
+ const isTranslationChild =
290
+ field?.meta?.interface === constants.interfaces.TRANSLATIONS;
291
+
292
+ getSelectedNodes({
293
+ node: field,
294
+ skipFn: (node) =>
295
+ node.meta?.interface === constants.interfaces.MANY_TO_ANY,
296
+ conditionFn: (node) => node.meta?.required === true,
297
+ mapFn: (node) => ({ key: node.key, label: node.display_label }),
298
+ actionFn: (node) => {
299
+ let fPath = node.key;
300
+
301
+ if (fPath.includes("update.")) return;
302
+
303
+ const label = formatLabel(node.display_label);
304
+ const message = error_messages.REQUIRED.replace("{field}", label);
305
+
306
+ const buildError = (path, translated) => {
307
+ let obj = {
308
+ label,
309
+ fieldPath: path,
310
+ description: "",
311
+ message,
312
+ };
313
+ if (translated) {
314
+ obj.translation_path = translated;
315
+ }
316
+ addError(path, obj, node);
317
+ };
318
+
319
+ const isMultiLang =
320
+ isTranslationChild &&
321
+ Array.isArray(language_codes) &&
322
+ language_codes.length > 1;
323
+
324
+ if (isMultiLang) {
325
+ language_codes.forEach((lang, index) => {
326
+ const langPath = fPath.replace("create.", `create[${index}].`);
327
+ const transformedPath = fPath.replace("create.", `${lang}.`);
328
+ buildError(langPath, transformedPath);
329
+ });
330
+ } else {
331
+ const singlePath = fPath.replace(
332
+ "create.",
333
+ isTranslationChild ? "create[lang_code]." : "create[0]."
334
+ );
335
+ buildError(singlePath);
336
+ }
337
+ },
338
+ });
339
+ }
340
+
247
341
  const validType =
248
342
  field?.alternateType?.length > 0
249
- ? [field.type, ...field?.alternateType].some((type) => typeChecks[type](fieldValue))
250
- : typeChecks[field.type](fieldValue,{ key: currentPath, updateValue });
343
+ ? [field.type, ...field?.alternateType].some((type) =>
344
+ typeChecks[type](fieldValue)
345
+ )
346
+ : typeChecks[field.type](fieldValue, { key: currentPath, updateValue });
251
347
 
252
- if (
253
- !isEmpty(fieldValue) &&
254
- !validType
255
- ) {
348
+ if (!isEmpty(fieldValue) && !validType) {
256
349
  const message = error_messages.INVALID_TYPE.replace(
257
350
  `{field}`,
258
351
  formatLabel(field.display_label)
@@ -281,9 +374,9 @@ const handleRegexValidation = (
281
374
  ) => {
282
375
  let message = "";
283
376
  const fieldLabel = formatLabel(field.display_label);
284
- const flags = `${rule.options.case_sensitive ? "" : "i"}${rule.options.multiline ? "m" : ""}${
285
- rule.options.global ? "g" : ""
286
- }`;
377
+ const flags = `${rule.options.case_sensitive ? "" : "i"}${
378
+ rule.options.multiline ? "m" : ""
379
+ }${rule.options.global ? "g" : ""}`;
287
380
  const regex = new RegExp(rule.value[0], flags);
288
381
 
289
382
  const isValid = (() => {
@@ -293,22 +386,32 @@ const handleRegexValidation = (
293
386
  return regex.test(fieldValue);
294
387
  case constants.regexTypes.START_WITH:
295
388
  message = custom_message ?? error_messages.REGEX_START_WITH;
296
- return fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
389
+ return fieldValue?.startsWith(
390
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
391
+ );
297
392
  case constants.regexTypes.ENDS_WITH:
298
393
  message = custom_message ?? error_messages.REGEX_ENDS_WITH;
299
- return fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
394
+ return fieldValue?.endsWith(
395
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
396
+ );
300
397
  case constants.regexTypes.CONTAINS:
301
398
  message = custom_message ?? error_messages.REGEX_CONTAINS;
302
399
  return regex.test(fieldValue);
303
400
  case constants.regexTypes.EXACT:
304
401
  message = custom_message ?? error_messages.REGEX_EXACT;
305
- return fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "");
402
+ return (
403
+ fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "")
404
+ );
306
405
  case constants.regexTypes.NOT_START_WITH:
307
406
  message = custom_message ?? error_messages.REGEX_NOT_START_WITH;
308
- return !fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
407
+ return !fieldValue?.startsWith(
408
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
409
+ );
309
410
  case constants.regexTypes.NOT_ENDS_WITH:
310
411
  message = custom_message ?? error_messages.REGEX_NOT_ENDS_WITH;
311
- return !fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
412
+ return !fieldValue?.endsWith(
413
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
414
+ );
312
415
  case constants.regexTypes.NOT_CONTAINS:
313
416
  message = custom_message ?? error_messages.REGEX_NOT_CONTAINS;
314
417
  return !regex.test(fieldValue);
@@ -318,7 +421,9 @@ const handleRegexValidation = (
318
421
  })();
319
422
 
320
423
  if (!isValid) {
321
- message = message?.replace(`{field}`, fieldLabel)?.replace(`{value}`, rule.value[0]);
424
+ message = message
425
+ ?.replace(`{field}`, fieldLabel)
426
+ ?.replace(`{value}`, rule.value[0]);
322
427
  addError(
323
428
  currentPath,
324
429
  { label: fieldLabel, fieldPath: currentPath, description: "", message },
@@ -358,10 +463,9 @@ const validateOperatorRule = (
358
463
  const date = new Date(value);
359
464
  date.setHours(0, 0, 0, 0);
360
465
  if (forMessage) {
361
- return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(
362
- 2,
363
- "0"
364
- )}-${String(date.getUTCDate()).padStart(2, "0")}`;
466
+ return `${date.getUTCFullYear()}-${String(
467
+ date.getUTCMonth() + 1
468
+ ).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
365
469
  }
366
470
  return Math.floor(date.getTime() / 1000);
367
471
  }
@@ -370,13 +474,13 @@ const validateOperatorRule = (
370
474
 
371
475
  if (forMessage) {
372
476
  return (
373
- `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(
374
- date.getUTCDate()
375
- ).padStart(2, "0")} ` +
376
- `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(
477
+ `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(
377
478
  2,
378
479
  "0"
379
- )}:${String(date.getUTCSeconds()).padStart(2, "0")}`
480
+ )}-${String(date.getUTCDate()).padStart(2, "0")} ` +
481
+ `${String(date.getUTCHours()).padStart(2, "0")}:${String(
482
+ date.getUTCMinutes()
483
+ ).padStart(2, "0")}:${String(date.getUTCSeconds()).padStart(2, "0")}`
380
484
  );
381
485
  }
382
486
 
@@ -400,7 +504,9 @@ const validateOperatorRule = (
400
504
 
401
505
  const fieldValueParsed = getComparableValue(fieldValue);
402
506
 
403
- const messageValue = Array.isArray(rule.value) ? rule.value.join(", ") : String(rule.value);
507
+ const messageValue = Array.isArray(rule.value)
508
+ ? rule.value.join(", ")
509
+ : String(rule.value);
404
510
  let message = "";
405
511
 
406
512
  switch (rule.options.operator) {
@@ -481,7 +587,9 @@ const validateOperatorRule = (
481
587
  case constants.operatorTypes.EXISTS:
482
588
  message = custom_message ?? error_messages.EXISTS;
483
589
  message = message?.replace(`{field}`, formatLabel(field.display_label));
484
- valid = rule.value[0] ? fieldValue !== undefined : fieldValue === undefined;
590
+ valid = rule.value[0]
591
+ ? fieldValue !== undefined
592
+ : fieldValue === undefined;
485
593
  break;
486
594
  case constants.operatorTypes.TYPE:
487
595
  message = custom_message ?? error_messages.TYPE;
@@ -545,18 +653,33 @@ const generateErrorMessage = (
545
653
  return message;
546
654
  };
547
655
 
548
- const handleRule = (rule, field, value, addError, currentPath, formData, error_messages) => {
656
+ const handleRule = (
657
+ rule,
658
+ field,
659
+ value,
660
+ addError,
661
+ currentPath,
662
+ formData,
663
+ error_messages
664
+ ) => {
549
665
  const ruleValue = rule?.value;
550
- let messageValue = Array.isArray(ruleValue) ? ruleValue.join(", ") : String(ruleValue);
666
+ let messageValue = Array.isArray(ruleValue)
667
+ ? ruleValue.join(", ")
668
+ : String(ruleValue);
551
669
  const fieldLabel = formatLabel(field.display_label);
552
670
  const custom_message = rule?.custom_message;
553
671
 
554
- if (rule.case === constants.rulesTypes.ONE_OF && choices.includes(field?.meta?.interface)) {
672
+ if (
673
+ rule.case === constants.rulesTypes.ONE_OF &&
674
+ choices.includes(field?.meta?.interface)
675
+ ) {
555
676
  const fieldChoices = field?.meta?.options?.choices || [];
556
677
  const ruleValues = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
557
678
 
558
679
  const labelValuePairs = ruleValues.map((val) => {
559
- const matched = fieldChoices.find((item) => String(item.value) === String(val));
680
+ const matched = fieldChoices.find(
681
+ (item) => String(item.value) === String(val)
682
+ );
560
683
  return matched ? `${matched.label} (${matched.value})` : val;
561
684
  });
562
685
 
@@ -586,7 +709,8 @@ const handleRule = (rule, field, value, addError, currentPath, formData, error_m
586
709
  if (value) addValidationError("EMPTY", {}, custom_message);
587
710
  break;
588
711
  case constants.rulesTypes.NOT_EMPTY:
589
- if (!value || value.length === 0) addValidationError("NOT_EMPTY", {}, custom_message);
712
+ if (!value || value.length === 0)
713
+ addValidationError("NOT_EMPTY", {}, custom_message);
590
714
  break;
591
715
  case constants.rulesTypes.ONE_OF:
592
716
  if (!ruleValue?.includes(value))
@@ -594,10 +718,15 @@ const handleRule = (rule, field, value, addError, currentPath, formData, error_m
594
718
  break;
595
719
  case constants.rulesTypes.NOT_ONE_OF:
596
720
  if (ruleValue?.includes(value))
597
- addValidationError("NOT_ONE_OF", { value: messageValue }, custom_message);
721
+ addValidationError(
722
+ "NOT_ONE_OF",
723
+ { value: messageValue },
724
+ custom_message
725
+ );
598
726
  break;
599
727
  case constants.rulesTypes.NOT_ALLOWED:
600
- if (ruleValue?.includes(value)) addValidationError("NOT_ALLOWED", {}, custom_message);
728
+ if (ruleValue?.includes(value))
729
+ addValidationError("NOT_ALLOWED", {}, custom_message);
601
730
  break;
602
731
  case constants.rulesTypes.MIN:
603
732
  case constants.rulesTypes.MAX:
@@ -650,11 +779,15 @@ const validateField = (
650
779
  error_messages,
651
780
  onlyFormFields,
652
781
  apiVersion,
653
- fieldOptions
782
+ fieldOptions,
783
+ language_codes
654
784
  ) => {
655
785
  if (onlyFormFields == true && (value === undefined || value === null)) return;
656
786
 
657
- const currentPath = fieldPath ? `${fieldPath}.${field.key.split(".").pop()}` : field.key;
787
+ const { is_m2a_item } = field?.meta;
788
+ const currentPath = fieldPath
789
+ ? `${fieldPath}.${field.key.split(".").pop()}`
790
+ : field.key;
658
791
  const fieldLabel = formatLabel(field.display_label);
659
792
 
660
793
  validateMetaRules(
@@ -667,7 +800,8 @@ const validateField = (
667
800
  onlyFormFields,
668
801
  apiVersion,
669
802
  fieldOptions,
670
- formData
803
+ formData,
804
+ language_codes
671
805
  );
672
806
 
673
807
  if (
@@ -676,29 +810,12 @@ const validateField = (
676
810
  value &&
677
811
  typeChecks[constants.types.OBJECT](value)
678
812
  ) {
679
- const isManyToAnyItem =
680
- field.display_label === "item" && field?.meta?.interface === constants.interfaces.MANY_TO_ANY;
681
-
682
- const defaultKeys = new Set([
683
- "_id",
684
- "created_at",
685
- "updated_at",
686
- "__v",
687
- "created_by",
688
- "updated_by",
689
- "nox_created_by",
690
- "nox_updated_by",
691
- "nox_created_at",
692
- "nox_updated_at",
693
- ]);
694
-
695
813
  let itemSchemaId = null;
696
814
 
697
- if (isManyToAnyItem) {
698
- const itemKey = Object.keys(value).find((key) => !defaultKeys.has(key));
699
- if (itemKey) {
700
- const childField = field.children.find((child) => child.display_label === itemKey);
701
- itemSchemaId = childField?.schema_id ?? null;
815
+ if (is_m2a_item) {
816
+ const fieldPath = getM2AItemParentPath(currentPath);
817
+ if (fieldPath) {
818
+ itemSchemaId = getValue(formData, fieldPath)?.collection_id;
702
819
  }
703
820
  }
704
821
 
@@ -717,7 +834,8 @@ const validateField = (
717
834
  error_messages,
718
835
  onlyFormFields,
719
836
  apiVersion,
720
- fieldOptions
837
+ fieldOptions,
838
+ language_codes
721
839
  )
722
840
  );
723
841
  } else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
@@ -727,7 +845,14 @@ const validateField = (
727
845
  const itemPath = `${currentPath}[${index}]`;
728
846
 
729
847
  if (choices.includes(field?.meta?.interface) && !isEmpty(item)) {
730
- applyValidations(field, item, addError, itemPath, formData, error_messages);
848
+ applyValidations(
849
+ field,
850
+ item,
851
+ addError,
852
+ itemPath,
853
+ formData,
854
+ error_messages
855
+ );
731
856
  }
732
857
 
733
858
  if (!typeChecks[itemType](item)) {
@@ -760,13 +885,23 @@ const validateField = (
760
885
  error_messages,
761
886
  onlyFormFields,
762
887
  apiVersion,
763
- fieldOptions
888
+ fieldOptions,
889
+ language_codes
764
890
  )
765
891
  );
766
892
  });
767
893
  }
768
894
  } else {
769
- if (!applyValidations(field, value, addError, currentPath, formData, error_messages)) {
895
+ if (
896
+ !applyValidations(
897
+ field,
898
+ value,
899
+ addError,
900
+ currentPath,
901
+ formData,
902
+ error_messages
903
+ )
904
+ ) {
770
905
  addError(
771
906
  currentPath,
772
907
  {
@@ -781,11 +916,32 @@ const validateField = (
781
916
  }
782
917
  };
783
918
 
784
- const applyValidations = (field, value, addError, currentPath, formData, error_messages) => {
785
- if (!field.validations || value === null || value === undefined || value === "") return true;
919
+ const applyValidations = (
920
+ field,
921
+ value,
922
+ addError,
923
+ currentPath,
924
+ formData,
925
+ error_messages
926
+ ) => {
927
+ if (
928
+ !field.validations ||
929
+ value === null ||
930
+ value === undefined ||
931
+ value === ""
932
+ )
933
+ return true;
786
934
  return !field.validations.some(
787
935
  (rule) =>
788
- handleRule(rule, field, value, addError, currentPath, formData, error_messages) === false
936
+ handleRule(
937
+ rule,
938
+ field,
939
+ value,
940
+ addError,
941
+ currentPath,
942
+ formData,
943
+ error_messages
944
+ ) === false
789
945
  );
790
946
  };
791
947
 
@@ -808,6 +964,10 @@ const schema = {
808
964
  language: { type: constants.types.STRING, array_type: null },
809
965
  maxLevel: { type: constants.types.NUMBER, array_type: null },
810
966
  onlyFormFields: { type: constants.types.BOOLEAN, array_type: null },
967
+ language_codes: {
968
+ type: constants.types.ARRAY,
969
+ array_type: constants.types.OBJECT_ID,
970
+ },
811
971
  };
812
972
 
813
973
  const validate = (data) => {
@@ -833,7 +993,8 @@ const validate = (data) => {
833
993
  } = data;
834
994
 
835
995
  const error_messages =
836
- constants.LOCALE_MESSAGES[language] ?? constants.LOCALE_MESSAGES[constants.LANGUAGES.en];
996
+ constants.LOCALE_MESSAGES[language] ??
997
+ constants.LOCALE_MESSAGES[constants.LANGUAGES.en];
837
998
 
838
999
  let result = { status: true, errors: {}, data: structuredClone(formData) };
839
1000
 
@@ -850,12 +1011,18 @@ const validate = (data) => {
850
1011
  const secondParentField = fields.find((f) => f.path === secondParent);
851
1012
  if (
852
1013
  secondParentField &&
853
- secondParentField?.meta?.interface === constants.interfaces.TRANSLATIONS
1014
+ secondParentField?.meta?.interface ===
1015
+ constants.interfaces.TRANSLATIONS
854
1016
  ) {
855
1017
  const languageKey = secondParentField?.meta?.options?.language_field;
856
1018
  const firstParentValue = getValue(formData, firstParent);
857
- if (firstParentValue && typeChecks[constants.types.OBJECT](firstParentValue)) {
858
- const codeKey = Object.keys(firstParentValue).find((key) => key.includes(languageKey));
1019
+ if (
1020
+ firstParentValue &&
1021
+ typeChecks[constants.types.OBJECT](firstParentValue)
1022
+ ) {
1023
+ const codeKey = Object.keys(firstParentValue).find((key) =>
1024
+ key.includes(languageKey)
1025
+ );
859
1026
  const codeValue = codeKey ? firstParentValue[codeKey] : null;
860
1027
  if (codeValue) {
861
1028
  const translation_key = fieldPath.replace(
@@ -881,7 +1048,10 @@ const validate = (data) => {
881
1048
  // Validate Data
882
1049
  const defaultField = { meta: { hidden: false } };
883
1050
  if (!data) {
884
- const message = error_messages.REQUIRED.replace(`{field}`, formatLabel("data"));
1051
+ const message = error_messages.REQUIRED.replace(
1052
+ `{field}`,
1053
+ formatLabel("data")
1054
+ );
885
1055
  addError(
886
1056
  "data",
887
1057
  {
@@ -897,10 +1067,10 @@ const validate = (data) => {
897
1067
 
898
1068
  // validate data type
899
1069
  if (!typeChecks[constants.types.OBJECT](data)) {
900
- const message = error_messages.INVALID_TYPE.replace(`{field}`, formatLabel("data")).replace(
901
- `{type}`,
902
- constants.types.OBJECT
903
- );
1070
+ const message = error_messages.INVALID_TYPE.replace(
1071
+ `{field}`,
1072
+ formatLabel("data")
1073
+ ).replace(`{type}`, constants.types.OBJECT);
904
1074
  addError(
905
1075
  "data",
906
1076
  {
@@ -920,7 +1090,10 @@ const validate = (data) => {
920
1090
  const fieldValue = data[key];
921
1091
  // Skip empty values
922
1092
  if (fieldValue == null || fieldValue == undefined) {
923
- const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(key));
1093
+ const message = error_messages.REQUIRED.replace(
1094
+ `{field}`,
1095
+ formatLabel(key)
1096
+ );
924
1097
  addError(
925
1098
  key,
926
1099
  {
@@ -936,10 +1109,10 @@ const validate = (data) => {
936
1109
 
937
1110
  // Validate field type
938
1111
  if (!typeChecks[expectedType] || !typeChecks[expectedType](fieldValue)) {
939
- const message = error_messages.INVALID_TYPE.replace(`{field}`, key).replace(
940
- `{type}`,
941
- expectedType
942
- );
1112
+ const message = error_messages.INVALID_TYPE.replace(
1113
+ `{field}`,
1114
+ key
1115
+ ).replace(`{type}`, expectedType);
943
1116
  addError(
944
1117
  key,
945
1118
  {
@@ -959,7 +1132,11 @@ const validate = (data) => {
959
1132
  // Determine the expected type of array items
960
1133
  const arrayItemType = schema[key].array_type; // Define item types like "relations[]": "object"
961
1134
 
962
- if (arrayItemType && typeChecks[arrayItemType] && !typeChecks[arrayItemType](item)) {
1135
+ if (
1136
+ arrayItemType &&
1137
+ typeChecks[arrayItemType] &&
1138
+ !typeChecks[arrayItemType](item)
1139
+ ) {
963
1140
  const message = error_messages.INVALID_TYPE.replace(
964
1141
  `{field}`,
965
1142
  `${key}[${index}]`
@@ -981,10 +1158,10 @@ const validate = (data) => {
981
1158
 
982
1159
  // Validate API Version
983
1160
  if (!constants.API_VERSIONS.includes(apiVersion)) {
984
- const message = error_messages.IN.replace(`{field}`, formatLabel("apiVersion")).replace(
985
- `{value}`,
986
- constants.API_VERSIONS.join(", ")
987
- );
1161
+ const message = error_messages.IN.replace(
1162
+ `{field}`,
1163
+ formatLabel("apiVersion")
1164
+ ).replace(`{value}`, constants.API_VERSIONS.join(", "));
988
1165
  addError(
989
1166
  "apiVersion",
990
1167
  {
@@ -1008,7 +1185,9 @@ const validate = (data) => {
1008
1185
  let allFields = isSeparatedFields ? [] : fields;
1009
1186
 
1010
1187
  if (!isSeparatedFields) {
1011
- schemaFields = fields.filter((field) => field?.schema_id?.toString() === formId?.toString());
1188
+ schemaFields = fields.filter(
1189
+ (field) => field?.schema_id?.toString() === formId?.toString()
1190
+ );
1012
1191
  }
1013
1192
 
1014
1193
  let currentDepthMap = new Map();
@@ -1059,7 +1238,8 @@ const validate = (data) => {
1059
1238
  error_messages,
1060
1239
  onlyFormFields,
1061
1240
  apiVersion,
1062
- fieldOptions
1241
+ fieldOptions,
1242
+ data.language_codes
1063
1243
  );
1064
1244
  });
1065
1245
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nox-validation",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "validate dynamic schema",
5
5
  "main": "index.js",
6
6
  "scripts": {