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,1396 @@
1
+ const fs = require('fs');
2
+
3
+
4
+ class SnomedStrings {
5
+ constructor(buffer = null) {
6
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
7
+ this.decoder = new TextDecoder('utf-8');
8
+ this.builder = null;
9
+ this.currentOffset = 0; // ADD: Track offset directly
10
+ }
11
+
12
+ getEntry(offset) {
13
+ if (offset > this.master.length) {
14
+ throw new Error('Wrong length index getting snomed name');
15
+ }
16
+
17
+ // Read 2-byte length prefix (little-endian)
18
+ const length = this.master.readUInt16LE(offset);
19
+
20
+ // Bounds check
21
+ if (offset + 2 + length > this.master.length) {
22
+ throw new Error('Wrong length index getting snomed name (2)');
23
+ }
24
+
25
+ // Read UTF-8 bytes and decode to string
26
+ const stringBytes = this.master.subarray(offset + 2, offset + 2 + length);
27
+ return this.decoder.decode(stringBytes);
28
+ }
29
+
30
+ startBuild() {
31
+ this.builder = [];
32
+ this.currentOffset = 0; // RESET: offset when starting build
33
+ }
34
+
35
+ reopen() {
36
+ this.builder = [this.master];
37
+ this.currentOffset = this.master.length; // SET: offset to current master length
38
+ }
39
+
40
+ addString(str) {
41
+ if (!this.builder) {
42
+ throw new Error('Must call startBuild() first');
43
+ }
44
+
45
+ const utf8Bytes = Buffer.from(str, 'utf8');
46
+
47
+ if (utf8Bytes.length > 65535) {
48
+ throw new Error(`Snomed Description too long: ${str}`);
49
+ }
50
+
51
+ // FIXED: Use pre-calculated offset instead of reduce
52
+ const currentOffset = this.currentOffset;
53
+
54
+ // Create length prefix (2 bytes, little-endian)
55
+ const lengthPrefix = Buffer.allocUnsafe(2);
56
+ lengthPrefix.writeUInt16LE(utf8Bytes.length, 0);
57
+
58
+ // Add length prefix + string bytes to builder
59
+ this.builder.push(lengthPrefix, utf8Bytes);
60
+
61
+ // UPDATE: offset for next call
62
+ this.currentOffset += 2 + utf8Bytes.length;
63
+
64
+ return currentOffset;
65
+ }
66
+
67
+ doneBuild() {
68
+ if (!this.builder) {
69
+ throw new Error('No build in progress');
70
+ }
71
+
72
+ this.master = Buffer.concat(this.builder);
73
+ this.builder = null;
74
+ this.currentOffset = 0; // RESET: for potential future builds
75
+ }
76
+
77
+ clear() {
78
+ this.master = Buffer.alloc(0);
79
+ this.currentOffset = 0; // RESET: offset when clearing
80
+ }
81
+
82
+ get length() {
83
+ return this.master.length;
84
+ }
85
+ }
86
+
87
+ class SnomedWords {
88
+ constructor(buffer = null) {
89
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
90
+ this.builder = null;
91
+ }
92
+
93
+ getEntry(index) {
94
+ const offset = index * 5;
95
+
96
+ if (offset > this.master.length - 5) {
97
+ throw new Error('invalid index');
98
+ }
99
+
100
+ // Read 4-byte cardinal (little-endian) + 1-byte flag
101
+ const stringIndex = this.master.readUInt32LE(offset);
102
+ const flags = this.master.readUInt8(offset + 4);
103
+
104
+ return { index: stringIndex, flags };
105
+ }
106
+
107
+ getString(index) {
108
+ const entry = this.getEntry(index);
109
+ return entry.index;
110
+ }
111
+
112
+ count() {
113
+ return Math.floor(this.master.length / 5);
114
+ }
115
+
116
+ startBuild() {
117
+ this.builder = [];
118
+ }
119
+
120
+ addWord(index, flags) {
121
+ if (!this.builder) {
122
+ throw new Error('Must call startBuild() first');
123
+ }
124
+
125
+ // Create 5-byte record: 4-byte index + 1-byte flag
126
+ const record = Buffer.allocUnsafe(5);
127
+ record.writeUInt32LE(index, 0); // 4-byte cardinal (little-endian)
128
+ record.writeUInt8(flags, 4); // 1-byte flag
129
+
130
+ this.builder.push(record);
131
+ }
132
+
133
+ doneBuild() {
134
+ if (!this.builder) {
135
+ throw new Error('No build in progress');
136
+ }
137
+
138
+ this.master = Buffer.concat(this.builder);
139
+ this.builder = null;
140
+ }
141
+
142
+ clear() {
143
+ this.master = Buffer.alloc(0);
144
+ }
145
+
146
+ get length() {
147
+ return this.master.length;
148
+ }
149
+ }
150
+
151
+ class SnomedStems {
152
+ constructor(buffer = null) {
153
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
154
+ this.builder = null;
155
+ }
156
+
157
+ getEntry(index) {
158
+ const offset = index * 8;
159
+
160
+ if (offset > this.master.length - 7) {
161
+ throw new Error('invalid index');
162
+ }
163
+
164
+ // Read two 4-byte cardinals (little-endian)
165
+ const stringIndex = this.master.readUInt32LE(offset);
166
+ const reference = this.master.readUInt32LE(offset + 4);
167
+
168
+ return { index: stringIndex, reference };
169
+ }
170
+
171
+ getString(index) {
172
+ const entry = this.getEntry(index);
173
+ return entry.index;
174
+ }
175
+
176
+ count() {
177
+ return Math.floor(this.master.length / 8);
178
+ }
179
+
180
+ startBuild() {
181
+ this.builder = [];
182
+ }
183
+
184
+ addStem(index, reference) {
185
+ if (!this.builder) {
186
+ throw new Error('Must call startBuild() first');
187
+ }
188
+
189
+ // Create 8-byte record: two 4-byte cardinals
190
+ const record = Buffer.allocUnsafe(8);
191
+ record.writeUInt32LE(index, 0); // First 4-byte cardinal
192
+ record.writeUInt32LE(reference, 4); // Second 4-byte cardinal
193
+
194
+ this.builder.push(record);
195
+ }
196
+
197
+ doneBuild() {
198
+ if (!this.builder) {
199
+ throw new Error('No build in progress');
200
+ }
201
+
202
+ this.master = Buffer.concat(this.builder);
203
+ this.builder = null;
204
+ }
205
+
206
+ clear() {
207
+ this.master = Buffer.alloc(0);
208
+ }
209
+
210
+ get length() {
211
+ return this.master.length;
212
+ }
213
+ }
214
+
215
+ class SnomedReferences {
216
+ constructor(buffer = null) {
217
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
218
+ this.builder = null;
219
+ this.currentOffset = 0; // ADD: Track offset directly
220
+ this.MAGIC_NO_CHILDREN = 0xFFFFFFFF; // Assuming this constant - adjust as needed
221
+ }
222
+
223
+ getReferences(index) {
224
+ if (index === this.MAGIC_NO_CHILDREN || index === 0) {
225
+ return null;
226
+ }
227
+
228
+ // Handle incremental building case
229
+ if (this.builder && index >= this.master.length) {
230
+ this.post();
231
+ }
232
+
233
+ if (index >= this.master.length) {
234
+ throw new Error(`Wrong length index getting Snomed list. asked for ${index}, limit is ${this.master.length}`);
235
+ }
236
+
237
+ // Read count of elements
238
+ const count = this.master.readUInt32LE(index);
239
+
240
+ // Bounds check for the full array
241
+ if (index + 4 + count * 4 > this.master.length) {
242
+ throw new Error(`Wrong length index (${index}, ${count}) getting Snomed list (length = ${this.master.length})`);
243
+ }
244
+
245
+ // Read the cardinal array
246
+ const result = new Array(count);
247
+ let offset = index + 4;
248
+
249
+ for (let i = 0; i < count; i++) {
250
+ result[i] = this.master.readUInt32LE(offset);
251
+ offset += 4;
252
+ }
253
+
254
+ return result;
255
+ }
256
+
257
+ getLength(index) {
258
+ if (index > this.master.length) {
259
+ throw new Error('Wrong length index getting Snomed list');
260
+ }
261
+
262
+ return this.master.readUInt32LE(index);
263
+ }
264
+
265
+ startBuild() {
266
+ this.builder = [];
267
+ this.currentOffset = 0; // RESET: offset when starting build
268
+ }
269
+
270
+ addReferences(cardinalArray) {
271
+ if (!this.builder) {
272
+ throw new Error('Must call startBuild() first');
273
+ }
274
+
275
+ // FIXED: Use pre-calculated offset instead of reduce
276
+ const currentOffset = this.currentOffset;
277
+
278
+ // Create buffer for count + array data
279
+ const totalBytes = 4 + (cardinalArray.length * 4);
280
+ const record = Buffer.allocUnsafe(totalBytes);
281
+
282
+ // Write count
283
+ record.writeUInt32LE(cardinalArray.length, 0);
284
+
285
+ // Write each cardinal
286
+ let offset = 4;
287
+ for (const value of cardinalArray) {
288
+ record.writeUInt32LE(value, offset);
289
+ offset += 4;
290
+ }
291
+
292
+ this.builder.push(record);
293
+
294
+ // UPDATE: offset for next call
295
+ this.currentOffset += totalBytes;
296
+
297
+ return currentOffset;
298
+ }
299
+
300
+ post() {
301
+ if (this.builder) {
302
+ this.master = Buffer.concat([this.master, ...this.builder]);
303
+ this.builder = [];
304
+ this.currentOffset = this.master.length; // UPDATE: offset to new master length
305
+ }
306
+ }
307
+
308
+ doneBuild() {
309
+ if (!this.builder) {
310
+ throw new Error('No build in progress');
311
+ }
312
+
313
+ this.master = Buffer.concat([this.master, ...this.builder]);
314
+ this.builder = null;
315
+ this.currentOffset = 0; // RESET: for potential future builds
316
+ }
317
+
318
+ clear() {
319
+ this.master = Buffer.alloc(0);
320
+ this.currentOffset = 0; // RESET: offset when clearing
321
+ }
322
+
323
+ get length() {
324
+ return this.master.length;
325
+ }
326
+ }
327
+
328
+ class SnomedDescriptions {
329
+ static DESC_SIZE = 40;
330
+
331
+ // Flag constants
332
+ static FLAG_Active = 0;
333
+ static FLAG_RetiredWithoutStatedReason = 1;
334
+ static FLAG_Duplicate = 2;
335
+ static FLAG_Outdated = 3;
336
+ static FLAG_Ambiguous = 4;
337
+ static FLAG_Erroneous = 5;
338
+ static FLAG_Limited = 6;
339
+ static FLAG_Inappropriate = 7;
340
+ static FLAG_ConceptInactive = 8;
341
+ static FLAG_MovedElswhere = 10;
342
+ static FLAG_PendingMove = 11;
343
+
344
+ constructor(buffer = null) {
345
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
346
+ this.builder = null;
347
+ this.currentOffset = 0; // ADD: Track offset directly
348
+ }
349
+
350
+ count() {
351
+ return Math.floor(this.master.length / SnomedDescriptions.DESC_SIZE);
352
+ }
353
+
354
+ getDescription(index) {
355
+ if (index >= this.master.length) {
356
+ throw new Error('Wrong length index getting snomed Desc Details');
357
+ }
358
+
359
+ const offset = index;
360
+
361
+ return {
362
+ iDesc: this.master.readUInt32LE(offset + 0),
363
+ active: this.master.readUInt8(offset + 4) !== 0,
364
+ id: this.master.readBigUInt64LE(offset + 5),
365
+ concept: this.master.readUInt32LE(offset + 13),
366
+ module: this.master.readUInt32LE(offset + 17),
367
+ kind: this.master.readUInt32LE(offset + 21),
368
+ caps: this.master.readUInt32LE(offset + 25),
369
+ date: this.master.readUInt16LE(offset + 29),
370
+ lang: this.master.readUInt8(offset + 31),
371
+ refsets: this.master.readUInt32LE(offset + 32),
372
+ valueses: this.master.readUInt32LE(offset + 36)
373
+ };
374
+ }
375
+
376
+ conceptByIndex(index) {
377
+ if (index >= this.master.length) {
378
+ throw new Error('Wrong length index getting snomed Desc Details');
379
+ }
380
+
381
+ return this.master.readUInt32LE(index + 13);
382
+ }
383
+
384
+ startBuild() {
385
+ this.builder = [];
386
+ this.currentOffset = 0; // RESET: offset when starting build
387
+ }
388
+
389
+ addDescription(iDesc, id, date, concept, module, kind, caps, active, lang) {
390
+ if (!this.builder) {
391
+ throw new Error('Must call startBuild() first');
392
+ }
393
+
394
+ // FIXED: Use pre-calculated offset instead of reduce
395
+ const currentOffset = this.currentOffset;
396
+
397
+ // Create 40-byte record
398
+ const record = Buffer.allocUnsafe(SnomedDescriptions.DESC_SIZE);
399
+
400
+ record.writeUInt32LE(iDesc, 0); // 4 bytes
401
+ record.writeUInt8(active ? 1 : 0, 4); // 1 byte
402
+ record.writeBigUInt64LE(BigInt(id), 5); // 8 bytes
403
+ record.writeUInt32LE(concept, 13); // 4 bytes
404
+ record.writeUInt32LE(module, 17); // 4 bytes
405
+ record.writeUInt32LE(kind, 21); // 4 bytes
406
+ record.writeUInt32LE(caps, 25); // 4 bytes
407
+ record.writeUInt16LE(date, 29); // 2 bytes
408
+ record.writeUInt8(lang, 31); // 1 byte
409
+ record.writeUInt32LE(0, 32); // 4 bytes (refsets)
410
+ record.writeUInt32LE(0, 36); // 4 bytes (valueses)
411
+
412
+ this.builder.push(record);
413
+
414
+ // UPDATE: offset for next call
415
+ this.currentOffset += SnomedDescriptions.DESC_SIZE;
416
+
417
+ return currentOffset;
418
+ }
419
+
420
+ setRefsets(index, refsets, valueses) {
421
+ if (index >= this.master.length) {
422
+ throw new Error('Wrong length index getting snomed Desc Details');
423
+ }
424
+
425
+ if (index % SnomedDescriptions.DESC_SIZE !== 0) {
426
+ throw new Error('Index must be aligned to DESC_SIZE');
427
+ }
428
+
429
+ this.master.writeUInt32LE(refsets, index + 32);
430
+ this.master.writeUInt32LE(valueses, index + 36);
431
+ }
432
+
433
+ doneBuild() {
434
+ if (!this.builder) {
435
+ throw new Error('No build in progress');
436
+ }
437
+
438
+ this.master = Buffer.concat(this.builder);
439
+ this.builder = null;
440
+ this.currentOffset = 0; // RESET: for potential future builds
441
+ }
442
+
443
+ clear() {
444
+ this.master = Buffer.alloc(0);
445
+ this.currentOffset = 0; // RESET: offset when clearing
446
+ }
447
+
448
+ get length() {
449
+ return this.master.length;
450
+ }
451
+ }
452
+
453
+ class SnomedDescriptionIndex {
454
+ constructor(buffer = null) {
455
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
456
+ this.builder = null;
457
+ }
458
+
459
+ findDescription(identity) {
460
+ // Convert to BigInt if it's not already
461
+ const targetId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
462
+
463
+ let result = false;
464
+ let L = 0;
465
+ let H = Math.floor(this.master.length / 12) - 1;
466
+
467
+ while (L <= H) {
468
+ const I = Math.floor((L + H) / 2);
469
+ const aConcept = this.master.readBigUInt64LE(I * 12);
470
+
471
+ if (aConcept < targetId) {
472
+ L = I + 1;
473
+ } else {
474
+ H = I - 1;
475
+ if (aConcept === targetId) {
476
+ result = true;
477
+ L = I; // Found it, but continue searching left for first occurrence
478
+ }
479
+ }
480
+ }
481
+
482
+ if (result) {
483
+ const index = this.master.readUInt32LE(L * 12 + 8);
484
+ return { found: true, index };
485
+ } else {
486
+ return { found: false, index: 0 };
487
+ }
488
+ }
489
+
490
+ startBuild() {
491
+ this.builder = [];
492
+ }
493
+
494
+ addDescription(id, reference) {
495
+ if (!this.builder) {
496
+ throw new Error('Must call startBuild() first');
497
+ }
498
+
499
+ // Convert to BigInt if needed
500
+ const bigIntId = typeof id === 'string' ? BigInt(id) : BigInt(id);
501
+
502
+ // Create 12-byte record: 8-byte ID + 4-byte reference
503
+ const record = Buffer.allocUnsafe(12);
504
+ record.writeBigUInt64LE(bigIntId, 0);
505
+ record.writeUInt32LE(reference, 8);
506
+
507
+ this.builder.push(record);
508
+ }
509
+
510
+ doneBuild() {
511
+ if (!this.builder) {
512
+ throw new Error('No build in progress');
513
+ }
514
+
515
+ this.master = Buffer.concat(this.builder);
516
+ this.builder = null;
517
+ }
518
+
519
+ clear() {
520
+ this.master = Buffer.alloc(0);
521
+ }
522
+
523
+ get length() {
524
+ return this.master.length;
525
+ }
526
+
527
+ // Helper method to get count of entries
528
+ count() {
529
+ return Math.floor(this.master.length / 12);
530
+ }
531
+ }
532
+
533
+ class SnomedConceptList {
534
+ static CONCEPT_SIZE = 56;
535
+ static MASK_CONCEPT_STATUS = 0x0F;
536
+ static MASK_CONCEPT_PRIMITIVE = 0x10;
537
+ static MAGIC_NO_CHILDREN = 0xFFFFFFFF;
538
+
539
+ constructor(buffer = null) {
540
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
541
+ this.builder = null;
542
+ this.currentOffset = 0; // ADD: Track offset directly
543
+ }
544
+
545
+ // Helper method to check post-build state and bounds
546
+ #checkPostBuildAccess(index) {
547
+ if (this.builder !== null) {
548
+ throw new Error('Cannot call setX methods before doneBuild()');
549
+ }
550
+ if (index >= this.master.length) {
551
+ throw new Error(`Wrong length index ${index} getting snomed Concept Details. Max = ${this.master.length}`);
552
+ }
553
+ if (index % SnomedConceptList.CONCEPT_SIZE !== 0) {
554
+ throw new Error(`Wrong length index ${index} getting snomed Concept Details`);
555
+ }
556
+ }
557
+
558
+ findConcept(identity) {
559
+ const targetId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
560
+
561
+ let result = false;
562
+ let L = 0;
563
+ let H = Math.floor(this.master.length / SnomedConceptList.CONCEPT_SIZE) - 1;
564
+
565
+ while (L <= H) {
566
+ const I = Math.floor((L + H) / 2);
567
+ const aConcept = this.master.readBigUInt64LE(I * SnomedConceptList.CONCEPT_SIZE);
568
+
569
+ if (aConcept < targetId) {
570
+ L = I + 1;
571
+ } else {
572
+ H = I - 1;
573
+ if (aConcept === targetId) {
574
+ result = true;
575
+ L = I;
576
+ }
577
+ }
578
+ }
579
+
580
+ const index = L * SnomedConceptList.CONCEPT_SIZE;
581
+ return { found: result, index };
582
+ }
583
+
584
+ getConcept(index) {
585
+ this.#checkPostBuildAccess(index);
586
+
587
+ return {
588
+ identity: this.master.readBigUInt64LE(index + 0),
589
+ flags: this.master.readUInt8(index + 8),
590
+ parents: this.master.readUInt32LE(index + 9),
591
+ descriptions: this.master.readUInt32LE(index + 13),
592
+ inbounds: this.master.readUInt32LE(index + 17),
593
+ outbounds: this.master.readUInt32LE(index + 21),
594
+ effectiveTime: this.master.readUInt16LE(index + 34),
595
+ refsets: this.master.readUInt32LE(index + 44)
596
+ };
597
+ }
598
+
599
+ getConceptId(index) {
600
+ if (index >= this.master.length) {
601
+ throw new Error('Wrong length index getting snomed Concept Details');
602
+ }
603
+ return this.master.readBigUInt64LE(index + 0);
604
+ }
605
+
606
+ getParent(index) {
607
+ this.#checkPostBuildAccess(index);
608
+ return this.master.readUInt32LE(index + 9);
609
+ }
610
+
611
+ getIdentity(index) {
612
+ this.#checkPostBuildAccess(index);
613
+ return this.master.readBigUInt64LE(index + 0);
614
+ }
615
+
616
+ getDescriptions(index) {
617
+ this.#checkPostBuildAccess(index);
618
+ return this.master.readUInt32LE(index + 13);
619
+ }
620
+
621
+ getInbounds(index) {
622
+ this.#checkPostBuildAccess(index);
623
+ return this.master.readUInt32LE(index + 17);
624
+ }
625
+
626
+ getOutbounds(index) {
627
+ this.#checkPostBuildAccess(index);
628
+ return this.master.readUInt32LE(index + 21);
629
+ }
630
+
631
+ getAllDesc(index) {
632
+ this.#checkPostBuildAccess(index);
633
+ return this.master.readUInt32LE(index + 25);
634
+ }
635
+
636
+ getDepth(index) {
637
+ this.#checkPostBuildAccess(index);
638
+ return this.master.readUInt8(index + 29);
639
+ }
640
+
641
+ getStems(index) {
642
+ this.#checkPostBuildAccess(index);
643
+ return this.master.readUInt32LE(index + 30);
644
+ }
645
+
646
+ getModuleId(index) {
647
+ this.#checkPostBuildAccess(index);
648
+ return this.master.readUInt32LE(index + 36);
649
+ }
650
+
651
+ getStatus(index) {
652
+ this.#checkPostBuildAccess(index);
653
+ return this.master.readUInt32LE(index + 40);
654
+ }
655
+
656
+ getRefsets(index) {
657
+ this.#checkPostBuildAccess(index);
658
+ return this.master.readUInt32LE(index + 44);
659
+ }
660
+
661
+ getNormalForm(index) {
662
+ this.#checkPostBuildAccess(index);
663
+ return this.master.readUInt32LE(index + 48);
664
+ }
665
+
666
+ setNormalFormDuringBuild(index, value) {
667
+ // Special version that can be called during building phase
668
+ // This bypasses the post-build access check for normal forms specifically
669
+ if (index >= this.master.length) {
670
+ throw new Error(`Wrong length index ${index} getting snomed Concept Details. Max = ${this.master.length}`);
671
+ }
672
+ if (index % SnomedConceptList.CONCEPT_SIZE !== 0) {
673
+ throw new Error(`Wrong length index ${index} getting snomed Concept Details`);
674
+ }
675
+
676
+ this.master.writeUInt32LE(value, index + 48);
677
+ }
678
+ // Also add a helper to check if a concept exists by index
679
+ conceptExists(index) {
680
+ try {
681
+ if (index >= this.master.length) {
682
+ return false;
683
+ }
684
+ if (index % SnomedConceptList.CONCEPT_SIZE !== 0) {
685
+ return false;
686
+ }
687
+ return true;
688
+ } catch (error) {
689
+ return false;
690
+ }
691
+ }
692
+
693
+ // Add a helper to get concept identity safely
694
+ getConceptIdentitySafe(index) {
695
+ try {
696
+ if (!this.conceptExists(index)) {
697
+ return null;
698
+ }
699
+ return this.master.readBigUInt64LE(index + 0);
700
+ } catch (error) {
701
+ return null;
702
+ }
703
+ }
704
+
705
+ // All the setter methods - these require doneBuild() to have been called
706
+ setParents(index, active, inactive) {
707
+ this.#checkPostBuildAccess(index);
708
+ this.master.writeUInt32LE(active, index + 9);
709
+ this.master.writeUInt32LE(inactive, index + 52);
710
+ }
711
+
712
+ setDescriptions(index, value) {
713
+ this.#checkPostBuildAccess(index);
714
+ this.master.writeUInt32LE(value, index + 13);
715
+ }
716
+
717
+ setInbounds(index, value) {
718
+ this.#checkPostBuildAccess(index);
719
+ this.master.writeUInt32LE(value, index + 17);
720
+ }
721
+
722
+ setOutbounds(index, value) {
723
+ this.#checkPostBuildAccess(index);
724
+ this.master.writeUInt32LE(value, index + 21);
725
+ }
726
+
727
+ setAllDesc(index, value) {
728
+ this.#checkPostBuildAccess(index);
729
+ this.master.writeUInt32LE(value, index + 25);
730
+ }
731
+
732
+ setDepth(index, value) {
733
+ this.#checkPostBuildAccess(index);
734
+ this.master.writeUInt8(value, index + 29);
735
+ }
736
+
737
+ setStems(index, value) {
738
+ this.#checkPostBuildAccess(index);
739
+ this.master.writeUInt32LE(value, index + 30);
740
+ }
741
+
742
+ setDate(index, effectiveTime) {
743
+ this.#checkPostBuildAccess(index);
744
+ this.master.writeUInt16LE(effectiveTime, index + 34);
745
+ }
746
+
747
+ setModuleId(index, value) {
748
+ this.#checkPostBuildAccess(index);
749
+ this.master.writeUInt32LE(value, index + 36);
750
+ }
751
+
752
+ setStatus(index, value) {
753
+ this.#checkPostBuildAccess(index);
754
+ this.master.writeUInt32LE(value, index + 40);
755
+ }
756
+
757
+ setRefsets(index, value) {
758
+ this.#checkPostBuildAccess(index);
759
+ this.master.writeUInt32LE(value, index + 44);
760
+ }
761
+
762
+ setNormalForm(index, value) {
763
+ this.#checkPostBuildAccess(index);
764
+ this.master.writeUInt32LE(value, index + 48);
765
+ }
766
+
767
+ setFlag(index, flags) {
768
+ this.#checkPostBuildAccess(index);
769
+ this.master.writeUInt8(flags, index + 8);
770
+ }
771
+
772
+ count() {
773
+ return Math.floor(this.master.length / SnomedConceptList.CONCEPT_SIZE);
774
+ }
775
+
776
+ // Build methods
777
+ startBuild() {
778
+ this.builder = [];
779
+ this.currentOffset = 0; // RESET: offset when starting build
780
+ }
781
+
782
+ addConcept(identity, effectiveTime, flags) {
783
+ if (!this.builder) {
784
+ throw new Error('Must call startBuild() first');
785
+ }
786
+
787
+ // FIXED: Use pre-calculated offset instead of reduce
788
+ const currentOffset = this.currentOffset;
789
+ const bigIntId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
790
+
791
+ // Create 56-byte record
792
+ const record = Buffer.allocUnsafe(SnomedConceptList.CONCEPT_SIZE);
793
+
794
+ record.writeBigUInt64LE(bigIntId, 0); // identity
795
+ record.writeUInt8(flags, 8); // flags
796
+ record.writeUInt32LE(0, 9); // active parents
797
+ record.writeUInt32LE(0, 13); // descriptions
798
+ record.writeUInt32LE(0, 17); // inbounds
799
+ record.writeUInt32LE(0, 21); // outbounds
800
+ record.writeUInt32LE(0, 25); // closures
801
+ record.writeUInt8(0, 29); // depth
802
+ record.writeUInt32LE(0, 30); // stems
803
+ record.writeUInt16LE(effectiveTime, 34); // date
804
+ record.writeUInt32LE(0, 36); // moduleId
805
+ record.writeUInt32LE(0, 40); // status
806
+ record.writeUInt32LE(0, 44); // refsets
807
+ record.writeUInt32LE(0, 48); // normal form
808
+ record.writeUInt32LE(0, 52); // inactive parents
809
+
810
+ this.builder.push(record);
811
+
812
+ // UPDATE: offset for next call
813
+ this.currentOffset += SnomedConceptList.CONCEPT_SIZE;
814
+
815
+ return currentOffset;
816
+ }
817
+
818
+ doneBuild() {
819
+ if (!this.builder) {
820
+ throw new Error('No build in progress');
821
+ }
822
+
823
+ this.master = Buffer.concat(this.builder);
824
+ this.builder = null; // This enables the setX methods
825
+ this.currentOffset = 0; // RESET: for potential future builds
826
+ }
827
+
828
+ clear() {
829
+ this.master = Buffer.alloc(0);
830
+ this.currentOffset = 0; // RESET: offset when clearing
831
+ }
832
+
833
+ get length() {
834
+ return this.master.length;
835
+ }
836
+ }
837
+
838
+ class SnomedRelationshipList {
839
+ static RELATIONSHIP_SIZE = 40;
840
+ static REFSET_SIZE_NOLANG = 28;
841
+ static REFSET_SIZE_LANG = 32;
842
+
843
+ constructor(buffer = null) {
844
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
845
+ this.builder = null;
846
+ this.currentOffset = 0; // ADD: Track offset directly
847
+ }
848
+
849
+ getRelationship(index) {
850
+ if (index >= this.master.length) {
851
+ throw new Error('Wrong length index getting snomed relationship Details');
852
+ }
853
+
854
+ return {
855
+ source: this.master.readUInt32LE(index + 0),
856
+ target: this.master.readUInt32LE(index + 4),
857
+ relType: this.master.readUInt32LE(index + 8),
858
+ module: this.master.readUInt32LE(index + 12),
859
+ kind: this.master.readUInt32LE(index + 16),
860
+ modifier: this.master.readUInt32LE(index + 20),
861
+ date: this.master.readUInt16LE(index + 24),
862
+ active: this.master.readUInt8(index + 26) !== 0,
863
+ defining: this.master.readUInt8(index + 27) !== 0,
864
+ group: this.master.readInt32LE(index + 28), // Signed integer
865
+ identity: this.master.readBigUInt64LE(index + 32)
866
+ };
867
+ }
868
+
869
+ count() {
870
+ return Math.floor(this.master.length / SnomedRelationshipList.RELATIONSHIP_SIZE);
871
+ }
872
+
873
+ // Build methods
874
+ startBuild() {
875
+ this.builder = [];
876
+ this.currentOffset = 0; // RESET: offset when starting build
877
+ }
878
+
879
+ addRelationship(identity, source, target, relType, module, kind, modifier, date, active, defining, group) {
880
+ if (!this.builder) {
881
+ throw new Error('Must call startBuild() first');
882
+ }
883
+
884
+ // FIXED: Use pre-calculated offset instead of reduce
885
+ const currentOffset = this.currentOffset;
886
+ const bigIntId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
887
+
888
+ // Create 40-byte record
889
+ const record = Buffer.allocUnsafe(SnomedRelationshipList.RELATIONSHIP_SIZE);
890
+
891
+ record.writeUInt32LE(source, 0);
892
+ record.writeUInt32LE(target, 4);
893
+ record.writeUInt32LE(relType, 8);
894
+ record.writeUInt32LE(module, 12);
895
+ record.writeUInt32LE(kind, 16);
896
+ record.writeUInt32LE(modifier, 20);
897
+ record.writeUInt16LE(date, 24);
898
+ record.writeUInt8(active ? 1 : 0, 26);
899
+ record.writeUInt8(defining ? 1 : 0, 27);
900
+ record.writeInt32LE(group, 28); // Signed integer
901
+ record.writeBigUInt64LE(bigIntId, 32);
902
+
903
+ this.builder.push(record);
904
+
905
+ // UPDATE: offset for next call
906
+ this.currentOffset += SnomedRelationshipList.RELATIONSHIP_SIZE;
907
+
908
+ return currentOffset;
909
+ }
910
+
911
+ doneBuild() {
912
+ if (!this.builder) {
913
+ throw new Error('No build in progress');
914
+ }
915
+
916
+ this.master = Buffer.concat(this.builder);
917
+ this.builder = null;
918
+ this.currentOffset = 0; // RESET: for potential future builds
919
+ }
920
+
921
+ clear() {
922
+ this.master = Buffer.alloc(0);
923
+ this.currentOffset = 0; // RESET: offset when clearing
924
+ }
925
+
926
+ get length() {
927
+ return this.master.length;
928
+ }
929
+ }
930
+
931
+ class SnomedReferenceSetMembers {
932
+ static MAGIC_NO_CHILDREN = 0xFFFFFFFF;
933
+
934
+ constructor(buffer = null) {
935
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
936
+ this.builder = null;
937
+ this.currentOffset = 0; // ADD: Track offset directly
938
+ }
939
+
940
+ getMemberCount(index) {
941
+ if (index === SnomedReferenceSetMembers.MAGIC_NO_CHILDREN) {
942
+ return 0;
943
+ }
944
+
945
+ if (index > this.master.length) {
946
+ throw new Error('Wrong length index getting Snomed list');
947
+ }
948
+
949
+ return this.master.readUInt32LE(index);
950
+ }
951
+
952
+ getMembers(index) {
953
+ if (index === SnomedReferenceSetMembers.MAGIC_NO_CHILDREN) {
954
+ return null;
955
+ }
956
+
957
+ if (index > this.master.length) {
958
+ throw new Error(`Wrong length index getting Snomed list. asked for ${index}, limit is ${this.master.length}`);
959
+ }
960
+
961
+ // Read count and ids flag
962
+ const count = this.master.readUInt32LE(index);
963
+ const ids = this.master.readUInt8(index + 4) !== 0;
964
+
965
+ let offset = index + 5; // Skip count + ids flag
966
+ const result = new Array(count);
967
+
968
+ for (let i = 0; i < count; i++) {
969
+ const member = {};
970
+
971
+ if (ids) {
972
+ // Read full member with GUID
973
+ member.id = this.master.subarray(offset, offset + 16); // 16-byte GUID as Buffer
974
+ offset += 16;
975
+ member.module = this.master.readUInt32LE(offset);
976
+ offset += 4;
977
+ member.date = this.master.readUInt16LE(offset);
978
+ offset += 2;
979
+ member.kind = this.master.readUInt8(offset);
980
+ offset += 1;
981
+ member.ref = this.master.readUInt32LE(offset);
982
+ offset += 4;
983
+ member.values = this.master.readUInt32LE(offset);
984
+ offset += 4;
985
+ } else {
986
+ // Read basic member without GUID/module/date
987
+ member.id = null;
988
+ member.module = null;
989
+ member.date = null;
990
+ member.kind = this.master.readUInt8(offset);
991
+ offset += 1;
992
+ member.ref = this.master.readUInt32LE(offset);
993
+ offset += 4;
994
+ member.values = this.master.readUInt32LE(offset);
995
+ offset += 4;
996
+ }
997
+
998
+ result[i] = member;
999
+ }
1000
+
1001
+ return result;
1002
+ }
1003
+
1004
+ startBuild() {
1005
+ this.builder = [];
1006
+ this.currentOffset = 0; // RESET: offset when starting build
1007
+ }
1008
+
1009
+ addMembers(ids, membersArray) {
1010
+ if (!this.builder) {
1011
+ throw new Error('Must call startBuild() first');
1012
+ }
1013
+
1014
+ // FIXED: Use pre-calculated offset instead of reduce
1015
+ const currentOffset = this.currentOffset;
1016
+
1017
+ // Calculate total size needed
1018
+ const count = membersArray.length;
1019
+ const bytesPerMember = ids ? 31 : 9; // 31 bytes with IDs, 9 bytes without
1020
+ const totalSize = 5 + (count * bytesPerMember); // 5 = count(4) + ids_flag(1)
1021
+
1022
+ const record = Buffer.allocUnsafe(totalSize);
1023
+ let offset = 0;
1024
+
1025
+ // Write count and ids flag
1026
+ record.writeUInt32LE(count, offset);
1027
+ offset += 4;
1028
+ record.writeUInt8(ids ? 1 : 0, offset);
1029
+ offset += 1;
1030
+
1031
+ // Write each member
1032
+ for (const member of membersArray) {
1033
+ if (ids) {
1034
+ // Write full member with GUID
1035
+ if (member.id && Buffer.isBuffer(member.id)) {
1036
+ member.id.copy(record, offset, 0, 16);
1037
+ } else if (typeof member.id === 'string') {
1038
+ // Convert GUID string to buffer if needed
1039
+ const guidBuffer = this.#guidStringToBuffer(member.id);
1040
+ guidBuffer.copy(record, offset, 0, 16);
1041
+ } else {
1042
+ // Zero-fill if no GUID provided
1043
+ record.fill(0, offset, offset + 16);
1044
+ }
1045
+ offset += 16;
1046
+
1047
+ record.writeUInt32LE(member.module || 0, offset);
1048
+ offset += 4;
1049
+ record.writeUInt16LE(member.date || 0, offset);
1050
+ offset += 2;
1051
+ }
1052
+
1053
+ record.writeUInt8(member.kind || 0, offset);
1054
+ offset += 1;
1055
+ record.writeUInt32LE(member.ref || 0, offset);
1056
+ offset += 4;
1057
+ record.writeUInt32LE(member.values || 0, offset);
1058
+ offset += 4;
1059
+ }
1060
+
1061
+ this.builder.push(record);
1062
+
1063
+ // UPDATE: offset for next call
1064
+ this.currentOffset += totalSize;
1065
+
1066
+ return currentOffset;
1067
+ }
1068
+
1069
+ // Helper method to convert GUID string to 16-byte buffer
1070
+ #guidStringToBuffer(guidString) {
1071
+ // Remove hyphens and braces from GUID string
1072
+ const cleanGuid = guidString.replace(/[-{}]/g, '');
1073
+ if (cleanGuid.length !== 32) {
1074
+ throw new Error('Invalid GUID format');
1075
+ }
1076
+ return Buffer.from(cleanGuid, 'hex');
1077
+ }
1078
+
1079
+ // Helper method to convert 16-byte buffer to GUID string
1080
+ guidBufferToString(guidBuffer) {
1081
+ if (!Buffer.isBuffer(guidBuffer) || guidBuffer.length !== 16) {
1082
+ return null;
1083
+ }
1084
+ const hex = guidBuffer.toString('hex');
1085
+ return `${hex.substr(0, 8)}-${hex.substr(8, 4)}-${hex.substr(12, 4)}-${hex.substr(16, 4)}-${hex.substr(20, 12)}`;
1086
+ }
1087
+
1088
+ doneBuild() {
1089
+ if (!this.builder) {
1090
+ throw new Error('No build in progress');
1091
+ }
1092
+
1093
+ this.master = Buffer.concat(this.builder);
1094
+ this.builder = null;
1095
+ this.currentOffset = 0; // RESET: for potential future builds
1096
+ }
1097
+
1098
+ clear() {
1099
+ this.master = Buffer.alloc(0);
1100
+ this.currentOffset = 0; // RESET: offset when clearing
1101
+ }
1102
+
1103
+ get length() {
1104
+ return this.master.length;
1105
+ }
1106
+ }
1107
+
1108
+ class SnomedReferenceSetIndex {
1109
+ static REFSET_SIZE_LANG = 32; // Always using lang version
1110
+ static REFSET_SIZE_NOLANG = 28;
1111
+
1112
+ constructor(buffer = null, hasLangs = true) {
1113
+ this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
1114
+ this.builder = null;
1115
+ this.hasLangs = hasLangs;
1116
+ this.recordSize = hasLangs ? SnomedReferenceSetIndex.REFSET_SIZE_LANG : SnomedReferenceSetIndex.REFSET_SIZE_NOLANG;
1117
+ }
1118
+
1119
+ getReferenceSet(index) {
1120
+ const byteIndex = index * this.recordSize;
1121
+
1122
+ if (byteIndex >= this.master.length) {
1123
+ throw new Error('Wrong length index getting snomed relationship Details');
1124
+ }
1125
+
1126
+ const result = {
1127
+ definition: this.master.readUInt32LE(byteIndex + 0),
1128
+ filename: this.master.readUInt32LE(byteIndex + 4),
1129
+ membersByRef: this.master.readUInt32LE(byteIndex + 8),
1130
+ membersByName: this.master.readUInt32LE(byteIndex + 12),
1131
+ fieldTypes: this.master.readUInt32LE(byteIndex + 16),
1132
+ name: this.master.readUInt32LE(byteIndex + 20),
1133
+ fieldNames: this.master.readUInt32LE(byteIndex + 24)
1134
+ };
1135
+
1136
+ // Only read langs field if hasLangs is true
1137
+ if (this.hasLangs) {
1138
+ result.langs = this.master.readUInt32LE(byteIndex + 28);
1139
+ } else {
1140
+ result.langs = 0;
1141
+ }
1142
+
1143
+ return result;
1144
+ }
1145
+
1146
+ getMembersByConcept(conceptIndex, byName = false) {
1147
+ // Linear search through records looking for matching concept
1148
+ for (let i = 0; i < this.count(); i++) {
1149
+ const offset = i * this.recordSize;
1150
+ const definition = this.master.readUInt32LE(offset + 0);
1151
+
1152
+ if (definition === conceptIndex) {
1153
+ if (byName) {
1154
+ return this.master.readUInt32LE(offset + 12); // membersByName
1155
+ } else {
1156
+ return this.master.readUInt32LE(offset + 8); // membersByRef
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ return 0; // Not found
1162
+ }
1163
+
1164
+ getRefSetByConcept(conceptIndex) {
1165
+ // Linear search through records looking for matching concept, return record index
1166
+ for (let i = 0; i < this.count(); i++) {
1167
+ const offset = i * this.recordSize;
1168
+ const definition = this.master.readUInt32LE(offset + 0);
1169
+
1170
+ if (definition === conceptIndex) {
1171
+ return i; // Return record index, not byte offset
1172
+ }
1173
+ }
1174
+
1175
+ return 0; // Not found
1176
+ }
1177
+
1178
+ count() {
1179
+ return Math.floor(this.master.length / this.recordSize);
1180
+ }
1181
+
1182
+ // Build methods
1183
+ startBuild() {
1184
+ this.builder = [];
1185
+ }
1186
+
1187
+ addReferenceSet(name, filename, definition, membersByRef, membersByName, fieldTypes, fieldNames, langs) {
1188
+ if (!this.builder) {
1189
+ throw new Error('Must call startBuild() first');
1190
+ }
1191
+
1192
+ // Create 32-byte record - store in the order used by Pascal AddReferenceSet
1193
+ const record = Buffer.allocUnsafe(this.recordSize);
1194
+
1195
+ record.writeUInt32LE(definition, 0); // First stored
1196
+ record.writeUInt32LE(filename, 4); // Second stored
1197
+ record.writeUInt32LE(membersByRef, 8); // Third stored
1198
+ record.writeUInt32LE(membersByName, 12); // Fourth stored
1199
+ record.writeUInt32LE(fieldTypes, 16); // Fifth stored
1200
+ record.writeUInt32LE(name, 20); // Sixth stored
1201
+ record.writeUInt32LE(fieldNames, 24); // Seventh stored
1202
+
1203
+ if (this.hasLangs) {
1204
+ record.writeUInt32LE(langs, 28); // Eighth stored
1205
+ }
1206
+
1207
+ this.builder.push(record);
1208
+ }
1209
+
1210
+ doneBuild() {
1211
+ if (!this.builder) {
1212
+ throw new Error('No build in progress');
1213
+ }
1214
+
1215
+ this.master = Buffer.concat(this.builder);
1216
+ this.builder = null;
1217
+ }
1218
+
1219
+ clear() {
1220
+ this.master = Buffer.alloc(0);
1221
+ }
1222
+
1223
+ get length() {
1224
+ return this.master.length;
1225
+ }
1226
+ }
1227
+
1228
+
1229
+ class SnomedFileReader {
1230
+ constructor(filePath) {
1231
+ this.filePath = filePath;
1232
+ this.buffer = null;
1233
+ this.offset = 0;
1234
+ }
1235
+
1236
+ async load() {
1237
+ // Read entire file into buffer
1238
+ this.buffer = await fs.promises.readFile(this.filePath);
1239
+ this.offset = 0;
1240
+ }
1241
+
1242
+ // Read a string (Free Pascal TReader format: type byte + length + string bytes)
1243
+ readString() {
1244
+ // Read TValueType enum (1 byte)
1245
+ const valueType = this.buffer.readUInt8(this.offset);
1246
+ this.offset += 1;
1247
+
1248
+ let length;
1249
+ // Based on the actual file format we're seeing:
1250
+ // Type 6 appears to use 1-byte length (not 4-byte as we expected)
1251
+ if (valueType === 6) {
1252
+ length = this.buffer.readUInt8(this.offset); // 1-byte length
1253
+ this.offset += 1;
1254
+ } else {
1255
+ // We'll handle other types when we encounter them
1256
+ throw new Error(`Unknown string type: ${valueType}`);
1257
+ }
1258
+
1259
+ if (length === 0) {
1260
+ return '';
1261
+ }
1262
+
1263
+ const str = this.buffer.toString('utf8', this.offset, this.offset + length);
1264
+ this.offset += length;
1265
+ return str;
1266
+ }
1267
+
1268
+ // Read a 4-byte integer (TReader format: type byte + integer)
1269
+ readInteger() {
1270
+ // Read type byte first
1271
+ const valueType = this.buffer.readUInt8(this.offset);
1272
+ this.offset += 1;
1273
+
1274
+ let value;
1275
+ switch (valueType) {
1276
+ case 2: // Int8
1277
+ value = this.buffer.readInt8(this.offset);
1278
+ this.offset += 1;
1279
+ break;
1280
+ case 3: // Int16
1281
+ value = this.buffer.readInt16LE(this.offset);
1282
+ this.offset += 2;
1283
+ break;
1284
+ case 4: // Int32
1285
+ value = this.buffer.readInt32LE(this.offset);
1286
+ this.offset += 4;
1287
+ break;
1288
+ default:
1289
+ throw new Error(`Unknown integer type: ${valueType}`);
1290
+ }
1291
+
1292
+ return value;
1293
+ }
1294
+
1295
+ // Read an 8-byte UInt64 (little-endian)
1296
+ readUInt64() {
1297
+ const value = this.buffer.readBigUInt64LE(this.offset);
1298
+ this.offset += 8;
1299
+ return value;
1300
+ }
1301
+
1302
+ // Read a byte array (length-prefixed using TReader.ReadInteger format)
1303
+ readBytes() {
1304
+ const length = this.readInteger(); // Use TReader format, not raw bytes
1305
+ const bytes = this.buffer.subarray(this.offset, this.offset + length);
1306
+ this.offset += length;
1307
+ return bytes;
1308
+ }
1309
+
1310
+ // Main loading method that matches the Pascal logic
1311
+ async loadSnomedData() {
1312
+ await this.load();
1313
+
1314
+ const result = {
1315
+ cacheVersion: this.readString(), // This is "16" or "17" - the cache format version
1316
+ versionUri: this.readString(),
1317
+ versionDate: this.readString(),
1318
+ isTesting: false,
1319
+
1320
+ // Version-dependent settings
1321
+ hasLangs: false, // Default for older versions
1322
+ isUTF16: false, // Will be determined by version
1323
+
1324
+ // Extract edition and version from URI
1325
+ edition: null,
1326
+ version: null,
1327
+
1328
+ // Structure data
1329
+ strings: this.readBytes(),
1330
+ refs: this.readBytes(),
1331
+ desc: this.readBytes(),
1332
+ words: this.readBytes(),
1333
+ stems: this.readBytes(),
1334
+ concept: this.readBytes(),
1335
+ rel: this.readBytes(),
1336
+ refSetIndex: this.readBytes(),
1337
+ refSetMembers: this.readBytes(),
1338
+ descRef: this.readBytes(),
1339
+
1340
+ // Additional data
1341
+ isAIndex: this.readInteger(),
1342
+ inactiveRoots: [],
1343
+ activeRoots: [],
1344
+ defaultLanguage: null // Will be read last
1345
+ };
1346
+
1347
+ result.isTesting = result.versionUri.includes("/xsct/");
1348
+ // Parse edition and version from URI
1349
+ const uriParts = result.versionUri.split('/');
1350
+ if (uriParts.length >= 7) {
1351
+ result.edition = uriParts[4];
1352
+ result.version = uriParts[6];
1353
+ }
1354
+
1355
+ // Determine settings based on cache version string (matching Pascal logic)
1356
+ if (result.cacheVersion === '17') { // SNOMED_CACHE_VERSION_CURRENT
1357
+ result.hasLangs = true;
1358
+ } else if (result.cacheVersion === '16') { // Assuming this is UTF8 version for now
1359
+ result.isUTF16 = false;
1360
+ result.hasLangs = false;
1361
+ } else {
1362
+ throw new Error(`Unsupported SNOMED cache version: ${result.cacheVersion}`);
1363
+ }
1364
+
1365
+ const inactiveRootsLength = this.readInteger();
1366
+
1367
+ for (let i = 0; i < inactiveRootsLength; i++) {
1368
+ result.inactiveRoots.push(this.readUInt64());
1369
+ }
1370
+
1371
+ // Read active roots array
1372
+ const activeRootsLength = this.readInteger();
1373
+ for (let i = 0; i < activeRootsLength; i++) {
1374
+ result.activeRoots.push(this.readUInt64());
1375
+ }
1376
+
1377
+ // Read default language LAST
1378
+ result.defaultLanguage = this.readInteger();
1379
+
1380
+ return result;
1381
+ }
1382
+ }
1383
+
1384
+ module.exports = {
1385
+ SnomedStrings,
1386
+ SnomedWords,
1387
+ SnomedStems,
1388
+ SnomedReferences,
1389
+ SnomedDescriptions,
1390
+ SnomedDescriptionIndex,
1391
+ SnomedConceptList,
1392
+ SnomedRelationshipList,
1393
+ SnomedReferenceSetMembers,
1394
+ SnomedReferenceSetIndex,
1395
+ SnomedFileReader
1396
+ };