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,904 @@
1
+ const { TerminologyError } = require('../operation-context');
2
+ const { CodeSystem } = require('../library/codesystem');
3
+ const ValueSet = require('../library/valueset');
4
+ const {VersionUtilities} = require("../../library/version-utilities");
5
+ const {getValuePrimitive} = require("../../library/utilities");
6
+ const {Issue} = require("../library/operation-outcome");
7
+ const {Languages} = require("../../library/languages");
8
+ const {ConceptMap} = require("../library/conceptmap");
9
+ const {Renderer} = require("../library/renderer");
10
+
11
+ /**
12
+ * Custom error for terminology setup issues
13
+ */
14
+ class TerminologySetupError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'TerminologySetupError';
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Abstract base class for terminology operations
23
+ */
24
+ class TerminologyWorker {
25
+ usedSources = [];
26
+ additionalResources = []; // Resources provided via tx-resource parameter or cache
27
+ foundParameters = [];
28
+ renderer;
29
+
30
+ /**
31
+ * @param {OperationContext} opContext - Operation context
32
+ * @param {Logger} log - Provider for code systems and resources
33
+ * @param {Provider} provider - Provider for code systems and resources
34
+ * @param {LanguageDefinitions} languages - Language definitions
35
+ * @param {I18nSupport} i18n - Internationalization support
36
+ */
37
+ constructor(opContext, log, provider, languages, i18n) {
38
+ this.opContext = opContext;
39
+ this.log = log;
40
+ this.provider = provider;
41
+ this.languages = languages;
42
+ this.i18n = i18n;
43
+ this.noCacheThisOne = false;
44
+ this.params = null; // Will be set by subclasses
45
+ this.renderer = new Renderer(i18n, languages, provider);
46
+ }
47
+
48
+ /**
49
+ * Abstract method to get operation name
50
+ * @returns {string} Operation name
51
+ */
52
+ opName() {
53
+ return '??';
54
+ }
55
+
56
+ /**
57
+ * Abstract method to get value set handle
58
+ * @returns {ValueSet} Value set being processed
59
+ */
60
+ vsHandle() {
61
+ throw new Error('vsHandle() must be implemented by subclass');
62
+ }
63
+
64
+ /**
65
+ * Check if operation should be terminated due to time/cost limits
66
+ * @param {string} place - Location identifier for debugging
67
+ */
68
+ deadCheck(place = 'unknown') {
69
+ this.opContext.deadCheck(place);
70
+ }
71
+
72
+ /**
73
+ * Add cost diagnostics to an error
74
+ * @param {TooCostlyError} e - The error to enhance
75
+ * @returns {TooCostlyError} Enhanced error
76
+ */
77
+ costDiags(e) {
78
+ e.diagnostics = this.opContext.diagnostics();
79
+ return e;
80
+ }
81
+
82
+ /**
83
+ * Find a resource in additional resources by URL and version
84
+ * @param {string} url - Resource URL
85
+ * @param {string} version - Resource version (optional)
86
+ * @param {string} resourceType - Expected resource type
87
+ * @param {boolean} error - Whether to throw error if type mismatch
88
+ * @returns {CodeSystem|ValueSet|null} Found resource or null
89
+ */
90
+ findInAdditionalResources(url, version = '', resourceType, error = true) {
91
+ if (!this.additionalResources || this.additionalResources.length === 0) {
92
+ return null;
93
+ }
94
+
95
+ const matches = [];
96
+
97
+ for (const resource of this.additionalResources) {
98
+ this.deadCheck('findInAdditionalResources');
99
+
100
+ if (url && ((resource.url === url) || (resource.vurl === url)) &&
101
+ (!version || VersionUtilities.versionMatchesByAlgorithm(version, resource.version, resource.versionAlgorithm()))) {
102
+
103
+ if (resource.resourceType !== resourceType) {
104
+ if (error) {
105
+ throw new Error(`Attempt to reference ${url} as a ${resourceType} when it's a ${resource.resourceType}`);
106
+ } else {
107
+ return null;
108
+ }
109
+ }
110
+ matches.push(resource);
111
+ }
112
+ }
113
+
114
+ if (matches.length === 0) {
115
+ return null;
116
+ } else {
117
+ // Find the latest version
118
+ let latest = 0;
119
+ for (let i = 1; i < matches.length; i++) {
120
+ if (VersionUtilities.isSemVer(matches[latest].version) && VersionUtilities.isSemVer(matches[i].version) && VersionUtilities.isThisOrLater(matches[latest].version, matches[i].version)) {
121
+ latest = i;
122
+ }
123
+ }
124
+ return matches[latest];
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Find and load a code system provider
130
+ * @param {string} url - Code system URL
131
+ * @param {string} version - Code system version (optional)
132
+ * @param {TxParameters} params - Operation parameters
133
+ * @param {Array<string>} kinds - Allowed content modes
134
+ * @param {OperationOutcome} op - Op for errors
135
+ * * @param {boolean} nullOk - Whether null result is acceptable
136
+ * @returns {CodeSystemProvider|null} Code system provider or null
137
+ */
138
+ async findCodeSystem(url, version = '', params, kinds = ['complete'], op, nullOk = false, checkVer = false, noVParams = false) {
139
+ if (!url) {
140
+ return null;
141
+ }
142
+
143
+ if (!noVParams) {
144
+ version = this.determineVersionBase(url, version, params);
145
+ }
146
+ let codeSystemResource = null;
147
+ let provider = null;
148
+ const supplements = this.loadSupplements(url, version);
149
+
150
+ // First check additional resources
151
+ codeSystemResource = this.findInAdditionalResources(url, version, 'CodeSystem', !nullOk);
152
+
153
+ if (codeSystemResource) {
154
+ if (codeSystemResource.content === 'complete') {
155
+ // Create provider from complete code system
156
+ provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
157
+ }
158
+ }
159
+
160
+ // If no provider from additional resources, try main provider
161
+ if (!provider) {
162
+ provider = await this.provider.getCodeSystemProvider(this.opContext, url, version, supplements);
163
+ }
164
+
165
+ // If still no provider but we have a code system with allowed content mode
166
+ if (!provider && codeSystemResource && kinds.includes(codeSystemResource.content)) {
167
+ provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
168
+ }
169
+
170
+ if (!provider && !nullOk) {
171
+ if (!version) {
172
+ throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_EXP", this.i18n.translate("UNKNOWN_CODESYSTEM_EXP", params.FHTTPLanguages, [url]), "not-found", 404);
173
+ } else {
174
+ const versions = await this.listVersions(url);
175
+ if (versions.length === 0) {
176
+ throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_VERSION_EXP_NONE", this.i18n.translate("UNKNOWN_CODESYSTEM_VERSION_EXP_NONE", params.FHTTPLanguages, [url, version]), "not-found", 404);
177
+ } else {
178
+ throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_VERSION_EXP", this.i18n.translate("UNKNOWN_CODESYSTEM_VERSION_EXP", params.FHTTPLanguages, [url, version, this.presentVersionList(versions)]), "not-found", 404);
179
+ }
180
+ }
181
+ }
182
+ if (provider) {
183
+ if (checkVer) {
184
+ this.checkVersion(url, provider.version(), params, provider.versionAlgorithm(), op);
185
+ }
186
+ }
187
+
188
+ return provider;
189
+ }
190
+
191
+ /**
192
+ * List available versions for a code system
193
+ * @param {string} url - Code system URL
194
+ * @returns {Array<string>} Available versions
195
+ */
196
+ async listVersions(url) {
197
+ const versions = new Set();
198
+
199
+ // Check additional resources
200
+ if (this.additionalResources) {
201
+ for (const resource of this.additionalResources) {
202
+ this.deadCheck('listVersions-additional');
203
+ if (resource.url === url && resource.version) {
204
+ versions.add(resource.version);
205
+ }
206
+ }
207
+ }
208
+
209
+ // Check main provider
210
+ const providerVersions = await this.provider.listCodeSystemVersions(url);
211
+ for (const version of providerVersions) {
212
+ this.deadCheck('listVersions-provider');
213
+ versions.add(version);
214
+ }
215
+
216
+ return Array.from(versions).sort();
217
+ }
218
+
219
+ async listDisplaysFromCodeSystem(displays, cs, c) {
220
+ // list all known language displays
221
+ await cs.designations(c, displays);
222
+ displays.source = cs;
223
+ }
224
+
225
+ listDisplaysFromConcept(displays, c) {
226
+ // list all known provided displays
227
+ // todo: supplements
228
+ for (let ccd of c.designations || []) {
229
+ displays.addDesignationFromConcept(ccd);
230
+ }
231
+ }
232
+
233
+ listDisplaysFromIncludeConcept(displays, c, vs) {
234
+ if (c.display) {
235
+ displays.baseLang = this.languages.parse(vs.language);
236
+ displays.addDesignation(true, "active", '', '', c.display.trim());
237
+ }
238
+ for (let cd of c.designations || []) {
239
+ // see https://chat.fhir.org/#narrow/stream/179202-terminology/topic/ValueSet.20designations.20and.20languages
240
+ displays.addDesignationFromConcept(cd);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Load supplements for a code system
246
+ * @param {string} url - Code system URL
247
+ * @param {string} version - Code system version
248
+ * @returns {Array<CodeSystem>} Supplement code systems
249
+ */
250
+ loadSupplements(url, version = '') {
251
+ const supplements = [];
252
+
253
+ if (!this.additionalResources) {
254
+ return supplements;
255
+ }
256
+
257
+ for (const resource of this.additionalResources) {
258
+ this.deadCheck('loadSupplements');
259
+ if (resource.resourceType === 'CodeSystem' && resource instanceof CodeSystem) {
260
+ const cs = resource;
261
+ // Check if this code system supplements the target URL
262
+ const supplementsUrl = cs.jsonObj.supplements;
263
+
264
+ if (!supplementsUrl) {
265
+ continue;
266
+ }
267
+
268
+ // Handle exact URL match (no version specified in supplements)
269
+ if (supplementsUrl === url) {
270
+ // If we're looking for a specific version, only include if no version in supplements URL
271
+ if (!version) {
272
+ supplements.push(cs);
273
+ }
274
+ continue;
275
+ }
276
+
277
+ // Handle versioned URL (format: url|version)
278
+ if (supplementsUrl.startsWith(`${url}|`)) {
279
+ if (!version) {
280
+ // No version specified in search, include all supplements for this URL
281
+ supplements.push(cs);
282
+ } else {
283
+ // Version specified, check if it matches the tail of supplements URL
284
+ const supplementsVersion = supplementsUrl.substring(`${url}|`.length);
285
+ if (supplementsVersion === version || VersionUtilities.versionMatches(supplementsVersion, version)) {
286
+ supplements.push(cs);
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+
293
+ return supplements;
294
+ }
295
+
296
+ /**
297
+ * Check supplements for a code system provider
298
+ * @param {CodeSystemProvider} cs - Code system provider
299
+ * @param {Object} src - Source element (for extensions)
300
+ */
301
+ checkSupplements(cs, src, requiredSupplements) {
302
+ // Check for required supplements in extensions
303
+ if (src && src.getExtensions) {
304
+ const supplementExtensions = src.getExtensions('http://hl7.org/fhir/StructureDefinition/valueset-supplement');
305
+ for (const ext of supplementExtensions) {
306
+ const supplementUrl = ext.valueString || ext.valueUri;
307
+ if (supplementUrl && !cs.hasSupplement(this.opContext, supplementUrl)) {
308
+ throw new TerminologyError(`ValueSet depends on supplement '${supplementUrl}' on ${cs.systemUri} that is not known`);
309
+ }
310
+ }
311
+ }
312
+
313
+ // Remove required supplements that are satisfied
314
+ for (let i = requiredSupplements.length - 1; i >= 0; i--) {
315
+ if (cs.hasSupplement(requiredSupplements[i])) {
316
+ requiredSupplements.splice(i, 1);
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Find a ValueSet by URL and optional version
323
+ * @param {string} url - ValueSet URL (may include |version)
324
+ * @param {string} version - ValueSet version (optional, overrides URL version)
325
+ * @returns {ValueSet|null} Found ValueSet or null
326
+ */
327
+ async findValueSet(url, version = '') {
328
+ if (!url) {
329
+ return null;
330
+ }
331
+
332
+ // Parse URL|version format
333
+ let effectiveUrl = url;
334
+ let effectiveVersion = version;
335
+
336
+ if (!effectiveVersion && url.includes('|')) {
337
+ const parts = url.split('|');
338
+ effectiveUrl = parts[0];
339
+ effectiveVersion = parts[1];
340
+ }
341
+
342
+ // First check additional resources
343
+ const fromAdditional = this.findInAdditionalResources(effectiveUrl, effectiveVersion, 'ValueSet', false);
344
+ if (fromAdditional) {
345
+ return fromAdditional;
346
+ }
347
+
348
+ // Then try the provider
349
+ if (this.provider && this.provider.findValueSet) {
350
+ const vs = await this.provider.findValueSet(this.opContext, effectiveUrl, effectiveVersion);
351
+ if (vs) {
352
+ return vs;
353
+ }
354
+ }
355
+
356
+ return null;
357
+ }
358
+
359
+ /**
360
+ * Apply version pinning rules from parameters
361
+ * @param {string} url - ValueSet URL
362
+ * @returns {string} Potentially versioned URL
363
+ */
364
+ pinValueSet(url) {
365
+ if (!url || !this.params) {
366
+ return url;
367
+ }
368
+
369
+ let baseUrl = url.includes("|") ? url.substring(0, url.indexOf("|")) : url;
370
+ let version = url.includes("|") ? url.substring(url.indexOf("|") + 1) : null;
371
+ version = this.determineVersionBase(url, version, this.params);
372
+ return version ? baseUrl+"|"+version : url;
373
+ }
374
+
375
+ /**
376
+ * Build a canonical URL from system and version
377
+ * @param {string} system - System URL
378
+ * @param {string} version - Version (optional)
379
+ * @returns {string} Canonical URL (system|version or just system)
380
+ */
381
+ canonical(system, version = '') {
382
+ if (!system) return '';
383
+ if (!version) return system;
384
+ return `${system}|${version}`;
385
+ }
386
+
387
+ /**
388
+ * Parse a canonical URL into system and version parts
389
+ * @param {string} canonical - Canonical URL (may include |version)
390
+ * @returns {{system: string, version: string}}
391
+ */
392
+ parseCanonical(canonical) {
393
+ if (!canonical) {
394
+ return { system: '', version: '' };
395
+ }
396
+
397
+ const pipeIndex = canonical.indexOf('|');
398
+ if (pipeIndex < 0) {
399
+ return { system: canonical, version: '' };
400
+ }
401
+
402
+ return {
403
+ system: canonical.substring(0, pipeIndex),
404
+ version: canonical.substring(pipeIndex + 1)
405
+ };
406
+ }
407
+
408
+ /**
409
+ * Process a ValueSet, recording context and extracting embedded expansion parameters
410
+ * @param {Object} vs - ValueSet resource (raw JSON)
411
+ * @param {Object} params - Parameters resource to add extracted params to
412
+ */
413
+ seeValueSet(vs, params) {
414
+ // Build canonical URL from url and version
415
+ const vurl = vs.url ? (vs.url + (vs.version ? '|' + vs.version : '')) : null;
416
+ if (vurl) {
417
+ this.opContext.seeContext(vurl);
418
+ }
419
+ // Check for expansion parameter extensions on compose
420
+ if (vs.jsonObj.compose && vs.jsonObj.compose.extension) {
421
+ for (const ext of vs.jsonObj.compose.extension) {
422
+ if (ext.url === 'http://hl7.org/fhir/StructureDefinition/valueset-expansion-parameter' ||
423
+ ext.url === 'http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter') {
424
+ // Get name and value from nested extensions
425
+ const nameExt = ext.extension?.find(e => e.url === 'name');
426
+ const valueExt = ext.extension?.find(e => e.url === 'value');
427
+
428
+ if (nameExt && valueExt) {
429
+ const name = nameExt.valueString || nameExt.valueCode;
430
+ if (name) {
431
+ this.params.seeParameter(name, valueExt, false);
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+ if (!params.FHTTPLanguages && vs.jsonObj.language) {
438
+ params.HTTPLanguages = Languages.fromAcceptLanguage(vs.jsonObj.language, this.languages, !this.isValidating());
439
+ }
440
+ }
441
+
442
+ isValidating() {
443
+ return false;
444
+ }
445
+
446
+ // ========== Parameter Handling ==========
447
+
448
+ /**
449
+ * Build a Parameters resource from the request
450
+ * Handles GET query params, POST form body, and POST Parameters resource
451
+ * @param {express.Request} req
452
+ * @returns {Object} Parameters resource
453
+ */
454
+ buildParameters(req) {
455
+ // If POST with Parameters resource, use directly
456
+ if (req.method === 'POST' && req.body && req.body.resourceType === 'Parameters') {
457
+ return req.body;
458
+ }
459
+
460
+ // Convert query params or form body to Parameters
461
+ const source = req.method === 'POST' ? {...req.query, ...req.body} : req.query;
462
+ const params = {
463
+ resourceType: 'Parameters',
464
+ parameter: []
465
+ };
466
+
467
+ for (const [name, value] of Object.entries(source)) {
468
+ if (value === undefined || value === null) continue;
469
+
470
+ if (Array.isArray(value)) {
471
+ // Repeating parameter
472
+ for (const v of value) {
473
+ params.parameter.push({name, valueString: String(v)});
474
+ }
475
+ } else if (typeof value === 'object') {
476
+ // Could be a resource or complex type - check resourceType
477
+ if (value.resourceType) {
478
+ params.parameter.push({name, resource: value});
479
+ } else {
480
+ // Assume it's a complex type like Coding or CodeableConcept
481
+ params.parameter.push(this.buildComplexParameter(name, value));
482
+ }
483
+ } else {
484
+ params.parameter.push({name, valueString: String(value)});
485
+ }
486
+ }
487
+
488
+ return params;
489
+ }
490
+
491
+ /**
492
+ * Build a parameter for complex types
493
+ */
494
+ buildComplexParameter(name, value) {
495
+ // Detect type based on structure
496
+ if (value.system !== undefined || value.code !== undefined || value.display !== undefined) {
497
+ return {name, valueCoding: value};
498
+ }
499
+ if (value.coding !== undefined || value.text !== undefined) {
500
+ return {name, valueCodeableConcept: value};
501
+ }
502
+ // Fallback - stringify
503
+ return {name, valueString: JSON.stringify(value)};
504
+ }
505
+
506
+
507
+ addHttpParams(req, params) {
508
+ if (req.headers && req.headers['accept-language']) {
509
+ params.parameter.push({name: '__Accept-Language', valueCode: req.headers['accept-language']});
510
+ }
511
+ if (req.headers && req.headers['content-language']) {
512
+ params.parameter.push({name: '__Content-Language', valueCode: req.headers['content-language']});
513
+ }
514
+ }
515
+
516
+ // ========== Additional Resources Handling ==========
517
+
518
+ /**
519
+ * Set up additional resources from tx-resource parameters and cache
520
+ * @param {Object} params - Parameters resource
521
+ */
522
+ setupAdditionalResources(params) {
523
+ if (!params || !params.parameter) return;
524
+
525
+ // Collect tx-resource parameters (resources provided inline)
526
+ const txResources = [];
527
+ for (const param of params.parameter) {
528
+ this.deadCheck('setupAdditionalResources');
529
+ if (param.name === 'tx-resource' && param.resource) {
530
+ let res = this.wrapRawResource(param.resource);
531
+ if (res) {
532
+ txResources.push(res);
533
+ }
534
+ }
535
+ }
536
+
537
+ // Check for cache-id
538
+ const cacheIdParam = this.findParameter(params, 'cache-id');
539
+ const cacheId = cacheIdParam ? this.getParameterValue(cacheIdParam) : null;
540
+
541
+ if (cacheId && this.opContext.resourceCache) {
542
+ // Merge tx-resources with cached resources
543
+ if (txResources.length > 0) {
544
+ this.opContext.resourceCache.add(cacheId, txResources);
545
+ }
546
+
547
+ // Set additional resources to all resources for this cache-id
548
+ this.additionalResources = this.opContext.resourceCache.get(cacheId);
549
+ } else {
550
+ // No cache-id, just use the tx-resources directly
551
+ this.additionalResources = txResources;
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Wrap a raw resource in its appropriate class wrapper
557
+ * @param {Object} resource - Raw resource object
558
+ * @returns {CodeSystem|ValueSet|null} Wrapped resource or null
559
+ */
560
+ wrapRawResource(resource) {
561
+ if (resource.resourceType === 'CodeSystem') {
562
+ return new CodeSystem(resource, this.provider.getFhirVersion());
563
+ }
564
+ if (resource.resourceType === 'ValueSet') {
565
+ return new ValueSet(resource, this.provider.getFhirVersion());
566
+ }
567
+ if (resource.resourceType === 'ConceptMap') {
568
+ return new ConceptMap(resource, this.provider.getFhirVersion());
569
+ }
570
+ return null;
571
+ }
572
+
573
+ // ========== Parameters Handling ==========
574
+
575
+ /**
576
+ * Convert query parameters to a Parameters resource
577
+ * @param {Object} query - Query parameters
578
+ * @returns {Object} Parameters resource
579
+ */
580
+ queryToParameters(query) {
581
+ const params = {
582
+ resourceType: 'Parameters',
583
+ parameter: []
584
+ };
585
+
586
+ if (!query) return params;
587
+
588
+ for (const [name, value] of Object.entries(query)) {
589
+ if (Array.isArray(value)) {
590
+ // Repeating parameter
591
+ for (const v of value) {
592
+ params.parameter.push({ name, valueString: v });
593
+ }
594
+ } else {
595
+ params.parameter.push({ name, valueString: value });
596
+ }
597
+ }
598
+
599
+ return params;
600
+ }
601
+
602
+ /**
603
+ * Convert form body to a Parameters resource, merging with query params
604
+ * @param {Object} body - Form body
605
+ * @param {Object} query - Query parameters
606
+ * @returns {Object} Parameters resource
607
+ */
608
+ formToParameters(body, query) {
609
+ const params = {
610
+ resourceType: 'Parameters',
611
+ parameter: []
612
+ };
613
+
614
+ // Add query params first
615
+ if (query) {
616
+ for (const [name, value] of Object.entries(query)) {
617
+ if (Array.isArray(value)) {
618
+ for (const v of value) {
619
+ params.parameter.push({ name, valueString: v });
620
+ }
621
+ } else {
622
+ params.parameter.push({ name, valueString: value });
623
+ }
624
+ }
625
+ }
626
+
627
+ // Add/override with body params
628
+ if (body) {
629
+ for (const [name, value] of Object.entries(body)) {
630
+ if (Array.isArray(value)) {
631
+ for (const v of value) {
632
+ params.parameter.push({ name, valueString: v });
633
+ }
634
+ } else {
635
+ params.parameter.push({ name, valueString: value });
636
+ }
637
+ }
638
+ }
639
+
640
+ return params;
641
+ }
642
+
643
+ /**
644
+ * Find a parameter in a Parameters resource
645
+ * @param {Object} params - Parameters resource
646
+ * @param {string} name - Parameter name
647
+ * @returns {Object|null} Parameter object or null
648
+ */
649
+ findParameter(params, name) {
650
+ if (!params || !params.parameter) return null;
651
+ return params.parameter.find(p => p.name === name) || null;
652
+ }
653
+
654
+ /**
655
+ * Get the value from a parameter (handles various value types)
656
+ * @param {Object} param - Parameter object
657
+ * @returns {*} Parameter value
658
+ */
659
+ getParameterValue(param) {
660
+ if (!param) return null;
661
+
662
+ // Check for resource
663
+ if (param.resource) return param.resource;
664
+
665
+ // Check for various value types
666
+ const valueTypes = [
667
+ 'valueString', 'valueCode', 'valueUri', 'valueCanonical', 'valueUrl',
668
+ 'valueBoolean', 'valueInteger', 'valueDecimal',
669
+ 'valueDateTime', 'valueDate', 'valueTime',
670
+ 'valueCoding', 'valueCodeableConcept',
671
+ 'valueIdentifier', 'valueQuantity'
672
+ ];
673
+
674
+ for (const vt of valueTypes) {
675
+ if (param[vt] !== undefined) {
676
+ return param[vt];
677
+ }
678
+ }
679
+
680
+ return null;
681
+ }
682
+
683
+ /**
684
+ * Get a string parameter value
685
+ * @param {Object} params - Parameters resource
686
+ * @param {string} name - Parameter name
687
+ * @returns {string|null} Parameter value or null
688
+ */
689
+ getStringParam(params, name) {
690
+ const p = this.findParameter(params, name);
691
+ if (!p) return null;
692
+ return getValuePrimitive(p);
693
+ }
694
+
695
+ /**
696
+ * Get a resource parameter value
697
+ * @param {Object} params - Parameters resource
698
+ * @param {string} name - Parameter name
699
+ * @returns {Object|null} Resource or null
700
+ */
701
+ getResourceParam(params, name) {
702
+ const p = this.findParameter(params, name);
703
+ return p?.resource || null;
704
+ }
705
+
706
+ /**
707
+ * Get a Coding parameter value
708
+ * @param {Object} params - Parameters resource
709
+ * @param {string} name - Parameter name
710
+ * @returns {Object|null} Coding or null
711
+ */
712
+ getCodingParam(params, name) {
713
+ const p = this.findParameter(params, name);
714
+ return p?.valueCoding || null;
715
+ }
716
+
717
+ /**
718
+ * Get a CodeableConcept parameter value
719
+ * @param {Object} params - Parameters resource
720
+ * @param {string} name - Parameter name
721
+ * @returns {Object|null} CodeableConcept or null
722
+ */
723
+ getCodeableConceptParam(params, name) {
724
+ const p = this.findParameter(params, name);
725
+ return p?.valueCodeableConcept || null;
726
+ }
727
+
728
+ /**
729
+ * Render a coded value as string for debugging/logging
730
+ * @param {string|Object} system - System URI or coding object
731
+ * @param {string} version - Version (optional)
732
+ * @param {string} code - Code (optional)
733
+ * @param {string} display - Display (optional)
734
+ * @returns {string} Rendered string
735
+ */
736
+ displayCoded(system, version = '', code = '', display = '') {
737
+ if (typeof system === 'object') {
738
+ // Handle coding or codeable concept objects
739
+ if (system.system !== undefined) {
740
+ // Coding object
741
+ return this.renderer.displayCoded(system.system, system.version, system.code, system.display);
742
+ } else if (system.codings) {
743
+ // Codeable concept object
744
+ const rendered = system.codings.map(c => this.displayCoded(c)).join(', ');
745
+ return `[${rendered}]`;
746
+ }
747
+ }
748
+
749
+ let result = system;
750
+ if (version) {
751
+ result += `|${version}`;
752
+ }
753
+ if (code) {
754
+ result += `#${code}`;
755
+ }
756
+ if (display) {
757
+ result += ` ("${display}")`;
758
+ }
759
+
760
+ return result;
761
+ }
762
+ determineVersionBase(url, version, params) {
763
+ if (params === null) {
764
+ return version;
765
+ }
766
+ let result = version;
767
+ let list = params.rulesForSystem(url);
768
+ let b = false;
769
+ for (let t of list) {
770
+ if (t.mode === 'override') {
771
+ if (!b) {
772
+ result = t.version;
773
+ this.foundParameters.push(t.asParam());
774
+ b = true;
775
+ } else if (result !== t.version) {
776
+ throw new Issue("error", "exception", null, 'SYSTEM_VERSION_MULTIPLE_OVERRIDE', this.FI18n.translate('SYSTEM_VERSION_MULTIPLE_OVERRIDE', params.FHTTPLanguages, [url, result, t.version]), 'version-error');
777
+ }
778
+ }
779
+ }
780
+ if (!result) {
781
+ b = false;
782
+ for (let t of list) {
783
+ if (t.mode === 'default') {
784
+ if (!b) {
785
+ result = t.version;
786
+ this.foundParameters.push(t.asParam());
787
+ b = true;
788
+ } else if (version !== t.version) {
789
+ throw new Issue("error", "exception", null, 'SYSTEM_VERSION_MULTIPLE_DEFAULT', this.FI18n.translate('SYSTEM_VERSION_MULTIPLE_DEFAULT', params.FHTTPLanguages, [url, result, t.version]), 'version-error');
790
+ }
791
+ }
792
+ }
793
+ }
794
+ for (let t of list) {
795
+ if (t.mode === 'check') {
796
+ if (!result) {
797
+ result = t.version;
798
+ this.foundParameters.push(t.asParam());
799
+ }
800
+ // if we decide to allow check to guide the selection.
801
+ // waiting for discussion
802
+ //else if (TFHIRVersions.isSubset(result, t.version)) {
803
+ // result = t.version;
804
+ //}
805
+ }
806
+ }
807
+ return result;
808
+ }
809
+
810
+ checkVersion(url, version, params, versionAlgorithm, op) {
811
+ if (params) {
812
+ let list = params.rulesForSystem(url);
813
+ for (let t of list) {
814
+ if (t.mode === 'check') {
815
+ if (!VersionUtilities.versionMatchesByAlgorithm(t.version, version, versionAlgorithm)) {
816
+ let issue = new Issue("error", "exception", null, 'VALUESET_VERSION_CHECK', this.i18n.translate('VALUESET_VERSION_CHECK', params.FHTTPLanguages, [url, version, t.version]), 'version-error', 400);
817
+ if (op) {
818
+ op.addIssue(issue);
819
+ } else {
820
+ throw issue;
821
+ }
822
+ }
823
+ }
824
+ }
825
+ }
826
+ }
827
+
828
+ makeVurl(resource) {
829
+ let result = resource.vurl;
830
+ if (!result && resource.url) {
831
+ if (resource.version) {
832
+ result = resource.url+"|"+resource.version;
833
+ } else {
834
+ result = resource.url;
835
+ }
836
+ }
837
+ return result;
838
+ }
839
+
840
+ // Note: findParameter, getStringParam, getResourceParam, getCodingParam,
841
+ // and getCodeableConceptParam are inherited from TerminologyWorker base class
842
+
843
+ fixForVersion(resource) {
844
+ if (this.provider.fhirVersion >= 5) {
845
+ return resource;
846
+ }
847
+ let rt = resource.resourceType;
848
+ switch (rt) {
849
+ case "ValueSet": {
850
+ let vs = new ValueSet(resource);
851
+ if (this.provider.fhirVersion == 4) {
852
+ return vs.convertFromR5(resource, "R4");
853
+ } else if (this.provider.fhirVersion == 3) {
854
+ return vs.convertFromR5(resource, "R3");
855
+ } else {
856
+ return resource;
857
+ }
858
+ }
859
+ default:
860
+ return resource;
861
+ }
862
+ }
863
+
864
+
865
+ seeSourceVS(vs, url) {
866
+ let s = url;
867
+ if (vs) {
868
+ if (vs.jsonObj) vs = vs.jsonObj;
869
+ s = vs.name || vs.title || vs.id || vs.url;
870
+ }
871
+ if (!this.usedSources.find(u => u == s)) {
872
+ this.usedSources.push(s);
873
+ }
874
+ }
875
+
876
+ seeSourceProvider(cs, url) {
877
+ let s = url;
878
+ if (cs) {
879
+ if (cs instanceof CodeSystem) {
880
+ cs = cs.jsonObj;
881
+ s = cs.name || cs.title || cs.id || cs.url;
882
+ } else {
883
+ s = cs.name() || cs.system();
884
+ }
885
+ }
886
+ if (!this.usedSources.find(u => u == s)) {
887
+ this.usedSources.push(s);
888
+ }
889
+ }
890
+
891
+ presentVersionList(items) {
892
+ if (items.length === 0) return '';
893
+ if (items.length === 1) return items[0];
894
+ if (items.length === 2) return `${items[0]} or ${items[1]}`;
895
+
896
+ const lastItem = items.pop();
897
+ return `${items.join(', ')} and ${lastItem}`;
898
+ }
899
+ }
900
+
901
+ module.exports = {
902
+ TerminologyWorker,
903
+ TerminologySetupError
904
+ };