fhirsmith 0.14.2 → 0.15.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 +23 -0
- package/library/changelog.js +57 -0
- package/package.json +1 -1
- package/registry/crawler.js +17 -0
- package/registry/fhirsmith-releases.js +3 -6
- package/registry/model.js +3 -0
- package/registry/registry.js +21 -3
- package/tx/README.md +20 -4
- package/tx/cm/cm-api.js +25 -10
- package/tx/cm/cm-database.js +0 -4
- package/tx/cm/cm-package.js +53 -14
- package/tx/cs/cs-provider-api.js +22 -7
- package/tx/cs/cs-provider-list.js +10 -9
- package/tx/library/resource-ids.js +107 -0
- package/tx/library.js +96 -24
- package/tx/ocl/cm-ocl.cjs +23 -15
- package/tx/ocl/cs-ocl.cjs +9 -10
- package/tx/ocl/vs-ocl.cjs +17 -17
- package/tx/provider.js +80 -13
- package/tx/tx.fhir.org.yml +16 -8
- package/tx/tx.js +7 -1
- package/tx/vs/vs-api.js +25 -10
- package/tx/vs/vs-database.js +0 -4
- package/tx/vs/vs-package.js +48 -31
- package/tx/vs/vs-vsac.js +20 -5
- package/tx/workers/metadata.js +2 -1
- package/tx/workers/read.js +3 -18
package/tx/library.js
CHANGED
|
@@ -39,6 +39,7 @@ const { OCLValueSetProvider } = require('./ocl/vs-ocl');
|
|
|
39
39
|
const { OCLConceptMapProvider } = require('./ocl/cm-ocl');
|
|
40
40
|
const {UriServicesFactory} = require("./cs/cs-uri");
|
|
41
41
|
const {debugLog} = require("./operation-context");
|
|
42
|
+
const { checkIdPrefix } = require("./library/resource-ids");
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* This class holds all the loaded content ready for processing
|
|
@@ -72,6 +73,17 @@ class Library {
|
|
|
72
73
|
packageSources = [];
|
|
73
74
|
externalSources = [];
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* {Map<Object, string>} the id prefix the library YAML gives for the source a provider was
|
|
78
|
+
* loaded from (npm:hl7.terminology=tho), by provider
|
|
79
|
+
*/
|
|
80
|
+
#sourcePrefixes = new Map();
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* {Set<string>} every id prefix the library YAML gives - autonumbered prefixes skip them
|
|
84
|
+
*/
|
|
85
|
+
#givenPrefixes = new Set();
|
|
86
|
+
|
|
75
87
|
baseUrl = null;
|
|
76
88
|
cacheFolder = null;
|
|
77
89
|
startTime = Date.now();
|
|
@@ -171,6 +183,8 @@ class Library {
|
|
|
171
183
|
|
|
172
184
|
this.log.info('Fetching Data from '+this.baseUrl);
|
|
173
185
|
|
|
186
|
+
this.#checkSourcePrefixes(config.sources);
|
|
187
|
+
|
|
174
188
|
for (const source of config.sources) {
|
|
175
189
|
try {
|
|
176
190
|
await this.processSource(source, this.packageManager, "fetch");
|
|
@@ -223,15 +237,20 @@ class Library {
|
|
|
223
237
|
this.assignIds();
|
|
224
238
|
}
|
|
225
239
|
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Parse a source entry from the library YAML: type[!]:details[=prefix]
|
|
242
|
+
*
|
|
243
|
+
* @param {string} source
|
|
244
|
+
* @returns {{type: string, details: string, isDefault: boolean, prefix: string|null}}
|
|
245
|
+
*/
|
|
246
|
+
#parseSource(source) {
|
|
228
247
|
const colonIndex = source.indexOf(':');
|
|
229
248
|
if (colonIndex === -1) {
|
|
230
249
|
throw new Error(`Invalid source format: ${source}`);
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
let type = source.substring(0, colonIndex);
|
|
234
|
-
|
|
253
|
+
let details = source.substring(colonIndex + 1);
|
|
235
254
|
|
|
236
255
|
// Handle special markers (like ! for default)
|
|
237
256
|
let isDefault = false;
|
|
@@ -240,6 +259,59 @@ class Library {
|
|
|
240
259
|
isDefault = true;
|
|
241
260
|
}
|
|
242
261
|
|
|
262
|
+
// the id prefix for the resources the source provides: everything after the last '='
|
|
263
|
+
let prefix = null;
|
|
264
|
+
const eqIndex = details.lastIndexOf('=');
|
|
265
|
+
if (eqIndex > -1) {
|
|
266
|
+
prefix = details.substring(eqIndex + 1);
|
|
267
|
+
details = details.substring(0, eqIndex);
|
|
268
|
+
const problem = checkIdPrefix(prefix);
|
|
269
|
+
if (problem) {
|
|
270
|
+
throw new Error(`Invalid source '${source}': ${problem}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { type, details, isDefault, prefix };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Check the id prefixes the library YAML gives before anything is loaded - each id space
|
|
278
|
+
* must belong to one source
|
|
279
|
+
*
|
|
280
|
+
* @param {string[]} sources
|
|
281
|
+
*/
|
|
282
|
+
#checkSourcePrefixes(sources) {
|
|
283
|
+
const seen = new Map();
|
|
284
|
+
for (const source of sources) {
|
|
285
|
+
const { prefix } = this.#parseSource(source);
|
|
286
|
+
if (prefix) {
|
|
287
|
+
if (seen.has(prefix)) {
|
|
288
|
+
throw new Error(`The id prefix '${prefix}' is given for both '${seen.get(prefix)}' and '${source}'`);
|
|
289
|
+
}
|
|
290
|
+
seen.set(prefix, source);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async processSource(source, packageManager, mode) {
|
|
296
|
+
const { type, details, isDefault, prefix } = this.#parseSource(source);
|
|
297
|
+
|
|
298
|
+
// the providers this source adds, so they can be given its id prefix
|
|
299
|
+
const csCount = this.codeSystemProviders.length;
|
|
300
|
+
const vsCount = this.valueSetProviders.length;
|
|
301
|
+
const cmCount = this.conceptMapProviders.length;
|
|
302
|
+
|
|
303
|
+
await this.#loadSource(type, details, isDefault, mode, packageManager);
|
|
304
|
+
|
|
305
|
+
if (prefix) {
|
|
306
|
+
this.#givenPrefixes.add(prefix);
|
|
307
|
+
for (const provider of [...this.codeSystemProviders.slice(csCount),
|
|
308
|
+
...this.valueSetProviders.slice(vsCount), ...this.conceptMapProviders.slice(cmCount)]) {
|
|
309
|
+
this.#sourcePrefixes.set(provider, prefix);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async #loadSource(type, details, isDefault, mode, packageManager) {
|
|
243
315
|
// Switch statement for different source types
|
|
244
316
|
switch (type) {
|
|
245
317
|
case 'internal':
|
|
@@ -1000,6 +1072,7 @@ class Library {
|
|
|
1000
1072
|
// Now add the existing value set providers after the FHIR core packages
|
|
1001
1073
|
provider.valueSetProviders.push(...this.valueSetProviders);
|
|
1002
1074
|
provider.conceptMapProviders.push(...this.conceptMapProviders);
|
|
1075
|
+
provider.applyTHOPrecedence();
|
|
1003
1076
|
|
|
1004
1077
|
// bind UCUM common value set
|
|
1005
1078
|
let ucum = provider.codeSystemFactories.get("http://unitsofmeasure.org");
|
|
@@ -1030,30 +1103,29 @@ class Library {
|
|
|
1030
1103
|
}
|
|
1031
1104
|
|
|
1032
1105
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
* ids
|
|
1036
|
-
*
|
|
1106
|
+
* Every loaded resource is served in the id space of the provider that loaded it: its id is
|
|
1107
|
+
* the provider's spaceId, '-', and the id it had in its source (see
|
|
1108
|
+
* tx/library/resource-ids.js). The spaceId is the prefix the library YAML gives for the
|
|
1109
|
+
* provider's source, or, if it gives none, a number - the next one, counting providers of
|
|
1110
|
+
* the same kind, that the YAML doesn't use as a prefix. Numbers depend on the order of the
|
|
1111
|
+
* sources, so a source whose ids should stay the same when the YAML changes needs a prefix.
|
|
1112
|
+
*
|
|
1113
|
+
* (Code system factories don't have ids - they are not available directly.)
|
|
1037
1114
|
*/
|
|
1038
1115
|
assignIds() {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
i = 0;
|
|
1052
|
-
for (const cmp of this.conceptMapProviders) {
|
|
1053
|
-
cmp.spaceId = String(++i);
|
|
1054
|
-
cmp.assignIds(ids);
|
|
1116
|
+
for (const providers of [this.codeSystemProviders, this.valueSetProviders, this.conceptMapProviders]) {
|
|
1117
|
+
let n = 0;
|
|
1118
|
+
for (const provider of providers) {
|
|
1119
|
+
let spaceId = this.#sourcePrefixes.get(provider);
|
|
1120
|
+
if (!spaceId) {
|
|
1121
|
+
do {
|
|
1122
|
+
n++;
|
|
1123
|
+
} while (this.#givenPrefixes.has(String(n)));
|
|
1124
|
+
spaceId = String(n);
|
|
1125
|
+
}
|
|
1126
|
+
provider.assignIds(spaceId);
|
|
1127
|
+
}
|
|
1055
1128
|
}
|
|
1056
|
-
|
|
1057
1129
|
}
|
|
1058
1130
|
|
|
1059
1131
|
async close() {
|
package/tx/ocl/cm-ocl.cjs
CHANGED
|
@@ -3,6 +3,7 @@ const { ConceptMap } = require('../library/conceptmap');
|
|
|
3
3
|
const { PAGE_SIZE } = require('./shared/constants');
|
|
4
4
|
const { createOclHttpClient } = require('./http/client');
|
|
5
5
|
const { fetchAllPages, extractItemsAndNext } = require('./http/pagination');
|
|
6
|
+
const { stripIdPrefix } = require('../library/resource-ids');
|
|
6
7
|
|
|
7
8
|
const DEFAULT_MAX_SEARCH_PAGES = 10;
|
|
8
9
|
|
|
@@ -24,7 +25,7 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
24
25
|
this._canonicalBySourceUrl = new Map();
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
prefixIds() {
|
|
28
29
|
if (!this.spaceId) {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
@@ -33,13 +34,18 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
33
34
|
this._idMap.clear();
|
|
34
35
|
|
|
35
36
|
for (const cm of unique) {
|
|
36
|
-
|
|
37
|
-
const nextId = `${this.spaceId}-${cm.id}`;
|
|
38
|
-
cm.id = nextId;
|
|
39
|
-
cm.jsonObj.id = nextId;
|
|
40
|
-
}
|
|
37
|
+
this.#prefixId(cm);
|
|
41
38
|
this._idMap.set(cm.id, cm);
|
|
42
|
-
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// concept maps are served in this provider's id space - including the ones that arrive
|
|
43
|
+
// after the spaceId is assigned
|
|
44
|
+
#prefixId(cm) {
|
|
45
|
+
if (this.spaceId && cm.id && !cm.id.startsWith(`${this.spaceId}-`)) {
|
|
46
|
+
const nextId = `${this.spaceId}-${cm.id}`;
|
|
47
|
+
cm.id = nextId;
|
|
48
|
+
cm.jsonObj.id = nextId;
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -56,7 +62,7 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
56
62
|
|
|
57
63
|
const mappingId = this.#extractMappingId(url);
|
|
58
64
|
if (mappingId) {
|
|
59
|
-
return await this
|
|
65
|
+
return await this.#fetchMappingById(mappingId);
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
try {
|
|
@@ -81,14 +87,15 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
81
87
|
if (this._idMap.has(id)) {
|
|
82
88
|
return this._idMap.get(id);
|
|
83
89
|
}
|
|
90
|
+
// only ids in this provider's space - the rest is the OCL mapping id
|
|
91
|
+
const rawId = stripIdPrefix(id, this.spaceId);
|
|
92
|
+
return rawId ? await this.#fetchMappingById(rawId) : null;
|
|
93
|
+
}
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (this._idMap.has(rawId)) {
|
|
91
|
-
return this._idMap.get(rawId);
|
|
95
|
+
async #fetchMappingById(rawId) {
|
|
96
|
+
const id = this.spaceId ? `${this.spaceId}-${rawId}` : rawId;
|
|
97
|
+
if (this._idMap.has(id)) {
|
|
98
|
+
return this._idMap.get(id);
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
try {
|
|
@@ -378,6 +385,7 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
378
385
|
}
|
|
379
386
|
|
|
380
387
|
#indexConceptMap(cm) {
|
|
388
|
+
this.#prefixId(cm);
|
|
381
389
|
this.conceptMapMap.set(cm.url, cm);
|
|
382
390
|
if (cm.version) {
|
|
383
391
|
this.conceptMapMap.set(`${cm.url}|${cm.version}`, cm);
|
package/tx/ocl/cs-ocl.cjs
CHANGED
|
@@ -92,15 +92,12 @@ class OCLCodeSystemProvider extends AbstractCodeSystemProvider {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
prefixIds() {
|
|
96
|
+
const codeSystems = [...new Set(this._idToCodeSystem.values())];
|
|
96
97
|
this._usedIds.clear();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
cs.jsonObj.id = cs.id;
|
|
101
|
-
}
|
|
102
|
-
ids.add(`CodeSystem/${cs.id}`);
|
|
103
|
-
this._usedIds.add(cs.id);
|
|
98
|
+
this._idToCodeSystem.clear();
|
|
99
|
+
for (const cs of codeSystems) {
|
|
100
|
+
this.#trackCodeSystemId(cs);
|
|
104
101
|
this._idToCodeSystem.set(cs.id, cs);
|
|
105
102
|
}
|
|
106
103
|
}
|
|
@@ -358,9 +355,11 @@ class OCLCodeSystemProvider extends AbstractCodeSystemProvider {
|
|
|
358
355
|
return;
|
|
359
356
|
}
|
|
360
357
|
|
|
361
|
-
|
|
358
|
+
// code systems are served in this provider's id space
|
|
359
|
+
const prefix = this.spaceId ? `${this.spaceId}-` : '';
|
|
360
|
+
if (!cs.id || !cs.id.startsWith(prefix) || this._usedIds.has(cs.id)) {
|
|
362
361
|
const raw = cs.id || cs.name || cs.url || 'ocl-cs';
|
|
363
|
-
const base =
|
|
362
|
+
const base = raw.startsWith(prefix) ? String(raw) : `${prefix}${raw}`;
|
|
364
363
|
let candidate = base;
|
|
365
364
|
let index = 1;
|
|
366
365
|
while (this._usedIds.has(candidate)) {
|
package/tx/ocl/vs-ocl.cjs
CHANGED
|
@@ -114,6 +114,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
114
114
|
id: cached.canonicalUrl
|
|
115
115
|
}, 'R5');
|
|
116
116
|
this.#applyCachedCompose(valueSetObj, paramsKey);
|
|
117
|
+
this.#prefixId(valueSetObj);
|
|
117
118
|
// Indexa o ValueSet restaurado para torná-lo disponível via fetchValueSet
|
|
118
119
|
this.valueSetMap.set(valueSetObj.url, valueSetObj);
|
|
119
120
|
if (valueSetObj.version) {
|
|
@@ -224,7 +225,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
|
|
228
|
+
prefixIds() {
|
|
228
229
|
if (!this.spaceId) {
|
|
229
230
|
return;
|
|
230
231
|
}
|
|
@@ -233,13 +234,18 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
233
234
|
this._idMap.clear();
|
|
234
235
|
|
|
235
236
|
for (const vs of unique) {
|
|
236
|
-
|
|
237
|
-
const nextId = `${this.spaceId}-${vs.id}`;
|
|
238
|
-
vs.id = nextId;
|
|
239
|
-
vs.jsonObj.id = nextId;
|
|
240
|
-
}
|
|
237
|
+
this.#prefixId(vs);
|
|
241
238
|
this._idMap.set(vs.id, vs);
|
|
242
|
-
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// value sets are served in this provider's id space - including the ones that arrive
|
|
243
|
+
// after the spaceId is assigned
|
|
244
|
+
#prefixId(vs) {
|
|
245
|
+
if (this.spaceId && vs.id && !vs.id.startsWith(`${this.spaceId}-`)) {
|
|
246
|
+
const nextId = `${this.spaceId}-${vs.id}`;
|
|
247
|
+
vs.id = nextId;
|
|
248
|
+
vs.jsonObj.id = nextId;
|
|
243
249
|
}
|
|
244
250
|
}
|
|
245
251
|
|
|
@@ -345,16 +351,9 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
345
351
|
}
|
|
346
352
|
|
|
347
353
|
#getLocalValueSetById(id) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (this.spaceId && id.startsWith(`${this.spaceId}-`)) {
|
|
353
|
-
const unprefixed = id.substring(this.spaceId.length + 1);
|
|
354
|
-
return this._idMap.get(id) || this._idMap.get(unprefixed) || this.valueSetMap.get(unprefixed) || null;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return this._idMap.get(id) || this.valueSetMap.get(id) || null;
|
|
354
|
+
// _idMap is keyed by the ids the value sets are served with - prefixed, once the
|
|
355
|
+
// spaceId is assigned
|
|
356
|
+
return this._idMap.get(id) || null;
|
|
358
357
|
}
|
|
359
358
|
|
|
360
359
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -391,6 +390,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
391
390
|
}
|
|
392
391
|
|
|
393
392
|
#indexValueSet(vs) {
|
|
393
|
+
this.#prefixId(vs);
|
|
394
394
|
const existing = this.valueSetMap.get(vs.url) || null;
|
|
395
395
|
|
|
396
396
|
// When fresh discovery metadata replaces a cold-cached entry, carry over
|
package/tx/provider.js
CHANGED
|
@@ -9,6 +9,7 @@ const {PackageContentLoader} = require("../library/package-manager");
|
|
|
9
9
|
const {PackageValueSetProvider} = require("./vs/vs-package");
|
|
10
10
|
const ValueSet = require("./library/valueset");
|
|
11
11
|
const {PackageConceptMapProvider} = require("./cm/cm-package");
|
|
12
|
+
const { applyIdPrefix, CORE_PREFIX } = require("./library/resource-ids");
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* This class holds what information is in context
|
|
@@ -153,6 +154,12 @@ class Provider {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Load the FHIR core package for this endpoint. Its resources are served in the 'core' id
|
|
159
|
+
* space (CodeSystem/core-v3-MaritalStatus): the core packages for different FHIR versions
|
|
160
|
+
* share ids, and so does other content (hl7.terminology), so no endpoint can serve them on
|
|
161
|
+
* their own ids (see tx/library/resource-ids.js)
|
|
162
|
+
*/
|
|
156
163
|
async loadNpm(packageManager, cacheFolder, details, isDefault, mode) {
|
|
157
164
|
// Parse packageId and version from details (e.g., "hl7.terminology.r4#6.0.2")
|
|
158
165
|
let packageId = details;
|
|
@@ -171,6 +178,9 @@ class Provider {
|
|
|
171
178
|
await contentLoader.initialize();
|
|
172
179
|
|
|
173
180
|
const resources = await contentLoader.getResourcesByType("CodeSystem");
|
|
181
|
+
let i = 0;
|
|
182
|
+
// the index has the urls as they are in the package; the version is taken out of the url
|
|
183
|
+
// when the code system is loaded (see normalizeVersionedCanonicals in package-manager.js)
|
|
174
184
|
for (const resource of resources) {
|
|
175
185
|
let cs;
|
|
176
186
|
try {
|
|
@@ -183,13 +193,24 @@ class Provider {
|
|
|
183
193
|
continue;
|
|
184
194
|
}
|
|
185
195
|
cs.sourcePackage = contentLoader.pid();
|
|
196
|
+
if (typeof resource.url === 'string' && resource.url.includes('|')) {
|
|
197
|
+
cs.urlHadVersion = true; // see addCodeSystem
|
|
198
|
+
}
|
|
199
|
+
// these objects belong to this endpoint, so they can be given ids here
|
|
200
|
+
i++;
|
|
201
|
+
if (!cs.id) {
|
|
202
|
+
cs.id = String(i);
|
|
203
|
+
}
|
|
204
|
+
applyIdPrefix(cs, CORE_PREFIX);
|
|
186
205
|
this.addCodeSystem(cs);
|
|
187
206
|
}
|
|
188
207
|
const vs = new PackageValueSetProvider(contentLoader);
|
|
189
208
|
await vs.initialize();
|
|
209
|
+
vs.assignIds(CORE_PREFIX);
|
|
190
210
|
this.valueSetProviders.push(vs);
|
|
191
211
|
const cm = new PackageConceptMapProvider(contentLoader);
|
|
192
212
|
await cm.initialize();
|
|
213
|
+
cm.assignIds(CORE_PREFIX);
|
|
193
214
|
this.conceptMapProviders.push(cm);
|
|
194
215
|
}
|
|
195
216
|
|
|
@@ -582,27 +603,38 @@ class Provider {
|
|
|
582
603
|
return pid != null && pid.startsWith("hl7.terminology");
|
|
583
604
|
}
|
|
584
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Add a code system to the ones this endpoint knows, by url and by url|version.
|
|
608
|
+
*
|
|
609
|
+
* THO precedence, as in CanonicalResourceManager.see() in the Java core: content that
|
|
610
|
+
* moved from the FHIR core packages to terminology.hl7.org has been QA'd there since, and
|
|
611
|
+
* its versions went backwards (coverage-selfpay is 4.0.1 in hl7.fhir.r4.core but 1.0.1 in
|
|
612
|
+
* hl7.terminology), so version precedence can't decide between them. Instead, a code system
|
|
613
|
+
* from an hl7.terminology package replaces every code system with the same url from a FHIR
|
|
614
|
+
* core package - none of the core versions stay available.
|
|
615
|
+
*
|
|
616
|
+
* Except: hl7.fhir.r4.core has the v2 tables 0006, 0360 and 0391 in two versions each,
|
|
617
|
+
* with the version in the url (http://terminology.hl7.org/CodeSystem/v2-0006|2.1 - see
|
|
618
|
+
* urlHadVersion in loadNpm). hl7.terminology doesn't publish those versions, so they stay
|
|
619
|
+
* available by url|version; THO's code system is the default for the url.
|
|
620
|
+
*/
|
|
585
621
|
addCodeSystem(cs) {
|
|
586
|
-
// Special case, mirroring CanonicalResourceManager.see() in the Java core:
|
|
587
|
-
// when content moved from the FHIR core packages to terminology.hl7.org,
|
|
588
|
-
// the versions went backwards (e.g. coverage-selfpay is 4.0.1 in
|
|
589
|
-
// hl7.fhir.r4.core (2019) but 1.0.1 in hl7.terminology (2024)), so plain
|
|
590
|
-
// version precedence would leave the stale core copy as the default. A
|
|
591
|
-
// resource from an hl7.terminology package displaces any same-URL
|
|
592
|
-
// resource that came from a FHIR core package, regardless of version.
|
|
593
622
|
if (Provider.#isTHOPackage(cs.sourcePackage)) {
|
|
594
623
|
for (const [key, t] of [...this.codeSystems]) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
// carry historical versions (e.g. the normalized R4 v2 tables 0006/0360/0391
|
|
598
|
-
// at 2.1/2.3.1/2.4/2.6/2.7) that hl7.terminology does not publish at all
|
|
599
|
-
if (key === t.url && t.url === cs.url && VersionUtilities.isCorePackage(t.sourcePackage)) {
|
|
624
|
+
if (t.url === cs.url && VersionUtilities.isCorePackage(t.sourcePackage)
|
|
625
|
+
&& (key === t.url || !t.urlHadVersion)) {
|
|
600
626
|
this.codeSystems.delete(key);
|
|
601
627
|
}
|
|
602
628
|
}
|
|
629
|
+
} else if (VersionUtilities.isCorePackage(cs.sourcePackage) && !cs.urlHadVersion) {
|
|
630
|
+
// THO's code system is always the default for its url
|
|
631
|
+
const t = this.codeSystems.get(cs.url);
|
|
632
|
+
if (t && Provider.#isTHOPackage(t.sourcePackage)) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
603
635
|
}
|
|
604
636
|
const existing = this.codeSystems.get(cs.url);
|
|
605
|
-
//
|
|
637
|
+
// a core-package resource never displaces a THO one as the default
|
|
606
638
|
const yieldsToTHO = existing && Provider.#isTHOPackage(existing.sourcePackage)
|
|
607
639
|
&& VersionUtilities.isCorePackage(cs.sourcePackage);
|
|
608
640
|
if (!existing || (!yieldsToTHO && cs.isMoreRecent(existing))) {
|
|
@@ -619,6 +651,41 @@ class Provider {
|
|
|
619
651
|
}
|
|
620
652
|
}
|
|
621
653
|
|
|
654
|
+
/**
|
|
655
|
+
* THO precedence for value sets and concept maps, as for code systems (see addCodeSystem):
|
|
656
|
+
* once the library's providers have been added after this endpoint's core package, the core
|
|
657
|
+
* package's value sets and concept maps whose urls an hl7.terminology package also provides
|
|
658
|
+
* are left out. (No core value set or concept map has the version in its url.)
|
|
659
|
+
*/
|
|
660
|
+
applyTHOPrecedence() {
|
|
661
|
+
const thoValueSets = new Set();
|
|
662
|
+
for (const vp of this.valueSetProviders) {
|
|
663
|
+
if (vp instanceof PackageValueSetProvider && Provider.#isTHOPackage(vp.sourcePackage())) {
|
|
664
|
+
for (const vs of vp.valueSetMap.values()) {
|
|
665
|
+
thoValueSets.add(vs.url);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const thoConceptMaps = new Set();
|
|
670
|
+
for (const cmp of this.conceptMapProviders) {
|
|
671
|
+
if (cmp instanceof PackageConceptMapProvider && Provider.#isTHOPackage(cmp.sourcePackage())) {
|
|
672
|
+
for (const cm of cmp.conceptMapMap.values()) {
|
|
673
|
+
thoConceptMaps.add(cm.url);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
for (const vp of this.valueSetProviders) {
|
|
678
|
+
if (vp instanceof PackageValueSetProvider && VersionUtilities.isCorePackage(vp.sourcePackage())) {
|
|
679
|
+
vp.excludeUrls(thoValueSets);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
for (const cmp of this.conceptMapProviders) {
|
|
683
|
+
if (cmp instanceof PackageConceptMapProvider && VersionUtilities.isCorePackage(cmp.sourcePackage())) {
|
|
684
|
+
cmp.excludeUrls(thoConceptMaps);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
622
689
|
deleteCodeSystem(cs) {
|
|
623
690
|
this.codeSystems.delete(cs.vurl);
|
|
624
691
|
this.codeSystems.delete(cs.url);
|
package/tx/tx.fhir.org.yml
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
base:
|
|
2
2
|
url: https://storage.googleapis.com/tx-fhir-org
|
|
3
3
|
|
|
4
|
+
# Each source is type[!]:details[=prefix] - see tx/README.md.
|
|
5
|
+
#
|
|
6
|
+
# The prefix (after the last '=') is the id space for the resources the source provides:
|
|
7
|
+
# with npm:hl7.terminology=tho, THO's v3-ActCode is CodeSystem/tho-v3-ActCode. Every
|
|
8
|
+
# resource is served with a prefixed id - never the id it has in its source, which other
|
|
9
|
+
# sources may share - and each endpoint's own FHIR core package uses 'core'. A source with
|
|
10
|
+
# no prefix gets a number, which changes when sources are added, removed or reordered, so
|
|
11
|
+
# any source whose resources people link to by id should have one.
|
|
4
12
|
sources:
|
|
5
13
|
- internal:lang
|
|
6
14
|
- internal:country
|
|
@@ -29,14 +37,14 @@ sources:
|
|
|
29
37
|
- snomed:sct_test_20250909b.cache
|
|
30
38
|
- cpt:CodeSystem-cpt.db|cpt-2023-fragment-0.1.db
|
|
31
39
|
- omop:omop_v20250227.db
|
|
32
|
-
- npm:hl7.terminology
|
|
33
|
-
- npm:fhir.tx.support
|
|
34
|
-
- npm:ihe.formatcode.fhir
|
|
35
|
-
- npm:fhir.dicom
|
|
36
|
-
- npm:hl7.fhir.us.core
|
|
37
|
-
- npm:us.cdc.phinvads
|
|
38
|
-
- npm:hl7.fhir.uv.sdc
|
|
39
|
-
- internal:vsac
|
|
40
|
+
- npm:hl7.terminology=tho
|
|
41
|
+
- npm:fhir.tx.support=tx
|
|
42
|
+
- npm:ihe.formatcode.fhir=ihe
|
|
43
|
+
- npm:fhir.dicom=dicom
|
|
44
|
+
- npm:hl7.fhir.us.core=us
|
|
45
|
+
- npm:us.cdc.phinvads=cdc
|
|
46
|
+
- npm:hl7.fhir.uv.sdc=sdc
|
|
47
|
+
- internal:vsac=vsac
|
|
40
48
|
- icd11:icd11-2026-01.db
|
|
41
49
|
|
|
42
50
|
ignored:
|
package/tx/tx.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
// with support for multiple endpoints at different FHIR versions.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
|
+
const { reportedReleaseDate } = require('../library/changelog');
|
|
9
|
+
|
|
10
|
+
// when this server process started (the release date of a snapshot build)
|
|
11
|
+
const SERVER_STARTED = new Date(Date.now() - process.uptime() * 1000);
|
|
8
12
|
const express = require('express');
|
|
9
13
|
const path = require('path');
|
|
10
14
|
const fs = require('fs');
|
|
@@ -223,7 +227,9 @@ class TXModule {
|
|
|
223
227
|
description: config.description || 'FHIR Terminology Server',
|
|
224
228
|
contactUrl: config.contactUrl,
|
|
225
229
|
contact: config.contact,
|
|
226
|
-
|
|
230
|
+
// when this version was released, from its CHANGELOG.md heading - or, for a snapshot,
|
|
231
|
+
// when the server started
|
|
232
|
+
releaseDate: config.releaseDate || reportedReleaseDate(packageJson.version, SERVER_STARTED),
|
|
227
233
|
host: config.host ? config.host : "localhost"
|
|
228
234
|
});
|
|
229
235
|
|
package/tx/vs/vs-api.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
const { assignIdSpace } = require('../library/resource-ids');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Abstract base class for value set providers
|
|
3
5
|
* Defines the interface that all value set providers must implement
|
|
4
6
|
*/
|
|
5
7
|
class AbstractValueSetProvider {
|
|
6
8
|
/**
|
|
7
|
-
* {
|
|
9
|
+
* {string} The prefix of this provider's id space (set by assignIds) - given for its source in the library
|
|
10
|
+
* YAML, autonumbered, or 'core' for an endpoint's core package. Every value set this
|
|
11
|
+
* provider provides has an id that starts with spaceId + '-' (see tx/library/resource-ids.js)
|
|
8
12
|
*/
|
|
9
13
|
spaceId;
|
|
10
14
|
|
|
@@ -12,14 +16,25 @@ class AbstractValueSetProvider {
|
|
|
12
16
|
throw new Error('code must be implemented by AbstractValueSetProvider subclass');
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
|
-
*
|
|
16
|
-
* in
|
|
19
|
+
* Put this provider in an id space: set spaceId, and prefix the ids of the value sets it
|
|
20
|
+
* provides with spaceId + '-'. A provider is put in one id space, once - asking for a
|
|
21
|
+
* different one later is an error (see assignIdSpace)
|
|
17
22
|
*
|
|
18
|
-
* @param {
|
|
23
|
+
* @param {string} spaceId
|
|
19
24
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
assignIds(spaceId) {
|
|
26
|
+
if (assignIdSpace(this, spaceId)) {
|
|
27
|
+
this.prefixIds();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Prefix the ids of the value sets this provider provides with spaceId + '-', once spaceId
|
|
33
|
+
* is set (by assignIds). Value sets the provider loads later must be given ids
|
|
34
|
+
* in the same space.
|
|
35
|
+
*/
|
|
36
|
+
prefixIds() {
|
|
37
|
+
throw new Error('prefixIds must be implemented by AbstractValueSetProvider subclass');
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
/**
|
|
@@ -35,11 +50,11 @@ class AbstractValueSetProvider {
|
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
|
38
|
-
* Fetches a specific value set by id.
|
|
39
|
-
*
|
|
53
|
+
* Fetches a specific value set by id. The id is in this provider's space (spaceId + '-' + the
|
|
54
|
+
* value set's own id); an id that isn't - including an unprefixed one - finds nothing
|
|
40
55
|
*
|
|
41
56
|
* @param {string} id - The id of the value set
|
|
42
|
-
* @returns {Promise<ValueSet>} The requested value set
|
|
57
|
+
* @returns {Promise<ValueSet>} The requested value set, or null
|
|
43
58
|
* @throws {Error} Must be implemented by subclasses
|
|
44
59
|
*/
|
|
45
60
|
// eslint-disable-next-line no-unused-vars
|
package/tx/vs/vs-database.js
CHANGED
|
@@ -1114,10 +1114,6 @@ class ValueSetDatabase {
|
|
|
1114
1114
|
return { query, params };
|
|
1115
1115
|
}
|
|
1116
1116
|
|
|
1117
|
-
// eslint-disable-next-line no-unused-vars
|
|
1118
|
-
assignIds(ids) {
|
|
1119
|
-
// nothing - we don't do any assigning.
|
|
1120
|
-
}
|
|
1121
1117
|
|
|
1122
1118
|
/**
|
|
1123
1119
|
* Get a list of all ValueSet URLs in the database
|