fhirsmith 0.8.5 → 0.8.6
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 +24 -0
- package/README.md +52 -22
- package/extension-tracker/extension-tracker-template.html +3 -1
- package/library/html-server.js +7 -0
- package/library/logger.js +234 -194
- package/package.json +2 -1
- package/packages/packages-template.html +3 -1
- package/publisher/publisher-template.html +1 -0
- package/registry/registry-template.html +3 -1
- package/root-template.html +3 -2
- package/server.js +48 -12
- package/translations/Messages.properties +1 -1
- package/translations/rendering-phrases.properties +3 -1
- package/tx/cs/cs-api.js +4 -0
- package/tx/cs/cs-cs.js +5 -1
- package/tx/cs/cs-snomed.js +4 -0
- package/tx/html/tx-template.html +3 -2
- package/tx/library/codesystem.js +4 -0
- package/tx/library/renderer.js +20 -4
- package/tx/operation-context.js +74 -19
- package/tx/tx-html.js +5 -5
- package/tx/tx.js +1 -0
- package/tx/workers/expand.js +149 -112
- package/tx/workers/metadata.js +3 -2
- package/tx/workers/read.js +6 -3
- package/tx/workers/related.js +228 -87
- package/xig/xig-template.html +3 -1
- package/library/logger-telnet.js +0 -205
package/server.js
CHANGED
|
@@ -28,7 +28,7 @@ try {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const Logger = require('./library/logger');
|
|
31
|
-
const serverLog = Logger.getInstance().child({ module: 'server' });
|
|
31
|
+
const serverLog = Logger.getInstance(config.logging || {}).child({ module: 'server' });
|
|
32
32
|
const packageJson = require('./package.json');
|
|
33
33
|
|
|
34
34
|
// Startup banner
|
|
@@ -64,6 +64,7 @@ const FolderModule = require("./folder/folder");
|
|
|
64
64
|
const ExtensionTrackerModule = require("./extension-tracker/extension-tracker");
|
|
65
65
|
|
|
66
66
|
htmlServer.useLog(serverLog);
|
|
67
|
+
htmlServer.setSponsorMessage(config.sponsorMessage ? config.sponsorMessage : '');
|
|
67
68
|
|
|
68
69
|
const app = express();
|
|
69
70
|
|
|
@@ -386,22 +387,44 @@ async function buildRootPageContent() {
|
|
|
386
387
|
|
|
387
388
|
// Memory usage
|
|
388
389
|
const memUsage = process.memoryUsage();
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
390
|
+
const heapStats = v8.getHeapStatistics();
|
|
391
|
+
|
|
392
|
+
// V8 heap: used vs limit
|
|
393
|
+
const v8UsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(0);
|
|
394
|
+
const v8LimitMB = (heapStats.heap_size_limit / 1024 / 1024).toFixed(0);
|
|
395
|
+
const v8PCT = (memUsage.heapUsed * 100) / heapStats.heap_size_limit;
|
|
396
|
+
|
|
397
|
+
// Process RSS vs cgroup limit (or system total)
|
|
398
|
+
const rssMB = (memUsage.rss / 1024 / 1024).toFixed(0);
|
|
399
|
+
let memLimit;
|
|
400
|
+
try {
|
|
401
|
+
const raw = fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
|
|
402
|
+
memLimit = raw === 'max' ? os.totalmem() : parseInt(raw);
|
|
403
|
+
} catch {
|
|
404
|
+
memLimit = os.totalmem();
|
|
405
|
+
}
|
|
406
|
+
const memLimitMB = (memLimit / 1024 / 1024).toFixed(0);
|
|
407
|
+
const processPCT = (memUsage.rss * 100) / memLimit;
|
|
408
|
+
|
|
409
|
+
// System memory
|
|
393
410
|
const totalMemMB = (os.totalmem() / 1024 / 1024).toFixed(0);
|
|
411
|
+
const usedMemMB = ((os.totalmem() - os.freemem()) / 1024 / 1024).toFixed(0);
|
|
412
|
+
const sysMemPCT = ((os.totalmem() - os.freemem()) * 100) / os.totalmem();
|
|
413
|
+
|
|
414
|
+
// Average requests per minute
|
|
415
|
+
const uptimeMinutesTotal = uptimeMs / 60000;
|
|
416
|
+
const avgReqPerMin = uptimeMinutesTotal > 0 ? (stats.requestCount / uptimeMinutesTotal).toFixed(1) : '0.0';
|
|
394
417
|
|
|
395
418
|
content += '<table class="grid">';
|
|
396
419
|
content += '<tr>';
|
|
397
420
|
content += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
|
|
398
421
|
content += `<td><strong>Request Count:</strong> ${stats.requestCount} (static: ${stats.staticRequestCount})</td>`;
|
|
399
|
-
content += `<td><strong>
|
|
422
|
+
content += `<td><strong>Avg Requests/min:</strong> ${avgReqPerMin}</td>`;
|
|
400
423
|
content += '</tr>';
|
|
401
424
|
content += '<tr>';
|
|
402
|
-
content += `<td><strong>
|
|
403
|
-
content += `<td><strong>
|
|
404
|
-
content += `<td><strong>
|
|
425
|
+
content += `<td style="background-color:${pctColor(v8PCT)}"><strong>V8 Memory:</strong> ${v8UsedMB} MB of ${v8LimitMB} MB (${v8PCT.toFixed(0)}%)</td>`;
|
|
426
|
+
content += `<td style="background-color:${pctColor(processPCT)}"><strong>Process Memory:</strong> ${rssMB} MB of ${memLimitMB} MB (${processPCT.toFixed(0)}%)</td>`;
|
|
427
|
+
content += `<td style="background-color:${pctColor(sysMemPCT)}"><strong>System Memory:</strong> ${usedMemMB} MB of ${totalMemMB} MB (${sysMemPCT.toFixed(0)}%)</td>`;
|
|
405
428
|
content += '</tr>';
|
|
406
429
|
content += getLogStats();
|
|
407
430
|
content += '</table>';
|
|
@@ -543,10 +566,24 @@ app.get('/dashboard', async (req, res) => {
|
|
|
543
566
|
// Memory usage
|
|
544
567
|
const memUsage = process.memoryUsage();
|
|
545
568
|
const heapStats = v8.getHeapStatistics();
|
|
546
|
-
const
|
|
569
|
+
const v8PCT = (memUsage.heapUsed * 100) / heapStats.heap_size_limit; // % of V8 heap limit used
|
|
570
|
+
|
|
571
|
+
// Process RSS as % of cgroup memory limit (or system total as fallback)
|
|
572
|
+
let memLimit;
|
|
573
|
+
try {
|
|
574
|
+
const raw = fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
|
|
575
|
+
memLimit = raw === 'max' ? os.totalmem() : parseInt(raw);
|
|
576
|
+
} catch {
|
|
577
|
+
memLimit = os.totalmem();
|
|
578
|
+
}
|
|
579
|
+
const processPCT = (memUsage.rss * 100) / memLimit;
|
|
580
|
+
|
|
547
581
|
const totalMemBytes = os.totalmem();
|
|
548
582
|
const freeMemBytes = os.freemem();
|
|
549
583
|
const sysMemPCT = ((totalMemBytes - freeMemBytes) * 100) / totalMemBytes; // % of system memory used
|
|
584
|
+
|
|
585
|
+
const memMaxPCT = Math.max(v8PCT, processPCT, sysMemPCT);
|
|
586
|
+
|
|
550
587
|
const fstats = fs.statfsSync(folders.logsDir());
|
|
551
588
|
const diskPCT = 100 - ((fstats.bavail * 100) / fstats.blocks); // % of disk used
|
|
552
589
|
|
|
@@ -555,8 +592,7 @@ app.get('/dashboard', async (req, res) => {
|
|
|
555
592
|
content += '<tr>';
|
|
556
593
|
content += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
|
|
557
594
|
content += `<td><strong>Request Count:</strong> ${stats.requestCount} (static: ${stats.staticRequestCount})</td>`;
|
|
558
|
-
content += `<td style="background-color:${pctColor(
|
|
559
|
-
content += `<td style="background-color:${pctColor(sysMemPCT)}"><strong>System Memory:</strong> ${sysMemPCT.toFixed(0)}%</td>`;
|
|
595
|
+
content += `<td style="background-color:${pctColor(memMaxPCT)}"><strong>Memory:</strong> ${v8PCT.toFixed(0)}% V8, ${processPCT.toFixed(0)}% Process, ${sysMemPCT.toFixed(0)}% System</td>`;
|
|
560
596
|
content += `<td style="background-color:${pctColor(diskPCT)}"><strong>Disk:</strong> ${diskPCT.toFixed(0)}%</td>`;
|
|
561
597
|
content += '</tr>';
|
|
562
598
|
content += '</table>';
|
|
@@ -1274,7 +1274,7 @@ VALUESET_SHAREABLE_MISSING = Published value sets SHOULD conform to the Shareabl
|
|
|
1274
1274
|
VALUESET_SHAREABLE_MISSING_HL7 = Value sets published by HL7 SHALL conform to the ShareableValueSet profile, which says that the element ValueSet.{0} is mandatory, but it is not present
|
|
1275
1275
|
VALUESET_SUPPLEMENT_MISSING_one = Required supplement not found: {1}
|
|
1276
1276
|
VALUESET_SUPPLEMENT_MISSING_other = Required supplements not found: {1}
|
|
1277
|
-
VALUESET_TOO_COSTLY = The value set ''{0}'' expansion has too many codes to
|
|
1277
|
+
VALUESET_TOO_COSTLY = The value set ''{0}'' expansion has too many codes to produce ({1})
|
|
1278
1278
|
VALUESET_TOO_COSTLY_COUNT = The value set ''{0}'' expansion has {2} codes, which is too many to display ({1})
|
|
1279
1279
|
VALUESET_TOO_COSTLY_TIME = The value set ''{0}'' {2} took too long to process (>{1}sec)
|
|
1280
1280
|
VALUESET_UNC_SYSTEM_WARNING = Unknown System ''{0}'' specified, so Concepts and Filters can''t be checked (Details: {1})
|
|
@@ -1214,4 +1214,6 @@ CONSENT_HT_RESOURCE_TYPE = Resource Type
|
|
|
1214
1214
|
CONSENT_HT_DOC_TYPE = Document Type
|
|
1215
1215
|
CONSENT_HT_CODE = Code
|
|
1216
1216
|
WEB_SOURCE = Web Source
|
|
1217
|
-
GENERAL_COPY = Click to Copy
|
|
1217
|
+
GENERAL_COPY = Click to Copy
|
|
1218
|
+
CODESYSTEM_LVL = Level
|
|
1219
|
+
FEATURE = Feature
|
package/tx/cs/cs-api.js
CHANGED
package/tx/cs/cs-cs.js
CHANGED
|
@@ -1521,7 +1521,11 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1521
1521
|
}
|
|
1522
1522
|
|
|
1523
1523
|
versionNeeded() {
|
|
1524
|
-
return this.codeSystem.jsonObj.versionNeeded;
|
|
1524
|
+
return this.codeSystem.jsonObj.versionNeeded ? true : false;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
hasMultiHierarchy() {
|
|
1528
|
+
return this.codeSystem.hasMultiHierarchy;
|
|
1525
1529
|
}
|
|
1526
1530
|
|
|
1527
1531
|
}
|
package/tx/cs/cs-snomed.js
CHANGED
package/tx/html/tx-template.html
CHANGED
|
@@ -117,9 +117,10 @@
|
|
|
117
117
|
<div class="container">
|
|
118
118
|
<div class="inner-wrapper">
|
|
119
119
|
<p>
|
|
120
|
-
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org 2011+. |
|
|
120
|
+
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> v[%fhir-version%] © HL7.org 2011+. |
|
|
121
121
|
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
122
|
-
|
|
122
|
+
([%ms%] ms)
|
|
123
|
+
[%sponsorMessage%]
|
|
123
124
|
</p>
|
|
124
125
|
</div>
|
|
125
126
|
</div>
|
package/tx/library/codesystem.js
CHANGED
|
@@ -74,6 +74,8 @@ class CodeSystem extends CanonicalResource {
|
|
|
74
74
|
*/
|
|
75
75
|
childToParentsMap = new Map();
|
|
76
76
|
|
|
77
|
+
hasMultiHierarchy = false;
|
|
78
|
+
|
|
77
79
|
/**
|
|
78
80
|
* Static factory method for convenience
|
|
79
81
|
* @param {string} jsonString - JSON string representation of CodeSystem
|
|
@@ -326,6 +328,8 @@ class CodeSystem extends CanonicalResource {
|
|
|
326
328
|
|
|
327
329
|
// Third pass: handle nested concept structures
|
|
328
330
|
this._buildNestedHierarchy(this.jsonObj.concept);
|
|
331
|
+
|
|
332
|
+
this.hasMultiHierarchy = Array.from(this.childToParentsMap.values()).some(parents => parents.length > 1);
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
/**
|
package/tx/library/renderer.js
CHANGED
|
@@ -133,7 +133,7 @@ class Renderer {
|
|
|
133
133
|
return result;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
async renderMetadataTable(res, tbl) {
|
|
136
|
+
async renderMetadataTable(res, tbl, sourcePackage) {
|
|
137
137
|
this.renderMetadataVersion(res, tbl);
|
|
138
138
|
await this.renderMetadataProfiles(res, tbl);
|
|
139
139
|
this.renderMetadataTags(res, tbl);
|
|
@@ -154,6 +154,11 @@ class Renderer {
|
|
|
154
154
|
this.renderProperty(tbl, 'EXT_FMM_LEVEL', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm'));
|
|
155
155
|
this.renderProperty(tbl, 'PAT_PERIOD', res.effectivePeriod);
|
|
156
156
|
this.renderPropertyLink(tbl, 'WEB_SOURCE', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/web-source'));
|
|
157
|
+
this.renderProperty(tbl, 'GENERAL_SOURCE', sourcePackage);
|
|
158
|
+
for (let ext of Extensions.list(res, 'http://hl7.org/fhir/uv/application-feature/StructureDefinition/feature')) {
|
|
159
|
+
this.renderProperty(tbl, 'FEATURE', this.featureSummary(ext));
|
|
160
|
+
}
|
|
161
|
+
|
|
157
162
|
|
|
158
163
|
// capability statement things
|
|
159
164
|
this.renderProperty(tbl, 'Kind', res.kind);
|
|
@@ -165,7 +170,6 @@ class Renderer {
|
|
|
165
170
|
}
|
|
166
171
|
}
|
|
167
172
|
this.renderProperty(tbl, 'GENERAL_URL', res.implementation?.url);
|
|
168
|
-
this.renderProperty(tbl, 'Kind', res.kind);
|
|
169
173
|
this.renderProperty(tbl, 'EX_SCEN_FVER', res.fhirVersion);
|
|
170
174
|
|
|
171
175
|
if (res.content === 'supplement' && res.supplements) {
|
|
@@ -364,7 +368,7 @@ class Renderer {
|
|
|
364
368
|
return div_.toString();
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
async renderCodeSystem(cs) {
|
|
371
|
+
async renderCodeSystem(cs, sourcePackage) {
|
|
368
372
|
if (cs.json) {
|
|
369
373
|
cs = cs.json;
|
|
370
374
|
}
|
|
@@ -373,7 +377,7 @@ class Renderer {
|
|
|
373
377
|
|
|
374
378
|
// Metadata table
|
|
375
379
|
div_.h3().tx("Properties");
|
|
376
|
-
await this.renderMetadataTable(cs, div_.table("grid"));
|
|
380
|
+
await this.renderMetadataTable(cs, div_.table("grid"), sourcePackage);
|
|
377
381
|
|
|
378
382
|
// Code system properties
|
|
379
383
|
const hasProps = this.generateProperties(div_, cs);
|
|
@@ -2227,6 +2231,18 @@ class Renderer {
|
|
|
2227
2231
|
}
|
|
2228
2232
|
}
|
|
2229
2233
|
|
|
2234
|
+
featureSummary(ext) {
|
|
2235
|
+
let defn = Extensions.readString(ext, 'definition');
|
|
2236
|
+
let value = Extensions.readString(ext, 'value');
|
|
2237
|
+
switch (defn) {
|
|
2238
|
+
case 'http://hl7.org/fhir/uv/tx-tests/FeatureDefinition/test-version':
|
|
2239
|
+
return 'Tx Test version = ' + value;
|
|
2240
|
+
case 'http://hl7.org/fhir/uv/tx-ecosystem/FeatureDefinition/CodeSystemAsParameter':
|
|
2241
|
+
return 'CodeSystems as parameters = ' + value;
|
|
2242
|
+
default:
|
|
2243
|
+
return defn+' = ' + value;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2230
2246
|
}
|
|
2231
2247
|
|
|
2232
2248
|
module.exports = { Renderer };
|
package/tx/operation-context.js
CHANGED
|
@@ -15,7 +15,7 @@ function isDebugging() {
|
|
|
15
15
|
}
|
|
16
16
|
// Also check for debug flags in case inspector not yet attached
|
|
17
17
|
return process.execArgv.some(arg =>
|
|
18
|
-
|
|
18
|
+
arg.includes('--inspect') || arg.includes('--debug')
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -233,16 +233,16 @@ class ExpansionCache {
|
|
|
233
233
|
// Resources are now CodeSystem/ValueSet wrappers, not raw JSON
|
|
234
234
|
if (additionalResources && additionalResources.length > 0) {
|
|
235
235
|
const resourceHashes = additionalResources
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
236
|
+
.map(r => {
|
|
237
|
+
// Get the JSON object from wrapper or use directly
|
|
238
|
+
const json = r.jsonObj || r;
|
|
239
|
+
// Create a content hash for this resource
|
|
240
|
+
return crypto.createHash('sha256')
|
|
241
|
+
.update(JSON.stringify(json))
|
|
242
|
+
.digest('hex')
|
|
243
|
+
.substring(0, 16); // Use first 16 chars for brevity
|
|
244
|
+
})
|
|
245
|
+
.sort();
|
|
246
246
|
keyParts.push(`additional:${resourceHashes.join(',')}`);
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -314,7 +314,7 @@ class ExpansionCache {
|
|
|
314
314
|
|
|
315
315
|
// Get entries sorted by lastUsed (oldest first)
|
|
316
316
|
const entries = Array.from(this.cache.entries())
|
|
317
|
-
|
|
317
|
+
.sort((a, b) => a[1].lastUsed - b[1].lastUsed);
|
|
318
318
|
|
|
319
319
|
const toEvict = Math.min(count, entries.length);
|
|
320
320
|
for (let i = 0; i < toEvict; i++) {
|
|
@@ -413,7 +413,29 @@ class ExpansionCache {
|
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Read the cgroup memory limit once at startup.
|
|
418
|
+
* Returns the byte limit, or 0 if unavailable (disables the check).
|
|
419
|
+
*/
|
|
420
|
+
function readMemoryLimit() {
|
|
421
|
+
try {
|
|
422
|
+
const raw = require('fs').readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
|
|
423
|
+
if (raw === 'max') return 0; // no cgroup limit
|
|
424
|
+
return parseInt(raw);
|
|
425
|
+
} catch {
|
|
426
|
+
return 0; // not on Linux / no cgroup
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const MEMORY_LIMIT = readMemoryLimit();
|
|
431
|
+
const MEMORY_FRACTION = 0.98;
|
|
432
|
+
const MEMORY_THRESHOLD = MEMORY_LIMIT > 0 ? MEMORY_LIMIT * MEMORY_FRACTION : 0; // 90% of cgroup limit
|
|
433
|
+
const CHECK_FREQUENCY = 100;
|
|
434
|
+
|
|
416
435
|
class OperationContext {
|
|
436
|
+
// Shared counter across all instances — only check RSS every CHECK_FREQUENCY calls
|
|
437
|
+
static _checkCounter = 0;
|
|
438
|
+
|
|
417
439
|
constructor(langs, i18n = null, id = null, timeLimit = 30, resourceCache = null, expansionCache = null) {
|
|
418
440
|
this.i18n = i18n;
|
|
419
441
|
this.langs = this._ensureLanguages(langs);
|
|
@@ -445,8 +467,8 @@ class OperationContext {
|
|
|
445
467
|
*/
|
|
446
468
|
copy() {
|
|
447
469
|
const newContext = new OperationContext(
|
|
448
|
-
|
|
449
|
-
|
|
470
|
+
this.langs, this.i18n, this.id, this.timeLimit / 1000,
|
|
471
|
+
this.resourceCache, this.expansionCache
|
|
450
472
|
);
|
|
451
473
|
newContext.contexts = [...this.contexts];
|
|
452
474
|
newContext.startTime = this.startTime;
|
|
@@ -458,31 +480,64 @@ class OperationContext {
|
|
|
458
480
|
}
|
|
459
481
|
|
|
460
482
|
/**
|
|
461
|
-
* Check if operation has exceeded time limit
|
|
483
|
+
* Check if operation has exceeded time limit, or is pushing is over the memory limit
|
|
462
484
|
* Skipped when running under debugger
|
|
485
|
+
*
|
|
486
|
+
* note: if the server pushes over the memory limit for the process, the process is terminated.
|
|
487
|
+
* the memory check here is intended to prevent process termination on the grounds that some
|
|
488
|
+
* big operation is pushing the limit. It might not be the big operation that is terminated first,
|
|
489
|
+
* but eventually it'll get terminated.
|
|
490
|
+
*
|
|
491
|
+
* this is called a *lot* so it's important to be efficient. Only check every CHECK_FREQUENCY
|
|
492
|
+
* times means that there could be a small overrun, but it's called often enough that the
|
|
493
|
+
* overrun won't be that signiifcant
|
|
494
|
+
*
|
|
463
495
|
* @param {string} place - Location identifier for debugging
|
|
464
496
|
* @returns {boolean} true if operation should be terminated
|
|
465
497
|
*/
|
|
466
498
|
deadCheck(place = 'unknown') {
|
|
467
|
-
// Skip time limit checks when debugging
|
|
468
499
|
if (this.debugging) {
|
|
469
500
|
return false;
|
|
470
501
|
}
|
|
471
502
|
|
|
472
|
-
|
|
503
|
+
OperationContext._checkCounter++;
|
|
504
|
+
if (OperationContext._checkCounter < CHECK_FREQUENCY) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
OperationContext._checkCounter = 0;
|
|
473
508
|
|
|
509
|
+
// Time check
|
|
510
|
+
const elapsed = performance.now() - this.startTime;
|
|
474
511
|
if (elapsed > this.timeLimit) {
|
|
475
512
|
const timeInSeconds = Math.round(this.timeLimit / 1000);
|
|
476
513
|
this.log(`Operation took too long @ ${place} (${this.constructor.name})`);
|
|
477
|
-
|
|
478
|
-
|
|
514
|
+
const error = new Issue("error", "too-costly", null,
|
|
515
|
+
`Operation exceeded time limit of ${timeInSeconds} seconds at ${place}`);
|
|
479
516
|
error.diagnostics = this.diagnostics();
|
|
480
517
|
throw error;
|
|
481
518
|
}
|
|
482
519
|
|
|
520
|
+
// Memory check (piggyback on same sample)
|
|
521
|
+
if (MEMORY_THRESHOLD > 0) {
|
|
522
|
+
const rss = process.memoryUsage.rss();
|
|
523
|
+
if (rss > MEMORY_THRESHOLD) {
|
|
524
|
+
const usedGB = (rss / 1024 / 1024 / 1024).toFixed(1);
|
|
525
|
+
const limitGB = (MEMORY_LIMIT / 1024 / 1024 / 1024).toFixed(1);
|
|
526
|
+
this.log(`Memory Limit: ${usedGB} GB of ${limitGB} GB limit @ ${place}`);
|
|
527
|
+
const error = new Issue("error", "too-costly", null,
|
|
528
|
+
`Operation aborted: server memory usage (${usedGB} GB) exceeds safe threshold (${MEMORY_FRACTION * 100}% of ${limitGB} GB limit) at ${place}`);
|
|
529
|
+
error.diagnostics = this.diagnostics();
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
483
534
|
return false;
|
|
484
535
|
}
|
|
485
536
|
|
|
537
|
+
unSeeAll() {
|
|
538
|
+
this.contexts = [];
|
|
539
|
+
}
|
|
540
|
+
|
|
486
541
|
/**
|
|
487
542
|
* Track a context URL and detect circular references
|
|
488
543
|
* @param {string} vurl - Value set URL to track
|
package/tx/tx-html.js
CHANGED
|
@@ -319,7 +319,7 @@ class TxHtmlRenderer {
|
|
|
319
319
|
case 'Parameters':
|
|
320
320
|
return await this.renderParameters(json);
|
|
321
321
|
case 'CodeSystem':
|
|
322
|
-
return await this.renderCodeSystem(json, inBundle, _fmt, op);
|
|
322
|
+
return await this.renderCodeSystem(json, inBundle, _fmt, op, req.sourcePackage);
|
|
323
323
|
case 'ValueSet': {
|
|
324
324
|
let exp = undefined;
|
|
325
325
|
if (!inBundle && !op && (!_fmt || _fmt == 'html')) {
|
|
@@ -330,10 +330,10 @@ class TxHtmlRenderer {
|
|
|
330
330
|
exp = error;
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
-
return await this.renderValueSet(json, inBundle, _fmt, op, exp);
|
|
333
|
+
return await this.renderValueSet(json, inBundle, _fmt, op, exp, req.sourcePackage);
|
|
334
334
|
}
|
|
335
335
|
case 'ConceptMap':
|
|
336
|
-
return await this.renderConceptMap(json, inBundle, _fmt, op);
|
|
336
|
+
return await this.renderConceptMap(json, inBundle, _fmt, op, req.sourcePackage);
|
|
337
337
|
case 'CapabilityStatement':
|
|
338
338
|
return await this.renderCapabilityStatement(json, inBundle);
|
|
339
339
|
case 'TerminologyCapabilities':
|
|
@@ -632,7 +632,7 @@ class TxHtmlRenderer {
|
|
|
632
632
|
/**
|
|
633
633
|
* Render CodeSystem resource
|
|
634
634
|
*/
|
|
635
|
-
async renderCodeSystem(json, inBundle, _fmt) {
|
|
635
|
+
async renderCodeSystem(json, inBundle, _fmt, op, sourcePackage) {
|
|
636
636
|
if (inBundle) {
|
|
637
637
|
return await this.renderResourceWithNarrative(json, await this.renderer.renderCodeSystem(json));
|
|
638
638
|
} else {
|
|
@@ -645,7 +645,7 @@ class TxHtmlRenderer {
|
|
|
645
645
|
html += `</ul>`;
|
|
646
646
|
|
|
647
647
|
if (!_fmt || _fmt == 'html') {
|
|
648
|
-
html += await this.renderResourceWithNarrative(json, await this.renderer.renderCodeSystem(json));
|
|
648
|
+
html += await this.renderResourceWithNarrative(json, await this.renderer.renderCodeSystem(json, sourcePackage));
|
|
649
649
|
} else if (_fmt == "html/json") {
|
|
650
650
|
html += await this.renderResourceJson(json);
|
|
651
651
|
} else if (_fmt == "html/xml") {
|
package/tx/tx.js
CHANGED
|
@@ -173,6 +173,7 @@ class TXModule {
|
|
|
173
173
|
this.metadataHandler = new MetadataHandler({
|
|
174
174
|
baseUrl: config.baseUrl,
|
|
175
175
|
serverVersion: packageJson.version,
|
|
176
|
+
txVersion: packageJson.txVersion,
|
|
176
177
|
softwareName: config.softwareName || 'FHIRsmith',
|
|
177
178
|
name: config.name || 'FHIRTerminologyServer',
|
|
178
179
|
title: config.title || 'FHIR Terminology Server',
|