fhirsmith 0.18.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/ACCESSIBILITY.md +119 -0
  2. package/CHANGELOG.md +149 -0
  3. package/CONFIGURATION.md +305 -0
  4. package/LICENSE +29 -0
  5. package/README.md +29 -122
  6. package/cluster.js +1220 -0
  7. package/cluster.md +707 -0
  8. package/config-template.json +77 -8
  9. package/extension-tracker/extension-tracker-template.html +5 -8
  10. package/extension-tracker/extension-tracker.js +0 -2
  11. package/folder/folder.js +7 -6
  12. package/ips/analysis.js +560 -0
  13. package/ips/ips-template.html +5 -8
  14. package/ips/ips.js +132 -59
  15. package/ips/nationality.js +120 -0
  16. package/ips/pipeline.js +74 -21
  17. package/ips/presenter.js +1 -1
  18. package/ips/readme.md +132 -25
  19. package/ips/render.js +466 -77
  20. package/ips/store.js +79 -22
  21. package/ips/structure.js +247 -0
  22. package/ips/terminology.js +1 -1
  23. package/library/body-limit.js +22 -0
  24. package/library/cache-generation.js +15 -0
  25. package/library/cgroup-memory.js +133 -1
  26. package/library/cluster-counts.js +121 -0
  27. package/library/cluster-events.js +153 -0
  28. package/library/cluster-metrics.js +193 -0
  29. package/library/cluster-status.js +468 -0
  30. package/library/config-reader.js +96 -0
  31. package/library/daily-report.js +100 -2
  32. package/library/html-server.js +27 -1
  33. package/library/i18nsupport.js +23 -1
  34. package/library/keep-alive.js +18 -0
  35. package/library/languages.js +17 -3
  36. package/library/logger.js +31 -0
  37. package/library/memory-guard.js +428 -0
  38. package/library/native-memory.js +52 -0
  39. package/library/npm-audit.js +51 -25
  40. package/library/openapi-doc.js +7 -5
  41. package/library/package-manager.js +8 -0
  42. package/library/role.js +197 -0
  43. package/library/server-credentials.js +169 -0
  44. package/library/sqlite-utilities.js +59 -0
  45. package/library/ssrf-guard.js +100 -0
  46. package/library/static-response-stats.js +16 -0
  47. package/library/version-utilities.js +50 -0
  48. package/library/worker-count.js +192 -0
  49. package/nginx.md +30 -11
  50. package/npmprojector/indexer.js +18 -10
  51. package/package.json +11 -8
  52. package/packages/feed-check-page.js +117 -0
  53. package/packages/feed-check.js +279 -0
  54. package/packages/npm-check.js +57 -3
  55. package/packages/npm-signing.js +219 -0
  56. package/packages/openapi.yaml +207 -1
  57. package/packages/package-crawler.js +322 -41
  58. package/packages/packages-template.html +6 -8
  59. package/packages/packages.js +604 -57
  60. package/packages/readme.md +113 -1
  61. package/publisher/publisher-template.html +2 -5
  62. package/publisher/publisher.js +121 -110
  63. package/publisher/readme.md +75 -0
  64. package/registry/crawler.js +84 -108
  65. package/registry/documentation/configuration.md +34 -0
  66. package/registry/registry-template.html +5 -8
  67. package/registry/registry.js +5 -4
  68. package/root-bare-template.html +7 -4
  69. package/root-template.html +5 -8
  70. package/server.js +398 -198
  71. package/shl/shl.js +38 -37
  72. package/static/a11y/a11y-check.js +832 -0
  73. package/static/a11y/axe.min.js +12 -0
  74. package/static/assets/css/bootstrap-fhir.css +15 -15
  75. package/static/assets/images/flags/au.svg +18 -0
  76. package/static/assets/images/flags/unknown.svg +11 -0
  77. package/static/assets/images/flags/us.svg +63 -0
  78. package/static/assets/images/icon_audit.png +0 -0
  79. package/static/assets/images/icon_clinical.png +0 -0
  80. package/static/assets/images/icon_document.png +0 -0
  81. package/static/assets/images/icon_external.png +0 -0
  82. package/static/assets/images/icon_medication.png +0 -0
  83. package/static/assets/images/icon_organization.png +0 -0
  84. package/static/assets/images/icon_other.png +0 -0
  85. package/static/assets/images/icon_person.png +0 -0
  86. package/static/assets/images/icon_section.png +0 -0
  87. package/stats-db.js +45 -0
  88. package/stats.js +197 -10
  89. package/testing/testing-template.html +5 -8
  90. package/token/nginx_passport_setup.md +1 -1
  91. package/token/security_guide.md +1 -1
  92. package/token/token-template.html +2 -5
  93. package/token/token.js +40 -36
  94. package/translations/Messages.properties +19 -0
  95. package/tx/README.md +154 -4
  96. package/tx/closure/closure-store.js +126 -3
  97. package/tx/cm/cm-database.js +69 -57
  98. package/tx/cm/cm-package.js +29 -1
  99. package/tx/cs/cs-api.js +284 -19
  100. package/tx/cs/cs-api.md +41 -4
  101. package/tx/cs/cs-areacode.js +1 -1
  102. package/tx/cs/cs-country.js +17 -5
  103. package/tx/cs/cs-cpt.js +33 -16
  104. package/tx/cs/cs-cs.js +148 -14
  105. package/tx/cs/cs-currency.js +1 -1
  106. package/tx/cs/cs-icd11.js +21 -13
  107. package/tx/cs/cs-icf.js +22 -3
  108. package/tx/cs/cs-lang.js +2 -2
  109. package/tx/cs/cs-loinc.js +185 -66
  110. package/tx/cs/cs-mimetypes.js +12 -2
  111. package/tx/cs/cs-ndc.js +40 -25
  112. package/tx/cs/cs-omop.js +54 -29
  113. package/tx/cs/cs-rxnorm.js +300 -196
  114. package/tx/cs/cs-snomed.js +608 -64
  115. package/tx/cs/cs-ucum.js +14 -4
  116. package/tx/cs/cs-unii.js +18 -14
  117. package/tx/cs/cs-virtual.js +210 -0
  118. package/tx/ecosystem-resolver.js +413 -0
  119. package/tx/html/cluster-dash-metrics.liquid +26 -0
  120. package/tx/html/cluster-home-metrics.liquid +31 -0
  121. package/tx/html/cluster-metrics-script.liquid +251 -0
  122. package/tx/html/codesystem-operations.liquid +3 -3
  123. package/tx/html/ecl-panel.liquid +1 -1
  124. package/tx/html/external-metrics.liquid +17 -0
  125. package/tx/html/operations-form.liquid +17 -17
  126. package/tx/html/search-form.liquid +11 -11
  127. package/tx/html/status-line.liquid +58 -0
  128. package/tx/html/status-panel.liquid +140 -0
  129. package/tx/html/status-tasks.liquid +25 -0
  130. package/tx/html/tx-template.html +2 -5
  131. package/tx/importers/atc-to-fhir.js +17 -16
  132. package/tx/importers/export-icd11-codesystem.js +3 -2
  133. package/tx/importers/import-icd11.js +4 -3
  134. package/tx/importers/import-loinc.module.js +118 -56
  135. package/tx/importers/import-ndc.module.js +56 -44
  136. package/tx/importers/import-rxnorm.module.js +118 -79
  137. package/tx/importers/import-sct.module.js +57 -58
  138. package/tx/importers/import-unii.module.js +37 -36
  139. package/tx/importers/subset-loinc.module.js +49 -48
  140. package/tx/importers/subset-rxnorm.module.js +29 -28
  141. package/tx/importers/terminal.js +18 -0
  142. package/tx/importers/tx-import-base.js +13 -12
  143. package/tx/importers/tx-import-settings.js +5 -4
  144. package/tx/importers/tx-import.js +53 -52
  145. package/tx/library/canonical-resource.js +49 -0
  146. package/tx/library/codesystem.js +94 -5
  147. package/tx/library/designations.js +45 -2
  148. package/tx/library/extensions.js +13 -7
  149. package/tx/library/implicit-valuesets.js +66 -0
  150. package/tx/library/renderer.js +77 -21
  151. package/tx/library/sqlite-utilities.js +2 -0
  152. package/tx/library/ucum-parsers.js +11 -1
  153. package/tx/library/wait-for-cache.js +43 -0
  154. package/tx/library.js +321 -22
  155. package/tx/openapi-operations.js +6 -4
  156. package/tx/operation-context.js +460 -99
  157. package/tx/params.js +25 -0
  158. package/tx/problems.js +3 -5
  159. package/tx/provider.js +278 -42
  160. package/tx/resource-cache-sqlite.js +1050 -0
  161. package/tx/sct/ecl.js +47 -6
  162. package/tx/sct/editions.js +39 -0
  163. package/tx/sct/expressions.js +10 -1
  164. package/tx/sct/load-config.js +107 -0
  165. package/tx/sct/mmap.js +109 -0
  166. package/tx/sct/structures.js +166 -13
  167. package/tx/sct/text-index.js +5 -1
  168. package/tx/syndication/syndication-feed.js +2 -2
  169. package/tx/tx-html.js +44 -8
  170. package/tx/tx.fhir.org.yml +8 -8
  171. package/tx/tx.js +282 -80
  172. package/tx/usage-tracker.js +39 -11
  173. package/tx/vs/vs-api.js +19 -0
  174. package/tx/vs/vs-database.js +386 -62
  175. package/tx/vs/vs-package.js +27 -4
  176. package/tx/vs/vs-vsac.js +648 -100
  177. package/tx/workers/batch-validate.js +37 -10
  178. package/tx/workers/batch.js +6 -1
  179. package/tx/workers/cache-control.js +15 -4
  180. package/tx/workers/closure.js +156 -13
  181. package/tx/workers/compare.js +8 -5
  182. package/tx/workers/expand.js +1173 -289
  183. package/tx/workers/metadata.js +35 -0
  184. package/tx/workers/search.js +53 -1
  185. package/tx/workers/supplement-filters.js +145 -0
  186. package/tx/workers/translate.js +169 -4
  187. package/tx/workers/validate.js +223 -119
  188. package/tx/workers/worker.js +123 -31
  189. package/tx/xversion/xv-valueset.js +7 -4
  190. package/utilities/submit-test-report.js +3 -1
  191. package/utilities/tx-stress-test.js +482 -0
  192. package/vcl/readme.md +37 -0
  193. package/vcl/vcl-parser.js +15 -0
  194. package/xig/readme.md +52 -0
  195. package/xig/xig-template.html +5 -8
  196. package/xig/xig.js +238 -422
  197. package/changelog-template.md +0 -22
  198. package/registry/test-runner.js +0 -237
  199. package/release.sh +0 -80
  200. package/test-scripts/repro-re2-wasm-leak.js +0 -34
  201. package/tsconfig.eslint.json +0 -9
  202. package/tx/html/dash-metrics.liquid +0 -147
  203. package/tx/html/home-metrics.liquid +0 -225
@@ -174,28 +174,45 @@ class TerminologyWorker {
174
174
  if (!noVParams) {
175
175
  version = this.determineVersionBase(url, version, params);
176
176
  }
177
- let codeSystemResource = null;
178
- let provider = null;
179
- const supplements = this.loadSupplements(url, version, statedSupplements);
180
-
181
177
  // First check additional resources
182
- codeSystemResource = this.findInAdditionalResources(url, version, 'CodeSystem', !nullOk);
178
+ const codeSystemResource = this.findInAdditionalResources(url, version, 'CodeSystem', !nullOk);
183
179
 
184
- if (codeSystemResource) {
185
- if (codeSystemResource.content === 'complete') {
180
+ const build = async (supplements) => {
181
+ let provider = null;
182
+ if (codeSystemResource && codeSystemResource.content === 'complete') {
186
183
  // Create provider from complete code system
187
184
  provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
188
185
  }
189
- }
190
-
191
- // If no provider from additional resources, try main provider
192
- if (!provider) {
193
- provider = await this.provider.getCodeSystemProvider(this.opContext, url, version, supplements);
194
- }
186
+ // If no provider from additional resources, try main provider
187
+ if (!provider) {
188
+ provider = await this.provider.getCodeSystemProvider(this.opContext, url, version, supplements);
189
+ }
190
+ // If still no provider but we have a code system with allowed content mode
191
+ if (!provider && codeSystemResource && kinds.includes(codeSystemResource.content)) {
192
+ provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
193
+ }
194
+ // Track for lifecycle cleanup at end of request, at once: what follows can throw (a client
195
+ // that has gone, in loadSupplements). Providers built from a factory open a fresh sqlite
196
+ // connection that needs releasing; resource-built providers and others are no-ops.
197
+ this.opContext.registerProvider(provider);
198
+ return provider;
199
+ };
195
200
 
196
- // If still no provider but we have a code system with allowed content mode
197
- if (!provider && codeSystemResource && kinds.includes(codeSystemResource.content)) {
198
- provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
201
+ const supplements = this.loadSupplements(url, version, statedSupplements);
202
+ let provider = await build(supplements);
203
+
204
+ // The supplements were chosen for the version asked for. When no version was asked for (or
205
+ // only part of one), it's the version the provider turned out to be that decides which apply:
206
+ // a supplement pinned to another version of the code system (supplements = url|2.80 on
207
+ // LOINC 2.83) is not used - so a value set that requires it fails - and one pinned to this
208
+ // version is
209
+ const actual = provider ? provider.version() : null;
210
+ if (actual && actual !== version) {
211
+ const fitting = this.loadSupplements(url, actual, statedSupplements);
212
+ const key = (list) => list.map(cs => cs.vurl || cs.url).sort().join(' ');
213
+ if (key(fitting) !== key(supplements)) {
214
+ provider = await build(fitting);
215
+ }
199
216
  }
200
217
 
201
218
  if (!provider && !nullOk) {
@@ -210,14 +227,8 @@ class TerminologyWorker {
210
227
  }
211
228
  }
212
229
  }
213
- if (provider) {
214
- if (checkVer) {
215
- this.checkVersion(url, provider.version(), params, provider.versionAlgorithm(), op);
216
- }
217
- // Track for lifecycle cleanup at end of request. Providers built from
218
- // a factory open a fresh sqlite connection that needs releasing;
219
- // resource-built providers and others without close() are no-ops.
220
- this.opContext.registerProvider(provider);
230
+ if (provider && checkVer) {
231
+ this.checkVersion(url, provider.version(), params, provider.versionAlgorithm(), op);
221
232
  }
222
233
 
223
234
  return provider;
@@ -284,10 +295,52 @@ class TerminologyWorker {
284
295
  * @returns {Array<CodeSystem>} Supplement code systems
285
296
  */
286
297
  loadSupplements(url, version = '', statedSupplements) {
298
+ return this.#oneVersionEach(this.#findSupplements(url, version, statedSupplements), statedSupplements);
299
+ }
300
+
301
+ /**
302
+ * Only one version of a supplement applies. When more than one is in scope (the server has
303
+ * one, and the request passes another in tx-resource), the one the request names by version
304
+ * (useSupplement url|version, or a value set's supplement extension) wins; failing that, the
305
+ * request's own over the server's; failing that, the latest
306
+ *
307
+ * @param {CodeSystem[]} supplements - server ones first, then the request's
308
+ * @param {Set<string>} statedSupplements
309
+ * @returns {CodeSystem[]}
310
+ */
311
+ #oneVersionEach(supplements, statedSupplements) {
312
+ const byUrl = new Map();
313
+ for (const cs of supplements) {
314
+ if (!byUrl.has(cs.url)) {
315
+ byUrl.set(cs.url, []);
316
+ }
317
+ byUrl.get(cs.url).push(cs);
318
+ }
319
+ if ([...byUrl.values()].every(list => list.length === 1)) {
320
+ return supplements;
321
+ }
322
+ const result = [];
323
+ for (const list of byUrl.values()) {
324
+ if (list.length === 1) {
325
+ result.push(list[0]);
326
+ continue;
327
+ }
328
+ let chosen = statedSupplements ? list.find(cs => cs.version && statedSupplements.has(cs.vurl)) : null;
329
+ if (!chosen) {
330
+ chosen = list.find(cs => (this.additionalResources || []).includes(cs));
331
+ }
332
+ if (!chosen) {
333
+ chosen = list.reduce((a, b) => (VersionUtilities.compareVersionsGeneral(b.version, a.version) > 0 ? b : a));
334
+ }
335
+ result.push(chosen);
336
+ }
337
+ return result;
338
+ }
339
+
340
+ #findSupplements(url, version, statedSupplements) {
287
341
  const supplements = [];
288
342
 
289
343
  supplements.push(...this.provider.loadSupplements(url, version, statedSupplements));
290
- // todo: look in provider for supplements
291
344
 
292
345
  if (!this.additionalResources) {
293
346
  return supplements;
@@ -298,14 +351,16 @@ class TerminologyWorker {
298
351
  if (resource.resourceType === 'CodeSystem' && resource instanceof CodeSystem) {
299
352
  const cs = resource;
300
353
  // Check if this code system supplements the target URL
301
- const supplementsUrl = cs.jsonObj.supplements;
354
+ // (supplementsUrl rather than jsonObj.supplements: a resource from the shared
355
+ // resource cache answers it without being loaded - see resource-cache-sqlite.js)
356
+ const supplementsUrl = cs.supplementsUrl;
302
357
 
303
358
  if (!supplementsUrl) {
304
359
  continue;
305
360
  }
306
361
 
307
- // we consider either language packs or specified supplements
308
- if (!(cs.isLangPack() || (statedSupplements && (statedSupplements.has(cs.url) || statedSupplements.has(cs.vurl))))) {
362
+ // we consider either global supplements or specified supplements
363
+ if (!(cs.isGlobalSupplement() || (statedSupplements && (statedSupplements.has(cs.url) || statedSupplements.has(cs.vurl))))) {
309
364
  continue;
310
365
  }
311
366
  if (this.hasSupplement(cs, supplements)) {
@@ -325,7 +380,11 @@ class TerminologyWorker {
325
380
  } else {
326
381
  // Version specified, check if it matches the tail of supplements URL
327
382
  const supplementsVersion = supplementsUrl.substring(`${url}|`.length);
328
- if (supplementsVersion === version || VersionUtilities.versionMatches(supplementsVersion, version)) {
383
+ // not every code system's versions are semver (SNOMED CT's are URIs, ICF's are dates):
384
+ // versionMatches throws on those, so match by the version's own algorithm, as
385
+ // CodeSystem.isSupplementFor does for the server's own supplements
386
+ if (supplementsVersion === version || VersionUtilities.versionMatchesByAlgorithm(supplementsVersion, version,
387
+ VersionUtilities.guessVersionAlgorithmFromVersion(version))) {
329
388
  supplements.push(cs);
330
389
  }
331
390
  }
@@ -420,7 +479,12 @@ class TerminologyWorker {
420
479
 
421
480
  // Then try the provider
422
481
  if (this.provider && this.provider.findValueSet) {
423
- const vs = await this.provider.findValueSet(this.opContext, effectiveUrl, effectiveVersion);
482
+ // an implicit value set in the generic form (http://snomed.info/sct?fhir_vs...) is drawn
483
+ // from the code system version the request asks for, if it asks for one
484
+ const systemVersionFor = this.params && this.params.requestedSystemVersion
485
+ ? (system) => this.params.requestedSystemVersion(system)
486
+ : null;
487
+ const vs = await this.provider.findValueSet(this.opContext, effectiveUrl, effectiveVersion, systemVersionFor);
424
488
  if (vs) {
425
489
  return vs;
426
490
  }
@@ -740,7 +804,7 @@ class TerminologyWorker {
740
804
  if (!this.opContext.resourceCache.isSealed(cacheId)) {
741
805
  const toCache = txResources.concat(primaryResources);
742
806
  if (toCache.length > 0) {
743
- this.opContext.resourceCache.add(cacheId, toCache);
807
+ this.addToClientCache(cacheId, toCache);
744
808
  }
745
809
  this.additionalResources = this.opContext.resourceCache.get(cacheId);
746
810
  } else {
@@ -756,6 +820,34 @@ class TerminologyWorker {
756
820
  }
757
821
  }
758
822
 
823
+ /**
824
+ * Add resources to the client side cache cacheId. If that would take the cache over
825
+ * cacheMaxConcepts, the cache is dropped and this throws the 404 cache-id-unknown
826
+ * (CACHE_ID_DISCARDED_TOO_BIG), in the request's language: the operation is not performed.
827
+ * @param {string} cacheId
828
+ * @param {Object[]} resources
829
+ */
830
+ addToClientCache(cacheId, resources) {
831
+ try {
832
+ this.opContext.resourceCache.add(cacheId, resources);
833
+ } catch (e) {
834
+ throw this.translateCacheIssue(e);
835
+ }
836
+ }
837
+
838
+ /**
839
+ * The resource cache raises its limits (CACHE_ID_DISCARDED_TOO_BIG, CACHE_REQUEST_TOO_BIG,
840
+ * CACHE_START_TOO_BIG) in English, with their parameters: put the message in the request's language
841
+ * @param {Error} e
842
+ * @returns {Error} e
843
+ */
844
+ translateCacheIssue(e) {
845
+ if (e && e.msgId && e.msgParams) {
846
+ e.message = this.i18n.translate(e.msgId, this.opContext.langs, e.msgParams);
847
+ }
848
+ return e;
849
+ }
850
+
759
851
  /**
760
852
  * Wrap a raw resource in its appropriate class wrapper
761
853
  * @param {Object} resource - Raw resource object
@@ -160,12 +160,15 @@ function valueSetR5ToR4(r5Obj) {
160
160
  if (exp.property && exp.property.length > 0) {
161
161
  exp.extension = exp.extension || [];
162
162
  for (let prop of exp.property) {
163
+ // uri is 0..1: a property without one (a provider that doesn't declare its properties)
164
+ // must not produce an empty uri extension, which is not valid
165
+ const ext = [{ url: "code", valueCode: prop.code }];
166
+ if (prop.uri) {
167
+ ext.push({ url: "uri", valueUri: prop.uri });
168
+ }
163
169
  exp.extension.push({
164
170
  url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.property",
165
- extension: [
166
- { url: "code", valueCode: prop.code },
167
- { url: "uri", valueUri: prop.uri }
168
- ]
171
+ extension: ext
169
172
  });
170
173
  }
171
174
  delete exp.property;
@@ -120,7 +120,9 @@ async function main() {
120
120
  console.log(`Submitting ${path.basename(opts.file)} (${report.result}, ${Array.isArray(report.test) ? report.test.length : 0} tests) to ${url}`);
121
121
  let res;
122
122
  try {
123
- res = await fetch(url, { method: 'POST', headers, body });
123
+ // sent compact: the file is pretty-printed (about 1.7x the size), and the server limits
124
+ // the size of a report (npm.fhir.org: about 1.5 MB)
125
+ res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(report) });
124
126
  } catch (e) {
125
127
  console.error(`Could not reach ${url}: ${e.cause ? e.cause.message : e.message}`);
126
128
  process.exit(1);