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.
@@ -6,6 +6,7 @@ const { ValueSetDatabase } = require('./vs-database');
6
6
  const { checkContained } = require('../library/canonical-resource');
7
7
  const { VersionUtilities } = require('../../library/version-utilities');
8
8
  const {validateParameter} = require("../../library/utilities");
9
+ const { applyIdPrefix, stripIdPrefix } = require('../library/resource-ids');
9
10
 
10
11
  /**
11
12
  * Package-based ValueSet provider using shared database layer
@@ -78,6 +79,8 @@ class PackageValueSetProvider extends AbstractValueSetProvider {
78
79
  Object.defineProperty(vs, 'isCached', { value: true, enumerable: false, configurable: true, writable: true });
79
80
  }
80
81
  }
82
+ // nothing, unless the spaceId was assigned before the provider was initialized
83
+ this.#applyIdPrefix();
81
84
  this.initialized = true;
82
85
  }
83
86
 
@@ -212,7 +215,9 @@ class PackageValueSetProvider extends AbstractValueSetProvider {
212
215
  this._validateSearchParams(searchParams);
213
216
 
214
217
  if (this.USE_DATABASE_SEARCH) {
215
- return await this.database.search(this.spaceId, this.valueSetMap, searchParams, elements);
218
+ const results = await this.database.search(this.spaceId, this.valueSetMap, searchParams, elements);
219
+ return this.#excludedIds.size === 0 ? results
220
+ : results.filter(r => !this.#excludedIds.has(stripIdPrefix((r.jsonObj || r).id, this.spaceId)));
216
221
  } else {
217
222
  const matches = [];
218
223
  const seen = new Set(); // Track by URL to avoid duplicates from versioned keys
@@ -398,38 +403,19 @@ class PackageValueSetProvider extends AbstractValueSetProvider {
398
403
  }
399
404
 
400
405
  async fetchValueSetById(id) {
401
- return this.valueSetMap.get(id);
406
+ // the map is keyed by the value set's own id (and its url); the value set itself
407
+ // carries the prefixed id, which is how a url key that happens to match is excluded
408
+ const vs = this.valueSetMap.get(stripIdPrefix(id, this.spaceId));
409
+ return vs && vs.id === id ? vs : null;
402
410
  }
403
411
 
404
- // eslint-disable-next-line no-unused-vars
405
- assignIds(ids) {
406
- if (!this.spaceId) {
407
- return;
408
- }
409
-
410
- const prefix = this.spaceId + '-';
411
- const alreadyPrefixed = new Set();
412
-
413
- // Get all current entries - we'll iterate and modify
414
- const entries = Array.from(this.valueSetMap.entries());
415
-
416
- // eslint-disable-next-line no-unused-vars
417
- for (const [key, vs] of entries) {
418
- // Skip if we've already processed this ValueSet instance
419
- if (alreadyPrefixed.has(vs)) {
420
- continue;
421
- }
422
-
423
- // Update the id on the ValueSet itself
424
- if (vs.id && !vs.id.startsWith(prefix)) {
425
- const oldId = vs.id;
426
- vs.id = prefix + oldId;
427
-
428
- // Add to map under the new id as well
429
- this.valueSetMap.set(vs.id, vs);
430
- }
412
+ prefixIds() {
413
+ this.#applyIdPrefix();
414
+ }
431
415
 
432
- alreadyPrefixed.add(vs);
416
+ #applyIdPrefix() {
417
+ for (const vs of new Set(this.valueSetMap.values())) {
418
+ applyIdPrefix(vs, this.spaceId);
433
419
  }
434
420
  }
435
421
 
@@ -438,7 +424,38 @@ class PackageValueSetProvider extends AbstractValueSetProvider {
438
424
  }
439
425
 
440
426
  async listAllValueSets() {
441
- return await this.database.listAllValueSets();
427
+ const urls = await this.database.listAllValueSets();
428
+ return this.#excludedUrls.size === 0 ? urls : urls.filter(url => !this.#excludedUrls.has(url));
429
+ }
430
+
431
+ // the value sets left out by excludeUrls - by url, and by their own (unprefixed) id
432
+ #excludedUrls = new Set();
433
+ #excludedIds = new Set();
434
+
435
+ /**
436
+ * Leave out the value sets with any of these urls: they can't be fetched, and they aren't
437
+ * found by search. The FHIR core package's value sets are left out where hl7.terminology
438
+ * provides the same url (see Provider.applyTHOPrecedence). Must be called once the
439
+ * provider is initialized - the database is shared, so it isn't changed.
440
+ *
441
+ * @param {Set<string>} urls
442
+ */
443
+ excludeUrls(urls) {
444
+ const excluded = new Set();
445
+ for (const vs of this.valueSetMap.values()) {
446
+ if (vs && urls.has(vs.url)) {
447
+ excluded.add(vs);
448
+ }
449
+ }
450
+ for (const [key, vs] of [...this.valueSetMap]) {
451
+ if (excluded.has(vs)) {
452
+ this.valueSetMap.delete(key);
453
+ }
454
+ }
455
+ for (const vs of excluded) {
456
+ this.#excludedUrls.add(vs.url);
457
+ this.#excludedIds.add(stripIdPrefix(vs.id, this.spaceId) ?? vs.id);
458
+ }
442
459
  }
443
460
  }
444
461
 
package/tx/vs/vs-vsac.js CHANGED
@@ -6,6 +6,7 @@ const { ValueSetDatabase } = require('./vs-database');
6
6
  const { VersionUtilities } = require('../../library/version-utilities');
7
7
  const folders = require('../../library/folder-setup');
8
8
  const {debugLog} = require("../operation-context");
9
+ const { applyIdPrefix, stripIdPrefix } = require("../library/resource-ids");
9
10
 
10
11
  // Persisted watermark for the phase-1b _lastUpdated scan.
11
12
  const VSAC_LAST_UPDATED_KEY = 'vsac_last_updated_date';
@@ -422,6 +423,10 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
422
423
  }
423
424
  }
424
425
  }
426
+ // the value sets are served in this provider's id space
427
+ for (const vs of new Set(newMap.values())) {
428
+ applyIdPrefix(vs, this.spaceId);
429
+ }
425
430
  // Atomic replacement of the map
426
431
  this.valueSetMap = newMap;
427
432
  }
@@ -466,7 +471,10 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
466
471
  }
467
472
 
468
473
  async fetchValueSetById(id) {
469
- return await this.checkFullVS(this.valueSetMap.get(id));
474
+ // the map is keyed by the value set's own id (and its url); the value set itself
475
+ // carries the prefixed id, which is how a url key that happens to match is excluded
476
+ const vs = this.valueSetMap.get(stripIdPrefix(id, this.spaceId));
477
+ return vs && vs.id === id ? await this.checkFullVS(vs) : null;
470
478
  }
471
479
  /**
472
480
  * Searches for value sets based on criteria
@@ -556,9 +564,16 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
556
564
  await this.database.close();
557
565
  }
558
566
 
559
- // eslint-disable-next-line no-unused-vars
560
- assignIds(ids) {
561
- // nothing?
567
+ prefixIds() {
568
+ this.#applyIdPrefix();
569
+ }
570
+
571
+ // the map is replaced every time it's reloaded from the database (see _reloadMap), and that
572
+ // can happen before or after the spaceId is assigned - so both prefix whatever is there
573
+ #applyIdPrefix() {
574
+ for (const vs of new Set(this.valueSetMap.values())) {
575
+ applyIdPrefix(vs, this.spaceId);
576
+ }
562
577
  }
563
578
 
564
579
  // when we get a valueset from vsac via search, the compose is not
@@ -898,7 +913,7 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
898
913
  html += `<tr>`;
899
914
  html += `<td>${escape(timeOnly(row.ts))}</td>`;
900
915
  html += `<td><span style="color:${colour}">${label}</span></td>`;
901
- html += `<td>${escape(this.urlTail(row.url) || '')} v <a href="../ValueSet/${escape(this.urlTail(row.url) || '')}-${escape(row.version || '')}">${escape(row.version || '')}</a></td>`;
916
+ html += `<td>${escape(this.urlTail(row.url) || '')} v <a href="../ValueSet/${escape(this.spaceId ? this.spaceId + '-' : '')}${escape(this.urlTail(row.url) || '')}-${escape(row.version || '')}">${escape(row.version || '')}</a></td>`;
902
917
  html += `</tr>`;
903
918
  }
904
919
  }
@@ -202,7 +202,8 @@ class MetadataHandler {
202
202
  software: {
203
203
  name: this.config.softwareName || 'FHIR Terminology Server',
204
204
  version: serverVersion,
205
- releaseDate: this.config.releaseDate || now
205
+ // only when it's known - this used to fall back to the time of the request
206
+ ...(this.config.releaseDate ? { releaseDate: this.config.releaseDate } : {})
206
207
  },
207
208
  implementation: {
208
209
  description: `FHIR Server running at ${baseUrl}`,
@@ -61,21 +61,6 @@ class ReadWorker extends TerminologyWorker {
61
61
  }
62
62
  }
63
63
 
64
- /**
65
- * The resource as served at [type]/[id] must carry that id. Providers that
66
- * share the server with others prefix their ids with a space id ("1-foo") but
67
- * may keep the resource itself under its original id ("foo") - package
68
- * ConceptMaps do - and then the HTML tabs, which link to [type]/[resource.id],
69
- * point at a resource that doesn't exist (issue #256). Copy rather than
70
- * mutate: the provider's object is shared and keyed on its original id.
71
- */
72
- withId(json, id) {
73
- if (!json || json.id === id) {
74
- return json;
75
- }
76
- return { ...json, id: id };
77
- }
78
-
79
64
  /**
80
65
  * Handle CodeSystem read
81
66
  */
@@ -83,7 +68,7 @@ class ReadWorker extends TerminologyWorker {
83
68
  let cs = this.provider.getCodeSystemById(this.opContext, id);
84
69
  if (cs != null) {
85
70
  req.sourcePackage = cs.sourcePackage;
86
- return res.json(this.withId(cs.jsonObj, id));
71
+ return res.json(cs.jsonObj);
87
72
  }
88
73
 
89
74
  if (id.startsWith("x-")) {
@@ -142,7 +127,7 @@ class ReadWorker extends TerminologyWorker {
142
127
  const vs = await vsp.fetchValueSetById(id);
143
128
  if (vs) {
144
129
  req.sourcePackage = vs.sourcePackage;
145
- return res.json(this.withId(vs.jsonObj, id));
130
+ return res.json(vs.jsonObj);
146
131
  }
147
132
  }
148
133
 
@@ -158,7 +143,7 @@ class ReadWorker extends TerminologyWorker {
158
143
  const cm = await cmsp.fetchConceptMapById(id);
159
144
  if (cm) {
160
145
  req.sourcePackage = cm.sourcePackage;
161
- return res.json(this.withId(cm.jsonObj, id));
146
+ return res.json(cm.jsonObj);
162
147
  }
163
148
  }
164
149