fhirsmith 0.18.1 → 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 (214) hide show
  1. package/ACCESSIBILITY.md +119 -0
  2. package/CHANGELOG.md +190 -9
  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 +331 -33
  15. package/ips/jurisdictions.js +65 -0
  16. package/ips/nationality.js +120 -0
  17. package/ips/pipeline.js +131 -17
  18. package/ips/presenter.js +1 -1
  19. package/ips/profile-editor.js +283 -4
  20. package/ips/readme.md +170 -20
  21. package/ips/render.js +769 -109
  22. package/ips/scripts.js +46 -0
  23. package/ips/store.js +123 -11
  24. package/ips/structure.js +247 -0
  25. package/ips/terminology.js +1 -1
  26. package/library/body-limit.js +22 -0
  27. package/library/cache-generation.js +15 -0
  28. package/library/cgroup-memory.js +133 -1
  29. package/library/cluster-counts.js +121 -0
  30. package/library/cluster-events.js +153 -0
  31. package/library/cluster-metrics.js +193 -0
  32. package/library/cluster-status.js +468 -0
  33. package/library/config-reader.js +96 -0
  34. package/library/daily-report.js +100 -2
  35. package/library/html-server.js +27 -1
  36. package/library/i18nsupport.js +23 -1
  37. package/library/keep-alive.js +18 -0
  38. package/library/languages.js +17 -3
  39. package/library/logger.js +31 -0
  40. package/library/memory-guard.js +428 -0
  41. package/library/native-memory.js +52 -0
  42. package/library/npm-audit.js +51 -25
  43. package/library/openapi-doc.js +7 -5
  44. package/library/package-manager.js +8 -0
  45. package/library/role.js +197 -0
  46. package/library/server-credentials.js +169 -0
  47. package/library/sqlite-utilities.js +59 -0
  48. package/library/ssrf-guard.js +100 -0
  49. package/library/static-response-stats.js +16 -0
  50. package/library/version-utilities.js +50 -0
  51. package/library/worker-count.js +192 -0
  52. package/nginx.md +30 -11
  53. package/npmprojector/indexer.js +18 -10
  54. package/package.json +11 -8
  55. package/packages/feed-check-page.js +117 -0
  56. package/packages/feed-check.js +279 -0
  57. package/packages/npm-check.js +57 -3
  58. package/packages/npm-signing.js +219 -0
  59. package/packages/openapi.yaml +207 -1
  60. package/packages/package-crawler.js +322 -41
  61. package/packages/packages-template.html +6 -8
  62. package/packages/packages.js +604 -57
  63. package/packages/readme.md +113 -1
  64. package/publisher/publisher-template.html +2 -5
  65. package/publisher/publisher.js +121 -110
  66. package/publisher/readme.md +75 -0
  67. package/registry/api.js +179 -1
  68. package/registry/crawler.js +86 -108
  69. package/registry/documentation/api.md +68 -2
  70. package/registry/documentation/configuration.md +34 -0
  71. package/registry/documentation/internals.md +4 -1
  72. package/registry/documentation/readme.md +3 -2
  73. package/registry/model.js +8 -0
  74. package/registry/openapi.yaml +84 -0
  75. package/registry/registry-template.html +5 -8
  76. package/registry/registry.js +58 -6
  77. package/root-bare-template.html +7 -4
  78. package/root-template.html +5 -8
  79. package/server.js +398 -198
  80. package/shl/shl.js +38 -37
  81. package/static/a11y/a11y-check.js +832 -0
  82. package/static/a11y/axe.min.js +12 -0
  83. package/static/assets/css/bootstrap-fhir.css +15 -15
  84. package/static/assets/images/flags/au.svg +18 -0
  85. package/static/assets/images/flags/unknown.svg +11 -0
  86. package/static/assets/images/flags/us.svg +63 -0
  87. package/static/assets/images/icon_audit.png +0 -0
  88. package/static/assets/images/icon_clinical.png +0 -0
  89. package/static/assets/images/icon_document.png +0 -0
  90. package/static/assets/images/icon_external.png +0 -0
  91. package/static/assets/images/icon_medication.png +0 -0
  92. package/static/assets/images/icon_organization.png +0 -0
  93. package/static/assets/images/icon_other.png +0 -0
  94. package/static/assets/images/icon_person.png +0 -0
  95. package/static/assets/images/icon_section.png +0 -0
  96. package/stats-db.js +45 -0
  97. package/stats.js +197 -10
  98. package/testing/testing-template.html +5 -8
  99. package/token/nginx_passport_setup.md +1 -1
  100. package/token/security_guide.md +1 -1
  101. package/token/token-template.html +2 -5
  102. package/token/token.js +40 -36
  103. package/translations/Messages.properties +19 -0
  104. package/tx/README.md +158 -4
  105. package/tx/closure/closure-store.js +126 -3
  106. package/tx/cm/cm-database.js +69 -57
  107. package/tx/cm/cm-package.js +50 -16
  108. package/tx/cs/cs-api.js +284 -19
  109. package/tx/cs/cs-api.md +41 -4
  110. package/tx/cs/cs-areacode.js +1 -1
  111. package/tx/cs/cs-country.js +17 -5
  112. package/tx/cs/cs-cpt.js +33 -16
  113. package/tx/cs/cs-cs.js +199 -24
  114. package/tx/cs/cs-currency.js +1 -1
  115. package/tx/cs/cs-icd11.js +21 -13
  116. package/tx/cs/cs-icf.js +22 -3
  117. package/tx/cs/cs-lang.js +2 -2
  118. package/tx/cs/cs-loinc.js +193 -67
  119. package/tx/cs/cs-mimetypes.js +12 -2
  120. package/tx/cs/cs-ndc.js +40 -25
  121. package/tx/cs/cs-omop.js +54 -29
  122. package/tx/cs/cs-rxnorm.js +300 -196
  123. package/tx/cs/cs-snomed.js +861 -95
  124. package/tx/cs/cs-ucum.js +14 -4
  125. package/tx/cs/cs-unii.js +18 -14
  126. package/tx/cs/cs-virtual.js +210 -0
  127. package/tx/ecosystem-resolver.js +413 -0
  128. package/tx/html/cluster-dash-metrics.liquid +26 -0
  129. package/tx/html/cluster-home-metrics.liquid +31 -0
  130. package/tx/html/cluster-metrics-script.liquid +251 -0
  131. package/tx/html/codesystem-operations.liquid +3 -3
  132. package/tx/html/ecl-panel.liquid +1 -1
  133. package/tx/html/external-metrics.liquid +17 -0
  134. package/tx/html/operations-form.liquid +17 -17
  135. package/tx/html/search-form.liquid +11 -11
  136. package/tx/html/status-line.liquid +58 -0
  137. package/tx/html/status-panel.liquid +140 -0
  138. package/tx/html/status-tasks.liquid +25 -0
  139. package/tx/html/tx-template.html +2 -5
  140. package/tx/importers/atc-to-fhir.js +17 -16
  141. package/tx/importers/export-icd11-codesystem.js +3 -2
  142. package/tx/importers/import-icd11.js +4 -3
  143. package/tx/importers/import-loinc.module.js +118 -56
  144. package/tx/importers/import-ndc.module.js +56 -44
  145. package/tx/importers/import-rxnorm.module.js +118 -79
  146. package/tx/importers/import-sct.module.js +125 -61
  147. package/tx/importers/import-unii.module.js +37 -36
  148. package/tx/importers/subset-loinc.module.js +49 -48
  149. package/tx/importers/subset-rxnorm.module.js +29 -28
  150. package/tx/importers/terminal.js +18 -0
  151. package/tx/importers/tx-import-base.js +13 -12
  152. package/tx/importers/tx-import-settings.js +5 -4
  153. package/tx/importers/tx-import.js +53 -52
  154. package/tx/library/canonical-resource.js +49 -0
  155. package/tx/library/codesystem.js +94 -5
  156. package/tx/library/designations.js +45 -2
  157. package/tx/library/extensions.js +13 -7
  158. package/tx/library/implicit-valuesets.js +66 -0
  159. package/tx/library/renderer.js +77 -21
  160. package/tx/library/sqlite-utilities.js +2 -0
  161. package/tx/library/tho-precedence.js +40 -0
  162. package/tx/library/ucum-parsers.js +11 -1
  163. package/tx/library/wait-for-cache.js +43 -0
  164. package/tx/library.js +321 -22
  165. package/tx/openapi-operations.js +6 -4
  166. package/tx/operation-context.js +460 -99
  167. package/tx/params.js +25 -0
  168. package/tx/problems.js +3 -5
  169. package/tx/provider.js +314 -67
  170. package/tx/resource-cache-sqlite.js +1050 -0
  171. package/tx/sct/documentation.md +45 -21
  172. package/tx/sct/ecl.js +81 -17
  173. package/tx/sct/editions.js +39 -0
  174. package/tx/sct/expressions.js +10 -1
  175. package/tx/sct/load-config.js +107 -0
  176. package/tx/sct/mmap.js +109 -0
  177. package/tx/sct/structures.js +175 -15
  178. package/tx/sct/text-index.js +5 -1
  179. package/tx/syndication/syndication-feed.js +2 -2
  180. package/tx/tx-html.js +44 -8
  181. package/tx/tx.fhir.org.yml +8 -8
  182. package/tx/tx.js +282 -80
  183. package/tx/usage-tracker.js +39 -11
  184. package/tx/vs/vs-api.js +19 -0
  185. package/tx/vs/vs-database.js +386 -62
  186. package/tx/vs/vs-package.js +44 -17
  187. package/tx/vs/vs-vsac.js +649 -101
  188. package/tx/workers/batch-validate.js +37 -10
  189. package/tx/workers/batch.js +6 -1
  190. package/tx/workers/cache-control.js +15 -4
  191. package/tx/workers/closure.js +156 -13
  192. package/tx/workers/compare.js +8 -5
  193. package/tx/workers/expand.js +1173 -289
  194. package/tx/workers/metadata.js +35 -0
  195. package/tx/workers/search.js +53 -1
  196. package/tx/workers/supplement-filters.js +145 -0
  197. package/tx/workers/translate.js +169 -4
  198. package/tx/workers/validate.js +223 -119
  199. package/tx/workers/worker.js +123 -31
  200. package/tx/xversion/xv-valueset.js +7 -4
  201. package/utilities/submit-test-report.js +3 -1
  202. package/utilities/tx-stress-test.js +482 -0
  203. package/vcl/readme.md +37 -0
  204. package/vcl/vcl-parser.js +15 -0
  205. package/xig/readme.md +52 -0
  206. package/xig/xig-template.html +5 -8
  207. package/xig/xig.js +238 -422
  208. package/changelog-template.md +0 -22
  209. package/registry/test-runner.js +0 -237
  210. package/release.sh +0 -80
  211. package/test-scripts/repro-re2-wasm-leak.js +0 -34
  212. package/tsconfig.eslint.json +0 -9
  213. package/tx/html/dash-metrics.liquid +0 -147
  214. package/tx/html/home-metrics.liquid +0 -225
@@ -23,14 +23,28 @@ const VALID_CONCEPTMAP_EQUIVALENCES = new Set([
23
23
  const VALID_CODESYSTEM_CONTENT = new Set([
24
24
  'not-present', 'example', 'fragment', 'complete', 'supplement'
25
25
  ]);
26
+ // How many of an include's listed codes displayValueSetInclude writes out before
27
+ // summarising the rest as a count.
28
+ const MAX_INCLUDE_CODES_SHOWN = 20;
29
+
26
30
  const VALID_PUBLICATION_STATUS = new Set(['draft', 'active', 'retired', 'unknown']);
27
31
 
32
+ /**
33
+ * How many of the codes a ValueSet.compose include (or exclude) lists are rendered by
34
+ * default. Past this, the list ends with a line saying how many more there are and, when
35
+ * the renderer has been given showAllCodesHref, a link to the page with all of them.
36
+ * VSAC's Medication Brand Name lists 33,341 codes in one include.
37
+ */
38
+ const MAX_RENDERED_INCLUDE_CODES = 500;
39
+
28
40
  /**
29
41
  * @typedef {Object} TerminologyLinkResolver
30
42
  * @property {function(OperationContext, string, string=): {description: string, link: string}|null} resolveURL
31
43
  * Given a URL and optional version, returns description and link, or null if not found
32
44
  * @property {function(OperationContext, string, string, string=): {display: string, link: string}|null} resolveCode
33
45
  * Given a URL, code, and optional version, returns display and link, or null if not found
46
+ * @property {function(OperationContext, string, string, string[]): Array<{description: string, link: string}|null>} [resolveCodes]
47
+ * resolveCode for many codes of one code system, in their order; used where present
34
48
  */
35
49
 
36
50
  class Renderer {
@@ -38,6 +52,10 @@ class Renderer {
38
52
  constructor(opContext, linkResolver = null) {
39
53
  this.opContext = opContext;
40
54
  this.linkResolver = linkResolver;
55
+ /** How many codes of an include to render; null renders them all. */
56
+ this.includeCodeLimit = MAX_RENDERED_INCLUDE_CODES;
57
+ /** Where "Show all" goes when an include is cut short; null for no link. */
58
+ this.showAllCodesHref = null;
41
59
  }
42
60
 
43
61
  displayCoded(...args) {
@@ -108,22 +126,21 @@ class Renderer {
108
126
  return '[' + result + ']';
109
127
  }
110
128
 
129
+ /**
130
+ * A one-line summary of a compose include/exclude, for the operation log and the
131
+ * $validate-code diagnostics. An include that lists many codes is summarised: the
132
+ * first MAX_INCLUDE_CODES_SHOWN codes, then how many more there are. (Written in
133
+ * full, a 33,000-code include is a ~250 KB string, built and kept for every
134
+ * validation against the value set.)
135
+ */
111
136
  displayValueSetInclude(inc) {
112
137
  let result;
113
138
  if (inc.system) {
114
139
  result = '(' + inc.system + ')';
115
140
  if (inc.concept) {
116
- result = result + '(';
117
- let first = true;
118
- for (const cc of inc.concept) {
119
- if (first) {
120
- first = false;
121
- } else {
122
- result = result + ',';
123
- }
124
- result = result + cc.code;
125
- }
126
- result = result + ')';
141
+ const shown = inc.concept.slice(0, MAX_INCLUDE_CODES_SHOWN).map(cc => cc.code).join(',');
142
+ const more = inc.concept.length - MAX_INCLUDE_CODES_SHOWN;
143
+ result = result + '(' + shown + (more > 0 ? ',... +' + more + ' more (' + inc.concept.length + ' codes)' : '') + ')';
127
144
  }
128
145
  if (inc.filter) {
129
146
  result = result + '(';
@@ -297,6 +314,7 @@ class Renderer {
297
314
  span.tx(value);
298
315
  let btn = span.button();
299
316
  btn.attr("class", "btn-copy");
317
+ btn.attr("aria-label", "Copy");
300
318
  btn.attr("data-clipboard-text", value);
301
319
  btn.attr("data-original-title", this.translate('GENERAL_COPY'));
302
320
  }
@@ -631,15 +649,15 @@ class Renderer {
631
649
  this._requireAllowed(vs.status, VALID_PUBLICATION_STATUS, 'ValueSet.status', 'invalid status');
632
650
 
633
651
  let div_ = div();
634
- div_.h2().tx("Properties");
652
+ div_.h4().tx("Properties");
635
653
  let tbl = div_.table("grid");
636
654
  await this.renderMetadataTable(vs, tbl);
637
655
  if (vs.compose) {
638
- div_.h2().tx("Logical Definition");
656
+ div_.h4().tx("Logical Definition");
639
657
  await this.renderCompose(vs, div_.table("grid"));
640
658
  }
641
659
  if (vs.expansion) {
642
- div_.h2().tx("Expansion");
660
+ div_.h4().tx("Expansion");
643
661
  await this.renderExpansion(div_, vs, tbl);
644
662
  }
645
663
 
@@ -726,8 +744,14 @@ class Renderer {
726
744
  const ul = li.ul();
727
745
  for (let c of inc.concept) {
728
746
  this._requireValidCode(c.code, 'ValueSet.compose.include.concept.code');
747
+ }
748
+ const limit = this.includeCodeLimit;
749
+ const shown = limit != null && inc.concept.length > limit ? inc.concept.slice(0, limit) : inc.concept;
750
+ const links = await this.resolveIncludeCodes(inc, shown);
751
+ for (let i = 0; i < shown.length; i++) {
752
+ const c = shown[i];
753
+ const link = links[i];
729
754
  const li = ul.li();
730
- const link = this.linkResolver ? await this.linkResolver.resolveCode(this.opContext, inc.system, inc.version, c.code) : null;
731
755
  if (link) {
732
756
  li.ah(link.link).tx(c.code);
733
757
  } else {
@@ -739,6 +763,15 @@ class Renderer {
739
763
  li.span("opaque: 0.5").tx(": "+link.description);
740
764
  }
741
765
  }
766
+ if (shown.length < inc.concept.length) {
767
+ const more = inc.concept.length - shown.length;
768
+ const li = ul.li();
769
+ li.i().tx(`\u2026 and ${more.toLocaleString('en-US')} more code${more === 1 ? '' : 's'}.`);
770
+ if (this.showAllCodesHref) {
771
+ li.tx(" ");
772
+ li.ah(this.showAllCodesHref).tx("Show all");
773
+ }
774
+ }
742
775
  } else {
743
776
  li.tx(this.translate('VALUE_SET_CODES_FROM')+" ");
744
777
  await this.renderLink(li,inc.system+(inc.version ? "|"+inc.version : ""));
@@ -777,6 +810,29 @@ class Renderer {
777
810
  }
778
811
  }
779
812
 
813
+ /**
814
+ * The link and description for each of the codes of an include that are shown, in one
815
+ * request to the link resolver for the include's code system where it can take one.
816
+ *
817
+ * @param {Object} inc the include
818
+ * @param {Object[]} concepts the include's concepts being rendered
819
+ * @returns {Promise<Array<{link: string, description: string}|null>>}
820
+ */
821
+ async resolveIncludeCodes(inc, concepts) {
822
+ if (!this.linkResolver || concepts.length === 0) {
823
+ return concepts.map(() => null);
824
+ }
825
+ const codes = concepts.map((c) => c.code);
826
+ if (this.linkResolver.resolveCodes) {
827
+ return await this.linkResolver.resolveCodes(this.opContext, inc.system, inc.version, codes);
828
+ }
829
+ const links = [];
830
+ for (const code of codes) {
831
+ links.push(await this.linkResolver.resolveCode(this.opContext, inc.system, inc.version, code));
832
+ }
833
+ return links;
834
+ }
835
+
780
836
  async generateProperties(x, cs) {
781
837
  if (!cs.property || cs.property.length === 0) {
782
838
  return false;
@@ -1389,7 +1445,7 @@ class Renderer {
1389
1445
  let div_ = div();
1390
1446
  let tbl;
1391
1447
  if (showProps) {
1392
- div_.h2().tx("Expansion Properties");
1448
+ div_.h3().tx("Expansion Properties");
1393
1449
  tbl = div_.table("grid");
1394
1450
  } else {
1395
1451
  tbl = div(); // dummy
@@ -1828,7 +1884,7 @@ class Renderer {
1828
1884
 
1829
1885
  // Formats
1830
1886
  if (cs.format && cs.format.length > 0) {
1831
- div_.h3().tx(this.translate('Formats') || 'Formats');
1887
+ div_.h4().tx(this.translate('Formats') || 'Formats');
1832
1888
  div_.para().tx(cs.format.join(', '));
1833
1889
  }
1834
1890
 
@@ -2098,7 +2154,7 @@ class Renderer {
2098
2154
 
2099
2155
  // Code Systems
2100
2156
  if (tc.codeSystem && tc.codeSystem.length > 0) {
2101
- div_.h3().tx('Code Systems');
2157
+ div_.h4().tx('Code Systems');
2102
2158
  const ul = div_.ul();
2103
2159
  for (const cs of tc.codeSystem) {
2104
2160
  if (cs.uri) {
@@ -2192,10 +2248,10 @@ class Renderer {
2192
2248
  let div_ = div();
2193
2249
 
2194
2250
  // Metadata table
2195
- div_.h3().tx("Properties");
2251
+ div_.h4().tx("Properties");
2196
2252
  await this.renderMetadataTable(cm, div_.table("grid"));
2197
2253
 
2198
- div_.h3("Mapping Details");
2254
+ div_.h4("Mapping Details");
2199
2255
  // Source/Target scope line (mirrors Java: CONC_MAP_FROM / CONC_MAP_TO)
2200
2256
  const p = div_.para();
2201
2257
  p.tx(this.translate('CONC_MAP_FROM') + " ");
@@ -2769,4 +2825,4 @@ class Renderer {
2769
2825
 
2770
2826
  }
2771
2827
 
2772
- module.exports = { Renderer, VALID_FILTER_OPS };
2828
+ module.exports = { Renderer, VALID_FILTER_OPS, MAX_RENDERED_INCLUDE_CODES, MAX_INCLUDE_CODES_SHOWN };
@@ -0,0 +1,2 @@
1
+ // moved to library/sqlite-utilities.js, so that every module can use it
2
+ module.exports = require('../../library/sqlite-utilities');
@@ -0,0 +1,40 @@
1
+ //
2
+ // THO precedence for value sets and concept maps (see Provider.applyTHOPrecedence).
3
+ //
4
+ // Where hl7.terminology provides a resource with the same url as one in the FHIR core
5
+ // package, THO's copy is the one used - versions were reset when content moved to THO, so
6
+ // version order can't decide. But the core copy is still what a reference that names its
7
+ // version (url|5.0.0) means, so it stays available to that reference, and only to it.
8
+ //
9
+
10
+ const { VersionUtilities } = require('../../library/version-utilities');
11
+
12
+ /**
13
+ * Whether a package provider should keep a map key for a resource that THO also provides.
14
+ * Only the keys that name a version are kept - url|version, and url|major.minor for a
15
+ * semver version - and only where THO doesn't provide that version itself. The bare url
16
+ * (the default for a reference with no version) and the resource's id are not kept.
17
+ *
18
+ * @param {string} key - the map key
19
+ * @param {{url: string, version: string}} resource - the core package's resource
20
+ * @param {Set<string>} thoVersions - the versions of the url that hl7.terminology provides
21
+ * @returns {boolean}
22
+ */
23
+ function keepForPinnedReference(key, resource, thoVersions) {
24
+ const version = resource.version;
25
+ if (!version) {
26
+ return false;
27
+ }
28
+ if (key === resource.url + '|' + version) {
29
+ return !thoVersions.has(version);
30
+ }
31
+ if (VersionUtilities.isSemVer(version)) {
32
+ const mm = VersionUtilities.getMajMin(version);
33
+ if (mm && key === resource.url + '|' + mm) {
34
+ return ![...thoVersions].some(v => v && VersionUtilities.isSemVer(v) && VersionUtilities.getMajMin(v) === mm);
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+
40
+ module.exports = { keepForPinnedReference };
@@ -185,6 +185,11 @@ class Lexer {
185
185
  }
186
186
 
187
187
  // Expression parser for UCUM unit expressions (port of ExpressionParser.java)
188
+ // A UCUM expression parses into a chain of Terms, one level per operator and per bracket, and
189
+ // the parser and every walker of the result recurse along that chain. Bound it so that a hostile
190
+ // unit is a clean error rather than a RangeError (stack overflow). Real units have a few terms.
191
+ const MAX_UCUM_TERMS = 100;
192
+
188
193
  class ExpressionParser {
189
194
  constructor(model) {
190
195
  this.model = model;
@@ -192,6 +197,7 @@ class ExpressionParser {
192
197
 
193
198
  parse(code) {
194
199
  const lexer = new Lexer(code);
200
+ this.terms = 0;
195
201
  const res = this._parseTerm(lexer, true);
196
202
 
197
203
  if (!lexer.finished()) {
@@ -202,6 +208,9 @@ class ExpressionParser {
202
208
  }
203
209
 
204
210
  _parseTerm(lexer, first) {
211
+ if (++this.terms > MAX_UCUM_TERMS) {
212
+ lexer.error(`The unit has more than ${MAX_UCUM_TERMS} terms and brackets`);
213
+ }
205
214
  const res = new Term();
206
215
 
207
216
  if (first && lexer.getType() === TokenType.NONE) {
@@ -1026,5 +1035,6 @@ module.exports = {
1026
1035
  UcumEssenceParser,
1027
1036
  Search,
1028
1037
  Converter,
1029
- UcumValidator
1038
+ UcumValidator,
1039
+ MAX_UCUM_TERMS
1030
1040
  };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A tx-worker (cluster.md) only reads the package caches the master builds. Until the master
3
+ * has finished one - normally it has, since the workers start once it is ready - the worker
4
+ * waits for it, and gives up with a clear error rather than building it itself.
5
+ *
6
+ * @module tx/library/wait-for-cache
7
+ */
8
+
9
+ // how long a worker waits for the master to finish a cache, and how often it looks
10
+ const DEFAULT_WAIT_MS = 5 * 60 * 1000;
11
+ const POLL_MS = 500;
12
+
13
+ /**
14
+ * Resolve once isReady() says true; reject if it hasn't within waitMs.
15
+ *
16
+ * @param {function(): Promise<boolean>} isReady
17
+ * @param {string} what - the cache, for the messages
18
+ * @param {Object} [options]
19
+ * @param {number} [options.waitMs] - default DEFAULT_WAIT_MS, or FHIRSMITH_CACHE_WAIT_MS
20
+ * @returns {Promise<void>}
21
+ */
22
+ async function waitForCache(isReady, what, options = {}) {
23
+ const waitMs = options.waitMs ?? (Number(process.env.FHIRSMITH_CACHE_WAIT_MS) || DEFAULT_WAIT_MS);
24
+ const start = Date.now();
25
+ let logged = false;
26
+ for (;;) {
27
+ if (await isReady()) {
28
+ return;
29
+ }
30
+ if (Date.now() - start >= waitMs) {
31
+ throw new Error(`${what} is not ready after ${Math.round(waitMs / 1000)} s. A tx-worker only reads it; ` +
32
+ 'the master builds it: start the master first, and the workers once it answers /health (cluster.md)');
33
+ }
34
+ if (!logged) {
35
+ logged = true;
36
+ require('../../library/logger').getInstance().child({ module: 'tx' })
37
+ .warn(`${what} is missing, incomplete or not yet migrated: waiting for the master to finish it`);
38
+ }
39
+ await new Promise(resolve => setTimeout(resolve, POLL_MS));
40
+ }
41
+ }
42
+
43
+ module.exports = { waitForCache };