cassproject 5.0.20 → 5.0.23

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.
Files changed (107) hide show
  1. package/package.json +49 -52
  2. package/src/com/eduworks/ec/array/EcArray.js +1 -1
  3. package/src/com/eduworks/ec/array/EcAsyncHelper.js +1 -1
  4. package/src/com/eduworks/ec/array/EcObject.js +1 -1
  5. package/src/com/eduworks/ec/crypto/EcAes.js +1 -1
  6. package/src/com/eduworks/ec/crypto/EcAesCtrAsync.js +20 -22
  7. package/src/com/eduworks/ec/crypto/EcCrypto.js +8 -4
  8. package/src/com/eduworks/ec/crypto/EcPk.js +7 -7
  9. package/src/com/eduworks/ec/crypto/EcPpk.js +7 -7
  10. package/src/com/eduworks/ec/crypto/EcRsaOaepAsync.js +20 -19
  11. package/src/com/eduworks/ec/crypto/EcRsaOaepAsyncWorker.js +7 -6
  12. package/src/com/eduworks/ec/crypto/forgeAsync.js +1 -9
  13. package/src/com/eduworks/ec/crypto/forgeAsyncNode.js +8 -8
  14. package/src/com/eduworks/ec/graph/EcDirectedGraph.js +1 -2
  15. package/src/com/eduworks/ec/graph/EcFrameworkGraph.js +18 -24
  16. package/src/com/eduworks/ec/promises/helpers.js +1 -3
  17. package/src/com/eduworks/ec/remote/EcRemote.js +9 -9
  18. package/src/com/eduworks/ec/task/EcAsyncTask.js +5 -20
  19. package/src/com/eduworks/ec/task/EcAsyncTaskManager.js +3 -4
  20. package/src/com/eduworks/ec/task/EcAsyncTaskWorker.js +9 -11
  21. package/src/com/eduworks/schema/cfd/competency/CfdFramework.js +36 -37
  22. package/src/com/eduworks/schema/ebac/EbacContact.js +3 -9
  23. package/src/com/eduworks/schema/ebac/EbacContactGrant.js +3 -9
  24. package/src/com/eduworks/schema/ebac/EbacCredential.js +3 -9
  25. package/src/com/eduworks/schema/ebac/EbacCredentialCommit.js +3 -9
  26. package/src/com/eduworks/schema/ebac/EbacCredentialRequest.js +3 -9
  27. package/src/com/eduworks/schema/ebac/EbacCredentials.js +3 -9
  28. package/src/com/eduworks/schema/ebac/EbacEncryptedSecret.js +3 -9
  29. package/src/com/eduworks/schema/ebac/EbacEncryptedValue.js +5 -12
  30. package/src/com/eduworks/schema/ebac/EbacSignature.js +1 -6
  31. package/src/index/env.js +6 -3
  32. package/src/index/polyfills.js +13 -27
  33. package/src/org/angles/schema/angles/VideoStory.js +1 -1
  34. package/src/org/cass/competency/EcCompetency.js +1 -2
  35. package/src/org/cass/competency/EcDirectory.js +1 -2
  36. package/src/org/cass/competency/EcFramework.js +9 -12
  37. package/src/org/cass/exporter/CSVExport.js +12 -13
  38. package/src/org/cass/importer/ASNImport.js +3 -6
  39. package/src/org/cass/importer/CSVImport.js +9 -17
  40. package/src/org/cass/importer/CTDLASNCSVConceptImport.js +25 -121
  41. package/src/org/cass/importer/CTDLASNCSVImport.js +88 -133
  42. package/src/org/cass/importer/FrameworkImport.js +16 -30
  43. package/src/org/cass/importer/MedbiqImport.js +3 -4
  44. package/src/org/cass/importer/TabStructuredImport.js +6 -6
  45. package/src/org/cass/profile/EcAssertion.js +40 -51
  46. package/src/org/cassproject/ebac/identity/EcContactGrant.js +2 -2
  47. package/src/org/cassproject/ebac/identity/EcIdentityManager.js +56 -62
  48. package/src/org/cassproject/ebac/identity/EcRekeyRequest.js +3 -2
  49. package/src/org/cassproject/ebac/identity/remote/EcRemoteIdentityManager.js +51 -62
  50. package/src/org/cassproject/ebac/identity/remote/OAuth2FileBasedRemoteIdentityManager.js +56 -70
  51. package/src/org/cassproject/ebac/identity/remote/RemoteIdentityManagerInterface.js +3 -3
  52. package/src/org/cassproject/ebac/repository/EcEncryptedValue.js +51 -44
  53. package/src/org/cassproject/ebac/repository/EcRepository.js +228 -301
  54. package/src/org/cassproject/general/repository/GeneralFile.js +8 -9
  55. package/src/org/cassproject/schema/cass/competency/Competency.js +10 -11
  56. package/src/org/cassproject/schema/cass/competency/Framework.js +10 -11
  57. package/src/org/cassproject/schema/cass/competency/Level.js +10 -11
  58. package/src/org/cassproject/schema/cass/competency/Relation.js +10 -11
  59. package/src/org/cassproject/schema/cass/competency/RollupRule.js +7 -7
  60. package/src/org/cassproject/schema/cass/profile/Assertion.js +20 -27
  61. package/src/org/cassproject/schema/cass/profile/AssertionEnvelope.js +3 -5
  62. package/src/org/cassproject/schema/general/EcRemoteLinkedData.js +41 -44
  63. package/src/org/json/ld/EcLinkedData.js +10 -12
  64. package/src/org/schema/EcOrganization.js +10 -10
  65. package/src/org/schema/EcQuestion.js +8 -8
  66. package/src/org/w3/skos/Concept.js +6 -6
  67. package/src/org/w3/skos/ConceptScheme.js +6 -6
  68. package/src/org/w3/skos/EcConcept.js +1 -2
  69. package/src/org/w3/skos/EcConceptScheme.js +1 -2
  70. package/src/test/0.start.test.js +1 -1
  71. package/src/test/0.testLibraries.test.js +172 -0
  72. package/src/test/1.ASNImport.test.js +122 -0
  73. package/src/test/1.CSVExport.test.js +57 -0
  74. package/src/test/1.CSVImport.test.js +185 -0
  75. package/src/test/1.CTDLASNCSVConceptImport.test.js +348 -0
  76. package/src/test/1.CTDLASNCSVImport.import.test.js +497 -0
  77. package/src/test/1.Ebac.schema.test.js +107 -0
  78. package/src/test/1.EcAssertion.decrypt.test.js +98 -0
  79. package/src/test/1.EcAssertion.pinned.test.js +160 -0
  80. package/src/test/1.EcCrypto.formats.test.js +293 -0
  81. package/src/test/1.EcCrypto.generateUUID.test.js +47 -0
  82. package/src/test/1.EcEncryptedValue.helpers.test.js +236 -0
  83. package/src/test/1.EcHelpers.misc.test.js +259 -0
  84. package/src/test/1.EcIdentityManager.keys.test.js +439 -0
  85. package/src/test/1.EcOrganization.rekey.test.js +68 -0
  86. package/src/test/1.EcRemote.responses.test.js +155 -0
  87. package/src/test/1.EcRemoteIdentityManager.flows.test.js +326 -0
  88. package/src/test/1.EcRemoteLinkedData.pinned.test.js +473 -0
  89. package/src/test/1.EcRepository.l0.test.js +14 -5
  90. package/src/test/1.EcRepository.l1.test.js +16 -7
  91. package/src/test/1.EcRepository.l2.test.js +20 -11
  92. package/src/test/1.EcRepository.unit.test.js +961 -0
  93. package/src/test/1.EcRsaOaep.test.js +2 -12
  94. package/src/test/1.EcRsaOaepAsync.test.js +2 -12
  95. package/src/test/1.EcRsaOaepAsyncWorker.test.js +2 -12
  96. package/src/test/1.EcRsaOaepAsyncWorker.workers.test.js +147 -0
  97. package/src/test/1.FrameworkImport.test.js +170 -0
  98. package/src/test/1.MedbiqImport.test.js +92 -0
  99. package/src/test/1.OAuth2FileBasedRemoteIdentityManager.test.js +399 -0
  100. package/src/test/1.TabStructuredImport.test.js +61 -0
  101. package/src/test/1.polyfills.test.js +89 -0
  102. package/src/test/1.schema.competency.pinned.test.js +384 -0
  103. package/src/test/3.EcFrameworkGraph.test.js +3 -2
  104. package/src/test/4.schema.test.js +33 -28
  105. package/src/test/4.skyId.test.js +4 -2
  106. package/src/test/cypressValue.js +9 -0
  107. package/src/test/2.EcCompetency.test.js +0 -17
@@ -18,13 +18,61 @@ function setVersionIdentifier(f) {
18
18
  },
19
19
  "ceterms:identifierType": parts.length > 2 ? parts[2] : undefined
20
20
  };
21
- } catch (e) {
21
+ } catch (e) { // NOSONAR - A value that cannot be split (not a string) is dropped from the list.
22
22
  delete f["ceterms:versionIdentifier"][i];
23
23
  }
24
24
  }
25
25
  }
26
26
  }
27
27
 
28
+ const CTID_PATTERN = /^ce-[0-9a-fA-F-]+$/;
29
+
30
+ function isCtidOrAllowedRegistryUrl(value, allowedOrigins) {
31
+ if (value === "" || CTID_PATTERN.test(value)) return true;
32
+ let origin = null;
33
+ try {
34
+ origin = new URL(value).origin;
35
+ } catch (e) {
36
+ // Not parseable as a URL; reported as an error by the caller.
37
+ }
38
+ return origin != null && allowedOrigins.includes(origin);
39
+ }
40
+
41
+ function registryUrlFieldErrors(raw, field, rowIndex, allowedOrigins) {
42
+ if (raw == null) return [];
43
+ const errors = [];
44
+ const values = Array.isArray(raw) ? raw : String(raw).split("|");
45
+ for (let value of values) {
46
+ value = String(value).trim();
47
+ if (!isCtidOrAllowedRegistryUrl(value, allowedOrigins)) {
48
+ errors.push(`Row ${rowIndex}: ${field} must be a CTID or a credential registry URL for this environment (got: ${value})`);
49
+ }
50
+ }
51
+ return errors;
52
+ }
53
+
54
+ function rowHasAnyProperty(row, props) {
55
+ for (const prop of props) {
56
+ if (row[prop]) return true;
57
+ }
58
+ return false;
59
+ }
60
+
61
+ // True when a row of the given type has one of the required properties, or any typed row has a child property.
62
+ function hasHierarchyProperty(tabularData, type, rules) {
63
+ let hasRequiredProp = false;
64
+ let hasChildProp = false;
65
+ for (const row of tabularData) {
66
+ if (!row?.["@type"]) continue;
67
+ if (row["@type"] === type && rules.requiredProperties && rowHasAnyProperty(row, rules.requiredProperties))
68
+ hasRequiredProp = true;
69
+ if (rules.childProperties && rowHasAnyProperty(row, rules.childProperties))
70
+ hasChildProp = true;
71
+ if (hasRequiredProp && hasChildProp) break;
72
+ }
73
+ return hasRequiredProp || hasChildProp;
74
+ }
75
+
28
76
  module.exports = class CTDLASNCSVImport {
29
77
  static analyzeFile(file, success, failure) {
30
78
  if (file == null) {
@@ -74,7 +122,7 @@ module.exports = class CTDLASNCSVImport {
74
122
  line: i
75
123
  });
76
124
  } else {
77
- const originalAlreadyAdded = duplicates.find((duplicate) => duplicate.line === uniqueRows[existing].line);
125
+ const originalAlreadyAdded = duplicates.some((duplicate) => duplicate.line === uniqueRows[existing].line);
78
126
  if (!originalAlreadyAdded) {
79
127
  duplicates.push(uniqueRows[existing]);
80
128
  }
@@ -91,20 +139,11 @@ module.exports = class CTDLASNCSVImport {
91
139
  for (let i = 0; i < tabularData.length; i++) {
92
140
  if (i == 0) continue;
93
141
  let col = tabularData[i];
94
- if (
95
- col[typeCol] != null &&
96
- col[typeCol].trim() == "ceasn:CompetencyFramework"
97
- )
142
+ if (col[typeCol]?.trim() == "ceasn:CompetencyFramework")
98
143
  frameworkCounter++;
99
- else if (
100
- col[typeCol] != null &&
101
- col[typeCol].trim() == "ceterms:Collection"
102
- )
103
- collectionCounter++;
104
- else if (
105
- col[typeCol] != null &&
106
- col[typeCol].trim() == "ceasn:Competency"
107
- ) {
144
+ else if (col[typeCol]?.trim() == "ceterms:Collection")
145
+ collectionCounter++;
146
+ else if (col[typeCol]?.trim() == "ceasn:Competency") {
108
147
  competencyCounter++;
109
148
  // if (colCompetencyText && !col[colCompetencyText].trim()) {
110
149
  // this.error('CTDLASN Parse Error: Competency text is empty on line ' + (i+1));
@@ -178,7 +217,7 @@ module.exports = class CTDLASNCSVImport {
178
217
  }
179
218
  }
180
219
 
181
- const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassTerms"))));
220
+ const terms = structuredClone(await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassTerms"));
182
221
  let frameworks = {};
183
222
  let frameworkArray = [];
184
223
  let frameworkRows = {};
@@ -187,7 +226,6 @@ module.exports = class CTDLASNCSVImport {
187
226
  let relations = [];
188
227
  let relationById = {};
189
228
  let competencyCtids = {};
190
- let rowsWithErrors = new Set(); // Track rows that have errors to avoid duplicate processing
191
229
  for (let i = 0; i < tabularData.length; i++) {
192
230
  if (!tabularData[i]) {
193
231
  continue;
@@ -197,7 +235,7 @@ module.exports = class CTDLASNCSVImport {
197
235
  if (!pretranslatedE) {
198
236
  continue;
199
237
  }
200
- if (pretranslatedE["@type"] && pretranslatedE["@type"].toLowerCase() &&
238
+ if (pretranslatedE["@type"]?.toLowerCase() &&
201
239
  (pretranslatedE["@type"].toLowerCase().startsWith('sample') || pretranslatedE["@type"].toLowerCase().startsWith('instruction'))
202
240
  ) {
203
241
  continue;
@@ -210,7 +248,7 @@ module.exports = class CTDLASNCSVImport {
210
248
  }
211
249
  // Skip competency if ctid is specified
212
250
  if (skip && Array.isArray(skip) && skip.length > 0) {
213
- if (skip.find((element) => element.ctid ? element.ctid.includes(pretranslatedE["ceterms:ctid"]) : element === pretranslatedE["ceterms:ctid"])) {
251
+ if (skip.find((element) => element.ctid ? element.ctid.includes(pretranslatedE["ceterms:ctid"]) : element === pretranslatedE["ceterms:ctid"])) { // NOSONAR - some() would also match a falsy skip entry, find() does not
214
252
  continue;
215
253
  }
216
254
  }
@@ -230,7 +268,6 @@ module.exports = class CTDLASNCSVImport {
230
268
  rowHasError = true;
231
269
  }
232
270
  if (rowHasError) {
233
- rowsWithErrors.add(i);
234
271
  continue;
235
272
  }
236
273
  if (
@@ -238,7 +275,7 @@ module.exports = class CTDLASNCSVImport {
238
275
  "ceasn:CompetencyFramework"
239
276
  ) {
240
277
  // Validate required properties (only if validation rules provided)
241
- if (validationRules && validationRules.requiredProps) {
278
+ if (validationRules?.requiredProps) {
242
279
  const validationErrors = CTDLASNCSVImport.validateRequiredProperties(
243
280
  pretranslatedE,
244
281
  "ceasn:CompetencyFramework",
@@ -251,12 +288,11 @@ module.exports = class CTDLASNCSVImport {
251
288
  } else {
252
289
  errors.push(validationErrors);
253
290
  }
254
- rowsWithErrors.add(i);
255
291
  continue;
256
292
  }
257
293
  }
258
294
  // Validate registry URL fields (only if url rules provided)
259
- if (validationRules && validationRules.urlRules) {
295
+ if (validationRules?.urlRules) {
260
296
  const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
261
297
  pretranslatedE,
262
298
  "ceasn:CompetencyFramework",
@@ -265,7 +301,6 @@ module.exports = class CTDLASNCSVImport {
265
301
  );
266
302
  if (urlErrors) {
267
303
  errors.push(...urlErrors);
268
- rowsWithErrors.add(i);
269
304
  continue;
270
305
  }
271
306
  }
@@ -281,7 +316,6 @@ module.exports = class CTDLASNCSVImport {
281
316
  let existing = await EcRepository.get(translator.id);
282
317
  if (existing && existing.type !== 'Framework') {
283
318
  errors.push(`Row ${i + 2}: ${translator.id} already exists as a ${existing.type}`);
284
- rowsWithErrors.add(i);
285
319
  continue;
286
320
  }
287
321
  } catch (e) {
@@ -335,10 +369,7 @@ module.exports = class CTDLASNCSVImport {
335
369
  }
336
370
  if (ceo != null) f.addOwner(ceo.ppk.toPk());
337
371
  if (
338
- EcFramework.template != null &&
339
- EcFramework.template[
340
- "schema:dateCreated"
341
- ] != null
372
+ EcFramework.template?.["schema:dateCreated"] != null
342
373
  ) {
343
374
  CTDLASNCSVImport.setDateCreated(e, f);
344
375
  }
@@ -369,7 +400,7 @@ module.exports = class CTDLASNCSVImport {
369
400
  for (let i = val.length-1; i >=0; i--) {
370
401
  if (!val[i].startsWith('http')) {
371
402
  if (val[i] === "Deprecated" || val[i] === "Published" || val[i] === "Draft" ) {
372
- val[i] = "http://credreg.net/ctdlasn/vocabs/publicationStatus/" + val[i];
403
+ val[i] = "http://credreg.net/ctdlasn/vocabs/publicationStatus/" + val[i]; // NOSONAR - JSON-LD identifier, never fetched
373
404
  } else {
374
405
  val.splice(i, 1);
375
406
  }
@@ -401,7 +432,7 @@ module.exports = class CTDLASNCSVImport {
401
432
  pretranslatedE["@type"] == "ceasn:Competency"
402
433
  ) {
403
434
  // Validate required properties (only if validation rules provided)
404
- if (validationRules && validationRules.requiredProps) {
435
+ if (validationRules?.requiredProps) {
405
436
  const validationErrors = CTDLASNCSVImport.validateRequiredProperties(
406
437
  pretranslatedE,
407
438
  "ceasn:Competency",
@@ -414,12 +445,11 @@ module.exports = class CTDLASNCSVImport {
414
445
  } else {
415
446
  errors.push(validationErrors);
416
447
  }
417
- rowsWithErrors.add(i);
418
448
  continue;
419
449
  }
420
450
  }
421
451
  // Validate registry URL fields (only if url rules provided)
422
- if (validationRules && validationRules.urlRules) {
452
+ if (validationRules?.urlRules) {
423
453
  const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
424
454
  pretranslatedE,
425
455
  "ceasn:Competency",
@@ -428,7 +458,6 @@ module.exports = class CTDLASNCSVImport {
428
458
  );
429
459
  if (urlErrors) {
430
460
  errors.push(...urlErrors);
431
- rowsWithErrors.add(i);
432
461
  continue;
433
462
  }
434
463
  }
@@ -469,7 +498,6 @@ module.exports = class CTDLASNCSVImport {
469
498
  f.copyFrom(e);
470
499
  if (e["id"] == null) {
471
500
  errors.push(`Row ${i+2}: Competency is missing an id`);
472
- rowsWithErrors.add(i);
473
501
  continue;
474
502
  }
475
503
  if (e["ceasn:isPartOf"] != null) {
@@ -515,7 +543,6 @@ module.exports = class CTDLASNCSVImport {
515
543
  errors.push(
516
544
  `Row ${i+2}: Could not find framework:${e["type"]} (Possibly missing ceasn:isPartOf or ceasn:isChildOf)`
517
545
  );
518
- rowsWithErrors.add(i);
519
546
  continue;
520
547
  }
521
548
  if (parent != null) {
@@ -535,7 +562,6 @@ module.exports = class CTDLASNCSVImport {
535
562
  `Row ${i+2}: Object cannot trace to framework:` +
536
563
  e["type"]
537
564
  );
538
- rowsWithErrors.add(i);
539
565
  continue;
540
566
  }
541
567
  } else {
@@ -543,13 +569,11 @@ module.exports = class CTDLASNCSVImport {
543
569
  `Row ${i+2}: Object has no framework:` +
544
570
  e["type"]
545
571
  );
546
- rowsWithErrors.add(i);
547
572
  continue;
548
573
  }
549
574
  }
550
575
  if (
551
- EcCompetency.template != null &&
552
- EcCompetency.template["@owner"] != null
576
+ EcCompetency.template?.["@owner"] != null
553
577
  ) {
554
578
  f["owner"] =
555
579
  EcCompetency.template["@owner"];
@@ -576,10 +600,7 @@ module.exports = class CTDLASNCSVImport {
576
600
  }
577
601
  if (ceo != null) f.addOwner(ceo.ppk.toPk());
578
602
  if (
579
- EcCompetency.template != null &&
580
- EcCompetency.template[
581
- "schema:dateCreated"
582
- ] != null
603
+ EcCompetency.template?.["schema:dateCreated"] != null
583
604
  ) {
584
605
  CTDLASNCSVImport.setDateCreated(e, f);
585
606
  }
@@ -610,7 +631,7 @@ module.exports = class CTDLASNCSVImport {
610
631
  for (let i = val.length-1; i >=0; i--) {
611
632
  if (!val[i].startsWith('http')) {
612
633
  if (val[i] === "Deprecated" || val[i] === "Published" || val[i] === "Draft" ) {
613
- val[i] = "http://credreg.net/ctdlasn/vocabs/publicationStatus/" + val[i];
634
+ val[i] = "http://credreg.net/ctdlasn/vocabs/publicationStatus/" + val[i]; // NOSONAR - JSON-LD identifier, never fetched
614
635
  } else {
615
636
  val.splice(i, 1);
616
637
  }
@@ -738,13 +759,11 @@ module.exports = class CTDLASNCSVImport {
738
759
  pretranslatedE["@type"] == ""
739
760
  ) {
740
761
  errors.push(`Row ${i+2}: Missing or empty @type`);
741
- rowsWithErrors.add(i);
742
762
  continue;
743
763
  } else {
744
764
  errors.push(
745
765
  `Row ${i+2}: Found unknown type:` + pretranslatedE["@type"]
746
766
  );
747
- rowsWithErrors.add(i);
748
767
  continue;
749
768
  }
750
769
  }
@@ -804,7 +823,7 @@ module.exports = class CTDLASNCSVImport {
804
823
  }
805
824
  }
806
825
 
807
- const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassTerms"))));
826
+ const terms = structuredClone(await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassTerms"));
808
827
  let frameworks = {};
809
828
  let frameworkArray = [];
810
829
  let frameworkRows = {};
@@ -813,7 +832,6 @@ module.exports = class CTDLASNCSVImport {
813
832
  let relations = [];
814
833
  let relationById = {};
815
834
  let competencyCtids = {};
816
- let rowsWithErrors = new Set(); // Track rows that have errors
817
835
  for (let i = 0; i < tabularData.length; i++) {
818
836
  let pretranslatedE = tabularData[i];
819
837
  // Skip extra lines if found in file
@@ -836,7 +854,6 @@ module.exports = class CTDLASNCSVImport {
836
854
  rowHasError = true;
837
855
  }
838
856
  if (rowHasError) {
839
- rowsWithErrors.add(i);
840
857
  continue;
841
858
  }
842
859
  if (
@@ -852,7 +869,7 @@ module.exports = class CTDLASNCSVImport {
852
869
  }
853
870
  // Skip competency if ctid is specified
854
871
  if (skip && Array.isArray(skip) && skip.length > 0) {
855
- if (skip.find((element) => element.ctid ? element.ctid.includes(pretranslatedE["ceterms:ctid"]) : element === pretranslatedE["ceterms:ctid"])) {
872
+ if (skip.find((element) => element.ctid ? element.ctid.includes(pretranslatedE["ceterms:ctid"]) : element === pretranslatedE["ceterms:ctid"])) { // NOSONAR - some() would also match a falsy skip entry, find() does not
856
873
  continue;
857
874
  }
858
875
  }
@@ -861,7 +878,7 @@ module.exports = class CTDLASNCSVImport {
861
878
  "ceterms:Collection"
862
879
  ) {
863
880
  // Validate required properties (only if validation rules provided)
864
- if (validationRules && validationRules.requiredProps) {
881
+ if (validationRules?.requiredProps) {
865
882
  const validationErrors = CTDLASNCSVImport.validateRequiredProperties(
866
883
  pretranslatedE,
867
884
  "ceterms:Collection",
@@ -874,12 +891,11 @@ module.exports = class CTDLASNCSVImport {
874
891
  } else {
875
892
  errors.push(validationErrors);
876
893
  }
877
- rowsWithErrors.add(i);
878
894
  continue;
879
895
  }
880
896
  }
881
897
  // Validate registry URL fields (only if url rules provided)
882
- if (validationRules && validationRules.urlRules) {
898
+ if (validationRules?.urlRules) {
883
899
  const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
884
900
  pretranslatedE,
885
901
  "ceterms:Collection",
@@ -888,7 +904,6 @@ module.exports = class CTDLASNCSVImport {
888
904
  );
889
905
  if (urlErrors) {
890
906
  errors.push(...urlErrors);
891
- rowsWithErrors.add(i);
892
907
  continue;
893
908
  }
894
909
  }
@@ -935,10 +950,7 @@ module.exports = class CTDLASNCSVImport {
935
950
  }
936
951
  if (ceo != null) f.addOwner(ceo.ppk.toPk());
937
952
  if (
938
- EcFramework.template != null &&
939
- EcFramework.template[
940
- "schema:dateCreated"
941
- ] != null
953
+ EcFramework.template?.["schema:dateCreated"] != null
942
954
  ) {
943
955
  CTDLASNCSVImport.setDateCreated(e, f);
944
956
  }
@@ -964,7 +976,7 @@ module.exports = class CTDLASNCSVImport {
964
976
  pretranslatedE["@type"] == "ceasn:Competency"
965
977
  ) {
966
978
  // Validate required properties (only if validation rules provided)
967
- if (validationRules && validationRules.requiredProps) {
979
+ if (validationRules?.requiredProps) {
968
980
  const validationErrors = CTDLASNCSVImport.validateRequiredProperties(
969
981
  pretranslatedE,
970
982
  "ceterms:Collection:Competency",
@@ -977,12 +989,11 @@ module.exports = class CTDLASNCSVImport {
977
989
  } else {
978
990
  errors.push(validationErrors);
979
991
  }
980
- rowsWithErrors.add(i);
981
992
  continue;
982
993
  }
983
994
  }
984
995
  // Validate registry URL fields (only if url rules provided)
985
- if (validationRules && validationRules.urlRules) {
996
+ if (validationRules?.urlRules) {
986
997
  const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
987
998
  pretranslatedE,
988
999
  "ceasn:Competency",
@@ -991,7 +1002,6 @@ module.exports = class CTDLASNCSVImport {
991
1002
  );
992
1003
  if (urlErrors) {
993
1004
  errors.push(...urlErrors);
994
- rowsWithErrors.add(i);
995
1005
  continue;
996
1006
  }
997
1007
  }
@@ -1019,7 +1029,6 @@ module.exports = class CTDLASNCSVImport {
1019
1029
  f.copyFrom(e);
1020
1030
  if (e["id"] == null) {
1021
1031
  errors.push(`Row ${i+2}: Competency is missing an id`);
1022
- rowsWithErrors.add(i);
1023
1032
  continue;
1024
1033
  }
1025
1034
  if (e["ceterms:isMemberOf"] != null) {
@@ -1034,16 +1043,14 @@ module.exports = class CTDLASNCSVImport {
1034
1043
  }
1035
1044
  }
1036
1045
  if (
1037
- EcCompetency.template != null &&
1038
- EcCompetency.template["@owner"] != null
1046
+ EcCompetency.template?.["@owner"] != null
1039
1047
  ) {
1040
1048
  f["owner"] =
1041
1049
  EcCompetency.template["@owner"];
1042
1050
  }
1043
1051
  if (e["owner"] == null) {
1044
1052
  if (
1045
- frameworkRows[EcRemoteLinkedData.trimVersionFromUrl(e["ceterms:isMemberOf"][0])] &&
1046
- frameworkRows[EcRemoteLinkedData.trimVersionFromUrl(e["ceterms:isMemberOf"][0])]["owner"] != null
1053
+ frameworkRows[EcRemoteLinkedData.trimVersionFromUrl(e["ceterms:isMemberOf"][0])]?.["owner"] != null
1047
1054
  )
1048
1055
  e["owner"] =
1049
1056
  frameworkRows[EcRemoteLinkedData.trimVersionFromUrl(
@@ -1061,10 +1068,7 @@ module.exports = class CTDLASNCSVImport {
1061
1068
  }
1062
1069
  if (ceo != null) f.addOwner(ceo.ppk.toPk());
1063
1070
  if (
1064
- EcCompetency.template != null &&
1065
- EcCompetency.template[
1066
- "schema:dateCreated"
1067
- ] != null
1071
+ EcCompetency.template?.["schema:dateCreated"] != null
1068
1072
  ) {
1069
1073
  CTDLASNCSVImport.setDateCreated(e, f);
1070
1074
  }
@@ -1095,7 +1099,7 @@ module.exports = class CTDLASNCSVImport {
1095
1099
  for (let i = val.length-1; i >=0; i--) {
1096
1100
  if (!val[i].startsWith('http')) {
1097
1101
  if (val[i] === "Deprecated" || val[i] === "Published" || val[i] === "Draft" ) {
1098
- val[i] = "http://credreg.net/ctdlasn/vocabs/publicationStatus/" + val[i];
1102
+ val[i] = "http://credreg.net/ctdlasn/vocabs/publicationStatus/" + val[i]; // NOSONAR - JSON-LD identifier, never fetched
1099
1103
  } else {
1100
1104
  val.splice(i, 1);
1101
1105
  }
@@ -1215,13 +1219,11 @@ module.exports = class CTDLASNCSVImport {
1215
1219
  pretranslatedE["@type"] == ""
1216
1220
  ) {
1217
1221
  errors.push(`Row ${i+2}: Missing or empty @type`);
1218
- rowsWithErrors.add(i);
1219
1222
  continue;
1220
1223
  } else {
1221
1224
  errors.push(
1222
1225
  `Row ${i+2}: Found unknown type:` + pretranslatedE["@type"]
1223
1226
  );
1224
- rowsWithErrors.add(i);
1225
1227
  continue;
1226
1228
  }
1227
1229
  }
@@ -1250,16 +1252,14 @@ module.exports = class CTDLASNCSVImport {
1250
1252
  } else if (type == "Concept") {
1251
1253
  type = "ConceptScheme";
1252
1254
  }
1253
- } else {
1254
- if (type == "Framework") {
1255
- type = "Competency";
1256
- } else if (type == "ConceptScheme") {
1257
- type = "Concept";
1258
- }
1255
+ } else if (type == "Framework") {
1256
+ type = "Competency";
1257
+ } else if (type == "ConceptScheme") {
1258
+ type = "Concept";
1259
1259
  }
1260
1260
  }
1261
1261
  if (endpoint != null) {
1262
- if (endpoint.indexOf("ce-") != -1) {
1262
+ if (endpoint.includes("ce-")) {
1263
1263
  ctid = ctid.substring(3);
1264
1264
  }
1265
1265
  return endpoint + ctid;
@@ -1298,7 +1298,7 @@ module.exports = class CTDLASNCSVImport {
1298
1298
  if (typeof thisKey == "string") {
1299
1299
  if (translator[key].trim().length == 0) {
1300
1300
  translator[key] = null;
1301
- } else if (thisKey.indexOf("|") != -1) {
1301
+ } else if (thisKey.includes("|")) {
1302
1302
  thisKey = thisKey.split("|");
1303
1303
  translator[key] = thisKey;
1304
1304
  for (let i = 0; i < thisKey.length; i++) {
@@ -1378,7 +1378,7 @@ module.exports = class CTDLASNCSVImport {
1378
1378
  collectionFlag
1379
1379
  ) {
1380
1380
  if (!EcArray.isArray(e[field])) {
1381
- let makeArray = Array(e[field]);
1381
+ let makeArray = new Array(e[field]);
1382
1382
  e[field] = makeArray;
1383
1383
  }
1384
1384
  for (let i = 0; i < e[field].length; i++) {
@@ -1522,25 +1522,9 @@ module.exports = class CTDLASNCSVImport {
1522
1522
  const allowedOrigins = urlRules.allowedOrigins;
1523
1523
  if (!fields || !allowedOrigins || allowedOrigins.length === 0) return null;
1524
1524
 
1525
- const ctidPattern = /^ce-[0-9a-fA-F-]+$/;
1526
1525
  const errors = [];
1527
1526
  for (const field of fields) {
1528
- const raw = obj[field];
1529
- if (raw == null) continue;
1530
- const values = Array.isArray(raw) ? raw : String(raw).split("|");
1531
- for (let value of values) {
1532
- value = String(value).trim();
1533
- if (value === "" || ctidPattern.test(value)) continue;
1534
- let origin = null;
1535
- try {
1536
- origin = new URL(value).origin;
1537
- } catch (e) {
1538
- // Not parseable as a URL; falls through to the error below.
1539
- }
1540
- if (origin == null || !allowedOrigins.includes(origin)) {
1541
- errors.push(`Row ${rowIndex}: ${field} must be a CTID or a credential registry URL for this environment (got: ${value})`);
1542
- }
1543
- }
1527
+ errors.push(...registryUrlFieldErrors(obj[field], field, rowIndex, allowedOrigins));
1544
1528
  }
1545
1529
  return errors.length > 0 ? errors : null;
1546
1530
  }
@@ -1553,40 +1537,11 @@ module.exports = class CTDLASNCSVImport {
1553
1537
 
1554
1538
  // Check each type defined in hierarchy rules
1555
1539
  for (const [type, rules] of Object.entries(hierarchyRules)) {
1556
- const hasType = tabularData.some(r => r && r["@type"] === type);
1540
+ const hasType = tabularData.some(r => r?.["@type"] === type);
1557
1541
  if (!hasType) continue;
1558
1542
 
1559
- let hasRequiredProp = false;
1560
- let hasChildProp = false;
1561
-
1562
- for (const row of tabularData) {
1563
- if (!row || !row["@type"]) continue;
1564
-
1565
- // Check if this row has any of the required properties
1566
- if (row["@type"] === type && rules.requiredProperties) {
1567
- for (const prop of rules.requiredProperties) {
1568
- if (row[prop]) {
1569
- hasRequiredProp = true;
1570
- break;
1571
- }
1572
- }
1573
- }
1574
-
1575
- // Check if any row has child properties
1576
- if (rules.childProperties) {
1577
- for (const prop of rules.childProperties) {
1578
- if (row[prop]) {
1579
- hasChildProp = true;
1580
- break;
1581
- }
1582
- }
1583
- }
1584
-
1585
- if (hasRequiredProp && hasChildProp) break;
1586
- }
1587
-
1588
1543
  // Validate that at least one of the hierarchy properties exists
1589
- if (!hasRequiredProp && !hasChildProp) {
1544
+ if (!hasHierarchyProperty(tabularData, type, rules)) {
1590
1545
  return rules.errorMessage || `CSV must contain hierarchy properties for ${type}`;
1591
1546
  }
1592
1547
  }
@@ -1602,7 +1557,7 @@ module.exports = class CTDLASNCSVImport {
1602
1557
  let timestamp = object.getTimestamp();
1603
1558
  let date;
1604
1559
  if (timestamp != null) {
1605
- date = new Date(parseInt(timestamp)).toISOString();
1560
+ date = new Date(Number.parseInt(timestamp)).toISOString();
1606
1561
  } else {
1607
1562
  date = new Date().toISOString();
1608
1563
  }
@@ -67,17 +67,13 @@ module.exports = class FrameworkImport {
67
67
  FrameworkImport.compMap = {};
68
68
  FrameworkImport.savedComp = 0;
69
69
  FrameworkImport.savedRel = 0;
70
- for (let i = 0; i < source.competency.length; i++) {
71
- let id = source.competency[i];
70
+ for (let sourceCompetencyId of source.competency) {
72
71
  EcCompetency.get(
73
- id,
72
+ sourceCompetencyId,
74
73
  function (comp) {
75
74
  let competency = new EcCompetency();
76
75
  competency.copyFrom(comp);
77
- if (
78
- repo == null ||
79
- repo.selectedServer.indexOf(serverUrl) != -1
80
- )
76
+ if (repo?.selectedServer.includes(serverUrl) ?? true)
81
77
  competency.generateId(serverUrl);
82
78
  else competency.generateShortId(serverUrl);
83
79
  FrameworkImport.compMap[
@@ -100,25 +96,15 @@ module.exports = class FrameworkImport {
100
96
  ) {
101
97
  FrameworkImport.targetUsable.save(
102
98
  function (p1) {
103
- for (
104
- let i = 0;
105
- i < source.relation.length;
106
- i++
107
- ) {
108
- let id = source.relation[i];
99
+ for (let sourceRelationId of source.relation) {
109
100
  EcAlignment.get(
110
- id,
101
+ sourceRelationId,
111
102
  function (rel) {
112
103
  let relation = new EcAlignment();
113
104
  relation.copyFrom(
114
105
  rel
115
106
  );
116
- if (
117
- repo == null ||
118
- repo.selectedServer.indexOf(
119
- serverUrl
120
- ) != -1
121
- )
107
+ if (repo?.selectedServer.includes(serverUrl) ?? true)
122
108
  relation.generateId(
123
109
  serverUrl
124
110
  );
@@ -223,18 +209,18 @@ module.exports = class FrameworkImport {
223
209
  );
224
210
  }
225
211
  } else {
226
- for (let i = 0; i < source.competency.length; i++) {
212
+ for (let sourceCompetencyId of source.competency) {
227
213
  if (
228
214
  target.competency == null ||
229
- target.competency.indexOf(source.competency[i]) == -1 &&
230
- target.competency.indexOf(
215
+ !target.competency.includes(sourceCompetencyId) &&
216
+ !target.competency.includes(
231
217
  EcRemoteLinkedData.trimVersionFromUrl(
232
- source.competency[i]
218
+ sourceCompetencyId
233
219
  )
234
- ) == -1
220
+ )
235
221
  ) {
236
222
  EcCompetency.get(
237
- source.competency[i],
223
+ sourceCompetencyId,
238
224
  function (comp) {
239
225
  FrameworkImport.competencies.push(comp);
240
226
  FrameworkImport.targetUsable.addCompetency(comp.id);
@@ -254,14 +240,14 @@ module.exports = class FrameworkImport {
254
240
  ) {
255
241
  if (
256
242
  target.relation == null ||
257
- target.relation.indexOf(
243
+ !target.relation.includes(
258
244
  source.relation[i]
259
- ) == -1 &&
260
- target.relation.indexOf(
245
+ ) &&
246
+ !target.relation.includes(
261
247
  EcRemoteLinkedData.trimVersionFromUrl(
262
248
  source.competency[i]
263
249
  )
264
- ) == -1
250
+ )
265
251
  ) {
266
252
  EcAlignment.get(
267
253
  source.relation[i],
@@ -49,7 +49,7 @@ module.exports = class MedbiqImport extends Importer {
49
49
  }
50
50
  } else {
51
51
  let newCompetency = new EcCompetency();
52
- if (obj["lom"] != null && obj["lom"]["general"] != null) {
52
+ if (obj["lom"]?.["general"] != null) {
53
53
  newCompetency.name = obj["lom"]["general"]["title"][
54
54
  "string"
55
55
  ].toString();
@@ -135,9 +135,8 @@ module.exports = class MedbiqImport extends Importer {
135
135
  ) {
136
136
  MedbiqImport.progressObject = null;
137
137
  MedbiqImport.saved = 0;
138
- for (let i = 0; i < MedbiqImport.medbiqXmlCompetencies.length; i++) {
139
- let comp = MedbiqImport.medbiqXmlCompetencies[i];
140
- if (repo == null || repo.selectedServer.indexOf(serverUrl) != -1)
138
+ for (let comp of MedbiqImport.medbiqXmlCompetencies) {
139
+ if (repo?.selectedServer.includes(serverUrl) ?? true)
141
140
  comp.generateId(serverUrl);
142
141
  else comp.generateShortId(serverUrl);
143
142
  if (owner != null) comp.addOwner(owner.ppk.toPk());