nox-validation 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,78 @@ const validateMetaRules = (
244
273
  );
245
274
  }
246
275
 
276
+ if (
277
+ !fieldValue &&
278
+ ![
279
+ constants.interfaces.FILES,
280
+ constants.interfaces.FILE,
281
+ constants.interfaces.FILE_IMAGE,
282
+ constants.interfaces.MANY_TO_MANY,
283
+ constants.interfaces.ONE_TO_MANY,
284
+ constants.interfaces.MANY_TO_ONE,
285
+ constants.interfaces.MANY_TO_ANY,
286
+ ].includes(field?.meta?.interface)
287
+ ) {
288
+ const isTranslationChild =
289
+ field?.meta?.interface === constants.interfaces.TRANSLATIONS;
290
+
291
+ getSelectedNodes({
292
+ node: field,
293
+ skipFn: (node) =>
294
+ node.meta?.interface === constants.interfaces.MANY_TO_ANY,
295
+ conditionFn: (node) => node.meta?.required === true,
296
+ mapFn: (node) => ({ key: node.key, label: node.display_label }),
297
+ actionFn: (node) => {
298
+ let fPath = node.key;
299
+
300
+ if (fPath.includes("update.")) return;
301
+
302
+ const label = formatLabel(node.display_label);
303
+ const message = error_messages.REQUIRED.replace("{field}", label);
304
+
305
+ const buildError = (path, translated) => {
306
+ let obj = {
307
+ label,
308
+ fieldPath: path,
309
+ description: "",
310
+ message,
311
+ };
312
+ if (translated) {
313
+ obj.translation_path = translated;
314
+ }
315
+ addError(path, obj, node);
316
+ };
317
+
318
+ const isMultiLang =
319
+ isTranslationChild &&
320
+ Array.isArray(language_codes) &&
321
+ language_codes.length > 1;
322
+
323
+ if (isMultiLang) {
324
+ language_codes.forEach((lang, index) => {
325
+ const langPath = fPath.replace("create.", `create[${index}].`);
326
+ const transformedPath = fPath.replace("create.", `${lang}.`);
327
+ buildError(langPath, transformedPath);
328
+ });
329
+ } else {
330
+ const singlePath = fPath.replace(
331
+ "create.",
332
+ isTranslationChild ? "create[lang_code]." : "create[0]."
333
+ );
334
+ buildError(singlePath);
335
+ }
336
+ },
337
+ });
338
+ }
339
+
247
340
  const validType =
248
341
  field?.alternateType?.length > 0
249
- ? [field.type, ...field?.alternateType].some((type) => typeChecks[type](fieldValue))
250
- : typeChecks[field.type](fieldValue,{ key: currentPath, updateValue });
342
+ ? [field.type, ...field?.alternateType].some((type) =>
343
+ typeChecks[type](fieldValue)
344
+ )
345
+ : typeChecks[field.type](fieldValue, { key: currentPath, updateValue });
251
346
 
252
- if (
253
- !isEmpty(fieldValue) &&
254
- !validType
255
- ) {
347
+ if (!isEmpty(fieldValue) && !validType) {
256
348
  const message = error_messages.INVALID_TYPE.replace(
257
349
  `{field}`,
258
350
  formatLabel(field.display_label)
@@ -281,9 +373,9 @@ const handleRegexValidation = (
281
373
  ) => {
282
374
  let message = "";
283
375
  const fieldLabel = formatLabel(field.display_label);
284
- const flags = `${rule.options.case_sensitive ? "" : "i"}${rule.options.multiline ? "m" : ""}${
285
- rule.options.global ? "g" : ""
286
- }`;
376
+ const flags = `${rule.options.case_sensitive ? "" : "i"}${
377
+ rule.options.multiline ? "m" : ""
378
+ }${rule.options.global ? "g" : ""}`;
287
379
  const regex = new RegExp(rule.value[0], flags);
288
380
 
289
381
  const isValid = (() => {
@@ -293,22 +385,32 @@ const handleRegexValidation = (
293
385
  return regex.test(fieldValue);
294
386
  case constants.regexTypes.START_WITH:
295
387
  message = custom_message ?? error_messages.REGEX_START_WITH;
296
- return fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
388
+ return fieldValue?.startsWith(
389
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
390
+ );
297
391
  case constants.regexTypes.ENDS_WITH:
298
392
  message = custom_message ?? error_messages.REGEX_ENDS_WITH;
299
- return fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
393
+ return fieldValue?.endsWith(
394
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
395
+ );
300
396
  case constants.regexTypes.CONTAINS:
301
397
  message = custom_message ?? error_messages.REGEX_CONTAINS;
302
398
  return regex.test(fieldValue);
303
399
  case constants.regexTypes.EXACT:
304
400
  message = custom_message ?? error_messages.REGEX_EXACT;
305
- return fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "");
401
+ return (
402
+ fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "")
403
+ );
306
404
  case constants.regexTypes.NOT_START_WITH:
307
405
  message = custom_message ?? error_messages.REGEX_NOT_START_WITH;
308
- return !fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
406
+ return !fieldValue?.startsWith(
407
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
408
+ );
309
409
  case constants.regexTypes.NOT_ENDS_WITH:
310
410
  message = custom_message ?? error_messages.REGEX_NOT_ENDS_WITH;
311
- return !fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
411
+ return !fieldValue?.endsWith(
412
+ rule.value[0].replace(/^\//, "").replace(/\/$/, "")
413
+ );
312
414
  case constants.regexTypes.NOT_CONTAINS:
313
415
  message = custom_message ?? error_messages.REGEX_NOT_CONTAINS;
314
416
  return !regex.test(fieldValue);
@@ -318,7 +420,9 @@ const handleRegexValidation = (
318
420
  })();
319
421
 
320
422
  if (!isValid) {
321
- message = message?.replace(`{field}`, fieldLabel)?.replace(`{value}`, rule.value[0]);
423
+ message = message
424
+ ?.replace(`{field}`, fieldLabel)
425
+ ?.replace(`{value}`, rule.value[0]);
322
426
  addError(
323
427
  currentPath,
324
428
  { label: fieldLabel, fieldPath: currentPath, description: "", message },
@@ -358,10 +462,9 @@ const validateOperatorRule = (
358
462
  const date = new Date(value);
359
463
  date.setHours(0, 0, 0, 0);
360
464
  if (forMessage) {
361
- return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(
362
- 2,
363
- "0"
364
- )}-${String(date.getUTCDate()).padStart(2, "0")}`;
465
+ return `${date.getUTCFullYear()}-${String(
466
+ date.getUTCMonth() + 1
467
+ ).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
365
468
  }
366
469
  return Math.floor(date.getTime() / 1000);
367
470
  }
@@ -370,13 +473,13 @@ const validateOperatorRule = (
370
473
 
371
474
  if (forMessage) {
372
475
  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(
476
+ `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(
377
477
  2,
378
478
  "0"
379
- )}:${String(date.getUTCSeconds()).padStart(2, "0")}`
479
+ )}-${String(date.getUTCDate()).padStart(2, "0")} ` +
480
+ `${String(date.getUTCHours()).padStart(2, "0")}:${String(
481
+ date.getUTCMinutes()
482
+ ).padStart(2, "0")}:${String(date.getUTCSeconds()).padStart(2, "0")}`
380
483
  );
381
484
  }
382
485
 
@@ -400,7 +503,9 @@ const validateOperatorRule = (
400
503
 
401
504
  const fieldValueParsed = getComparableValue(fieldValue);
402
505
 
403
- const messageValue = Array.isArray(rule.value) ? rule.value.join(", ") : String(rule.value);
506
+ const messageValue = Array.isArray(rule.value)
507
+ ? rule.value.join(", ")
508
+ : String(rule.value);
404
509
  let message = "";
405
510
 
406
511
  switch (rule.options.operator) {
@@ -481,7 +586,9 @@ const validateOperatorRule = (
481
586
  case constants.operatorTypes.EXISTS:
482
587
  message = custom_message ?? error_messages.EXISTS;
483
588
  message = message?.replace(`{field}`, formatLabel(field.display_label));
484
- valid = rule.value[0] ? fieldValue !== undefined : fieldValue === undefined;
589
+ valid = rule.value[0]
590
+ ? fieldValue !== undefined
591
+ : fieldValue === undefined;
485
592
  break;
486
593
  case constants.operatorTypes.TYPE:
487
594
  message = custom_message ?? error_messages.TYPE;
@@ -545,18 +652,33 @@ const generateErrorMessage = (
545
652
  return message;
546
653
  };
547
654
 
548
- const handleRule = (rule, field, value, addError, currentPath, formData, error_messages) => {
655
+ const handleRule = (
656
+ rule,
657
+ field,
658
+ value,
659
+ addError,
660
+ currentPath,
661
+ formData,
662
+ error_messages
663
+ ) => {
549
664
  const ruleValue = rule?.value;
550
- let messageValue = Array.isArray(ruleValue) ? ruleValue.join(", ") : String(ruleValue);
665
+ let messageValue = Array.isArray(ruleValue)
666
+ ? ruleValue.join(", ")
667
+ : String(ruleValue);
551
668
  const fieldLabel = formatLabel(field.display_label);
552
669
  const custom_message = rule?.custom_message;
553
670
 
554
- if (rule.case === constants.rulesTypes.ONE_OF && choices.includes(field?.meta?.interface)) {
671
+ if (
672
+ rule.case === constants.rulesTypes.ONE_OF &&
673
+ choices.includes(field?.meta?.interface)
674
+ ) {
555
675
  const fieldChoices = field?.meta?.options?.choices || [];
556
676
  const ruleValues = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
557
677
 
558
678
  const labelValuePairs = ruleValues.map((val) => {
559
- const matched = fieldChoices.find((item) => String(item.value) === String(val));
679
+ const matched = fieldChoices.find(
680
+ (item) => String(item.value) === String(val)
681
+ );
560
682
  return matched ? `${matched.label} (${matched.value})` : val;
561
683
  });
562
684
 
@@ -586,7 +708,8 @@ const handleRule = (rule, field, value, addError, currentPath, formData, error_m
586
708
  if (value) addValidationError("EMPTY", {}, custom_message);
587
709
  break;
588
710
  case constants.rulesTypes.NOT_EMPTY:
589
- if (!value || value.length === 0) addValidationError("NOT_EMPTY", {}, custom_message);
711
+ if (!value || value.length === 0)
712
+ addValidationError("NOT_EMPTY", {}, custom_message);
590
713
  break;
591
714
  case constants.rulesTypes.ONE_OF:
592
715
  if (!ruleValue?.includes(value))
@@ -594,10 +717,15 @@ const handleRule = (rule, field, value, addError, currentPath, formData, error_m
594
717
  break;
595
718
  case constants.rulesTypes.NOT_ONE_OF:
596
719
  if (ruleValue?.includes(value))
597
- addValidationError("NOT_ONE_OF", { value: messageValue }, custom_message);
720
+ addValidationError(
721
+ "NOT_ONE_OF",
722
+ { value: messageValue },
723
+ custom_message
724
+ );
598
725
  break;
599
726
  case constants.rulesTypes.NOT_ALLOWED:
600
- if (ruleValue?.includes(value)) addValidationError("NOT_ALLOWED", {}, custom_message);
727
+ if (ruleValue?.includes(value))
728
+ addValidationError("NOT_ALLOWED", {}, custom_message);
601
729
  break;
602
730
  case constants.rulesTypes.MIN:
603
731
  case constants.rulesTypes.MAX:
@@ -650,11 +778,15 @@ const validateField = (
650
778
  error_messages,
651
779
  onlyFormFields,
652
780
  apiVersion,
653
- fieldOptions
781
+ fieldOptions,
782
+ language_codes
654
783
  ) => {
655
784
  if (onlyFormFields == true && (value === undefined || value === null)) return;
656
785
 
657
- const currentPath = fieldPath ? `${fieldPath}.${field.key.split(".").pop()}` : field.key;
786
+ const { is_m2a_item } = field?.meta;
787
+ const currentPath = fieldPath
788
+ ? `${fieldPath}.${field.key.split(".").pop()}`
789
+ : field.key;
658
790
  const fieldLabel = formatLabel(field.display_label);
659
791
 
660
792
  validateMetaRules(
@@ -667,7 +799,8 @@ const validateField = (
667
799
  onlyFormFields,
668
800
  apiVersion,
669
801
  fieldOptions,
670
- formData
802
+ formData,
803
+ language_codes
671
804
  );
672
805
 
673
806
  if (
@@ -676,29 +809,12 @@ const validateField = (
676
809
  value &&
677
810
  typeChecks[constants.types.OBJECT](value)
678
811
  ) {
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
812
  let itemSchemaId = null;
696
813
 
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;
814
+ if (is_m2a_item) {
815
+ const fieldPath = getM2AItemParentPath(currentPath);
816
+ if (fieldPath) {
817
+ itemSchemaId = getValue(formData, fieldPath)?.collection_id;
702
818
  }
703
819
  }
704
820
 
@@ -717,7 +833,8 @@ const validateField = (
717
833
  error_messages,
718
834
  onlyFormFields,
719
835
  apiVersion,
720
- fieldOptions
836
+ fieldOptions,
837
+ language_codes
721
838
  )
722
839
  );
723
840
  } else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
@@ -727,7 +844,14 @@ const validateField = (
727
844
  const itemPath = `${currentPath}[${index}]`;
728
845
 
729
846
  if (choices.includes(field?.meta?.interface) && !isEmpty(item)) {
730
- applyValidations(field, item, addError, itemPath, formData, error_messages);
847
+ applyValidations(
848
+ field,
849
+ item,
850
+ addError,
851
+ itemPath,
852
+ formData,
853
+ error_messages
854
+ );
731
855
  }
732
856
 
733
857
  if (!typeChecks[itemType](item)) {
@@ -760,13 +884,23 @@ const validateField = (
760
884
  error_messages,
761
885
  onlyFormFields,
762
886
  apiVersion,
763
- fieldOptions
887
+ fieldOptions,
888
+ language_codes
764
889
  )
765
890
  );
766
891
  });
767
892
  }
768
893
  } else {
769
- if (!applyValidations(field, value, addError, currentPath, formData, error_messages)) {
894
+ if (
895
+ !applyValidations(
896
+ field,
897
+ value,
898
+ addError,
899
+ currentPath,
900
+ formData,
901
+ error_messages
902
+ )
903
+ ) {
770
904
  addError(
771
905
  currentPath,
772
906
  {
@@ -781,11 +915,32 @@ const validateField = (
781
915
  }
782
916
  };
783
917
 
784
- const applyValidations = (field, value, addError, currentPath, formData, error_messages) => {
785
- if (!field.validations || value === null || value === undefined || value === "") return true;
918
+ const applyValidations = (
919
+ field,
920
+ value,
921
+ addError,
922
+ currentPath,
923
+ formData,
924
+ error_messages
925
+ ) => {
926
+ if (
927
+ !field.validations ||
928
+ value === null ||
929
+ value === undefined ||
930
+ value === ""
931
+ )
932
+ return true;
786
933
  return !field.validations.some(
787
934
  (rule) =>
788
- handleRule(rule, field, value, addError, currentPath, formData, error_messages) === false
935
+ handleRule(
936
+ rule,
937
+ field,
938
+ value,
939
+ addError,
940
+ currentPath,
941
+ formData,
942
+ error_messages
943
+ ) === false
789
944
  );
790
945
  };
791
946
 
@@ -808,6 +963,10 @@ const schema = {
808
963
  language: { type: constants.types.STRING, array_type: null },
809
964
  maxLevel: { type: constants.types.NUMBER, array_type: null },
810
965
  onlyFormFields: { type: constants.types.BOOLEAN, array_type: null },
966
+ language_codes: {
967
+ type: constants.types.ARRAY,
968
+ array_type: constants.types.OBJECT_ID,
969
+ },
811
970
  };
812
971
 
813
972
  const validate = (data) => {
@@ -833,7 +992,8 @@ const validate = (data) => {
833
992
  } = data;
834
993
 
835
994
  const error_messages =
836
- constants.LOCALE_MESSAGES[language] ?? constants.LOCALE_MESSAGES[constants.LANGUAGES.en];
995
+ constants.LOCALE_MESSAGES[language] ??
996
+ constants.LOCALE_MESSAGES[constants.LANGUAGES.en];
837
997
 
838
998
  let result = { status: true, errors: {}, data: structuredClone(formData) };
839
999
 
@@ -850,12 +1010,18 @@ const validate = (data) => {
850
1010
  const secondParentField = fields.find((f) => f.path === secondParent);
851
1011
  if (
852
1012
  secondParentField &&
853
- secondParentField?.meta?.interface === constants.interfaces.TRANSLATIONS
1013
+ secondParentField?.meta?.interface ===
1014
+ constants.interfaces.TRANSLATIONS
854
1015
  ) {
855
1016
  const languageKey = secondParentField?.meta?.options?.language_field;
856
1017
  const firstParentValue = getValue(formData, firstParent);
857
- if (firstParentValue && typeChecks[constants.types.OBJECT](firstParentValue)) {
858
- const codeKey = Object.keys(firstParentValue).find((key) => key.includes(languageKey));
1018
+ if (
1019
+ firstParentValue &&
1020
+ typeChecks[constants.types.OBJECT](firstParentValue)
1021
+ ) {
1022
+ const codeKey = Object.keys(firstParentValue).find((key) =>
1023
+ key.includes(languageKey)
1024
+ );
859
1025
  const codeValue = codeKey ? firstParentValue[codeKey] : null;
860
1026
  if (codeValue) {
861
1027
  const translation_key = fieldPath.replace(
@@ -881,7 +1047,10 @@ const validate = (data) => {
881
1047
  // Validate Data
882
1048
  const defaultField = { meta: { hidden: false } };
883
1049
  if (!data) {
884
- const message = error_messages.REQUIRED.replace(`{field}`, formatLabel("data"));
1050
+ const message = error_messages.REQUIRED.replace(
1051
+ `{field}`,
1052
+ formatLabel("data")
1053
+ );
885
1054
  addError(
886
1055
  "data",
887
1056
  {
@@ -897,10 +1066,10 @@ const validate = (data) => {
897
1066
 
898
1067
  // validate data type
899
1068
  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
- );
1069
+ const message = error_messages.INVALID_TYPE.replace(
1070
+ `{field}`,
1071
+ formatLabel("data")
1072
+ ).replace(`{type}`, constants.types.OBJECT);
904
1073
  addError(
905
1074
  "data",
906
1075
  {
@@ -920,7 +1089,10 @@ const validate = (data) => {
920
1089
  const fieldValue = data[key];
921
1090
  // Skip empty values
922
1091
  if (fieldValue == null || fieldValue == undefined) {
923
- const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(key));
1092
+ const message = error_messages.REQUIRED.replace(
1093
+ `{field}`,
1094
+ formatLabel(key)
1095
+ );
924
1096
  addError(
925
1097
  key,
926
1098
  {
@@ -936,10 +1108,10 @@ const validate = (data) => {
936
1108
 
937
1109
  // Validate field type
938
1110
  if (!typeChecks[expectedType] || !typeChecks[expectedType](fieldValue)) {
939
- const message = error_messages.INVALID_TYPE.replace(`{field}`, key).replace(
940
- `{type}`,
941
- expectedType
942
- );
1111
+ const message = error_messages.INVALID_TYPE.replace(
1112
+ `{field}`,
1113
+ key
1114
+ ).replace(`{type}`, expectedType);
943
1115
  addError(
944
1116
  key,
945
1117
  {
@@ -959,7 +1131,11 @@ const validate = (data) => {
959
1131
  // Determine the expected type of array items
960
1132
  const arrayItemType = schema[key].array_type; // Define item types like "relations[]": "object"
961
1133
 
962
- if (arrayItemType && typeChecks[arrayItemType] && !typeChecks[arrayItemType](item)) {
1134
+ if (
1135
+ arrayItemType &&
1136
+ typeChecks[arrayItemType] &&
1137
+ !typeChecks[arrayItemType](item)
1138
+ ) {
963
1139
  const message = error_messages.INVALID_TYPE.replace(
964
1140
  `{field}`,
965
1141
  `${key}[${index}]`
@@ -981,10 +1157,10 @@ const validate = (data) => {
981
1157
 
982
1158
  // Validate API Version
983
1159
  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
- );
1160
+ const message = error_messages.IN.replace(
1161
+ `{field}`,
1162
+ formatLabel("apiVersion")
1163
+ ).replace(`{value}`, constants.API_VERSIONS.join(", "));
988
1164
  addError(
989
1165
  "apiVersion",
990
1166
  {
@@ -1008,7 +1184,9 @@ const validate = (data) => {
1008
1184
  let allFields = isSeparatedFields ? [] : fields;
1009
1185
 
1010
1186
  if (!isSeparatedFields) {
1011
- schemaFields = fields.filter((field) => field?.schema_id?.toString() === formId?.toString());
1187
+ schemaFields = fields.filter(
1188
+ (field) => field?.schema_id?.toString() === formId?.toString()
1189
+ );
1012
1190
  }
1013
1191
 
1014
1192
  let currentDepthMap = new Map();
@@ -1059,7 +1237,8 @@ const validate = (data) => {
1059
1237
  error_messages,
1060
1238
  onlyFormFields,
1061
1239
  apiVersion,
1062
- fieldOptions
1240
+ fieldOptions,
1241
+ data.language_codes
1063
1242
  );
1064
1243
  });
1065
1244
  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.2",
4
4
  "description": "validate dynamic schema",
5
5
  "main": "index.js",
6
6
  "scripts": {