fhirsmith 0.18.2 → 1.0.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.
- package/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +149 -0
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +132 -59
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +74 -21
- package/ips/presenter.js +1 -1
- package/ips/readme.md +132 -25
- package/ips/render.js +466 -77
- package/ips/store.js +79 -22
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/crawler.js +84 -108
- package/registry/documentation/configuration.md +34 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +5 -4
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +154 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +29 -1
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +148 -14
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +185 -66
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +608 -64
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +57 -58
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +278 -42
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/ecl.js +47 -6
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +166 -13
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +27 -4
- package/tx/vs/vs-vsac.js +648 -100
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/tx/sct/structures.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const { mapFile, prewarmFile } = require('./mmap');
|
|
3
|
+
|
|
4
|
+
// The ArrayBuffers of cache files loaded by SnomedFileReader. A structure uses its section of
|
|
5
|
+
// one in place (nothing writes to a loaded structure, and the file stays loaded for the life
|
|
6
|
+
// of the process); any other buffer is copied, so the structure owns its bytes.
|
|
7
|
+
const fileBuffers = new WeakSet();
|
|
8
|
+
|
|
9
|
+
function adopt(buffer) {
|
|
10
|
+
if (!buffer) return Buffer.alloc(0);
|
|
11
|
+
return fileBuffers.has(buffer.buffer) ? buffer : Buffer.from(buffer);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A structure made from a buffer is a loaded one, and is read-only: it may be sharing the
|
|
15
|
+
// cache file's memory (see adopt). Only a structure the importer creates empty and builds can
|
|
16
|
+
// be written to. Every method that changes a structure calls this first.
|
|
17
|
+
function checkWritable(structure) {
|
|
18
|
+
if (structure.readOnly) {
|
|
19
|
+
throw new Error(`${structure.constructor.name} is read-only: it was loaded from a cache file, and can only be changed while importing`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
2
22
|
|
|
3
23
|
|
|
4
24
|
class SnomedStrings {
|
|
5
25
|
constructor(buffer = null) {
|
|
6
|
-
this.master =
|
|
26
|
+
this.master = adopt(buffer);
|
|
27
|
+
this.readOnly = !!buffer;
|
|
7
28
|
this.decoder = new TextDecoder('utf-8');
|
|
8
29
|
this.builder = null;
|
|
9
30
|
this.currentOffset = 0; // ADD: Track offset directly
|
|
@@ -28,11 +49,13 @@ class SnomedStrings {
|
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
startBuild() {
|
|
52
|
+
checkWritable(this);
|
|
31
53
|
this.builder = [];
|
|
32
54
|
this.currentOffset = 0; // RESET: offset when starting build
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
reopen() {
|
|
58
|
+
checkWritable(this);
|
|
36
59
|
this.builder = [this.master];
|
|
37
60
|
this.currentOffset = this.master.length; // SET: offset to current master length
|
|
38
61
|
}
|
|
@@ -75,6 +98,7 @@ class SnomedStrings {
|
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
clear() {
|
|
101
|
+
checkWritable(this);
|
|
78
102
|
this.master = Buffer.alloc(0);
|
|
79
103
|
this.currentOffset = 0; // RESET: offset when clearing
|
|
80
104
|
}
|
|
@@ -86,7 +110,8 @@ class SnomedStrings {
|
|
|
86
110
|
|
|
87
111
|
class SnomedWords {
|
|
88
112
|
constructor(buffer = null) {
|
|
89
|
-
this.master =
|
|
113
|
+
this.master = adopt(buffer);
|
|
114
|
+
this.readOnly = !!buffer;
|
|
90
115
|
this.builder = null;
|
|
91
116
|
}
|
|
92
117
|
|
|
@@ -114,6 +139,7 @@ class SnomedWords {
|
|
|
114
139
|
}
|
|
115
140
|
|
|
116
141
|
startBuild() {
|
|
142
|
+
checkWritable(this);
|
|
117
143
|
this.builder = [];
|
|
118
144
|
}
|
|
119
145
|
|
|
@@ -140,6 +166,7 @@ class SnomedWords {
|
|
|
140
166
|
}
|
|
141
167
|
|
|
142
168
|
clear() {
|
|
169
|
+
checkWritable(this);
|
|
143
170
|
this.master = Buffer.alloc(0);
|
|
144
171
|
}
|
|
145
172
|
|
|
@@ -150,7 +177,8 @@ class SnomedWords {
|
|
|
150
177
|
|
|
151
178
|
class SnomedStems {
|
|
152
179
|
constructor(buffer = null) {
|
|
153
|
-
this.master =
|
|
180
|
+
this.master = adopt(buffer);
|
|
181
|
+
this.readOnly = !!buffer;
|
|
154
182
|
this.builder = null;
|
|
155
183
|
}
|
|
156
184
|
|
|
@@ -178,6 +206,7 @@ class SnomedStems {
|
|
|
178
206
|
}
|
|
179
207
|
|
|
180
208
|
startBuild() {
|
|
209
|
+
checkWritable(this);
|
|
181
210
|
this.builder = [];
|
|
182
211
|
}
|
|
183
212
|
|
|
@@ -204,6 +233,7 @@ class SnomedStems {
|
|
|
204
233
|
}
|
|
205
234
|
|
|
206
235
|
clear() {
|
|
236
|
+
checkWritable(this);
|
|
207
237
|
this.master = Buffer.alloc(0);
|
|
208
238
|
}
|
|
209
239
|
|
|
@@ -214,7 +244,8 @@ class SnomedStems {
|
|
|
214
244
|
|
|
215
245
|
class SnomedReferences {
|
|
216
246
|
constructor(buffer = null) {
|
|
217
|
-
this.master =
|
|
247
|
+
this.master = adopt(buffer);
|
|
248
|
+
this.readOnly = !!buffer;
|
|
218
249
|
this.builder = null;
|
|
219
250
|
this.currentOffset = 0; // ADD: Track offset directly
|
|
220
251
|
this.MAGIC_NO_CHILDREN = 0xFFFFFFFF; // Assuming this constant - adjust as needed
|
|
@@ -263,6 +294,7 @@ class SnomedReferences {
|
|
|
263
294
|
}
|
|
264
295
|
|
|
265
296
|
startBuild() {
|
|
297
|
+
checkWritable(this);
|
|
266
298
|
this.builder = [];
|
|
267
299
|
this.currentOffset = 0; // RESET: offset when starting build
|
|
268
300
|
}
|
|
@@ -316,6 +348,7 @@ class SnomedReferences {
|
|
|
316
348
|
}
|
|
317
349
|
|
|
318
350
|
clear() {
|
|
351
|
+
checkWritable(this);
|
|
319
352
|
this.master = Buffer.alloc(0);
|
|
320
353
|
this.currentOffset = 0; // RESET: offset when clearing
|
|
321
354
|
}
|
|
@@ -342,7 +375,8 @@ class SnomedDescriptions {
|
|
|
342
375
|
static FLAG_PendingMove = 11;
|
|
343
376
|
|
|
344
377
|
constructor(buffer = null) {
|
|
345
|
-
this.master =
|
|
378
|
+
this.master = adopt(buffer);
|
|
379
|
+
this.readOnly = !!buffer;
|
|
346
380
|
this.builder = null;
|
|
347
381
|
this.currentOffset = 0; // ADD: Track offset directly
|
|
348
382
|
}
|
|
@@ -382,6 +416,7 @@ class SnomedDescriptions {
|
|
|
382
416
|
}
|
|
383
417
|
|
|
384
418
|
startBuild() {
|
|
419
|
+
checkWritable(this);
|
|
385
420
|
this.builder = [];
|
|
386
421
|
this.currentOffset = 0; // RESET: offset when starting build
|
|
387
422
|
}
|
|
@@ -417,7 +452,44 @@ class SnomedDescriptions {
|
|
|
417
452
|
return currentOffset;
|
|
418
453
|
}
|
|
419
454
|
|
|
455
|
+
/**
|
|
456
|
+
* The concepts that have an active description in the language (the RF2 language code
|
|
457
|
+
* byte) whose term passes test, for descriptions [start, end) by position, leaving out
|
|
458
|
+
* descriptions of type skipKind (a concept index, or -1 for none). Reads the records in
|
|
459
|
+
* place rather than decoding each one, since a scan covers the whole edition. Adds the
|
|
460
|
+
* concept indexes to out (a Set); returns how many descriptions there are.
|
|
461
|
+
*/
|
|
462
|
+
matchActiveTerms(start, end, lang, strings, test, out, skipKind = -1) {
|
|
463
|
+
const size = SnomedDescriptions.DESC_SIZE;
|
|
464
|
+
const count = Math.floor(this.master.length / size);
|
|
465
|
+
const last = Math.min(end, count);
|
|
466
|
+
for (let i = start; i < last; i++) {
|
|
467
|
+
const offset = i * size;
|
|
468
|
+
if (this.master.readUInt8(offset + 4) === 0 || this.master.readUInt8(offset + 31) !== lang
|
|
469
|
+
|| this.master.readUInt32LE(offset + 21) === skipKind) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (test(strings.getEntry(this.master.readUInt32LE(offset)))) {
|
|
473
|
+
out.add(this.master.readUInt32LE(offset + 13));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return count;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** The language code bytes that active descriptions use */
|
|
480
|
+
activeLanguages() {
|
|
481
|
+
const size = SnomedDescriptions.DESC_SIZE;
|
|
482
|
+
const langs = new Set();
|
|
483
|
+
for (let offset = 0; offset + size <= this.master.length; offset += size) {
|
|
484
|
+
if (this.master.readUInt8(offset + 4) !== 0) {
|
|
485
|
+
langs.add(this.master.readUInt8(offset + 31));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return langs;
|
|
489
|
+
}
|
|
490
|
+
|
|
420
491
|
setRefsets(index, refsets, valueses) {
|
|
492
|
+
checkWritable(this);
|
|
421
493
|
if (index >= this.master.length) {
|
|
422
494
|
throw new Error('Wrong length index getting snomed Desc Details');
|
|
423
495
|
}
|
|
@@ -441,6 +513,7 @@ class SnomedDescriptions {
|
|
|
441
513
|
}
|
|
442
514
|
|
|
443
515
|
clear() {
|
|
516
|
+
checkWritable(this);
|
|
444
517
|
this.master = Buffer.alloc(0);
|
|
445
518
|
this.currentOffset = 0; // RESET: offset when clearing
|
|
446
519
|
}
|
|
@@ -452,7 +525,8 @@ class SnomedDescriptions {
|
|
|
452
525
|
|
|
453
526
|
class SnomedDescriptionIndex {
|
|
454
527
|
constructor(buffer = null) {
|
|
455
|
-
this.master =
|
|
528
|
+
this.master = adopt(buffer);
|
|
529
|
+
this.readOnly = !!buffer;
|
|
456
530
|
this.builder = null;
|
|
457
531
|
}
|
|
458
532
|
|
|
@@ -488,6 +562,7 @@ class SnomedDescriptionIndex {
|
|
|
488
562
|
}
|
|
489
563
|
|
|
490
564
|
startBuild() {
|
|
565
|
+
checkWritable(this);
|
|
491
566
|
this.builder = [];
|
|
492
567
|
}
|
|
493
568
|
|
|
@@ -517,6 +592,7 @@ class SnomedDescriptionIndex {
|
|
|
517
592
|
}
|
|
518
593
|
|
|
519
594
|
clear() {
|
|
595
|
+
checkWritable(this);
|
|
520
596
|
this.master = Buffer.alloc(0);
|
|
521
597
|
}
|
|
522
598
|
|
|
@@ -537,7 +613,8 @@ class SnomedConceptList {
|
|
|
537
613
|
static MAGIC_NO_CHILDREN = 0xFFFFFFFF;
|
|
538
614
|
|
|
539
615
|
constructor(buffer = null) {
|
|
540
|
-
this.master =
|
|
616
|
+
this.master = adopt(buffer);
|
|
617
|
+
this.readOnly = !!buffer;
|
|
541
618
|
this.builder = null;
|
|
542
619
|
this.currentOffset = 0; // ADD: Track offset directly
|
|
543
620
|
}
|
|
@@ -669,6 +746,7 @@ class SnomedConceptList {
|
|
|
669
746
|
}
|
|
670
747
|
|
|
671
748
|
setNormalFormDuringBuild(index, value) {
|
|
749
|
+
checkWritable(this);
|
|
672
750
|
// Special version that can be called during building phase
|
|
673
751
|
// This bypasses the post-build access check for normal forms specifically
|
|
674
752
|
if (index >= this.master.length) {
|
|
@@ -709,67 +787,80 @@ class SnomedConceptList {
|
|
|
709
787
|
|
|
710
788
|
// All the setter methods - these require doneBuild() to have been called
|
|
711
789
|
setParents(index, active, inactive) {
|
|
790
|
+
checkWritable(this);
|
|
712
791
|
this.#checkPostBuildAccess(index);
|
|
713
792
|
this.master.writeUInt32LE(active, index + 9);
|
|
714
793
|
this.master.writeUInt32LE(inactive, index + 52);
|
|
715
794
|
}
|
|
716
795
|
|
|
717
796
|
setDescriptions(index, value) {
|
|
797
|
+
checkWritable(this);
|
|
718
798
|
this.#checkPostBuildAccess(index);
|
|
719
799
|
this.master.writeUInt32LE(value, index + 13);
|
|
720
800
|
}
|
|
721
801
|
|
|
722
802
|
setModuleId(index, moduleIndex) {
|
|
803
|
+
checkWritable(this);
|
|
723
804
|
this.#checkPostBuildAccess(index);
|
|
724
805
|
this.master.writeUInt32LE(moduleIndex, index + 36);
|
|
725
806
|
}
|
|
726
807
|
|
|
727
808
|
setInbounds(index, value) {
|
|
809
|
+
checkWritable(this);
|
|
728
810
|
this.#checkPostBuildAccess(index);
|
|
729
811
|
this.master.writeUInt32LE(value, index + 17);
|
|
730
812
|
}
|
|
731
813
|
|
|
732
814
|
setOutbounds(index, value) {
|
|
815
|
+
checkWritable(this);
|
|
733
816
|
this.#checkPostBuildAccess(index);
|
|
734
817
|
this.master.writeUInt32LE(value, index + 21);
|
|
735
818
|
}
|
|
736
819
|
|
|
737
820
|
setAllDesc(index, value) {
|
|
821
|
+
checkWritable(this);
|
|
738
822
|
this.#checkPostBuildAccess(index);
|
|
739
823
|
this.master.writeUInt32LE(value, index + 25);
|
|
740
824
|
}
|
|
741
825
|
|
|
742
826
|
setDepth(index, value) {
|
|
827
|
+
checkWritable(this);
|
|
743
828
|
this.#checkPostBuildAccess(index);
|
|
744
829
|
this.master.writeUInt8(value, index + 29);
|
|
745
830
|
}
|
|
746
831
|
|
|
747
832
|
setStems(index, value) {
|
|
833
|
+
checkWritable(this);
|
|
748
834
|
this.#checkPostBuildAccess(index);
|
|
749
835
|
this.master.writeUInt32LE(value, index + 30);
|
|
750
836
|
}
|
|
751
837
|
|
|
752
838
|
setDate(index, effectiveTime) {
|
|
839
|
+
checkWritable(this);
|
|
753
840
|
this.#checkPostBuildAccess(index);
|
|
754
841
|
this.master.writeUInt16LE(effectiveTime, index + 34);
|
|
755
842
|
}
|
|
756
843
|
|
|
757
844
|
setStatus(index, value) {
|
|
845
|
+
checkWritable(this);
|
|
758
846
|
this.#checkPostBuildAccess(index);
|
|
759
847
|
this.master.writeUInt32LE(value, index + 40);
|
|
760
848
|
}
|
|
761
849
|
|
|
762
850
|
setRefsets(index, value) {
|
|
851
|
+
checkWritable(this);
|
|
763
852
|
this.#checkPostBuildAccess(index);
|
|
764
853
|
this.master.writeUInt32LE(value, index + 44);
|
|
765
854
|
}
|
|
766
855
|
|
|
767
856
|
setNormalForm(index, value) {
|
|
857
|
+
checkWritable(this);
|
|
768
858
|
this.#checkPostBuildAccess(index);
|
|
769
859
|
this.master.writeUInt32LE(value, index + 48);
|
|
770
860
|
}
|
|
771
861
|
|
|
772
862
|
setFlag(index, flags) {
|
|
863
|
+
checkWritable(this);
|
|
773
864
|
this.#checkPostBuildAccess(index);
|
|
774
865
|
this.master.writeUInt8(flags, index + 8);
|
|
775
866
|
}
|
|
@@ -780,6 +871,7 @@ class SnomedConceptList {
|
|
|
780
871
|
|
|
781
872
|
// Build methods
|
|
782
873
|
startBuild() {
|
|
874
|
+
checkWritable(this);
|
|
783
875
|
this.builder = [];
|
|
784
876
|
this.currentOffset = 0; // RESET: offset when starting build
|
|
785
877
|
}
|
|
@@ -831,6 +923,7 @@ class SnomedConceptList {
|
|
|
831
923
|
}
|
|
832
924
|
|
|
833
925
|
clear() {
|
|
926
|
+
checkWritable(this);
|
|
834
927
|
this.master = Buffer.alloc(0);
|
|
835
928
|
this.currentOffset = 0; // RESET: offset when clearing
|
|
836
929
|
}
|
|
@@ -846,7 +939,8 @@ class SnomedRelationshipList {
|
|
|
846
939
|
static REFSET_SIZE_LANG = 32;
|
|
847
940
|
|
|
848
941
|
constructor(buffer = null) {
|
|
849
|
-
this.master =
|
|
942
|
+
this.master = adopt(buffer);
|
|
943
|
+
this.readOnly = !!buffer;
|
|
850
944
|
this.builder = null;
|
|
851
945
|
this.currentOffset = 0; // ADD: Track offset directly
|
|
852
946
|
}
|
|
@@ -871,12 +965,21 @@ class SnomedRelationshipList {
|
|
|
871
965
|
};
|
|
872
966
|
}
|
|
873
967
|
|
|
968
|
+
// Whether the relationship row at index is active, without decoding the whole row
|
|
969
|
+
isActive(index) {
|
|
970
|
+
if (index >= this.master.length) {
|
|
971
|
+
throw new Error('Wrong length index getting snomed relationship Details');
|
|
972
|
+
}
|
|
973
|
+
return this.master.readUInt8(index + 26) !== 0;
|
|
974
|
+
}
|
|
975
|
+
|
|
874
976
|
count() {
|
|
875
977
|
return Math.floor(this.master.length / SnomedRelationshipList.RELATIONSHIP_SIZE);
|
|
876
978
|
}
|
|
877
979
|
|
|
878
980
|
// Build methods
|
|
879
981
|
startBuild() {
|
|
982
|
+
checkWritable(this);
|
|
880
983
|
this.builder = [];
|
|
881
984
|
this.currentOffset = 0; // RESET: offset when starting build
|
|
882
985
|
}
|
|
@@ -924,6 +1027,7 @@ class SnomedRelationshipList {
|
|
|
924
1027
|
}
|
|
925
1028
|
|
|
926
1029
|
clear() {
|
|
1030
|
+
checkWritable(this);
|
|
927
1031
|
this.master = Buffer.alloc(0);
|
|
928
1032
|
this.currentOffset = 0; // RESET: offset when clearing
|
|
929
1033
|
}
|
|
@@ -937,7 +1041,8 @@ class SnomedReferenceSetMembers {
|
|
|
937
1041
|
static MAGIC_NO_CHILDREN = 0xFFFFFFFF;
|
|
938
1042
|
|
|
939
1043
|
constructor(buffer = null) {
|
|
940
|
-
this.master =
|
|
1044
|
+
this.master = adopt(buffer);
|
|
1045
|
+
this.readOnly = !!buffer;
|
|
941
1046
|
this.builder = null;
|
|
942
1047
|
this.currentOffset = 0; // ADD: Track offset directly
|
|
943
1048
|
}
|
|
@@ -1007,6 +1112,7 @@ class SnomedReferenceSetMembers {
|
|
|
1007
1112
|
}
|
|
1008
1113
|
|
|
1009
1114
|
startBuild() {
|
|
1115
|
+
checkWritable(this);
|
|
1010
1116
|
this.builder = [];
|
|
1011
1117
|
this.currentOffset = 0; // RESET: offset when starting build
|
|
1012
1118
|
}
|
|
@@ -1101,6 +1207,7 @@ class SnomedReferenceSetMembers {
|
|
|
1101
1207
|
}
|
|
1102
1208
|
|
|
1103
1209
|
clear() {
|
|
1210
|
+
checkWritable(this);
|
|
1104
1211
|
this.master = Buffer.alloc(0);
|
|
1105
1212
|
this.currentOffset = 0; // RESET: offset when clearing
|
|
1106
1213
|
}
|
|
@@ -1115,7 +1222,8 @@ class SnomedReferenceSetIndex {
|
|
|
1115
1222
|
static REFSET_SIZE_NOLANG = 28;
|
|
1116
1223
|
|
|
1117
1224
|
constructor(buffer = null, hasLangs = true) {
|
|
1118
|
-
this.master =
|
|
1225
|
+
this.master = adopt(buffer);
|
|
1226
|
+
this.readOnly = !!buffer;
|
|
1119
1227
|
this.builder = null;
|
|
1120
1228
|
this.hasLangs = hasLangs;
|
|
1121
1229
|
this.recordSize = hasLangs ? SnomedReferenceSetIndex.REFSET_SIZE_LANG : SnomedReferenceSetIndex.REFSET_SIZE_NOLANG;
|
|
@@ -1186,6 +1294,7 @@ class SnomedReferenceSetIndex {
|
|
|
1186
1294
|
|
|
1187
1295
|
// Build methods
|
|
1188
1296
|
startBuild() {
|
|
1297
|
+
checkWritable(this);
|
|
1189
1298
|
this.builder = [];
|
|
1190
1299
|
}
|
|
1191
1300
|
|
|
@@ -1222,6 +1331,7 @@ class SnomedReferenceSetIndex {
|
|
|
1222
1331
|
}
|
|
1223
1332
|
|
|
1224
1333
|
clear() {
|
|
1334
|
+
checkWritable(this);
|
|
1225
1335
|
this.master = Buffer.alloc(0);
|
|
1226
1336
|
}
|
|
1227
1337
|
|
|
@@ -1232,15 +1342,58 @@ class SnomedReferenceSetIndex {
|
|
|
1232
1342
|
|
|
1233
1343
|
|
|
1234
1344
|
class SnomedFileReader {
|
|
1235
|
-
|
|
1345
|
+
/**
|
|
1346
|
+
* @param {string} filePath
|
|
1347
|
+
* @param {string} [loadMode] 'read' (default): read the file into memory. 'mmap': map it
|
|
1348
|
+
* read-only, so its pages are file-backed and shared with the page cache. 'prewarm': mapped,
|
|
1349
|
+
* then read through once in the background at startup, so its pages are already in the
|
|
1350
|
+
* page cache when requests arrive; startup doesn't wait for it. Where mapping is not
|
|
1351
|
+
* available (see mmap.js) the file is read, and `mapped` stays false.
|
|
1352
|
+
* @param {object} [log] - a logger with warn(); says why a file that was to be mapped was read
|
|
1353
|
+
*/
|
|
1354
|
+
constructor(filePath, loadMode = 'read', log = null) {
|
|
1236
1355
|
this.filePath = filePath;
|
|
1356
|
+
this.loadMode = loadMode;
|
|
1357
|
+
this.log = log;
|
|
1237
1358
|
this.buffer = null;
|
|
1359
|
+
this.mapped = false; // true once load() has the buffer as a memory mapping of the file
|
|
1238
1360
|
this.offset = 0;
|
|
1239
1361
|
}
|
|
1240
1362
|
|
|
1363
|
+
/**
|
|
1364
|
+
* The edition version URI in a cache file's header, without loading the file.
|
|
1365
|
+
* @param {string} filePath
|
|
1366
|
+
* @returns {string|null}
|
|
1367
|
+
*/
|
|
1368
|
+
static versionUri(filePath) {
|
|
1369
|
+
const head = Buffer.alloc(1024);
|
|
1370
|
+
const fd = fs.openSync(filePath, 'r');
|
|
1371
|
+
let n;
|
|
1372
|
+
try {
|
|
1373
|
+
n = fs.readSync(fd, head, 0, head.length, 0);
|
|
1374
|
+
} finally {
|
|
1375
|
+
fs.closeSync(fd);
|
|
1376
|
+
}
|
|
1377
|
+
const reader = new SnomedFileReader(filePath);
|
|
1378
|
+
reader.buffer = head.subarray(0, n);
|
|
1379
|
+
try {
|
|
1380
|
+
reader.readString(); // cache version
|
|
1381
|
+
return reader.readString();
|
|
1382
|
+
} catch {
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1241
1387
|
async load() {
|
|
1242
|
-
|
|
1243
|
-
this.
|
|
1388
|
+
const warn = this.log ? (msg) => this.log.warn(msg) : undefined;
|
|
1389
|
+
const mapped = this.loadMode !== 'read' ? mapFile(this.filePath, warn) : null;
|
|
1390
|
+
this.mapped = mapped !== null;
|
|
1391
|
+
this.buffer = mapped || await fs.promises.readFile(this.filePath);
|
|
1392
|
+
fileBuffers.add(this.buffer.buffer);
|
|
1393
|
+
if (mapped && this.loadMode === 'prewarm') {
|
|
1394
|
+
// not awaited: the server starts while the file is read into the page cache
|
|
1395
|
+
this.prewarmed = prewarmFile(this.filePath).catch(() => null);
|
|
1396
|
+
}
|
|
1244
1397
|
this.offset = 0;
|
|
1245
1398
|
}
|
|
1246
1399
|
|
package/tx/sct/text-index.js
CHANGED
|
@@ -89,9 +89,10 @@ class SnomedTextIndex {
|
|
|
89
89
|
* Build the index for a loaded edition. Yields to the event loop every
|
|
90
90
|
* BUILD_SLICE ms of work.
|
|
91
91
|
* @param {object} sct - SnomedServices
|
|
92
|
+
* @param {number|null} lang - only descriptions in this language (byte), or null for all
|
|
92
93
|
* @returns {Promise<SnomedTextIndex>}
|
|
93
94
|
*/
|
|
94
|
-
static async build(sct) {
|
|
95
|
+
static async build(sct, lang = null) {
|
|
95
96
|
const concepts = sct.concepts;
|
|
96
97
|
const conceptSize = concepts.constructor.CONCEPT_SIZE;
|
|
97
98
|
const conceptCount = concepts.count();
|
|
@@ -117,6 +118,9 @@ class SnomedTextIndex {
|
|
|
117
118
|
if (descMaster.readUInt8(descIndex + 4) === 0) {
|
|
118
119
|
continue; // inactive description
|
|
119
120
|
}
|
|
121
|
+
if (lang !== null && descMaster.readUInt8(descIndex + 31) !== lang) {
|
|
122
|
+
continue; // another language
|
|
123
|
+
}
|
|
120
124
|
const term = sct.strings.getEntry(descMaster.readUInt32LE(descIndex));
|
|
121
125
|
for (const word of tokenize(term)) {
|
|
122
126
|
if (word.length < MIN_WORD_LENGTH) {
|
|
@@ -202,11 +202,11 @@ function renderHtml(params) {
|
|
|
202
202
|
h.push('<tr><th>Title</th><th>Type</th><th>Version</th><th>Added</th><th>Download</th></tr>');
|
|
203
203
|
for (const e of [...sel.entries].reverse()) {
|
|
204
204
|
h.push('<tr>');
|
|
205
|
-
h.push(`<td>${esc(e.title)}${e.summary ? `<br/><span style="color:
|
|
205
|
+
h.push(`<td>${esc(e.title)}${e.summary ? `<br/><span style="background-color: #eeeeee">${esc(e.summary)}</span>` : ''}${e.rights ? `<br/><i>${esc(e.rights)}</i>` : ''}</td>`);
|
|
206
206
|
h.push(`<td>${esc(e.category.term)}${isFhirCategory(e) ? ` (R${esc((e.fhirVersion || fhirVersion).split('.')[0])})` : ''}</td>`);
|
|
207
207
|
h.push(`<td><code>${esc(e.version)}</code></td>`);
|
|
208
208
|
h.push(`<td>${esc(e.inducted.substring(0, 10))}</td>`);
|
|
209
|
-
h.push(`<td><a href="${esc(linkHref(e, params.base))}">${esc(e.link.length.toLocaleString())} bytes</a><br/><span style="font-size: 0.8em; color:
|
|
209
|
+
h.push(`<td><a href="${esc(linkHref(e, params.base))}">${esc(e.link.length.toLocaleString())} bytes</a><br/><span style="font-size: 0.8em; background-color: #eeeeee">sha256: ${esc(e.link.sha256)}</span></td>`);
|
|
210
210
|
h.push('</tr>');
|
|
211
211
|
}
|
|
212
212
|
h.push('</table>');
|
package/tx/tx-html.js
CHANGED
|
@@ -21,7 +21,7 @@ const {ParametersXML} = require("./xml/parameters-xml");
|
|
|
21
21
|
const {OperationOutcomeXML} = require("./xml/operationoutcome-xml");
|
|
22
22
|
const {debugLog} = require("./operation-context");
|
|
23
23
|
const {InvalidError} = require("./library/errors");
|
|
24
|
-
const {VALID_FILTER_OPS} = require("./library/renderer");
|
|
24
|
+
const {VALID_FILTER_OPS, MAX_RENDERED_INCLUDE_CODES} = require("./library/renderer");
|
|
25
25
|
const txOpenApi = require("./openapi");
|
|
26
26
|
|
|
27
27
|
const txHtmlLog = Logger.getInstance().child({ module: 'tx-html' });
|
|
@@ -61,6 +61,12 @@ const ELEMENT_OPTIONS = ['id', 'url', 'version', 'name', 'title', 'status', 'dat
|
|
|
61
61
|
// something, not about terminology, so it does not belong in the FHIR tools namespace.
|
|
62
62
|
const SNOMED_URI = 'http://snomed.info/sct';
|
|
63
63
|
|
|
64
|
+
// A value set page lists the first MAX_RENDERED_INCLUDE_CODES codes of each include;
|
|
65
|
+
// ?_showAll=true lists them all. Underscored, as the other parameters that control how a
|
|
66
|
+
// result is presented are (_format, _summary, _elements), which also keeps it apart from
|
|
67
|
+
// the $expand parameters: the page's query string is passed to the expansion it shows.
|
|
68
|
+
const SHOW_ALL_PARAM = '_showAll';
|
|
69
|
+
|
|
64
70
|
const TRANSIENT_VALUESET = 'http://healthintersections.com.au/fhirsmith/StructureDefinition/valueset-transient';
|
|
65
71
|
|
|
66
72
|
const SEVERITY_ALERT_CLASS = {
|
|
@@ -223,8 +229,8 @@ const YAML_STYLE = '<style>' +
|
|
|
223
229
|
'font-family: Menlo, Consolas, monospace; font-size: 90%; line-height: 1.45; }' +
|
|
224
230
|
'.yaml-source .y-k { color: #0b5394; font-weight: bold; }' +
|
|
225
231
|
'.yaml-source .y-v { color: #444; }' +
|
|
226
|
-
'.yaml-source .y-p, .yaml-source .y-d { color: #
|
|
227
|
-
'.yaml-source .y-c { color: #
|
|
232
|
+
'.yaml-source .y-p, .yaml-source .y-d { color: #444; }' +
|
|
233
|
+
'.yaml-source .y-c { color: #444; font-style: italic; }' +
|
|
228
234
|
'</style>';
|
|
229
235
|
|
|
230
236
|
/**
|
|
@@ -428,7 +434,7 @@ class TxHtmlRenderer {
|
|
|
428
434
|
html += '</h3>';
|
|
429
435
|
|
|
430
436
|
// List Packages
|
|
431
|
-
html += '<
|
|
437
|
+
html += '<h4>FHIR Packages</h4>';
|
|
432
438
|
if (provider.packageSources && provider.packageSources.length > 0) {
|
|
433
439
|
const sorted = [...provider.packageSources].sort();
|
|
434
440
|
html += '<ul>';
|
|
@@ -441,7 +447,7 @@ class TxHtmlRenderer {
|
|
|
441
447
|
}
|
|
442
448
|
|
|
443
449
|
// List Packages
|
|
444
|
-
html += '<
|
|
450
|
+
html += '<h4>External Sources</h4>';
|
|
445
451
|
if (provider.externalSources && provider.externalSources.length > 0) {
|
|
446
452
|
const sorted = [...provider.externalSources].sort();
|
|
447
453
|
html += '<ul>';
|
|
@@ -462,7 +468,7 @@ class TxHtmlRenderer {
|
|
|
462
468
|
html += '<p><em>No External Sources Configured</em></p>';
|
|
463
469
|
}
|
|
464
470
|
|
|
465
|
-
html += '<
|
|
471
|
+
html += '<h5 class="mt-4">Special CodeSystems</h5>';
|
|
466
472
|
html += '<table class="grid">';
|
|
467
473
|
html += '<thead><tr><th>Name</th><th>URI</th><th>Version</th><th>Use Count</th></tr></thead>';
|
|
468
474
|
html += '<tbody>';
|
|
@@ -592,6 +598,7 @@ class TxHtmlRenderer {
|
|
|
592
598
|
case 'CodeSystem':
|
|
593
599
|
return await this.renderCodeSystem(json, inBundle, _fmt, op, req.sourcePackage);
|
|
594
600
|
case 'ValueSet': {
|
|
601
|
+
this.limitIncludeCodes(json, req, inBundle);
|
|
595
602
|
let exp = undefined;
|
|
596
603
|
if (!inBundle && !op && (!_fmt || _fmt == 'html')) {
|
|
597
604
|
try {
|
|
@@ -626,6 +633,35 @@ class TxHtmlRenderer {
|
|
|
626
633
|
}
|
|
627
634
|
}
|
|
628
635
|
|
|
636
|
+
/**
|
|
637
|
+
* How many codes of each include the value set's page lists, and where its "Show all"
|
|
638
|
+
* link goes: this page with ?_showAll=true, or, for a value set shown in a bundle, the
|
|
639
|
+
* value set's own page. A page that came from a POST has no URL to ask again with, so
|
|
640
|
+
* it says how many codes there are but has no link.
|
|
641
|
+
*
|
|
642
|
+
* @param {Object} json the value set
|
|
643
|
+
* @param {express.Request|null} req
|
|
644
|
+
* @param {boolean} inBundle
|
|
645
|
+
*/
|
|
646
|
+
limitIncludeCodes(json, req, inBundle) {
|
|
647
|
+
if (!inBundle && req?.query?.[SHOW_ALL_PARAM] === 'true') {
|
|
648
|
+
this.renderer.includeCodeLimit = null;
|
|
649
|
+
this.renderer.showAllCodesHref = null;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
this.renderer.includeCodeLimit = MAX_RENDERED_INCLUDE_CODES;
|
|
653
|
+
if (inBundle) {
|
|
654
|
+
this.renderer.showAllCodesHref = json.id
|
|
655
|
+
? `${this.path}/ValueSet/${encodeURIComponent(json.id)}?_format=html&${SHOW_ALL_PARAM}=true` : null;
|
|
656
|
+
} else if (req?.method === 'GET' && req.originalUrl) {
|
|
657
|
+
const url = new URL(req.originalUrl, 'http://localhost');
|
|
658
|
+
url.searchParams.set(SHOW_ALL_PARAM, 'true');
|
|
659
|
+
this.renderer.showAllCodesHref = url.pathname + url.search;
|
|
660
|
+
} else {
|
|
661
|
+
this.renderer.showAllCodesHref = null;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
629
665
|
/**
|
|
630
666
|
* Render Parameters resource
|
|
631
667
|
*/
|
|
@@ -1686,7 +1722,7 @@ class TxHtmlRenderer {
|
|
|
1686
1722
|
|
|
1687
1723
|
if (entry.resource) {
|
|
1688
1724
|
const resource = entry.resource;
|
|
1689
|
-
html += `<
|
|
1725
|
+
html += `<h3>${escape(resource.resourceType)}/${escape(resource.id || 'unknown')}</h3>`;
|
|
1690
1726
|
|
|
1691
1727
|
if (entry.fullUrl) {
|
|
1692
1728
|
html += `<p><small><a href="${escape(entry.fullUrl)}">${escape(entry.fullUrl)}</a></small></p>`;
|
|
@@ -1746,7 +1782,7 @@ class TxHtmlRenderer {
|
|
|
1746
1782
|
|
|
1747
1783
|
// Links
|
|
1748
1784
|
if (json.link && json.link.length > 0) {
|
|
1749
|
-
html += '<
|
|
1785
|
+
html += '<h3>Links</h3>';
|
|
1750
1786
|
html += '<ul>';
|
|
1751
1787
|
for (const link of json.link) {
|
|
1752
1788
|
html += `<li><strong>${escape(link.relation)}:</strong> <a href="${escape(link.url)}">${escape(link.url)}</a></li>`;
|
package/tx/tx.fhir.org.yml
CHANGED
|
@@ -20,19 +20,19 @@ sources:
|
|
|
20
20
|
- internal:hgvs
|
|
21
21
|
- ucum:tx/data/ucum-essence.xml
|
|
22
22
|
- loinc:loinc-2.77-a.db
|
|
23
|
-
- loinc!:Loinc_2.
|
|
24
|
-
- rxnorm:
|
|
23
|
+
- loinc!:Loinc_2.83c.db
|
|
24
|
+
- rxnorm:rxnorm_20261005.cache
|
|
25
25
|
- ndc:ndc-20211101.db
|
|
26
26
|
- unii:unii_20240622.db
|
|
27
|
-
- snomed:
|
|
28
|
-
- snomed!:
|
|
29
|
-
- snomed:
|
|
30
|
-
- snomed:sct_nl_20240930.cache
|
|
27
|
+
- snomed:sct_intl_20250201.cache
|
|
28
|
+
- snomed!:Snomed_Intl_20260701-a.cache
|
|
29
|
+
- snomed:snomed-nl-20260930-a.cache
|
|
31
30
|
- snomed:sct_uk_20230412.cache
|
|
32
31
|
- snomed:sct_us_20230301.cache
|
|
33
32
|
- snomed:sct_us_20240301.cache
|
|
34
|
-
- snomed:sct_us_20250901.cache
|
|
35
|
-
- snomed:
|
|
33
|
+
- snomed:sct_us_20250901-c.cache
|
|
34
|
+
- snomed:Snomed_loinc_20261021-a.cache
|
|
35
|
+
- snomed:sct_test_20250909i.cache
|
|
36
36
|
- cpt:CodeSystem-cpt.db|cpt-2023-fragment-0.1.db
|
|
37
37
|
- omop:omop_v20250227.db
|
|
38
38
|
- npm:hl7.terminology=tho
|