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/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 heapUsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(2);
390
- const heapAvailableMB = ((memUsage.heapTotal - memUsage.heapUsed) / 1024 / 1024).toFixed(2);
391
- const rssMB = (memUsage.rss / 1024 / 1024).toFixed(2);
392
- const freeMemMB = (os.freemem() / 1024 / 1024).toFixed(0);
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>Free Memory:</strong> ${freeMemMB} MB of ${totalMemMB} MB</td>`;
422
+ content += `<td><strong>Avg Requests/min:</strong> ${avgReqPerMin}</td>`;
400
423
  content += '</tr>';
401
424
  content += '<tr>';
402
- content += `<td><strong>Heap Used:</strong> ${heapUsedMB} MB</td>`;
403
- content += `<td><strong>Heap Available:</strong> ${heapAvailableMB} MB</td>`;
404
- content += `<td><strong>Process Memory:</strong> ${rssMB} MB</td>`;
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 nodeMemPCT = (memUsage.heapUsed * 100) / heapStats.heap_size_limit; // % of Node.js memory limit used
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(nodeMemPCT)}"><strong>Node Memory:</strong> ${nodeMemPCT.toFixed(0)}%</td>`;
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 display ({1})
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
@@ -719,7 +719,11 @@ class CodeSystemProvider {
719
719
  return false;
720
720
  }
721
721
 
722
+ hasMultiHierarchy() {
723
+ return false;
724
+ }
722
725
  /**
726
+ *
723
727
  * @returns {string} valueset for the code system
724
728
  */
725
729
  valueSet() {
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
  }
@@ -1348,6 +1348,10 @@ class SnomedProvider extends BaseCSServices {
1348
1348
  return result;
1349
1349
  }
1350
1350
 
1351
+ hasMultiHierarchy() {
1352
+ return true;
1353
+ }
1354
+
1351
1355
  }
1352
1356
 
1353
1357
  /**
@@ -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> &copy; HL7.org 2011+. &nbsp;|&nbsp;
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%] &copy; HL7.org 2011+. &nbsp;|&nbsp;
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%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
122
- FHIR Version [%fhir-version%] &nbsp;|&nbsp; ([%ms%] ms)
122
+ &nbsp; ([%ms%] ms)
123
+ [%sponsorMessage%]
123
124
  </p>
124
125
  </div>
125
126
  </div>
@@ -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
  /**
@@ -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 };
@@ -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
- arg.includes('--inspect') || arg.includes('--debug')
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
- .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();
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
- .sort((a, b) => a[1].lastUsed - b[1].lastUsed);
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
- this.langs, this.i18n, this.id, this.timeLimit / 1000,
449
- this.resourceCache, this.expansionCache
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
- const elapsed = performance.now() - this.startTime;
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
- const error = new Issue("error", "too-costly", null, `Operation exceeded time limit of ${timeInSeconds} seconds at ${place}`);
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',