fhirsmith 0.9.6 → 0.10.0

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 (49) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/library/folder-content-loader.js +91 -0
  3. package/npmprojector/npmprojector.js +2 -6
  4. package/package.json +1 -1
  5. package/packages/package-crawler.js +123 -6
  6. package/packages/packages.js +104 -0
  7. package/publisher/publisher.js +290 -12
  8. package/registry/crawler.js +85 -6
  9. package/registry/registry.js +24 -7
  10. package/server.js +11 -2
  11. package/stats.js +26 -18
  12. package/translations/Messages.properties +2 -1
  13. package/tx/cs/cs-cs.js +8 -0
  14. package/tx/cs/cs-loinc.js +1 -0
  15. package/tx/cs/cs-provider-list.js +2 -1
  16. package/tx/cs/cs-snomed.js +142 -59
  17. package/tx/data/snomed-testing.cache +0 -0
  18. package/tx/html/home-metrics.liquid +10 -10
  19. package/tx/library/canonical-resource.js +4 -2
  20. package/tx/library/codesystem.js +31 -0
  21. package/tx/library/conceptmap.js +24 -0
  22. package/tx/library/designations.js +27 -20
  23. package/tx/library/renderer.js +303 -22
  24. package/tx/library/ucum-types.js +4 -1
  25. package/tx/library/valueset.js +46 -0
  26. package/tx/library.js +65 -21
  27. package/tx/operation-context.js +122 -27
  28. package/tx/params.js +36 -8
  29. package/tx/provider.js +6 -3
  30. package/tx/tx-html.js +34 -0
  31. package/tx/tx.js +92 -30
  32. package/tx/vs/vs-vsac.js +157 -9
  33. package/tx/workers/cache-control.js +186 -0
  34. package/tx/workers/{related.js → compare.js} +27 -27
  35. package/tx/workers/expand.js +100 -96
  36. package/tx/workers/lookup.js +6 -0
  37. package/tx/workers/metadata.js +11 -6
  38. package/tx/workers/read.js +1 -1
  39. package/tx/workers/translate.js +20 -29
  40. package/tx/workers/validate.js +18 -10
  41. package/tx/workers/worker.js +134 -47
  42. package/tx/xversion/xv-bundle.js +1 -2
  43. package/tx/xversion/xv-codesystem.js +5 -2
  44. package/tx/xversion/xv-parameters.js +4 -4
  45. package/tx/xversion/xv-resource.js +2 -2
  46. package/tx/xversion/xv-terminologyCapabilities.js +11 -6
  47. package/tx/xversion/xv-valueset.js +7 -7
  48. package/publisher/task-draft.js +0 -463
  49. package/tx/data/OperationDefinition-ValueSet-related.json +0 -133
@@ -758,7 +758,7 @@ class RegistryModule {
758
758
  case '11000267109': edition = 'KR'; break;
759
759
  case '900000001000122104': edition = 'ES-ES'; break;
760
760
  case '2011000195101': edition = 'CH'; break;
761
- case '11000279109': edition = 'CX'; break;
761
+ case '11000279109': edition = 'CZ'; break;
762
762
  case '999000021000000109': edition = 'UK+Clinical'; break;
763
763
  case '5631000179106': edition = 'UY'; break;
764
764
  case '21000325107': edition = 'CL'; break;
@@ -976,7 +976,15 @@ class RegistryModule {
976
976
 
977
977
  try {
978
978
  const params = this._normalizeQueryParams(req.query);
979
- const {fhirVersion, url, valueSet, usage} = params;
979
+ const {fhirVersion, valueSet, usage, version} = params;
980
+ let {url} = params;
981
+
982
+ // If a version was supplied separately, fold it into the code system
983
+ // URL using the canonical url|version syntax that the resolver expects.
984
+ // Don't double up if the caller already encoded a version in the url.
985
+ if (version && url && !url.includes('|')) {
986
+ url = `${url}|${version}`;
987
+ }
980
988
 
981
989
  // Convert authoritativeOnly to boolean
982
990
  const authoritativeOnly = params.authoritativeOnly === 'true';
@@ -1219,6 +1227,7 @@ class RegistryModule {
1219
1227
  buildResolveFormContent(queryParams = {}) {
1220
1228
  const fhirVersion = queryParams.fhirVersion || '';
1221
1229
  const url = queryParams.url || '';
1230
+ const version = queryParams.version || '';
1222
1231
  const valueSet = queryParams.valueSet || '';
1223
1232
  const authoritativeOnly = queryParams.authoritativeOnly === 'true';
1224
1233
 
@@ -1233,7 +1242,7 @@ class RegistryModule {
1233
1242
  // FHIR Version field
1234
1243
  html += '<p>';
1235
1244
  html += '<label for="fhirVersion" class="form-label fw-bold">FHIR Version <span class="text-danger">*</span></label>';
1236
- html += `<input type="text" class="form-control" id="fhirVersion" name="fhirVersion" size="8"
1245
+ html += `<input type="text" class="form-control" id="fhirVersion" name="fhirVersion" size="8"
1237
1246
  value="${escape(fhirVersion)}" required>`;
1238
1247
  html += '</p>';
1239
1248
  html += '<p class="text-muted small">Examples: R4, 4.0.1, 5.0.0, etc.</p>';
@@ -1241,15 +1250,23 @@ class RegistryModule {
1241
1250
  html += '<div class="alert alert-info">Either Code System URL or Value Set URL must be provided:</div>';
1242
1251
  html += '<p>';
1243
1252
  html += '<label for="url" class="form-label fw-bold">Code System URL</label>';
1244
- html += `<input type="url" class="form-control" id="url" name="url"
1253
+ html += `<input type="url" class="form-control" id="url" name="url"
1245
1254
  value="${escape(url)}">`;
1246
1255
  html += '</p>';
1247
1256
  html += '<p class="text-muted small">Example: http://loinc.org</p>';
1248
1257
 
1258
+ // Code System Version field (optional)
1259
+ html += '<p>';
1260
+ html += '<label for="version" class="form-label fw-bold">Code System Version</label>';
1261
+ html += `<input type="text" class="form-control" id="version" name="version"
1262
+ value="${escape(version)}">`;
1263
+ html += '</p>';
1264
+ html += '<p class="text-muted small">Optional. Example: 2.74 (combined with the Code System URL as url|version)</p>';
1265
+
1249
1266
  // ValueSet URL field - now vertical
1250
1267
  html += '<p>';
1251
1268
  html += '<label for="valueSet" class="form-label fw-bold">Value Set URL</label>';
1252
- html += `<input type="url" class="form-control" id="valueSet" name="valueSet"
1269
+ html += `<input type="url" class="form-control" id="valueSet" name="valueSet"
1253
1270
  value="${escape(valueSet)}">`;
1254
1271
  html += '</p>';
1255
1272
  html += '<p class="text-muted small">Example: http://hl7.org/fhir/ValueSet/observation-codes</p>';
@@ -1257,7 +1274,7 @@ class RegistryModule {
1257
1274
 
1258
1275
  // Authoritative Only checkbox
1259
1276
  html += '<p>';
1260
- html += `<input type="checkbox" class="form-check-input" id="authoritativeOnly"
1277
+ html += `<input type="checkbox" class="form-check-input" id="authoritativeOnly"
1261
1278
  name="authoritativeOnly" value="true" ${authoritativeOnly ? 'checked' : ''}>`;
1262
1279
  html += '<label class="form-check-label" for="authoritativeOnly">&nbsp;Show only authoritative servers</label>';
1263
1280
  html += '</p>';
@@ -1276,7 +1293,7 @@ class RegistryModule {
1276
1293
  document.querySelector('form').addEventListener('submit', function(e) {
1277
1294
  const url = document.getElementById('url').value.trim();
1278
1295
  const valueSet = document.getElementById('valueSet').value.trim();
1279
-
1296
+
1280
1297
  if (!url && !valueSet) {
1281
1298
  e.preventDefault();
1282
1299
  alert('You must provide either a Code System URL or a Value Set URL');
package/server.js CHANGED
@@ -7,7 +7,7 @@
7
7
  //
8
8
 
9
9
  const express = require('express');
10
- // const cors = require('cors');
10
+ const cors = require('cors');
11
11
  const path = require('path');
12
12
  const fs = require('fs');
13
13
  const os = require('os');
@@ -82,7 +82,11 @@ app.use((req, res, next) => {
82
82
  next();
83
83
  });
84
84
 
85
- // app.use(cors(config.server.cors));
85
+ // Single, app-level CORS policy for all modules, driven by config.server.cors.
86
+ // This also handles OPTIONS preflight, so modules must NOT set their own
87
+ // Access-Control-* headers (doing so stacks a second CORS layer and produces
88
+ // duplicate, conflicting headers that browsers reject).
89
+ app.use(cors(config.server.cors));
86
90
 
87
91
  // Module instances
88
92
  const modules = {};
@@ -426,6 +430,11 @@ async function buildRootPageContent() {
426
430
  content += `<td style="background-color:${pctColor(processPCT)}"><strong>Process Memory:</strong> ${rssMB} MB of ${memLimitMB} MB (${processPCT.toFixed(0)}%)</td>`;
427
431
  content += `<td style="background-color:${pctColor(sysMemPCT)}"><strong>System Memory:</strong> ${usedMemMB} MB of ${totalMemMB} MB (${sysMemPCT.toFixed(0)}%)</td>`;
428
432
  content += '</tr>';
433
+ content += '<tr>';
434
+ content += `<td><strong>Expansion Cache:</strong> ${stats.expansionItems()} items</td>`;
435
+ content += `<td><strong>Client Cache:</strong> ${stats.clientCaches()} caches / ${stats.clientConcepts()} concepts</td>`;
436
+ content += `<td><strong>Max Client Cache:</strong> ${stats.maxClientCaches()} caches / ${stats.maxClientConcepts()} concepts</td>`;
437
+ content += '</tr>';
429
438
  content += getLogStats();
430
439
  content += '</table>';
431
440
 
package/stats.js CHANGED
@@ -36,24 +36,26 @@ class ServerStats {
36
36
  : (now - this.startTime) / 60000;
37
37
  const requestsPerMin = minutesSinceStart > 0 ? requestsDelta / minutesSinceStart : 0;
38
38
 
39
- const currentCpu = this.readSystemCpu();
40
- const idleDelta = currentCpu.idle - this.lastUsage.idle;
41
- const totalDelta = currentCpu.total - this.lastUsage.total;
42
- const percent = totalDelta > 0 ? 100 * (1 - idleDelta / totalDelta) : 0;
43
-
44
39
  const loopDelay = this.eventLoopMonitor.mean / 1e6;
45
- let cacheCount = 0;
40
+ // Two distinct caches: the expansion cache (count entries) and the client
41
+ // (resource) cache (count concepts held - a sense of how much it's carrying).
42
+ let expansionItems = 0;
43
+ let clientConcepts = 0;
46
44
  for (let m of this.cachingModules) {
47
- cacheCount = cacheCount + m.cacheCount();
45
+ if (typeof m.expansionItemCount === 'function') {
46
+ expansionItems = expansionItems + m.expansionItemCount();
47
+ }
48
+ if (typeof m.clientConceptCount === 'function') {
49
+ clientConcepts = clientConcepts + m.clientConceptCount();
50
+ }
48
51
  }
49
52
 
50
- this.history.push({time: now, mem: currentMem - this.startMem, rpm: requestsPerMin, tat: requestsTat, cpu: percent, block: loopDelay, cache : cacheCount});
53
+ this.history.push({time: now, mem: currentMem - this.startMem, rpm: requestsPerMin, tat: requestsTat, block: loopDelay, expansion: expansionItems, clientConcepts: clientConcepts});
51
54
 
52
55
  this.eventLoopMonitor.reset();
53
56
  this.requestCountSnapshot = combinedCount;
54
57
  this.requestTime = 0;
55
58
  this.lastTime = now;
56
- this.lastUsage = currentCpu;
57
59
 
58
60
  // Prune old data (keep 24 hours)
59
61
  const cutoff = now - (24 * 60 * 60 * 1000); // 24 hours ago
@@ -65,7 +67,6 @@ class ServerStats {
65
67
  this.started = true;
66
68
  this.startMem = process.memoryUsage().heapUsed;
67
69
  this.startTime = Date.now();
68
- this.lastUsage = this.readSystemCpu();
69
70
  this.lastTime = this.startTime;
70
71
  this.eventLoopMonitor = monitorEventLoopDelay({ resolution: 20 });
71
72
  this.eventLoopMonitor.enable();
@@ -141,17 +142,24 @@ class ServerStats {
141
142
  clearInterval(this.timer);
142
143
  }
143
144
 
144
- readSystemCpu() {
145
- const os = require('os');
146
- const cpus = os.cpus();
147
- let idle = 0, total = 0;
148
- for (const cpu of cpus) {
149
- idle += cpu.times.idle;
150
- total += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq;
145
+ // Live cache aggregates across all registered caching modules (defensive: a
146
+ // module that doesn't expose a given metric contributes 0).
147
+ _sumCachingModules(method) {
148
+ let total = 0;
149
+ for (let m of this.cachingModules) {
150
+ if (typeof m[method] === 'function') {
151
+ total = total + m[method]();
152
+ }
151
153
  }
152
- return { idle, total };
154
+ return total;
153
155
  }
154
156
 
157
+ expansionItems() { return this._sumCachingModules('expansionItemCount'); }
158
+ clientCaches() { return this._sumCachingModules('clientCacheCount'); }
159
+ clientConcepts() { return this._sumCachingModules('clientConceptCount'); }
160
+ maxClientCaches() { return this._sumCachingModules('maxClientCacheCount'); }
161
+ maxClientConcepts() { return this._sumCachingModules('maxClientConceptCount'); }
162
+
155
163
  getTaskColor(status) {
156
164
  switch (status) {
157
165
  case "started": return "LightGrey";
@@ -544,7 +544,7 @@ NO_VALID_DISPLAY_FOUND_LANG_NONE_other = ''{5}'' is the default display; the cod
544
544
  NO_VALID_DISPLAY_FOUND_LANG_SOME_one = ''{5}'' is the default display; no valid Display Names found for {1}#{2} in the language {4}
545
545
  NO_VALID_DISPLAY_FOUND_LANG_SOME_other = ''{5}'' is the default display; no valid Display Names found for {1}#{2} in the language {4}
546
546
  NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR = Wrong Display Name ''{0}'' for {1}#{2}. There are no valid display names found for language(s) ''{3}''. Default display is ''{4}''
547
- NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK = There are no valid display names found for the code {1}#{2} for language(s) ''{3}''. The display is ''{4}'' which is the default language display
547
+ NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK = There are no valid display names found for the code {1}#{2} for language(s) ''{3}''. The display is ''{0}'' which is a valid display for the default language
548
548
  NO_VALID_DISPLAY_FOUND_one = No valid Display Names found for {1}#{2} in the language {4}
549
549
  NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the languages {4}
550
550
  No_validator_configured = No validator configured
@@ -1100,6 +1100,7 @@ Unable_to_resolve_system__value_set_has_multiple_matches = The System URI could
1100
1100
  Unable_to_resolve_system__value_set_has_no_includes_or_expansion = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': value set has no includes or expansion
1101
1101
  UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE = The code system {1} referred to from value set {0} has a grammar, and the code might be valid in it
1102
1102
  Unable_to_resolve_value_Set_ = A definition for the value Set ''{0}'' could not be found
1103
+ CACHE_ID_UNKNOWN = The cache ''{0}'' is not known to this server. Caches are created with $cache-control?mode=start; this one was never created, or has expired or been released
1103
1104
  Unable_to_validate_code_without_using_server = Unable to validate code without using server because: {0}
1104
1105
  Undefined_attribute__on__for_type__properties__ = Undefined attribute ''@{0}'' on {1} for type {2}
1105
1106
  Undefined_element_ = Undefined element ''{0}'' at {1}
package/tx/cs/cs-cs.js CHANGED
@@ -1140,6 +1140,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1140
1140
  const allConcepts = this.codeSystem.getAllConcepts();
1141
1141
 
1142
1142
  for (const concept of allConcepts) {
1143
+ this.opContext.deadCheck('cs:searchFilter');
1143
1144
  const rating = this._calculateSearchRating(concept, searchTerm);
1144
1145
  if (rating > 0) {
1145
1146
  results.add(concept, rating);
@@ -1285,6 +1286,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1285
1286
 
1286
1287
  const allCodes = this.codeSystem.getAllCodes();
1287
1288
  for (const code of allCodes) {
1289
+ this.opContext.deadCheck('cs:conceptFilter:is-not-a');
1288
1290
  if (!excludeSet.has(code)) {
1289
1291
  const concept = this.codeSystem.getConceptByCode(code);
1290
1292
  if (concept) {
@@ -1316,6 +1318,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1316
1318
  const regex = regexUtilities.compile('^' + value + '$');
1317
1319
  const allCodes = this.codeSystem.getAllCodes();
1318
1320
  for (const code of allCodes) {
1321
+ this.opContext.deadCheck('cs:conceptFilter:regex');
1319
1322
  if (regex.test(code)) {
1320
1323
  const concept = this.codeSystem.getConceptByCode(code);
1321
1324
  if (concept) {
@@ -1349,6 +1352,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1349
1352
  }
1350
1353
  const descendants = this.codeSystem.getDescendants(ancestorCode);
1351
1354
  for (const code of descendants) {
1355
+ this.opContext.deadCheck('cs:addDescendants');
1352
1356
  if (code !== ancestorCode) {
1353
1357
  const concept = this.codeSystem.getConceptByCode(code);
1354
1358
  if (concept) {
@@ -1370,6 +1374,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1370
1374
  if (concept) {
1371
1375
  const descendants = this.codeSystem.getChildren(parentCode);
1372
1376
  for (const code of descendants) {
1377
+ this.opContext.deadCheck('cs:addChildren');
1373
1378
  if (code !== parentCode) { // should not be
1374
1379
  const concept = this.codeSystem.getConceptByCode(code);
1375
1380
  if (concept) {
@@ -1393,6 +1398,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1393
1398
 
1394
1399
  const allCodes = this.codeSystem.getAllCodes();
1395
1400
  for (const code of allCodes) {
1401
+ this.opContext.deadCheck('cs:childExistsFilter');
1396
1402
  const hasChildren = this.codeSystem.getChildren(code).length > 0;
1397
1403
  if (hasChildren === wantChildren) {
1398
1404
  const concept = this.codeSystem.getConceptByCode(code);
@@ -1419,6 +1425,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1419
1425
  const allConcepts = this.codeSystem.getAllConcepts();
1420
1426
 
1421
1427
  for (const concept of allConcepts) {
1428
+ this.opContext.deadCheck('cs:propertyFilter');
1422
1429
  if (this._conceptMatchesPropertyFilter(concept, propertyDef, op, value)) {
1423
1430
  results.add(concept, 0);
1424
1431
  }
@@ -1498,6 +1505,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1498
1505
  const allConcepts = this.codeSystem.getAllConcepts();
1499
1506
 
1500
1507
  for (const concept of allConcepts) {
1508
+ this.opContext.deadCheck('cs:knownPropertyFilter');
1501
1509
  let matches = false;
1502
1510
 
1503
1511
  if (prop === 'notSelectable') {
package/tx/cs/cs-loinc.js CHANGED
@@ -947,6 +947,7 @@ class LoincServices extends BaseCSServices {
947
947
  reject(err);
948
948
  } else {
949
949
  for (const row of rows) {
950
+ if (this.opContext) this.opContext.deadCheck('loinc:findRegexMatches');
950
951
  if (regex.test(row[valueColumn])) {
951
952
  matchingKeys.push(row[keyColumn]);
952
953
  }
@@ -5,7 +5,8 @@ const { AbstractCodeSystemProvider } = require('./cs-provider-api');
5
5
  */
6
6
  class ListCodeSystemProvider extends AbstractCodeSystemProvider {
7
7
  /**
8
- * {Map<String, CodeSystem>} A list of code system factories that contains all the preloaded native code systems
8
+ * {CodeSystem[]} The preloaded FHIR code systems in this list. This is an
9
+ * array — append with .push(), not Map-style .set().
9
10
  */
10
11
  codeSystems = [];
11
12