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.
- package/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"npmprojector": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"basePath": "/fhir",
|
|
5
|
+
"npmPath": "./data/fhir-package",
|
|
6
|
+
"fhirVersion": "r4",
|
|
7
|
+
"resourceTypes": null,
|
|
8
|
+
"resourceFolders": ["data/medications", "data/patients"],
|
|
9
|
+
"searchParametersFolder": "data/search",
|
|
10
|
+
"searchParametersPath": null,
|
|
11
|
+
"debounceMs": 500
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FHIR Indexer
|
|
3
|
+
// Builds in-memory search indexes using FHIRPath expressions
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
const fhirpath = require('fhirpath');
|
|
7
|
+
|
|
8
|
+
class FHIRIndexer {
|
|
9
|
+
constructor(fhirModel = null) {
|
|
10
|
+
this.resources = new Map(); // resourceType -> id -> resource
|
|
11
|
+
this.indexes = new Map(); // resourceType -> paramName -> value -> Set<id>
|
|
12
|
+
this.searchParams = new Map(); // resourceType -> paramName -> SearchParameter
|
|
13
|
+
this.fhirModel = fhirModel; // FHIRPath model for type resolution
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load resources and search parameters, build all indexes
|
|
18
|
+
*/
|
|
19
|
+
build(resources, searchParameters) {
|
|
20
|
+
this.resources.clear();
|
|
21
|
+
this.indexes.clear();
|
|
22
|
+
this.searchParams.clear();
|
|
23
|
+
|
|
24
|
+
// Index search parameters by resource type
|
|
25
|
+
for (const sp of searchParameters) {
|
|
26
|
+
if (!sp.expression) continue;
|
|
27
|
+
|
|
28
|
+
const types = sp.base || [];
|
|
29
|
+
for (const type of types) {
|
|
30
|
+
if (!this.searchParams.has(type)) {
|
|
31
|
+
this.searchParams.set(type, new Map());
|
|
32
|
+
}
|
|
33
|
+
this.searchParams.get(type).set(sp.code, sp);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Store resources and build indexes
|
|
38
|
+
for (const resource of resources) {
|
|
39
|
+
const type = resource.resourceType;
|
|
40
|
+
if (!type) continue;
|
|
41
|
+
|
|
42
|
+
// Store the resource
|
|
43
|
+
if (!this.resources.has(type)) {
|
|
44
|
+
this.resources.set(type, new Map());
|
|
45
|
+
}
|
|
46
|
+
this.resources.get(type).set(resource.id, resource);
|
|
47
|
+
|
|
48
|
+
// Build indexes for this resource
|
|
49
|
+
this.indexResource(resource);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this.getStats();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Index a single resource against all applicable search parameters
|
|
57
|
+
*/
|
|
58
|
+
indexResource(resource) {
|
|
59
|
+
const type = resource.resourceType;
|
|
60
|
+
const params = this.searchParams.get(type);
|
|
61
|
+
if (!params) return;
|
|
62
|
+
|
|
63
|
+
if (!this.indexes.has(type)) {
|
|
64
|
+
this.indexes.set(type, new Map());
|
|
65
|
+
}
|
|
66
|
+
const typeIndexes = this.indexes.get(type);
|
|
67
|
+
|
|
68
|
+
for (const [paramName, searchParam] of params) {
|
|
69
|
+
if (!typeIndexes.has(paramName)) {
|
|
70
|
+
typeIndexes.set(paramName, new Map());
|
|
71
|
+
}
|
|
72
|
+
const paramIndex = typeIndexes.get(paramName);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const values = fhirpath.evaluate(resource, searchParam.expression, null, this.fhirModel);
|
|
76
|
+
for (const value of values) {
|
|
77
|
+
const normalizedValues = this.normalizeValue(value, searchParam.type);
|
|
78
|
+
for (const normalized of normalizedValues) {
|
|
79
|
+
if (normalized === null || normalized === undefined) continue;
|
|
80
|
+
|
|
81
|
+
if (!paramIndex.has(normalized)) {
|
|
82
|
+
paramIndex.set(normalized, new Set());
|
|
83
|
+
}
|
|
84
|
+
paramIndex.get(normalized).add(resource.id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.log(err);
|
|
89
|
+
// FHIRPath evaluation failed - skip this param for this resource
|
|
90
|
+
// Logged at debug level to avoid noise
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalize a value based on search parameter type
|
|
97
|
+
* Returns an array of normalized values (some types produce multiple index entries)
|
|
98
|
+
*/
|
|
99
|
+
normalizeValue(value, paramType) {
|
|
100
|
+
if (value === null || value === undefined) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
switch (paramType) {
|
|
105
|
+
case 'string':
|
|
106
|
+
return [String(value).toLowerCase()];
|
|
107
|
+
|
|
108
|
+
case 'token':
|
|
109
|
+
// Handle CodeableConcept, Coding, Identifier, code, etc.
|
|
110
|
+
if (typeof value === 'string') {
|
|
111
|
+
return [value.toLowerCase()];
|
|
112
|
+
}
|
|
113
|
+
if (value.coding) {
|
|
114
|
+
// CodeableConcept
|
|
115
|
+
return value.coding.map(c => this.tokenKey(c.system, c.code)).filter(Boolean);
|
|
116
|
+
}
|
|
117
|
+
if (value.system !== undefined || value.code !== undefined || value.value !== undefined) {
|
|
118
|
+
// Coding or Identifier
|
|
119
|
+
return [this.tokenKey(value.system, value.code || value.value)].filter(Boolean);
|
|
120
|
+
}
|
|
121
|
+
return [String(value).toLowerCase()];
|
|
122
|
+
|
|
123
|
+
case 'reference':
|
|
124
|
+
// Handle Reference type
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
return [value];
|
|
127
|
+
}
|
|
128
|
+
if (value.reference) {
|
|
129
|
+
return [value.reference];
|
|
130
|
+
}
|
|
131
|
+
return [];
|
|
132
|
+
|
|
133
|
+
case 'date':
|
|
134
|
+
// Store as ISO string prefix for simple matching
|
|
135
|
+
if (typeof value === 'string') {
|
|
136
|
+
return [value];
|
|
137
|
+
}
|
|
138
|
+
if (value instanceof Date) {
|
|
139
|
+
return [value.toISOString()];
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
|
|
143
|
+
case 'quantity':
|
|
144
|
+
// Index the numeric value (simplified - ignores units)
|
|
145
|
+
if (typeof value === 'number') {
|
|
146
|
+
return [value];
|
|
147
|
+
}
|
|
148
|
+
if (value.value !== undefined) {
|
|
149
|
+
return [value.value];
|
|
150
|
+
}
|
|
151
|
+
return [];
|
|
152
|
+
|
|
153
|
+
case 'number':
|
|
154
|
+
if (typeof value === 'number') {
|
|
155
|
+
return [value];
|
|
156
|
+
}
|
|
157
|
+
return [parseFloat(value)].filter(v => !isNaN(v));
|
|
158
|
+
|
|
159
|
+
case 'uri':
|
|
160
|
+
return [String(value)];
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
return [String(value).toLowerCase()];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create a token key from system and code
|
|
169
|
+
*/
|
|
170
|
+
tokenKey(system, code) {
|
|
171
|
+
if (!code && !system) return null;
|
|
172
|
+
if (!system) return `|${code}`.toLowerCase();
|
|
173
|
+
if (!code) return `${system}|`.toLowerCase();
|
|
174
|
+
return `${system}|${code}`.toLowerCase();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Read a single resource by type and id
|
|
179
|
+
*/
|
|
180
|
+
read(resourceType, id) {
|
|
181
|
+
const typeResources = this.resources.get(resourceType);
|
|
182
|
+
if (!typeResources) return null;
|
|
183
|
+
return typeResources.get(id) || null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Search for resources
|
|
188
|
+
*/
|
|
189
|
+
search(resourceType, queryParams) {
|
|
190
|
+
const typeResources = this.resources.get(resourceType);
|
|
191
|
+
if (!typeResources) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const typeIndexes = this.indexes.get(resourceType);
|
|
196
|
+
const typeSearchParams = this.searchParams.get(resourceType);
|
|
197
|
+
|
|
198
|
+
// Start with all resource IDs
|
|
199
|
+
let matchingIds = null;
|
|
200
|
+
|
|
201
|
+
for (const [paramName, paramValue] of Object.entries(queryParams)) {
|
|
202
|
+
// Skip special parameters
|
|
203
|
+
if (paramName.startsWith('_')) continue;
|
|
204
|
+
|
|
205
|
+
if (!typeSearchParams?.has(paramName) || !typeIndexes?.has(paramName)) {
|
|
206
|
+
// Unknown search parameter - skip or could throw
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const searchParam = typeSearchParams.get(paramName);
|
|
211
|
+
const paramIndex = typeIndexes.get(paramName);
|
|
212
|
+
|
|
213
|
+
// Find matching IDs for this parameter
|
|
214
|
+
const paramMatchingIds = this.searchParam(paramValue, searchParam, paramIndex);
|
|
215
|
+
|
|
216
|
+
// Intersect with running result
|
|
217
|
+
if (matchingIds === null) {
|
|
218
|
+
matchingIds = paramMatchingIds;
|
|
219
|
+
} else {
|
|
220
|
+
matchingIds = new Set([...matchingIds].filter(id => paramMatchingIds.has(id)));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Early exit if no matches
|
|
224
|
+
if (matchingIds.size === 0) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// If no search params provided, return all
|
|
230
|
+
if (matchingIds === null) {
|
|
231
|
+
return Array.from(typeResources.values());
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Return matching resources
|
|
235
|
+
return Array.from(matchingIds).map(id => typeResources.get(id)).filter(Boolean);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Search a single parameter
|
|
240
|
+
*/
|
|
241
|
+
searchParam(queryValue, searchParam, paramIndex) {
|
|
242
|
+
const matchingIds = new Set();
|
|
243
|
+
const paramType = searchParam.type;
|
|
244
|
+
|
|
245
|
+
// Handle OR (comma-separated values)
|
|
246
|
+
const values = String(queryValue).split(',');
|
|
247
|
+
|
|
248
|
+
for (const value of values) {
|
|
249
|
+
const trimmedValue = value.trim();
|
|
250
|
+
|
|
251
|
+
switch (paramType) {
|
|
252
|
+
case 'string':
|
|
253
|
+
// String search: case-insensitive starts-with by default
|
|
254
|
+
const lowerValue = trimmedValue.toLowerCase();
|
|
255
|
+
for (const [indexedValue, ids] of paramIndex) {
|
|
256
|
+
if (indexedValue.startsWith(lowerValue)) {
|
|
257
|
+
ids.forEach(id => matchingIds.add(id));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case 'token':
|
|
263
|
+
// Token search: exact match on system|code, |code, or code
|
|
264
|
+
const tokenMatches = this.matchToken(trimmedValue, paramIndex);
|
|
265
|
+
tokenMatches.forEach(id => matchingIds.add(id));
|
|
266
|
+
break;
|
|
267
|
+
|
|
268
|
+
case 'reference':
|
|
269
|
+
// Reference search: exact match
|
|
270
|
+
const refIds = paramIndex.get(trimmedValue);
|
|
271
|
+
if (refIds) {
|
|
272
|
+
refIds.forEach(id => matchingIds.add(id));
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
|
|
276
|
+
case 'date':
|
|
277
|
+
// Simple prefix matching for dates (no range support as specified)
|
|
278
|
+
for (const [indexedValue, ids] of paramIndex) {
|
|
279
|
+
if (indexedValue.startsWith(trimmedValue)) {
|
|
280
|
+
ids.forEach(id => matchingIds.add(id));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case 'number':
|
|
286
|
+
case 'quantity':
|
|
287
|
+
// Numeric equality
|
|
288
|
+
const numValue = parseFloat(trimmedValue);
|
|
289
|
+
if (!isNaN(numValue)) {
|
|
290
|
+
const ids = paramIndex.get(numValue);
|
|
291
|
+
if (ids) {
|
|
292
|
+
ids.forEach(id => matchingIds.add(id));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case 'uri':
|
|
298
|
+
// Exact match for URI
|
|
299
|
+
const uriIds = paramIndex.get(trimmedValue);
|
|
300
|
+
if (uriIds) {
|
|
301
|
+
uriIds.forEach(id => matchingIds.add(id));
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
default:
|
|
306
|
+
// Default: exact match (case-insensitive)
|
|
307
|
+
const defaultIds = paramIndex.get(trimmedValue.toLowerCase());
|
|
308
|
+
if (defaultIds) {
|
|
309
|
+
defaultIds.forEach(id => matchingIds.add(id));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return matchingIds;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Match a token query against the index
|
|
319
|
+
* Supports: system|code, |code, code, system|
|
|
320
|
+
*/
|
|
321
|
+
matchToken(query, paramIndex) {
|
|
322
|
+
const matchingIds = new Set();
|
|
323
|
+
const lowerQuery = query.toLowerCase();
|
|
324
|
+
|
|
325
|
+
if (lowerQuery.includes('|')) {
|
|
326
|
+
// Exact system|code match or system| or |code
|
|
327
|
+
const ids = paramIndex.get(lowerQuery);
|
|
328
|
+
if (ids) {
|
|
329
|
+
ids.forEach(id => matchingIds.add(id));
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
// Code-only search: match any system
|
|
333
|
+
for (const [indexedValue, ids] of paramIndex) {
|
|
334
|
+
const parts = indexedValue.split('|');
|
|
335
|
+
const indexedCode = parts[parts.length - 1];
|
|
336
|
+
if (indexedCode === lowerQuery) {
|
|
337
|
+
ids.forEach(id => matchingIds.add(id));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return matchingIds;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get all supported resource types
|
|
347
|
+
*/
|
|
348
|
+
getResourceTypes() {
|
|
349
|
+
return Array.from(this.resources.keys());
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get search parameters for a resource type
|
|
354
|
+
*/
|
|
355
|
+
getSearchParams(resourceType) {
|
|
356
|
+
const params = this.searchParams.get(resourceType);
|
|
357
|
+
if (!params) return [];
|
|
358
|
+
return Array.from(params.values());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get statistics about the indexed data
|
|
363
|
+
*/
|
|
364
|
+
getStats() {
|
|
365
|
+
const stats = {
|
|
366
|
+
resourceTypes: {},
|
|
367
|
+
totalResources: 0,
|
|
368
|
+
totalIndexEntries: 0
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
for (const [type, resources] of this.resources) {
|
|
372
|
+
const typeIndexes = this.indexes.get(type);
|
|
373
|
+
let indexEntries = 0;
|
|
374
|
+
|
|
375
|
+
if (typeIndexes) {
|
|
376
|
+
for (const [, paramIndex] of typeIndexes) {
|
|
377
|
+
indexEntries += paramIndex.size;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
stats.resourceTypes[type] = {
|
|
382
|
+
count: resources.size,
|
|
383
|
+
indexedParams: typeIndexes?.size || 0,
|
|
384
|
+
indexEntries
|
|
385
|
+
};
|
|
386
|
+
stats.totalResources += resources.size;
|
|
387
|
+
stats.totalIndexEntries += indexEntries;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return stats;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = FHIRIndexer;
|