fhirsmith 0.5.6 → 0.7.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 (78) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -0
  3. package/configurations/projector.json +21 -0
  4. package/configurations/readme.md +5 -0
  5. package/library/html-server.js +2 -1
  6. package/library/package-manager.js +37 -34
  7. package/library/utilities.js +10 -1
  8. package/library/version-utilities.js +85 -0
  9. package/package.json +1 -1
  10. package/packages/package-crawler.js +144 -52
  11. package/packages/packages.js +15 -7
  12. package/publisher/publisher.js +15 -3
  13. package/registry/api.js +173 -191
  14. package/registry/crawler.js +100 -65
  15. package/registry/model.js +14 -8
  16. package/registry/registry.js +5 -0
  17. package/root-template.html +1 -0
  18. package/server.js +113 -45
  19. package/tx/README.md +4 -4
  20. package/tx/cs/cs-api.js +18 -1
  21. package/tx/cs/cs-base.js +1 -0
  22. package/tx/cs/cs-loinc.js +5 -2
  23. package/tx/cs/cs-provider-api.js +25 -1
  24. package/tx/cs/cs-provider-list.js +2 -2
  25. package/tx/cs/cs-rxnorm.js +9 -2
  26. package/tx/cs/cs-snomed.js +17 -2
  27. package/tx/html/codesystem-operations.liquid +17 -24
  28. package/tx/html/valueset-operations.liquid +46 -52
  29. package/tx/library/canonical-resource.js +6 -1
  30. package/tx/library/codesystem.js +6 -1
  31. package/tx/library/renderer.js +81 -7
  32. package/tx/library.js +145 -13
  33. package/tx/ocl/README.md +236 -0
  34. package/tx/ocl/cache/cache-paths.cjs +32 -0
  35. package/tx/ocl/cache/cache-paths.js +2 -0
  36. package/tx/ocl/cache/cache-utils.cjs +43 -0
  37. package/tx/ocl/cache/cache-utils.js +2 -0
  38. package/tx/ocl/cm-ocl.cjs +531 -0
  39. package/tx/ocl/cm-ocl.js +1 -105
  40. package/tx/ocl/cs-ocl.cjs +1779 -0
  41. package/tx/ocl/cs-ocl.js +1 -38
  42. package/tx/ocl/fingerprint/fingerprint.cjs +67 -0
  43. package/tx/ocl/fingerprint/fingerprint.js +2 -0
  44. package/tx/ocl/http/client.cjs +31 -0
  45. package/tx/ocl/http/client.js +2 -0
  46. package/tx/ocl/http/pagination.cjs +98 -0
  47. package/tx/ocl/http/pagination.js +2 -0
  48. package/tx/ocl/jobs/background-queue.cjs +200 -0
  49. package/tx/ocl/jobs/background-queue.js +2 -0
  50. package/tx/ocl/mappers/concept-mapper.cjs +66 -0
  51. package/tx/ocl/mappers/concept-mapper.js +2 -0
  52. package/tx/ocl/model/concept-filter-context.cjs +51 -0
  53. package/tx/ocl/model/concept-filter-context.js +2 -0
  54. package/tx/ocl/shared/constants.cjs +15 -0
  55. package/tx/ocl/shared/constants.js +2 -0
  56. package/tx/ocl/shared/patches.cjs +224 -0
  57. package/tx/ocl/shared/patches.js +2 -0
  58. package/tx/ocl/vs-ocl.cjs +1848 -0
  59. package/tx/ocl/vs-ocl.js +1 -104
  60. package/tx/operation-context.js +8 -1
  61. package/tx/params.js +24 -3
  62. package/tx/provider.js +51 -2
  63. package/tx/sct/expressions.js +20 -9
  64. package/tx/tx-html.js +144 -51
  65. package/tx/tx.js +10 -2
  66. package/tx/vs/vs-vsac.js +4 -3
  67. package/tx/workers/batch-validate.js +3 -2
  68. package/tx/workers/batch.js +3 -2
  69. package/tx/workers/expand.js +125 -18
  70. package/tx/workers/lookup.js +5 -4
  71. package/tx/workers/read.js +2 -1
  72. package/tx/workers/related.js +3 -2
  73. package/tx/workers/search.js +6 -8
  74. package/tx/workers/subsumes.js +3 -2
  75. package/tx/workers/translate.js +4 -3
  76. package/tx/workers/validate.js +132 -40
  77. package/tx/workers/worker.js +1 -7
  78. package/tx/xversion/xv-terminologyCapabilities.js +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ All notable changes to the Health Intersections Node Server will be documented i
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [v0.7.0] - 2026-03-13
9
+
10
+ ### Added
11
+ - Add support for serving for OCL TX content (h/t Italo Macêdo from the OCL team)
12
+ - Add default configurations (wip)
13
+
14
+ ### Changed
15
+ - Make web-crawlers more robust after tx.fhir.org crash
16
+ - Don't accept NPM packages that have .js code or install scripts
17
+
18
+ ### Fixed
19
+ - Fix many bugs in expansion and validation for value sets that include two different versions of the same code system
20
+ - Fix CodeSystem search on system parameter to reduce user confusion
21
+ - Fix CodeSystem search such that default search is without any specified source
22
+ - Fix headers sent multiple times error
23
+
24
+ ### Tx Conformance Statement
25
+
26
+ FHIRsmith passed all 1452 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1-SNAPSHOT, runner v6.8.2)
27
+
28
+ ## [v0.6.0] - 2026-03-06
29
+
30
+ ### Added
31
+ - Add support to packages server for scoped packages
32
+ - Add support for exclusions and content tracking in tx-registry
33
+ - Add support for serving a host
34
+
35
+ ### Changed
36
+ - fix error in SCT expression validation
37
+ - fix null error in search
38
+ - fix search for code systems with uppercase letters in their name
39
+ - rework html interface for CodeSystem and ValueSet
40
+ - further work on publisehr
41
+
42
+ ### Tx Conformance Statement
43
+
44
+ FHIRsmith passed all 1382 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.0, runner v6.8.2)
45
+
8
46
  ## [v0.5.6] - 2026-02-26
9
47
 
10
48
  ### Changed
package/README.md CHANGED
@@ -229,6 +229,8 @@ GitHub Actions will automatically:
229
229
  - Change description
230
230
  ### Fixed
231
231
  - Bug fix description
232
+ ### Tx Conformance Statement
233
+ {copy content from text-cases-summary.txt}
232
234
  ```
233
235
  2. Update `package.json` to have the same release version
234
236
 
@@ -0,0 +1,21 @@
1
+ {
2
+ "hostName" : "NPM Publisher",
3
+ "server": {
4
+ "port": 3001,
5
+ "cors": {
6
+ "origin": true,
7
+ "credentials": true
8
+ }
9
+ },
10
+ "modules": {
11
+ "npmprojector": {
12
+ "enabled": false,
13
+ "fhirVersion": "r4",
14
+ "basePath": "/us-core",
15
+ "npm": "hl7.fhir.us.core#7.0.1",
16
+ "resourceFolders": ["data"],
17
+ "searchParametersFolder": "data",
18
+ "debounceMs": 500
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ This folder contains some basic starter configurations:
2
+
3
+ * Terminology server: see tx-config.json for a vanilla server that doesn't contain any licensed content
4
+ * NPM web server: see projector.json for a basic configuration to make a package available online
5
+
@@ -72,7 +72,8 @@ class HtmlServer {
72
72
  .replace(/\[%total-packages%\]/g, escape(renderOptions.totalPackages.toLocaleString()))
73
73
  .replace(/\[%endpoint-path%\]/g, escape(renderOptions.endpointpath))
74
74
  .replace(/\[%fhir-version%\]/g, escape(renderOptions.fhirversion))
75
- .replace(/\[%ms%\]/g, escape(renderOptions.processingTime.toString()));
75
+ .replace(/\[%ms%\]/g, escape(renderOptions.processingTime.toString()))
76
+ .replace(/\[%about%\]/g, renderOptions.about || '');
76
77
 
77
78
  // Handle any custom template variables
78
79
  if (options.templateVars) {
@@ -380,7 +380,6 @@ class PackageManager {
380
380
  return cachedPath;
381
381
  }
382
382
 
383
- console.log("Fetch Package "+packageId+"#"+version);
384
383
  // Not in cache, fetch from servers
385
384
  const packageData = await this.fetchFromServers(packageId, resolvedVersion);
386
385
 
@@ -593,42 +592,46 @@ class PackageManager {
593
592
  * @returns {Promise<string>} Path to extracted package folder
594
593
  */
595
594
  async fetchUrl(url) {
596
- console.log("Fetch Package from URL: " + url);
597
- const client = new CIBuildClient();
598
- const packageData = await client.fetchFromUrlSpecific(url);
599
-
600
- // Extract to a temp location to read package.json for name and version
601
- const tempKey = `_url_temp_${Date.now()}`;
602
- const tempPath = await this.extractToCache(tempKey, 'url', packageData);
603
- const tempFullPath = path.join(this.cacheFolder, tempPath);
604
-
605
- // Read package name and version from the extracted package
606
- const pkgJsonPath = path.join(tempFullPath, 'package', 'package.json');
607
- const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
608
- const packageId = pkgJson.name;
609
- const version = pkgJson.version;
610
-
611
- if (!packageId || !version) {
612
- throw new Error(`Package at ${url} has no name or version in package.json`);
613
- }
595
+ try {
596
+ const client = new CIBuildClient();
597
+ const packageData = await client.fetchFromUrlSpecific(url);
598
+
599
+ // Extract to a temp location to read package.json for name and version
600
+ const tempKey = `_url_temp_${Date.now()}`;
601
+ const tempPath = await this.extractToCache(tempKey, 'url', packageData);
602
+ const tempFullPath = path.join(this.cacheFolder, tempPath);
603
+
604
+ // Read package name and version from the extracted package
605
+ const pkgJsonPath = path.join(tempFullPath, 'package', 'package.json');
606
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
607
+ const packageId = pkgJson.name;
608
+ const version = pkgJson.version;
609
+
610
+ if (!packageId || !version) {
611
+ throw new Error(`Package at ${url} has no name or version in package.json`);
612
+ }
614
613
 
615
- // Use the same cache key format as npm packages
616
- const finalName = `${packageId}#${version}`;
617
- const finalPath = path.join(this.cacheFolder, finalName);
614
+ // Use the same cache key format as npm packages
615
+ const finalName = `${packageId}#${version}`;
616
+ const finalPath = path.join(this.cacheFolder, finalName);
618
617
 
619
- // If it already exists, the same package is already loaded - that's a config error
620
- try {
621
- await fs.access(finalPath);
622
- await fs.rm(tempFullPath, { recursive: true, force: true });
623
- throw new Error(`Package ${finalName} already exists in cache. Check library config for duplicates (url: ${url})`);
624
- } catch (e) {
625
- if (e.message.includes('already exists')) throw e;
626
- // Doesn't exist yet, rename temp to final
627
- await fs.rename(tempFullPath, finalPath);
628
- }
618
+ // If it already exists, the same package is already loaded - that's a config error
619
+ try {
620
+ await fs.access(finalPath);
621
+ await fs.rm(tempFullPath, { recursive: true, force: true });
622
+ throw new Error(`Package ${finalName} already exists in cache. Check library config for duplicates (url: ${url})`);
623
+ } catch (e) {
624
+ if (e.message.includes('already exists')) throw e;
625
+ // Doesn't exist yet, rename temp to final
626
+ await fs.rename(tempFullPath, finalPath);
627
+ }
629
628
 
630
- this.totalDownloaded = this.totalDownloaded + packageData.length;
631
- return finalName;
629
+ this.totalDownloaded = this.totalDownloaded + packageData.length;
630
+ return finalName;
631
+ } catch (error) {
632
+ console.error(`Error fetching package from URL ${url}: ${error.message}`);
633
+ throw error;
634
+ }
632
635
  }
633
636
 
634
637
  /**
@@ -274,4 +274,13 @@ class ArrayMatcher {
274
274
  }
275
275
  }
276
276
 
277
- module.exports = { Utilities, ArrayMatcher, validateParameter, validateOptionalParameter, validateArrayParameter, validateResource, strToBool, getValuePrimitive, getValueDT, getValueName, isAbsoluteUrl };
277
+ const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
278
+
279
+ function formatDateMMDDYYYY(s) {
280
+ const mm = parseInt(s.substring(0, 2), 10);
281
+ const dd = s.substring(2, 4);
282
+ const yyyy = s.substring(4, 8);
283
+ return dd + '-' + months[mm - 1] + ' ' + yyyy;
284
+ }
285
+
286
+ module.exports = { Utilities, ArrayMatcher, validateParameter, validateOptionalParameter, validateArrayParameter, validateResource, strToBool, getValuePrimitive, getValueDT, getValueName, isAbsoluteUrl, formatDateMMDDYYYY };
@@ -1051,6 +1051,91 @@ class VersionUtilities {
1051
1051
  return url;
1052
1052
  }
1053
1053
  }
1054
+
1055
+
1056
+ static isAnInteger(version) {
1057
+ return /^\d+$/.test(version);
1058
+ }
1059
+
1060
+ static appearsToBeDate(version) {
1061
+ if (!version || typeof version !== 'string') return false;
1062
+ // Strip optional time portion (T...) before checking
1063
+ const datePart = version.split('T')[0];
1064
+ return /^\d{4}-?\d{2}(-?\d{2})?$/.test(datePart);
1065
+
1066
+ }
1067
+
1068
+ static guessVersionAlgorithmFromVersion(version) {
1069
+ if (VersionUtilities.isSemVerWithWildcards(version)) {
1070
+ return 'semver';
1071
+ }
1072
+ if (this.appearsToBeDate(version)) {
1073
+ return 'date';
1074
+ }
1075
+ if (this.isAnInteger(version)) {
1076
+ return 'integer';
1077
+ }
1078
+ return 'alpha';
1079
+ }
1080
+
1081
+ static dateIsMoreRecent(date, date2) {
1082
+ return VersionUtilities.normaliseDateString(date) > VersionUtilities.normaliseDateString(date2);
1083
+ }
1084
+
1085
+ static normaliseDateString(date) {
1086
+ // Strip time portion, then remove dashes so all formats compare uniformly as YYYYMMDD or YYYYMM
1087
+ return date.split('T')[0].replace(/-/g, '');
1088
+ }
1089
+
1090
+
1091
+ /**
1092
+ * guesses the correct format, then compares accordingly
1093
+ */
1094
+ static compareVersionsGeneral(version1, version2) {
1095
+ if (version1 && version2) {
1096
+ if (version1 == version2) {
1097
+ return 0;
1098
+ }
1099
+ const fmt1 = VersionUtilities.guessVersionAlgorithmFromVersion(version1);
1100
+ const fmt2 = VersionUtilities.guessVersionAlgorithmFromVersion(version2);
1101
+ if (fmt1 != fmt2) {
1102
+ return version1.localeCompare(version2);
1103
+ }
1104
+ switch (fmt1) {
1105
+ case 'semver': {
1106
+ let b1 = VersionUtilities.isThisOrLater(version1, version2, VersionPrecision.PATCH);
1107
+ let b2 = VersionUtilities.isThisOrLater(version2, version1, VersionPrecision.PATCH);
1108
+ if (b1 && b2) {
1109
+ return 0;
1110
+ } else if (b2) {
1111
+ return 1;
1112
+ } else {
1113
+ return -1;
1114
+ }
1115
+ }
1116
+ case 'date':
1117
+ if (VersionUtilities.dateIsMoreRecent(version1, version2)) {
1118
+ return 1;
1119
+ } else if (VersionUtilities.dateIsMoreRecent(version2, version1)) {
1120
+ return -1;
1121
+ } else {
1122
+ return 0;
1123
+ }
1124
+ case 'integer':
1125
+ return parseInt(version1, 10) - parseInt(version2, 10);
1126
+ case 'alpha':
1127
+ return version1.localeCompare(version2);
1128
+ default:
1129
+ return version1.localeCompare(version2);
1130
+ }
1131
+ } else if (version1) {
1132
+ return 1;
1133
+ } else if (version2) {
1134
+ return -1;
1135
+ } else {
1136
+ return 0;
1137
+ }
1138
+ }
1054
1139
  }
1055
1140
 
1056
1141
  module.exports = { VersionUtilities, VersionPrecision, SemverParser };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhirsmith",
3
- "version": "0.5.6",
3
+ "version": "0.7.0",
4
4
  "description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
5
5
  "main": "server.js",
6
6
  "engines": {