From b7b640ea1f0c7c9981885a250521a68e3656fb8f Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 19 Mar 2026 17:16:46 +1100 Subject: [PATCH 1/6] XIG: show using resource package explicitly --- xig/xig.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/xig/xig.js b/xig/xig.js index 0d9ee7d..3e6a7d0 100644 --- a/xig/xig.js +++ b/xig/xig.js @@ -1241,13 +1241,14 @@ function buildAdditionalForm(queryParams) { } } + // Add text search field and package filter field html += `Text: `; html += `Package: `; // Add submit button with 'only used' checkbox immediately before it const onlyUsedChecked = onlyUsed === 'true' ? ' checked' : ''; - html += ` Only Used `; + html += ` Only Show Used `; html += ''; html += ''; @@ -2747,11 +2748,14 @@ function buildDependencyTable(dependencies) { } currentType = dep.ResourceType; html += ''; - html += ``; + html += ``; } html += ''; + // Package column + html += ``; + // Build the link to the resource detail page const packagePid = dep.PID.replace(/#/g, '|'); // Convert # to | for URL const resourceUrl = `/xig/resource/${encodeURIComponent(packagePid)}/${encodeURIComponent(dep.ResourceType)}/${encodeURIComponent(dep.Id)}`; From f6ef0015ae3b383a19ded49c4e7b500651444267 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 19 Mar 2026 17:17:16 +1100 Subject: [PATCH 2/6] Check conformance statement production at start up --- tx/tx.js | 111 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 18 deletions(-) diff --git a/tx/tx.js b/tx/tx.js index 241bbb1..b72360f 100644 --- a/tx/tx.js +++ b/tx/tx.js @@ -196,6 +196,9 @@ class TXModule { } this.log.info(`TX module initialized with ${config.endpoints.length} endpoint(s)`); + + // Self-test: verify metadata generation works for each endpoint before accepting traffic + await this.selfTest(); } /** @@ -388,8 +391,8 @@ class TXModule { } if (contentType.includes('application/json') || - contentType.includes('application/fhir+json') || - contentType.includes('application/json+fhir')) { + contentType.includes('application/fhir+json') || + contentType.includes('application/json+fhir')) { // If body is a Buffer, parse it if (Buffer.isBuffer(req.body)) { @@ -731,11 +734,11 @@ class TXModule { router.get('/CodeSystem/:id/\\$validate-code', async (req, res) => { const start = Date.now(); try { - let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n); + let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n); await worker.handleCodeSystemInstance(req, res, this.log); - } finally { - this.countRequest('$validate', Date.now() - start); - } + } finally { + this.countRequest('$validate', Date.now() - start); + } }); router.post('/CodeSystem/:id/\\$validate-code', async (req, res) => { const start = Date.now(); @@ -745,7 +748,7 @@ class TXModule { } finally { this.countRequest('$validate', Date.now() - start); } - + }); // ValueSet/[id]/$validate-code @@ -964,6 +967,78 @@ class TXModule { }); } + /** + * Self-test: exercise CapabilityStatement and TerminologyCapabilities generation + * for each endpoint immediately after startup, throwing on any failure. + */ + async selfTest() { + this.log.info('Running startup self-test for metadata endpoints...'); + + for (const endpointInfo of this.endpoints) { + const label = `${endpointInfo.path} (FHIR v${endpointInfo.fhirVersion})`; + + // Build a minimal mock req/res that captures what metadataHandler.handle() produces + const makeMockReqRes = (mode) => { + const captured = { data: null, status: 200 }; + + const req = { + method: 'GET', + query: { mode }, + headers: {}, + get: (name) => null, + txEndpoint: endpointInfo, + txProvider: endpointInfo.provider, + }; + + const res = { + statusCode: 200, + status(code) { captured.status = code; return this; }, + setHeader() { return this; }, + json(data) { captured.data = data; return this; }, + send(data) { captured.data = data; return this; }, + }; + + return { req, res, captured }; + }; + + // Test 1: CapabilityStatement (/metadata with no mode, or mode=full) + try { + const { req, res, captured } = makeMockReqRes(undefined); + await this.metadataHandler.handle(req, res); + if (!captured.data) { + throw new Error('No response data returned'); + } + const rt = captured.data.resourceType; + if (rt !== 'CapabilityStatement') { + throw new Error(`Expected CapabilityStatement, got ${rt}`); + } + this.log.info(` [OK] CapabilityStatement for ${label}`); + } catch (err) { + this.log.error(` [FAIL] CapabilityStatement for ${label}: ${err.message}`); + throw new Error(`Startup self-test failed (CapabilityStatement, ${label}): ${err.message}`); + } + + // Test 2: TerminologyCapabilities (/metadata?mode=terminology) + try { + const { req, res, captured } = makeMockReqRes('terminology'); + await this.metadataHandler.handle(req, res); + if (!captured.data) { + throw new Error('No response data returned'); + } + const rt = captured.data.resourceType; + if (rt !== 'TerminologyCapabilities') { + throw new Error(`Expected TerminologyCapabilities, got ${rt}`); + } + this.log.info(` [OK] TerminologyCapabilities for ${label}`); + } catch (err) { + this.log.error(` [FAIL] TerminologyCapabilities for ${label}: ${err.message}`); + throw new Error(`Startup self-test failed (TerminologyCapabilities, ${label}): ${err.message}`); + } + } + + this.log.info('Startup self-test passed.'); + } + /** * Build an OperationOutcome for errors */ @@ -1077,21 +1152,21 @@ class TXModule { ec = 0; checkProperJson() { // jsonStr) { - // const errors = []; - // if (jsonStr.includes("[]")) errors.push("Found [] in json"); - // if (jsonStr.includes('""')) errors.push('Found "" in json'); - // - // if (errors.length > 0) { - // this.ec++; - // const filename = `/Users/grahamegrieve/temp/tx-err-log/err${this.ec}.json`; - // writeFileSync(filename, jsonStr); - // throw new Error(errors.join('; ')); - // } + // const errors = []; + // if (jsonStr.includes("[]")) errors.push("Found [] in json"); + // if (jsonStr.includes('""')) errors.push('Found "" in json'); + // + // if (errors.length > 0) { + // this.ec++; + // const filename = `/Users/grahamegrieve/temp/tx-err-log/err${this.ec}.json`; + // writeFileSync(filename, jsonStr); + // throw new Error(errors.join('; ')); + // } } transformResourceForVersion(data, fhirVersion) { if (fhirVersion == "5.0" || !data.resourceType) { - return data; + return data; } switch (data.resourceType) { case "CodeSystem": return codeSystemFromR5(data, fhirVersion); From c3787c01aeae75ccd96325e77bdbff426da471dc Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 19 Mar 2026 17:17:47 +1100 Subject: [PATCH 3/6] Load URI provider on tx.fhir.org --- tx/library.js | 7 +++++++ tx/tx.fhir.org.yml | 1 + 2 files changed, 8 insertions(+) diff --git a/tx/library.js b/tx/library.js index 56def72..48e523c 100644 --- a/tx/library.js +++ b/tx/library.js @@ -34,6 +34,7 @@ const {VSACValueSetProvider} = require("./vs/vs-vsac"); const { OCLCodeSystemProvider, OCLSourceCodeSystemFactory } = require('./ocl/cs-ocl'); const { OCLValueSetProvider } = require('./ocl/vs-ocl'); const { OCLConceptMapProvider } = require('./ocl/cm-ocl'); +const {UriServicesFactory} = require("./cs/cs-uri"); /** * This class holds all the loaded content ready for processing @@ -454,6 +455,12 @@ class Library { this.registerProvider('internal', hgvs); break; } + case "urls" : { + const urls = new UriServicesFactory(this.i18n); + await urls.load(); + this.registerProvider('internal', urls); + break; + } case "vsac" : { if (!this.vsacCfg || !this.vsacCfg.apiKey) { throw new Error("Unable to load VSAC provider unless vsacCfg is provided in the configuration"); diff --git a/tx/tx.fhir.org.yml b/tx/tx.fhir.org.yml index 2be270e..3170a41 100644 --- a/tx/tx.fhir.org.yml +++ b/tx/tx.fhir.org.yml @@ -8,6 +8,7 @@ sources: - internal:areacode - internal:mimetypes - internal:usstates + - internal:urls - internal:hgvs - ucum:tx/data/ucum-essence.xml - loinc:loinc-2.77-a.db From fcea8de80641977603edcc1e8ee9c33e36fe51dd Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 19 Mar 2026 17:18:26 +1100 Subject: [PATCH 4/6] fix error getting SCT version for html format --- tx/cs/cs-snomed.js | 2 +- tx/cs/cs-uri.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tx/cs/cs-snomed.js b/tx/cs/cs-snomed.js index 824a5d6..9ac1e59 100644 --- a/tx/cs/cs-snomed.js +++ b/tx/cs/cs-snomed.js @@ -1275,7 +1275,7 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider { id() { const match = this.version().match(/^http:\/\/snomed\.info\/sct\/(\d+)(?:\/version\/(\d{8}))?$/); - return "SCT-"+match[1]+"-"+match[2]; + return match && match[1] && match[2] ? "SCT-"+match[1]+"-"+match[2] : null; } describeVersion(version) { diff --git a/tx/cs/cs-uri.js b/tx/cs/cs-uri.js index 4d9eaac..ce8fb45 100644 --- a/tx/cs/cs-uri.js +++ b/tx/cs/cs-uri.js @@ -21,7 +21,7 @@ class UriServices extends CodeSystemProvider { } version() { - return 'n/a'; + return null; } description() { @@ -182,7 +182,7 @@ class UriServicesFactory extends CodeSystemFactoryProvider { } defaultVersion() { - return 'n/a'; + return null; } system() { @@ -190,7 +190,7 @@ class UriServicesFactory extends CodeSystemFactoryProvider { } version() { - return 'n/a'; + return null; } // eslint-disable-next-line no-unused-vars From 4aa9e183761183be6a46edc2d15cc685b6ade420 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 19 Mar 2026 17:19:10 +1100 Subject: [PATCH 5/6] set up release --- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ccf8f..15ef6ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to the Health Intersections Node Server will be documented i The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.7.4] - 2026-03-19 + +### Changed + +- XIG: show using resource package explicitly +- TX: Check conformance statement production at start up + +### Fixed +- TX: Load URI provider on tx.fhir.org +- TX: fix error getting SCT version for html format + +### Tx Conformance Statement + +FHIRsmith passed all 1452 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1-SNAPSHOT, runner v6.9.0) + ## [v0.7.3] - 2026-03-19 ### Changed diff --git a/package.json b/package.json index 6e00f1e..51fc316 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fhirsmith", - "version": "0.7.3", + "version": "0.7.4", "description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem", "main": "server.js", "engines": { From 106bd3f3a1f6638cedb3affed201547686e3c1ff Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 19 Mar 2026 17:24:51 +1100 Subject: [PATCH 6/6] lint fix --- tx/tx.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tx/tx.js b/tx/tx.js index b72360f..69c4e76 100644 --- a/tx/tx.js +++ b/tx/tx.js @@ -985,6 +985,7 @@ class TXModule { method: 'GET', query: { mode }, headers: {}, + // eslint-disable-next-line no-unused-vars get: (name) => null, txEndpoint: endpointInfo, txProvider: endpointInfo.provider,
${escape(currentType)}
${escape(currentType)}
${escape(dep.PID || '')}