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,13 @@
1
+ {
2
+ "npmprojector": {
3
+ "enabled": true,
4
+ "basePath": "/fhir",
5
+ "npmPath": "./data/fhir-package",
6
+ "fhirVersion": "r4",
7
+ "resourceTypes": null,
8
+ "resourceFolders": ["data/medications", "data/patients"],
9
+ "searchParametersFolder": "data/search",
10
+ "searchParametersPath": null,
11
+ "debounceMs": 500
12
+ }
13
+ }
@@ -0,0 +1,394 @@
1
+ //
2
+ // FHIR Indexer
3
+ // Builds in-memory search indexes using FHIRPath expressions
4
+ //
5
+
6
+ const fhirpath = require('fhirpath');
7
+
8
+ class FHIRIndexer {
9
+ constructor(fhirModel = null) {
10
+ this.resources = new Map(); // resourceType -> id -> resource
11
+ this.indexes = new Map(); // resourceType -> paramName -> value -> Set<id>
12
+ this.searchParams = new Map(); // resourceType -> paramName -> SearchParameter
13
+ this.fhirModel = fhirModel; // FHIRPath model for type resolution
14
+ }
15
+
16
+ /**
17
+ * Load resources and search parameters, build all indexes
18
+ */
19
+ build(resources, searchParameters) {
20
+ this.resources.clear();
21
+ this.indexes.clear();
22
+ this.searchParams.clear();
23
+
24
+ // Index search parameters by resource type
25
+ for (const sp of searchParameters) {
26
+ if (!sp.expression) continue;
27
+
28
+ const types = sp.base || [];
29
+ for (const type of types) {
30
+ if (!this.searchParams.has(type)) {
31
+ this.searchParams.set(type, new Map());
32
+ }
33
+ this.searchParams.get(type).set(sp.code, sp);
34
+ }
35
+ }
36
+
37
+ // Store resources and build indexes
38
+ for (const resource of resources) {
39
+ const type = resource.resourceType;
40
+ if (!type) continue;
41
+
42
+ // Store the resource
43
+ if (!this.resources.has(type)) {
44
+ this.resources.set(type, new Map());
45
+ }
46
+ this.resources.get(type).set(resource.id, resource);
47
+
48
+ // Build indexes for this resource
49
+ this.indexResource(resource);
50
+ }
51
+
52
+ return this.getStats();
53
+ }
54
+
55
+ /**
56
+ * Index a single resource against all applicable search parameters
57
+ */
58
+ indexResource(resource) {
59
+ const type = resource.resourceType;
60
+ const params = this.searchParams.get(type);
61
+ if (!params) return;
62
+
63
+ if (!this.indexes.has(type)) {
64
+ this.indexes.set(type, new Map());
65
+ }
66
+ const typeIndexes = this.indexes.get(type);
67
+
68
+ for (const [paramName, searchParam] of params) {
69
+ if (!typeIndexes.has(paramName)) {
70
+ typeIndexes.set(paramName, new Map());
71
+ }
72
+ const paramIndex = typeIndexes.get(paramName);
73
+
74
+ try {
75
+ const values = fhirpath.evaluate(resource, searchParam.expression, null, this.fhirModel);
76
+ for (const value of values) {
77
+ const normalizedValues = this.normalizeValue(value, searchParam.type);
78
+ for (const normalized of normalizedValues) {
79
+ if (normalized === null || normalized === undefined) continue;
80
+
81
+ if (!paramIndex.has(normalized)) {
82
+ paramIndex.set(normalized, new Set());
83
+ }
84
+ paramIndex.get(normalized).add(resource.id);
85
+ }
86
+ }
87
+ } catch (err) {
88
+ console.log(err);
89
+ // FHIRPath evaluation failed - skip this param for this resource
90
+ // Logged at debug level to avoid noise
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Normalize a value based on search parameter type
97
+ * Returns an array of normalized values (some types produce multiple index entries)
98
+ */
99
+ normalizeValue(value, paramType) {
100
+ if (value === null || value === undefined) {
101
+ return [];
102
+ }
103
+
104
+ switch (paramType) {
105
+ case 'string':
106
+ return [String(value).toLowerCase()];
107
+
108
+ case 'token':
109
+ // Handle CodeableConcept, Coding, Identifier, code, etc.
110
+ if (typeof value === 'string') {
111
+ return [value.toLowerCase()];
112
+ }
113
+ if (value.coding) {
114
+ // CodeableConcept
115
+ return value.coding.map(c => this.tokenKey(c.system, c.code)).filter(Boolean);
116
+ }
117
+ if (value.system !== undefined || value.code !== undefined || value.value !== undefined) {
118
+ // Coding or Identifier
119
+ return [this.tokenKey(value.system, value.code || value.value)].filter(Boolean);
120
+ }
121
+ return [String(value).toLowerCase()];
122
+
123
+ case 'reference':
124
+ // Handle Reference type
125
+ if (typeof value === 'string') {
126
+ return [value];
127
+ }
128
+ if (value.reference) {
129
+ return [value.reference];
130
+ }
131
+ return [];
132
+
133
+ case 'date':
134
+ // Store as ISO string prefix for simple matching
135
+ if (typeof value === 'string') {
136
+ return [value];
137
+ }
138
+ if (value instanceof Date) {
139
+ return [value.toISOString()];
140
+ }
141
+ return [];
142
+
143
+ case 'quantity':
144
+ // Index the numeric value (simplified - ignores units)
145
+ if (typeof value === 'number') {
146
+ return [value];
147
+ }
148
+ if (value.value !== undefined) {
149
+ return [value.value];
150
+ }
151
+ return [];
152
+
153
+ case 'number':
154
+ if (typeof value === 'number') {
155
+ return [value];
156
+ }
157
+ return [parseFloat(value)].filter(v => !isNaN(v));
158
+
159
+ case 'uri':
160
+ return [String(value)];
161
+
162
+ default:
163
+ return [String(value).toLowerCase()];
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Create a token key from system and code
169
+ */
170
+ tokenKey(system, code) {
171
+ if (!code && !system) return null;
172
+ if (!system) return `|${code}`.toLowerCase();
173
+ if (!code) return `${system}|`.toLowerCase();
174
+ return `${system}|${code}`.toLowerCase();
175
+ }
176
+
177
+ /**
178
+ * Read a single resource by type and id
179
+ */
180
+ read(resourceType, id) {
181
+ const typeResources = this.resources.get(resourceType);
182
+ if (!typeResources) return null;
183
+ return typeResources.get(id) || null;
184
+ }
185
+
186
+ /**
187
+ * Search for resources
188
+ */
189
+ search(resourceType, queryParams) {
190
+ const typeResources = this.resources.get(resourceType);
191
+ if (!typeResources) {
192
+ return [];
193
+ }
194
+
195
+ const typeIndexes = this.indexes.get(resourceType);
196
+ const typeSearchParams = this.searchParams.get(resourceType);
197
+
198
+ // Start with all resource IDs
199
+ let matchingIds = null;
200
+
201
+ for (const [paramName, paramValue] of Object.entries(queryParams)) {
202
+ // Skip special parameters
203
+ if (paramName.startsWith('_')) continue;
204
+
205
+ if (!typeSearchParams?.has(paramName) || !typeIndexes?.has(paramName)) {
206
+ // Unknown search parameter - skip or could throw
207
+ continue;
208
+ }
209
+
210
+ const searchParam = typeSearchParams.get(paramName);
211
+ const paramIndex = typeIndexes.get(paramName);
212
+
213
+ // Find matching IDs for this parameter
214
+ const paramMatchingIds = this.searchParam(paramValue, searchParam, paramIndex);
215
+
216
+ // Intersect with running result
217
+ if (matchingIds === null) {
218
+ matchingIds = paramMatchingIds;
219
+ } else {
220
+ matchingIds = new Set([...matchingIds].filter(id => paramMatchingIds.has(id)));
221
+ }
222
+
223
+ // Early exit if no matches
224
+ if (matchingIds.size === 0) {
225
+ return [];
226
+ }
227
+ }
228
+
229
+ // If no search params provided, return all
230
+ if (matchingIds === null) {
231
+ return Array.from(typeResources.values());
232
+ }
233
+
234
+ // Return matching resources
235
+ return Array.from(matchingIds).map(id => typeResources.get(id)).filter(Boolean);
236
+ }
237
+
238
+ /**
239
+ * Search a single parameter
240
+ */
241
+ searchParam(queryValue, searchParam, paramIndex) {
242
+ const matchingIds = new Set();
243
+ const paramType = searchParam.type;
244
+
245
+ // Handle OR (comma-separated values)
246
+ const values = String(queryValue).split(',');
247
+
248
+ for (const value of values) {
249
+ const trimmedValue = value.trim();
250
+
251
+ switch (paramType) {
252
+ case 'string':
253
+ // String search: case-insensitive starts-with by default
254
+ const lowerValue = trimmedValue.toLowerCase();
255
+ for (const [indexedValue, ids] of paramIndex) {
256
+ if (indexedValue.startsWith(lowerValue)) {
257
+ ids.forEach(id => matchingIds.add(id));
258
+ }
259
+ }
260
+ break;
261
+
262
+ case 'token':
263
+ // Token search: exact match on system|code, |code, or code
264
+ const tokenMatches = this.matchToken(trimmedValue, paramIndex);
265
+ tokenMatches.forEach(id => matchingIds.add(id));
266
+ break;
267
+
268
+ case 'reference':
269
+ // Reference search: exact match
270
+ const refIds = paramIndex.get(trimmedValue);
271
+ if (refIds) {
272
+ refIds.forEach(id => matchingIds.add(id));
273
+ }
274
+ break;
275
+
276
+ case 'date':
277
+ // Simple prefix matching for dates (no range support as specified)
278
+ for (const [indexedValue, ids] of paramIndex) {
279
+ if (indexedValue.startsWith(trimmedValue)) {
280
+ ids.forEach(id => matchingIds.add(id));
281
+ }
282
+ }
283
+ break;
284
+
285
+ case 'number':
286
+ case 'quantity':
287
+ // Numeric equality
288
+ const numValue = parseFloat(trimmedValue);
289
+ if (!isNaN(numValue)) {
290
+ const ids = paramIndex.get(numValue);
291
+ if (ids) {
292
+ ids.forEach(id => matchingIds.add(id));
293
+ }
294
+ }
295
+ break;
296
+
297
+ case 'uri':
298
+ // Exact match for URI
299
+ const uriIds = paramIndex.get(trimmedValue);
300
+ if (uriIds) {
301
+ uriIds.forEach(id => matchingIds.add(id));
302
+ }
303
+ break;
304
+
305
+ default:
306
+ // Default: exact match (case-insensitive)
307
+ const defaultIds = paramIndex.get(trimmedValue.toLowerCase());
308
+ if (defaultIds) {
309
+ defaultIds.forEach(id => matchingIds.add(id));
310
+ }
311
+ }
312
+ }
313
+
314
+ return matchingIds;
315
+ }
316
+
317
+ /**
318
+ * Match a token query against the index
319
+ * Supports: system|code, |code, code, system|
320
+ */
321
+ matchToken(query, paramIndex) {
322
+ const matchingIds = new Set();
323
+ const lowerQuery = query.toLowerCase();
324
+
325
+ if (lowerQuery.includes('|')) {
326
+ // Exact system|code match or system| or |code
327
+ const ids = paramIndex.get(lowerQuery);
328
+ if (ids) {
329
+ ids.forEach(id => matchingIds.add(id));
330
+ }
331
+ } else {
332
+ // Code-only search: match any system
333
+ for (const [indexedValue, ids] of paramIndex) {
334
+ const parts = indexedValue.split('|');
335
+ const indexedCode = parts[parts.length - 1];
336
+ if (indexedCode === lowerQuery) {
337
+ ids.forEach(id => matchingIds.add(id));
338
+ }
339
+ }
340
+ }
341
+
342
+ return matchingIds;
343
+ }
344
+
345
+ /**
346
+ * Get all supported resource types
347
+ */
348
+ getResourceTypes() {
349
+ return Array.from(this.resources.keys());
350
+ }
351
+
352
+ /**
353
+ * Get search parameters for a resource type
354
+ */
355
+ getSearchParams(resourceType) {
356
+ const params = this.searchParams.get(resourceType);
357
+ if (!params) return [];
358
+ return Array.from(params.values());
359
+ }
360
+
361
+ /**
362
+ * Get statistics about the indexed data
363
+ */
364
+ getStats() {
365
+ const stats = {
366
+ resourceTypes: {},
367
+ totalResources: 0,
368
+ totalIndexEntries: 0
369
+ };
370
+
371
+ for (const [type, resources] of this.resources) {
372
+ const typeIndexes = this.indexes.get(type);
373
+ let indexEntries = 0;
374
+
375
+ if (typeIndexes) {
376
+ for (const [, paramIndex] of typeIndexes) {
377
+ indexEntries += paramIndex.size;
378
+ }
379
+ }
380
+
381
+ stats.resourceTypes[type] = {
382
+ count: resources.size,
383
+ indexedParams: typeIndexes?.size || 0,
384
+ indexEntries
385
+ };
386
+ stats.totalResources += resources.size;
387
+ stats.totalIndexEntries += indexEntries;
388
+ }
389
+
390
+ return stats;
391
+ }
392
+ }
393
+
394
+ module.exports = FHIRIndexer;