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.
- package/CHANGELOG.md +51 -0
- package/library/folder-content-loader.js +91 -0
- package/npmprojector/npmprojector.js +2 -6
- package/package.json +1 -1
- package/packages/package-crawler.js +123 -6
- package/packages/packages.js +104 -0
- package/publisher/publisher.js +290 -12
- package/registry/crawler.js +85 -6
- package/registry/registry.js +24 -7
- package/server.js +11 -2
- package/stats.js +26 -18
- package/translations/Messages.properties +2 -1
- package/tx/cs/cs-cs.js +8 -0
- package/tx/cs/cs-loinc.js +1 -0
- package/tx/cs/cs-provider-list.js +2 -1
- package/tx/cs/cs-snomed.js +142 -59
- package/tx/data/snomed-testing.cache +0 -0
- package/tx/html/home-metrics.liquid +10 -10
- package/tx/library/canonical-resource.js +4 -2
- package/tx/library/codesystem.js +31 -0
- package/tx/library/conceptmap.js +24 -0
- package/tx/library/designations.js +27 -20
- package/tx/library/renderer.js +303 -22
- package/tx/library/ucum-types.js +4 -1
- package/tx/library/valueset.js +46 -0
- package/tx/library.js +65 -21
- package/tx/operation-context.js +122 -27
- package/tx/params.js +36 -8
- package/tx/provider.js +6 -3
- package/tx/tx-html.js +34 -0
- package/tx/tx.js +92 -30
- package/tx/vs/vs-vsac.js +157 -9
- package/tx/workers/cache-control.js +186 -0
- package/tx/workers/{related.js → compare.js} +27 -27
- package/tx/workers/expand.js +100 -96
- package/tx/workers/lookup.js +6 -0
- package/tx/workers/metadata.js +11 -6
- package/tx/workers/read.js +1 -1
- package/tx/workers/translate.js +20 -29
- package/tx/workers/validate.js +18 -10
- package/tx/workers/worker.js +134 -47
- package/tx/xversion/xv-bundle.js +1 -2
- package/tx/xversion/xv-codesystem.js +5 -2
- package/tx/xversion/xv-parameters.js +4 -4
- package/tx/xversion/xv-resource.js +2 -2
- package/tx/xversion/xv-terminologyCapabilities.js +11 -6
- package/tx/xversion/xv-valueset.js +7 -7
- package/publisher/task-draft.js +0 -463
- package/tx/data/OperationDefinition-ValueSet-related.json +0 -133
package/registry/registry.js
CHANGED
|
@@ -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 = '
|
|
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,
|
|
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"> 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
let
|
|
148
|
-
for (
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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 ''{
|
|
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
|
-
* {
|
|
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
|
|