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/server.js ADDED
@@ -0,0 +1,524 @@
1
+ #!/usr/bin/env node
2
+
3
+ //
4
+ // Copyright 2025, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
5
+ //
6
+ // Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
7
+ //
8
+
9
+ const express = require('express');
10
+ // const cors = require('cors');
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+ const folders = require('./library/folder-setup'); // <-- ADD: load early
14
+
15
+ // Load configuration BEFORE logger
16
+ let config;
17
+ try {
18
+ const configPath = folders.filePath('config.json'); // <-- CHANGE: config now in data dir
19
+ const configData = fs.readFileSync(configPath, 'utf8');
20
+ config = JSON.parse(configData);
21
+ } catch (error) {
22
+ console.error('Failed to load configuration:', error.message);
23
+ process.exit(1);
24
+ }
25
+
26
+ const Logger = require('./library/logger');
27
+ const serverLog = Logger.getInstance().child({ module: 'server' });
28
+
29
+ const activeModules = config.modules ? Object.keys(config.modules)
30
+ .filter(mod => config.modules[mod].enabled)
31
+ .join(', ') : [];
32
+ serverLog.info(`Loaded Configuration. Active modules = ${activeModules}`);
33
+
34
+ // Import modules
35
+ const SHLModule = require('./shl/shl.js');
36
+ const VCLModule = require('./vcl/vcl.js');
37
+ const xigModule = require('./xig/xig.js');
38
+ const PackagesModule = require('./packages/packages.js');
39
+ const RegistryModule = require('./registry/registry.js');
40
+ const PublisherModule = require('./publisher/publisher.js');
41
+ const TokenModule = require('./token/token.js');
42
+ const NpmProjectorModule = require('./npmprojector/npmprojector.js');
43
+ const TXModule = require('./tx/tx.js');
44
+ const packageJson = require('./package.json');
45
+
46
+ const htmlServer = require('./library/html-server');
47
+ const ServerStats = require("./stats");
48
+ const {Liquid} = require("liquidjs");
49
+ const {escapeHtml} = require("./library/utilities");
50
+ htmlServer.useLog(serverLog);
51
+
52
+ const app = express();
53
+
54
+ const PORT = process.env.PORT || config.server.port || 3000;
55
+
56
+ // Middleware
57
+ app.use(express.raw({ type: 'application/fhir+json', limit: '50mb' }));
58
+ app.use(express.raw({ type: 'application/fhir+xml', limit: '50mb' }));
59
+ app.use(express.json({ limit: '50mb' }));
60
+ app.use((req, res, next) => {
61
+ const requestId = req.headers['x-request-id'];
62
+ if (requestId) {
63
+ res.setHeader('X-Request-Id', requestId);
64
+ }
65
+ next();
66
+ });
67
+
68
+ // app.use(cors(config.server.cors));
69
+
70
+ // Module instances
71
+ const modules = {};
72
+
73
+ let stats = null;
74
+
75
+ // Initialize modules based on configuration
76
+ async function initializeModules() {
77
+ stats = new ServerStats();
78
+
79
+ // Initialize SHL module
80
+ if (config.modules?.shl?.enabled) {
81
+ try {
82
+ modules.shl = new SHLModule(stats);
83
+ await modules.shl.initialize(config.modules.shl);
84
+ app.use('/shl', modules.shl.router);
85
+ } catch (error) {
86
+ serverLog.error('Failed to initialize SHL module:', error);
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ // Initialize VCL module
92
+ if (config.modules?.vcl?.enabled) {
93
+ try {
94
+ modules.vcl = new VCLModule(stats);
95
+ await modules.vcl.initialize(config.modules.vcl);
96
+ app.use('/VCL', modules.vcl.router);
97
+ } catch (error) {
98
+ serverLog.error('Failed to initialize VCL module:', error);
99
+ throw error;
100
+ }
101
+ }
102
+
103
+ // Initialize XIG module
104
+ if (config.modules?.xig?.enabled) {
105
+ try {
106
+ await xigModule.initializeXigModule(stats);
107
+ app.use('/xig', xigModule.router);
108
+ modules.xig = xigModule;
109
+ } catch (error) {
110
+ serverLog.error('Failed to initialize XIG module:', error);
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ // Initialize Packages module
116
+ if (config.modules?.packages?.enabled) {
117
+ try {
118
+ modules.packages = new PackagesModule(stats);
119
+ await modules.packages.initialize(config.modules.packages);
120
+ app.use('/packages', modules.packages.router);
121
+ } catch (error) {
122
+ console.error('Failed to initialize Server:', error);
123
+ serverLog.error('Failed to initialize Packages module:', error);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ // Initialize Registry module
129
+ if (config.modules?.registry?.enabled) {
130
+ try {
131
+ modules.registry = new RegistryModule(stats);
132
+ await modules.registry.initialize(config.modules.registry);
133
+ app.use('/tx-reg', modules.registry.router);
134
+ } catch (error) {
135
+ serverLog.error('Failed to initialize Registry module:', error);
136
+ throw error;
137
+ }
138
+ }
139
+
140
+ // Initialize Publisher module
141
+ if (config.modules?.publisher?.enabled) {
142
+ try {
143
+ modules.publisher = new PublisherModule(stats);
144
+ await modules.publisher.initialize(config.modules.publisher);
145
+ app.use('/publisher', modules.publisher.router);
146
+ } catch (error) {
147
+ serverLog.error('Failed to initialize Publisher module:', error);
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ // Initialize Token module
153
+ if (config.modules?.token?.enabled) {
154
+ try {
155
+ modules.token = new TokenModule(stats);
156
+ await modules.token.initialize(config.modules.token);
157
+ app.use('/token', modules.token.router);
158
+ } catch (error) {
159
+ serverLog.error('Failed to initialize Token module:', error);
160
+ throw error;
161
+ }
162
+ }
163
+
164
+ // Initialize NpmProjector module
165
+ if (config.modules?.npmprojector?.enabled) {
166
+ try {
167
+ modules.npmprojector = new NpmProjectorModule(stats);
168
+ await modules.npmprojector.initialize(config.modules.npmprojector);
169
+ const basePath = NpmProjectorModule.getBasePath(config.modules.npmprojector);
170
+ app.use(basePath, modules.npmprojector.router);
171
+ } catch (error) {
172
+ serverLog.error('Failed to initialize NpmProjector module:', error);
173
+ throw error;
174
+ }
175
+ }
176
+
177
+ // Initialize TX module
178
+ // Note: TX module registers its own endpoints directly on the app
179
+ // because it supports multiple endpoints at different paths
180
+ if (config.modules?.tx?.enabled) {
181
+ try {
182
+ modules.tx = new TXModule(stats);
183
+ await modules.tx.initialize(config.modules.tx, app);
184
+ } catch (error) {
185
+ console.log(error);
186
+ serverLog.error('Failed to initialize TX module:', error);
187
+ throw error;
188
+ }
189
+ }
190
+ }
191
+
192
+ async function loadTemplates() {
193
+ htmlServer.useLog(serverLog);
194
+
195
+ try {
196
+ // Load Root template
197
+ const rootTemplatePath = path.join(__dirname, 'root-template.html');
198
+ htmlServer.loadTemplate('root', rootTemplatePath);
199
+
200
+ // Load XIG template
201
+ const xigTemplatePath = path.join(__dirname, 'xig', 'xig-template.html');
202
+ htmlServer.loadTemplate('xig', xigTemplatePath);
203
+
204
+ // Load Packages template
205
+ const packagesTemplatePath = path.join(__dirname, 'packages', 'packages-template.html');
206
+ htmlServer.loadTemplate('packages', packagesTemplatePath);
207
+
208
+ const registryTemplatePath = path.join(__dirname, 'registry', 'registry-template.html');
209
+ htmlServer.loadTemplate('registry', registryTemplatePath);
210
+
211
+ const publisherTemplatePath = path.join(__dirname, 'publisher', 'publisher-template.html');
212
+ htmlServer.loadTemplate('publisher', publisherTemplatePath);
213
+
214
+ // Load Token template
215
+ const tokenTemplatePath = path.join(__dirname, 'token', 'token-template.html');
216
+ htmlServer.loadTemplate('token', tokenTemplatePath);
217
+
218
+ } catch (error) {
219
+ serverLog.error('Failed to load templates:', error);
220
+ // Don't fail initialization if templates fail to load
221
+ }
222
+ }
223
+
224
+ async function buildRootPageContent() {
225
+ stats.requestCount++;
226
+ let mc = 0;
227
+ let content = '<div class="row mb-4">';
228
+ content += '<div class="col-12">';
229
+
230
+ content += '<h3>Available Modules</h3>';
231
+ content += '<ul class="list-group">';
232
+
233
+ // Check which modules are enabled and add them to the list
234
+ if (config.modules.packages.enabled) {
235
+ mc++;
236
+ content += '<li class="list-group-item">';
237
+ content += '<a href="/packages" class="text-decoration-none">Package Server</a>: Browse and download FHIR Implementation Guide packages';
238
+ content += '</li>';
239
+ }
240
+
241
+ if (config.modules.xig.enabled) {
242
+ mc++;
243
+ content += '<li class="list-group-item">';
244
+ content += '<a href="/xig" class="text-decoration-none">FHIR IG Statistics</a>: Statistics and analysis of FHIR Implementation Guides';
245
+ content += '</li>';
246
+ }
247
+
248
+ if (config.modules.shl.enabled) {
249
+ mc++;
250
+ content += '<li class="list-group-item">';
251
+ content += '<a href="/shl" class="text-decoration-none">SHL Server</a>: SMART Health Links management and validation';
252
+ content += '</li>';
253
+ }
254
+
255
+ if (config.modules.vcl.enabled) {
256
+ mc++;
257
+ content += '<li class="list-group-item">';
258
+ content += '<a href="/VCL" class="text-decoration-none">VCL Server</a>: ValueSet Compose Language expression parsing';
259
+ content += '</li>';
260
+ }
261
+
262
+ if (config.modules.registry && config.modules.registry.enabled) {
263
+ mc++;
264
+ content += '<li class="list-group-item">';
265
+ content += '<a href="/tx-reg" class="text-decoration-none">Terminology Server Registry</a>: ';
266
+ content += 'Discover and query FHIR terminology servers for code system and value set support';
267
+ content += '</li>';
268
+ }
269
+
270
+ if (config.modules.publisher && config.modules.publisher.enabled) {
271
+ mc++;
272
+ content += '<li class="list-group-item">';
273
+ content += '<a href="/publisher" class="text-decoration-none">FHIR Publisher</a>: ';
274
+ content += 'Manage FHIR Implementation Guide publication tasks and approvals';
275
+ content += '</li>';
276
+ }
277
+
278
+ if (config.modules.token && config.modules.token.enabled) {
279
+ mc++;
280
+ content += '<li class="list-group-item">';
281
+ content += '<a href="/token" class="text-decoration-none">Token Server</a>: ';
282
+ content += 'OAuth authentication and API key management for FHIR services';
283
+ content += '</li>';
284
+ }
285
+
286
+ if (config.modules.npmprojector && config.modules.npmprojector.enabled) {
287
+ mc++;
288
+ content += '<li class="list-group-item">';
289
+ content += '<a href="/npmprojector" class="text-decoration-none">NpmProjector</a>: ';
290
+ content += 'Hot-reloading FHIR server with FHIRPath-based search indexes';
291
+ content += '</li>';
292
+ }
293
+
294
+ if (config.modules.tx && config.modules.tx.enabled) {
295
+ content += '<li class="list-group-item">';
296
+ content += '<strong>TX Terminology Server</strong>: ';
297
+ content += 'FHIR terminology services (CodeSystem, ValueSet, ConceptMap)';
298
+ if (config.modules.tx.endpoints && config.modules.tx.endpoints.length > 0) {
299
+ content += '<ul class="mt-2 mb-0">';
300
+ for (const endpoint of config.modules.tx.endpoints) {
301
+ mc++;
302
+ content += `<li><a href="${endpoint.path}" class="text-decoration-none">${endpoint.path}</a> (FHIR v${endpoint.fhirVersion}${endpoint.context ? ', context: ' + endpoint.context : ''})</li>`;
303
+ }
304
+ content += '</ul>';
305
+ }
306
+ content += '</li>';
307
+ }
308
+
309
+ content += '</ul>';
310
+
311
+ content += '<hr/>';
312
+
313
+
314
+ // Calculate uptime
315
+ const uptimeMs = Date.now() - stats.startTime;
316
+ const uptimeSeconds = Math.floor(uptimeMs / 1000);
317
+ const uptimeDays = Math.floor(uptimeSeconds / 86400);
318
+ const uptimeHours = Math.floor((uptimeSeconds % 86400) / 3600);
319
+ const uptimeMinutes = Math.floor((uptimeSeconds % 3600) / 60);
320
+ const uptimeSecs = uptimeSeconds % 60;
321
+ let uptimeStr = '';
322
+ if (uptimeDays > 0) uptimeStr += `${uptimeDays}d `;
323
+ if (uptimeHours > 0 || uptimeDays > 0) uptimeStr += `${uptimeHours}h `;
324
+ if (uptimeMinutes > 0 || uptimeHours > 0 || uptimeDays > 0) uptimeStr += `${uptimeMinutes}m `;
325
+ uptimeStr += `${uptimeSecs}s`;
326
+
327
+ // Memory usage
328
+ const memUsage = process.memoryUsage();
329
+ const heapUsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(2);
330
+ const heapTotalMB = (memUsage.heapTotal / 1024 / 1024).toFixed(2);
331
+ const rssMB = (memUsage.rss / 1024 / 1024).toFixed(2);
332
+
333
+ content += '<table class="grid">';
334
+ content += '<tr>';
335
+ content += `<td><strong>Module Count:</strong> ${mc}</td>`;
336
+ content += `<td><strong>Uptime:</strong> ${escapeHtml(uptimeStr)}</td>`;
337
+ content += `<td><strong>Request Count:</strong> ${stats.requestCount}</td>`;
338
+ content += '</tr>';
339
+ content += '<tr>';
340
+ content += `<td><strong>Heap Used:</strong> ${heapUsedMB} MB</td>`;
341
+ content += `<td><strong>Heap Total:</strong> ${heapTotalMB} MB</td>`;
342
+ content += `<td><strong>Process Memory:</strong> ${rssMB} MB</td>`;
343
+ content += '</tr>';
344
+
345
+ content += '</table>';
346
+
347
+
348
+ // ===== Metrics Graphs =====
349
+
350
+ const liquid = new Liquid({
351
+ root: path.join(__dirname, 'tx', 'html'),
352
+ extname: '.liquid'
353
+ });
354
+ content += await liquid.renderFile('home-metrics', {
355
+ historyJson: JSON.stringify(stats.history),
356
+ startTime: stats.startTime
357
+ });
358
+
359
+ content += '</div>';
360
+ return content;
361
+ }
362
+
363
+ // eslint-disable-next-line no-unused-vars
364
+ process.on('unhandledRejection', (reason, promise) => {
365
+ console.error('Unhandled Rejection:', reason);
366
+ });
367
+
368
+ app.get('/', async (req, res) => {
369
+ // Check if client wants HTML response
370
+ const acceptsHtml = req.headers.accept && req.headers.accept.includes('text/html');
371
+
372
+ if (acceptsHtml) {
373
+ try {
374
+ const startTime = Date.now();
375
+
376
+ // Load template if not already loaded
377
+ if (!htmlServer.hasTemplate('root')) {
378
+ const templatePath = path.join(__dirname, 'root-template.html');
379
+ htmlServer.loadTemplate('root', templatePath);
380
+ }
381
+
382
+ const content = await buildRootPageContent();
383
+
384
+ // Build basic stats for root page
385
+ const stats = {
386
+ version: packageJson.version,
387
+ enabledModules: Object.keys(config.modules).filter(m => config.modules[m].enabled).length,
388
+ processingTime: Date.now() - startTime
389
+ };
390
+
391
+ const html = htmlServer.renderPage('root', config.hostName || 'FHIRsmith Server', content, stats);
392
+ res.setHeader('Content-Type', 'text/html');
393
+ res.send(html);
394
+ } catch (error) {
395
+ serverLog.error('Error rendering root page:', error);
396
+ htmlServer.sendErrorResponse(res, 'root', error);
397
+ }
398
+ } else {
399
+ // Return JSON response for API clients
400
+ const enabledModules = {};
401
+ Object.keys(config.modules).forEach(moduleName => {
402
+ if (config.modules[moduleName].enabled) {
403
+ if (moduleName === 'tx') {
404
+ // TX module has multiple endpoints
405
+ enabledModules[moduleName] = {
406
+ enabled: true,
407
+ endpoints: config.modules.tx.endpoints.map(e => ({
408
+ path: e.path,
409
+ fhirVersion: e.fhirVersion,
410
+ context: e.context || null
411
+ }))
412
+ };
413
+ } else {
414
+ enabledModules[moduleName] = {
415
+ enabled: true,
416
+ endpoint: moduleName === 'vcl' ? '/VCL' : `/${moduleName}`
417
+ };
418
+ }
419
+ }
420
+ });
421
+
422
+ res.json({
423
+ message: 'FHIR Development Server',
424
+ version: '1.0.0',
425
+ modules: enabledModules,
426
+ endpoints: {
427
+ health: '/health',
428
+ ...Object.fromEntries(
429
+ Object.keys(enabledModules)
430
+ .filter(m => m !== 'tx')
431
+ .map(m => [
432
+ m,
433
+ m === 'vcl' ? '/VCL' : `/${m}`
434
+ ])
435
+ ),
436
+ // Add TX endpoints separately
437
+ ...(enabledModules.tx ? {
438
+ tx: config.modules.tx.endpoints.map(e => e.path)
439
+ } : {})
440
+ }
441
+ });
442
+ }
443
+ });
444
+
445
+
446
+ // Serve static files
447
+ app.use(express.static(path.join(__dirname, 'static')));
448
+
449
+ // Health check endpoint
450
+ app.get('/health', async (req, res) => {
451
+ const healthStatus = {
452
+ status: 'OK',
453
+ timestamp: new Date().toISOString(),
454
+ modules: {}
455
+ };
456
+
457
+ // Get status from each enabled module
458
+ Object.keys(modules).forEach(moduleName => {
459
+ if (modules[moduleName] && typeof modules[moduleName].getStatus === 'function') {
460
+ healthStatus.modules[moduleName] = modules[moduleName].getStatus();
461
+ } else if (moduleName === 'xig') {
462
+ // XIG has different status check
463
+ let xigStatus = 'Enabled';
464
+ if (modules.xig && modules.xig.isCacheLoaded && modules.xig.isCacheLoaded()) {
465
+ xigStatus = 'Running';
466
+ } else {
467
+ xigStatus = 'Enabled but not loaded';
468
+ }
469
+ healthStatus.modules.xig = { enabled: true, status: xigStatus };
470
+ }
471
+ });
472
+
473
+ res.json(healthStatus);
474
+ });
475
+
476
+ // Initialize everything
477
+ async function startServer() {
478
+ try {
479
+ // Load HTML templates
480
+ await loadTemplates();
481
+
482
+ // Initialize modules
483
+ await initializeModules().catch(error => {
484
+ serverLog.error('Failed to initialize modules:', error);
485
+ throw error;
486
+ });
487
+
488
+ // Start server
489
+ app.listen(PORT, () => {
490
+ stats.markStarted();
491
+ serverLog.info(`=== Server running on http://localhost:${PORT} ===`);
492
+ });
493
+ if (modules.packages && config.modules.packages.enabled) {
494
+ modules.packages.startInitialCrawler();
495
+ }
496
+ } catch (error) {
497
+ serverLog.error('Failed to start server:', error);
498
+ process.exit(1);
499
+ }
500
+ }
501
+
502
+ // Graceful shutdown
503
+ process.on('SIGINT', async () => {
504
+ serverLog.info('\nShutting down server...');
505
+
506
+ // Shutdown all modules
507
+ for (const [moduleName, moduleInstance] of Object.entries(modules)) {
508
+ try {
509
+ if (moduleInstance && typeof moduleInstance.shutdown === 'function') {
510
+ serverLog.info(`Shutting down ${moduleName} module...`);
511
+ await moduleInstance.shutdown();
512
+ serverLog.info(`${moduleName} module shut down`);
513
+ }
514
+ } catch (error) {
515
+ serverLog.error(`Error shutting down ${moduleName} module:`, error);
516
+ }
517
+ }
518
+ stats.finishStats();
519
+ serverLog.info('Server shutdown complete');
520
+ process.exit(0);
521
+ });
522
+
523
+ // Start the server
524
+ startServer();
@@ -0,0 +1,5 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgS0EUuZ78vq3tAzyF
3
+ mH0tlbC6o1Krkq4El/eDSz1yq7+hRANCAAQm2z5bOrm+VNL3cZaDGtn5q7h6nv/T
4
+ NGbgoKMATA4Hx8OPIXQ5HlGgPdFeZ840PxP1OmWfIPDtmuVulyg6T6nN
5
+ -----END PRIVATE KEY-----
@@ -0,0 +1,18 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICzTCCAnOgAwIBAgIUCDt7I+MKUnXKEP+V6o2GDtpy5QcwCgYIKoZIzj0EAwIw
3
+ gYIxCzAJBgNVBAYTAlhYMREwDwYDVQQIDAhWaWN0b3JpYTETMBEGA1UEBwwKV2Fy
4
+ cmFuZHl0ZTEcMBoGA1UECgwTSGVhbHRoSW50ZXJzZWN0aW9uczEMMAoGA1UECwwD
5
+ RGV2MR8wHQYDVQQDDBZIZWFsdGhJbnRlcnNlY3Rpb25zRGV2MB4XDTI1MDYxOTE0
6
+ MTU0NVoXDTI3MDYxOTE0MTU0NVowgYIxCzAJBgNVBAYTAlhYMREwDwYDVQQIDAhW
7
+ aWN0b3JpYTETMBEGA1UEBwwKV2FycmFuZHl0ZTEcMBoGA1UECgwTSGVhbHRoSW50
8
+ ZXJzZWN0aW9uczEMMAoGA1UECwwDRGV2MR8wHQYDVQQDDBZIZWFsdGhJbnRlcnNl
9
+ Y3Rpb25zRGV2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJts+Wzq5vlTS93GW
10
+ gxrZ+au4ep7/0zRm4KCjAEwOB8fDjyF0OR5RoD3RXmfOND8T9TplnyDw7Zrlbpco
11
+ Ok+pzaOBxDCBwTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFJqZws5+2DAvsso2
12
+ IDwJY1dDTwafMB8GA1UdIwQYMBaAFHeGcWpLiEul/3p0jQvVlTzo8Nf3MD0GA1Ud
13
+ HwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwuZXhhbXBsZWRvbWFpbi5leGFtcGxlL0NS
14
+ TC9TQ0EuY3JsMDAGA1UdJQQpMCcGCysGAQQBjjePZQEBBgsrBgEEAY43j2UBAgYL
15
+ KwYBBAGON49lAQMwCgYIKoZIzj0EAwIDSAAwRQIgXhherpbA37Ut3LwmM+1vR+UK
16
+ Jkk3d9tmq2yHurjNNwwCIQC0TTkFLoO0BsLeJ2WldcVnVsPNh4tbAJoU/vNAdcv1
17
+ bw==
18
+ -----END CERTIFICATE-----