fhirsmith 0.3.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 (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Health Intersections Node Server will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [v0.3.0] - 2026-02-05
9
+ ### Added
10
+ - Add first draft of publishing engine
11
+
12
+ ### Changed
13
+ - Move all runtime files to a data directory, where an environment variable says. Existing configurations MUST change
14
+ - Finish porting the terminology server
15
+ - Lots of QA related changes, and consistency.
16
+
17
+ ## [v0.2.0] - 2026-01-13
18
+ ### Added
19
+ - port tx.fhir.org to FHIRsmith, and pass all the tests
20
+
21
+ ### Changed
22
+ - rework logging, testing, etc infrastructure
23
+
24
+ ## [v0.1.1] - 2025-08-21
25
+ ### Added
26
+ - set up ci and release workflows with Docker
27
+ - Add tx-reg implementation
28
+
29
+ ### Changed
30
+
31
+ - rework logging from scratch
32
+
33
+ ## [v0.1.0] - 2025-08-20
34
+
35
+ First Documented Release
36
+
37
+ ### Added
38
+ - SHL Module: Support services for SHL and VHL implementations
39
+ - VCL Module: Support services for ValueSet Compose Language
40
+ - XIG Module: The Cross-IG Resource server
41
+ - Packages Modules: The server for packages2.fhir.org/packages
42
+ - Testing Infrastructure
package/FHIRsmith.png ADDED
Binary file
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # ![🔥](static/FHIRsmith64.png) FHIRsmith - FHIR Server toolkit
2
+
3
+
4
+
5
+ This server provides a set of server-side services that are useful for the FHIR Community. The set of are two kinds of services:
6
+
7
+ ## Modules useful to anyone in the community
8
+
9
+ * (Coming) R4/R6 interconverter
10
+ * [tx.fhir.org](tx/README.md) server
11
+ * [SHL Server](shl/readme.md) - SHL/VHL support services
12
+
13
+ ## Services useful the community as a whole
14
+
15
+ * [TX Registry](registry/readme.md) - **Terminology System Registry** as [described by the terminology ecosystem specification](https://build.fhir.org/ig/HL7/fhir-tx-ecosystem-ig)(as running at http://tx.fhir.org/tx-reg)
16
+ * [Package server](packages/readme.md) - **NPM-style FHIR package registry** with search, versioning, and downloads, consistent with the FHIR NPM Specification (as running at http://packages2.fhir.org/packages)
17
+ * [XIG server](xig/readme.md) - **Comprehensive FHIR IG analytics** with resource breakdowns by version, authority, and realm (as running at http://packages2.fhir.org/packages)
18
+ * [Publisher](publisher/readme.md) - FHIR publishing services (coming)
19
+ * [VCL](vcl/readme.md) - **Parse VCL expressions** into FHIR ValueSet resources for http://fhir.org/vcl
20
+ * (Coming) Token services
21
+
22
+ ## Build Status
23
+ ![CI Build](https://github.com/HealthIntersections/fhirsmith/actions/workflows/ci.yml/badge.svg)
24
+ [![Release](https://img.shields.io/github/v/release/HealthIntersections/fhirsmith?include_prereleases)](https://github.com/HealthIntersections/fhirsmith/releases)
25
+ [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue)](https://github.com/HealthIntersections/fhirsmith/pkgs/container/fhirsmith)
26
+
27
+ Note: In production, this server always runs behind an nginx reverse proxy, so there's no
28
+ in-build support for SSL, rate limiting etc.
29
+
30
+ ## Quick Start
31
+
32
+ There are 4 executable programs:
33
+ * the server (`node server`)
34
+ * the test cases (`npm test`)
35
+ * the terminology importer (`node --max-old-space-size=8192 tx/importers/tx-import XXX`) - see [Doco](tx/importers/readme.md)
36
+ * the test cases generater (`node tx/tests/testcases-generator.js`)
37
+
38
+ ### Data Directory
39
+
40
+ The server separates code from runtime data. All databases, caches, logs, and downloaded
41
+ files are stored in a single data directory. The location is determined by:
42
+
43
+ 1. The `FHIRSMITH_DATA_DIR` environment variable (if set)
44
+ 2. Otherwise, defaults to `./data` relative to the working directory
45
+
46
+ The data directory contains (depending on which modules are in use):
47
+ * `config.json` — server and module configuration
48
+ * `logs/` — server and nginx log files
49
+ * `terminology-cache/` — downloaded terminology packages and FHIR packages
50
+ * `packages/` — package server database
51
+ * `xig/` — XIG database
52
+ * `shl/` — SHL databases and certificates
53
+ * `registry/` — registry crawler data
54
+ * `publisher/` — publisher database and build workspace
55
+ * `token/` — token database
56
+
57
+ During development with a cloned repository, the data directory defaults to `[root]/data`
58
+ (the test cases require this setup). When deployed via Docker or npm, the data directory
59
+ is provided by the host — see Deployment below.
60
+
61
+ ### Prerequisites
62
+ - Node.js 16+
63
+ - NPM or Yarn
64
+ - Java 17+ (for FHIR validator, also for the test cases)
65
+
66
+ ### Installation
67
+
68
+ These instructions are for Development. For deployment, see below.
69
+
70
+ ```bash
71
+ # Clone the repository
72
+ git clone https://github.com/HealthIntersections/FHIRsmith
73
+ cd FHIRsmith
74
+
75
+ # Install dependencies
76
+ npm install
77
+
78
+ # Create required directories
79
+ mkdir -p data data/logs
80
+
81
+ # Copy example configuration
82
+ cp config.example.json data/config.json
83
+
84
+ # Edit configuration as needed
85
+ nano data/config.json
86
+ ```
87
+
88
+ Each Module has it's own entry in the config, as described by the module
89
+
90
+ ### Basic Configuration
91
+
92
+ Create a `config.json` file in your data directory (use `config-template.json` as a starting point):
93
+
94
+ ```json
95
+ {
96
+ "hostName" : "[descriptive name for the server]",
97
+ "server": {
98
+ "port": 3000,
99
+ "cors": {
100
+ "origin": "*",
101
+ "credentials": true
102
+ }
103
+ },
104
+ "modules": {
105
+ // per modules...
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### Start the Server
111
+
112
+ ```bash
113
+ # Development mode
114
+ npm run dev
115
+
116
+ # Production mode
117
+ npm start
118
+ ```
119
+
120
+ The server will be available at `http://localhost:{port}` using the port specified in the config.
121
+ In the production servers listed above, the server always sits behind an NGINX server which manages
122
+ SSL, security, rate limiting etc.
123
+
124
+ ## Testing
125
+
126
+ ```bash
127
+ npm test
128
+ ```
129
+
130
+ ## Deployment
131
+
132
+ There are three deployment options: npm global install, Docker, or clone-and-run. All three
133
+ use the `FHIRSMITH_DATA_DIR` environment variable to locate the data directory.
134
+
135
+ ### npm Global Install
136
+
137
+ ```bash
138
+ # Install globally
139
+ npm install -g fhirsmith
140
+
141
+ # Create a data directory
142
+ mkdir -p /var/lib/fhirsmith
143
+ cp node_modules/fhirsmith/config-template.json /var/lib/fhirsmith/config.json
144
+ # Edit config.json as needed
145
+
146
+ # Set the data directory and run
147
+ export FHIRSMITH_DATA_DIR=/var/lib/fhirsmith
148
+ fhirsmith
149
+ ```
150
+
151
+ Or run it inline:
152
+
153
+ ```bash
154
+ FHIRSMITH_DATA_DIR=/var/lib/fhirsmith fhirsmith
155
+ ```
156
+
157
+ ### Docker Installation
158
+
159
+ The server is available as a Docker image. Mount a host directory as the data directory:
160
+
161
+ ```bash
162
+ # Pull the latest image
163
+ docker pull ghcr.io/healthintersections/fhirsmith:latest
164
+
165
+ # Create and populate data directory on host
166
+ mkdir -p /path/to/data
167
+ cp config-template.json /path/to/data/config.json
168
+ # Edit config.json as needed
169
+
170
+ # Run with data directory mounted
171
+ docker run -d --name fhirsmith \
172
+ -p 3000:3000 \
173
+ -e FHIRSMITH_DATA_DIR=/app/data \
174
+ -v /path/to/data:/app/data \
175
+ ghcr.io/healthintersections/fhirsmith:latest
176
+ ```
177
+
178
+ Available tags:
179
+ - `latest`: Latest stable release
180
+ - `vX.Y.Z`: Specific version (e.g., `v1.0.0`)
181
+ - `cibuild`: Latest build from the main branch
182
+
183
+ ### Environment Variables
184
+
185
+ | Variable | Description | Default |
186
+ |---|---|---|
187
+ | `FHIRSMITH_DATA_DIR` | Path to the data directory | `./data` |
188
+ | `PORT` | Server port (overrides config) | from config.json |
189
+ | `NODE_ENV` | Node environment | `production` |
190
+
191
+ ### Windows Installation
192
+
193
+ You can install as a windows service using [windows-install.js](windows-install.js). You might need to
194
+ hack that.
195
+
196
+ ## Releases
197
+
198
+ This project follows [Semantic Versioning](https://semver.org/) and uses a [CHANGELOG.md](CHANGELOG.md) file to track changes.
199
+
200
+ ### What's in a Release
201
+
202
+ Each GitHub Release includes:
203
+ - **Release notes** extracted from CHANGELOG.md
204
+ - **Source code** archives (zip and tar.gz)
205
+ - **Docker images** pushed to GitHub Container Registry:
206
+ - `ghcr.io/healthintersections/fhirsmith:latest`
207
+ - `ghcr.io/healthintersections/fhirsmith:vX.Y.Z`
208
+ - `ghcr.io/healthintersections/fhirsmith:X.Y.Z`
209
+ - **npm package** published to npmjs.org as `fhirsmith` *(if you add this)*
210
+
211
+ ### Creating a Release
212
+
213
+ GitHub Actions will automatically:
214
+ - Run tests
215
+ - Create a GitHub Release with notes from CHANGELOG.md
216
+ - Build and publish Docker images with appropriate tags
217
+
218
+ **Prerequisites:**
219
+ - All tests passing on main branch
220
+ - CHANGELOG.md updated with changes
221
+
222
+ **Steps:**
223
+ 1. Update `CHANGELOG.md` with your changes under a new version section:
224
+ ```markdown
225
+ ## [vX.Y.Z] - YYYY-MM-DD
226
+ ### Added
227
+ - New feature description
228
+ ### Changed
229
+ - Change description
230
+ ### Fixed
231
+ - Bug fix description
232
+ ```
233
+ 2. Update `package.json` to have the same release version
234
+
235
+ 3. Commit your changes:
236
+ ```bash
237
+ git commit -m "Prepare release vX.Y.Z"
238
+ git push origin main
239
+ ```
240
+
241
+ 4. Tag and push the release:
242
+ ```bash
243
+ git tag vX.Y.Z
244
+ git push origin vX.Y.Z
245
+ ```
246
+
247
+ 5. Monitor the release:
248
+ - Check [GitHub Actions](https://github.com/HealthIntersections/fhirsmith/actions) for the Release workflow
249
+ - Verify the [GitHub Release](https://github.com/HealthIntersections/fhirsmith/releases) was created
250
+ - Confirm Docker images are available at [GHCR](https://github.com/HealthIntersections/fhirsmith/pkgs/container/fhirsmith)
251
+
252
+ 6. Update `package.json` to have the next release version -SNAPSHOT
253
+
254
+ **If a release fails:**
255
+ - Delete the tag: `git tag -d vX.Y.Z && git push origin :refs/tags/vX.Y.Z`
256
+ - Fix the issue
257
+ - Re-tag and push
258
+
259
+ ### Creating a Release
260
+
261
+ ## License
262
+
263
+ [BSD-3](https://opensource.org/license/bsd-3-clause)
264
+
265
+ ## Contributing
266
+
267
+ 1. Fork the repository
268
+ 2. Create a feature branch
269
+ 3. Make your changes
270
+ 4. Add tests if applicable
271
+ 5. Submit a pull request
272
+
273
+ ## Support
274
+
275
+ - **Issues:** [GitHub Issues](repository-url/issues)
276
+ - **Documentation:** [Wiki](repository-url/wiki)
277
+ - **FHIR Community:** [chat.fhir.org](https://chat.fhir.org)
@@ -0,0 +1,144 @@
1
+ {
2
+ "server": {
3
+ "port": 3000,
4
+ "cors": {
5
+ "origin": true,
6
+ "credentials": true
7
+ }
8
+ },
9
+ "modules": {
10
+ "shl": {
11
+ "enabled": false,
12
+ "database": "shl.db",
13
+ "password": "<!-- Uuid -->",
14
+ "certificates": {
15
+ "certFile": "<!-- filename -->",
16
+ "keyFile": "<!-- filename -->",
17
+ "kid": "<!-- kid -->"
18
+ },
19
+ "vhl": {
20
+ "issuer": "XXX"
21
+ },
22
+ "cleanup": {
23
+ "schedule": "0 * * * *"
24
+ },
25
+ "validator": {
26
+ "enabled": true,
27
+ "version": "4.0.1",
28
+ "txServer": "http://tx.fhir.org/r4",
29
+ "txLog": "./tx.log",
30
+ "port": 8081,
31
+ "packages": [
32
+ "hl7.fhir.us.core#6.0.0",
33
+ "hl7.fhir.uv.ips#1.1.0"
34
+ ],
35
+ "timeout": 60000
36
+ }
37
+ },
38
+ "vcl": {
39
+ "enabled": false
40
+ },
41
+ "xig": {
42
+ "enabled": false,
43
+ "autoUpdate": true
44
+ },
45
+ "packages": {
46
+ "enabled": false,
47
+ "database": "/absolute/path/to/packages.db",
48
+ "mirrorPath": "/absolute/path/to/mirror",
49
+ "crawler": {
50
+ "enabled": true,
51
+ "schedule": "0 * * * *"
52
+ }
53
+ },
54
+ "registry": {
55
+ "enabled": true,
56
+ "masterUrl": "https://fhir.github.io/ig-registry/tx-servers.json",
57
+ "crawlInterval": 30,
58
+ "timeout": 30000,
59
+ "userAgent": "YourServer/1.0",
60
+ "apiKeys": {}
61
+ },
62
+ "publisher": {
63
+ "enabled": true,
64
+ "database": "/absolute/path/to/publisher.db",
65
+ "sessionSecret": "change-this-to-a-secure-random-string",
66
+ "workspaceRoot": "/absolute/path/to/workspaces"
67
+ },
68
+ "npmprojector": {
69
+ "enabled": true,
70
+ "fhirVersion": "r4", // Options: "r4", "r5", "stu3", "dstu2"
71
+ "basePath": "/fhir", // Default: "/npmprojector"
72
+ "npmPath": "./data/fhir-package",
73
+ "resourceTypes": null,
74
+ "searchParametersPath": null,
75
+ "debounceMs": 500
76
+ },
77
+ "token": {
78
+ "enabled": true,
79
+ "database": "/absolute/path/to/token.db",
80
+ "sessionSecret": "change-this-to-a-secure-random-string-for-token-sessions",
81
+ "oauth": {
82
+ "google": {
83
+ "clientId": "your-google-client-id",
84
+ "clientSecret": "your-google-client-secret",
85
+ "redirectUri": "http://localhost:3000/token/callback/google",
86
+ "scope": ["openid", "profile", "email"]
87
+ },
88
+ "facebook": {
89
+ "clientId": "your-facebook-app-id",
90
+ "clientSecret": "your-facebook-app-secret",
91
+ "redirectUri": "http://localhost:3000/token/callback/facebook",
92
+ "scope": ["email"]
93
+ },
94
+ "apple": {
95
+ "clientId": "your-apple-service-id",
96
+ "teamId": "your-apple-team-id",
97
+ "keyId": "your-apple-key-id",
98
+ "privateKey": "/path/to/apple-private-key.p8",
99
+ "redirectUri": "http://localhost:3000/token/callback/apple",
100
+ "scope": ["email", "name"]
101
+ },
102
+ "github": {
103
+ "clientId": "your-github-client-id",
104
+ "clientSecret": "your-github-client-secret",
105
+ "redirectUri": "http://localhost:3000/token/callback/github",
106
+ "scope": ["user:email"]
107
+ }
108
+ },
109
+ "apiKeys": {
110
+ "defaultAllowedRequests": 50,
111
+ "maxKeysPerUser": 10,
112
+ "keyExpiration": null
113
+ },
114
+ "rateLimit": {
115
+ "windowMs": 900000,
116
+ "max": 100
117
+ }
118
+ },
119
+ "tx": {
120
+ "enabled": false,
121
+ "librarySource": "/absolute/path/to/library.yml",
122
+ "cacheTimeout": 30,
123
+ "expansionCacheSize": 1000,
124
+ "expansionCacheMemoryThreshold": 0,
125
+ "endpoints": [
126
+ {
127
+ "path": "/tx/r5",
128
+ "fhirVersion": "5.0",
129
+ "context": null
130
+ },
131
+ {
132
+ "path": "/tx/r4",
133
+ "fhirVersion": "4.0",
134
+ "context": null
135
+ },
136
+ {
137
+ "path": "/tx/r3",
138
+ "fhirVersion": "3.0",
139
+ "context": null
140
+ }
141
+ ]
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,58 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ class FolderSetup {
5
+ constructor() {
6
+ this._dataDir = null;
7
+ this._initialized = false;
8
+ }
9
+
10
+ init(dataDir = null) {
11
+ if (this._initialized) {
12
+ return this;
13
+ }
14
+
15
+ this._dataDir = dataDir || process.env.FHIRSMITH_DATA_DIR || path.join(__dirname, '..', 'data');
16
+ fs.mkdirSync(this._dataDir, { recursive: true });
17
+ this._initialized = true;
18
+
19
+ return this;
20
+ }
21
+
22
+ dataDir() {
23
+ if (!this._initialized) {
24
+ this.init();
25
+ }
26
+ return this._dataDir;
27
+ }
28
+
29
+ subDir(name) {
30
+ const dir = path.join(this.dataDir(), name);
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ return dir;
33
+ }
34
+
35
+ filePath(...relativePath) {
36
+ return path.join(this.dataDir(), ...relativePath);
37
+ }
38
+
39
+ ensureFilePath(...relativePath) {
40
+ const filePath = path.join(this.dataDir(), ...relativePath);
41
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
42
+ return filePath;
43
+ }
44
+
45
+ logsDir() {
46
+ return this.subDir('logs');
47
+ }
48
+
49
+ cacheDir() {
50
+ return this.subDir('cache');
51
+ }
52
+
53
+ databasesDir() {
54
+ return this.subDir('databases');
55
+ }
56
+ }
57
+
58
+ module.exports = new FolderSetup();
@@ -0,0 +1,166 @@
1
+
2
+ //
3
+ // Copyright 2025, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
4
+ //
5
+ // Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
6
+ //
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ class HtmlServer {
12
+ log;
13
+
14
+ constructor() {
15
+ this.templates = new Map(); // templateName -> template content
16
+ }
17
+
18
+ useLog(logv) {
19
+ this.log = logv;
20
+ }
21
+
22
+ // Template Management
23
+ loadTemplate(templateName, templatePath) {
24
+ try {
25
+ if (fs.existsSync(templatePath)) {
26
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
27
+ this.templates.set(templateName, templateContent);
28
+ return true;
29
+ } else {
30
+ this.log.error(`Template file not found: ${templatePath}`);
31
+ return false;
32
+ }
33
+ } catch (error) {
34
+ this.log.error(`Failed to load template '${templateName}':`, error.message);
35
+ return false;
36
+ }
37
+ }
38
+
39
+ getTemplate(templateName) {
40
+ return this.templates.get(templateName);
41
+ }
42
+
43
+ hasTemplate(templateName) {
44
+ return this.templates.has(templateName);
45
+ }
46
+
47
+ // HTML Utilities
48
+ escapeHtml(text) {
49
+ if (typeof text !== 'string') {
50
+ return String(text);
51
+ }
52
+
53
+ const map = {
54
+ '&': '&amp;',
55
+ '<': '&lt;',
56
+ '>': '&gt;',
57
+ '"': '&quot;',
58
+ "'": '&#39;'
59
+ };
60
+
61
+ return text.replace(/[&<>"']/g, function(m) { return map[m]; });
62
+ }
63
+
64
+ // Page Rendering - simple template substitution
65
+ renderPage(templateName, title, content, options = {}) {
66
+ const template = this.getTemplate(templateName);
67
+ if (!template) {
68
+ throw new Error(`Template '${templateName}' not found`);
69
+ }
70
+
71
+ // Default options
72
+ const renderOptions = {
73
+ version: '4.0.1',
74
+ downloadDate: 'Unknown',
75
+ totalResources: 0,
76
+ totalPackages: 0,
77
+ processingTime: 0,
78
+ ...options
79
+ };
80
+
81
+ // Perform template replacements
82
+ let html = template
83
+ .replace(/\[%title%\]/g, this.escapeHtml(title))
84
+ .replace(/\[%content%\]/g, content) // Content is assumed to be already-safe HTML
85
+ .replace(/\[%ver%\]/g, this.escapeHtml(renderOptions.version))
86
+ .replace(/\[%download-date%\]/g, this.escapeHtml(renderOptions.downloadDate))
87
+ .replace(/\[%total-resources%\]/g, this.escapeHtml(renderOptions.totalResources.toLocaleString()))
88
+ .replace(/\[%total-packages%\]/g, this.escapeHtml(renderOptions.totalPackages.toLocaleString()))
89
+ .replace(/\[%endpoint-path%\]/g, this.escapeHtml(renderOptions.endpointpath))
90
+ .replace(/\[%fhir-version%\]/g, this.escapeHtml(renderOptions.fhirversion))
91
+ .replace(/\[%ms%\]/g, this.escapeHtml(renderOptions.processingTime.toString()));
92
+
93
+ // Handle any custom template variables
94
+ if (options.templateVars) {
95
+ for (const [key, value] of Object.entries(options.templateVars)) {
96
+ const placeholder = `[%${key}%]`;
97
+ const escapedValue = typeof value === 'string' ? this.escapeHtml(value) : String(value);
98
+ html = html.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), escapedValue);
99
+ }
100
+ }
101
+
102
+ return html;
103
+ }
104
+
105
+ // Express Response Helper
106
+ sendHtmlResponse(res, templateName, title, content, options = {}) {
107
+ try {
108
+ const html = this.renderPage(templateName, title, content, options);
109
+ res.setHeader('Content-Type', 'text/html');
110
+ res.send(html);
111
+ } catch (error) {
112
+ this.log.error('[HtmlServer] Error rendering page:', error);
113
+ res.status(500).send(`<h1>Error</h1><p>Failed to render page: ${this.escapeHtml(error.message)}</p>`);
114
+ }
115
+ }
116
+
117
+ sendErrorResponse(res, templateName, error, statusCode = 500) {
118
+ const errorContent = `
119
+ <div class="alert alert-danger">
120
+ <h4>Error</h4>
121
+ <p>${this.escapeHtml(error.message || error)}</p>
122
+ </div>
123
+ `;
124
+
125
+ try {
126
+ const html = this.renderPage(templateName, 'Error', errorContent);
127
+ res.status(statusCode).setHeader('Content-Type', 'text/html');
128
+ res.send(html);
129
+ } catch (renderError) {
130
+ this.log.error('[HtmlServer] Error rendering error page:', renderError);
131
+ res.status(statusCode).send(`<h1>Error</h1><p>Failed to render error page: ${this.escapeHtml(renderError.message)}</p>`);
132
+ }
133
+ }
134
+
135
+ // Date Formatting Utility
136
+ formatDate(dateString) {
137
+ if (!dateString) return '';
138
+ try {
139
+ const date = new Date(dateString);
140
+ const year = date.getFullYear();
141
+ const month = String(date.getMonth() + 1).padStart(2, '0');
142
+ return `${year}-${month}`;
143
+ } catch (error) {
144
+ return dateString; // Return original if parsing fails
145
+ }
146
+ }
147
+
148
+ // Initialize templates from directory
149
+ loadTemplatesFromDirectory(templatesDir) {
150
+ if (!fs.existsSync(templatesDir)) {
151
+ this.log.warn(`Templates directory not found: ${templatesDir}`);
152
+ return;
153
+ }
154
+
155
+ const templateFiles = fs.readdirSync(templatesDir).filter(file => file.endsWith('.html'));
156
+
157
+ templateFiles.forEach(file => {
158
+ const templateName = path.basename(file, '.html');
159
+ const templatePath = path.join(templatesDir, file);
160
+ this.loadTemplate(templateName, templatePath);
161
+ });
162
+ }
163
+ }
164
+
165
+ // Export singleton instance
166
+ module.exports = new HtmlServer();