fhirsmith 0.4.2 → 0.5.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 +12 -0
- package/README.md +1 -1
- package/library/cron-utilities.js +136 -0
- package/library/html-server.js +13 -29
- package/library/html.js +3 -8
- package/library/languages.js +160 -37
- package/library/package-manager.js +48 -1
- package/library/utilities.js +100 -19
- package/package.json +2 -2
- package/packages/package-crawler.js +6 -1
- package/packages/packages.js +38 -54
- package/publisher/publisher.js +19 -27
- package/registry/api.js +11 -10
- package/registry/crawler.js +31 -29
- package/registry/model.js +5 -26
- package/registry/registry.js +32 -41
- package/server.js +53 -5
- package/shl/shl.js +0 -18
- package/static/assets/js/statuspage.js +1 -9
- package/stats.js +39 -1
- package/token/token.js +14 -9
- package/translations/Messages.properties +2 -1
- package/tx/README.md +17 -6
- package/tx/cs/cs-api.js +19 -1
- package/tx/cs/cs-base.js +77 -0
- package/tx/cs/cs-country.js +46 -0
- package/tx/cs/cs-cpt.js +9 -5
- package/tx/cs/cs-cs.js +27 -13
- package/tx/cs/cs-lang.js +60 -22
- package/tx/cs/cs-loinc.js +69 -98
- package/tx/cs/cs-mimetypes.js +4 -0
- package/tx/cs/cs-ndc.js +6 -0
- package/tx/cs/cs-omop.js +16 -15
- package/tx/cs/cs-rxnorm.js +23 -1
- package/tx/cs/cs-snomed.js +283 -40
- package/tx/cs/cs-ucum.js +90 -70
- package/tx/importers/import-sct.module.js +371 -35
- package/tx/importers/readme.md +117 -7
- package/tx/library/bundle.js +5 -0
- package/tx/library/capabilitystatement.js +3 -142
- package/tx/library/codesystem.js +19 -173
- package/tx/library/conceptmap.js +4 -218
- package/tx/library/designations.js +14 -1
- package/tx/library/extensions.js +7 -0
- package/tx/library/namingsystem.js +3 -89
- package/tx/library/operation-outcome.js +8 -3
- package/tx/library/parameters.js +3 -2
- package/tx/library/renderer.js +10 -6
- package/tx/library/terminologycapabilities.js +3 -243
- package/tx/library/valueset.js +3 -235
- package/tx/library.js +100 -13
- package/tx/operation-context.js +23 -4
- package/tx/params.js +35 -38
- package/tx/provider.js +6 -5
- package/tx/sct/expressions.js +12 -3
- package/tx/tx-html.js +80 -89
- package/tx/tx.fhir.org.yml +6 -5
- package/tx/tx.js +163 -13
- package/tx/vs/vs-database.js +56 -39
- package/tx/vs/vs-package.js +21 -2
- package/tx/vs/vs-vsac.js +175 -39
- package/tx/workers/batch-validate.js +2 -0
- package/tx/workers/batch.js +2 -0
- package/tx/workers/expand.js +132 -112
- package/tx/workers/lookup.js +33 -14
- package/tx/workers/metadata.js +2 -2
- package/tx/workers/read.js +3 -2
- package/tx/workers/related.js +574 -0
- package/tx/workers/search.js +46 -9
- package/tx/workers/subsumes.js +13 -3
- package/tx/workers/translate.js +7 -3
- package/tx/workers/validate.js +258 -285
- package/tx/workers/worker.js +43 -39
- package/tx/xml/bundle-xml.js +237 -0
- package/tx/xml/xml-base.js +215 -64
- package/tx/xversion/xv-bundle.js +71 -0
- package/tx/xversion/xv-capabiliityStatement.js +137 -0
- package/tx/xversion/xv-codesystem.js +169 -0
- package/tx/xversion/xv-conceptmap.js +224 -0
- package/tx/xversion/xv-namingsystem.js +88 -0
- package/tx/xversion/xv-operationoutcome.js +27 -0
- package/tx/xversion/xv-parameters.js +87 -0
- package/tx/xversion/xv-resource.js +45 -0
- package/tx/xversion/xv-terminologyCapabilities.js +214 -0
- package/tx/xversion/xv-valueset.js +234 -0
- package/utilities/dev-proxy-server.js +126 -0
- package/utilities/explode-results.js +58 -0
- package/utilities/split-by-system.js +198 -0
- package/utilities/vsac-cs-fetcher.js +0 -0
- package/{windows-install.js → utilities/windows-install.js} +2 -0
- package/vcl/vcl.js +0 -18
- package/xig/xig.js +108 -99
package/tx/library/valueset.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const {CanonicalResource} = require("./canonical-resource");
|
|
2
|
-
const {
|
|
3
|
-
const {VersionUtilities} = require("../../library/version-utilities");
|
|
2
|
+
const {valueSetToR5, valueSetFromR5} = require("../xversion/xv-valueset");
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Represents a FHIR ValueSet resource with version conversion support
|
|
@@ -23,7 +22,7 @@ class ValueSet extends CanonicalResource {
|
|
|
23
22
|
constructor(jsonObj, fhirVersion = 'R5') {
|
|
24
23
|
super(jsonObj, fhirVersion);
|
|
25
24
|
// Convert to R5 format internally (modifies input for performance)
|
|
26
|
-
this.jsonObj =
|
|
25
|
+
this.jsonObj = valueSetToR5(jsonObj, fhirVersion);
|
|
27
26
|
this.validate();
|
|
28
27
|
this.buildMaps();
|
|
29
28
|
}
|
|
@@ -51,241 +50,10 @@ class ValueSet extends CanonicalResource {
|
|
|
51
50
|
* @returns {string} JSON string
|
|
52
51
|
*/
|
|
53
52
|
toJSONString(version = 'R5') {
|
|
54
|
-
const outputObj =
|
|
53
|
+
const outputObj = valueSetFromR5(this.jsonObj, version);
|
|
55
54
|
return JSON.stringify(outputObj);
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
/**
|
|
59
|
-
* Converts input ValueSet to R5 format (modifies input object for performance)
|
|
60
|
-
* @param {Object} jsonObj - The input ValueSet object
|
|
61
|
-
* @param {string} version - Source FHIR version
|
|
62
|
-
* @returns {Object} The same object, potentially modified to R5 format
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
_convertToR5(jsonObj, version) {
|
|
66
|
-
if (version === 'R5') {
|
|
67
|
-
return jsonObj; // Already R5, no conversion needed
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (version === 'R3') {
|
|
71
|
-
// R3 to R5: Remove extensible field (we ignore it completely)
|
|
72
|
-
if (jsonObj.extensible !== undefined) {
|
|
73
|
-
delete jsonObj.extensible;
|
|
74
|
-
}
|
|
75
|
-
return jsonObj;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (version === 'R4') {
|
|
79
|
-
// R4 to R5: No structural conversion needed
|
|
80
|
-
// R5 is backward compatible for the structural elements we care about
|
|
81
|
-
return jsonObj;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
throw new Error(`Unsupported FHIR version: ${version}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Converts R5 ValueSet to target version format (clones object first)
|
|
89
|
-
* @param {Object} r5Obj - The R5 format ValueSet object
|
|
90
|
-
* @param {string} targetVersion - Target FHIR version
|
|
91
|
-
* @returns {Object} New object in target version format
|
|
92
|
-
* @private
|
|
93
|
-
*/
|
|
94
|
-
convertFromR5(r5Obj, targetVersion) {
|
|
95
|
-
if (VersionUtilities.isR5Ver(targetVersion)) {
|
|
96
|
-
return r5Obj; // No conversion needed
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Clone the object to avoid modifying the original
|
|
100
|
-
const cloned = JSON.parse(JSON.stringify(r5Obj));
|
|
101
|
-
|
|
102
|
-
if (VersionUtilities.isR4Ver(targetVersion)) {
|
|
103
|
-
return this._convertR5ToR4(cloned);
|
|
104
|
-
} else if (VersionUtilities.isR3Ver(targetVersion)) {
|
|
105
|
-
return this._convertR5ToR3(cloned);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
throw new Error(`Unsupported target FHIR version: ${targetVersion}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Converts R5 ValueSet to R4 format
|
|
113
|
-
* @param {Object} r5Obj - Cloned R5 ValueSet object
|
|
114
|
-
* @returns {Object} R4 format ValueSet
|
|
115
|
-
* @private
|
|
116
|
-
*/
|
|
117
|
-
_convertR5ToR4(r5Obj) {
|
|
118
|
-
// Remove R5-specific elements that don't exist in R4
|
|
119
|
-
if (r5Obj.versionAlgorithmString) {
|
|
120
|
-
delete r5Obj.versionAlgorithmString;
|
|
121
|
-
}
|
|
122
|
-
if (r5Obj.versionAlgorithmCoding) {
|
|
123
|
-
delete r5Obj.versionAlgorithmCoding;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Filter out R5-only filter operators in compose
|
|
127
|
-
if (r5Obj.compose && r5Obj.compose.include) {
|
|
128
|
-
r5Obj.compose.include = r5Obj.compose.include.map(include => {
|
|
129
|
-
if (include.filter && Array.isArray(include.filter)) {
|
|
130
|
-
include.filter = include.filter.map(filter => {
|
|
131
|
-
if (filter.op && this._isR5OnlyFilterOperator(filter.op)) {
|
|
132
|
-
// Remove R5-only operators
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
return filter;
|
|
136
|
-
}).filter(filter => filter !== null);
|
|
137
|
-
}
|
|
138
|
-
return include;
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (r5Obj.compose && r5Obj.compose.exclude) {
|
|
143
|
-
r5Obj.compose.exclude = r5Obj.compose.exclude.map(exclude => {
|
|
144
|
-
if (exclude.filter && Array.isArray(exclude.filter)) {
|
|
145
|
-
exclude.filter = exclude.filter.map(filter => {
|
|
146
|
-
if (filter.op && this._isR5OnlyFilterOperator(filter.op)) {
|
|
147
|
-
// Remove R5-only operators
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
return filter;
|
|
151
|
-
}).filter(filter => filter !== null);
|
|
152
|
-
}
|
|
153
|
-
return exclude;
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (r5Obj.expansion) {
|
|
158
|
-
let exp = r5Obj.expansion;
|
|
159
|
-
|
|
160
|
-
// Convert ValueSet.expansion.property to extensions
|
|
161
|
-
if (exp.property && exp.property.length > 0) {
|
|
162
|
-
exp.extension = exp.extension || [];
|
|
163
|
-
for (let prop of exp.property) {
|
|
164
|
-
exp.extension.push({
|
|
165
|
-
url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.property",
|
|
166
|
-
extension: [
|
|
167
|
-
{ url: "code", valueCode: prop.code },
|
|
168
|
-
{ url: "uri", valueUri: prop.uri }
|
|
169
|
-
]
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
delete exp.property;
|
|
173
|
-
this.convertContainsPropertyR5ToR4(exp.contains);
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return r5Obj;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Recursive function to convert contains.property
|
|
182
|
-
convertContainsPropertyR5ToR4(containsList) {
|
|
183
|
-
if (!containsList) return;
|
|
184
|
-
|
|
185
|
-
for (let item of containsList) {
|
|
186
|
-
if (item.property && item.property.length > 0) {
|
|
187
|
-
item.extension = item.extension || [];
|
|
188
|
-
for (let prop of item.property) {
|
|
189
|
-
let ext = {
|
|
190
|
-
url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.contains.property",
|
|
191
|
-
extension: [
|
|
192
|
-
{ url: "code", valueCode: prop.code }
|
|
193
|
-
]
|
|
194
|
-
};
|
|
195
|
-
let pn = getValueName(prop);
|
|
196
|
-
let subExt = { url: "value" };
|
|
197
|
-
subExt[pn] = prop[pn];
|
|
198
|
-
ext.extension.push(subExt);
|
|
199
|
-
item.extension.push(ext);
|
|
200
|
-
}
|
|
201
|
-
delete item.property;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Recurse into nested contains
|
|
205
|
-
if (item.contains) {
|
|
206
|
-
this.convertContainsPropertyR5ToR4(item.contains);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Converts R5 ValueSet to R3 format
|
|
213
|
-
* @param {Object} r5Obj - Cloned R5 ValueSet object
|
|
214
|
-
* @returns {Object} R3 format ValueSet
|
|
215
|
-
* @private
|
|
216
|
-
*/
|
|
217
|
-
_convertR5ToR3(r5Obj) {
|
|
218
|
-
// First apply R4 conversions
|
|
219
|
-
const r4Obj = this._convertR5ToR4(r5Obj);
|
|
220
|
-
|
|
221
|
-
// R3 has more limited filter operator support
|
|
222
|
-
if (r4Obj.compose && r4Obj.compose.include) {
|
|
223
|
-
r4Obj.compose.include = r4Obj.compose.include.map(include => {
|
|
224
|
-
if (include.filter && Array.isArray(include.filter)) {
|
|
225
|
-
include.filter = include.filter.map(filter => {
|
|
226
|
-
if (filter.op && !this._isR3CompatibleFilterOperator(filter.op)) {
|
|
227
|
-
// Remove non-R3-compatible operators
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
return filter;
|
|
231
|
-
}).filter(filter => filter !== null);
|
|
232
|
-
}
|
|
233
|
-
return include;
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (r4Obj.compose && r4Obj.compose.exclude) {
|
|
238
|
-
r4Obj.compose.exclude = r4Obj.compose.exclude.map(exclude => {
|
|
239
|
-
if (exclude.filter && Array.isArray(exclude.filter)) {
|
|
240
|
-
exclude.filter = exclude.filter.map(filter => {
|
|
241
|
-
if (filter.op && !this._isR3CompatibleFilterOperator(filter.op)) {
|
|
242
|
-
// Remove non-R3-compatible operators
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
return filter;
|
|
246
|
-
}).filter(filter => filter !== null);
|
|
247
|
-
}
|
|
248
|
-
return exclude;
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return r4Obj;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Checks if a filter operator is R5-only
|
|
257
|
-
* @param {string} operator - Filter operator code
|
|
258
|
-
* @returns {boolean} True if operator is R5-only
|
|
259
|
-
* @private
|
|
260
|
-
*/
|
|
261
|
-
_isR5OnlyFilterOperator(operator) {
|
|
262
|
-
const r5OnlyOperators = [
|
|
263
|
-
'generalizes', // Added in R5
|
|
264
|
-
// Add other R5-only operators as they're identified
|
|
265
|
-
];
|
|
266
|
-
return r5OnlyOperators.includes(operator);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Checks if a filter operator is compatible with R3
|
|
271
|
-
* @param {string} operator - Filter operator code
|
|
272
|
-
* @returns {boolean} True if operator is R3-compatible
|
|
273
|
-
* @private
|
|
274
|
-
*/
|
|
275
|
-
_isR3CompatibleFilterOperator(operator) {
|
|
276
|
-
const r3CompatibleOperators = [
|
|
277
|
-
'=', // Equal
|
|
278
|
-
'is-a', // Is-A relationship
|
|
279
|
-
'descendent-of', // Descendant of (note: R3 spelling)
|
|
280
|
-
'is-not-a', // Is-Not-A relationship
|
|
281
|
-
'regex', // Regular expression
|
|
282
|
-
'in', // In set
|
|
283
|
-
'not-in', // Not in set
|
|
284
|
-
'exists', // Property exists
|
|
285
|
-
];
|
|
286
|
-
return r3CompatibleOperators.includes(operator);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
57
|
/**
|
|
290
58
|
* Gets the FHIR version this ValueSet was loaded from
|
|
291
59
|
* @returns {string} FHIR version ('R3', 'R4', or 'R5')
|
package/tx/library.js
CHANGED
|
@@ -30,6 +30,7 @@ const {ListCodeSystemProvider} = require("./cs/cs-provider-list");
|
|
|
30
30
|
const { Provider } = require("./provider");
|
|
31
31
|
const {I18nSupport} = require("../library/i18nsupport");
|
|
32
32
|
const folders = require('../library/folder-setup');
|
|
33
|
+
const {VSACValueSetProvider} = require("./vs/vs-vsac");
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* This class holds all the loaded content ready for processing
|
|
@@ -68,6 +69,7 @@ class Library {
|
|
|
68
69
|
startMemory = process.memoryUsage();
|
|
69
70
|
lastTime = null;
|
|
70
71
|
totalDownloaded = 0;
|
|
72
|
+
vsacCfg = undefined;
|
|
71
73
|
|
|
72
74
|
registerProvider(source, factory, isDefault = false) {
|
|
73
75
|
this.#logSystem(factory.system(), factory.version(), source);
|
|
@@ -82,9 +84,12 @@ class Library {
|
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
constructor(configFile, log) {
|
|
87
|
+
constructor(configFile, vsacCfg, log, stats) {
|
|
86
88
|
this.configFile = configFile;
|
|
89
|
+
this.vsacCfg = vsacCfg;
|
|
87
90
|
this.log = log;
|
|
91
|
+
this.stats = stats;
|
|
92
|
+
|
|
88
93
|
// Only synchronous initialization here
|
|
89
94
|
this.codeSystemFactories = new Map();
|
|
90
95
|
this.codeSystemProviders = [];
|
|
@@ -140,7 +145,7 @@ class Library {
|
|
|
140
145
|
|
|
141
146
|
async load() {
|
|
142
147
|
this.startTime = Date.now();
|
|
143
|
-
this.languageDefinitions = await LanguageDefinitions.
|
|
148
|
+
this.languageDefinitions = await LanguageDefinitions.fromFiles(path.join(__dirname, '../tx/data'));
|
|
144
149
|
this.i18n = new I18nSupport(path.join(__dirname, '../translations'), this.languageDefinitions);
|
|
145
150
|
await this.i18n.load();
|
|
146
151
|
|
|
@@ -243,9 +248,21 @@ class Library {
|
|
|
243
248
|
break;
|
|
244
249
|
|
|
245
250
|
case 'npm':
|
|
246
|
-
await this.loadNpm(packageManager, details, isDefault, mode);
|
|
251
|
+
await this.loadNpm(packageManager, details, isDefault, mode, false);
|
|
252
|
+
break;
|
|
253
|
+
|
|
254
|
+
case 'npm/cs':
|
|
255
|
+
await this.loadNpm(packageManager, details, isDefault, mode, true);
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
case 'url':
|
|
259
|
+
await this.loadUrl(packageManager, details, isDefault, mode, false);
|
|
247
260
|
break;
|
|
248
261
|
|
|
262
|
+
case 'url/cs':
|
|
263
|
+
await this.loadUrl(packageManager, details, isDefault, mode, true);
|
|
264
|
+
break;
|
|
265
|
+
|
|
249
266
|
default:
|
|
250
267
|
throw new Error(`Unknown source type: ${type}`);
|
|
251
268
|
}
|
|
@@ -301,6 +318,21 @@ class Library {
|
|
|
301
318
|
this.registerProvider('internal', hgvs);
|
|
302
319
|
break;
|
|
303
320
|
}
|
|
321
|
+
case "vsac" : {
|
|
322
|
+
if (!this.vsacCfg || !this.vsacCfg.apiKey) {
|
|
323
|
+
throw new Error("Unable to load VSAC provider unless vsacCfg is provided in the configuration");
|
|
324
|
+
}
|
|
325
|
+
let vsac = new VSACValueSetProvider(this.vsacCfg, this.stats);
|
|
326
|
+
vsac.initialize();
|
|
327
|
+
this.valueSetProviders.push(vsac);
|
|
328
|
+
//const mem = process.memoryUsage();
|
|
329
|
+
let time = Math.floor(Date.now() - this.lastTime).toString().padStart(5)+" ";
|
|
330
|
+
let system = "vsac".padEnd(50);
|
|
331
|
+
let version = "n/a".padEnd(62);
|
|
332
|
+
this.log.info(`${time}${system}${version}${vsac.baseUrl}`);
|
|
333
|
+
this.lastTime = Date.now();
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
304
336
|
default:
|
|
305
337
|
throw new Error("Unknown Internal Provider "+details);
|
|
306
338
|
}
|
|
@@ -392,7 +424,7 @@ class Library {
|
|
|
392
424
|
this.registerProvider(omopFN, omop, isDefault);
|
|
393
425
|
}
|
|
394
426
|
|
|
395
|
-
async loadNpm(packageManager, details, isDefault, mode) {
|
|
427
|
+
async loadNpm(packageManager, details, isDefault, mode, csOnly) {
|
|
396
428
|
// Parse packageId and version from details (e.g., "hl7.terminology.r4#6.0.2")
|
|
397
429
|
let packageId = details;
|
|
398
430
|
let version = null;
|
|
@@ -422,14 +454,52 @@ class Library {
|
|
|
422
454
|
csc++;
|
|
423
455
|
}
|
|
424
456
|
this.codeSystemProviders.push(cp);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
457
|
+
let vs = null;
|
|
458
|
+
if (!csOnly) {
|
|
459
|
+
vs = new PackageValueSetProvider(contentLoader);
|
|
460
|
+
await vs.initialize();
|
|
461
|
+
this.valueSetProviders.push(vs);
|
|
462
|
+
const cm = new PackageConceptMapProvider(contentLoader);
|
|
463
|
+
await cm.initialize();
|
|
464
|
+
this.conceptMapProviders.push(cm);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
this.#logPackage(contentLoader.id(), contentLoader.version(), csc, vs ? vs.valueSetMap.size : 0);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async loadUrl(packageManager, url, isDefault, mode, csOnly) {
|
|
471
|
+
const packagePath = await packageManager.fetchUrl(url);
|
|
472
|
+
if (mode === "fetch" || mode === "cs") {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const fullPackagePath = path.join(this.cacheFolder, packagePath);
|
|
476
|
+
const contentLoader = new PackageContentLoader(fullPackagePath);
|
|
477
|
+
await contentLoader.initialize();
|
|
478
|
+
|
|
479
|
+
this.contentSources.push(contentLoader.id()+"#"+contentLoader.version());
|
|
480
|
+
|
|
481
|
+
let cp = new ListCodeSystemProvider();
|
|
482
|
+
const resources = await contentLoader.getResourcesByType("CodeSystem");
|
|
483
|
+
let csc = 0;
|
|
484
|
+
for (const resource of resources) {
|
|
485
|
+
const cs = new CodeSystem(await contentLoader.loadFile(resource, contentLoader.fhirVersion()));
|
|
486
|
+
cs.sourcePackage = contentLoader.pid();
|
|
487
|
+
cp.codeSystems.set(cs.url, cs);
|
|
488
|
+
cp.codeSystems.set(cs.vurl, cs);
|
|
489
|
+
csc++;
|
|
490
|
+
}
|
|
491
|
+
this.codeSystemProviders.push(cp);
|
|
492
|
+
let vs = null;
|
|
493
|
+
if (!csOnly) {
|
|
494
|
+
vs = new PackageValueSetProvider(contentLoader);
|
|
495
|
+
await vs.initialize();
|
|
496
|
+
this.valueSetProviders.push(vs);
|
|
497
|
+
const cm = new PackageConceptMapProvider(contentLoader);
|
|
498
|
+
await cm.initialize();
|
|
499
|
+
this.conceptMapProviders.push(cm);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
this.#logPackage(contentLoader.id(), contentLoader.version(), csc, vs ? vs.valueSetMap.size : 0);
|
|
433
503
|
}
|
|
434
504
|
|
|
435
505
|
/**
|
|
@@ -442,6 +512,17 @@ class Library {
|
|
|
442
512
|
// Ensure folder exists
|
|
443
513
|
await this.ensureFolderExists(this.cacheFolder);
|
|
444
514
|
|
|
515
|
+
if (fileName.includes("|")) {
|
|
516
|
+
// in this case, we split it into two. if the first file exists, we go with that. Otherwise
|
|
517
|
+
// fallback to the second.
|
|
518
|
+
let firstName = fileName.substring(0, fileName.indexOf("|"));
|
|
519
|
+
fileName = fileName.substring(fileName.indexOf("|")+1);
|
|
520
|
+
|
|
521
|
+
const firstPath = path.join(this.cacheFolder, firstName);
|
|
522
|
+
if (await this.fileExists(firstPath)) {
|
|
523
|
+
return firstPath;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
445
526
|
const filePath = path.join(this.cacheFolder, fileName);
|
|
446
527
|
|
|
447
528
|
// Check if file already exists
|
|
@@ -581,7 +662,7 @@ class Library {
|
|
|
581
662
|
|
|
582
663
|
// Load FHIR packages - these will be added to valueSetProviders first
|
|
583
664
|
for (const packageId of fhirPackages) {
|
|
584
|
-
await provider.loadNpm(this.packageManager, this.cacheFolder, packageId, false, "npm");
|
|
665
|
+
await provider.loadNpm(this.packageManager, this.cacheFolder, packageId, false, "npm", false);
|
|
585
666
|
}
|
|
586
667
|
|
|
587
668
|
|
|
@@ -608,6 +689,12 @@ class Library {
|
|
|
608
689
|
provider.valueSetProviders.push(...this.valueSetProviders);
|
|
609
690
|
provider.conceptMapProviders.push(...this.conceptMapProviders);
|
|
610
691
|
|
|
692
|
+
// bind UCUM common value set
|
|
693
|
+
let ucum = provider.codeSystemFactories.get("http://unitsofmeasure.org");
|
|
694
|
+
let vs = await provider.findValueSet(null, "http://hl7.org/fhir/ValueSet/ucum-common", null);
|
|
695
|
+
if (ucum && vs) {
|
|
696
|
+
ucum.processCommonUnits(vs);
|
|
697
|
+
}
|
|
611
698
|
return provider;
|
|
612
699
|
}
|
|
613
700
|
|
package/tx/operation-context.js
CHANGED
|
@@ -49,7 +49,8 @@ class TimeTracker {
|
|
|
49
49
|
* Stores resources by cache-id for reuse across requests
|
|
50
50
|
*/
|
|
51
51
|
class ResourceCache {
|
|
52
|
-
constructor() {
|
|
52
|
+
constructor(stats) {
|
|
53
|
+
this.stats = stats;
|
|
53
54
|
this.cache = new Map();
|
|
54
55
|
this.locks = new Map(); // For thread-safety with async operations
|
|
55
56
|
}
|
|
@@ -135,12 +136,20 @@ class ResourceCache {
|
|
|
135
136
|
* @param {number} maxAge - Maximum age in milliseconds
|
|
136
137
|
*/
|
|
137
138
|
prune(maxAge = 3600000) { // Default 1 hour
|
|
139
|
+
if (this.stats) {
|
|
140
|
+
this.stats.task("Client Cache", `Pruning (${this.cache.size} entries)`);
|
|
141
|
+
}
|
|
142
|
+
let i = 0;
|
|
138
143
|
const now = Date.now();
|
|
139
144
|
for (const [cacheId, entry] of this.cache.entries()) {
|
|
140
145
|
if (now - entry.lastUsed > maxAge) {
|
|
146
|
+
i++;
|
|
141
147
|
this.cache.delete(cacheId);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
150
|
+
if (this.stats) {
|
|
151
|
+
this.stats.task("Client Cache", `Pruned ${i} of ${this.cache.size} entries`);
|
|
152
|
+
}
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
/**
|
|
@@ -184,7 +193,8 @@ class ExpansionCache {
|
|
|
184
193
|
* @param {number} maxSize - Maximum number of entries to keep (default 1000)
|
|
185
194
|
* @param {number} memoryThresholdMB - Heap usage in MB that triggers dropping oldest half (0 = disabled)
|
|
186
195
|
*/
|
|
187
|
-
constructor(maxSize = ExpansionCache.DEFAULT_MAX_SIZE, memoryThresholdMB = 0) {
|
|
196
|
+
constructor(stats, maxSize = ExpansionCache.DEFAULT_MAX_SIZE, memoryThresholdMB = 0) {
|
|
197
|
+
this.stats = stats;
|
|
188
198
|
this.cache = new Map();
|
|
189
199
|
this.maxSize = maxSize;
|
|
190
200
|
this.memoryThresholdBytes = memoryThresholdMB * 1024 * 1024;
|
|
@@ -322,13 +332,22 @@ class ExpansionCache {
|
|
|
322
332
|
* @returns {boolean} True if eviction was triggered
|
|
323
333
|
*/
|
|
324
334
|
checkMemoryPressure() {
|
|
335
|
+
if (this.stats) {
|
|
336
|
+
this.stats.task('Expansion Cache', 'Checking Memory Pressure');
|
|
337
|
+
}
|
|
325
338
|
if (this.memoryThresholdBytes <= 0) return false;
|
|
326
339
|
|
|
327
340
|
const heapUsed = process.memoryUsage().heapUsed;
|
|
328
341
|
if (heapUsed > this.memoryThresholdBytes) {
|
|
329
|
-
this.evictOldestHalf();
|
|
342
|
+
const i = this.evictOldestHalf();
|
|
343
|
+
if (this.stats) {
|
|
344
|
+
this.stats.task('Expansion Cache', `Checked Memory Pressure: evicted half (${i} entries)`);
|
|
345
|
+
}
|
|
330
346
|
return true;
|
|
331
347
|
}
|
|
348
|
+
if (this.stats) {
|
|
349
|
+
this.stats.task('Expansion Cache', `Checked Memory Pressure - OK (${this.cache.size} entries)`);
|
|
350
|
+
}
|
|
332
351
|
return false;
|
|
333
352
|
}
|
|
334
353
|
|
|
@@ -464,7 +483,7 @@ class OperationContext {
|
|
|
464
483
|
seeContext(vurl) {
|
|
465
484
|
if (this.contexts.includes(vurl)) {
|
|
466
485
|
const contextList = '[' + this.contexts.join(', ') + ']';
|
|
467
|
-
throw new Issue("error", "processing", null, 'VALUESET_CIRCULAR_REFERENCE', this.i18n.formatMessage(this.langs, 'VALUESET_CIRCULAR_REFERENCE', [vurl, contextList]),
|
|
486
|
+
throw new Issue("error", "processing", null, 'VALUESET_CIRCULAR_REFERENCE', this.i18n.formatMessage(this.langs, 'VALUESET_CIRCULAR_REFERENCE', [vurl, contextList]), "vs-invalid").handleAsOO(400);
|
|
468
487
|
}
|
|
469
488
|
this.contexts.push(vurl);
|
|
470
489
|
}
|