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
@@ -0,0 +1,129 @@
1
+ //
2
+ // Validate Worker - Handles $validate-code operations
3
+ //
4
+ // GET /CodeSystem/$validate-code?{params}
5
+ // POST /CodeSystem/$validate-code
6
+ // GET /CodeSystem/{id}/$validate-code?{params}
7
+ // POST /CodeSystem/{id}/$validate-code
8
+ // GET /ValueSet/$validate-code?{params}
9
+ // POST /ValueSet/$validate-code
10
+ // GET /ValueSet/{id}/$validate-code?{params}
11
+ // POST /ValueSet/{id}/$validate-code
12
+ //
13
+
14
+ const { TerminologyWorker } = require('./worker');
15
+ const {OperationOutcome, Issue} = require("../library/operation-outcome");
16
+ const {Parameters} = require("../library/parameters");
17
+ const {ValidateWorker} = require("./validate");
18
+
19
+ class BatchValidateWorker extends TerminologyWorker {
20
+
21
+ globalNames = new Set();
22
+
23
+ /**
24
+ * @param {OperationContext} opContext - Operation context
25
+ * @param {Logger} log - Logger instance
26
+ * @param {Provider} provider - Provider for code systems and resources
27
+ * @param {LanguageDefinitions} languages - Language definitions
28
+ * @param {I18nSupport} i18n - Internationalization support
29
+ */
30
+ constructor(opContext, log, provider, languages, i18n) {
31
+ super(opContext, log, provider, languages, i18n);
32
+ this.globalNames.add("tx-resource");
33
+ this.globalNames.add("url");
34
+ this.globalNames.add("valueSet");
35
+ this.globalNames.add("lenient-display-validation");
36
+ this.globalNames.add("__Accept-Language");
37
+ this.globalNames.add("__Content-Language");
38
+ }
39
+
40
+ /**
41
+ * Get operation name
42
+ * @returns {string}
43
+ */
44
+ opName() {
45
+ return 'batch-validate-code';
46
+ }
47
+
48
+ async handleValueSet(req, res) {
49
+ try {
50
+ let params = req.body;
51
+ this.addHttpParams(req, params);
52
+
53
+ let globalParams = [];
54
+ for (const p of params.parameter) {
55
+ if (this.globalNames.has(p.name)) {
56
+ globalParams.push(p);
57
+ }
58
+ }
59
+
60
+ let output = [];
61
+
62
+ for (const p of params.parameter) {
63
+ if (p.name == 'validation') {
64
+ let op = new Parameters();
65
+ op.jsonObj.parameter = [];
66
+ for (const gp of globalParams) {
67
+ let exists = p.resource.parameter.find(pp => gp.name == pp.name);
68
+ if (gp.name == 'tx-resource' || !exists) {
69
+ op.jsonObj.parameter.push(gp);
70
+ }
71
+ }
72
+ op.jsonObj.parameter.push(...p.resource.parameter);
73
+
74
+ let worker = new ValidateWorker(this.opContext.copy(), this.log, this.provider, this.languages, this.i18n);
75
+ try {
76
+ let p;
77
+ if (this.hasValueSet(op.jsonObj.parameter)) {
78
+ p = await worker.handleValueSetInner(op.jsonObj);
79
+ } else {
80
+ p = await worker.handleCodeSystemInner(op.jsonObj);
81
+ }
82
+ output.push({name: "validation", resource : p});
83
+ } catch (error) {
84
+ this.log.error(error);
85
+ if (error instanceof Issue) {
86
+ let op = new OperationOutcome();
87
+ op.addIssue(error);
88
+ output.push({name: "validation", resource : op.jsonObj});
89
+ } else {
90
+ output.push({name: "validation", resource : this.operationOutcome('error', error.issueCode || 'exception', error.message) } );
91
+ }
92
+ }
93
+ }
94
+ }
95
+ let result = { resourceType : "Parameters", parameter: output}
96
+ req.logInfo = `${output.length} validations`;
97
+ return res.json(result);
98
+ } catch (error) {
99
+ this.log.error(error);
100
+ return res.status(error.statusCode || 500).json(this.operationOutcome(
101
+ 'error', error.issueCode || 'exception', error.message));
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Build an OperationOutcome
107
+ */
108
+ operationOutcome(severity, code, message) {
109
+ return {
110
+ resourceType: 'OperationOutcome',
111
+ issue: [{
112
+ severity,
113
+ code,
114
+ details: {
115
+ text: message
116
+ },
117
+ diagnostics: message
118
+ }]
119
+ };
120
+ }
121
+
122
+ hasValueSet(parameter) {
123
+ return parameter.find(p => p.name == 'url' || p.name == 'valueSet');
124
+ }
125
+ }
126
+
127
+ module.exports = {
128
+ BatchValidateWorker
129
+ };
@@ -0,0 +1,361 @@
1
+ //
2
+ // Batch Worker - Handles batch terminology operations
3
+ //
4
+ // POST /tx/r5 (with Bundle)
5
+ //
6
+
7
+ const { TerminologyWorker } = require('./worker');
8
+ const { Issue, OperationOutcome } = require('../library/operation-outcome');
9
+
10
+ class BatchWorker extends TerminologyWorker {
11
+ /**
12
+ * @param {OperationContext} opContext - Operation context
13
+ * @param {Logger} log - Logger instance
14
+ * @param {Provider} provider - Provider for terminology resources
15
+ * @param {LanguageDefinitions} languages - Language definitions
16
+ * @param {I18nSupport} i18n - Internationalization support
17
+ * @param {Object} workers - Map of operation workers (validate, lookup, subsumes, translate, expand)
18
+ */
19
+ constructor(opContext, log, provider, languages, i18n, workers) {
20
+ super(opContext, log, provider, languages, i18n);
21
+ this.workers = workers;
22
+ }
23
+
24
+ /**
25
+ * Get operation name
26
+ * @returns {string}
27
+ */
28
+ opName() {
29
+ return 'batch-validate-code';
30
+ }
31
+
32
+ /**
33
+ * Handle a batch request
34
+ * POST with Bundle resource
35
+ * @param {express.Request} req - Express request
36
+ * @param {express.Response} res - Express response
37
+ */
38
+ async handle(req, res) {
39
+ try {
40
+ await this.handleBatch(req, res);
41
+ } catch (error) {
42
+ this.log.error(error);
43
+ if (error instanceof Issue) {
44
+ const oo = new OperationOutcome();
45
+ oo.addIssue(error);
46
+ return res.status(error.statusCode || 500).json(oo.jsonObj);
47
+ } else {
48
+ return res.status(error.statusCode || 500).json(this.operationOutcome(
49
+ 'error', error.issueCode || 'exception', error.message));
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Handle the batch operation
56
+ * @param {express.Request} req - Express request
57
+ * @param {express.Response} res - Express response
58
+ */
59
+ async handleBatch(req, res) {
60
+ this.deadCheck('batch');
61
+
62
+ const bundle = req.body;
63
+
64
+ // Validate input is a Bundle
65
+ if (!bundle || bundle.resourceType !== 'Bundle') {
66
+ throw new Issue('error', 'invalid', null, null,
67
+ 'Request body must be a Bundle resource', null, 400);
68
+ }
69
+
70
+ // Must be a batch bundle
71
+ if (bundle.type !== 'batch') {
72
+ throw new Issue('error', 'invalid', null, null,
73
+ `Bundle type must be 'batch', got '${bundle.type}'`, null, 400);
74
+ }
75
+
76
+ // Process entries
77
+ const entries = bundle.entry || [];
78
+ const responseEntries = [];
79
+
80
+ for (let i = 0; i < entries.length; i++) {
81
+ this.deadCheck(`batch-entry-${i}`);
82
+ const entry = entries[i];
83
+ const responseEntry = await this.processEntry(entry, i);
84
+ responseEntries.push(responseEntry);
85
+ }
86
+
87
+ // Build response bundle
88
+ const responseBundle = {
89
+ resourceType: 'Bundle',
90
+ type: 'batch-response',
91
+ entry: responseEntries
92
+ };
93
+
94
+ return res.status(200).json(responseBundle);
95
+ }
96
+
97
+ /**
98
+ * Process a single batch entry
99
+ * @param {Object} entry - Bundle entry
100
+ * @param {number} index - Entry index for error reporting
101
+ * @returns {Object} Response entry
102
+ */
103
+ async processEntry(entry, index) {
104
+ try {
105
+ // Validate entry has request
106
+ if (!entry.request) {
107
+ return this.errorEntry(400, 'invalid', `Entry ${index}: missing request element`);
108
+ }
109
+
110
+ const request = entry.request;
111
+ const method = request.method?.toUpperCase();
112
+ const url = request.url;
113
+
114
+ if (!method) {
115
+ return this.errorEntry(400, 'invalid', `Entry ${index}: missing request.method`);
116
+ }
117
+
118
+ if (!url) {
119
+ return this.errorEntry(400, 'invalid', `Entry ${index}: missing request.url`);
120
+ }
121
+
122
+ // Parse the URL to determine the operation
123
+ const parsedOp = this.parseOperationUrl(url);
124
+
125
+ if (!parsedOp) {
126
+ return this.errorEntry(400, 'not-supported',
127
+ `Entry ${index}: unsupported URL pattern '${url}'`);
128
+ }
129
+
130
+ // Get the appropriate worker
131
+ const worker = this.getWorkerForOperation(parsedOp.operation);
132
+
133
+ if (!worker) {
134
+ return this.errorEntry(501, 'not-supported',
135
+ `Entry ${index}: operation '${parsedOp.operation}' not supported`);
136
+ }
137
+
138
+ // Build a mock request object for the worker
139
+ const mockReq = this.buildMockRequest(method, parsedOp, entry.resource, request);
140
+
141
+ // Build a mock response object to capture the result
142
+ const mockRes = this.buildMockResponse();
143
+
144
+ // Execute the operation
145
+ if (parsedOp.instanceId) {
146
+ await worker.handleInstance(mockReq, mockRes);
147
+ } else {
148
+ await worker.handle(mockReq, mockRes);
149
+ }
150
+
151
+ // Build response entry from captured result
152
+ return {
153
+ resource: mockRes.body,
154
+ response: {
155
+ status: `${mockRes.statusCode}`,
156
+ outcome: mockRes.statusCode >= 400 ? mockRes.body : undefined
157
+ }
158
+ };
159
+
160
+ } catch (error) {
161
+ this.log.error(error);
162
+ const statusCode = error.statusCode || 500;
163
+ const issueCode = error.issueCode || 'exception';
164
+
165
+ return this.errorEntry(statusCode, issueCode, error.message);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Parse an operation URL to extract the operation type and parameters
171
+ * @param {string} url - Request URL (e.g., "CodeSystem/$lookup?system=...&code=...")
172
+ * @returns {Object|null} Parsed operation info or null if not recognized
173
+ */
174
+ parseOperationUrl(url) {
175
+ // Remove leading slash if present
176
+ const cleanUrl = url.startsWith('/') ? url.substring(1) : url;
177
+
178
+ // Split URL and query string
179
+ const [path, queryString] = cleanUrl.split('?');
180
+ const queryParams = this.parseQueryString(queryString);
181
+
182
+ // Patterns to match:
183
+ // CodeSystem/$lookup
184
+ // CodeSystem/{id}/$lookup
185
+ // CodeSystem/$validate-code
186
+ // CodeSystem/{id}/$validate-code
187
+ // CodeSystem/$subsumes
188
+ // CodeSystem/{id}/$subsumes
189
+ // ValueSet/$expand
190
+ // ValueSet/{id}/$expand
191
+ // ValueSet/$validate-code
192
+ // ValueSet/{id}/$validate-code
193
+ // ConceptMap/$translate
194
+ // ConceptMap/{id}/$translate
195
+
196
+ // Type-level operations
197
+ const typeLevelMatch = path.match(/^(CodeSystem|ValueSet|ConceptMap)\/\$(.+)$/);
198
+ if (typeLevelMatch) {
199
+ return {
200
+ resourceType: typeLevelMatch[1],
201
+ operation: typeLevelMatch[2],
202
+ instanceId: null,
203
+ queryParams
204
+ };
205
+ }
206
+
207
+ // Instance-level operations
208
+ const instanceLevelMatch = path.match(/^(CodeSystem|ValueSet|ConceptMap)\/([^/$]+)\/\$(.+)$/);
209
+ if (instanceLevelMatch) {
210
+ return {
211
+ resourceType: instanceLevelMatch[1],
212
+ operation: instanceLevelMatch[3],
213
+ instanceId: instanceLevelMatch[2],
214
+ queryParams
215
+ };
216
+ }
217
+
218
+ return null;
219
+ }
220
+
221
+ /**
222
+ * Parse a query string into an object
223
+ * @param {string} queryString - Query string (without leading ?)
224
+ * @returns {Object} Parsed query parameters
225
+ */
226
+ parseQueryString(queryString) {
227
+ if (!queryString) {
228
+ return {};
229
+ }
230
+
231
+ const params = {};
232
+ const pairs = queryString.split('&');
233
+
234
+ for (const pair of pairs) {
235
+ const [key, value] = pair.split('=').map(decodeURIComponent);
236
+ if (key) {
237
+ // Handle repeated parameters
238
+ if (params[key] !== undefined) {
239
+ if (Array.isArray(params[key])) {
240
+ params[key].push(value);
241
+ } else {
242
+ params[key] = [params[key], value];
243
+ }
244
+ } else {
245
+ params[key] = value;
246
+ }
247
+ }
248
+ }
249
+
250
+ return params;
251
+ }
252
+
253
+ /**
254
+ * Get the worker for a given operation
255
+ * @param {string} operation - Operation name (e.g., 'lookup', 'validate-code', 'expand')
256
+ * @returns {Object|null} Worker instance or null
257
+ */
258
+ getWorkerForOperation(operation) {
259
+ const operationMap = {
260
+ 'lookup': this.workers.lookup,
261
+ 'validate-code': this.workers.validate,
262
+ 'subsumes': this.workers.subsumes,
263
+ 'expand': this.workers.expand,
264
+ 'translate': this.workers.translate
265
+ };
266
+
267
+ return operationMap[operation] || null;
268
+ }
269
+
270
+ /**
271
+ * Build a mock request object for a worker
272
+ * @param {string} method - HTTP method
273
+ * @param {Object} parsedOp - Parsed operation info
274
+ * @param {Object} resource - Request resource (for POST)
275
+ * @param {Object} request - Original request element
276
+ * @returns {Object} Mock request object
277
+ */
278
+ buildMockRequest(method, parsedOp, resource) {
279
+ return {
280
+ method,
281
+ params: {
282
+ id: parsedOp.instanceId
283
+ },
284
+ query: parsedOp.queryParams,
285
+ body: resource || {},
286
+ headers: {
287
+ 'content-type': 'application/json',
288
+ 'accept': 'application/json'
289
+ },
290
+ get: function(header) {
291
+ return this.headers[header.toLowerCase()];
292
+ }
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Build a mock response object to capture worker output
298
+ * @returns {Object} Mock response object
299
+ */
300
+ buildMockResponse() {
301
+ const mockRes = {
302
+ statusCode: 200,
303
+ body: null,
304
+ status: function(code) {
305
+ this.statusCode = code;
306
+ return this;
307
+ },
308
+ json: function(body) {
309
+ this.body = body;
310
+ return this;
311
+ }
312
+ };
313
+ return mockRes;
314
+ }
315
+
316
+ /**
317
+ * Build an error response entry
318
+ * @param {number} statusCode - HTTP status code
319
+ * @param {string} issueCode - FHIR issue code
320
+ * @param {string} message - Error message
321
+ * @returns {Object} Error entry
322
+ */
323
+ errorEntry(statusCode, issueCode, message) {
324
+ const outcome = {
325
+ resourceType: 'OperationOutcome',
326
+ issue: [{
327
+ severity: 'error',
328
+ code: issueCode,
329
+ diagnostics: message
330
+ }]
331
+ };
332
+
333
+ return {
334
+ resource: outcome,
335
+ response: {
336
+ status: `${statusCode}`,
337
+ outcome: outcome
338
+ }
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Build an OperationOutcome
344
+ * @param {string} severity - error, warning, information
345
+ * @param {string} code - Issue code
346
+ * @param {string} message - Diagnostic message
347
+ * @returns {Object} OperationOutcome resource
348
+ */
349
+ operationOutcome(severity, code, message) {
350
+ return {
351
+ resourceType: 'OperationOutcome',
352
+ issue: [{
353
+ severity,
354
+ code,
355
+ diagnostics: message
356
+ }]
357
+ };
358
+ }
359
+ }
360
+
361
+ module.exports = BatchWorker;
@@ -0,0 +1,32 @@
1
+ //
2
+ // Closure Worker - Handles ConceptMap $closure operation
3
+ //
4
+ // GET /ConceptMap/$closure?{params}
5
+ // POST /ConceptMap/$closure
6
+ //
7
+
8
+ class ClosureWorker {
9
+ /**
10
+ * Handle a $closure request
11
+ * @param {express.Request} req - Express request (with txProvider attached)
12
+ * @param {express.Response} res - Express response
13
+ * @param {Object} log - Logger instance
14
+ */
15
+ static handle(req, res) {
16
+ const params = req.method === 'POST' ? req.body : req.query;
17
+
18
+ this.log.debug('ConceptMap $closure with params:', params);
19
+
20
+ // TODO: Implement closure logic using provider
21
+ res.status(501).json({
22
+ resourceType: 'OperationOutcome',
23
+ issue: [{
24
+ severity: 'error',
25
+ code: 'not-supported',
26
+ diagnostics: 'ConceptMap $closure operation not yet implemented'
27
+ }]
28
+ });
29
+ }
30
+ }
31
+
32
+ module.exports = ClosureWorker;